Initial commit: breakpilot-compliance - Compliance SDK Platform
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>
This commit is contained in:
216
ai-compliance-sdk/internal/ucca/obligations_store.go
Normal file
216
ai-compliance-sdk/internal/ucca/obligations_store.go
Normal file
@@ -0,0 +1,216 @@
|
||||
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);
|
||||
`
|
||||
}
|
||||
Reference in New Issue
Block a user