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"}) }