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>
228 lines
5.6 KiB
Go
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
|
|
}
|