fix: resolve problemas de cadastro, seletor de localização e swagger

- Corrige violação de restrição de role no Registro de Candidato (usa 'candidate' em minúsculo)
- Corrige erro de chave duplicada para slug da empresa adicionando timestamp ao workspace do candidato
- Corrige crash no LocationPicker tratando respostas nulas no frontend e retornando arrays vazios no backend
- Corrige documentação do Swagger para o endpoint de Login e adiciona definição de segurança BearerAuth
This commit is contained in:
NANDO9322 2026-01-05 13:30:02 -03:00
parent 1b9bd81687
commit 1f9b54d719
8 changed files with 355 additions and 164 deletions

View file

@ -19,6 +19,9 @@ import (
// @version 1.0
// @description API for GoHorseJobs recruitment platform.
// @BasePath /
// @securityDefinitions.apikey BearerAuth
// @in header
// @name Authorization
func main() {
// Load .env file
if err := godotenv.Load(); err != nil {

View file

@ -15,6 +15,41 @@ const docTemplate = `{
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/admin/storage/test-connection": {
"post": {
"description": "Checks if the current configuration (DB or Env) allows access to the S3 bucket.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "Test Object Storage Connection",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
}
},
"/api/v1/applications": {
"get": {
"description": "List all applications for a job",
@ -262,7 +297,7 @@ const docTemplate = `{
"in": "body",
"required": true,
"schema": {
"type": "object"
"$ref": "#/definitions/github_com_rede5_gohorsejobs_backend_internal_core_dto.LoginRequest"
}
}
],
@ -288,6 +323,23 @@ const docTemplate = `{
}
}
},
"/api/v1/auth/logout": {
"post": {
"description": "Clears the httpOnly JWT cookie, effectively logging the user out.",
"tags": [
"Auth"
],
"summary": "User Logout",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object"
}
}
}
}
},
"/api/v1/companies": {
"get": {
"description": "Returns a list of all companies.",
@ -1636,63 +1688,6 @@ const docTemplate = `{
}
}
},
"/api/v1/system/credentials": {
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "Saves encrypted credentials payload (e.g. Stripe key encrypted by Backoffice)",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"System"
],
"summary": "Save Credentials",
"parameters": [
{
"description": "Credentials Payload",
"name": "request",
"in": "body",
"required": true,
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"400": {
"description": "Invalid Request",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "string"
}
}
}
}
},
"/api/v1/tokens": {
"post": {
"security": [
@ -1954,6 +1949,63 @@ const docTemplate = `{
}
}
},
"/api/v1/users/me/password": {
"patch": {
"security": [
{
"BearerAuth": []
}
],
"description": "Updates the current user's password.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Users"
],
"summary": "Update My Password",
"parameters": [
{
"description": "Password Details",
"name": "password",
"in": "body",
"required": true,
"schema": {
"type": "object"
}
}
],
"responses": {
"204": {
"description": "No Content",
"schema": {
"type": "string"
}
},
"400": {
"description": "Invalid Request",
"schema": {
"type": "string"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "string"
}
}
}
}
},
"/api/v1/users/me/profile": {
"patch": {
"security": [
@ -2112,6 +2164,17 @@ const docTemplate = `{
}
},
"definitions": {
"github_com_rede5_gohorsejobs_backend_internal_core_dto.LoginRequest": {
"type": "object",
"properties": {
"email": {
"type": "string"
},
"password": {
"type": "string"
}
}
},
"github_com_rede5_gohorsejobs_backend_internal_core_dto.SaveFCMTokenRequest": {
"type": "object",
"properties": {
@ -2766,6 +2829,13 @@ const docTemplate = `{
}
}
}
},
"securityDefinitions": {
"BearerAuth": {
"type": "apiKey",
"name": "Authorization",
"in": "header"
}
}
}`

View file

@ -8,6 +8,41 @@
},
"basePath": "/",
"paths": {
"/admin/storage/test-connection": {
"post": {
"description": "Checks if the current configuration (DB or Env) allows access to the S3 bucket.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "Test Object Storage Connection",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
}
},
"/api/v1/applications": {
"get": {
"description": "List all applications for a job",
@ -255,7 +290,7 @@
"in": "body",
"required": true,
"schema": {
"type": "object"
"$ref": "#/definitions/github_com_rede5_gohorsejobs_backend_internal_core_dto.LoginRequest"
}
}
],
@ -281,6 +316,23 @@
}
}
},
"/api/v1/auth/logout": {
"post": {
"description": "Clears the httpOnly JWT cookie, effectively logging the user out.",
"tags": [
"Auth"
],
"summary": "User Logout",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object"
}
}
}
}
},
"/api/v1/companies": {
"get": {
"description": "Returns a list of all companies.",
@ -1629,63 +1681,6 @@
}
}
},
"/api/v1/system/credentials": {
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "Saves encrypted credentials payload (e.g. Stripe key encrypted by Backoffice)",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"System"
],
"summary": "Save Credentials",
"parameters": [
{
"description": "Credentials Payload",
"name": "request",
"in": "body",
"required": true,
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"400": {
"description": "Invalid Request",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "string"
}
}
}
}
},
"/api/v1/tokens": {
"post": {
"security": [
@ -1947,6 +1942,63 @@
}
}
},
"/api/v1/users/me/password": {
"patch": {
"security": [
{
"BearerAuth": []
}
],
"description": "Updates the current user's password.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Users"
],
"summary": "Update My Password",
"parameters": [
{
"description": "Password Details",
"name": "password",
"in": "body",
"required": true,
"schema": {
"type": "object"
}
}
],
"responses": {
"204": {
"description": "No Content",
"schema": {
"type": "string"
}
},
"400": {
"description": "Invalid Request",
"schema": {
"type": "string"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "string"
}
}
}
}
},
"/api/v1/users/me/profile": {
"patch": {
"security": [
@ -2105,6 +2157,17 @@
}
},
"definitions": {
"github_com_rede5_gohorsejobs_backend_internal_core_dto.LoginRequest": {
"type": "object",
"properties": {
"email": {
"type": "string"
},
"password": {
"type": "string"
}
}
},
"github_com_rede5_gohorsejobs_backend_internal_core_dto.SaveFCMTokenRequest": {
"type": "object",
"properties": {
@ -2759,5 +2822,12 @@
}
}
}
},
"securityDefinitions": {
"BearerAuth": {
"type": "apiKey",
"name": "Authorization",
"in": "header"
}
}
}

View file

@ -1,5 +1,12 @@
basePath: /
definitions:
github_com_rede5_gohorsejobs_backend_internal_core_dto.LoginRequest:
properties:
email:
type: string
password:
type: string
type: object
github_com_rede5_gohorsejobs_backend_internal_core_dto.SaveFCMTokenRequest:
properties:
platform:
@ -468,6 +475,30 @@ info:
title: GoHorseJobs API
version: "1.0"
paths:
/admin/storage/test-connection:
post:
consumes:
- application/json
description: Checks if the current configuration (DB or Env) allows access to
the S3 bucket.
produces:
- application/json
responses:
"200":
description: OK
schema:
additionalProperties:
type: string
type: object
"500":
description: Internal Server Error
schema:
additionalProperties:
type: string
type: object
summary: Test Object Storage Connection
tags:
- admin
/api/v1/applications:
get:
consumes:
@ -630,7 +661,7 @@ paths:
name: login
required: true
schema:
type: object
$ref: '#/definitions/github_com_rede5_gohorsejobs_backend_internal_core_dto.LoginRequest'
produces:
- application/json
responses:
@ -649,6 +680,17 @@ paths:
summary: User Login
tags:
- Auth
/api/v1/auth/logout:
post:
description: Clears the httpOnly JWT cookie, effectively logging the user out.
responses:
"200":
description: OK
schema:
type: object
summary: User Logout
tags:
- Auth
/api/v1/companies:
get:
consumes:
@ -1512,43 +1554,6 @@ paths:
summary: List All Tickets (Admin)
tags:
- Support
/api/v1/system/credentials:
post:
consumes:
- application/json
description: Saves encrypted credentials payload (e.g. Stripe key encrypted
by Backoffice)
parameters:
- description: Credentials Payload
in: body
name: request
required: true
schema:
additionalProperties:
type: string
type: object
produces:
- application/json
responses:
"200":
description: OK
schema:
additionalProperties:
type: string
type: object
"400":
description: Invalid Request
schema:
type: string
"500":
description: Internal Server Error
schema:
type: string
security:
- BearerAuth: []
summary: Save Credentials
tags:
- System
/api/v1/tokens:
post:
consumes:
@ -1783,6 +1788,42 @@ paths:
summary: Upload Avatar
tags:
- Users
/api/v1/users/me/password:
patch:
consumes:
- application/json
description: Updates the current user's password.
parameters:
- description: Password Details
in: body
name: password
required: true
schema:
type: object
produces:
- application/json
responses:
"204":
description: No Content
schema:
type: string
"400":
description: Invalid Request
schema:
type: string
"401":
description: Unauthorized
schema:
type: string
"500":
description: Internal Server Error
schema:
type: string
security:
- BearerAuth: []
summary: Update My Password
tags:
- Users
/api/v1/users/me/profile:
patch:
consumes:
@ -1815,4 +1856,9 @@ paths:
summary: Update My Profile
tags:
- Users
securityDefinitions:
BearerAuth:
in: header
name: Authorization
type: apiKey
swagger: "2.0"

View file

@ -59,7 +59,7 @@ func NewCoreHandlers(l *auth.LoginUseCase, reg *auth.RegisterCandidateUseCase, c
// @Tags Auth
// @Accept json
// @Produce json
// @Param login body object true "Login Credentials"
// @Param login body dto.LoginRequest true "Login Credentials"
// @Success 200 {object} object
// @Failure 400 {string} string "Invalid Request"
// @Failure 401 {string} string "Unauthorized"

View file

@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"log"
"time"
"github.com/rede5/gohorsejobs/backend/internal/core/domain/entity"
"github.com/rede5/gohorsejobs/backend/internal/core/dto"
@ -44,7 +45,7 @@ func (uc *RegisterCandidateUseCase) Execute(ctx context.Context, input dto.Regis
// NOTE: ID is empty - will be auto-generated by DB (SERIAL)
candidateCompany := entity.NewCompany(
"", // DB generates SERIAL id
fmt.Sprintf("Candidate - %s", input.Name),
fmt.Sprintf("Candidate - %s (%d)", input.Name, time.Now().Unix()),
nil, // No document for candidates
nil, // No contact - will use user's contact info
)
@ -65,7 +66,7 @@ func (uc *RegisterCandidateUseCase) Execute(ctx context.Context, input dto.Regis
}
// Assign Role
user.AssignRole(entity.Role{Name: "CANDIDATE"})
user.AssignRole(entity.Role{Name: entity.RoleCandidate})
saved, err := uc.userRepo.Save(ctx, user)
if err != nil {

View file

@ -23,7 +23,7 @@ func (r *LocationRepository) ListCountries(ctx context.Context) ([]*entity.Count
}
defer rows.Close()
var countries []*entity.Country
countries := []*entity.Country{}
for rows.Next() {
c := &entity.Country{}
// Scan columns. Be careful with NULL handling if fields are nullable, but schema says NOT NULL mostly.
@ -61,7 +61,7 @@ func (r *LocationRepository) ListStates(ctx context.Context, countryID int64) ([
}
defer rows.Close()
var states []*entity.State
states := []*entity.State{}
for rows.Next() {
s := &entity.State{}
var iso2, typeStr sql.NullString
@ -88,7 +88,7 @@ func (r *LocationRepository) ListCities(ctx context.Context, stateID int64) ([]*
}
defer rows.Close()
var cities []*entity.City
cities := []*entity.City{}
for rows.Next() {
c := &entity.City{}
// schema: latitude NOT NULL, longitude NOT NULL.
@ -123,7 +123,7 @@ func (r *LocationRepository) Search(ctx context.Context, query string, countryID
}
defer rows.Close()
var results []*entity.LocationSearchResult
results := []*entity.LocationSearchResult{}
for rows.Next() {
res := &entity.LocationSearchResult{}
var stateID sql.NullInt64

View file

@ -27,9 +27,10 @@ export function LocationPicker({ value, onChange }: LocationPickerProps) {
// Initial Load
useEffect(() => {
locationsApi.listCountries().then(res => {
setCountries(res);
const data = res || [];
setCountries(data);
// Default to Brazil if available (User is Brazilian based on context)
const br = res.find(c => c.iso2 === "BR");
const br = data.find(c => c.iso2 === "BR");
if (br) setSelectedCountry(br.id.toString());
}).catch(console.error);
}, []);
@ -156,7 +157,7 @@ export function LocationPicker({ value, onChange }: LocationPickerProps) {
<SelectValue placeholder="Selecione" />
</SelectTrigger>
<SelectContent>
{countries.map((c) => (
{Array.isArray(countries) && countries.map((c) => (
<SelectItem key={c.id} value={c.id.toString()}>
<span className="flex items-center gap-2">
<span>{c.emoji}</span>