diff --git a/backend/cmd/api/main.go b/backend/cmd/api/main.go index 3e500b8..8856c7d 100644 --- a/backend/cmd/api/main.go +++ b/backend/cmd/api/main.go @@ -146,6 +146,7 @@ func main() { r.GET("/api/tipos-servicos", tiposServicosHandler.List) r.GET("/api/tipos-eventos", tiposEventosHandler.List) r.GET("/api/tipos-eventos/:id/precos", tiposEventosHandler.ListPrices) + r.GET("/api/public/codigos-acesso/verificar", codigosHandler.Verify) // Protected Routes api := r.Group("/api") diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 0b5b7ef..d02967c 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -2135,6 +2135,43 @@ const docTemplate = `{ } } }, + "/api/public/codigos-acesso/verificar": { + "get": { + "tags": [ + "codigos" + ], + "summary": "Verify Access Code", + "parameters": [ + { + "type": "string", + "description": "Code", + "name": "code", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "boolean" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, "/api/tipos-eventos": { "get": { "security": [ diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 2fe7702..cc733da 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -2129,6 +2129,43 @@ } } }, + "/api/public/codigos-acesso/verificar": { + "get": { + "tags": [ + "codigos" + ], + "summary": "Verify Access Code", + "parameters": [ + { + "type": "string", + "description": "Code", + "name": "code", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "boolean" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, "/api/tipos-eventos": { "get": { "security": [ diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 096d168..e2c2760 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -1874,6 +1874,30 @@ paths: summary: Update profissional tags: - profissionais + /api/public/codigos-acesso/verificar: + get: + parameters: + - description: Code + in: query + name: code + required: true + type: string + responses: + "200": + description: OK + schema: + additionalProperties: + type: boolean + type: object + "400": + description: Bad Request + schema: + additionalProperties: + type: string + type: object + summary: Verify Access Code + tags: + - codigos /api/tipos-eventos: get: consumes: diff --git a/backend/internal/codigos/handler.go b/backend/internal/codigos/handler.go index c59fd5c..09618f5 100644 --- a/backend/internal/codigos/handler.go +++ b/backend/internal/codigos/handler.go @@ -88,3 +88,34 @@ func (h *Handler) Delete(c *gin.Context) { } c.JSON(http.StatusOK, gin.H{"message": "deleted"}) } + +// Verify godoc +// @Summary Verify Access Code +// @Tags codigos +// @Param code query string true "Code" +// @Success 200 {object} map[string]bool +// @Failure 400 {object} map[string]string +// @Router /api/public/codigos-acesso/verificar [get] +func (h *Handler) Verify(c *gin.Context) { + code := c.Query("code") + if code == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "code required"}) + return + } + + err := h.service.Verify(c.Request.Context(), code) + if err != nil { + // Distinguish validation error from DB error strictly? + // For security, just say invalid. + // But service returns specific AppError. + if _, ok := err.(*AppError); ok { + c.JSON(http.StatusBadRequest, gin.H{"valid": false, "error": err.Error()}) + return + } + // If check failed (not found etc), return 404 or 400 + c.JSON(http.StatusBadRequest, gin.H{"valid": false, "error": "Código inválido"}) + return + } + + c.JSON(http.StatusOK, gin.H{"valid": true}) +} diff --git a/backend/internal/codigos/service.go b/backend/internal/codigos/service.go index 8a2303d..82f3829 100644 --- a/backend/internal/codigos/service.go +++ b/backend/internal/codigos/service.go @@ -77,3 +77,29 @@ func (s *Service) IncrementUse(ctx context.Context, id uuid.UUID) error { pgUUID.Valid = true return s.q.IncrementCodigoAcessoUso(ctx, pgUUID) } + +// Custom error for validation +type AppError struct { + Message string +} + +func (e *AppError) Error() string { + return e.Message +} + +func (s *Service) Verify(ctx context.Context, code string) error { + c, err := s.q.GetCodigoAcesso(ctx, code) + if err != nil { + return err // Not found or DB error + } + + if !c.Ativo { + return &AppError{Message: "Código inativo"} + } + + if time.Now().After(c.ExpiraEm.Time) { + return &AppError{Message: "Código expirado"} + } + + return nil +} diff --git a/frontend/pages/Home.tsx b/frontend/pages/Home.tsx index 4173a27..dcaad97 100644 --- a/frontend/pages/Home.tsx +++ b/frontend/pages/Home.tsx @@ -1,6 +1,7 @@ import React, { useState } from "react"; import { Button } from "../components/Button"; import { X } from "lucide-react"; +import { verifyAccessCode } from "../services/apiService"; interface HomeProps { onEnter: () => void; @@ -12,26 +13,28 @@ export const Home: React.FC = ({ onEnter }) => { const [showProfessionalPrompt, setShowProfessionalPrompt] = useState(false); const [codeError, setCodeError] = useState(""); - // Código mockado - em produção, verificar com o backend - const MOCK_ACCESS_CODE = "PHOTUM2025"; - const handleRegisterClick = () => { setShowProfessionalPrompt(true); setAccessCode(""); setCodeError(""); }; - const handleVerifyCode = () => { + const handleVerifyCode = async () => { if (accessCode.trim() === "") { setCodeError("Por favor, digite o código de acesso"); return; } - if (accessCode.toUpperCase() === MOCK_ACCESS_CODE) { - setShowAccessCodeModal(false); - window.location.href = "/cadastro"; - } else { - setCodeError("Código de acesso inválido ou expirado"); + try { + const res = await verifyAccessCode(accessCode.toUpperCase()); + if (res.data && res.data.valid) { + setShowAccessCodeModal(false); + window.location.href = "/cadastro"; + } else { + setCodeError(res.data?.error || "Código de acesso inválido ou expirado"); + } + } catch (e) { + setCodeError("Erro ao verificar código"); } }; diff --git a/frontend/services/apiService.ts b/frontend/services/apiService.ts index 034efde..e62b3e5 100644 --- a/frontend/services/apiService.ts +++ b/frontend/services/apiService.ts @@ -1059,3 +1059,10 @@ export async function listPassengers(carroId: string, token: string) { return { data, error: null }; } catch (err: any) { return { data: null, error: err.message }; } } + +/** + * Verifica se um código de acesso é válido + */ +export async function verifyAccessCode(code: string): Promise> { + return fetchFromBackend<{ valid: boolean; error?: string }>(`/api/public/codigos-acesso/verificar?code=${encodeURIComponent(code)}`); +}