Skip to content

Commit 6dbf590

Browse files
committed
refactor(store): wave B.15 — action_set repo (#273)
ActionSetRepo continues the action-chain migration started in #271: Get / List(filter) / Count(unassignedOnly) / ListMembers / ListInDefinition ActionSetMember rows are hydrated with action_name + action_type for display. Schedule stays as json.RawMessage at the boundary per the JSONB normalize plan. Call sites migrated (~20): - action_set_handler.go (full CRUD + actionSetToProto signature) - assignment_handler.go (source validation for ActionSet) - user_selection_handler.go (ActionSet source enrichment) - search/index.go (set reindex sweep + member-list enrichment + definition-set membership) Non-goals: - Definition / Execution / Assignment — subsequent sub-waves. - Write-side projector queries — stay on Queries() per pattern. Closes #273.
1 parent d53598e commit 6dbf590

10 files changed

Lines changed: 185 additions & 26 deletions

File tree

internal/api/action_dispatch.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ func (h *ActionHandler) DispatchDefinition(ctx context.Context, req *connect.Req
462462
return nil, err
463463
}
464464

465-
actionSets, err := h.store.Queries().ListActionSetsInDefinition(ctx, req.Msg.DefinitionId)
465+
actionSets, err := h.store.Repos().ActionSet.ListInDefinition(ctx, req.Msg.DefinitionId)
466466
if err != nil {
467467
return nil, apiErrorCtx(ctx, ErrInternal, connect.CodeInternal, "failed to get action sets in definition")
468468
}

internal/api/action_set_handler.go

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"github.com/manchtools/power-manage/server/internal/eventtypes"
1414
"github.com/manchtools/power-manage/server/internal/eventtypes/payloads"
1515
"github.com/manchtools/power-manage/server/internal/store"
16-
db "github.com/manchtools/power-manage/server/internal/store/generated"
1716
)
1817

1918
// ActionSetHandler handles action set RPCs.
@@ -68,7 +67,7 @@ func (h *ActionSetHandler) CreateActionSet(ctx context.Context, req *connect.Req
6867
return nil, err
6968
}
7069

71-
set, err := h.store.Queries().GetActionSetByID(ctx, id)
70+
set, err := h.store.Repos().ActionSet.Get(ctx, id)
7271
if err != nil {
7372
return nil, apiErrorCtx(ctx, ErrInternal, connect.CodeInternal, "failed to get action set")
7473
}
@@ -84,12 +83,12 @@ func (h *ActionSetHandler) GetActionSet(ctx context.Context, req *connect.Reques
8483
return nil, err
8584
}
8685

87-
set, err := h.store.Queries().GetActionSetByID(ctx, req.Msg.Id)
86+
set, err := h.store.Repos().ActionSet.Get(ctx, req.Msg.Id)
8887
if err != nil {
8988
return nil, handleGetError(ctx, err, ErrActionSetNotFound, "action set not found")
9089
}
9190

92-
members, err := h.store.Queries().ListActionSetMembers(ctx, req.Msg.Id)
91+
members, err := h.store.Repos().ActionSet.ListMembers(ctx, req.Msg.Id)
9392
if err != nil {
9493
return nil, apiErrorCtx(ctx, ErrInternal, connect.CodeInternal, "failed to get action set members")
9594
}
@@ -117,16 +116,12 @@ func (h *ActionSetHandler) ListActionSets(ctx context.Context, req *connect.Requ
117116
return nil, err
118117
}
119118

120-
sets, err := h.store.Queries().ListActionSets(ctx, db.ListActionSetsParams{
121-
Limit: pageSize,
122-
Offset: offset,
123-
UnassignedOnly: req.Msg.UnassignedOnly,
124-
})
119+
sets, err := h.store.Repos().ActionSet.List(ctx, store.ListActionSetsFilter{Limit: pageSize, Offset: offset, UnassignedOnly: req.Msg.UnassignedOnly})
125120
if err != nil {
126121
return nil, apiErrorCtx(ctx, ErrInternal, connect.CodeInternal, "failed to list action sets")
127122
}
128123

129-
count, err := h.store.Queries().CountActionSets(ctx, req.Msg.UnassignedOnly)
124+
count, err := h.store.Repos().ActionSet.Count(ctx, req.Msg.UnassignedOnly)
130125
if err != nil {
131126
return nil, apiErrorCtx(ctx, ErrInternal, connect.CodeInternal, "failed to count action sets")
132127
}
@@ -169,7 +164,7 @@ func (h *ActionSetHandler) RenameActionSet(ctx context.Context, req *connect.Req
169164
return nil, err
170165
}
171166

172-
set, err := h.store.Queries().GetActionSetByID(ctx, req.Msg.Id)
167+
set, err := h.store.Repos().ActionSet.Get(ctx, req.Msg.Id)
173168
if err != nil {
174169
return nil, handleGetError(ctx, err, ErrActionSetNotFound, "action set not found")
175170
}
@@ -209,7 +204,7 @@ func (h *ActionSetHandler) UpdateActionSetSchedule(ctx context.Context, req *con
209204
return nil, err
210205
}
211206

212-
set, err := h.store.Queries().GetActionSetByID(ctx, req.Msg.Id)
207+
set, err := h.store.Repos().ActionSet.Get(ctx, req.Msg.Id)
213208
if err != nil {
214209
return nil, handleGetError(ctx, err, ErrActionSetNotFound, "action set not found")
215210
}
@@ -243,7 +238,7 @@ func (h *ActionSetHandler) UpdateActionSetDescription(ctx context.Context, req *
243238
return nil, err
244239
}
245240

246-
set, err := h.store.Queries().GetActionSetByID(ctx, req.Msg.Id)
241+
set, err := h.store.Repos().ActionSet.Get(ctx, req.Msg.Id)
247242
if err != nil {
248243
return nil, handleGetError(ctx, err, ErrActionSetNotFound, "action set not found")
249244
}
@@ -295,7 +290,7 @@ func (h *ActionSetHandler) AddActionToSet(ctx context.Context, req *connect.Requ
295290
}
296291

297292
// Verify set exists
298-
_, err = h.store.Queries().GetActionSetByID(ctx, req.Msg.SetId)
293+
_, err = h.store.Repos().ActionSet.Get(ctx, req.Msg.SetId)
299294
if err != nil {
300295
return nil, handleGetError(ctx, err, ErrActionSetNotFound, "action set not found")
301296
}
@@ -320,7 +315,7 @@ func (h *ActionSetHandler) AddActionToSet(ctx context.Context, req *connect.Requ
320315
return nil, err
321316
}
322317

323-
set, err := h.store.Queries().GetActionSetByID(ctx, req.Msg.SetId)
318+
set, err := h.store.Repos().ActionSet.Get(ctx, req.Msg.SetId)
324319
if err != nil {
325320
return nil, apiErrorCtx(ctx, ErrInternal, connect.CodeInternal, "failed to get action set")
326321
}
@@ -360,7 +355,7 @@ func (h *ActionSetHandler) RemoveActionFromSet(ctx context.Context, req *connect
360355
return nil, err
361356
}
362357

363-
set, err := h.store.Queries().GetActionSetByID(ctx, req.Msg.SetId)
358+
set, err := h.store.Repos().ActionSet.Get(ctx, req.Msg.SetId)
364359
if err != nil {
365360
return nil, handleGetError(ctx, err, ErrActionSetNotFound, "action set not found")
366361
}
@@ -401,7 +396,7 @@ func (h *ActionSetHandler) ReorderActionInSet(ctx context.Context, req *connect.
401396
return nil, err
402397
}
403398

404-
set, err := h.store.Queries().GetActionSetByID(ctx, req.Msg.SetId)
399+
set, err := h.store.Repos().ActionSet.Get(ctx, req.Msg.SetId)
405400
if err != nil {
406401
return nil, handleGetError(ctx, err, ErrActionSetNotFound, "action set not found")
407402
}
@@ -411,7 +406,7 @@ func (h *ActionSetHandler) ReorderActionInSet(ctx context.Context, req *connect.
411406
}), nil
412407
}
413408

414-
func (h *ActionSetHandler) actionSetToProto(s db.ActionSetsProjection) *pm.ActionSet {
409+
func (h *ActionSetHandler) actionSetToProto(s store.ActionSet) *pm.ActionSet {
415410
set := &pm.ActionSet{
416411
Id: s.ID,
417412
Name: s.Name,

internal/api/assignment_handler.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func (h *AssignmentHandler) CreateAssignment(ctx context.Context, req *connect.R
5454
return nil, apiErrorCtx(ctx, ErrInternal, connect.CodeInternal, "failed to get action")
5555
}
5656
case pm.AssignmentSourceType_ASSIGNMENT_SOURCE_TYPE_ACTION_SET:
57-
_, err := h.store.Queries().GetActionSetByID(ctx, req.Msg.SourceId)
57+
_, err := h.store.Repos().ActionSet.Get(ctx, req.Msg.SourceId)
5858
if err != nil {
5959
if store.IsNotFound(err) {
6060
return nil, apiErrorCtx(ctx, ErrActionSetNotFound, connect.CodeNotFound, "action set not found")
@@ -342,7 +342,7 @@ func (h *AssignmentHandler) GetDeviceAssignments(ctx context.Context, req *conne
342342
protoActionSets := make([]*pm.ActionSet, 0, len(actionSetIDs))
343343
protoActionSetDetails := make([]*pm.GetActionSetResponse, 0, len(actionSetIDs))
344344
for id := range actionSetIDs {
345-
set, err := h.store.Queries().GetActionSetByID(ctx, id)
345+
set, err := h.store.Repos().ActionSet.Get(ctx, id)
346346
if err != nil {
347347
continue
348348
}
@@ -358,7 +358,7 @@ func (h *AssignmentHandler) GetDeviceAssignments(ctx context.Context, req *conne
358358
}
359359
protoActionSets = append(protoActionSets, protoSet)
360360

361-
members, err := h.store.Queries().ListActionSetMembers(ctx, id)
361+
members, err := h.store.Repos().ActionSet.ListMembers(ctx, id)
362362
if err != nil {
363363
continue
364364
}

internal/api/definition_handler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ func (h *DefinitionHandler) AddActionSetToDefinition(ctx context.Context, req *c
299299
}
300300

301301
// Verify action set exists
302-
actionSet, err := h.store.Queries().GetActionSetByID(ctx, req.Msg.ActionSetId)
302+
actionSet, err := h.store.Repos().ActionSet.Get(ctx, req.Msg.ActionSetId)
303303
if err != nil {
304304
return nil, handleGetError(ctx, err, ErrActionSetNotFound, "action set not found")
305305
}

internal/api/user_selection_handler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ func (h *UserSelectionHandler) ListAvailableActions(ctx context.Context, req *co
161161
logEnrichmentErr("GetActionByID", "action_id", asn.SourceID, err)
162162
}
163163
case "action_set":
164-
set, err := h.store.Queries().GetActionSetByID(ctx, asn.SourceID)
164+
set, err := h.store.Repos().ActionSet.Get(ctx, asn.SourceID)
165165
if err == nil {
166166
item.SourceName = set.Name
167167
item.SourceDescription = set.Description

internal/search/index.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ func (idx *Index) warmActionSets(ctx context.Context, actionNames map[string]str
440440
setMembers := map[string][]string{}
441441

442442
for {
443-
sets, err := idx.store.Queries().ListActionSets(ctx, db.ListActionSetsParams{
443+
sets, err := idx.store.Repos().ActionSet.List(ctx, store.ListActionSetsFilter{
444444
Limit: pageSize,
445445
Offset: offset,
446446
})
@@ -453,7 +453,7 @@ func (idx *Index) warmActionSets(ctx context.Context, actionNames map[string]str
453453

454454
for _, s := range sets {
455455
// Get members for this set.
456-
members, err := idx.store.Queries().ListActionSetMembers(ctx, s.ID)
456+
members, err := idx.store.Repos().ActionSet.ListMembers(ctx, s.ID)
457457
if err != nil {
458458
idx.logger.Warn("failed to list action set members", "set_id", s.ID, "error", err)
459459
continue

internal/store/action_set.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package store
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"time"
7+
)
8+
9+
// ActionSet is the action-set projection row — a named, ordered
10+
// collection of actions. Schedule stays as json.RawMessage at the
11+
// boundary per the JSONB normalize plan.
12+
type ActionSet struct {
13+
ID string
14+
Name string
15+
Description string
16+
MemberCount int32
17+
CreatedAt *time.Time
18+
CreatedBy string
19+
UpdatedAt *time.Time
20+
Schedule json.RawMessage
21+
}
22+
23+
// ActionSetMember is one row in the action-set membership join,
24+
// hydrated with the action's name + type for display.
25+
type ActionSetMember struct {
26+
SetID string
27+
ActionID string
28+
SortOrder int32
29+
AddedAt *time.Time
30+
ActionName string
31+
ActionType int32
32+
}
33+
34+
// ListActionSetsFilter pairs pagination with the "unassigned only"
35+
// flag (sets with no assignment targeting them).
36+
type ListActionSetsFilter struct {
37+
UnassignedOnly bool
38+
Limit int32
39+
Offset int32
40+
}
41+
42+
// ActionSetRepo reads action-set state. Writes flow through events.
43+
type ActionSetRepo interface {
44+
// Get returns a set by ID. Returns ErrNotFound when no set
45+
// with that ID exists.
46+
Get(ctx context.Context, id string) (ActionSet, error)
47+
48+
// List returns a page of sets.
49+
List(ctx context.Context, filter ListActionSetsFilter) ([]ActionSet, error)
50+
51+
// Count returns the total non-deleted set count, honouring
52+
// UnassignedOnly so pagination totals stay aligned.
53+
Count(ctx context.Context, unassignedOnly bool) (int64, error)
54+
55+
// ListMembers returns the action members of a set in their
56+
// configured sort order, hydrated with the action's display
57+
// name + type.
58+
ListMembers(ctx context.Context, setID string) ([]ActionSetMember, error)
59+
60+
// ListInDefinition returns every action set that belongs to a
61+
// given definition. Used by definition-detail handlers and the
62+
// definition reindex sweep.
63+
ListInDefinition(ctx context.Context, definitionID string) ([]ActionSet, error)
64+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package postgres
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
8+
"github.com/manchtools/power-manage/server/internal/store"
9+
"github.com/manchtools/power-manage/server/internal/store/generated"
10+
)
11+
12+
// ActionSet implements store.ActionSetRepo against
13+
// action_sets_projection + action_set_members_projection.
14+
type ActionSet struct {
15+
q *generated.Queries
16+
}
17+
18+
// NewActionSet returns an ActionSet repo bound to the given sqlc
19+
// handle.
20+
func NewActionSet(q *generated.Queries) *ActionSet {
21+
return &ActionSet{q: q}
22+
}
23+
24+
func (s *ActionSet) Get(ctx context.Context, id string) (store.ActionSet, error) {
25+
row, err := s.q.GetActionSetByID(ctx, id)
26+
if err != nil {
27+
return store.ActionSet{}, fmt.Errorf("action_set: get: %w", translateNotFound(err))
28+
}
29+
return actionSetFromRow(row), nil
30+
}
31+
32+
func (s *ActionSet) List(ctx context.Context, filter store.ListActionSetsFilter) ([]store.ActionSet, error) {
33+
rows, err := s.q.ListActionSets(ctx, generated.ListActionSetsParams{
34+
Limit: filter.Limit,
35+
Offset: filter.Offset,
36+
UnassignedOnly: filter.UnassignedOnly,
37+
})
38+
if err != nil {
39+
return nil, fmt.Errorf("action_set: list: %w", err)
40+
}
41+
out := make([]store.ActionSet, len(rows))
42+
for i, r := range rows {
43+
out[i] = actionSetFromRow(r)
44+
}
45+
return out, nil
46+
}
47+
48+
func (s *ActionSet) Count(ctx context.Context, unassignedOnly bool) (int64, error) {
49+
n, err := s.q.CountActionSets(ctx, unassignedOnly)
50+
if err != nil {
51+
return 0, fmt.Errorf("action_set: count: %w", translateNotFound(err))
52+
}
53+
return n, nil
54+
}
55+
56+
func (s *ActionSet) ListMembers(ctx context.Context, setID string) ([]store.ActionSetMember, error) {
57+
rows, err := s.q.ListActionSetMembers(ctx, setID)
58+
if err != nil {
59+
return nil, fmt.Errorf("action_set: list members: %w", err)
60+
}
61+
out := make([]store.ActionSetMember, len(rows))
62+
for i, r := range rows {
63+
out[i] = store.ActionSetMember{
64+
SetID: r.SetID,
65+
ActionID: r.ActionID,
66+
SortOrder: r.SortOrder,
67+
AddedAt: r.AddedAt,
68+
ActionName: r.ActionName,
69+
ActionType: r.ActionType,
70+
}
71+
}
72+
return out, nil
73+
}
74+
75+
func (s *ActionSet) ListInDefinition(ctx context.Context, definitionID string) ([]store.ActionSet, error) {
76+
rows, err := s.q.ListActionSetsInDefinition(ctx, definitionID)
77+
if err != nil {
78+
return nil, fmt.Errorf("action_set: list in definition: %w", err)
79+
}
80+
out := make([]store.ActionSet, len(rows))
81+
for i, r := range rows {
82+
out[i] = actionSetFromRow(r)
83+
}
84+
return out, nil
85+
}
86+
87+
func actionSetFromRow(r generated.ActionSetsProjection) store.ActionSet {
88+
return store.ActionSet{
89+
ID: r.ID,
90+
Name: r.Name,
91+
Description: r.Description,
92+
MemberCount: r.MemberCount,
93+
CreatedAt: r.CreatedAt,
94+
CreatedBy: r.CreatedBy,
95+
UpdatedAt: r.UpdatedAt,
96+
Schedule: json.RawMessage(r.Schedule),
97+
}
98+
}

internal/store/postgres/wire.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
func NewRepos(q *generated.Queries) *store.Repos {
1616
return &store.Repos{
1717
Action: NewAction(q),
18+
ActionSet: NewActionSet(q),
1819
AuthState: NewAuthState(q),
1920
Compliance: NewCompliance(q),
2021
Device: NewDevice(q),

internal/store/repos.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ package store
1414
// add a field here, populate it in postgres.NewRepos.
1515
type Repos struct {
1616
Action ActionRepo
17+
ActionSet ActionSetRepo
1718
AuthState AuthStateRepo
1819
Compliance ComplianceRepo
1920
Device DeviceRepo

0 commit comments

Comments
 (0)