refactor(go): split portfolio, workshop, training/models, roadmap stores
portfolio/store.go (818 LOC) → store_portfolio.go, store_items.go, store_metrics.go workshop/store.go (793 LOC) → store_sessions.go, store_participants.go, store_responses.go training/models.go (757 LOC) → models_enums.go, models_core.go, models_api.go, models_blocks.go roadmap/store.go (757 LOC) → store_roadmap.go, store_items.go, store_import.go All files under 350 LOC. Zero behavior changes, same package declarations. go vet passes on all five packages. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
337
ai-compliance-sdk/internal/roadmap/store_items.go
Normal file
337
ai-compliance-sdk/internal/roadmap/store_items.go
Normal file
@@ -0,0 +1,337 @@
|
||||
package roadmap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// RoadmapItem CRUD Operations
|
||||
// ============================================================================
|
||||
|
||||
// CreateItem creates a new roadmap item
|
||||
func (s *Store) CreateItem(ctx context.Context, item *RoadmapItem) error {
|
||||
item.ID = uuid.New()
|
||||
item.CreatedAt = time.Now().UTC()
|
||||
item.UpdatedAt = item.CreatedAt
|
||||
if item.Status == "" {
|
||||
item.Status = ItemStatusPlanned
|
||||
}
|
||||
if item.Priority == "" {
|
||||
item.Priority = ItemPriorityMedium
|
||||
}
|
||||
if item.Category == "" {
|
||||
item.Category = ItemCategoryTechnical
|
||||
}
|
||||
|
||||
dependsOn, _ := json.Marshal(item.DependsOn)
|
||||
blockedBy, _ := json.Marshal(item.BlockedBy)
|
||||
evidenceReq, _ := json.Marshal(item.EvidenceRequired)
|
||||
evidenceProv, _ := json.Marshal(item.EvidenceProvided)
|
||||
|
||||
_, err := s.pool.Exec(ctx, `
|
||||
INSERT INTO roadmap_items (
|
||||
id, roadmap_id, title, description, category, priority, status,
|
||||
control_id, regulation_ref, gap_id,
|
||||
effort_days, effort_hours, estimated_cost,
|
||||
assignee_id, assignee_name, department,
|
||||
planned_start, planned_end, actual_start, actual_end,
|
||||
depends_on, blocked_by,
|
||||
evidence_required, evidence_provided,
|
||||
notes, risk_notes,
|
||||
source_row, source_file, sort_order,
|
||||
created_at, updated_at
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7,
|
||||
$8, $9, $10,
|
||||
$11, $12, $13,
|
||||
$14, $15, $16,
|
||||
$17, $18, $19, $20,
|
||||
$21, $22,
|
||||
$23, $24,
|
||||
$25, $26,
|
||||
$27, $28, $29,
|
||||
$30, $31
|
||||
)
|
||||
`,
|
||||
item.ID, item.RoadmapID, item.Title, item.Description, string(item.Category), string(item.Priority), string(item.Status),
|
||||
item.ControlID, item.RegulationRef, item.GapID,
|
||||
item.EffortDays, item.EffortHours, item.EstimatedCost,
|
||||
item.AssigneeID, item.AssigneeName, item.Department,
|
||||
item.PlannedStart, item.PlannedEnd, item.ActualStart, item.ActualEnd,
|
||||
dependsOn, blockedBy,
|
||||
evidenceReq, evidenceProv,
|
||||
item.Notes, item.RiskNotes,
|
||||
item.SourceRow, item.SourceFile, item.SortOrder,
|
||||
item.CreatedAt, item.UpdatedAt,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// GetItem retrieves a roadmap item by ID
|
||||
func (s *Store) GetItem(ctx context.Context, id uuid.UUID) (*RoadmapItem, error) {
|
||||
var item RoadmapItem
|
||||
var category, priority, status string
|
||||
var dependsOn, blockedBy, evidenceReq, evidenceProv []byte
|
||||
|
||||
err := s.pool.QueryRow(ctx, `
|
||||
SELECT
|
||||
id, roadmap_id, title, description, category, priority, status,
|
||||
control_id, regulation_ref, gap_id,
|
||||
effort_days, effort_hours, estimated_cost,
|
||||
assignee_id, assignee_name, department,
|
||||
planned_start, planned_end, actual_start, actual_end,
|
||||
depends_on, blocked_by,
|
||||
evidence_required, evidence_provided,
|
||||
notes, risk_notes,
|
||||
source_row, source_file, sort_order,
|
||||
created_at, updated_at
|
||||
FROM roadmap_items WHERE id = $1
|
||||
`, id).Scan(
|
||||
&item.ID, &item.RoadmapID, &item.Title, &item.Description, &category, &priority, &status,
|
||||
&item.ControlID, &item.RegulationRef, &item.GapID,
|
||||
&item.EffortDays, &item.EffortHours, &item.EstimatedCost,
|
||||
&item.AssigneeID, &item.AssigneeName, &item.Department,
|
||||
&item.PlannedStart, &item.PlannedEnd, &item.ActualStart, &item.ActualEnd,
|
||||
&dependsOn, &blockedBy,
|
||||
&evidenceReq, &evidenceProv,
|
||||
&item.Notes, &item.RiskNotes,
|
||||
&item.SourceRow, &item.SourceFile, &item.SortOrder,
|
||||
&item.CreatedAt, &item.UpdatedAt,
|
||||
)
|
||||
|
||||
if err == pgx.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
item.Category = ItemCategory(category)
|
||||
item.Priority = ItemPriority(priority)
|
||||
item.Status = ItemStatus(status)
|
||||
json.Unmarshal(dependsOn, &item.DependsOn)
|
||||
json.Unmarshal(blockedBy, &item.BlockedBy)
|
||||
json.Unmarshal(evidenceReq, &item.EvidenceRequired)
|
||||
json.Unmarshal(evidenceProv, &item.EvidenceProvided)
|
||||
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
// ListItems lists items for a roadmap with optional filters
|
||||
func (s *Store) ListItems(ctx context.Context, roadmapID uuid.UUID, filters *RoadmapItemFilters) ([]RoadmapItem, error) {
|
||||
query := `
|
||||
SELECT
|
||||
id, roadmap_id, title, description, category, priority, status,
|
||||
control_id, regulation_ref, gap_id,
|
||||
effort_days, effort_hours, estimated_cost,
|
||||
assignee_id, assignee_name, department,
|
||||
planned_start, planned_end, actual_start, actual_end,
|
||||
depends_on, blocked_by,
|
||||
evidence_required, evidence_provided,
|
||||
notes, risk_notes,
|
||||
source_row, source_file, sort_order,
|
||||
created_at, updated_at
|
||||
FROM roadmap_items WHERE roadmap_id = $1`
|
||||
|
||||
args := []interface{}{roadmapID}
|
||||
argIdx := 2
|
||||
|
||||
if filters != nil {
|
||||
if filters.Status != "" {
|
||||
query += fmt.Sprintf(" AND status = $%d", argIdx)
|
||||
args = append(args, string(filters.Status))
|
||||
argIdx++
|
||||
}
|
||||
if filters.Priority != "" {
|
||||
query += fmt.Sprintf(" AND priority = $%d", argIdx)
|
||||
args = append(args, string(filters.Priority))
|
||||
argIdx++
|
||||
}
|
||||
if filters.Category != "" {
|
||||
query += fmt.Sprintf(" AND category = $%d", argIdx)
|
||||
args = append(args, string(filters.Category))
|
||||
argIdx++
|
||||
}
|
||||
if filters.AssigneeID != nil {
|
||||
query += fmt.Sprintf(" AND assignee_id = $%d", argIdx)
|
||||
args = append(args, *filters.AssigneeID)
|
||||
argIdx++
|
||||
}
|
||||
if filters.ControlID != "" {
|
||||
query += fmt.Sprintf(" AND control_id = $%d", argIdx)
|
||||
args = append(args, filters.ControlID)
|
||||
argIdx++
|
||||
}
|
||||
if filters.SearchQuery != "" {
|
||||
query += fmt.Sprintf(" AND (title ILIKE $%d OR description ILIKE $%d)", argIdx, argIdx)
|
||||
args = append(args, "%"+filters.SearchQuery+"%")
|
||||
argIdx++
|
||||
}
|
||||
}
|
||||
|
||||
query += " ORDER BY sort_order ASC, priority ASC, created_at ASC"
|
||||
|
||||
if filters != nil && filters.Limit > 0 {
|
||||
query += fmt.Sprintf(" LIMIT $%d", argIdx)
|
||||
args = append(args, filters.Limit)
|
||||
argIdx++
|
||||
|
||||
if filters.Offset > 0 {
|
||||
query += fmt.Sprintf(" OFFSET $%d", argIdx)
|
||||
args = append(args, filters.Offset)
|
||||
}
|
||||
}
|
||||
|
||||
rows, err := s.pool.Query(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var items []RoadmapItem
|
||||
for rows.Next() {
|
||||
var item RoadmapItem
|
||||
var category, priority, status string
|
||||
var dependsOn, blockedBy, evidenceReq, evidenceProv []byte
|
||||
|
||||
err := rows.Scan(
|
||||
&item.ID, &item.RoadmapID, &item.Title, &item.Description, &category, &priority, &status,
|
||||
&item.ControlID, &item.RegulationRef, &item.GapID,
|
||||
&item.EffortDays, &item.EffortHours, &item.EstimatedCost,
|
||||
&item.AssigneeID, &item.AssigneeName, &item.Department,
|
||||
&item.PlannedStart, &item.PlannedEnd, &item.ActualStart, &item.ActualEnd,
|
||||
&dependsOn, &blockedBy,
|
||||
&evidenceReq, &evidenceProv,
|
||||
&item.Notes, &item.RiskNotes,
|
||||
&item.SourceRow, &item.SourceFile, &item.SortOrder,
|
||||
&item.CreatedAt, &item.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
item.Category = ItemCategory(category)
|
||||
item.Priority = ItemPriority(priority)
|
||||
item.Status = ItemStatus(status)
|
||||
json.Unmarshal(dependsOn, &item.DependsOn)
|
||||
json.Unmarshal(blockedBy, &item.BlockedBy)
|
||||
json.Unmarshal(evidenceReq, &item.EvidenceRequired)
|
||||
json.Unmarshal(evidenceProv, &item.EvidenceProvided)
|
||||
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// UpdateItem updates a roadmap item
|
||||
func (s *Store) UpdateItem(ctx context.Context, item *RoadmapItem) error {
|
||||
item.UpdatedAt = time.Now().UTC()
|
||||
|
||||
dependsOn, _ := json.Marshal(item.DependsOn)
|
||||
blockedBy, _ := json.Marshal(item.BlockedBy)
|
||||
evidenceReq, _ := json.Marshal(item.EvidenceRequired)
|
||||
evidenceProv, _ := json.Marshal(item.EvidenceProvided)
|
||||
|
||||
_, err := s.pool.Exec(ctx, `
|
||||
UPDATE roadmap_items SET
|
||||
title = $2, description = $3, category = $4, priority = $5, status = $6,
|
||||
control_id = $7, regulation_ref = $8, gap_id = $9,
|
||||
effort_days = $10, effort_hours = $11, estimated_cost = $12,
|
||||
assignee_id = $13, assignee_name = $14, department = $15,
|
||||
planned_start = $16, planned_end = $17, actual_start = $18, actual_end = $19,
|
||||
depends_on = $20, blocked_by = $21,
|
||||
evidence_required = $22, evidence_provided = $23,
|
||||
notes = $24, risk_notes = $25,
|
||||
sort_order = $26, updated_at = $27
|
||||
WHERE id = $1
|
||||
`,
|
||||
item.ID, item.Title, item.Description, string(item.Category), string(item.Priority), string(item.Status),
|
||||
item.ControlID, item.RegulationRef, item.GapID,
|
||||
item.EffortDays, item.EffortHours, item.EstimatedCost,
|
||||
item.AssigneeID, item.AssigneeName, item.Department,
|
||||
item.PlannedStart, item.PlannedEnd, item.ActualStart, item.ActualEnd,
|
||||
dependsOn, blockedBy,
|
||||
evidenceReq, evidenceProv,
|
||||
item.Notes, item.RiskNotes,
|
||||
item.SortOrder, item.UpdatedAt,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteItem deletes a roadmap item
|
||||
func (s *Store) DeleteItem(ctx context.Context, id uuid.UUID) error {
|
||||
_, err := s.pool.Exec(ctx, "DELETE FROM roadmap_items WHERE id = $1", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// BulkCreateItems creates multiple items in a transaction
|
||||
func (s *Store) BulkCreateItems(ctx context.Context, items []RoadmapItem) error {
|
||||
tx, err := s.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
|
||||
for i := range items {
|
||||
item := &items[i]
|
||||
item.ID = uuid.New()
|
||||
item.CreatedAt = time.Now().UTC()
|
||||
item.UpdatedAt = item.CreatedAt
|
||||
|
||||
dependsOn, _ := json.Marshal(item.DependsOn)
|
||||
blockedBy, _ := json.Marshal(item.BlockedBy)
|
||||
evidenceReq, _ := json.Marshal(item.EvidenceRequired)
|
||||
evidenceProv, _ := json.Marshal(item.EvidenceProvided)
|
||||
|
||||
_, err := tx.Exec(ctx, `
|
||||
INSERT INTO roadmap_items (
|
||||
id, roadmap_id, title, description, category, priority, status,
|
||||
control_id, regulation_ref, gap_id,
|
||||
effort_days, effort_hours, estimated_cost,
|
||||
assignee_id, assignee_name, department,
|
||||
planned_start, planned_end, actual_start, actual_end,
|
||||
depends_on, blocked_by,
|
||||
evidence_required, evidence_provided,
|
||||
notes, risk_notes,
|
||||
source_row, source_file, sort_order,
|
||||
created_at, updated_at
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7,
|
||||
$8, $9, $10,
|
||||
$11, $12, $13,
|
||||
$14, $15, $16,
|
||||
$17, $18, $19, $20,
|
||||
$21, $22,
|
||||
$23, $24,
|
||||
$25, $26,
|
||||
$27, $28, $29,
|
||||
$30, $31
|
||||
)
|
||||
`,
|
||||
item.ID, item.RoadmapID, item.Title, item.Description, string(item.Category), string(item.Priority), string(item.Status),
|
||||
item.ControlID, item.RegulationRef, item.GapID,
|
||||
item.EffortDays, item.EffortHours, item.EstimatedCost,
|
||||
item.AssigneeID, item.AssigneeName, item.Department,
|
||||
item.PlannedStart, item.PlannedEnd, item.ActualStart, item.ActualEnd,
|
||||
dependsOn, blockedBy,
|
||||
evidenceReq, evidenceProv,
|
||||
item.Notes, item.RiskNotes,
|
||||
item.SourceRow, item.SourceFile, item.SortOrder,
|
||||
item.CreatedAt, item.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit(ctx)
|
||||
}
|
||||
Reference in New Issue
Block a user