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:
238
ai-compliance-sdk/internal/portfolio/store_portfolio.go
Normal file
238
ai-compliance-sdk/internal/portfolio/store_portfolio.go
Normal file
@@ -0,0 +1,238 @@
|
||||
package portfolio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
// Store handles portfolio data persistence
|
||||
type Store struct {
|
||||
pool *pgxpool.Pool
|
||||
}
|
||||
|
||||
// NewStore creates a new portfolio store
|
||||
func NewStore(pool *pgxpool.Pool) *Store {
|
||||
return &Store{pool: pool}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Portfolio CRUD Operations
|
||||
// ============================================================================
|
||||
|
||||
// CreatePortfolio creates a new portfolio
|
||||
func (s *Store) CreatePortfolio(ctx context.Context, p *Portfolio) error {
|
||||
p.ID = uuid.New()
|
||||
p.CreatedAt = time.Now().UTC()
|
||||
p.UpdatedAt = p.CreatedAt
|
||||
if p.Status == "" {
|
||||
p.Status = PortfolioStatusDraft
|
||||
}
|
||||
|
||||
settings, _ := json.Marshal(p.Settings)
|
||||
|
||||
_, err := s.pool.Exec(ctx, `
|
||||
INSERT INTO portfolios (
|
||||
id, tenant_id, namespace_id,
|
||||
name, description, status,
|
||||
department, business_unit, owner, owner_email,
|
||||
total_assessments, total_roadmaps, total_workshops,
|
||||
avg_risk_score, high_risk_count, conditional_count, approved_count,
|
||||
compliance_score, settings,
|
||||
created_at, updated_at, created_by, approved_at, approved_by
|
||||
) 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
|
||||
)
|
||||
`,
|
||||
p.ID, p.TenantID, p.NamespaceID,
|
||||
p.Name, p.Description, string(p.Status),
|
||||
p.Department, p.BusinessUnit, p.Owner, p.OwnerEmail,
|
||||
p.TotalAssessments, p.TotalRoadmaps, p.TotalWorkshops,
|
||||
p.AvgRiskScore, p.HighRiskCount, p.ConditionalCount, p.ApprovedCount,
|
||||
p.ComplianceScore, settings,
|
||||
p.CreatedAt, p.UpdatedAt, p.CreatedBy, p.ApprovedAt, p.ApprovedBy,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// GetPortfolio retrieves a portfolio by ID
|
||||
func (s *Store) GetPortfolio(ctx context.Context, id uuid.UUID) (*Portfolio, error) {
|
||||
var p Portfolio
|
||||
var status string
|
||||
var settings []byte
|
||||
|
||||
err := s.pool.QueryRow(ctx, `
|
||||
SELECT
|
||||
id, tenant_id, namespace_id,
|
||||
name, description, status,
|
||||
department, business_unit, owner, owner_email,
|
||||
total_assessments, total_roadmaps, total_workshops,
|
||||
avg_risk_score, high_risk_count, conditional_count, approved_count,
|
||||
compliance_score, settings,
|
||||
created_at, updated_at, created_by, approved_at, approved_by
|
||||
FROM portfolios WHERE id = $1
|
||||
`, id).Scan(
|
||||
&p.ID, &p.TenantID, &p.NamespaceID,
|
||||
&p.Name, &p.Description, &status,
|
||||
&p.Department, &p.BusinessUnit, &p.Owner, &p.OwnerEmail,
|
||||
&p.TotalAssessments, &p.TotalRoadmaps, &p.TotalWorkshops,
|
||||
&p.AvgRiskScore, &p.HighRiskCount, &p.ConditionalCount, &p.ApprovedCount,
|
||||
&p.ComplianceScore, &settings,
|
||||
&p.CreatedAt, &p.UpdatedAt, &p.CreatedBy, &p.ApprovedAt, &p.ApprovedBy,
|
||||
)
|
||||
|
||||
if err == pgx.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.Status = PortfolioStatus(status)
|
||||
json.Unmarshal(settings, &p.Settings)
|
||||
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
// ListPortfolios lists portfolios for a tenant with optional filters
|
||||
func (s *Store) ListPortfolios(ctx context.Context, tenantID uuid.UUID, filters *PortfolioFilters) ([]Portfolio, error) {
|
||||
query := `
|
||||
SELECT
|
||||
id, tenant_id, namespace_id,
|
||||
name, description, status,
|
||||
department, business_unit, owner, owner_email,
|
||||
total_assessments, total_roadmaps, total_workshops,
|
||||
avg_risk_score, high_risk_count, conditional_count, approved_count,
|
||||
compliance_score, settings,
|
||||
created_at, updated_at, created_by, approved_at, approved_by
|
||||
FROM portfolios 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, string(filters.Status))
|
||||
argIdx++
|
||||
}
|
||||
if filters.Department != "" {
|
||||
query += fmt.Sprintf(" AND department = $%d", argIdx)
|
||||
args = append(args, filters.Department)
|
||||
argIdx++
|
||||
}
|
||||
if filters.BusinessUnit != "" {
|
||||
query += fmt.Sprintf(" AND business_unit = $%d", argIdx)
|
||||
args = append(args, filters.BusinessUnit)
|
||||
argIdx++
|
||||
}
|
||||
if filters.Owner != "" {
|
||||
query += fmt.Sprintf(" AND owner ILIKE $%d", argIdx)
|
||||
args = append(args, "%"+filters.Owner+"%")
|
||||
argIdx++
|
||||
}
|
||||
if filters.MinRiskScore != nil {
|
||||
query += fmt.Sprintf(" AND avg_risk_score >= $%d", argIdx)
|
||||
args = append(args, *filters.MinRiskScore)
|
||||
argIdx++
|
||||
}
|
||||
if filters.MaxRiskScore != nil {
|
||||
query += fmt.Sprintf(" AND avg_risk_score <= $%d", argIdx)
|
||||
args = append(args, *filters.MaxRiskScore)
|
||||
argIdx++
|
||||
}
|
||||
}
|
||||
|
||||
query += " ORDER BY updated_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 portfolios []Portfolio
|
||||
for rows.Next() {
|
||||
var p Portfolio
|
||||
var status string
|
||||
var settings []byte
|
||||
|
||||
err := rows.Scan(
|
||||
&p.ID, &p.TenantID, &p.NamespaceID,
|
||||
&p.Name, &p.Description, &status,
|
||||
&p.Department, &p.BusinessUnit, &p.Owner, &p.OwnerEmail,
|
||||
&p.TotalAssessments, &p.TotalRoadmaps, &p.TotalWorkshops,
|
||||
&p.AvgRiskScore, &p.HighRiskCount, &p.ConditionalCount, &p.ApprovedCount,
|
||||
&p.ComplianceScore, &settings,
|
||||
&p.CreatedAt, &p.UpdatedAt, &p.CreatedBy, &p.ApprovedAt, &p.ApprovedBy,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.Status = PortfolioStatus(status)
|
||||
json.Unmarshal(settings, &p.Settings)
|
||||
|
||||
portfolios = append(portfolios, p)
|
||||
}
|
||||
|
||||
return portfolios, nil
|
||||
}
|
||||
|
||||
// UpdatePortfolio updates a portfolio
|
||||
func (s *Store) UpdatePortfolio(ctx context.Context, p *Portfolio) error {
|
||||
p.UpdatedAt = time.Now().UTC()
|
||||
|
||||
settings, _ := json.Marshal(p.Settings)
|
||||
|
||||
_, err := s.pool.Exec(ctx, `
|
||||
UPDATE portfolios SET
|
||||
name = $2, description = $3, status = $4,
|
||||
department = $5, business_unit = $6, owner = $7, owner_email = $8,
|
||||
settings = $9,
|
||||
updated_at = $10, approved_at = $11, approved_by = $12
|
||||
WHERE id = $1
|
||||
`,
|
||||
p.ID, p.Name, p.Description, string(p.Status),
|
||||
p.Department, p.BusinessUnit, p.Owner, p.OwnerEmail,
|
||||
settings,
|
||||
p.UpdatedAt, p.ApprovedAt, p.ApprovedBy,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// DeletePortfolio deletes a portfolio and its items
|
||||
func (s *Store) DeletePortfolio(ctx context.Context, id uuid.UUID) error {
|
||||
// Delete items first
|
||||
_, err := s.pool.Exec(ctx, "DELETE FROM portfolio_items WHERE portfolio_id = $1", id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Delete portfolio
|
||||
_, err = s.pool.Exec(ctx, "DELETE FROM portfolios WHERE id = $1", id)
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user