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>
169 lines
4.7 KiB
Go
169 lines
4.7 KiB
Go
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"
|
|
)
|
|
|
|
// ========================================
|
|
// GDPR / DATA SUBJECT RIGHTS
|
|
// ========================================
|
|
|
|
// GetMyData returns all data we have about the user
|
|
func (h *Handler) GetMyData(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()
|
|
ipAddress := middleware.GetClientIP(c)
|
|
userAgent := middleware.GetUserAgent(c)
|
|
|
|
// Get user info
|
|
var user models.User
|
|
err = h.db.Pool.QueryRow(ctx, `
|
|
SELECT id, external_id, email, role, created_at, updated_at
|
|
FROM users WHERE id = $1
|
|
`, userID).Scan(&user.ID, &user.ExternalID, &user.Email, &user.Role, &user.CreatedAt, &user.UpdatedAt)
|
|
|
|
// Get consents
|
|
consentRows, _ := h.db.Pool.Query(ctx, `
|
|
SELECT uc.consented, uc.consented_at, ld.type, ld.name, dv.version
|
|
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
|
|
`, userID)
|
|
defer consentRows.Close()
|
|
|
|
var consents []map[string]interface{}
|
|
for consentRows.Next() {
|
|
var consented bool
|
|
var consentedAt time.Time
|
|
var docType, docName, version string
|
|
consentRows.Scan(&consented, &consentedAt, &docType, &docName, &version)
|
|
consents = append(consents, map[string]interface{}{
|
|
"document_type": docType,
|
|
"document_name": docName,
|
|
"version": version,
|
|
"consented": consented,
|
|
"consented_at": consentedAt,
|
|
})
|
|
}
|
|
|
|
// Get cookie consents
|
|
cookieRows, _ := h.db.Pool.Query(ctx, `
|
|
SELECT cat.name, cc.consented, cc.updated_at
|
|
FROM cookie_consents cc
|
|
JOIN cookie_categories cat ON cc.category_id = cat.id
|
|
WHERE cc.user_id = $1
|
|
`, userID)
|
|
defer cookieRows.Close()
|
|
|
|
var cookieConsents []map[string]interface{}
|
|
for cookieRows.Next() {
|
|
var name string
|
|
var consented bool
|
|
var updatedAt time.Time
|
|
cookieRows.Scan(&name, &consented, &updatedAt)
|
|
cookieConsents = append(cookieConsents, map[string]interface{}{
|
|
"category": name,
|
|
"consented": consented,
|
|
"updated_at": updatedAt,
|
|
})
|
|
}
|
|
|
|
// Log data access
|
|
h.logAudit(ctx, &userID, "data_access", "user", &userID, nil, ipAddress, userAgent)
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"user": map[string]interface{}{
|
|
"id": user.ID,
|
|
"email": user.Email,
|
|
"created_at": user.CreatedAt,
|
|
},
|
|
"consents": consents,
|
|
"cookie_consents": cookieConsents,
|
|
"exported_at": time.Now(),
|
|
})
|
|
}
|
|
|
|
// RequestDataExport creates a data export request
|
|
func (h *Handler) RequestDataExport(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()
|
|
ipAddress := middleware.GetClientIP(c)
|
|
userAgent := middleware.GetUserAgent(c)
|
|
|
|
var requestID uuid.UUID
|
|
err = h.db.Pool.QueryRow(ctx, `
|
|
INSERT INTO data_export_requests (user_id, status)
|
|
VALUES ($1, 'pending')
|
|
RETURNING id
|
|
`, userID).Scan(&requestID)
|
|
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create export request"})
|
|
return
|
|
}
|
|
|
|
// Log to audit trail
|
|
h.logAudit(ctx, &userID, "data_export_requested", "export_request", &requestID, nil, ipAddress, userAgent)
|
|
|
|
c.JSON(http.StatusAccepted, gin.H{
|
|
"message": "Export request created. You will be notified when ready.",
|
|
"request_id": requestID,
|
|
})
|
|
}
|
|
|
|
// RequestDataDeletion creates a data deletion request
|
|
func (h *Handler) RequestDataDeletion(c *gin.Context) {
|
|
userID, err := middleware.GetUserID(c)
|
|
if err != nil || userID == uuid.Nil {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid user"})
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
Reason string `json:"reason"`
|
|
}
|
|
c.ShouldBindJSON(&req)
|
|
|
|
ctx := context.Background()
|
|
ipAddress := middleware.GetClientIP(c)
|
|
userAgent := middleware.GetUserAgent(c)
|
|
|
|
var requestID uuid.UUID
|
|
err = h.db.Pool.QueryRow(ctx, `
|
|
INSERT INTO data_deletion_requests (user_id, status, reason)
|
|
VALUES ($1, 'pending', $2)
|
|
RETURNING id
|
|
`, userID, req.Reason).Scan(&requestID)
|
|
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create deletion request"})
|
|
return
|
|
}
|
|
|
|
// Log to audit trail
|
|
h.logAudit(ctx, &userID, "data_deletion_requested", "deletion_request", &requestID, nil, ipAddress, userAgent)
|
|
|
|
c.JSON(http.StatusAccepted, gin.H{
|
|
"message": "Deletion request created. We will process your request within 30 days.",
|
|
"request_id": requestID,
|
|
})
|
|
}
|