refactor(go): split training/store, ucca/rules, ucca_handlers, document_export under 500 LOC
Each of the four oversized files (training/store.go 1569 LOC, ucca/rules.go 1231 LOC, ucca_handlers.go 1135 LOC, document_export.go 1101 LOC) is split by logical group into same-package files, all under the 500-line hard cap. Zero behavior changes, no renamed exported symbols. Also fixed pre-existing hazard_library split (missing functions and duplicate UUID keys from a prior session). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
235
ai-compliance-sdk/internal/training/store_modules.go
Normal file
235
ai-compliance-sdk/internal/training/store_modules.go
Normal file
@@ -0,0 +1,235 @@
|
||||
package training
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
// CreateModule creates a new training module
|
||||
func (s *Store) CreateModule(ctx context.Context, module *TrainingModule) error {
|
||||
module.ID = uuid.New()
|
||||
module.CreatedAt = time.Now().UTC()
|
||||
module.UpdatedAt = module.CreatedAt
|
||||
if !module.IsActive {
|
||||
module.IsActive = true
|
||||
}
|
||||
|
||||
isoControls, _ := json.Marshal(module.ISOControls)
|
||||
|
||||
_, err := s.pool.Exec(ctx, `
|
||||
INSERT INTO training_modules (
|
||||
id, tenant_id, academy_course_id, module_code, title, description,
|
||||
regulation_area, nis2_relevant, iso_controls, frequency_type,
|
||||
validity_days, risk_weight, content_type, duration_minutes,
|
||||
pass_threshold, is_active, 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
|
||||
)
|
||||
`,
|
||||
module.ID, module.TenantID, module.AcademyCourseID, module.ModuleCode, module.Title, module.Description,
|
||||
string(module.RegulationArea), module.NIS2Relevant, isoControls, string(module.FrequencyType),
|
||||
module.ValidityDays, module.RiskWeight, module.ContentType, module.DurationMinutes,
|
||||
module.PassThreshold, module.IsActive, module.SortOrder, module.CreatedAt, module.UpdatedAt,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// GetModule retrieves a module by ID
|
||||
func (s *Store) GetModule(ctx context.Context, id uuid.UUID) (*TrainingModule, error) {
|
||||
var module TrainingModule
|
||||
var regulationArea, frequencyType string
|
||||
var isoControls []byte
|
||||
|
||||
err := s.pool.QueryRow(ctx, `
|
||||
SELECT
|
||||
id, tenant_id, academy_course_id, module_code, title, description,
|
||||
regulation_area, nis2_relevant, iso_controls, frequency_type,
|
||||
validity_days, risk_weight, content_type, duration_minutes,
|
||||
pass_threshold, is_active, sort_order, created_at, updated_at
|
||||
FROM training_modules WHERE id = $1
|
||||
`, id).Scan(
|
||||
&module.ID, &module.TenantID, &module.AcademyCourseID, &module.ModuleCode, &module.Title, &module.Description,
|
||||
®ulationArea, &module.NIS2Relevant, &isoControls, &frequencyType,
|
||||
&module.ValidityDays, &module.RiskWeight, &module.ContentType, &module.DurationMinutes,
|
||||
&module.PassThreshold, &module.IsActive, &module.SortOrder, &module.CreatedAt, &module.UpdatedAt,
|
||||
)
|
||||
|
||||
if err == pgx.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
module.RegulationArea = RegulationArea(regulationArea)
|
||||
module.FrequencyType = FrequencyType(frequencyType)
|
||||
json.Unmarshal(isoControls, &module.ISOControls)
|
||||
if module.ISOControls == nil {
|
||||
module.ISOControls = []string{}
|
||||
}
|
||||
|
||||
return &module, nil
|
||||
}
|
||||
|
||||
// ListModules lists training modules for a tenant with optional filters
|
||||
func (s *Store) ListModules(ctx context.Context, tenantID uuid.UUID, filters *ModuleFilters) ([]TrainingModule, int, error) {
|
||||
countQuery := "SELECT COUNT(*) FROM training_modules WHERE tenant_id = $1"
|
||||
countArgs := []interface{}{tenantID}
|
||||
countArgIdx := 2
|
||||
|
||||
query := `
|
||||
SELECT
|
||||
id, tenant_id, academy_course_id, module_code, title, description,
|
||||
regulation_area, nis2_relevant, iso_controls, frequency_type,
|
||||
validity_days, risk_weight, content_type, duration_minutes,
|
||||
pass_threshold, is_active, sort_order, created_at, updated_at
|
||||
FROM training_modules WHERE tenant_id = $1`
|
||||
|
||||
args := []interface{}{tenantID}
|
||||
argIdx := 2
|
||||
|
||||
if filters != nil {
|
||||
if filters.RegulationArea != "" {
|
||||
query += fmt.Sprintf(" AND regulation_area = $%d", argIdx)
|
||||
args = append(args, string(filters.RegulationArea))
|
||||
argIdx++
|
||||
countQuery += fmt.Sprintf(" AND regulation_area = $%d", countArgIdx)
|
||||
countArgs = append(countArgs, string(filters.RegulationArea))
|
||||
countArgIdx++
|
||||
}
|
||||
if filters.FrequencyType != "" {
|
||||
query += fmt.Sprintf(" AND frequency_type = $%d", argIdx)
|
||||
args = append(args, string(filters.FrequencyType))
|
||||
argIdx++
|
||||
countQuery += fmt.Sprintf(" AND frequency_type = $%d", countArgIdx)
|
||||
countArgs = append(countArgs, string(filters.FrequencyType))
|
||||
countArgIdx++
|
||||
}
|
||||
if filters.IsActive != nil {
|
||||
query += fmt.Sprintf(" AND is_active = $%d", argIdx)
|
||||
args = append(args, *filters.IsActive)
|
||||
argIdx++
|
||||
countQuery += fmt.Sprintf(" AND is_active = $%d", countArgIdx)
|
||||
countArgs = append(countArgs, *filters.IsActive)
|
||||
countArgIdx++
|
||||
}
|
||||
if filters.NIS2Relevant != nil {
|
||||
query += fmt.Sprintf(" AND nis2_relevant = $%d", argIdx)
|
||||
args = append(args, *filters.NIS2Relevant)
|
||||
argIdx++
|
||||
countQuery += fmt.Sprintf(" AND nis2_relevant = $%d", countArgIdx)
|
||||
countArgs = append(countArgs, *filters.NIS2Relevant)
|
||||
countArgIdx++
|
||||
}
|
||||
if filters.Search != "" {
|
||||
query += fmt.Sprintf(" AND (title ILIKE $%d OR description ILIKE $%d OR module_code ILIKE $%d)", argIdx, argIdx, argIdx)
|
||||
args = append(args, "%"+filters.Search+"%")
|
||||
argIdx++
|
||||
countQuery += fmt.Sprintf(" AND (title ILIKE $%d OR description ILIKE $%d OR module_code ILIKE $%d)", countArgIdx, countArgIdx, countArgIdx)
|
||||
countArgs = append(countArgs, "%"+filters.Search+"%")
|
||||
countArgIdx++
|
||||
}
|
||||
}
|
||||
|
||||
var total int
|
||||
err := s.pool.QueryRow(ctx, countQuery, countArgs...).Scan(&total)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
query += " ORDER BY sort_order ASC, 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)
|
||||
argIdx++
|
||||
}
|
||||
}
|
||||
|
||||
rows, err := s.pool.Query(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var modules []TrainingModule
|
||||
for rows.Next() {
|
||||
var module TrainingModule
|
||||
var regulationArea, frequencyType string
|
||||
var isoControls []byte
|
||||
|
||||
err := rows.Scan(
|
||||
&module.ID, &module.TenantID, &module.AcademyCourseID, &module.ModuleCode, &module.Title, &module.Description,
|
||||
®ulationArea, &module.NIS2Relevant, &isoControls, &frequencyType,
|
||||
&module.ValidityDays, &module.RiskWeight, &module.ContentType, &module.DurationMinutes,
|
||||
&module.PassThreshold, &module.IsActive, &module.SortOrder, &module.CreatedAt, &module.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
module.RegulationArea = RegulationArea(regulationArea)
|
||||
module.FrequencyType = FrequencyType(frequencyType)
|
||||
json.Unmarshal(isoControls, &module.ISOControls)
|
||||
if module.ISOControls == nil {
|
||||
module.ISOControls = []string{}
|
||||
}
|
||||
|
||||
modules = append(modules, module)
|
||||
}
|
||||
|
||||
if modules == nil {
|
||||
modules = []TrainingModule{}
|
||||
}
|
||||
|
||||
return modules, total, nil
|
||||
}
|
||||
|
||||
// UpdateModule updates a training module
|
||||
func (s *Store) UpdateModule(ctx context.Context, module *TrainingModule) error {
|
||||
module.UpdatedAt = time.Now().UTC()
|
||||
isoControls, _ := json.Marshal(module.ISOControls)
|
||||
|
||||
_, err := s.pool.Exec(ctx, `
|
||||
UPDATE training_modules SET
|
||||
title = $2, description = $3, nis2_relevant = $4,
|
||||
iso_controls = $5, validity_days = $6, risk_weight = $7,
|
||||
duration_minutes = $8, pass_threshold = $9, is_active = $10,
|
||||
sort_order = $11, updated_at = $12
|
||||
WHERE id = $1
|
||||
`,
|
||||
module.ID, module.Title, module.Description, module.NIS2Relevant,
|
||||
isoControls, module.ValidityDays, module.RiskWeight,
|
||||
module.DurationMinutes, module.PassThreshold, module.IsActive,
|
||||
module.SortOrder, module.UpdatedAt,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteModule deletes a training module by ID
|
||||
func (s *Store) DeleteModule(ctx context.Context, id uuid.UUID) error {
|
||||
_, err := s.pool.Exec(ctx, `DELETE FROM training_modules WHERE id = $1`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetAcademyCourseID links a training module to an academy course
|
||||
func (s *Store) SetAcademyCourseID(ctx context.Context, moduleID, courseID uuid.UUID) error {
|
||||
_, err := s.pool.Exec(ctx, `
|
||||
UPDATE training_modules SET academy_course_id = $2, updated_at = $3 WHERE id = $1
|
||||
`, moduleID, courseID, time.Now().UTC())
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user