Files
breakpilot-compliance/ai-compliance-sdk/internal/roadmap/store_roadmap.go
Sharang Parnerkar 3fb5b94905 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>
2026-04-19 09:49:31 +02:00

228 lines
5.6 KiB
Go

package roadmap
import (
"context"
"fmt"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
)
// Store handles roadmap data persistence
type Store struct {
pool *pgxpool.Pool
}
// NewStore creates a new roadmap store
func NewStore(pool *pgxpool.Pool) *Store {
return &Store{pool: pool}
}
// ============================================================================
// Roadmap CRUD Operations
// ============================================================================
// CreateRoadmap creates a new roadmap
func (s *Store) CreateRoadmap(ctx context.Context, r *Roadmap) error {
r.ID = uuid.New()
r.CreatedAt = time.Now().UTC()
r.UpdatedAt = r.CreatedAt
if r.Status == "" {
r.Status = "draft"
}
if r.Version == "" {
r.Version = "1.0"
}
_, err := s.pool.Exec(ctx, `
INSERT INTO roadmaps (
id, tenant_id, namespace_id, title, description, version,
assessment_id, portfolio_id, status,
total_items, completed_items, progress,
start_date, target_date,
created_at, updated_at, created_by
) VALUES (
$1, $2, $3, $4, $5, $6,
$7, $8, $9,
$10, $11, $12,
$13, $14,
$15, $16, $17
)
`,
r.ID, r.TenantID, r.NamespaceID, r.Title, r.Description, r.Version,
r.AssessmentID, r.PortfolioID, r.Status,
r.TotalItems, r.CompletedItems, r.Progress,
r.StartDate, r.TargetDate,
r.CreatedAt, r.UpdatedAt, r.CreatedBy,
)
return err
}
// GetRoadmap retrieves a roadmap by ID
func (s *Store) GetRoadmap(ctx context.Context, id uuid.UUID) (*Roadmap, error) {
var r Roadmap
err := s.pool.QueryRow(ctx, `
SELECT
id, tenant_id, namespace_id, title, description, version,
assessment_id, portfolio_id, status,
total_items, completed_items, progress,
start_date, target_date,
created_at, updated_at, created_by
FROM roadmaps WHERE id = $1
`, id).Scan(
&r.ID, &r.TenantID, &r.NamespaceID, &r.Title, &r.Description, &r.Version,
&r.AssessmentID, &r.PortfolioID, &r.Status,
&r.TotalItems, &r.CompletedItems, &r.Progress,
&r.StartDate, &r.TargetDate,
&r.CreatedAt, &r.UpdatedAt, &r.CreatedBy,
)
if err == pgx.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, err
}
return &r, nil
}
// ListRoadmaps lists roadmaps for a tenant with optional filters
func (s *Store) ListRoadmaps(ctx context.Context, tenantID uuid.UUID, filters *RoadmapFilters) ([]Roadmap, error) {
query := `
SELECT
id, tenant_id, namespace_id, title, description, version,
assessment_id, portfolio_id, status,
total_items, completed_items, progress,
start_date, target_date,
created_at, updated_at, created_by
FROM roadmaps WHERE tenant_id = $1`
args := []interface{}{tenantID}
argIdx := 2
if filters != nil {
if filters.Status != "" {
query += fmt.Sprintf(" AND status = $%d", argIdx)
args = append(args, filters.Status)
argIdx++
}
if filters.AssessmentID != nil {
query += fmt.Sprintf(" AND assessment_id = $%d", argIdx)
args = append(args, *filters.AssessmentID)
argIdx++
}
if filters.PortfolioID != nil {
query += fmt.Sprintf(" AND portfolio_id = $%d", argIdx)
args = append(args, *filters.PortfolioID)
argIdx++
}
}
query += " ORDER BY created_at DESC"
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 roadmaps []Roadmap
for rows.Next() {
var r Roadmap
err := rows.Scan(
&r.ID, &r.TenantID, &r.NamespaceID, &r.Title, &r.Description, &r.Version,
&r.AssessmentID, &r.PortfolioID, &r.Status,
&r.TotalItems, &r.CompletedItems, &r.Progress,
&r.StartDate, &r.TargetDate,
&r.CreatedAt, &r.UpdatedAt, &r.CreatedBy,
)
if err != nil {
return nil, err
}
roadmaps = append(roadmaps, r)
}
return roadmaps, nil
}
// UpdateRoadmap updates a roadmap
func (s *Store) UpdateRoadmap(ctx context.Context, r *Roadmap) error {
r.UpdatedAt = time.Now().UTC()
_, err := s.pool.Exec(ctx, `
UPDATE roadmaps SET
title = $2, description = $3, version = $4,
assessment_id = $5, portfolio_id = $6, status = $7,
total_items = $8, completed_items = $9, progress = $10,
start_date = $11, target_date = $12,
updated_at = $13
WHERE id = $1
`,
r.ID, r.Title, r.Description, r.Version,
r.AssessmentID, r.PortfolioID, r.Status,
r.TotalItems, r.CompletedItems, r.Progress,
r.StartDate, r.TargetDate,
r.UpdatedAt,
)
return err
}
// DeleteRoadmap deletes a roadmap and its items
func (s *Store) DeleteRoadmap(ctx context.Context, id uuid.UUID) error {
// Delete items first
_, err := s.pool.Exec(ctx, "DELETE FROM roadmap_items WHERE roadmap_id = $1", id)
if err != nil {
return err
}
// Delete roadmap
_, err = s.pool.Exec(ctx, "DELETE FROM roadmaps WHERE id = $1", id)
return err
}
// UpdateRoadmapProgress recalculates and updates roadmap progress
func (s *Store) UpdateRoadmapProgress(ctx context.Context, roadmapID uuid.UUID) error {
var total, completed int
s.pool.QueryRow(ctx,
"SELECT COUNT(*) FROM roadmap_items WHERE roadmap_id = $1",
roadmapID).Scan(&total)
s.pool.QueryRow(ctx,
"SELECT COUNT(*) FROM roadmap_items WHERE roadmap_id = $1 AND status = 'COMPLETED'",
roadmapID).Scan(&completed)
progress := 0
if total > 0 {
progress = (completed * 100) / total
}
_, err := s.pool.Exec(ctx, `
UPDATE roadmaps SET
total_items = $2,
completed_items = $3,
progress = $4,
updated_at = $5
WHERE id = $1
`, roadmapID, total, completed, progress, time.Now().UTC())
return err
}