feat(api): Implementado novas tabelas ,refatorado a autenticação e aprimorado a documentação Swagger

- Implementado CRUDs para: cursos, empresas, anos_formaturas, tipos_servicos, tipos_eventos
- Implementado lógica de precificação de eventos (precos_tipos_eventos)
- Refatorado a autenticação: Simplificar o payload de cadastro/login (somente e-mail/senha), função padrão 'profissional'
- Corrigido o middleware de autenticação: Resolvido a incompatibilidade de tipo UUID vs String (corrigir erro 500)
- Aprimorado o Swagger: Adicionado structs nomeados, validação de duplicatas (409 Conflict) e segurança BearerAuth
- Atualizar o esquema do banco de dados: Adicionar tabelas e restrições
This commit is contained in:
NANDO9322 2025-12-09 17:05:19 -03:00
parent f89e1386f7
commit 7f1d4144db
29 changed files with 4416 additions and 152 deletions

View file

@ -3,11 +3,16 @@ package main
import (
"log"
"photum-backend/internal/anos_formaturas"
"photum-backend/internal/auth"
"photum-backend/internal/config"
"photum-backend/internal/cursos"
"photum-backend/internal/db"
"photum-backend/internal/empresas"
"photum-backend/internal/funcoes"
"photum-backend/internal/profissionais"
"photum-backend/internal/tipos_eventos"
"photum-backend/internal/tipos_servicos"
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
@ -45,11 +50,21 @@ func main() {
profissionaisService := profissionais.NewService(queries)
authService := auth.NewService(queries, profissionaisService, cfg)
funcoesService := funcoes.NewService(queries)
cursosService := cursos.NewService(queries)
empresasService := empresas.NewService(queries)
anosFormaturasService := anos_formaturas.NewService(queries)
tiposServicosService := tipos_servicos.NewService(queries)
tiposEventosService := tipos_eventos.NewService(queries)
// Initialize handlers
authHandler := auth.NewHandler(authService)
profissionaisHandler := profissionais.NewHandler(profissionaisService)
funcoesHandler := funcoes.NewHandler(funcoesService)
cursosHandler := cursos.NewHandler(cursosService)
empresasHandler := empresas.NewHandler(empresasService)
anosFormaturasHandler := anos_formaturas.NewHandler(anosFormaturasService)
tiposServicosHandler := tipos_servicos.NewHandler(tiposServicosService)
tiposEventosHandler := tipos_eventos.NewHandler(tiposEventosService)
r := gin.Default()
@ -65,8 +80,14 @@ func main() {
authGroup.POST("/logout", authHandler.Logout)
}
// Public API Routes
// Public API Routes (Data Lists)
r.GET("/api/funcoes", funcoesHandler.List)
r.GET("/api/cursos", cursosHandler.List)
r.GET("/api/empresas", empresasHandler.List)
r.GET("/api/anos-formaturas", anosFormaturasHandler.List)
r.GET("/api/tipos-servicos", tiposServicosHandler.List)
r.GET("/api/tipos-eventos", tiposEventosHandler.List)
r.GET("/api/tipos-eventos/:id/precos", tiposEventosHandler.ListPrices)
// Protected Routes
api := r.Group("/api")
@ -94,10 +115,29 @@ func main() {
funcoesGroup := api.Group("/funcoes")
{
funcoesGroup.POST("", funcoesHandler.Create)
// GET is now public
funcoesGroup.PUT("/:id", funcoesHandler.Update)
funcoesGroup.DELETE("/:id", funcoesHandler.Delete)
}
// protected CRUD (create/update/delete)
api.POST("/cursos", cursosHandler.Create)
api.PUT("/cursos/:id", cursosHandler.Update)
api.DELETE("/cursos/:id", cursosHandler.Delete)
api.POST("/empresas", empresasHandler.Create)
api.PUT("/empresas/:id", empresasHandler.Update)
api.DELETE("/empresas/:id", empresasHandler.Delete)
api.POST("/anos-formaturas", anosFormaturasHandler.Create)
api.PUT("/anos-formaturas/:id", anosFormaturasHandler.Update)
api.DELETE("/anos-formaturas/:id", anosFormaturasHandler.Delete)
api.POST("/tipos-servicos", tiposServicosHandler.Create)
api.PUT("/tipos-servicos/:id", tiposServicosHandler.Update)
api.DELETE("/tipos-servicos/:id", tiposServicosHandler.Delete)
api.POST("/tipos-eventos", tiposEventosHandler.Create)
api.POST("/tipos-eventos/precos", tiposEventosHandler.SetPrice)
}
log.Printf("Server running on port %s", cfg.AppPort)

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,26 @@
basePath: /
definitions:
auth.loginRequest:
anos_formaturas.AnoFormaturaResponse:
properties:
ano_semestre:
type: string
id:
type: string
type: object
anos_formaturas.CreateAnoFormaturaRequest:
properties:
ano_semestre:
example: "2029.1"
type: string
required:
- ano_semestre
type: object
auth.authRequest:
properties:
email:
type: string
senha:
minLength: 6
type: string
required:
- email
@ -20,25 +36,6 @@ definitions:
user:
$ref: '#/definitions/auth.userResponse'
type: object
auth.registerRequest:
properties:
email:
type: string
profissional_data:
$ref: '#/definitions/profissionais.CreateProfissionalInput'
role:
enum:
- profissional
- empresa
type: string
senha:
minLength: 6
type: string
required:
- email
- role
- senha
type: object
auth.userResponse:
properties:
email:
@ -48,6 +45,41 @@ definitions:
role:
type: string
type: object
cursos.CreateCursoRequest:
properties:
nome:
type: string
required:
- nome
type: object
cursos.CursoResponse:
properties:
id:
type: string
nome:
type: string
type: object
empresas.CreateEmpresaRequest:
properties:
nome:
type: string
required:
- nome
type: object
empresas.EmpresaResponse:
properties:
id:
type: string
nome:
type: string
type: object
funcoes.CreateFuncaoRequest:
properties:
nome:
type: string
required:
- nome
type: object
funcoes.FuncaoResponse:
properties:
id:
@ -209,6 +241,56 @@ definitions:
whatsapp:
type: string
type: object
tipos_eventos.CreateEventoRequest:
properties:
nome:
type: string
required:
- nome
type: object
tipos_eventos.EventoResponse:
properties:
id:
type: string
nome:
type: string
type: object
tipos_eventos.PrecoResponse:
properties:
funcao_nome:
type: string
funcao_profissional_id:
type: string
id:
type: string
tipo_evento_id:
type: string
valor:
type: number
type: object
tipos_eventos.PriceInput:
properties:
funcao_profissional_id:
type: string
tipo_evento_id:
type: string
valor:
type: number
type: object
tipos_servicos.CreateTipoServicoRequest:
properties:
nome:
type: string
required:
- nome
type: object
tipos_servicos.TipoServicoResponse:
properties:
id:
type: string
nome:
type: string
type: object
host: localhost:8080
info:
contact:
@ -223,6 +305,303 @@ info:
title: Photum Backend API
version: "1.0"
paths:
/api/anos-formaturas:
get:
consumes:
- application/json
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/anos_formaturas.AnoFormaturaResponse'
type: array
security:
- BearerAuth: []
summary: List all graduation years
tags:
- anos_formaturas
post:
consumes:
- application/json
parameters:
- description: Ano Semestre
in: body
name: request
required: true
schema:
$ref: '#/definitions/anos_formaturas.CreateAnoFormaturaRequest'
produces:
- application/json
responses:
"201":
description: Created
schema:
$ref: '#/definitions/anos_formaturas.AnoFormaturaResponse'
"400":
description: Bad Request
schema:
additionalProperties:
type: string
type: object
"409":
description: Conflict
schema:
additionalProperties:
type: string
type: object
security:
- BearerAuth: []
summary: Create a new graduation year
tags:
- anos_formaturas
/api/anos-formaturas/{id}:
delete:
consumes:
- application/json
parameters:
- description: ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"204":
description: No Content
security:
- BearerAuth: []
summary: Delete a graduation year
tags:
- anos_formaturas
put:
consumes:
- application/json
parameters:
- description: ID
in: path
name: id
required: true
type: string
- description: Ano Semestre
in: body
name: request
required: true
schema:
$ref: '#/definitions/anos_formaturas.CreateAnoFormaturaRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/anos_formaturas.AnoFormaturaResponse'
security:
- BearerAuth: []
summary: Update a graduation year
tags:
- anos_formaturas
/api/cursos:
get:
consumes:
- application/json
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/cursos.CursoResponse'
type: array
security:
- BearerAuth: []
summary: List all courses
tags:
- cursos
post:
consumes:
- application/json
parameters:
- description: Curso Name
in: body
name: request
required: true
schema:
$ref: '#/definitions/cursos.CreateCursoRequest'
produces:
- application/json
responses:
"201":
description: Created
schema:
$ref: '#/definitions/cursos.CursoResponse'
"400":
description: Bad Request
schema:
additionalProperties:
type: string
type: object
"409":
description: Conflict
schema:
additionalProperties:
type: string
type: object
security:
- BearerAuth: []
summary: Create a new course
tags:
- cursos
/api/cursos/{id}:
delete:
consumes:
- application/json
parameters:
- description: Curso ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"204":
description: No Content
security:
- BearerAuth: []
summary: Delete a course
tags:
- cursos
put:
consumes:
- application/json
parameters:
- description: Curso ID
in: path
name: id
required: true
type: string
- description: Curso Name
in: body
name: request
required: true
schema:
$ref: '#/definitions/cursos.CreateCursoRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/cursos.CursoResponse'
security:
- BearerAuth: []
summary: Update a course
tags:
- cursos
/api/empresas:
get:
consumes:
- application/json
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/empresas.EmpresaResponse'
type: array
security:
- BearerAuth: []
summary: List all companies
tags:
- empresas
post:
consumes:
- application/json
parameters:
- description: Empresa Name
in: body
name: request
required: true
schema:
$ref: '#/definitions/empresas.CreateEmpresaRequest'
produces:
- application/json
responses:
"201":
description: Created
schema:
$ref: '#/definitions/empresas.EmpresaResponse'
"400":
description: Bad Request
schema:
additionalProperties:
type: string
type: object
"409":
description: Conflict
schema:
additionalProperties:
type: string
type: object
security:
- BearerAuth: []
summary: Create a new company
tags:
- empresas
/api/empresas/{id}:
delete:
consumes:
- application/json
parameters:
- description: Empresa ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"204":
description: No Content
security:
- BearerAuth: []
summary: Delete a company
tags:
- empresas
put:
consumes:
- application/json
parameters:
- description: Empresa ID
in: path
name: id
required: true
type: string
- description: Empresa Name
in: body
name: request
required: true
schema:
$ref: '#/definitions/empresas.CreateEmpresaRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/empresas.EmpresaResponse'
security:
- BearerAuth: []
summary: Update a company
tags:
- empresas
/api/funcoes:
get:
consumes:
@ -243,8 +622,6 @@ paths:
additionalProperties:
type: string
type: object
security:
- BearerAuth: []
summary: List functions
tags:
- funcoes
@ -258,9 +635,7 @@ paths:
name: request
required: true
schema:
additionalProperties:
type: string
type: object
$ref: '#/definitions/funcoes.CreateFuncaoRequest'
produces:
- application/json
responses:
@ -274,6 +649,12 @@ paths:
additionalProperties:
type: string
type: object
"409":
description: Conflict
schema:
additionalProperties:
type: string
type: object
"500":
description: Internal Server Error
schema:
@ -337,9 +718,7 @@ paths:
name: request
required: true
schema:
additionalProperties:
type: string
type: object
$ref: '#/definitions/funcoes.CreateFuncaoRequest'
produces:
- application/json
responses:
@ -353,6 +732,12 @@ paths:
additionalProperties:
type: string
type: object
"409":
description: Conflict
schema:
additionalProperties:
type: string
type: object
"500":
description: Internal Server Error
schema:
@ -541,6 +926,194 @@ paths:
summary: Update profissional
tags:
- profissionais
/api/tipos-eventos:
get:
consumes:
- application/json
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/tipos_eventos.EventoResponse'
type: array
security:
- BearerAuth: []
summary: List all event types
tags:
- tipos_eventos
post:
consumes:
- application/json
parameters:
- description: Nome
in: body
name: request
required: true
schema:
$ref: '#/definitions/tipos_eventos.CreateEventoRequest'
produces:
- application/json
responses:
"201":
description: Created
schema:
$ref: '#/definitions/tipos_eventos.EventoResponse'
security:
- BearerAuth: []
summary: Create a new event type
tags:
- tipos_eventos
/api/tipos-eventos/{id}/precos:
get:
consumes:
- application/json
parameters:
- description: Event ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/tipos_eventos.PrecoResponse'
type: array
security:
- BearerAuth: []
summary: List prices for an event
tags:
- tipos_eventos
/api/tipos-eventos/precos:
post:
consumes:
- application/json
parameters:
- description: Price Input
in: body
name: request
required: true
schema:
$ref: '#/definitions/tipos_eventos.PriceInput'
produces:
- application/json
responses:
"200":
description: OK
schema:
additionalProperties:
type: string
type: object
security:
- BearerAuth: []
summary: Set price for an event function
tags:
- tipos_eventos
/api/tipos-servicos:
get:
consumes:
- application/json
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/tipos_servicos.TipoServicoResponse'
type: array
security:
- BearerAuth: []
summary: List all service types
tags:
- tipos_servicos
post:
consumes:
- application/json
parameters:
- description: Nome
in: body
name: request
required: true
schema:
$ref: '#/definitions/tipos_servicos.CreateTipoServicoRequest'
produces:
- application/json
responses:
"201":
description: Created
schema:
$ref: '#/definitions/tipos_servicos.TipoServicoResponse'
"400":
description: Bad Request
schema:
additionalProperties:
type: string
type: object
"409":
description: Conflict
schema:
additionalProperties:
type: string
type: object
security:
- BearerAuth: []
summary: Create a new service type
tags:
- tipos_servicos
/api/tipos-servicos/{id}:
delete:
consumes:
- application/json
parameters:
- description: ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"204":
description: No Content
security:
- BearerAuth: []
summary: Delete a service type
tags:
- tipos_servicos
put:
consumes:
- application/json
parameters:
- description: ID
in: path
name: id
required: true
type: string
- description: Nome
in: body
name: request
required: true
schema:
$ref: '#/definitions/tipos_servicos.CreateTipoServicoRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/tipos_servicos.TipoServicoResponse'
security:
- BearerAuth: []
summary: Update a service type
tags:
- tipos_servicos
/auth/login:
post:
consumes:
@ -552,7 +1125,7 @@ paths:
name: request
required: true
schema:
$ref: '#/definitions/auth.loginRequest'
$ref: '#/definitions/auth.authRequest'
produces:
- application/json
responses:
@ -630,14 +1203,15 @@ paths:
post:
consumes:
- application/json
description: Register a new user with optional professional profile
description: Register a new user (defaults to 'profissional' role) with email
and password
parameters:
- description: Register Request
in: body
name: request
required: true
schema:
$ref: '#/definitions/auth.registerRequest'
$ref: '#/definitions/auth.authRequest'
produces:
- application/json
responses:

View file

@ -0,0 +1,137 @@
package anos_formaturas
import (
"net/http"
"strings"
"photum-backend/internal/db/generated"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type Handler struct {
service *Service
}
func NewHandler(service *Service) *Handler {
return &Handler{service: service}
}
type AnoFormaturaResponse struct {
ID string `json:"id"`
AnoSemestre string `json:"ano_semestre"`
}
type CreateAnoFormaturaRequest struct {
AnoSemestre string `json:"ano_semestre" binding:"required" example:"2029.1"`
}
func toResponse(a generated.AnosFormatura) AnoFormaturaResponse {
return AnoFormaturaResponse{
ID: uuid.UUID(a.ID.Bytes).String(),
AnoSemestre: a.AnoSemestre,
}
}
// Create godoc
// @Summary Create a new graduation year
// @Tags anos_formaturas
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param request body CreateAnoFormaturaRequest true "Ano Semestre"
// @Success 201 {object} AnoFormaturaResponse
// @Failure 400 {object} map[string]string
// @Failure 409 {object} map[string]string
// @Router /api/anos-formaturas [post]
func (h *Handler) Create(c *gin.Context) {
var req CreateAnoFormaturaRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
ano, err := h.service.Create(c.Request.Context(), req.AnoSemestre)
if err != nil {
if strings.Contains(err.Error(), "duplicate key value") {
c.JSON(http.StatusConflict, gin.H{"error": "Ano/Semestre already exists"})
return
}
if strings.Contains(err.Error(), "invalid format") {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, toResponse(*ano))
}
// List godoc
// @Summary List all graduation years
// @Tags anos_formaturas
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {array} AnoFormaturaResponse
// @Router /api/anos-formaturas [get]
func (h *Handler) List(c *gin.Context) {
anos, err := h.service.List(c.Request.Context())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
var response []AnoFormaturaResponse
for _, ano := range anos {
response = append(response, toResponse(ano))
}
c.JSON(http.StatusOK, response)
}
// Update godoc
// @Summary Update a graduation year
// @Tags anos_formaturas
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "ID"
// @Param request body CreateAnoFormaturaRequest true "Ano Semestre"
// @Success 200 {object} AnoFormaturaResponse
// @Router /api/anos-formaturas/{id} [put]
func (h *Handler) Update(c *gin.Context) {
id := c.Param("id")
var req CreateAnoFormaturaRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
ano, err := h.service.Update(c.Request.Context(), id, req.AnoSemestre)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, toResponse(*ano))
}
// Delete godoc
// @Summary Delete a graduation year
// @Tags anos_formaturas
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "ID"
// @Success 204 {object} nil
// @Router /api/anos-formaturas/{id} [delete]
func (h *Handler) Delete(c *gin.Context) {
id := c.Param("id")
if err := h.service.Delete(c.Request.Context(), id); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.Status(http.StatusNoContent)
}

View file

@ -0,0 +1,80 @@
package anos_formaturas
import (
"context"
"errors"
"regexp"
"photum-backend/internal/db/generated"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
)
type Service struct {
queries *generated.Queries
}
func NewService(queries *generated.Queries) *Service {
return &Service{queries: queries}
}
func (s *Service) Create(ctx context.Context, anoSemestre string) (*generated.AnosFormatura, error) {
// Validate format YYYY.S
matched, _ := regexp.MatchString(`^\d{4}\.[1-2]$`, anoSemestre)
if !matched {
return nil, errors.New("invalid format. Expected YYYY.1 or YYYY.2")
}
ano, err := s.queries.CreateAnoFormatura(ctx, anoSemestre)
if err != nil {
return nil, err
}
return &ano, nil
}
func (s *Service) List(ctx context.Context) ([]generated.AnosFormatura, error) {
return s.queries.ListAnosFormaturas(ctx)
}
func (s *Service) GetByID(ctx context.Context, id string) (*generated.AnosFormatura, error) {
uuidVal, err := uuid.Parse(id)
if err != nil {
return nil, errors.New("invalid id")
}
ano, err := s.queries.GetAnoFormaturaByID(ctx, pgtype.UUID{Bytes: uuidVal, Valid: true})
if err != nil {
return nil, err
}
return &ano, nil
}
func (s *Service) Update(ctx context.Context, id, anoSemestre string) (*generated.AnosFormatura, error) {
uuidVal, err := uuid.Parse(id)
if err != nil {
return nil, errors.New("invalid id")
}
// Validate format YYYY.S
matched, _ := regexp.MatchString(`^\d{4}\.[1-2]$`, anoSemestre)
if !matched {
return nil, errors.New("invalid format. Expected YYYY.1 or YYYY.2")
}
ano, err := s.queries.UpdateAnoFormatura(ctx, generated.UpdateAnoFormaturaParams{
ID: pgtype.UUID{Bytes: uuidVal, Valid: true},
AnoSemestre: anoSemestre,
})
if err != nil {
return nil, err
}
return &ano, nil
}
func (s *Service) Delete(ctx context.Context, id string) error {
uuidVal, err := uuid.Parse(id)
if err != nil {
return errors.New("invalid id")
}
return s.queries.DeleteAnoFormatura(ctx, pgtype.UUID{Bytes: uuidVal, Valid: true})
}

View file

@ -2,6 +2,7 @@ package auth
import (
"net/http"
"strings"
"photum-backend/internal/profissionais"
@ -17,38 +18,39 @@ func NewHandler(service *Service) *Handler {
return &Handler{service: service}
}
type registerRequest struct {
Email string `json:"email" binding:"required,email"`
Senha string `json:"senha" binding:"required,min=6"`
Role string `json:"role" binding:"required,oneof=profissional empresa"`
ProfissionalData *profissionais.CreateProfissionalInput `json:"profissional_data"`
type authRequest struct {
Email string `json:"email" binding:"required,email"`
Senha string `json:"senha" binding:"required,min=6"`
}
// Register godoc
// @Summary Register a new user
// @Description Register a new user with optional professional profile
// @Description Register a new user (defaults to 'profissional' role) with email and password
// @Tags auth
// @Accept json
// @Produce json
// @Param request body registerRequest true "Register Request"
// @Param request body authRequest true "Register Request"
// @Success 201 {object} map[string]string
// @Failure 400 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Router /auth/register [post]
func (h *Handler) Register(c *gin.Context) {
var req registerRequest
var req authRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if req.Role == "profissional" && req.ProfissionalData == nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "profissional_data is required for role 'profissional'"})
return
}
// Default simplified registration: Role="profissional", No professional data yet
role := "profissional"
var profData *profissionais.CreateProfissionalInput = nil
_, err := h.service.Register(c.Request.Context(), req.Email, req.Senha, req.Role, req.ProfissionalData)
_, err := h.service.Register(c.Request.Context(), req.Email, req.Senha, role, profData)
if err != nil {
if strings.Contains(err.Error(), "duplicate key") {
c.JSON(http.StatusConflict, gin.H{"error": "email already registered"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
@ -56,11 +58,6 @@ func (h *Handler) Register(c *gin.Context) {
c.JSON(http.StatusCreated, gin.H{"message": "user created"})
}
type loginRequest struct {
Email string `json:"email" binding:"required,email"`
Senha string `json:"senha" binding:"required"`
}
type loginResponse struct {
AccessToken string `json:"access_token"`
ExpiresAt string `json:"expires_at"`
@ -80,13 +77,13 @@ type userResponse struct {
// @Tags auth
// @Accept json
// @Produce json
// @Param request body loginRequest true "Login Request"
// @Param request body authRequest true "Login Request"
// @Success 200 {object} loginResponse
// @Failure 401 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Router /auth/login [post]
func (h *Handler) Login(c *gin.Context) {
var req loginRequest
var req authRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
@ -94,7 +91,7 @@ func (h *Handler) Login(c *gin.Context) {
tokenPair, user, profData, err := h.service.Login(c.Request.Context(), req.Email, req.Senha)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"})
return
}
@ -110,7 +107,7 @@ func (h *Handler) Login(c *gin.Context) {
resp := loginResponse{
AccessToken: tokenPair.AccessToken,
ExpiresAt: "2025-...",
ExpiresAt: "2025-...", // logic to calculate if needed, or remove field
User: userResponse{
ID: uuid.UUID(user.ID.Bytes).String(),
Email: user.Email,
@ -131,11 +128,6 @@ func (h *Handler) Login(c *gin.Context) {
c.JSON(http.StatusOK, resp)
}
// Refresh and Logout handlers should be kept or restored if they were lost.
// I will assume they are needed and add them back in a subsequent edit if missing,
// or include them here if I can fit them.
// The previous content had them. I'll add them to be safe.
// Refresh godoc
// @Summary Refresh access token
// @Description Get a new access token using a valid refresh token

View file

@ -33,7 +33,7 @@ func AuthMiddleware(cfg *config.Config) gin.HandlerFunc {
return
}
c.Set("userID", claims.UserID)
c.Set("userID", claims.UserID.String())
c.Set("role", claims.Role)
c.Next()
}

View file

@ -0,0 +1,133 @@
package cursos
import (
"net/http"
"strings"
"photum-backend/internal/db/generated"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type Handler struct {
service *Service
}
func NewHandler(service *Service) *Handler {
return &Handler{service: service}
}
type CursoResponse struct {
ID string `json:"id"`
Nome string `json:"nome"`
}
type CreateCursoRequest struct {
Nome string `json:"nome" binding:"required"`
}
func toResponse(c generated.Curso) CursoResponse {
return CursoResponse{
ID: uuid.UUID(c.ID.Bytes).String(),
Nome: c.Nome,
}
}
// Create godoc
// @Summary Create a new course
// @Tags cursos
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param request body CreateCursoRequest true "Curso Name"
// @Success 201 {object} CursoResponse
// @Failure 400 {object} map[string]string
// @Failure 409 {object} map[string]string
// @Router /api/cursos [post]
func (h *Handler) Create(c *gin.Context) {
var req CreateCursoRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
curso, err := h.service.Create(c.Request.Context(), req.Nome)
if err != nil {
if strings.Contains(err.Error(), "duplicate key value") {
c.JSON(http.StatusConflict, gin.H{"error": "Curso already exists"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, toResponse(*curso))
}
// List godoc
// @Summary List all courses
// @Tags cursos
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {array} CursoResponse
// @Router /api/cursos [get]
func (h *Handler) List(c *gin.Context) {
cursos, err := h.service.List(c.Request.Context())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
var response []CursoResponse
for _, curso := range cursos {
response = append(response, toResponse(curso))
}
c.JSON(http.StatusOK, response)
}
// Update godoc
// @Summary Update a course
// @Tags cursos
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "Curso ID"
// @Param request body CreateCursoRequest true "Curso Name"
// @Success 200 {object} CursoResponse
// @Router /api/cursos/{id} [put]
func (h *Handler) Update(c *gin.Context) {
id := c.Param("id")
var req CreateCursoRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
curso, err := h.service.Update(c.Request.Context(), id, req.Nome)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, toResponse(*curso))
}
// Delete godoc
// @Summary Delete a course
// @Tags cursos
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "Curso ID"
// @Success 204 {object} nil
// @Router /api/cursos/{id} [delete]
func (h *Handler) Delete(c *gin.Context) {
id := c.Param("id")
if err := h.service.Delete(c.Request.Context(), id); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.Status(http.StatusNoContent)
}

View file

@ -0,0 +1,67 @@
package cursos
import (
"context"
"errors"
"photum-backend/internal/db/generated"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
)
type Service struct {
queries *generated.Queries
}
func NewService(queries *generated.Queries) *Service {
return &Service{queries: queries}
}
func (s *Service) Create(ctx context.Context, nome string) (*generated.Curso, error) {
curso, err := s.queries.CreateCurso(ctx, nome)
if err != nil {
return nil, err
}
return &curso, nil
}
func (s *Service) List(ctx context.Context) ([]generated.Curso, error) {
return s.queries.ListCursos(ctx)
}
func (s *Service) GetByID(ctx context.Context, id string) (*generated.Curso, error) {
uuidVal, err := uuid.Parse(id)
if err != nil {
return nil, errors.New("invalid id")
}
curso, err := s.queries.GetCursoByID(ctx, pgtype.UUID{Bytes: uuidVal, Valid: true})
if err != nil {
return nil, err
}
return &curso, nil
}
func (s *Service) Update(ctx context.Context, id, nome string) (*generated.Curso, error) {
uuidVal, err := uuid.Parse(id)
if err != nil {
return nil, errors.New("invalid id")
}
curso, err := s.queries.UpdateCurso(ctx, generated.UpdateCursoParams{
ID: pgtype.UUID{Bytes: uuidVal, Valid: true},
Nome: nome,
})
if err != nil {
return nil, err
}
return &curso, nil
}
func (s *Service) Delete(ctx context.Context, id string) error {
uuidVal, err := uuid.Parse(id)
if err != nil {
return errors.New("invalid id")
}
return s.queries.DeleteCurso(ctx, pgtype.UUID{Bytes: uuidVal, Valid: true})
}

View file

@ -0,0 +1,83 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: anos_formaturas.sql
package generated
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const createAnoFormatura = `-- name: CreateAnoFormatura :one
INSERT INTO anos_formaturas (ano_semestre) VALUES ($1) RETURNING id, ano_semestre, criado_em
`
func (q *Queries) CreateAnoFormatura(ctx context.Context, anoSemestre string) (AnosFormatura, error) {
row := q.db.QueryRow(ctx, createAnoFormatura, anoSemestre)
var i AnosFormatura
err := row.Scan(&i.ID, &i.AnoSemestre, &i.CriadoEm)
return i, err
}
const deleteAnoFormatura = `-- name: DeleteAnoFormatura :exec
DELETE FROM anos_formaturas WHERE id = $1
`
func (q *Queries) DeleteAnoFormatura(ctx context.Context, id pgtype.UUID) error {
_, err := q.db.Exec(ctx, deleteAnoFormatura, id)
return err
}
const getAnoFormaturaByID = `-- name: GetAnoFormaturaByID :one
SELECT id, ano_semestre, criado_em FROM anos_formaturas WHERE id = $1
`
func (q *Queries) GetAnoFormaturaByID(ctx context.Context, id pgtype.UUID) (AnosFormatura, error) {
row := q.db.QueryRow(ctx, getAnoFormaturaByID, id)
var i AnosFormatura
err := row.Scan(&i.ID, &i.AnoSemestre, &i.CriadoEm)
return i, err
}
const listAnosFormaturas = `-- name: ListAnosFormaturas :many
SELECT id, ano_semestre, criado_em FROM anos_formaturas ORDER BY ano_semestre
`
func (q *Queries) ListAnosFormaturas(ctx context.Context) ([]AnosFormatura, error) {
rows, err := q.db.Query(ctx, listAnosFormaturas)
if err != nil {
return nil, err
}
defer rows.Close()
var items []AnosFormatura
for rows.Next() {
var i AnosFormatura
if err := rows.Scan(&i.ID, &i.AnoSemestre, &i.CriadoEm); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updateAnoFormatura = `-- name: UpdateAnoFormatura :one
UPDATE anos_formaturas SET ano_semestre = $2 WHERE id = $1 RETURNING id, ano_semestre, criado_em
`
type UpdateAnoFormaturaParams struct {
ID pgtype.UUID `json:"id"`
AnoSemestre string `json:"ano_semestre"`
}
func (q *Queries) UpdateAnoFormatura(ctx context.Context, arg UpdateAnoFormaturaParams) (AnosFormatura, error) {
row := q.db.QueryRow(ctx, updateAnoFormatura, arg.ID, arg.AnoSemestre)
var i AnosFormatura
err := row.Scan(&i.ID, &i.AnoSemestre, &i.CriadoEm)
return i, err
}

View file

@ -0,0 +1,83 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: cursos.sql
package generated
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const createCurso = `-- name: CreateCurso :one
INSERT INTO cursos (nome) VALUES ($1) RETURNING id, nome, criado_em
`
func (q *Queries) CreateCurso(ctx context.Context, nome string) (Curso, error) {
row := q.db.QueryRow(ctx, createCurso, nome)
var i Curso
err := row.Scan(&i.ID, &i.Nome, &i.CriadoEm)
return i, err
}
const deleteCurso = `-- name: DeleteCurso :exec
DELETE FROM cursos WHERE id = $1
`
func (q *Queries) DeleteCurso(ctx context.Context, id pgtype.UUID) error {
_, err := q.db.Exec(ctx, deleteCurso, id)
return err
}
const getCursoByID = `-- name: GetCursoByID :one
SELECT id, nome, criado_em FROM cursos WHERE id = $1
`
func (q *Queries) GetCursoByID(ctx context.Context, id pgtype.UUID) (Curso, error) {
row := q.db.QueryRow(ctx, getCursoByID, id)
var i Curso
err := row.Scan(&i.ID, &i.Nome, &i.CriadoEm)
return i, err
}
const listCursos = `-- name: ListCursos :many
SELECT id, nome, criado_em FROM cursos ORDER BY nome
`
func (q *Queries) ListCursos(ctx context.Context) ([]Curso, error) {
rows, err := q.db.Query(ctx, listCursos)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Curso
for rows.Next() {
var i Curso
if err := rows.Scan(&i.ID, &i.Nome, &i.CriadoEm); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updateCurso = `-- name: UpdateCurso :one
UPDATE cursos SET nome = $2 WHERE id = $1 RETURNING id, nome, criado_em
`
type UpdateCursoParams struct {
ID pgtype.UUID `json:"id"`
Nome string `json:"nome"`
}
func (q *Queries) UpdateCurso(ctx context.Context, arg UpdateCursoParams) (Curso, error) {
row := q.db.QueryRow(ctx, updateCurso, arg.ID, arg.Nome)
var i Curso
err := row.Scan(&i.ID, &i.Nome, &i.CriadoEm)
return i, err
}

View file

@ -0,0 +1,83 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: empresas.sql
package generated
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const createEmpresa = `-- name: CreateEmpresa :one
INSERT INTO empresas (nome) VALUES ($1) RETURNING id, nome, criado_em
`
func (q *Queries) CreateEmpresa(ctx context.Context, nome string) (Empresa, error) {
row := q.db.QueryRow(ctx, createEmpresa, nome)
var i Empresa
err := row.Scan(&i.ID, &i.Nome, &i.CriadoEm)
return i, err
}
const deleteEmpresa = `-- name: DeleteEmpresa :exec
DELETE FROM empresas WHERE id = $1
`
func (q *Queries) DeleteEmpresa(ctx context.Context, id pgtype.UUID) error {
_, err := q.db.Exec(ctx, deleteEmpresa, id)
return err
}
const getEmpresaByID = `-- name: GetEmpresaByID :one
SELECT id, nome, criado_em FROM empresas WHERE id = $1
`
func (q *Queries) GetEmpresaByID(ctx context.Context, id pgtype.UUID) (Empresa, error) {
row := q.db.QueryRow(ctx, getEmpresaByID, id)
var i Empresa
err := row.Scan(&i.ID, &i.Nome, &i.CriadoEm)
return i, err
}
const listEmpresas = `-- name: ListEmpresas :many
SELECT id, nome, criado_em FROM empresas ORDER BY nome
`
func (q *Queries) ListEmpresas(ctx context.Context) ([]Empresa, error) {
rows, err := q.db.Query(ctx, listEmpresas)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Empresa
for rows.Next() {
var i Empresa
if err := rows.Scan(&i.ID, &i.Nome, &i.CriadoEm); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updateEmpresa = `-- name: UpdateEmpresa :one
UPDATE empresas SET nome = $2 WHERE id = $1 RETURNING id, nome, criado_em
`
type UpdateEmpresaParams struct {
ID pgtype.UUID `json:"id"`
Nome string `json:"nome"`
}
func (q *Queries) UpdateEmpresa(ctx context.Context, arg UpdateEmpresaParams) (Empresa, error) {
row := q.db.QueryRow(ctx, updateEmpresa, arg.ID, arg.Nome)
var i Empresa
err := row.Scan(&i.ID, &i.Nome, &i.CriadoEm)
return i, err
}

View file

@ -8,6 +8,12 @@ import (
"github.com/jackc/pgx/v5/pgtype"
)
type AnosFormatura struct {
ID pgtype.UUID `json:"id"`
AnoSemestre string `json:"ano_semestre"`
CriadoEm pgtype.Timestamptz `json:"criado_em"`
}
type CadastroProfissionai struct {
ID pgtype.UUID `json:"id"`
UsuarioID pgtype.UUID `json:"usuario_id"`
@ -38,6 +44,18 @@ type CadastroProfissionai struct {
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
}
type Curso struct {
ID pgtype.UUID `json:"id"`
Nome string `json:"nome"`
CriadoEm pgtype.Timestamptz `json:"criado_em"`
}
type Empresa struct {
ID pgtype.UUID `json:"id"`
Nome string `json:"nome"`
CriadoEm pgtype.Timestamptz `json:"criado_em"`
}
type FuncoesProfissionai struct {
ID pgtype.UUID `json:"id"`
Nome string `json:"nome"`
@ -45,6 +63,14 @@ type FuncoesProfissionai struct {
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
}
type PrecosTiposEvento struct {
ID pgtype.UUID `json:"id"`
TipoEventoID pgtype.UUID `json:"tipo_evento_id"`
FuncaoProfissionalID pgtype.UUID `json:"funcao_profissional_id"`
Valor pgtype.Numeric `json:"valor"`
CriadoEm pgtype.Timestamptz `json:"criado_em"`
}
type RefreshToken struct {
ID pgtype.UUID `json:"id"`
UsuarioID pgtype.UUID `json:"usuario_id"`
@ -56,6 +82,18 @@ type RefreshToken struct {
CriadoEm pgtype.Timestamptz `json:"criado_em"`
}
type TiposEvento struct {
ID pgtype.UUID `json:"id"`
Nome string `json:"nome"`
CriadoEm pgtype.Timestamptz `json:"criado_em"`
}
type TiposServico struct {
ID pgtype.UUID `json:"id"`
Nome string `json:"nome"`
CriadoEm pgtype.Timestamptz `json:"criado_em"`
}
type Usuario struct {
ID pgtype.UUID `json:"id"`
Email string `json:"email"`

View file

@ -0,0 +1,184 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: tipos_eventos.sql
package generated
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const createPrecoEvento = `-- name: CreatePrecoEvento :one
INSERT INTO precos_tipos_eventos (tipo_evento_id, funcao_profissional_id, valor)
VALUES ($1, $2, $3)
ON CONFLICT (tipo_evento_id, funcao_profissional_id) DO UPDATE
SET valor = $3
RETURNING id, tipo_evento_id, funcao_profissional_id, valor, criado_em
`
type CreatePrecoEventoParams struct {
TipoEventoID pgtype.UUID `json:"tipo_evento_id"`
FuncaoProfissionalID pgtype.UUID `json:"funcao_profissional_id"`
Valor pgtype.Numeric `json:"valor"`
}
func (q *Queries) CreatePrecoEvento(ctx context.Context, arg CreatePrecoEventoParams) (PrecosTiposEvento, error) {
row := q.db.QueryRow(ctx, createPrecoEvento, arg.TipoEventoID, arg.FuncaoProfissionalID, arg.Valor)
var i PrecosTiposEvento
err := row.Scan(
&i.ID,
&i.TipoEventoID,
&i.FuncaoProfissionalID,
&i.Valor,
&i.CriadoEm,
)
return i, err
}
const createTipoEvento = `-- name: CreateTipoEvento :one
INSERT INTO tipos_eventos (nome) VALUES ($1) RETURNING id, nome, criado_em
`
func (q *Queries) CreateTipoEvento(ctx context.Context, nome string) (TiposEvento, error) {
row := q.db.QueryRow(ctx, createTipoEvento, nome)
var i TiposEvento
err := row.Scan(&i.ID, &i.Nome, &i.CriadoEm)
return i, err
}
const deletePreco = `-- name: DeletePreco :exec
DELETE FROM precos_tipos_eventos WHERE id = $1
`
func (q *Queries) DeletePreco(ctx context.Context, id pgtype.UUID) error {
_, err := q.db.Exec(ctx, deletePreco, id)
return err
}
const deleteTipoEvento = `-- name: DeleteTipoEvento :exec
DELETE FROM tipos_eventos WHERE id = $1
`
func (q *Queries) DeleteTipoEvento(ctx context.Context, id pgtype.UUID) error {
_, err := q.db.Exec(ctx, deleteTipoEvento, id)
return err
}
const getPreco = `-- name: GetPreco :one
SELECT id, tipo_evento_id, funcao_profissional_id, valor, criado_em FROM precos_tipos_eventos WHERE tipo_evento_id = $1 AND funcao_profissional_id = $2
`
type GetPrecoParams struct {
TipoEventoID pgtype.UUID `json:"tipo_evento_id"`
FuncaoProfissionalID pgtype.UUID `json:"funcao_profissional_id"`
}
func (q *Queries) GetPreco(ctx context.Context, arg GetPrecoParams) (PrecosTiposEvento, error) {
row := q.db.QueryRow(ctx, getPreco, arg.TipoEventoID, arg.FuncaoProfissionalID)
var i PrecosTiposEvento
err := row.Scan(
&i.ID,
&i.TipoEventoID,
&i.FuncaoProfissionalID,
&i.Valor,
&i.CriadoEm,
)
return i, err
}
const getTipoEventoByID = `-- name: GetTipoEventoByID :one
SELECT id, nome, criado_em FROM tipos_eventos WHERE id = $1
`
func (q *Queries) GetTipoEventoByID(ctx context.Context, id pgtype.UUID) (TiposEvento, error) {
row := q.db.QueryRow(ctx, getTipoEventoByID, id)
var i TiposEvento
err := row.Scan(&i.ID, &i.Nome, &i.CriadoEm)
return i, err
}
const listPrecosByEventoID = `-- name: ListPrecosByEventoID :many
SELECT p.id, p.tipo_evento_id, p.funcao_profissional_id, p.valor, p.criado_em, f.nome as funcao_nome
FROM precos_tipos_eventos p
JOIN funcoes_profissionais f ON p.funcao_profissional_id = f.id
WHERE p.tipo_evento_id = $1
`
type ListPrecosByEventoIDRow struct {
ID pgtype.UUID `json:"id"`
TipoEventoID pgtype.UUID `json:"tipo_evento_id"`
FuncaoProfissionalID pgtype.UUID `json:"funcao_profissional_id"`
Valor pgtype.Numeric `json:"valor"`
CriadoEm pgtype.Timestamptz `json:"criado_em"`
FuncaoNome string `json:"funcao_nome"`
}
func (q *Queries) ListPrecosByEventoID(ctx context.Context, tipoEventoID pgtype.UUID) ([]ListPrecosByEventoIDRow, error) {
rows, err := q.db.Query(ctx, listPrecosByEventoID, tipoEventoID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListPrecosByEventoIDRow
for rows.Next() {
var i ListPrecosByEventoIDRow
if err := rows.Scan(
&i.ID,
&i.TipoEventoID,
&i.FuncaoProfissionalID,
&i.Valor,
&i.CriadoEm,
&i.FuncaoNome,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listTiposEventos = `-- name: ListTiposEventos :many
SELECT id, nome, criado_em FROM tipos_eventos ORDER BY nome
`
func (q *Queries) ListTiposEventos(ctx context.Context) ([]TiposEvento, error) {
rows, err := q.db.Query(ctx, listTiposEventos)
if err != nil {
return nil, err
}
defer rows.Close()
var items []TiposEvento
for rows.Next() {
var i TiposEvento
if err := rows.Scan(&i.ID, &i.Nome, &i.CriadoEm); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updateTipoEvento = `-- name: UpdateTipoEvento :one
UPDATE tipos_eventos SET nome = $2 WHERE id = $1 RETURNING id, nome, criado_em
`
type UpdateTipoEventoParams struct {
ID pgtype.UUID `json:"id"`
Nome string `json:"nome"`
}
func (q *Queries) UpdateTipoEvento(ctx context.Context, arg UpdateTipoEventoParams) (TiposEvento, error) {
row := q.db.QueryRow(ctx, updateTipoEvento, arg.ID, arg.Nome)
var i TiposEvento
err := row.Scan(&i.ID, &i.Nome, &i.CriadoEm)
return i, err
}

View file

@ -0,0 +1,83 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: tipos_servicos.sql
package generated
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const createTipoServico = `-- name: CreateTipoServico :one
INSERT INTO tipos_servicos (nome) VALUES ($1) RETURNING id, nome, criado_em
`
func (q *Queries) CreateTipoServico(ctx context.Context, nome string) (TiposServico, error) {
row := q.db.QueryRow(ctx, createTipoServico, nome)
var i TiposServico
err := row.Scan(&i.ID, &i.Nome, &i.CriadoEm)
return i, err
}
const deleteTipoServico = `-- name: DeleteTipoServico :exec
DELETE FROM tipos_servicos WHERE id = $1
`
func (q *Queries) DeleteTipoServico(ctx context.Context, id pgtype.UUID) error {
_, err := q.db.Exec(ctx, deleteTipoServico, id)
return err
}
const getTipoServicoByID = `-- name: GetTipoServicoByID :one
SELECT id, nome, criado_em FROM tipos_servicos WHERE id = $1
`
func (q *Queries) GetTipoServicoByID(ctx context.Context, id pgtype.UUID) (TiposServico, error) {
row := q.db.QueryRow(ctx, getTipoServicoByID, id)
var i TiposServico
err := row.Scan(&i.ID, &i.Nome, &i.CriadoEm)
return i, err
}
const listTiposServicos = `-- name: ListTiposServicos :many
SELECT id, nome, criado_em FROM tipos_servicos ORDER BY nome
`
func (q *Queries) ListTiposServicos(ctx context.Context) ([]TiposServico, error) {
rows, err := q.db.Query(ctx, listTiposServicos)
if err != nil {
return nil, err
}
defer rows.Close()
var items []TiposServico
for rows.Next() {
var i TiposServico
if err := rows.Scan(&i.ID, &i.Nome, &i.CriadoEm); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updateTipoServico = `-- name: UpdateTipoServico :one
UPDATE tipos_servicos SET nome = $2 WHERE id = $1 RETURNING id, nome, criado_em
`
type UpdateTipoServicoParams struct {
ID pgtype.UUID `json:"id"`
Nome string `json:"nome"`
}
func (q *Queries) UpdateTipoServico(ctx context.Context, arg UpdateTipoServicoParams) (TiposServico, error) {
row := q.db.QueryRow(ctx, updateTipoServico, arg.ID, arg.Nome)
var i TiposServico
err := row.Scan(&i.ID, &i.Nome, &i.CriadoEm)
return i, err
}

View file

@ -0,0 +1,14 @@
-- name: CreateAnoFormatura :one
INSERT INTO anos_formaturas (ano_semestre) VALUES ($1) RETURNING *;
-- name: ListAnosFormaturas :many
SELECT * FROM anos_formaturas ORDER BY ano_semestre;
-- name: GetAnoFormaturaByID :one
SELECT * FROM anos_formaturas WHERE id = $1;
-- name: UpdateAnoFormatura :one
UPDATE anos_formaturas SET ano_semestre = $2 WHERE id = $1 RETURNING *;
-- name: DeleteAnoFormatura :exec
DELETE FROM anos_formaturas WHERE id = $1;

View file

@ -0,0 +1,14 @@
-- name: CreateCurso :one
INSERT INTO cursos (nome) VALUES ($1) RETURNING *;
-- name: ListCursos :many
SELECT * FROM cursos ORDER BY nome;
-- name: GetCursoByID :one
SELECT * FROM cursos WHERE id = $1;
-- name: UpdateCurso :one
UPDATE cursos SET nome = $2 WHERE id = $1 RETURNING *;
-- name: DeleteCurso :exec
DELETE FROM cursos WHERE id = $1;

View file

@ -0,0 +1,14 @@
-- name: CreateEmpresa :one
INSERT INTO empresas (nome) VALUES ($1) RETURNING *;
-- name: ListEmpresas :many
SELECT * FROM empresas ORDER BY nome;
-- name: GetEmpresaByID :one
SELECT * FROM empresas WHERE id = $1;
-- name: UpdateEmpresa :one
UPDATE empresas SET nome = $2 WHERE id = $1 RETURNING *;
-- name: DeleteEmpresa :exec
DELETE FROM empresas WHERE id = $1;

View file

@ -0,0 +1,33 @@
-- name: CreateTipoEvento :one
INSERT INTO tipos_eventos (nome) VALUES ($1) RETURNING *;
-- name: ListTiposEventos :many
SELECT * FROM tipos_eventos ORDER BY nome;
-- name: GetTipoEventoByID :one
SELECT * FROM tipos_eventos WHERE id = $1;
-- name: UpdateTipoEvento :one
UPDATE tipos_eventos SET nome = $2 WHERE id = $1 RETURNING *;
-- name: DeleteTipoEvento :exec
DELETE FROM tipos_eventos WHERE id = $1;
-- name: CreatePrecoEvento :one
INSERT INTO precos_tipos_eventos (tipo_evento_id, funcao_profissional_id, valor)
VALUES ($1, $2, $3)
ON CONFLICT (tipo_evento_id, funcao_profissional_id) DO UPDATE
SET valor = $3
RETURNING *;
-- name: ListPrecosByEventoID :many
SELECT p.*, f.nome as funcao_nome
FROM precos_tipos_eventos p
JOIN funcoes_profissionais f ON p.funcao_profissional_id = f.id
WHERE p.tipo_evento_id = $1;
-- name: GetPreco :one
SELECT * FROM precos_tipos_eventos WHERE tipo_evento_id = $1 AND funcao_profissional_id = $2;
-- name: DeletePreco :exec
DELETE FROM precos_tipos_eventos WHERE id = $1;

View file

@ -0,0 +1,14 @@
-- name: CreateTipoServico :one
INSERT INTO tipos_servicos (nome) VALUES ($1) RETURNING *;
-- name: ListTiposServicos :many
SELECT * FROM tipos_servicos ORDER BY nome;
-- name: GetTipoServicoByID :one
SELECT * FROM tipos_servicos WHERE id = $1;
-- name: UpdateTipoServico :one
UPDATE tipos_servicos SET nome = $2 WHERE id = $1 RETURNING *;
-- name: DeleteTipoServico :exec
DELETE FROM tipos_servicos WHERE id = $1;

View file

@ -64,3 +64,83 @@ CREATE TABLE refresh_tokens (
revogado BOOLEAN NOT NULL DEFAULT FALSE,
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Cursos Table
CREATE TABLE cursos (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
nome VARCHAR(100) UNIQUE NOT NULL,
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
INSERT INTO cursos (nome) VALUES
('Administração Empresas'), ('Agronomia'), ('Arquitetura / Urbanismo'), ('Biomedicina'), ('Comunicação'), ('Contábeis'),
('Direito'), ('Economia'), ('Educação Física'), ('EFI I/EFI II'), ('EI/EFI'), ('EF II/EM'), ('EFI(5º ano)'), ('EFII'),
('EI'), ('EM'), ('EM / TEC'), ('Enfermagem'), ('Eng. Ambiental'), ('Eng. Elétrica'), ('Engenharia'), ('Estética'),
('Farmácia'), ('Fisioterapia'), ('Gastronomia'), ('Historia'), ('Jornalismo'), ('Med. Veterinária'), ('Medicina'),
('Nutrição'), ('Odontologia'), ('Outro'), ('Pedagogia'), ('Publicidade'), ('Superior Diversos'), ('Tec. Diversos'),
('Termomecânica'), ('Unificados'), ('Tec. Enfermagem'), ('Quimica'), ('EFI / EF II / EM'), ('Psicologia'),
('Terapia Ocupacinal'), ('R.I');
-- Empresas Table
CREATE TABLE empresas (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
nome VARCHAR(100) UNIQUE NOT NULL,
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
INSERT INTO empresas (nome) VALUES
('Arte Formaturas'), ('JR Formaturas'), ('Perfil'), ('Photum'), ('Populi Formaturas'), ('Prime'), ('Smart'), ('Viva SP'),
('Antares'), ('Forcamp'), ('PNI'), ('Fábio Ribeiro'), ('Told - B2_(CAMPINAS)'), ('Told - B2_(RECIFE)'),
('NOVO - Told - B2_(SP)'), ('NOVO - Told - RUB_(SP)'), ('NOVO - TOLD'), ('Alpha Digital'), ('Golden'), ('Festa da Beca'),
('Ponta Eventos'), ('Toy SP'), ('MVP Formaturas'), ('RUB');
-- Anos Formaturas Table
CREATE TABLE anos_formaturas (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
ano_semestre VARCHAR(20) UNIQUE NOT NULL, -- Ex: 2019.2
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
INSERT INTO anos_formaturas (ano_semestre) VALUES
('2019.2'), ('2020.1'), ('2020.2'), ('2021.1'), ('2021.2'), ('2022.1'), ('2022.2'), ('2023.1'), ('2023.2'),
('2024.1'), ('2024.2'), ('2025.1'), ('2025.2'), ('2026.1'), ('2026.2'), ('2027.1'), ('2027.2'), ('2028.1'),
('2028.2'), ('2029.1'), ('2029.2');
-- Tipos Servicos Table
CREATE TABLE tipos_servicos (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
nome VARCHAR(50) UNIQUE NOT NULL,
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
INSERT INTO tipos_servicos (nome) VALUES
('Fotógrafo'), ('Recepcionista'), ('Cinegrafista'), ('Coordenação'), ('Ponto de Foto'), ('Ponto de ID'), ('Estúdio'),
('Outro'), ('Controle');
-- Tipos Eventos Table
CREATE TABLE tipos_eventos (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
nome VARCHAR(100) UNIQUE NOT NULL,
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
INSERT INTO tipos_eventos (nome) VALUES
('Identificação'), ('Baile'), ('Colação'), ('Col/Baile (mesmo local)'), ('Col/Baile (local diferente)'), ('Missa / Culto'),
('Churrasco'), ('Trote'), ('Outro'), ('Balada'), ('Jantar'), ('Festa Junina'), ('Colação Oficial'), ('Family Day'),
('Refeição'), ('Estudio ID e Family Day'), ('Estudio Colação / Baile');
-- Precos Tipos Eventos Table (Junction Table for Pricing)
CREATE TABLE precos_tipos_eventos (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tipo_evento_id UUID REFERENCES tipos_eventos(id) ON DELETE CASCADE,
funcao_profissional_id UUID REFERENCES funcoes_profissionais(id) ON DELETE CASCADE,
valor NUMERIC(10,2) NOT NULL DEFAULT 0.00,
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(tipo_evento_id, funcao_profissional_id)
);
-- Initial Pricing Seed (Examples based on image, requires joining IDs which makes raw SQL insert hard without known UUIDs.
-- For simplicity in schema.sql, we'll skip complex dynamic inserts.
-- The user can populate via API or we can write a more complex PL/pgSQL block if absolutely necessary,
-- but usually schema.sql is structure + static data. Dynamic pricing is better handled via admin or separate migration script.
-- Leaving table empty for now, or adding a comment.)

View file

@ -0,0 +1,133 @@
package empresas
import (
"net/http"
"strings"
"photum-backend/internal/db/generated"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type Handler struct {
service *Service
}
func NewHandler(service *Service) *Handler {
return &Handler{service: service}
}
type EmpresaResponse struct {
ID string `json:"id"`
Nome string `json:"nome"`
}
type CreateEmpresaRequest struct {
Nome string `json:"nome" binding:"required"`
}
func toResponse(e generated.Empresa) EmpresaResponse {
return EmpresaResponse{
ID: uuid.UUID(e.ID.Bytes).String(),
Nome: e.Nome,
}
}
// Create godoc
// @Summary Create a new company
// @Tags empresas
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param request body CreateEmpresaRequest true "Empresa Name"
// @Success 201 {object} EmpresaResponse
// @Failure 400 {object} map[string]string
// @Failure 409 {object} map[string]string
// @Router /api/empresas [post]
func (h *Handler) Create(c *gin.Context) {
var req CreateEmpresaRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
empresa, err := h.service.Create(c.Request.Context(), req.Nome)
if err != nil {
if strings.Contains(err.Error(), "duplicate key value") {
c.JSON(http.StatusConflict, gin.H{"error": "Empresa already exists"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, toResponse(*empresa))
}
// List godoc
// @Summary List all companies
// @Tags empresas
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {array} EmpresaResponse
// @Router /api/empresas [get]
func (h *Handler) List(c *gin.Context) {
empresas, err := h.service.List(c.Request.Context())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
var response []EmpresaResponse
for _, empresa := range empresas {
response = append(response, toResponse(empresa))
}
c.JSON(http.StatusOK, response)
}
// Update godoc
// @Summary Update a company
// @Tags empresas
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "Empresa ID"
// @Param request body CreateEmpresaRequest true "Empresa Name"
// @Success 200 {object} EmpresaResponse
// @Router /api/empresas/{id} [put]
func (h *Handler) Update(c *gin.Context) {
id := c.Param("id")
var req CreateEmpresaRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
empresa, err := h.service.Update(c.Request.Context(), id, req.Nome)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, toResponse(*empresa))
}
// Delete godoc
// @Summary Delete a company
// @Tags empresas
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "Empresa ID"
// @Success 204 {object} nil
// @Router /api/empresas/{id} [delete]
func (h *Handler) Delete(c *gin.Context) {
id := c.Param("id")
if err := h.service.Delete(c.Request.Context(), id); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.Status(http.StatusNoContent)
}

View file

@ -0,0 +1,67 @@
package empresas
import (
"context"
"errors"
"photum-backend/internal/db/generated"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
)
type Service struct {
queries *generated.Queries
}
func NewService(queries *generated.Queries) *Service {
return &Service{queries: queries}
}
func (s *Service) Create(ctx context.Context, nome string) (*generated.Empresa, error) {
empresa, err := s.queries.CreateEmpresa(ctx, nome)
if err != nil {
return nil, err
}
return &empresa, nil
}
func (s *Service) List(ctx context.Context) ([]generated.Empresa, error) {
return s.queries.ListEmpresas(ctx)
}
func (s *Service) GetByID(ctx context.Context, id string) (*generated.Empresa, error) {
uuidVal, err := uuid.Parse(id)
if err != nil {
return nil, errors.New("invalid id")
}
empresa, err := s.queries.GetEmpresaByID(ctx, pgtype.UUID{Bytes: uuidVal, Valid: true})
if err != nil {
return nil, err
}
return &empresa, nil
}
func (s *Service) Update(ctx context.Context, id, nome string) (*generated.Empresa, error) {
uuidVal, err := uuid.Parse(id)
if err != nil {
return nil, errors.New("invalid id")
}
empresa, err := s.queries.UpdateEmpresa(ctx, generated.UpdateEmpresaParams{
ID: pgtype.UUID{Bytes: uuidVal, Valid: true},
Nome: nome,
})
if err != nil {
return nil, err
}
return &empresa, nil
}
func (s *Service) Delete(ctx context.Context, id string) error {
uuidVal, err := uuid.Parse(id)
if err != nil {
return errors.New("invalid id")
}
return s.queries.DeleteEmpresa(ctx, pgtype.UUID{Bytes: uuidVal, Valid: true})
}

View file

@ -2,6 +2,7 @@ package funcoes
import (
"net/http"
"strings"
"photum-backend/internal/db/generated"
@ -22,6 +23,10 @@ type FuncaoResponse struct {
Nome string `json:"nome"`
}
type CreateFuncaoRequest struct {
Nome string `json:"nome" binding:"required"`
}
func toResponse(f generated.FuncoesProfissionai) FuncaoResponse {
return FuncaoResponse{
ID: uuid.UUID(f.ID.Bytes).String(),
@ -36,15 +41,14 @@ func toResponse(f generated.FuncoesProfissionai) FuncaoResponse {
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param request body map[string]string true "Create Function Request"
// @Param request body CreateFuncaoRequest true "Create Function Request"
// @Success 201 {object} FuncaoResponse
// @Failure 400 {object} map[string]string
// @Failure 409 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Router /api/funcoes [post]
func (h *Handler) Create(c *gin.Context) {
var req struct {
Nome string `json:"nome" binding:"required"`
}
var req CreateFuncaoRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
@ -52,6 +56,10 @@ func (h *Handler) Create(c *gin.Context) {
funcao, err := h.service.Create(c.Request.Context(), req.Nome)
if err != nil {
if strings.Contains(err.Error(), "duplicate key value") {
c.JSON(http.StatusConflict, gin.H{"error": "Funcao already exists"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
@ -65,7 +73,6 @@ func (h *Handler) Create(c *gin.Context) {
// @Tags funcoes
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {array} FuncaoResponse
// @Failure 500 {object} map[string]string
// @Router /api/funcoes [get]
@ -92,16 +99,15 @@ func (h *Handler) List(c *gin.Context) {
// @Produce json
// @Security BearerAuth
// @Param id path string true "Function ID"
// @Param request body map[string]string true "Update Function Request"
// @Param request body CreateFuncaoRequest true "Update Function Request"
// @Success 200 {object} FuncaoResponse
// @Failure 400 {object} map[string]string
// @Failure 409 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Router /api/funcoes/{id} [put]
func (h *Handler) Update(c *gin.Context) {
id := c.Param("id")
var req struct {
Nome string `json:"nome" binding:"required"`
}
var req CreateFuncaoRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
@ -109,6 +115,10 @@ func (h *Handler) Update(c *gin.Context) {
funcao, err := h.service.Update(c.Request.Context(), id, req.Nome)
if err != nil {
if strings.Contains(err.Error(), "duplicate key value") {
c.JSON(http.StatusConflict, gin.H{"error": "Funcao already exists"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

View file

@ -0,0 +1,150 @@
package tipos_eventos
import (
"net/http"
"photum-backend/internal/db/generated"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
)
type Handler struct {
service *Service
}
func NewHandler(service *Service) *Handler {
return &Handler{service: service}
}
type EventoResponse struct {
ID string `json:"id"`
Nome string `json:"nome"`
}
type PrecoResponse struct {
ID string `json:"id"`
TipoEventoID string `json:"tipo_evento_id"`
FuncaoProfissionalID string `json:"funcao_profissional_id"`
FuncaoNome string `json:"funcao_nome"`
Valor float64 `json:"valor"`
}
func toEventoResponse(e generated.TiposEvento) EventoResponse {
return EventoResponse{
ID: uuid.UUID(e.ID.Bytes).String(),
Nome: e.Nome,
}
}
func fromPgNumeric(n pgtype.Numeric) float64 {
f, _ := n.Float64Value()
return f.Float64
}
type CreateEventoRequest struct {
Nome string `json:"nome" binding:"required"`
}
// Create godoc
// @Summary Create a new event type
// @Tags tipos_eventos
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param request body CreateEventoRequest true "Nome"
// @Success 201 {object} EventoResponse
// @Router /api/tipos-eventos [post]
func (h *Handler) Create(c *gin.Context) {
var req CreateEventoRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
event, err := h.service.Create(c.Request.Context(), req.Nome)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, toEventoResponse(*event))
}
// List godoc
// @Summary List all event types
// @Tags tipos_eventos
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {array} EventoResponse
// @Router /api/tipos-eventos [get]
func (h *Handler) List(c *gin.Context) {
events, err := h.service.List(c.Request.Context())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
var response []EventoResponse
for _, e := range events {
response = append(response, toEventoResponse(e))
}
c.JSON(http.StatusOK, response)
}
// SetPrice godoc
// @Summary Set price for an event function
// @Tags tipos_eventos
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param request body PriceInput true "Price Input"
// @Success 200 {object} map[string]string
// @Router /api/tipos-eventos/precos [post]
func (h *Handler) SetPrice(c *gin.Context) {
var req PriceInput
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
_, err := h.service.SetPrice(c.Request.Context(), req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "price set"})
}
// ListPrices godoc
// @Summary List prices for an event
// @Tags tipos_eventos
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "Event ID"
// @Success 200 {array} PrecoResponse
// @Router /api/tipos-eventos/{id}/precos [get]
func (h *Handler) ListPrices(c *gin.Context) {
id := c.Param("id")
prices, err := h.service.ListPrices(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
var response []PrecoResponse
for _, p := range prices {
response = append(response, PrecoResponse{
ID: uuid.UUID(p.ID.Bytes).String(),
TipoEventoID: uuid.UUID(p.TipoEventoID.Bytes).String(),
FuncaoProfissionalID: uuid.UUID(p.FuncaoProfissionalID.Bytes).String(),
FuncaoNome: p.FuncaoNome,
Valor: fromPgNumeric(p.Valor),
})
}
c.JSON(http.StatusOK, response)
}

View file

@ -0,0 +1,112 @@
package tipos_eventos
import (
"context"
"errors"
"photum-backend/internal/db/generated"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
)
type Service struct {
queries *generated.Queries
}
func NewService(queries *generated.Queries) *Service {
return &Service{queries: queries}
}
// Event CRUD
func (s *Service) Create(ctx context.Context, nome string) (*generated.TiposEvento, error) {
event, err := s.queries.CreateTipoEvento(ctx, nome)
if err != nil {
return nil, err
}
return &event, nil
}
func (s *Service) List(ctx context.Context) ([]generated.TiposEvento, error) {
return s.queries.ListTiposEventos(ctx)
}
func (s *Service) GetByID(ctx context.Context, id string) (*generated.TiposEvento, error) {
uuidVal, err := uuid.Parse(id)
if err != nil {
return nil, errors.New("invalid id")
}
event, err := s.queries.GetTipoEventoByID(ctx, pgtype.UUID{Bytes: uuidVal, Valid: true})
if err != nil {
return nil, err
}
return &event, nil
}
func (s *Service) Update(ctx context.Context, id, nome string) (*generated.TiposEvento, error) {
uuidVal, err := uuid.Parse(id)
if err != nil {
return nil, errors.New("invalid id")
}
event, err := s.queries.UpdateTipoEvento(ctx, generated.UpdateTipoEventoParams{
ID: pgtype.UUID{Bytes: uuidVal, Valid: true},
Nome: nome,
})
if err != nil {
return nil, err
}
return &event, nil
}
func (s *Service) Delete(ctx context.Context, id string) error {
uuidVal, err := uuid.Parse(id)
if err != nil {
return errors.New("invalid id")
}
return s.queries.DeleteTipoEvento(ctx, pgtype.UUID{Bytes: uuidVal, Valid: true})
}
// Pricing Logic
type PriceInput struct {
TipoEventoID string `json:"tipo_evento_id"`
FuncaoProfissionalID string `json:"funcao_profissional_id"`
Valor float64 `json:"valor"`
}
func (s *Service) SetPrice(ctx context.Context, input PriceInput) (*generated.PrecosTiposEvento, error) {
eventoUUID, err := uuid.Parse(input.TipoEventoID)
if err != nil {
return nil, errors.New("invalid tipo_evento_id")
}
funcaoUUID, err := uuid.Parse(input.FuncaoProfissionalID)
if err != nil {
return nil, errors.New("invalid funcao_profissional_id")
}
precio, err := s.queries.CreatePrecoEvento(ctx, generated.CreatePrecoEventoParams{
TipoEventoID: pgtype.UUID{Bytes: eventoUUID, Valid: true},
FuncaoProfissionalID: pgtype.UUID{Bytes: funcaoUUID, Valid: true},
Valor: toPgNumeric(input.Valor),
})
if err != nil {
return nil, err
}
return &precio, nil
}
func (s *Service) ListPrices(ctx context.Context, eventoID string) ([]generated.ListPrecosByEventoIDRow, error) {
eventoUUID, err := uuid.Parse(eventoID)
if err != nil {
return nil, errors.New("invalid evento_id")
}
return s.queries.ListPrecosByEventoID(ctx, pgtype.UUID{Bytes: eventoUUID, Valid: true})
}
// Helper (Assuming user doesn't have it in shared utils or similar)
func toPgNumeric(f float64) pgtype.Numeric {
var n pgtype.Numeric
n.Scan(f)
return n
}

View file

@ -0,0 +1,133 @@
package tipos_servicos
import (
"net/http"
"strings"
"photum-backend/internal/db/generated"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type Handler struct {
service *Service
}
func NewHandler(service *Service) *Handler {
return &Handler{service: service}
}
type TipoServicoResponse struct {
ID string `json:"id"`
Nome string `json:"nome"`
}
type CreateTipoServicoRequest struct {
Nome string `json:"nome" binding:"required"`
}
func toResponse(t generated.TiposServico) TipoServicoResponse {
return TipoServicoResponse{
ID: uuid.UUID(t.ID.Bytes).String(),
Nome: t.Nome,
}
}
// Create godoc
// @Summary Create a new service type
// @Tags tipos_servicos
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param request body CreateTipoServicoRequest true "Nome"
// @Success 201 {object} TipoServicoResponse
// @Failure 400 {object} map[string]string
// @Failure 409 {object} map[string]string
// @Router /api/tipos-servicos [post]
func (h *Handler) Create(c *gin.Context) {
var req CreateTipoServicoRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
tipo, err := h.service.Create(c.Request.Context(), req.Nome)
if err != nil {
if strings.Contains(err.Error(), "duplicate key value") {
c.JSON(http.StatusConflict, gin.H{"error": "Tipo de Servico already exists"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, toResponse(*tipo))
}
// List godoc
// @Summary List all service types
// @Tags tipos_servicos
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {array} TipoServicoResponse
// @Router /api/tipos-servicos [get]
func (h *Handler) List(c *gin.Context) {
tipos, err := h.service.List(c.Request.Context())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
var response []TipoServicoResponse
for _, tipo := range tipos {
response = append(response, toResponse(tipo))
}
c.JSON(http.StatusOK, response)
}
// Update godoc
// @Summary Update a service type
// @Tags tipos_servicos
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "ID"
// @Param request body CreateTipoServicoRequest true "Nome"
// @Success 200 {object} TipoServicoResponse
// @Router /api/tipos-servicos/{id} [put]
func (h *Handler) Update(c *gin.Context) {
id := c.Param("id")
var req CreateTipoServicoRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
tipo, err := h.service.Update(c.Request.Context(), id, req.Nome)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, toResponse(*tipo))
}
// Delete godoc
// @Summary Delete a service type
// @Tags tipos_servicos
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "ID"
// @Success 204 {object} nil
// @Router /api/tipos-servicos/{id} [delete]
func (h *Handler) Delete(c *gin.Context) {
id := c.Param("id")
if err := h.service.Delete(c.Request.Context(), id); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.Status(http.StatusNoContent)
}

View file

@ -0,0 +1,67 @@
package tipos_servicos
import (
"context"
"errors"
"photum-backend/internal/db/generated"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
)
type Service struct {
queries *generated.Queries
}
func NewService(queries *generated.Queries) *Service {
return &Service{queries: queries}
}
func (s *Service) Create(ctx context.Context, nome string) (*generated.TiposServico, error) {
tipo, err := s.queries.CreateTipoServico(ctx, nome)
if err != nil {
return nil, err
}
return &tipo, nil
}
func (s *Service) List(ctx context.Context) ([]generated.TiposServico, error) {
return s.queries.ListTiposServicos(ctx)
}
func (s *Service) GetByID(ctx context.Context, id string) (*generated.TiposServico, error) {
uuidVal, err := uuid.Parse(id)
if err != nil {
return nil, errors.New("invalid id")
}
tipo, err := s.queries.GetTipoServicoByID(ctx, pgtype.UUID{Bytes: uuidVal, Valid: true})
if err != nil {
return nil, err
}
return &tipo, nil
}
func (s *Service) Update(ctx context.Context, id, nome string) (*generated.TiposServico, error) {
uuidVal, err := uuid.Parse(id)
if err != nil {
return nil, errors.New("invalid id")
}
tipo, err := s.queries.UpdateTipoServico(ctx, generated.UpdateTipoServicoParams{
ID: pgtype.UUID{Bytes: uuidVal, Valid: true},
Nome: nome,
})
if err != nil {
return nil, err
}
return &tipo, nil
}
func (s *Service) Delete(ctx context.Context, id string) error {
uuidVal, err := uuid.Parse(id)
if err != nil {
return errors.New("invalid id")
}
return s.queries.DeleteTipoServico(ctx, pgtype.UUID{Bytes: uuidVal, Valid: true})
}