Services: Admin-Compliance, Backend-Compliance, AI-Compliance-SDK, Consent-SDK, Developer-Portal, PCA-Platform, DSMS Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
217 lines
6.1 KiB
Go
217 lines
6.1 KiB
Go
package ucca
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
// ObligationsStore handles persistence of obligations assessments
|
|
type ObligationsStore struct {
|
|
pool *pgxpool.Pool
|
|
}
|
|
|
|
// NewObligationsStore creates a new ObligationsStore
|
|
func NewObligationsStore(pool *pgxpool.Pool) *ObligationsStore {
|
|
return &ObligationsStore{pool: pool}
|
|
}
|
|
|
|
// CreateAssessment persists a new obligations assessment
|
|
func (s *ObligationsStore) CreateAssessment(ctx context.Context, a *ObligationsAssessment) error {
|
|
if a.ID == uuid.Nil {
|
|
a.ID = uuid.New()
|
|
}
|
|
a.CreatedAt = time.Now().UTC()
|
|
a.UpdatedAt = a.CreatedAt
|
|
|
|
// Marshal JSON fields
|
|
factsJSON, err := json.Marshal(a.Facts)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal facts: %w", err)
|
|
}
|
|
|
|
overviewJSON, err := json.Marshal(a.Overview)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal overview: %w", err)
|
|
}
|
|
|
|
_, err = s.pool.Exec(ctx, `
|
|
INSERT INTO obligations_assessments (
|
|
id, tenant_id, organization_name, facts, overview,
|
|
status, created_at, updated_at, created_by
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
`,
|
|
a.ID, a.TenantID, a.OrganizationName, factsJSON, overviewJSON,
|
|
a.Status, a.CreatedAt, a.UpdatedAt, a.CreatedBy,
|
|
)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to insert assessment: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetAssessment retrieves an assessment by ID
|
|
func (s *ObligationsStore) GetAssessment(ctx context.Context, tenantID, assessmentID uuid.UUID) (*ObligationsAssessment, error) {
|
|
var a ObligationsAssessment
|
|
var factsJSON, overviewJSON []byte
|
|
|
|
err := s.pool.QueryRow(ctx, `
|
|
SELECT id, tenant_id, organization_name, facts, overview,
|
|
status, created_at, updated_at, created_by
|
|
FROM obligations_assessments
|
|
WHERE id = $1 AND tenant_id = $2
|
|
`, assessmentID, tenantID).Scan(
|
|
&a.ID, &a.TenantID, &a.OrganizationName, &factsJSON, &overviewJSON,
|
|
&a.Status, &a.CreatedAt, &a.UpdatedAt, &a.CreatedBy,
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("assessment not found: %w", err)
|
|
}
|
|
|
|
// Unmarshal JSON fields
|
|
if err := json.Unmarshal(factsJSON, &a.Facts); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal facts: %w", err)
|
|
}
|
|
if err := json.Unmarshal(overviewJSON, &a.Overview); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal overview: %w", err)
|
|
}
|
|
|
|
return &a, nil
|
|
}
|
|
|
|
// ListAssessments returns all assessments for a tenant
|
|
func (s *ObligationsStore) ListAssessments(ctx context.Context, tenantID uuid.UUID, limit, offset int) ([]*ObligationsAssessment, error) {
|
|
if limit <= 0 {
|
|
limit = 20
|
|
}
|
|
if limit > 100 {
|
|
limit = 100
|
|
}
|
|
|
|
rows, err := s.pool.Query(ctx, `
|
|
SELECT id, tenant_id, organization_name, facts, overview,
|
|
status, created_at, updated_at, created_by
|
|
FROM obligations_assessments
|
|
WHERE tenant_id = $1
|
|
ORDER BY created_at DESC
|
|
LIMIT $2 OFFSET $3
|
|
`, tenantID, limit, offset)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to query assessments: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var assessments []*ObligationsAssessment
|
|
for rows.Next() {
|
|
var a ObligationsAssessment
|
|
var factsJSON, overviewJSON []byte
|
|
|
|
if err := rows.Scan(
|
|
&a.ID, &a.TenantID, &a.OrganizationName, &factsJSON, &overviewJSON,
|
|
&a.Status, &a.CreatedAt, &a.UpdatedAt, &a.CreatedBy,
|
|
); err != nil {
|
|
return nil, fmt.Errorf("failed to scan row: %w", err)
|
|
}
|
|
|
|
if err := json.Unmarshal(factsJSON, &a.Facts); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal facts: %w", err)
|
|
}
|
|
if err := json.Unmarshal(overviewJSON, &a.Overview); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal overview: %w", err)
|
|
}
|
|
|
|
assessments = append(assessments, &a)
|
|
}
|
|
|
|
return assessments, nil
|
|
}
|
|
|
|
// UpdateAssessment updates an existing assessment
|
|
func (s *ObligationsStore) UpdateAssessment(ctx context.Context, a *ObligationsAssessment) error {
|
|
a.UpdatedAt = time.Now().UTC()
|
|
|
|
factsJSON, err := json.Marshal(a.Facts)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal facts: %w", err)
|
|
}
|
|
|
|
overviewJSON, err := json.Marshal(a.Overview)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal overview: %w", err)
|
|
}
|
|
|
|
result, err := s.pool.Exec(ctx, `
|
|
UPDATE obligations_assessments
|
|
SET organization_name = $3, facts = $4, overview = $5,
|
|
status = $6, updated_at = $7
|
|
WHERE id = $1 AND tenant_id = $2
|
|
`,
|
|
a.ID, a.TenantID, a.OrganizationName, factsJSON, overviewJSON,
|
|
a.Status, a.UpdatedAt,
|
|
)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update assessment: %w", err)
|
|
}
|
|
|
|
if result.RowsAffected() == 0 {
|
|
return fmt.Errorf("assessment not found")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeleteAssessment deletes an assessment
|
|
func (s *ObligationsStore) DeleteAssessment(ctx context.Context, tenantID, assessmentID uuid.UUID) error {
|
|
result, err := s.pool.Exec(ctx, `
|
|
DELETE FROM obligations_assessments
|
|
WHERE id = $1 AND tenant_id = $2
|
|
`, assessmentID, tenantID)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete assessment: %w", err)
|
|
}
|
|
|
|
if result.RowsAffected() == 0 {
|
|
return fmt.Errorf("assessment not found")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetMigrationSQL returns the SQL to create the required table
|
|
func (s *ObligationsStore) GetMigrationSQL() string {
|
|
return `
|
|
-- Obligations Assessments Table
|
|
CREATE TABLE IF NOT EXISTS obligations_assessments (
|
|
id UUID PRIMARY KEY,
|
|
tenant_id UUID NOT NULL,
|
|
organization_name VARCHAR(255),
|
|
facts JSONB NOT NULL,
|
|
overview JSONB NOT NULL,
|
|
status VARCHAR(50) NOT NULL DEFAULT 'completed',
|
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
created_by UUID,
|
|
|
|
CONSTRAINT fk_tenant FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
|
|
);
|
|
|
|
-- Indexes
|
|
CREATE INDEX IF NOT EXISTS idx_obligations_assessments_tenant ON obligations_assessments(tenant_id);
|
|
CREATE INDEX IF NOT EXISTS idx_obligations_assessments_created ON obligations_assessments(created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_obligations_assessments_status ON obligations_assessments(status);
|
|
|
|
-- GIN index for JSON queries
|
|
CREATE INDEX IF NOT EXISTS idx_obligations_assessments_overview ON obligations_assessments USING GIN (overview);
|
|
`
|
|
}
|