[split-required] [guardrail-change] Enforce 500 LOC budget across all services
Install LOC guardrails (check-loc.sh, architecture.md, pre-commit hook) and split all 44 files exceeding 500 LOC into domain-focused modules: - consent-service (Go): models, handlers, services, database splits - backend-core (Python): security_api, rbac_api, pdf_service, auth splits - admin-core (TypeScript): 5 page.tsx + sidebar extractions - pitch-deck (TypeScript): 6 slides, 3 UI components, engine.ts splits - voice-service (Python): enhanced_task_orchestrator split Result: 0 violations, 36 exempted (pipeline, tests, pure-data files). Go build verified clean. No behavior changes — pure structural splits. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
244
consent-service/internal/handlers/consents_public.go
Normal file
244
consent-service/internal/handlers/consents_public.go
Normal file
@@ -0,0 +1,244 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/breakpilot/consent-service/internal/middleware"
|
||||
"github.com/breakpilot/consent-service/internal/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// ========================================
|
||||
// PUBLIC ENDPOINTS - Consent
|
||||
// ========================================
|
||||
|
||||
// CreateConsent creates a new user consent
|
||||
func (h *Handler) CreateConsent(c *gin.Context) {
|
||||
var req models.CreateConsentRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
userID, err := middleware.GetUserID(c)
|
||||
if err != nil || userID == uuid.Nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid user"})
|
||||
return
|
||||
}
|
||||
|
||||
versionID, err := uuid.Parse(req.VersionID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid version ID"})
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
ipAddress := middleware.GetClientIP(c)
|
||||
userAgent := middleware.GetUserAgent(c)
|
||||
|
||||
// Upsert consent
|
||||
var consentID uuid.UUID
|
||||
err = h.db.Pool.QueryRow(ctx, `
|
||||
INSERT INTO user_consents (user_id, document_version_id, consented, ip_address, user_agent)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
ON CONFLICT (user_id, document_version_id)
|
||||
DO UPDATE SET consented = $3, consented_at = NOW(), withdrawn_at = NULL
|
||||
RETURNING id
|
||||
`, userID, versionID, req.Consented, ipAddress, userAgent).Scan(&consentID)
|
||||
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save consent"})
|
||||
return
|
||||
}
|
||||
|
||||
// Log to audit trail
|
||||
h.logAudit(ctx, &userID, "consent_given", "document_version", &versionID, nil, ipAddress, userAgent)
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"message": "Consent saved successfully",
|
||||
"consent_id": consentID,
|
||||
})
|
||||
}
|
||||
|
||||
// GetMyConsents returns all consents for the current user
|
||||
func (h *Handler) GetMyConsents(c *gin.Context) {
|
||||
userID, err := middleware.GetUserID(c)
|
||||
if err != nil || userID == uuid.Nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid user"})
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
rows, err := h.db.Pool.Query(ctx, `
|
||||
SELECT uc.id, uc.consented, uc.consented_at, uc.withdrawn_at,
|
||||
ld.id, ld.type, ld.name, ld.is_mandatory,
|
||||
dv.id, dv.version, dv.language, dv.title
|
||||
FROM user_consents uc
|
||||
JOIN document_versions dv ON uc.document_version_id = dv.id
|
||||
JOIN legal_documents ld ON dv.document_id = ld.id
|
||||
WHERE uc.user_id = $1
|
||||
ORDER BY uc.consented_at DESC
|
||||
`, userID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch consents"})
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var consents []map[string]interface{}
|
||||
for rows.Next() {
|
||||
var (
|
||||
consentID uuid.UUID
|
||||
consented bool
|
||||
consentedAt time.Time
|
||||
withdrawnAt *time.Time
|
||||
docID uuid.UUID
|
||||
docType string
|
||||
docName string
|
||||
isMandatory bool
|
||||
versionID uuid.UUID
|
||||
version string
|
||||
language string
|
||||
title string
|
||||
)
|
||||
|
||||
if err := rows.Scan(&consentID, &consented, &consentedAt, &withdrawnAt,
|
||||
&docID, &docType, &docName, &isMandatory,
|
||||
&versionID, &version, &language, &title); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
consents = append(consents, map[string]interface{}{
|
||||
"consent_id": consentID,
|
||||
"consented": consented,
|
||||
"consented_at": consentedAt,
|
||||
"withdrawn_at": withdrawnAt,
|
||||
"document": map[string]interface{}{
|
||||
"id": docID,
|
||||
"type": docType,
|
||||
"name": docName,
|
||||
"is_mandatory": isMandatory,
|
||||
},
|
||||
"version": map[string]interface{}{
|
||||
"id": versionID,
|
||||
"version": version,
|
||||
"language": language,
|
||||
"title": title,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"consents": consents})
|
||||
}
|
||||
|
||||
// CheckConsent checks if the user has consented to a document
|
||||
func (h *Handler) CheckConsent(c *gin.Context) {
|
||||
docType := c.Param("documentType")
|
||||
language := c.DefaultQuery("language", "de")
|
||||
|
||||
userID, err := middleware.GetUserID(c)
|
||||
if err != nil || userID == uuid.Nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid user"})
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Get latest published version
|
||||
var latestVersionID uuid.UUID
|
||||
var latestVersion string
|
||||
err = h.db.Pool.QueryRow(ctx, `
|
||||
SELECT dv.id, dv.version
|
||||
FROM document_versions dv
|
||||
JOIN legal_documents ld ON dv.document_id = ld.id
|
||||
WHERE ld.type = $1 AND dv.language = $2 AND dv.status = 'published'
|
||||
ORDER BY dv.published_at DESC
|
||||
LIMIT 1
|
||||
`, docType, language).Scan(&latestVersionID, &latestVersion)
|
||||
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, models.ConsentCheckResponse{
|
||||
HasConsent: false,
|
||||
NeedsUpdate: false,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Check if user has consented to this version
|
||||
var consentedVersionID uuid.UUID
|
||||
var consentedVersion string
|
||||
var consentedAt time.Time
|
||||
err = h.db.Pool.QueryRow(ctx, `
|
||||
SELECT dv.id, dv.version, uc.consented_at
|
||||
FROM user_consents uc
|
||||
JOIN document_versions dv ON uc.document_version_id = dv.id
|
||||
JOIN legal_documents ld ON dv.document_id = ld.id
|
||||
WHERE uc.user_id = $1 AND ld.type = $2 AND uc.consented = true AND uc.withdrawn_at IS NULL
|
||||
ORDER BY uc.consented_at DESC
|
||||
LIMIT 1
|
||||
`, userID, docType).Scan(&consentedVersionID, &consentedVersion, &consentedAt)
|
||||
|
||||
if err != nil {
|
||||
// No consent found
|
||||
latestIDStr := latestVersionID.String()
|
||||
c.JSON(http.StatusOK, models.ConsentCheckResponse{
|
||||
HasConsent: false,
|
||||
CurrentVersionID: &latestIDStr,
|
||||
NeedsUpdate: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Check if consent is for latest version
|
||||
needsUpdate := consentedVersionID != latestVersionID
|
||||
latestIDStr := latestVersionID.String()
|
||||
consentedVerStr := consentedVersion
|
||||
|
||||
c.JSON(http.StatusOK, models.ConsentCheckResponse{
|
||||
HasConsent: true,
|
||||
CurrentVersionID: &latestIDStr,
|
||||
ConsentedVersion: &consentedVerStr,
|
||||
NeedsUpdate: needsUpdate,
|
||||
ConsentedAt: &consentedAt,
|
||||
})
|
||||
}
|
||||
|
||||
// WithdrawConsent withdraws a consent
|
||||
func (h *Handler) WithdrawConsent(c *gin.Context) {
|
||||
consentID, err := uuid.Parse(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid consent ID"})
|
||||
return
|
||||
}
|
||||
|
||||
userID, err := middleware.GetUserID(c)
|
||||
if err != nil || userID == uuid.Nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid user"})
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
ipAddress := middleware.GetClientIP(c)
|
||||
userAgent := middleware.GetUserAgent(c)
|
||||
|
||||
// Update consent
|
||||
result, err := h.db.Pool.Exec(ctx, `
|
||||
UPDATE user_consents
|
||||
SET withdrawn_at = NOW(), consented = false
|
||||
WHERE id = $1 AND user_id = $2
|
||||
`, consentID, userID)
|
||||
|
||||
if err != nil || result.RowsAffected() == 0 {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Consent not found"})
|
||||
return
|
||||
}
|
||||
|
||||
// Log to audit trail
|
||||
h.logAudit(ctx, &userID, "consent_withdrawn", "consent", &consentID, nil, ipAddress, userAgent)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Consent withdrawn successfully"})
|
||||
}
|
||||
Reference in New Issue
Block a user