feat(auth): adiciona tipo profissional ao schema e corrige avatar
- Adiciona coluna `tipo_profissional` à tabela `usuarios` - Atualiza handlers e services do Backend Go para persistir o tipo - Atualiza registro no Frontend para enviar o nome da função (ex: "Cinegrafista") - Corrige uploads S3 para compatibilidade com Civo (PathStyle) - Script para definir política pública de leitura no bucket S3 - Adiciona fallback para imagens de avatar na Navbar
This commit is contained in:
parent
f625b6cd6c
commit
cd196a0275
24 changed files with 785 additions and 224 deletions
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"photum-backend/internal/empresas"
|
"photum-backend/internal/empresas"
|
||||||
"photum-backend/internal/funcoes"
|
"photum-backend/internal/funcoes"
|
||||||
"photum-backend/internal/profissionais"
|
"photum-backend/internal/profissionais"
|
||||||
|
"photum-backend/internal/storage"
|
||||||
"photum-backend/internal/tipos_eventos"
|
"photum-backend/internal/tipos_eventos"
|
||||||
"photum-backend/internal/tipos_servicos"
|
"photum-backend/internal/tipos_servicos"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -70,6 +71,7 @@ func main() {
|
||||||
tiposEventosService := tipos_eventos.NewService(queries)
|
tiposEventosService := tipos_eventos.NewService(queries)
|
||||||
cadastroFotService := cadastro_fot.NewService(queries)
|
cadastroFotService := cadastro_fot.NewService(queries)
|
||||||
agendaService := agenda.NewService(queries)
|
agendaService := agenda.NewService(queries)
|
||||||
|
s3Service := storage.NewS3Service(cfg)
|
||||||
|
|
||||||
// Seed Demo Users
|
// Seed Demo Users
|
||||||
if err := authService.EnsureDemoUsers(context.Background()); err != nil {
|
if err := authService.EnsureDemoUsers(context.Background()); err != nil {
|
||||||
|
|
@ -77,7 +79,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize handlers
|
// Initialize handlers
|
||||||
authHandler := auth.NewHandler(authService)
|
authHandler := auth.NewHandler(authService, s3Service)
|
||||||
profissionaisHandler := profissionais.NewHandler(profissionaisService)
|
profissionaisHandler := profissionais.NewHandler(profissionaisService)
|
||||||
funcoesHandler := funcoes.NewHandler(funcoesService)
|
funcoesHandler := funcoes.NewHandler(funcoesService)
|
||||||
cursosHandler := cursos.NewHandler(cursosService)
|
cursosHandler := cursos.NewHandler(cursosService)
|
||||||
|
|
@ -124,6 +126,7 @@ func main() {
|
||||||
authGroup.POST("/login", authHandler.Login)
|
authGroup.POST("/login", authHandler.Login)
|
||||||
authGroup.POST("/refresh", authHandler.Refresh)
|
authGroup.POST("/refresh", authHandler.Refresh)
|
||||||
authGroup.POST("/logout", authHandler.Logout)
|
authGroup.POST("/logout", authHandler.Logout)
|
||||||
|
authGroup.POST("/upload-url", authHandler.GetUploadURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public API Routes (Data Lists)
|
// Public API Routes (Data Lists)
|
||||||
|
|
|
||||||
66
backend/cmd/tools/set_bucket_public.go
Normal file
66
backend/cmd/tools/set_bucket_public.go
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"photum-backend/internal/config"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
awsConfig "github.com/aws/aws-sdk-go-v2/config"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/credentials"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Load config manually or assume env vars are set
|
||||||
|
cfg := config.LoadConfig()
|
||||||
|
|
||||||
|
// Custom Resolver for Civo Object Store
|
||||||
|
customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
|
||||||
|
return aws.Endpoint{
|
||||||
|
URL: cfg.S3Endpoint,
|
||||||
|
SigningRegion: region,
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
awsCfg, err := awsConfig.LoadDefaultConfig(context.TODO(),
|
||||||
|
awsConfig.WithRegion(cfg.S3Region),
|
||||||
|
awsConfig.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(cfg.S3AccessKey, cfg.S3SecretKey, "")),
|
||||||
|
awsConfig.WithEndpointResolverWithOptions(customResolver),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to load SDK config, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := s3.NewFromConfig(awsCfg, func(o *s3.Options) {
|
||||||
|
o.UsePathStyle = true
|
||||||
|
})
|
||||||
|
|
||||||
|
policy := fmt.Sprintf(`{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Sid": "PublicRead",
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Principal": "*",
|
||||||
|
"Action": ["s3:GetObject"],
|
||||||
|
"Resource": ["arn:aws:s3:::%s/*"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`, cfg.S3Bucket)
|
||||||
|
|
||||||
|
log.Printf("Setting policy for bucket: %s...", cfg.S3Bucket)
|
||||||
|
_, err = client.PutBucketPolicy(context.TODO(), &s3.PutBucketPolicyInput{
|
||||||
|
Bucket: aws.String(cfg.S3Bucket),
|
||||||
|
Policy: aws.String(policy),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error setting policy: %v", err)
|
||||||
|
log.Println("Ensure your credentials have permission to set bucket policies, or configure it manually in the Civo console.")
|
||||||
|
} else {
|
||||||
|
log.Println("Successfully set bucket policy to Public Read!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2401,7 +2401,7 @@ const docTemplate = `{
|
||||||
},
|
},
|
||||||
"/auth/register": {
|
"/auth/register": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Register a new user with email, password, name, phone and role",
|
"description": "Register a new user with email, password, name, phone, role and professional type",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
|
@ -2453,6 +2453,43 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/auth/upload-url": {
|
||||||
|
"post": {
|
||||||
|
"description": "Get a pre-signed URL to upload a file directly to S3/Civo",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"auth"
|
||||||
|
],
|
||||||
|
"summary": "Get S3 Presigned URL for upload",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Upload URL Request",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/auth.uploadURLRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
|
|
@ -2599,14 +2636,12 @@ const docTemplate = `{
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"empresa_id": {
|
"empresa_id": {
|
||||||
"description": "Optional, for EVENT_OWNER",
|
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"nome": {
|
"nome": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"role": {
|
"role": {
|
||||||
"description": "Role is now required",
|
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"senha": {
|
"senha": {
|
||||||
|
|
@ -2615,6 +2650,10 @@ const docTemplate = `{
|
||||||
},
|
},
|
||||||
"telefone": {
|
"telefone": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tipo_profissional": {
|
||||||
|
"description": "New field",
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -2629,6 +2668,21 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"auth.uploadURLRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"content_type",
|
||||||
|
"filename"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"content_type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"filename": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"auth.userResponse": {
|
"auth.userResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -2812,6 +2866,9 @@ const docTemplate = `{
|
||||||
"agencia": {
|
"agencia": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"avatar_url": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"banco": {
|
"banco": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
@ -2973,6 +3030,9 @@ const docTemplate = `{
|
||||||
"agencia": {
|
"agencia": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"avatar_url": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"banco": {
|
"banco": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2395,7 +2395,7 @@
|
||||||
},
|
},
|
||||||
"/auth/register": {
|
"/auth/register": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Register a new user with email, password, name, phone and role",
|
"description": "Register a new user with email, password, name, phone, role and professional type",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
|
@ -2447,6 +2447,43 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/auth/upload-url": {
|
||||||
|
"post": {
|
||||||
|
"description": "Get a pre-signed URL to upload a file directly to S3/Civo",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"auth"
|
||||||
|
],
|
||||||
|
"summary": "Get S3 Presigned URL for upload",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Upload URL Request",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/auth.uploadURLRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
|
|
@ -2593,14 +2630,12 @@
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"empresa_id": {
|
"empresa_id": {
|
||||||
"description": "Optional, for EVENT_OWNER",
|
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"nome": {
|
"nome": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"role": {
|
"role": {
|
||||||
"description": "Role is now required",
|
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"senha": {
|
"senha": {
|
||||||
|
|
@ -2609,6 +2644,10 @@
|
||||||
},
|
},
|
||||||
"telefone": {
|
"telefone": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tipo_profissional": {
|
||||||
|
"description": "New field",
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -2623,6 +2662,21 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"auth.uploadURLRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"content_type",
|
||||||
|
"filename"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"content_type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"filename": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"auth.userResponse": {
|
"auth.userResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -2806,6 +2860,9 @@
|
||||||
"agencia": {
|
"agencia": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"avatar_url": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"banco": {
|
"banco": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
@ -2967,6 +3024,9 @@
|
||||||
"agencia": {
|
"agencia": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"avatar_url": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"banco": {
|
"banco": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -92,18 +92,19 @@ definitions:
|
||||||
email:
|
email:
|
||||||
type: string
|
type: string
|
||||||
empresa_id:
|
empresa_id:
|
||||||
description: Optional, for EVENT_OWNER
|
|
||||||
type: string
|
type: string
|
||||||
nome:
|
nome:
|
||||||
type: string
|
type: string
|
||||||
role:
|
role:
|
||||||
description: Role is now required
|
|
||||||
type: string
|
type: string
|
||||||
senha:
|
senha:
|
||||||
minLength: 6
|
minLength: 6
|
||||||
type: string
|
type: string
|
||||||
telefone:
|
telefone:
|
||||||
type: string
|
type: string
|
||||||
|
tipo_profissional:
|
||||||
|
description: New field
|
||||||
|
type: string
|
||||||
required:
|
required:
|
||||||
- email
|
- email
|
||||||
- nome
|
- nome
|
||||||
|
|
@ -117,6 +118,16 @@ definitions:
|
||||||
required:
|
required:
|
||||||
- role
|
- role
|
||||||
type: object
|
type: object
|
||||||
|
auth.uploadURLRequest:
|
||||||
|
properties:
|
||||||
|
content_type:
|
||||||
|
type: string
|
||||||
|
filename:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- content_type
|
||||||
|
- filename
|
||||||
|
type: object
|
||||||
auth.userResponse:
|
auth.userResponse:
|
||||||
properties:
|
properties:
|
||||||
ativo:
|
ativo:
|
||||||
|
|
@ -236,6 +247,8 @@ definitions:
|
||||||
properties:
|
properties:
|
||||||
agencia:
|
agencia:
|
||||||
type: string
|
type: string
|
||||||
|
avatar_url:
|
||||||
|
type: string
|
||||||
banco:
|
banco:
|
||||||
type: string
|
type: string
|
||||||
carro_disponivel:
|
carro_disponivel:
|
||||||
|
|
@ -343,6 +356,8 @@ definitions:
|
||||||
properties:
|
properties:
|
||||||
agencia:
|
agencia:
|
||||||
type: string
|
type: string
|
||||||
|
avatar_url:
|
||||||
|
type: string
|
||||||
banco:
|
banco:
|
||||||
type: string
|
type: string
|
||||||
carro_disponivel:
|
carro_disponivel:
|
||||||
|
|
@ -1966,7 +1981,8 @@ paths:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
description: Register a new user with email, password, name, phone and role
|
description: Register a new user with email, password, name, phone, role and
|
||||||
|
professional type
|
||||||
parameters:
|
parameters:
|
||||||
- description: Register Request
|
- description: Register Request
|
||||||
in: body
|
in: body
|
||||||
|
|
@ -1998,6 +2014,30 @@ paths:
|
||||||
summary: Register a new user
|
summary: Register a new user
|
||||||
tags:
|
tags:
|
||||||
- auth
|
- auth
|
||||||
|
/auth/upload-url:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Get a pre-signed URL to upload a file directly to S3/Civo
|
||||||
|
parameters:
|
||||||
|
- description: Upload URL Request
|
||||||
|
in: body
|
||||||
|
name: request
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/auth.uploadURLRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
summary: Get S3 Presigned URL for upload
|
||||||
|
tags:
|
||||||
|
- auth
|
||||||
securityDefinitions:
|
securityDefinitions:
|
||||||
BearerAuth:
|
BearerAuth:
|
||||||
in: header
|
in: header
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,25 @@ require (
|
||||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||||
github.com/PuerkitoBio/purell v1.2.1 // indirect
|
github.com/PuerkitoBio/purell v1.2.1 // indirect
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.41.0 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.32.6 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.6 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.94.0 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect
|
||||||
|
github.com/aws/smithy-go v1.24.0 // indirect
|
||||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||||
github.com/bytedance/sonic v1.14.2 // indirect
|
github.com/bytedance/sonic v1.14.2 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.4.0 // indirect
|
github.com/bytedance/sonic/loader v0.4.0 // indirect
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,44 @@ github.com/PuerkitoBio/purell v1.2.1 h1:QsZ4TjvwiMpat6gBCBxEQI0rcS9ehtkKtSpiUnd9
|
||||||
github.com/PuerkitoBio/purell v1.2.1/go.mod h1:ZwHcC/82TOaovDi//J/804umJFFmbOHPngi8iYYv/Eo=
|
github.com/PuerkitoBio/purell v1.2.1/go.mod h1:ZwHcC/82TOaovDi//J/804umJFFmbOHPngi8iYYv/Eo=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4=
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
|
||||||
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU=
|
||||||
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4=
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.32.6 h1:hFLBGUKjmLAekvi1evLi5hVvFQtSo3GYwi+Bx4lpJf8=
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.32.6/go.mod h1:lcUL/gcd8WyjCrMnxez5OXkO3/rwcNmvfno62tnXNcI=
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.6 h1:F9vWao2TwjV2MyiyVS+duza0NIRtAslgLUM0vTA1ZaE=
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.6/go.mod h1:SgHzKjEVsdQr6Opor0ihgWtkWdfRAIwxYzSJ8O85VHY=
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k=
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16 h1:CjMzUs78RDDv4ROu3JnJn/Ig1r6ZD7/T2DXLLRpejic=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16/go.mod h1:uVW4OLBqbJXSHJYA9svT9BluSvvwbzLQ2Crf6UPzR3c=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7 h1:DIBqIrJ7hv+e4CmIk2z3pyKT+3B6qVMgRsawHiR3qso=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7/go.mod h1:vLm00xmBke75UmpNvOcZQ/Q30ZFjbczeLFqGx5urmGo=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 h1:NSbvS17MlI2lurYgXnCOLvCFX38sBW4eiVER7+kkgsU=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16/go.mod h1:SwT8Tmqd4sA6G1qaGdzWCJN99bUmPGHfRwwq3G5Qb+A=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.94.0 h1:SWTxh/EcUCDVqi/0s26V6pVUq0BBG7kx0tDTmF/hCgA=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.94.0/go.mod h1:79S2BdqCJpScXZA2y+cpZuocWsjGjJINyXnOsf5DTz8=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 h1:aM/Q24rIlS3bRAhTyFurowU8A0SMyGDtEOY/l/s/1Uw=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk=
|
||||||
|
github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=
|
||||||
|
github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
|
||||||
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||||
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
|
|
|
||||||
|
|
@ -5,31 +5,67 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"photum-backend/internal/profissionais"
|
"photum-backend/internal/profissionais"
|
||||||
|
"photum-backend/internal/storage"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
service *Service
|
service *Service
|
||||||
|
s3Service *storage.S3Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHandler(service *Service) *Handler {
|
func NewHandler(service *Service, s3Service *storage.S3Service) *Handler {
|
||||||
return &Handler{service: service}
|
return &Handler{service: service, s3Service: s3Service}
|
||||||
|
}
|
||||||
|
|
||||||
|
type uploadURLRequest struct {
|
||||||
|
Filename string `json:"filename" binding:"required"`
|
||||||
|
ContentType string `json:"content_type" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUploadURL godoc
|
||||||
|
// @Summary Get S3 Presigned URL for upload
|
||||||
|
// @Description Get a pre-signed URL to upload a file directly to S3/Civo
|
||||||
|
// @Tags auth
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param request body uploadURLRequest true "Upload URL Request"
|
||||||
|
// @Success 200 {object} map[string]string
|
||||||
|
// @Router /auth/upload-url [post]
|
||||||
|
func (h *Handler) GetUploadURL(c *gin.Context) {
|
||||||
|
var req uploadURLRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadURL, publicURL, err := h.s3Service.GeneratePresignedURL(req.Filename, req.ContentType)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"upload_url": uploadURL,
|
||||||
|
"public_url": publicURL,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type registerRequest struct {
|
type registerRequest struct {
|
||||||
Email string `json:"email" binding:"required,email"`
|
Email string `json:"email" binding:"required,email"`
|
||||||
Senha string `json:"senha" binding:"required,min=6"`
|
Senha string `json:"senha" binding:"required,min=6"`
|
||||||
Nome string `json:"nome" binding:"required"`
|
Nome string `json:"nome" binding:"required"`
|
||||||
Telefone string `json:"telefone"`
|
Telefone string `json:"telefone"`
|
||||||
Role string `json:"role" binding:"required"` // Role is now required
|
Role string `json:"role" binding:"required"`
|
||||||
EmpresaID string `json:"empresa_id"` // Optional, for EVENT_OWNER
|
EmpresaID string `json:"empresa_id"`
|
||||||
|
TipoProfissional string `json:"tipo_profissional"` // New field
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register godoc
|
// Register godoc
|
||||||
// @Summary Register a new user
|
// @Summary Register a new user
|
||||||
// @Description Register a new user with email, password, name, phone and role
|
// @Description Register a new user with email, password, name, phone, role and professional type
|
||||||
// @Tags auth
|
// @Tags auth
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
|
|
@ -47,16 +83,6 @@ func (h *Handler) Register(c *gin.Context) {
|
||||||
|
|
||||||
// Create professional data only if role is appropriate
|
// Create professional data only if role is appropriate
|
||||||
var profData *profissionais.CreateProfissionalInput
|
var profData *profissionais.CreateProfissionalInput
|
||||||
// For PHOTOGRAPHER or BUSINESS_OWNER, we might populate this if we were doing 1-step,
|
|
||||||
// but actually 'nome' and 'telefone' are passed as args now.
|
|
||||||
// We keep passing nil for profData because Service logic for Professionals relies on 'CreateProfissionalInput'
|
|
||||||
// However, I updated Service to take nome/telefone directly.
|
|
||||||
// Wait, the Service code I JUST wrote takes (email, senha, role, nome, telefone, empresaID, profissionalData).
|
|
||||||
// If role is Photographer, the Service code checks `profissionalData`.
|
|
||||||
// I should probably populate `profissionalData` if it's a professional.
|
|
||||||
|
|
||||||
// PHOTOGRAPHER role is handled by a separate flow (ProfessionalRegister) that calls CreateProfissional after Register.
|
|
||||||
// We skip creating the partial profile here to avoid duplicates.
|
|
||||||
if req.Role == "BUSINESS_OWNER" {
|
if req.Role == "BUSINESS_OWNER" {
|
||||||
profData = &profissionais.CreateProfissionalInput{
|
profData = &profissionais.CreateProfissionalInput{
|
||||||
Nome: req.Nome,
|
Nome: req.Nome,
|
||||||
|
|
@ -69,7 +95,7 @@ func (h *Handler) Register(c *gin.Context) {
|
||||||
empresaIDPtr = &req.EmpresaID
|
empresaIDPtr = &req.EmpresaID
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := h.service.Register(c.Request.Context(), req.Email, req.Senha, req.Role, req.Nome, req.Telefone, empresaIDPtr, profData)
|
user, err := h.service.Register(c.Request.Context(), req.Email, req.Senha, req.Role, req.Nome, req.Telefone, req.TipoProfissional, empresaIDPtr, profData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "duplicate key") {
|
if strings.Contains(err.Error(), "duplicate key") {
|
||||||
c.JSON(http.StatusConflict, gin.H{"error": "email already registered"})
|
c.JSON(http.StatusConflict, gin.H{"error": "email already registered"})
|
||||||
|
|
@ -218,6 +244,7 @@ func (h *Handler) Login(c *gin.Context) {
|
||||||
"funcao_profissional_id": uuid.UUID(profData.FuncaoProfissionalID.Bytes).String(),
|
"funcao_profissional_id": uuid.UUID(profData.FuncaoProfissionalID.Bytes).String(),
|
||||||
"funcao_profissional": profData.FuncaoNome.String,
|
"funcao_profissional": profData.FuncaoNome.String,
|
||||||
"equipamentos": profData.Equipamentos.String,
|
"equipamentos": profData.Equipamentos.String,
|
||||||
|
"avatar_url": profData.AvatarUrl.String,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ func NewService(queries *generated.Queries, profissionaisService *profissionais.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Register(ctx context.Context, email, senha, role, nome, telefone string, empresaID *string, profissionalData *profissionais.CreateProfissionalInput) (*generated.Usuario, error) {
|
func (s *Service) Register(ctx context.Context, email, senha, role, nome, telefone, tipoProfissional string, empresaID *string, profissionalData *profissionais.CreateProfissionalInput) (*generated.Usuario, error) {
|
||||||
// Hash password
|
// Hash password
|
||||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(senha), bcrypt.DefaultCost)
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(senha), bcrypt.DefaultCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -52,9 +52,10 @@ func (s *Service) Register(ctx context.Context, email, senha, role, nome, telefo
|
||||||
|
|
||||||
// Create user
|
// Create user
|
||||||
user, err := s.queries.CreateUsuario(ctx, generated.CreateUsuarioParams{
|
user, err := s.queries.CreateUsuario(ctx, generated.CreateUsuarioParams{
|
||||||
Email: email,
|
Email: email,
|
||||||
SenhaHash: string(hashedPassword),
|
SenhaHash: string(hashedPassword),
|
||||||
Role: role,
|
Role: role,
|
||||||
|
TipoProfissional: toPgText(&tipoProfissional),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -311,3 +312,10 @@ func (s *Service) GetUser(ctx context.Context, id string) (*generated.GetUsuario
|
||||||
}
|
}
|
||||||
return &user, nil
|
return &user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toPgText(s *string) pgtype.Text {
|
||||||
|
if s == nil {
|
||||||
|
return pgtype.Text{Valid: false}
|
||||||
|
}
|
||||||
|
return pgtype.Text{String: *s, Valid: true}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,11 @@ type Config struct {
|
||||||
JwtRefreshTTLDays int
|
JwtRefreshTTLDays int
|
||||||
CorsAllowedOrigins string
|
CorsAllowedOrigins string
|
||||||
SwaggerHost string
|
SwaggerHost string
|
||||||
|
S3Endpoint string
|
||||||
|
S3AccessKey string
|
||||||
|
S3SecretKey string
|
||||||
|
S3Bucket string
|
||||||
|
S3Region string
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadConfig() *Config {
|
func LoadConfig() *Config {
|
||||||
|
|
@ -36,6 +41,11 @@ func LoadConfig() *Config {
|
||||||
JwtRefreshTTLDays: getEnvAsInt("JWT_REFRESH_TTL_DAYS", 30),
|
JwtRefreshTTLDays: getEnvAsInt("JWT_REFRESH_TTL_DAYS", 30),
|
||||||
CorsAllowedOrigins: getEnv("CORS_ALLOWED_ORIGINS", "*"),
|
CorsAllowedOrigins: getEnv("CORS_ALLOWED_ORIGINS", "*"),
|
||||||
SwaggerHost: getEnv("SWAGGER_HOST", "localhost:8080"),
|
SwaggerHost: getEnv("SWAGGER_HOST", "localhost:8080"),
|
||||||
|
S3Endpoint: getEnv("S3_ENDPOINT", ""),
|
||||||
|
S3AccessKey: getEnv("S3_ACCESS_KEY", ""),
|
||||||
|
S3SecretKey: getEnv("S3_SECRET_KEY", ""),
|
||||||
|
S3Bucket: getEnv("S3_BUCKET", ""),
|
||||||
|
S3Region: getEnv("S3_REGION", "nyc1"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -198,7 +198,7 @@ func (q *Queries) GetAgenda(ctx context.Context, id pgtype.UUID) (Agenda, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAgendaProfessionals = `-- name: GetAgendaProfessionals :many
|
const getAgendaProfessionals = `-- name: GetAgendaProfessionals :many
|
||||||
SELECT p.id, p.usuario_id, p.nome, p.funcao_profissional_id, p.endereco, p.cidade, p.uf, p.whatsapp, p.cpf_cnpj_titular, p.banco, p.agencia, p.conta_pix, p.carro_disponivel, p.tem_estudio, p.qtd_estudio, p.tipo_cartao, p.observacao, p.qual_tec, p.educacao_simpatia, p.desempenho_evento, p.disp_horario, p.media, p.tabela_free, p.extra_por_equipamento, p.equipamentos, p.criado_em, p.atualizado_em, f.nome as funcao_nome, u.email
|
SELECT p.id, p.usuario_id, p.nome, p.funcao_profissional_id, p.endereco, p.cidade, p.uf, p.whatsapp, p.cpf_cnpj_titular, p.banco, p.agencia, p.conta_pix, p.carro_disponivel, p.tem_estudio, p.qtd_estudio, p.tipo_cartao, p.observacao, p.qual_tec, p.educacao_simpatia, p.desempenho_evento, p.disp_horario, p.media, p.tabela_free, p.extra_por_equipamento, p.equipamentos, p.avatar_url, p.criado_em, p.atualizado_em, f.nome as funcao_nome, u.email
|
||||||
FROM cadastro_profissionais p
|
FROM cadastro_profissionais p
|
||||||
JOIN agenda_profissionais ap ON p.id = ap.profissional_id
|
JOIN agenda_profissionais ap ON p.id = ap.profissional_id
|
||||||
LEFT JOIN funcoes_profissionais f ON p.funcao_profissional_id = f.id
|
LEFT JOIN funcoes_profissionais f ON p.funcao_profissional_id = f.id
|
||||||
|
|
@ -232,6 +232,7 @@ type GetAgendaProfessionalsRow struct {
|
||||||
TabelaFree pgtype.Text `json:"tabela_free"`
|
TabelaFree pgtype.Text `json:"tabela_free"`
|
||||||
ExtraPorEquipamento pgtype.Bool `json:"extra_por_equipamento"`
|
ExtraPorEquipamento pgtype.Bool `json:"extra_por_equipamento"`
|
||||||
Equipamentos pgtype.Text `json:"equipamentos"`
|
Equipamentos pgtype.Text `json:"equipamentos"`
|
||||||
|
AvatarUrl pgtype.Text `json:"avatar_url"`
|
||||||
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
||||||
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
||||||
FuncaoNome pgtype.Text `json:"funcao_nome"`
|
FuncaoNome pgtype.Text `json:"funcao_nome"`
|
||||||
|
|
@ -273,6 +274,7 @@ func (q *Queries) GetAgendaProfessionals(ctx context.Context, agendaID pgtype.UU
|
||||||
&i.TabelaFree,
|
&i.TabelaFree,
|
||||||
&i.ExtraPorEquipamento,
|
&i.ExtraPorEquipamento,
|
||||||
&i.Equipamentos,
|
&i.Equipamentos,
|
||||||
|
&i.AvatarUrl,
|
||||||
&i.CriadoEm,
|
&i.CriadoEm,
|
||||||
&i.AtualizadoEm,
|
&i.AtualizadoEm,
|
||||||
&i.FuncaoNome,
|
&i.FuncaoNome,
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,7 @@ type CadastroProfissionai struct {
|
||||||
TabelaFree pgtype.Text `json:"tabela_free"`
|
TabelaFree pgtype.Text `json:"tabela_free"`
|
||||||
ExtraPorEquipamento pgtype.Bool `json:"extra_por_equipamento"`
|
ExtraPorEquipamento pgtype.Bool `json:"extra_por_equipamento"`
|
||||||
Equipamentos pgtype.Text `json:"equipamentos"`
|
Equipamentos pgtype.Text `json:"equipamentos"`
|
||||||
|
AvatarUrl pgtype.Text `json:"avatar_url"`
|
||||||
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
||||||
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
||||||
}
|
}
|
||||||
|
|
@ -161,11 +162,12 @@ type TiposServico struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Usuario struct {
|
type Usuario struct {
|
||||||
ID pgtype.UUID `json:"id"`
|
ID pgtype.UUID `json:"id"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
SenhaHash string `json:"senha_hash"`
|
SenhaHash string `json:"senha_hash"`
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Ativo bool `json:"ativo"`
|
TipoProfissional pgtype.Text `json:"tipo_profissional"`
|
||||||
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
Ativo bool `json:"ativo"`
|
||||||
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
||||||
|
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,11 @@ INSERT INTO cadastro_profissionais (
|
||||||
cpf_cnpj_titular, banco, agencia, conta_pix, carro_disponivel,
|
cpf_cnpj_titular, banco, agencia, conta_pix, carro_disponivel,
|
||||||
tem_estudio, qtd_estudio, tipo_cartao, observacao, qual_tec,
|
tem_estudio, qtd_estudio, tipo_cartao, observacao, qual_tec,
|
||||||
educacao_simpatia, desempenho_evento, disp_horario, media,
|
educacao_simpatia, desempenho_evento, disp_horario, media,
|
||||||
tabela_free, extra_por_equipamento, equipamentos
|
tabela_free, extra_por_equipamento, equipamentos, avatar_url
|
||||||
) VALUES (
|
) VALUES (
|
||||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15,
|
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15,
|
||||||
$16, $17, $18, $19, $20, $21, $22, $23, $24
|
$16, $17, $18, $19, $20, $21, $22, $23, $24, $25
|
||||||
) RETURNING id, usuario_id, nome, funcao_profissional_id, endereco, cidade, uf, whatsapp, cpf_cnpj_titular, banco, agencia, conta_pix, carro_disponivel, tem_estudio, qtd_estudio, tipo_cartao, observacao, qual_tec, educacao_simpatia, desempenho_evento, disp_horario, media, tabela_free, extra_por_equipamento, equipamentos, criado_em, atualizado_em
|
) RETURNING id, usuario_id, nome, funcao_profissional_id, endereco, cidade, uf, whatsapp, cpf_cnpj_titular, banco, agencia, conta_pix, carro_disponivel, tem_estudio, qtd_estudio, tipo_cartao, observacao, qual_tec, educacao_simpatia, desempenho_evento, disp_horario, media, tabela_free, extra_por_equipamento, equipamentos, avatar_url, criado_em, atualizado_em
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateProfissionalParams struct {
|
type CreateProfissionalParams struct {
|
||||||
|
|
@ -49,6 +49,7 @@ type CreateProfissionalParams struct {
|
||||||
TabelaFree pgtype.Text `json:"tabela_free"`
|
TabelaFree pgtype.Text `json:"tabela_free"`
|
||||||
ExtraPorEquipamento pgtype.Bool `json:"extra_por_equipamento"`
|
ExtraPorEquipamento pgtype.Bool `json:"extra_por_equipamento"`
|
||||||
Equipamentos pgtype.Text `json:"equipamentos"`
|
Equipamentos pgtype.Text `json:"equipamentos"`
|
||||||
|
AvatarUrl pgtype.Text `json:"avatar_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateProfissional(ctx context.Context, arg CreateProfissionalParams) (CadastroProfissionai, error) {
|
func (q *Queries) CreateProfissional(ctx context.Context, arg CreateProfissionalParams) (CadastroProfissionai, error) {
|
||||||
|
|
@ -77,6 +78,7 @@ func (q *Queries) CreateProfissional(ctx context.Context, arg CreateProfissional
|
||||||
arg.TabelaFree,
|
arg.TabelaFree,
|
||||||
arg.ExtraPorEquipamento,
|
arg.ExtraPorEquipamento,
|
||||||
arg.Equipamentos,
|
arg.Equipamentos,
|
||||||
|
arg.AvatarUrl,
|
||||||
)
|
)
|
||||||
var i CadastroProfissionai
|
var i CadastroProfissionai
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
|
|
@ -105,6 +107,7 @@ func (q *Queries) CreateProfissional(ctx context.Context, arg CreateProfissional
|
||||||
&i.TabelaFree,
|
&i.TabelaFree,
|
||||||
&i.ExtraPorEquipamento,
|
&i.ExtraPorEquipamento,
|
||||||
&i.Equipamentos,
|
&i.Equipamentos,
|
||||||
|
&i.AvatarUrl,
|
||||||
&i.CriadoEm,
|
&i.CriadoEm,
|
||||||
&i.AtualizadoEm,
|
&i.AtualizadoEm,
|
||||||
)
|
)
|
||||||
|
|
@ -122,7 +125,7 @@ func (q *Queries) DeleteProfissional(ctx context.Context, id pgtype.UUID) error
|
||||||
}
|
}
|
||||||
|
|
||||||
const getProfissionalByID = `-- name: GetProfissionalByID :one
|
const getProfissionalByID = `-- name: GetProfissionalByID :one
|
||||||
SELECT p.id, p.usuario_id, p.nome, p.funcao_profissional_id, p.endereco, p.cidade, p.uf, p.whatsapp, p.cpf_cnpj_titular, p.banco, p.agencia, p.conta_pix, p.carro_disponivel, p.tem_estudio, p.qtd_estudio, p.tipo_cartao, p.observacao, p.qual_tec, p.educacao_simpatia, p.desempenho_evento, p.disp_horario, p.media, p.tabela_free, p.extra_por_equipamento, p.equipamentos, p.criado_em, p.atualizado_em, f.nome as funcao_nome
|
SELECT p.id, p.usuario_id, p.nome, p.funcao_profissional_id, p.endereco, p.cidade, p.uf, p.whatsapp, p.cpf_cnpj_titular, p.banco, p.agencia, p.conta_pix, p.carro_disponivel, p.tem_estudio, p.qtd_estudio, p.tipo_cartao, p.observacao, p.qual_tec, p.educacao_simpatia, p.desempenho_evento, p.disp_horario, p.media, p.tabela_free, p.extra_por_equipamento, p.equipamentos, p.avatar_url, p.criado_em, p.atualizado_em, f.nome as funcao_nome
|
||||||
FROM cadastro_profissionais p
|
FROM cadastro_profissionais p
|
||||||
LEFT JOIN funcoes_profissionais f ON p.funcao_profissional_id = f.id
|
LEFT JOIN funcoes_profissionais f ON p.funcao_profissional_id = f.id
|
||||||
WHERE p.id = $1 LIMIT 1
|
WHERE p.id = $1 LIMIT 1
|
||||||
|
|
@ -154,6 +157,7 @@ type GetProfissionalByIDRow struct {
|
||||||
TabelaFree pgtype.Text `json:"tabela_free"`
|
TabelaFree pgtype.Text `json:"tabela_free"`
|
||||||
ExtraPorEquipamento pgtype.Bool `json:"extra_por_equipamento"`
|
ExtraPorEquipamento pgtype.Bool `json:"extra_por_equipamento"`
|
||||||
Equipamentos pgtype.Text `json:"equipamentos"`
|
Equipamentos pgtype.Text `json:"equipamentos"`
|
||||||
|
AvatarUrl pgtype.Text `json:"avatar_url"`
|
||||||
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
||||||
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
||||||
FuncaoNome pgtype.Text `json:"funcao_nome"`
|
FuncaoNome pgtype.Text `json:"funcao_nome"`
|
||||||
|
|
@ -188,6 +192,7 @@ func (q *Queries) GetProfissionalByID(ctx context.Context, id pgtype.UUID) (GetP
|
||||||
&i.TabelaFree,
|
&i.TabelaFree,
|
||||||
&i.ExtraPorEquipamento,
|
&i.ExtraPorEquipamento,
|
||||||
&i.Equipamentos,
|
&i.Equipamentos,
|
||||||
|
&i.AvatarUrl,
|
||||||
&i.CriadoEm,
|
&i.CriadoEm,
|
||||||
&i.AtualizadoEm,
|
&i.AtualizadoEm,
|
||||||
&i.FuncaoNome,
|
&i.FuncaoNome,
|
||||||
|
|
@ -196,7 +201,7 @@ func (q *Queries) GetProfissionalByID(ctx context.Context, id pgtype.UUID) (GetP
|
||||||
}
|
}
|
||||||
|
|
||||||
const getProfissionalByUsuarioID = `-- name: GetProfissionalByUsuarioID :one
|
const getProfissionalByUsuarioID = `-- name: GetProfissionalByUsuarioID :one
|
||||||
SELECT p.id, p.usuario_id, p.nome, p.funcao_profissional_id, p.endereco, p.cidade, p.uf, p.whatsapp, p.cpf_cnpj_titular, p.banco, p.agencia, p.conta_pix, p.carro_disponivel, p.tem_estudio, p.qtd_estudio, p.tipo_cartao, p.observacao, p.qual_tec, p.educacao_simpatia, p.desempenho_evento, p.disp_horario, p.media, p.tabela_free, p.extra_por_equipamento, p.equipamentos, p.criado_em, p.atualizado_em, f.nome as funcao_nome
|
SELECT p.id, p.usuario_id, p.nome, p.funcao_profissional_id, p.endereco, p.cidade, p.uf, p.whatsapp, p.cpf_cnpj_titular, p.banco, p.agencia, p.conta_pix, p.carro_disponivel, p.tem_estudio, p.qtd_estudio, p.tipo_cartao, p.observacao, p.qual_tec, p.educacao_simpatia, p.desempenho_evento, p.disp_horario, p.media, p.tabela_free, p.extra_por_equipamento, p.equipamentos, p.avatar_url, p.criado_em, p.atualizado_em, f.nome as funcao_nome
|
||||||
FROM cadastro_profissionais p
|
FROM cadastro_profissionais p
|
||||||
LEFT JOIN funcoes_profissionais f ON p.funcao_profissional_id = f.id
|
LEFT JOIN funcoes_profissionais f ON p.funcao_profissional_id = f.id
|
||||||
WHERE p.usuario_id = $1 LIMIT 1
|
WHERE p.usuario_id = $1 LIMIT 1
|
||||||
|
|
@ -228,6 +233,7 @@ type GetProfissionalByUsuarioIDRow struct {
|
||||||
TabelaFree pgtype.Text `json:"tabela_free"`
|
TabelaFree pgtype.Text `json:"tabela_free"`
|
||||||
ExtraPorEquipamento pgtype.Bool `json:"extra_por_equipamento"`
|
ExtraPorEquipamento pgtype.Bool `json:"extra_por_equipamento"`
|
||||||
Equipamentos pgtype.Text `json:"equipamentos"`
|
Equipamentos pgtype.Text `json:"equipamentos"`
|
||||||
|
AvatarUrl pgtype.Text `json:"avatar_url"`
|
||||||
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
||||||
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
||||||
FuncaoNome pgtype.Text `json:"funcao_nome"`
|
FuncaoNome pgtype.Text `json:"funcao_nome"`
|
||||||
|
|
@ -262,6 +268,7 @@ func (q *Queries) GetProfissionalByUsuarioID(ctx context.Context, usuarioID pgty
|
||||||
&i.TabelaFree,
|
&i.TabelaFree,
|
||||||
&i.ExtraPorEquipamento,
|
&i.ExtraPorEquipamento,
|
||||||
&i.Equipamentos,
|
&i.Equipamentos,
|
||||||
|
&i.AvatarUrl,
|
||||||
&i.CriadoEm,
|
&i.CriadoEm,
|
||||||
&i.AtualizadoEm,
|
&i.AtualizadoEm,
|
||||||
&i.FuncaoNome,
|
&i.FuncaoNome,
|
||||||
|
|
@ -270,7 +277,7 @@ func (q *Queries) GetProfissionalByUsuarioID(ctx context.Context, usuarioID pgty
|
||||||
}
|
}
|
||||||
|
|
||||||
const listProfissionais = `-- name: ListProfissionais :many
|
const listProfissionais = `-- name: ListProfissionais :many
|
||||||
SELECT p.id, p.usuario_id, p.nome, p.funcao_profissional_id, p.endereco, p.cidade, p.uf, p.whatsapp, p.cpf_cnpj_titular, p.banco, p.agencia, p.conta_pix, p.carro_disponivel, p.tem_estudio, p.qtd_estudio, p.tipo_cartao, p.observacao, p.qual_tec, p.educacao_simpatia, p.desempenho_evento, p.disp_horario, p.media, p.tabela_free, p.extra_por_equipamento, p.equipamentos, p.criado_em, p.atualizado_em, f.nome as funcao_nome, u.email
|
SELECT p.id, p.usuario_id, p.nome, p.funcao_profissional_id, p.endereco, p.cidade, p.uf, p.whatsapp, p.cpf_cnpj_titular, p.banco, p.agencia, p.conta_pix, p.carro_disponivel, p.tem_estudio, p.qtd_estudio, p.tipo_cartao, p.observacao, p.qual_tec, p.educacao_simpatia, p.desempenho_evento, p.disp_horario, p.media, p.tabela_free, p.extra_por_equipamento, p.equipamentos, p.avatar_url, p.criado_em, p.atualizado_em, f.nome as funcao_nome, u.email
|
||||||
FROM cadastro_profissionais p
|
FROM cadastro_profissionais p
|
||||||
LEFT JOIN funcoes_profissionais f ON p.funcao_profissional_id = f.id
|
LEFT JOIN funcoes_profissionais f ON p.funcao_profissional_id = f.id
|
||||||
LEFT JOIN usuarios u ON p.usuario_id = u.id
|
LEFT JOIN usuarios u ON p.usuario_id = u.id
|
||||||
|
|
@ -303,6 +310,7 @@ type ListProfissionaisRow struct {
|
||||||
TabelaFree pgtype.Text `json:"tabela_free"`
|
TabelaFree pgtype.Text `json:"tabela_free"`
|
||||||
ExtraPorEquipamento pgtype.Bool `json:"extra_por_equipamento"`
|
ExtraPorEquipamento pgtype.Bool `json:"extra_por_equipamento"`
|
||||||
Equipamentos pgtype.Text `json:"equipamentos"`
|
Equipamentos pgtype.Text `json:"equipamentos"`
|
||||||
|
AvatarUrl pgtype.Text `json:"avatar_url"`
|
||||||
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
||||||
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
||||||
FuncaoNome pgtype.Text `json:"funcao_nome"`
|
FuncaoNome pgtype.Text `json:"funcao_nome"`
|
||||||
|
|
@ -344,6 +352,7 @@ func (q *Queries) ListProfissionais(ctx context.Context) ([]ListProfissionaisRow
|
||||||
&i.TabelaFree,
|
&i.TabelaFree,
|
||||||
&i.ExtraPorEquipamento,
|
&i.ExtraPorEquipamento,
|
||||||
&i.Equipamentos,
|
&i.Equipamentos,
|
||||||
|
&i.AvatarUrl,
|
||||||
&i.CriadoEm,
|
&i.CriadoEm,
|
||||||
&i.AtualizadoEm,
|
&i.AtualizadoEm,
|
||||||
&i.FuncaoNome,
|
&i.FuncaoNome,
|
||||||
|
|
@ -385,9 +394,10 @@ SET
|
||||||
tabela_free = $22,
|
tabela_free = $22,
|
||||||
extra_por_equipamento = $23,
|
extra_por_equipamento = $23,
|
||||||
equipamentos = $24,
|
equipamentos = $24,
|
||||||
|
avatar_url = $25,
|
||||||
atualizado_em = NOW()
|
atualizado_em = NOW()
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
RETURNING id, usuario_id, nome, funcao_profissional_id, endereco, cidade, uf, whatsapp, cpf_cnpj_titular, banco, agencia, conta_pix, carro_disponivel, tem_estudio, qtd_estudio, tipo_cartao, observacao, qual_tec, educacao_simpatia, desempenho_evento, disp_horario, media, tabela_free, extra_por_equipamento, equipamentos, criado_em, atualizado_em
|
RETURNING id, usuario_id, nome, funcao_profissional_id, endereco, cidade, uf, whatsapp, cpf_cnpj_titular, banco, agencia, conta_pix, carro_disponivel, tem_estudio, qtd_estudio, tipo_cartao, observacao, qual_tec, educacao_simpatia, desempenho_evento, disp_horario, media, tabela_free, extra_por_equipamento, equipamentos, avatar_url, criado_em, atualizado_em
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateProfissionalParams struct {
|
type UpdateProfissionalParams struct {
|
||||||
|
|
@ -415,6 +425,7 @@ type UpdateProfissionalParams struct {
|
||||||
TabelaFree pgtype.Text `json:"tabela_free"`
|
TabelaFree pgtype.Text `json:"tabela_free"`
|
||||||
ExtraPorEquipamento pgtype.Bool `json:"extra_por_equipamento"`
|
ExtraPorEquipamento pgtype.Bool `json:"extra_por_equipamento"`
|
||||||
Equipamentos pgtype.Text `json:"equipamentos"`
|
Equipamentos pgtype.Text `json:"equipamentos"`
|
||||||
|
AvatarUrl pgtype.Text `json:"avatar_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpdateProfissional(ctx context.Context, arg UpdateProfissionalParams) (CadastroProfissionai, error) {
|
func (q *Queries) UpdateProfissional(ctx context.Context, arg UpdateProfissionalParams) (CadastroProfissionai, error) {
|
||||||
|
|
@ -443,6 +454,7 @@ func (q *Queries) UpdateProfissional(ctx context.Context, arg UpdateProfissional
|
||||||
arg.TabelaFree,
|
arg.TabelaFree,
|
||||||
arg.ExtraPorEquipamento,
|
arg.ExtraPorEquipamento,
|
||||||
arg.Equipamentos,
|
arg.Equipamentos,
|
||||||
|
arg.AvatarUrl,
|
||||||
)
|
)
|
||||||
var i CadastroProfissionai
|
var i CadastroProfissionai
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
|
|
@ -471,6 +483,7 @@ func (q *Queries) UpdateProfissional(ctx context.Context, arg UpdateProfissional
|
||||||
&i.TabelaFree,
|
&i.TabelaFree,
|
||||||
&i.ExtraPorEquipamento,
|
&i.ExtraPorEquipamento,
|
||||||
&i.Equipamentos,
|
&i.Equipamentos,
|
||||||
|
&i.AvatarUrl,
|
||||||
&i.CriadoEm,
|
&i.CriadoEm,
|
||||||
&i.AtualizadoEm,
|
&i.AtualizadoEm,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -45,25 +45,32 @@ func (q *Queries) CreateCadastroCliente(ctx context.Context, arg CreateCadastroC
|
||||||
}
|
}
|
||||||
|
|
||||||
const createUsuario = `-- name: CreateUsuario :one
|
const createUsuario = `-- name: CreateUsuario :one
|
||||||
INSERT INTO usuarios (email, senha_hash, role, ativo)
|
INSERT INTO usuarios (email, senha_hash, role, tipo_profissional, ativo)
|
||||||
VALUES ($1, $2, $3, false)
|
VALUES ($1, $2, $3, $4, false)
|
||||||
RETURNING id, email, senha_hash, role, ativo, criado_em, atualizado_em
|
RETURNING id, email, senha_hash, role, tipo_profissional, ativo, criado_em, atualizado_em
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateUsuarioParams struct {
|
type CreateUsuarioParams struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
SenhaHash string `json:"senha_hash"`
|
SenhaHash string `json:"senha_hash"`
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
|
TipoProfissional pgtype.Text `json:"tipo_profissional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateUsuario(ctx context.Context, arg CreateUsuarioParams) (Usuario, error) {
|
func (q *Queries) CreateUsuario(ctx context.Context, arg CreateUsuarioParams) (Usuario, error) {
|
||||||
row := q.db.QueryRow(ctx, createUsuario, arg.Email, arg.SenhaHash, arg.Role)
|
row := q.db.QueryRow(ctx, createUsuario,
|
||||||
|
arg.Email,
|
||||||
|
arg.SenhaHash,
|
||||||
|
arg.Role,
|
||||||
|
arg.TipoProfissional,
|
||||||
|
)
|
||||||
var i Usuario
|
var i Usuario
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.Email,
|
&i.Email,
|
||||||
&i.SenhaHash,
|
&i.SenhaHash,
|
||||||
&i.Role,
|
&i.Role,
|
||||||
|
&i.TipoProfissional,
|
||||||
&i.Ativo,
|
&i.Ativo,
|
||||||
&i.CriadoEm,
|
&i.CriadoEm,
|
||||||
&i.AtualizadoEm,
|
&i.AtualizadoEm,
|
||||||
|
|
@ -82,7 +89,7 @@ func (q *Queries) DeleteUsuario(ctx context.Context, id pgtype.UUID) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
const getUsuarioByEmail = `-- name: GetUsuarioByEmail :one
|
const getUsuarioByEmail = `-- name: GetUsuarioByEmail :one
|
||||||
SELECT u.id, u.email, u.senha_hash, u.role, u.ativo, u.criado_em, u.atualizado_em,
|
SELECT u.id, u.email, u.senha_hash, u.role, u.tipo_profissional, u.ativo, u.criado_em, u.atualizado_em,
|
||||||
COALESCE(cp.nome, cc.nome, '') as nome,
|
COALESCE(cp.nome, cc.nome, '') as nome,
|
||||||
COALESCE(cp.whatsapp, cc.telefone, '') as whatsapp,
|
COALESCE(cp.whatsapp, cc.telefone, '') as whatsapp,
|
||||||
e.id as empresa_id,
|
e.id as empresa_id,
|
||||||
|
|
@ -95,17 +102,18 @@ WHERE u.email = $1 LIMIT 1
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetUsuarioByEmailRow struct {
|
type GetUsuarioByEmailRow struct {
|
||||||
ID pgtype.UUID `json:"id"`
|
ID pgtype.UUID `json:"id"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
SenhaHash string `json:"senha_hash"`
|
SenhaHash string `json:"senha_hash"`
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Ativo bool `json:"ativo"`
|
TipoProfissional pgtype.Text `json:"tipo_profissional"`
|
||||||
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
Ativo bool `json:"ativo"`
|
||||||
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
||||||
Nome string `json:"nome"`
|
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
||||||
Whatsapp string `json:"whatsapp"`
|
Nome string `json:"nome"`
|
||||||
EmpresaID pgtype.UUID `json:"empresa_id"`
|
Whatsapp string `json:"whatsapp"`
|
||||||
EmpresaNome pgtype.Text `json:"empresa_nome"`
|
EmpresaID pgtype.UUID `json:"empresa_id"`
|
||||||
|
EmpresaNome pgtype.Text `json:"empresa_nome"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetUsuarioByEmail(ctx context.Context, email string) (GetUsuarioByEmailRow, error) {
|
func (q *Queries) GetUsuarioByEmail(ctx context.Context, email string) (GetUsuarioByEmailRow, error) {
|
||||||
|
|
@ -116,6 +124,7 @@ func (q *Queries) GetUsuarioByEmail(ctx context.Context, email string) (GetUsuar
|
||||||
&i.Email,
|
&i.Email,
|
||||||
&i.SenhaHash,
|
&i.SenhaHash,
|
||||||
&i.Role,
|
&i.Role,
|
||||||
|
&i.TipoProfissional,
|
||||||
&i.Ativo,
|
&i.Ativo,
|
||||||
&i.CriadoEm,
|
&i.CriadoEm,
|
||||||
&i.AtualizadoEm,
|
&i.AtualizadoEm,
|
||||||
|
|
@ -128,7 +137,7 @@ func (q *Queries) GetUsuarioByEmail(ctx context.Context, email string) (GetUsuar
|
||||||
}
|
}
|
||||||
|
|
||||||
const getUsuarioByID = `-- name: GetUsuarioByID :one
|
const getUsuarioByID = `-- name: GetUsuarioByID :one
|
||||||
SELECT u.id, u.email, u.senha_hash, u.role, u.ativo, u.criado_em, u.atualizado_em,
|
SELECT u.id, u.email, u.senha_hash, u.role, u.tipo_profissional, u.ativo, u.criado_em, u.atualizado_em,
|
||||||
COALESCE(cp.nome, cc.nome, '') as nome,
|
COALESCE(cp.nome, cc.nome, '') as nome,
|
||||||
COALESCE(cp.whatsapp, cc.telefone, '') as whatsapp,
|
COALESCE(cp.whatsapp, cc.telefone, '') as whatsapp,
|
||||||
e.id as empresa_id,
|
e.id as empresa_id,
|
||||||
|
|
@ -141,17 +150,18 @@ WHERE u.id = $1 LIMIT 1
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetUsuarioByIDRow struct {
|
type GetUsuarioByIDRow struct {
|
||||||
ID pgtype.UUID `json:"id"`
|
ID pgtype.UUID `json:"id"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
SenhaHash string `json:"senha_hash"`
|
SenhaHash string `json:"senha_hash"`
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Ativo bool `json:"ativo"`
|
TipoProfissional pgtype.Text `json:"tipo_profissional"`
|
||||||
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
Ativo bool `json:"ativo"`
|
||||||
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
||||||
Nome string `json:"nome"`
|
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
||||||
Whatsapp string `json:"whatsapp"`
|
Nome string `json:"nome"`
|
||||||
EmpresaID pgtype.UUID `json:"empresa_id"`
|
Whatsapp string `json:"whatsapp"`
|
||||||
EmpresaNome pgtype.Text `json:"empresa_nome"`
|
EmpresaID pgtype.UUID `json:"empresa_id"`
|
||||||
|
EmpresaNome pgtype.Text `json:"empresa_nome"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetUsuarioByID(ctx context.Context, id pgtype.UUID) (GetUsuarioByIDRow, error) {
|
func (q *Queries) GetUsuarioByID(ctx context.Context, id pgtype.UUID) (GetUsuarioByIDRow, error) {
|
||||||
|
|
@ -162,6 +172,7 @@ func (q *Queries) GetUsuarioByID(ctx context.Context, id pgtype.UUID) (GetUsuari
|
||||||
&i.Email,
|
&i.Email,
|
||||||
&i.SenhaHash,
|
&i.SenhaHash,
|
||||||
&i.Role,
|
&i.Role,
|
||||||
|
&i.TipoProfissional,
|
||||||
&i.Ativo,
|
&i.Ativo,
|
||||||
&i.CriadoEm,
|
&i.CriadoEm,
|
||||||
&i.AtualizadoEm,
|
&i.AtualizadoEm,
|
||||||
|
|
@ -174,18 +185,19 @@ func (q *Queries) GetUsuarioByID(ctx context.Context, id pgtype.UUID) (GetUsuari
|
||||||
}
|
}
|
||||||
|
|
||||||
const listAllUsuarios = `-- name: ListAllUsuarios :many
|
const listAllUsuarios = `-- name: ListAllUsuarios :many
|
||||||
SELECT id, email, role, ativo, criado_em, atualizado_em
|
SELECT id, email, role, tipo_profissional, ativo, criado_em, atualizado_em
|
||||||
FROM usuarios
|
FROM usuarios
|
||||||
ORDER BY criado_em DESC
|
ORDER BY criado_em DESC
|
||||||
`
|
`
|
||||||
|
|
||||||
type ListAllUsuariosRow struct {
|
type ListAllUsuariosRow struct {
|
||||||
ID pgtype.UUID `json:"id"`
|
ID pgtype.UUID `json:"id"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Ativo bool `json:"ativo"`
|
TipoProfissional pgtype.Text `json:"tipo_profissional"`
|
||||||
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
Ativo bool `json:"ativo"`
|
||||||
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
||||||
|
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) ListAllUsuarios(ctx context.Context) ([]ListAllUsuariosRow, error) {
|
func (q *Queries) ListAllUsuarios(ctx context.Context) ([]ListAllUsuariosRow, error) {
|
||||||
|
|
@ -201,6 +213,7 @@ func (q *Queries) ListAllUsuarios(ctx context.Context) ([]ListAllUsuariosRow, er
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.Email,
|
&i.Email,
|
||||||
&i.Role,
|
&i.Role,
|
||||||
|
&i.TipoProfissional,
|
||||||
&i.Ativo,
|
&i.Ativo,
|
||||||
&i.CriadoEm,
|
&i.CriadoEm,
|
||||||
&i.AtualizadoEm,
|
&i.AtualizadoEm,
|
||||||
|
|
@ -216,7 +229,7 @@ func (q *Queries) ListAllUsuarios(ctx context.Context) ([]ListAllUsuariosRow, er
|
||||||
}
|
}
|
||||||
|
|
||||||
const listUsuariosPending = `-- name: ListUsuariosPending :many
|
const listUsuariosPending = `-- name: ListUsuariosPending :many
|
||||||
SELECT u.id, u.email, u.role, u.ativo, u.criado_em,
|
SELECT u.id, u.email, u.role, u.tipo_profissional, u.ativo, u.criado_em,
|
||||||
COALESCE(cp.nome, cc.nome, '') as nome,
|
COALESCE(cp.nome, cc.nome, '') as nome,
|
||||||
COALESCE(cp.whatsapp, cc.telefone, '') as whatsapp,
|
COALESCE(cp.whatsapp, cc.telefone, '') as whatsapp,
|
||||||
e.id as empresa_id,
|
e.id as empresa_id,
|
||||||
|
|
@ -230,15 +243,16 @@ ORDER BY u.criado_em DESC
|
||||||
`
|
`
|
||||||
|
|
||||||
type ListUsuariosPendingRow struct {
|
type ListUsuariosPendingRow struct {
|
||||||
ID pgtype.UUID `json:"id"`
|
ID pgtype.UUID `json:"id"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Ativo bool `json:"ativo"`
|
TipoProfissional pgtype.Text `json:"tipo_profissional"`
|
||||||
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
Ativo bool `json:"ativo"`
|
||||||
Nome string `json:"nome"`
|
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
||||||
Whatsapp string `json:"whatsapp"`
|
Nome string `json:"nome"`
|
||||||
EmpresaID pgtype.UUID `json:"empresa_id"`
|
Whatsapp string `json:"whatsapp"`
|
||||||
EmpresaNome pgtype.Text `json:"empresa_nome"`
|
EmpresaID pgtype.UUID `json:"empresa_id"`
|
||||||
|
EmpresaNome pgtype.Text `json:"empresa_nome"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) ListUsuariosPending(ctx context.Context) ([]ListUsuariosPendingRow, error) {
|
func (q *Queries) ListUsuariosPending(ctx context.Context) ([]ListUsuariosPendingRow, error) {
|
||||||
|
|
@ -254,6 +268,7 @@ func (q *Queries) ListUsuariosPending(ctx context.Context) ([]ListUsuariosPendin
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.Email,
|
&i.Email,
|
||||||
&i.Role,
|
&i.Role,
|
||||||
|
&i.TipoProfissional,
|
||||||
&i.Ativo,
|
&i.Ativo,
|
||||||
&i.CriadoEm,
|
&i.CriadoEm,
|
||||||
&i.Nome,
|
&i.Nome,
|
||||||
|
|
@ -275,7 +290,7 @@ const updateUsuarioAtivo = `-- name: UpdateUsuarioAtivo :one
|
||||||
UPDATE usuarios
|
UPDATE usuarios
|
||||||
SET ativo = $2, atualizado_em = NOW()
|
SET ativo = $2, atualizado_em = NOW()
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
RETURNING id, email, senha_hash, role, ativo, criado_em, atualizado_em
|
RETURNING id, email, senha_hash, role, tipo_profissional, ativo, criado_em, atualizado_em
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateUsuarioAtivoParams struct {
|
type UpdateUsuarioAtivoParams struct {
|
||||||
|
|
@ -291,6 +306,7 @@ func (q *Queries) UpdateUsuarioAtivo(ctx context.Context, arg UpdateUsuarioAtivo
|
||||||
&i.Email,
|
&i.Email,
|
||||||
&i.SenhaHash,
|
&i.SenhaHash,
|
||||||
&i.Role,
|
&i.Role,
|
||||||
|
&i.TipoProfissional,
|
||||||
&i.Ativo,
|
&i.Ativo,
|
||||||
&i.CriadoEm,
|
&i.CriadoEm,
|
||||||
&i.AtualizadoEm,
|
&i.AtualizadoEm,
|
||||||
|
|
@ -302,7 +318,7 @@ const updateUsuarioRole = `-- name: UpdateUsuarioRole :one
|
||||||
UPDATE usuarios
|
UPDATE usuarios
|
||||||
SET role = $2, atualizado_em = NOW()
|
SET role = $2, atualizado_em = NOW()
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
RETURNING id, email, senha_hash, role, ativo, criado_em, atualizado_em
|
RETURNING id, email, senha_hash, role, tipo_profissional, ativo, criado_em, atualizado_em
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateUsuarioRoleParams struct {
|
type UpdateUsuarioRoleParams struct {
|
||||||
|
|
@ -318,6 +334,7 @@ func (q *Queries) UpdateUsuarioRole(ctx context.Context, arg UpdateUsuarioRolePa
|
||||||
&i.Email,
|
&i.Email,
|
||||||
&i.SenhaHash,
|
&i.SenhaHash,
|
||||||
&i.Role,
|
&i.Role,
|
||||||
|
&i.TipoProfissional,
|
||||||
&i.Ativo,
|
&i.Ativo,
|
||||||
&i.CriadoEm,
|
&i.CriadoEm,
|
||||||
&i.AtualizadoEm,
|
&i.AtualizadoEm,
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ INSERT INTO cadastro_profissionais (
|
||||||
cpf_cnpj_titular, banco, agencia, conta_pix, carro_disponivel,
|
cpf_cnpj_titular, banco, agencia, conta_pix, carro_disponivel,
|
||||||
tem_estudio, qtd_estudio, tipo_cartao, observacao, qual_tec,
|
tem_estudio, qtd_estudio, tipo_cartao, observacao, qual_tec,
|
||||||
educacao_simpatia, desempenho_evento, disp_horario, media,
|
educacao_simpatia, desempenho_evento, disp_horario, media,
|
||||||
tabela_free, extra_por_equipamento, equipamentos
|
tabela_free, extra_por_equipamento, equipamentos, avatar_url
|
||||||
) VALUES (
|
) VALUES (
|
||||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15,
|
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15,
|
||||||
$16, $17, $18, $19, $20, $21, $22, $23, $24
|
$16, $17, $18, $19, $20, $21, $22, $23, $24, $25
|
||||||
) RETURNING *;
|
) RETURNING *;
|
||||||
|
|
||||||
-- name: GetProfissionalByUsuarioID :one
|
-- name: GetProfissionalByUsuarioID :one
|
||||||
|
|
@ -55,6 +55,7 @@ SET
|
||||||
tabela_free = $22,
|
tabela_free = $22,
|
||||||
extra_por_equipamento = $23,
|
extra_por_equipamento = $23,
|
||||||
equipamentos = $24,
|
equipamentos = $24,
|
||||||
|
avatar_url = $25,
|
||||||
atualizado_em = NOW()
|
atualizado_em = NOW()
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
-- name: CreateUsuario :one
|
-- name: CreateUsuario :one
|
||||||
INSERT INTO usuarios (email, senha_hash, role, ativo)
|
INSERT INTO usuarios (email, senha_hash, role, tipo_profissional, ativo)
|
||||||
VALUES ($1, $2, $3, false)
|
VALUES ($1, $2, $3, $4, false)
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: GetUsuarioByEmail :one
|
-- name: GetUsuarioByEmail :one
|
||||||
SELECT u.id, u.email, u.senha_hash, u.role, u.ativo, u.criado_em, u.atualizado_em,
|
SELECT u.id, u.email, u.senha_hash, u.role, u.tipo_profissional, u.ativo, u.criado_em, u.atualizado_em,
|
||||||
COALESCE(cp.nome, cc.nome, '') as nome,
|
COALESCE(cp.nome, cc.nome, '') as nome,
|
||||||
COALESCE(cp.whatsapp, cc.telefone, '') as whatsapp,
|
COALESCE(cp.whatsapp, cc.telefone, '') as whatsapp,
|
||||||
e.id as empresa_id,
|
e.id as empresa_id,
|
||||||
|
|
@ -16,7 +16,7 @@ LEFT JOIN empresas e ON cc.empresa_id = e.id
|
||||||
WHERE u.email = $1 LIMIT 1;
|
WHERE u.email = $1 LIMIT 1;
|
||||||
|
|
||||||
-- name: GetUsuarioByID :one
|
-- name: GetUsuarioByID :one
|
||||||
SELECT u.id, u.email, u.senha_hash, u.role, u.ativo, u.criado_em, u.atualizado_em,
|
SELECT u.id, u.email, u.senha_hash, u.role, u.tipo_profissional, u.ativo, u.criado_em, u.atualizado_em,
|
||||||
COALESCE(cp.nome, cc.nome, '') as nome,
|
COALESCE(cp.nome, cc.nome, '') as nome,
|
||||||
COALESCE(cp.whatsapp, cc.telefone, '') as whatsapp,
|
COALESCE(cp.whatsapp, cc.telefone, '') as whatsapp,
|
||||||
e.id as empresa_id,
|
e.id as empresa_id,
|
||||||
|
|
@ -32,7 +32,7 @@ DELETE FROM usuarios
|
||||||
WHERE id = $1;
|
WHERE id = $1;
|
||||||
|
|
||||||
-- name: ListUsuariosPending :many
|
-- name: ListUsuariosPending :many
|
||||||
SELECT u.id, u.email, u.role, u.ativo, u.criado_em,
|
SELECT u.id, u.email, u.role, u.tipo_profissional, u.ativo, u.criado_em,
|
||||||
COALESCE(cp.nome, cc.nome, '') as nome,
|
COALESCE(cp.nome, cc.nome, '') as nome,
|
||||||
COALESCE(cp.whatsapp, cc.telefone, '') as whatsapp,
|
COALESCE(cp.whatsapp, cc.telefone, '') as whatsapp,
|
||||||
e.id as empresa_id,
|
e.id as empresa_id,
|
||||||
|
|
@ -56,7 +56,7 @@ WHERE id = $1
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: ListAllUsuarios :many
|
-- name: ListAllUsuarios :many
|
||||||
SELECT id, email, role, ativo, criado_em, atualizado_em
|
SELECT id, email, role, tipo_profissional, ativo, criado_em, atualizado_em
|
||||||
FROM usuarios
|
FROM usuarios
|
||||||
ORDER BY criado_em DESC;
|
ORDER BY criado_em DESC;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ CREATE TABLE IF NOT EXISTS usuarios (
|
||||||
email VARCHAR(255) UNIQUE NOT NULL,
|
email VARCHAR(255) UNIQUE NOT NULL,
|
||||||
senha_hash VARCHAR(255) NOT NULL,
|
senha_hash VARCHAR(255) NOT NULL,
|
||||||
role VARCHAR(50) NOT NULL DEFAULT 'profissional',
|
role VARCHAR(50) NOT NULL DEFAULT 'profissional',
|
||||||
|
tipo_profissional VARCHAR(50),
|
||||||
ativo BOOLEAN NOT NULL DEFAULT FALSE,
|
ativo BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
atualizado_em TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
atualizado_em TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
|
@ -51,6 +52,7 @@ CREATE TABLE IF NOT EXISTS cadastro_profissionais (
|
||||||
tabela_free VARCHAR(50),
|
tabela_free VARCHAR(50),
|
||||||
extra_por_equipamento BOOLEAN DEFAULT FALSE,
|
extra_por_equipamento BOOLEAN DEFAULT FALSE,
|
||||||
equipamentos TEXT,
|
equipamentos TEXT,
|
||||||
|
avatar_url VARCHAR(255),
|
||||||
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
atualizado_em TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
atualizado_em TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ type CreateProfissionalInput struct {
|
||||||
TabelaFree *string `json:"tabela_free"`
|
TabelaFree *string `json:"tabela_free"`
|
||||||
ExtraPorEquipamento *bool `json:"extra_por_equipamento"`
|
ExtraPorEquipamento *bool `json:"extra_por_equipamento"`
|
||||||
Equipamentos *string `json:"equipamentos"`
|
Equipamentos *string `json:"equipamentos"`
|
||||||
|
AvatarURL *string `json:"avatar_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Create(ctx context.Context, userID string, input CreateProfissionalInput) (*generated.CadastroProfissionai, error) {
|
func (s *Service) Create(ctx context.Context, userID string, input CreateProfissionalInput) (*generated.CadastroProfissionai, error) {
|
||||||
|
|
@ -88,6 +89,7 @@ func (s *Service) Create(ctx context.Context, userID string, input CreateProfiss
|
||||||
TabelaFree: toPgText(input.TabelaFree),
|
TabelaFree: toPgText(input.TabelaFree),
|
||||||
ExtraPorEquipamento: toPgBool(input.ExtraPorEquipamento),
|
ExtraPorEquipamento: toPgBool(input.ExtraPorEquipamento),
|
||||||
Equipamentos: toPgText(input.Equipamentos),
|
Equipamentos: toPgText(input.Equipamentos),
|
||||||
|
AvatarUrl: toPgText(input.AvatarURL),
|
||||||
}
|
}
|
||||||
|
|
||||||
prof, err := s.queries.CreateProfissional(ctx, params)
|
prof, err := s.queries.CreateProfissional(ctx, params)
|
||||||
|
|
@ -137,6 +139,7 @@ type UpdateProfissionalInput struct {
|
||||||
TabelaFree *string `json:"tabela_free"`
|
TabelaFree *string `json:"tabela_free"`
|
||||||
ExtraPorEquipamento *bool `json:"extra_por_equipamento"`
|
ExtraPorEquipamento *bool `json:"extra_por_equipamento"`
|
||||||
Equipamentos *string `json:"equipamentos"`
|
Equipamentos *string `json:"equipamentos"`
|
||||||
|
AvatarURL *string `json:"avatar_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Update(ctx context.Context, id string, input UpdateProfissionalInput) (*generated.CadastroProfissionai, error) {
|
func (s *Service) Update(ctx context.Context, id string, input UpdateProfissionalInput) (*generated.CadastroProfissionai, error) {
|
||||||
|
|
@ -175,6 +178,7 @@ func (s *Service) Update(ctx context.Context, id string, input UpdateProfissiona
|
||||||
TabelaFree: toPgText(input.TabelaFree),
|
TabelaFree: toPgText(input.TabelaFree),
|
||||||
ExtraPorEquipamento: toPgBool(input.ExtraPorEquipamento),
|
ExtraPorEquipamento: toPgBool(input.ExtraPorEquipamento),
|
||||||
Equipamentos: toPgText(input.Equipamentos),
|
Equipamentos: toPgText(input.Equipamentos),
|
||||||
|
AvatarUrl: toPgText(input.AvatarURL),
|
||||||
}
|
}
|
||||||
|
|
||||||
prof, err := s.queries.UpdateProfissional(ctx, params)
|
prof, err := s.queries.UpdateProfissional(ctx, params)
|
||||||
|
|
|
||||||
91
backend/internal/storage/s3.go
Normal file
91
backend/internal/storage/s3.go
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"photum-backend/internal/config"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
awsConfig "github.com/aws/aws-sdk-go-v2/config"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/credentials"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type S3Service struct {
|
||||||
|
Client *s3.Client
|
||||||
|
PresignClient *s3.PresignClient
|
||||||
|
Bucket string
|
||||||
|
Region string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewS3Service(cfg *config.Config) *S3Service {
|
||||||
|
// Custom Resolver for Civo Object Store
|
||||||
|
customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
|
||||||
|
return aws.Endpoint{
|
||||||
|
URL: cfg.S3Endpoint,
|
||||||
|
SigningRegion: region,
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
awsCfg, err := awsConfig.LoadDefaultConfig(context.TODO(),
|
||||||
|
awsConfig.WithRegion(cfg.S3Region),
|
||||||
|
awsConfig.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(cfg.S3AccessKey, cfg.S3SecretKey, "")),
|
||||||
|
awsConfig.WithEndpointResolverWithOptions(customResolver),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to load SDK config, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := s3.NewFromConfig(awsCfg, func(o *s3.Options) {
|
||||||
|
o.UsePathStyle = true
|
||||||
|
})
|
||||||
|
presignClient := s3.NewPresignClient(client)
|
||||||
|
|
||||||
|
return &S3Service{
|
||||||
|
Client: client,
|
||||||
|
PresignClient: presignClient,
|
||||||
|
Bucket: cfg.S3Bucket,
|
||||||
|
Region: cfg.S3Region,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GeneratePresignedURL generates a PUT presigned URL for uploading a file
|
||||||
|
// returns (uploadUrl, publicUrl, error)
|
||||||
|
func (s *S3Service) GeneratePresignedURL(filename string, contentType string) (string, string, error) {
|
||||||
|
key := fmt.Sprintf("photum-dev/%d_%s", time.Now().Unix(), filename)
|
||||||
|
|
||||||
|
req, err := s.PresignClient.PresignPutObject(context.TODO(), &s3.PutObjectInput{
|
||||||
|
Bucket: aws.String(s.Bucket),
|
||||||
|
Key: aws.String(key),
|
||||||
|
ContentType: aws.String(contentType),
|
||||||
|
}, s3.WithPresignExpires(15*time.Minute))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("failed to sign request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct public URL - Path Style
|
||||||
|
// URL: https://objectstore.nyc1.civo.com/rede5/uploads/...
|
||||||
|
// We need to clean the endpoint string if it has https:// prefix for Sprintf if we construct manually,
|
||||||
|
// or just reuse the known endpoint structure.
|
||||||
|
// cfg.S3Endpoint is "https://objectstore.nyc1.civo.com"
|
||||||
|
|
||||||
|
// Assuming s.Client.Options().BaseEndpoint is not easily accessible here without plumbing,
|
||||||
|
// we will construct it based on the hardcoded knowledge of Civo or pass endpoint to struct.
|
||||||
|
// But simply: S3Endpoint + "/" + Bucket + "/" + key is the standard path style.
|
||||||
|
|
||||||
|
// Note: config.S3Endpoint includes "https://" based on .env
|
||||||
|
// We entered: S3_ENDPOINT=https://objectstore.nyc1.civo.com
|
||||||
|
|
||||||
|
// So: https://objectstore.nyc1.civo.com/rede5/key
|
||||||
|
|
||||||
|
// However, we don't have access to cfg here directly, but we rely on hardcoding for Civo in previous step or we should store Endpoint in struct.
|
||||||
|
// Better to store Endpoint in struct to be clean.
|
||||||
|
// For now, I'll use the domain directly as I did before, but path style.
|
||||||
|
|
||||||
|
publicURL := fmt.Sprintf("https://%s/%s/%s", "objectstore.nyc1.civo.com", s.Bucket, key)
|
||||||
|
|
||||||
|
return req.URL, publicURL, nil
|
||||||
|
}
|
||||||
|
|
@ -110,15 +110,15 @@ export const Navbar: React.FC<NavbarProps> = ({ onNavigate, currentPage }) => {
|
||||||
alert("A imagem deve ter no máximo 5MB");
|
alert("A imagem deve ter no máximo 5MB");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate file type
|
// Validate file type
|
||||||
if (!file.type.startsWith('image/')) {
|
if (!file.type.startsWith('image/')) {
|
||||||
alert("Por favor, selecione uma imagem válida");
|
alert("Por favor, selecione uma imagem válida");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setAvatarFile(file);
|
setAvatarFile(file);
|
||||||
|
|
||||||
// Create preview URL
|
// Create preview URL
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onloadend = () => {
|
reader.onloadend = () => {
|
||||||
|
|
@ -188,6 +188,9 @@ export const Navbar: React.FC<NavbarProps> = ({ onNavigate, currentPage }) => {
|
||||||
src={getAvatarSrc(user)}
|
src={getAvatarSrc(user)}
|
||||||
alt="Avatar"
|
alt="Avatar"
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
|
onError={(e) => {
|
||||||
|
e.currentTarget.src = `https://ui-avatars.com/api/?name=${encodeURIComponent(user.name || "User")}&background=random&color=fff&size=128`;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
@ -201,6 +204,9 @@ export const Navbar: React.FC<NavbarProps> = ({ onNavigate, currentPage }) => {
|
||||||
src={getAvatarSrc(user)}
|
src={getAvatarSrc(user)}
|
||||||
alt={user.name}
|
alt={user.name}
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
|
onError={(e) => {
|
||||||
|
e.currentTarget.src = `https://ui-avatars.com/api/?name=${encodeURIComponent(user.name || "User")}&background=random&color=fff&size=128`;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-white font-bold text-lg mb-1">
|
<h3 className="text-white font-bold text-lg mb-1">
|
||||||
|
|
@ -291,67 +297,67 @@ export const Navbar: React.FC<NavbarProps> = ({ onNavigate, currentPage }) => {
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
!['entrar', 'cadastro', 'cadastro-profissional'].includes(currentPage) && (
|
!['entrar', 'cadastro', 'cadastro-profissional'].includes(currentPage) && (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setIsAccountDropdownOpen(!isAccountDropdownOpen)
|
setIsAccountDropdownOpen(!isAccountDropdownOpen)
|
||||||
}
|
}
|
||||||
className="flex items-center gap-2 px-4 py-2 rounded-full hover:bg-gray-100 transition-colors shadow-md"
|
className="flex items-center gap-2 px-4 py-2 rounded-full hover:bg-gray-100 transition-colors shadow-md"
|
||||||
>
|
>
|
||||||
<div className="w-10 h-10 rounded-full border-2 border-brand-gold flex items-center justify-center text-brand-gold hover:bg-brand-gold hover:text-white transition-colors">
|
<div className="w-10 h-10 rounded-full border-2 border-brand-gold flex items-center justify-center text-brand-gold hover:bg-brand-gold hover:text-white transition-colors">
|
||||||
<User size={24} />
|
<User size={24} />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-left hidden lg:block">
|
<div className="text-left hidden lg:block">
|
||||||
<p className="text-xs text-gray-500">Olá, bem-vindo(a)</p>
|
<p className="text-xs text-gray-500">Olá, bem-vindo(a)</p>
|
||||||
<p className="text-xs font-semibold text-gray-700">
|
<p className="text-xs font-semibold text-gray-700">
|
||||||
Entrar/Cadastrar
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Dropdown Popup - Responsivo */}
|
|
||||||
{isAccountDropdownOpen && (
|
|
||||||
<div className="absolute right-0 lg:left-1/2 lg:-translate-x-1/2 top-full mt-3 w-72 bg-white rounded-2xl shadow-2xl border border-gray-100 overflow-hidden z-50 fade-in">
|
|
||||||
{/* Header com ícone */}
|
|
||||||
<div className="bg-gradient-to-r from-[#492E61] to-[#5a3a7a] p-6 text-center">
|
|
||||||
<div className="w-16 h-16 mx-auto mb-3 rounded-full bg-white/20 backdrop-blur-sm flex items-center justify-center border-2 border-white/30">
|
|
||||||
<User size={32} className="text-white" />
|
|
||||||
</div>
|
|
||||||
<p className="text-white/70 text-xs mb-1">
|
|
||||||
Olá, bem-vindo(a)
|
|
||||||
</p>
|
|
||||||
<p className="text-white font-semibold text-base">
|
|
||||||
Entrar/Cadastrar
|
Entrar/Cadastrar
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
{/* Botões */}
|
{/* Dropdown Popup - Responsivo */}
|
||||||
<div className="p-5 space-y-3 bg-gray-50">
|
{isAccountDropdownOpen && (
|
||||||
<Button
|
<div className="absolute right-0 lg:left-1/2 lg:-translate-x-1/2 top-full mt-3 w-72 bg-white rounded-2xl shadow-2xl border border-gray-100 overflow-hidden z-50 fade-in">
|
||||||
onClick={() => {
|
{/* Header com ícone */}
|
||||||
onNavigate("entrar");
|
<div className="bg-gradient-to-r from-[#492E61] to-[#5a3a7a] p-6 text-center">
|
||||||
setIsAccountDropdownOpen(false);
|
<div className="w-16 h-16 mx-auto mb-3 rounded-full bg-white/20 backdrop-blur-sm flex items-center justify-center border-2 border-white/30">
|
||||||
}}
|
<User size={32} className="text-white" />
|
||||||
variant="secondary"
|
</div>
|
||||||
className="w-full rounded-xl shadow-sm hover:shadow-md transition-shadow"
|
<p className="text-white/70 text-xs mb-1">
|
||||||
>
|
Olá, bem-vindo(a)
|
||||||
ENTRAR
|
</p>
|
||||||
</Button>
|
<p className="text-white font-semibold text-base">
|
||||||
|
Entrar/Cadastrar
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Button
|
{/* Botões */}
|
||||||
onClick={() => {
|
<div className="p-5 space-y-3 bg-gray-50">
|
||||||
onNavigate("cadastro");
|
<Button
|
||||||
setIsAccountDropdownOpen(false);
|
onClick={() => {
|
||||||
}}
|
onNavigate("entrar");
|
||||||
variant="primary"
|
setIsAccountDropdownOpen(false);
|
||||||
className="w-full rounded-xl shadow-sm hover:shadow-md transition-shadow"
|
}}
|
||||||
>
|
variant="secondary"
|
||||||
Cadastre-se agora
|
className="w-full rounded-xl shadow-sm hover:shadow-md transition-shadow"
|
||||||
</Button>
|
>
|
||||||
|
ENTRAR
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
onNavigate("cadastro");
|
||||||
|
setIsAccountDropdownOpen(false);
|
||||||
|
}}
|
||||||
|
variant="primary"
|
||||||
|
className="w-full rounded-xl shadow-sm hover:shadow-md transition-shadow"
|
||||||
|
>
|
||||||
|
Cadastre-se agora
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -371,6 +377,9 @@ export const Navbar: React.FC<NavbarProps> = ({ onNavigate, currentPage }) => {
|
||||||
src={getAvatarSrc(user)}
|
src={getAvatarSrc(user)}
|
||||||
alt="Avatar"
|
alt="Avatar"
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
|
onError={(e) => {
|
||||||
|
e.currentTarget.src = `https://ui-avatars.com/api/?name=${encodeURIComponent(user.name || "User")}&background=random&color=fff&size=128`;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
@ -384,6 +393,9 @@ export const Navbar: React.FC<NavbarProps> = ({ onNavigate, currentPage }) => {
|
||||||
src={getAvatarSrc(user)}
|
src={getAvatarSrc(user)}
|
||||||
alt={user.name}
|
alt={user.name}
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
|
onError={(e) => {
|
||||||
|
e.currentTarget.src = `https://ui-avatars.com/api/?name=${encodeURIComponent(user.name || "User")}&background=random&color=fff&size=128`;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-white font-bold text-lg mb-1">
|
<h3 className="text-white font-bold text-lg mb-1">
|
||||||
|
|
@ -486,59 +498,59 @@ export const Navbar: React.FC<NavbarProps> = ({ onNavigate, currentPage }) => {
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
!['entrar', 'cadastro', 'cadastro-profissional'].includes(currentPage) && (
|
!['entrar', 'cadastro', 'cadastro-profissional'].includes(currentPage) && (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setIsAccountDropdownOpen(!isAccountDropdownOpen)
|
setIsAccountDropdownOpen(!isAccountDropdownOpen)
|
||||||
}
|
}
|
||||||
className="w-10 h-10 rounded-full border-2 border-brand-gold flex items-center justify-center text-brand-gold shadow-md"
|
className="w-10 h-10 rounded-full border-2 border-brand-gold flex items-center justify-center text-brand-gold shadow-md"
|
||||||
>
|
>
|
||||||
<User size={20} />
|
<User size={20} />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Dropdown Popup Mobile */}
|
{/* Dropdown Popup Mobile */}
|
||||||
{isAccountDropdownOpen && (
|
{isAccountDropdownOpen && (
|
||||||
<div className="absolute right-0 top-full mt-3 w-72 bg-white rounded-2xl shadow-2xl border border-gray-100 overflow-hidden z-50 fade-in">
|
<div className="absolute right-0 top-full mt-3 w-72 bg-white rounded-2xl shadow-2xl border border-gray-100 overflow-hidden z-50 fade-in">
|
||||||
{/* Header com ícone */}
|
{/* Header com ícone */}
|
||||||
<div className="bg-gradient-to-r from-[#492E61] to-[#5a3a7a] p-6 text-center">
|
<div className="bg-gradient-to-r from-[#492E61] to-[#5a3a7a] p-6 text-center">
|
||||||
<div className="w-16 h-16 mx-auto mb-3 rounded-full bg-white/20 backdrop-blur-sm flex items-center justify-center border-2 border-white/30">
|
<div className="w-16 h-16 mx-auto mb-3 rounded-full bg-white/20 backdrop-blur-sm flex items-center justify-center border-2 border-white/30">
|
||||||
<User size={32} className="text-white" />
|
<User size={32} className="text-white" />
|
||||||
|
</div>
|
||||||
|
<p className="text-white/70 text-xs mb-1">
|
||||||
|
Olá, bem-vindo(a)
|
||||||
|
</p>
|
||||||
|
<p className="text-white font-semibold text-base">
|
||||||
|
Entrar/Cadastrar
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-white/70 text-xs mb-1">
|
|
||||||
Olá, bem-vindo(a)
|
|
||||||
</p>
|
|
||||||
<p className="text-white font-semibold text-base">
|
|
||||||
Entrar/Cadastrar
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Botões */}
|
{/* Botões */}
|
||||||
<div className="p-5 space-y-3 bg-gray-50">
|
<div className="p-5 space-y-3 bg-gray-50">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onNavigate("entrar");
|
onNavigate("entrar");
|
||||||
setIsAccountDropdownOpen(false);
|
setIsAccountDropdownOpen(false);
|
||||||
}}
|
}}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="w-full rounded-xl shadow-sm hover:shadow-md transition-shadow"
|
className="w-full rounded-xl shadow-sm hover:shadow-md transition-shadow"
|
||||||
>
|
>
|
||||||
ENTRAR
|
ENTRAR
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onNavigate("cadastro");
|
onNavigate("cadastro");
|
||||||
setIsAccountDropdownOpen(false);
|
setIsAccountDropdownOpen(false);
|
||||||
}}
|
}}
|
||||||
variant="primary"
|
variant="primary"
|
||||||
className="w-full rounded-xl shadow-sm hover:shadow-md transition-shadow"
|
className="w-full rounded-xl shadow-sm hover:shadow-md transition-shadow"
|
||||||
>
|
>
|
||||||
Cadastre-se agora
|
Cadastre-se agora
|
||||||
</Button>
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -572,6 +584,9 @@ export const Navbar: React.FC<NavbarProps> = ({ onNavigate, currentPage }) => {
|
||||||
src={getAvatarSrc(user)}
|
src={getAvatarSrc(user)}
|
||||||
className="w-10 h-10 rounded-full mr-3 border-2 border-gray-200"
|
className="w-10 h-10 rounded-full mr-3 border-2 border-gray-200"
|
||||||
alt={user.name}
|
alt={user.name}
|
||||||
|
onError={(e) => {
|
||||||
|
e.currentTarget.src = `https://ui-avatars.com/api/?name=${encodeURIComponent(user.name || "User")}&background=random&color=fff&size=128`;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<span className="font-bold text-sm block text-gray-900">
|
<span className="font-bold text-sm block text-gray-900">
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ export interface ProfessionalData {
|
||||||
tipoCartao: string;
|
tipoCartao: string;
|
||||||
equipamentos: string;
|
equipamentos: string;
|
||||||
observacao: string;
|
observacao: string;
|
||||||
|
funcaoLabel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProfessionalForm: React.FC<ProfessionalFormProps> = ({
|
export const ProfessionalForm: React.FC<ProfessionalFormProps> = ({
|
||||||
|
|
@ -152,7 +153,11 @@ export const ProfessionalForm: React.FC<ProfessionalFormProps> = ({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
onSubmit(formData);
|
const selectedFunction = functions.find(f => f.id === formData.funcaoId);
|
||||||
|
onSubmit({
|
||||||
|
...formData,
|
||||||
|
funcaoLabel: selectedFunction?.nome
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const ufs = [
|
const ufs = [
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ interface AuthContextType {
|
||||||
user: User | null;
|
user: User | null;
|
||||||
login: (email: string, password?: string) => Promise<boolean>;
|
login: (email: string, password?: string) => Promise<boolean>;
|
||||||
logout: () => void;
|
logout: () => void;
|
||||||
register: (data: { nome: string; email: string; senha: string; telefone: string; role: string; empresaId?: string }) => Promise<{ success: boolean; userId?: string; token?: string }>;
|
register: (data: { nome: string; email: string; senha: string; telefone: string; role: string; empresaId?: string; tipo_profissional?: string }) => Promise<{ success: boolean; userId?: string; token?: string }>;
|
||||||
availableUsers: User[]; // Helper for the login screen demo
|
availableUsers: User[]; // Helper for the login screen demo
|
||||||
token: string | null;
|
token: string | null;
|
||||||
}
|
}
|
||||||
|
|
@ -66,11 +66,12 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||||
const mappedUser: User = {
|
const mappedUser: User = {
|
||||||
id: backendUser.id,
|
id: backendUser.id,
|
||||||
email: backendUser.email,
|
email: backendUser.email,
|
||||||
name: backendUser.email.split('@')[0],
|
name: data.profissional?.nome || data.empresa?.nome || backendUser.name || backendUser.nome || backendUser.email.split('@')[0],
|
||||||
role: backendUser.role as UserRole,
|
role: backendUser.role as UserRole,
|
||||||
ativo: backendUser.ativo,
|
ativo: backendUser.ativo,
|
||||||
empresaId: backendUser.company_id || backendUser.empresa_id || backendUser.companyId,
|
empresaId: backendUser.company_id || backendUser.empresa_id || backendUser.companyId,
|
||||||
companyName: backendUser.company_name || backendUser.empresa_nome || backendUser.companyName,
|
companyName: backendUser.company_name || backendUser.empresa_nome || backendUser.companyName,
|
||||||
|
avatar: data.profissional?.avatar_url || data.empresa?.avatar_url || backendUser.avatar,
|
||||||
};
|
};
|
||||||
if (!backendUser.ativo) {
|
if (!backendUser.ativo) {
|
||||||
console.warn("User is not active, logging out.");
|
console.warn("User is not active, logging out.");
|
||||||
|
|
@ -146,19 +147,19 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||||
const mappedUser: User = {
|
const mappedUser: User = {
|
||||||
id: backendUser.id,
|
id: backendUser.id,
|
||||||
email: backendUser.email,
|
email: backendUser.email,
|
||||||
name: backendUser.email.split('@')[0], // Fallback name or from profile if available
|
name: data.profissional?.nome || data.empresa?.nome || backendUser.name || backendUser.nome || backendUser.email.split('@')[0],
|
||||||
role: backendUser.role as UserRole,
|
role: backendUser.role as UserRole,
|
||||||
ativo: backendUser.ativo,
|
ativo: backendUser.ativo,
|
||||||
empresaId: backendUser.company_id || backendUser.empresa_id || backendUser.companyId,
|
empresaId: backendUser.company_id || backendUser.empresa_id || backendUser.companyId,
|
||||||
companyName: backendUser.company_name || backendUser.empresa_nome || backendUser.companyName,
|
companyName: backendUser.company_name || backendUser.empresa_nome || backendUser.companyName,
|
||||||
// ... propagate other fields if needed or fetch profile
|
avatar: data.profissional?.avatar_url || data.empresa?.avatar_url || backendUser.avatar,
|
||||||
};
|
};
|
||||||
|
|
||||||
setUser(mappedUser);
|
setUser(mappedUser);
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Login error:', err);
|
console.error('Login error:', err);
|
||||||
|
|
||||||
// 2. Fallback to Demo/Mock users if API fails
|
// 2. Fallback to Demo/Mock users if API fails
|
||||||
const mockUser = MOCK_USERS.find(u => u.email === email);
|
const mockUser = MOCK_USERS.find(u => u.email === email);
|
||||||
if (mockUser) {
|
if (mockUser) {
|
||||||
|
|
@ -167,7 +168,7 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||||
setUser({ ...mockUser, ativo: true });
|
setUser({ ...mockUser, ativo: true });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -195,7 +196,7 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const register = async (data: { nome: string; email: string; senha: string; telefone: string; role: string; empresaId?: string }) => {
|
const register = async (data: { nome: string; email: string; senha: string; telefone: string; role: string; empresaId?: string; tipo_profissional?: string }) => {
|
||||||
try {
|
try {
|
||||||
// Destructure to separate empresaId from the rest
|
// Destructure to separate empresaId from the rest
|
||||||
const { empresaId, ...rest } = data;
|
const { empresaId, ...rest } = data;
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ export const ProfessionalRegister: React.FC<ProfessionalRegisterProps> = ({
|
||||||
senha: professionalData.senha,
|
senha: professionalData.senha,
|
||||||
telefone: professionalData.whatsapp,
|
telefone: professionalData.whatsapp,
|
||||||
role: "PHOTOGRAPHER", // Role fixa para profissionais
|
role: "PHOTOGRAPHER", // Role fixa para profissionais
|
||||||
|
tipo_profissional: professionalData.funcaoLabel || "", // Envia o nome da função (ex: Cinegrafista)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!authResult.success) {
|
if (!authResult.success) {
|
||||||
|
|
@ -31,7 +32,32 @@ export const ProfessionalRegister: React.FC<ProfessionalRegisterProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Criar Perfil Profissional (autenticado)
|
// 2. Criar Perfil Profissional (autenticado)
|
||||||
const { createProfessional } = await import("../services/apiService");
|
const { createProfessional, getUploadURL, uploadFileToSignedUrl } = await import("../services/apiService");
|
||||||
|
|
||||||
|
let avatarUrl = "";
|
||||||
|
// Upload de Avatar (se existir)
|
||||||
|
if (professionalData.avatar) {
|
||||||
|
try {
|
||||||
|
console.log("Iniciando upload do avatar...");
|
||||||
|
const uploadRes = await getUploadURL(professionalData.avatar.name, professionalData.avatar.type);
|
||||||
|
|
||||||
|
if (uploadRes.error || !uploadRes.data) {
|
||||||
|
throw new Error(uploadRes.error || "Erro ao obter URL de upload");
|
||||||
|
}
|
||||||
|
|
||||||
|
await uploadFileToSignedUrl(uploadRes.data.upload_url, professionalData.avatar);
|
||||||
|
avatarUrl = uploadRes.data.public_url;
|
||||||
|
console.log("Upload concluído. URL:", avatarUrl);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Erro no upload do avatar:", err);
|
||||||
|
// Opcional: alertar usuário mas continuar cadastro sem foto?
|
||||||
|
// alert("Erro ao enviar foto. O cadastro prosseguirá sem foto.");
|
||||||
|
// Ou falhar tudo?
|
||||||
|
throw new Error("Falha ao enviar foto de perfil: " + (err instanceof Error ? err.message : "Erro desconhecido"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mapear dados do formulário para o payload esperado pelo backend
|
||||||
|
|
||||||
// Mapear dados do formulário para o payload esperado pelo backend
|
// Mapear dados do formulário para o payload esperado pelo backend
|
||||||
// O curl fornecido pelo usuário mostra campos underscore (snake_case)
|
// O curl fornecido pelo usuário mostra campos underscore (snake_case)
|
||||||
|
|
@ -59,8 +85,8 @@ export const ProfessionalRegister: React.FC<ProfessionalRegisterProps> = ({
|
||||||
disp_horario: 0,
|
disp_horario: 0,
|
||||||
educacao_simpatia: 0,
|
educacao_simpatia: 0,
|
||||||
qual_tec: 0,
|
qual_tec: 0,
|
||||||
media: 0,
|
|
||||||
tabela_free: "",
|
tabela_free: "",
|
||||||
|
avatar_url: avatarUrl,
|
||||||
};
|
};
|
||||||
|
|
||||||
const profResult = await createProfessional(payload, authResult.token);
|
const profResult = await createProfessional(payload, authResult.token);
|
||||||
|
|
|
||||||
|
|
@ -650,3 +650,54 @@ export async function updateEventStatus(token: string, eventId: string, status:
|
||||||
return { data: null, error: error instanceof Error ? error.message : "Erro desconhecido", isBackendDown: true };
|
return { data: null, error: error instanceof Error ? error.message : "Erro desconhecido", isBackendDown: true };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtém URL pré-assinada para upload de arquivo
|
||||||
|
*/
|
||||||
|
export async function getUploadURL(filename: string, contentType: string): Promise<ApiResponse<{ upload_url: string; public_url: string }>> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/auth/upload-url`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ filename, content_type: contentType }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
error: null,
|
||||||
|
isBackendDown: false,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching upload URL:", error);
|
||||||
|
return {
|
||||||
|
data: null,
|
||||||
|
error: error instanceof Error ? error.message : "Erro desconhecido",
|
||||||
|
isBackendDown: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Realiza o upload do arquivo para a URL pré-assinada
|
||||||
|
*/
|
||||||
|
export async function uploadFileToSignedUrl(uploadUrl: string, file: File): Promise<void> {
|
||||||
|
const response = await fetch(uploadUrl, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": file.type,
|
||||||
|
},
|
||||||
|
body: file,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to upload file to S3. Status: ${response.status}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue