diff --git a/Makefile b/Makefile index 9ba89cf..ae67ba8 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,9 @@ run: build dev: air +swagger: + swag init -g cmd/main.go + clean: rm -rf $(BIN_DIR) diff --git a/cmd/main.go b/cmd/main.go index b02f1a7..ddf46da 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -9,8 +9,15 @@ import ( "portfolio-backend/internal/routes" "github.com/gin-gonic/gin" + + _ "portfolio-backend/docs" ) +// @title Portfolio Builder API +// @version 1.0 +// @description Uma API open source para o gerenciamento de portfólios de desenvolvedores. +// @host localhost:8080 +// @BasePath /api func main() { config.LoadEnv() diff --git a/docs/docs.go b/docs/docs.go new file mode 100644 index 0000000..60f6356 --- /dev/null +++ b/docs/docs.go @@ -0,0 +1,607 @@ +// Package docs Code generated by swaggo/swag. DO NOT EDIT +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": {}, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/health": { + "get": { + "description": "Retorna uma mensagem simples para indicar que o servidor está rodando", + "produces": [ + "application/json" + ], + "tags": [ + "health" + ], + "summary": "Verifica o status da API", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.HealthResponse" + } + } + } + } + }, + "/projects": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Retorna todos os projetos com paginação, ordenação e total de registros", + "produces": [ + "application/json" + ], + "tags": [ + "Projects" + ], + "summary": "Lista todos os projetos do usuário", + "parameters": [ + { + "type": "integer", + "description": "Página atual", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "Tamanho da página", + "name": "size", + "in": "query" + }, + { + "type": "string", + "description": "Campo para ordenar (ex: name, created_at)", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "description": "Ordem (asc ou desc)", + "name": "order", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.ProjectListResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Cria um novo projeto para o usuário autenticado", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Projects" + ], + "summary": "Cria um novo projeto", + "parameters": [ + { + "description": "Dados do projeto", + "name": "project", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.ProjectSwagger" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.ProjectSwagger" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/projects/export/csv": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Exporta todos os projetos do usuário autenticado em formato CSV", + "produces": [ + "text/csv" + ], + "tags": [ + "Projects" + ], + "summary": "Exporta projetos para CSV", + "responses": { + "200": { + "description": "Arquivo CSV", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/projects/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Retorna um projeto específico do usuário autenticado", + "produces": [ + "application/json" + ], + "tags": [ + "Projects" + ], + "summary": "Busca um projeto por ID", + "parameters": [ + { + "type": "string", + "description": "ID do projeto", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.ProjectSwagger" + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Atualiza um projeto existente do usuário autenticado", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Projects" + ], + "summary": "Atualiza um projeto", + "parameters": [ + { + "type": "string", + "description": "ID do projeto", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Dados do projeto atualizado", + "name": "project", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.ProjectSwagger" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Remove um projeto do usuário autenticado", + "produces": [ + "application/json" + ], + "tags": [ + "Projects" + ], + "summary": "Deleta um projeto", + "parameters": [ + { + "type": "string", + "description": "ID do projeto", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/projects/{id}/favorite": { + "patch": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Alterna o status de favorito de um projeto do usuário", + "produces": [ + "application/json" + ], + "tags": [ + "Projects" + ], + "summary": "Favorita/Desfavorita um projeto", + "parameters": [ + { + "type": "string", + "description": "ID do projeto", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content", + "schema": { + "type": "string" + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/register": { + "post": { + "description": "Cria um novo usuário na plataforma com nome, email e senha", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Registra um novo usuário", + "parameters": [ + { + "description": "Dados para registro", + "name": "registerData", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.RegisterInput" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.RegisterResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/models.ErrorResponse" + } + }, + "409": { + "description": "Conflict", + "schema": { + "$ref": "#/definitions/models.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/models.ErrorResponse" + } + } + } + } + } + }, + "definitions": { + "models.ErrorResponse": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "Erro ao validar os dados" + } + } + }, + "models.HealthResponse": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "ok" + } + } + }, + "models.ProjectListResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/models.ProjectSwagger" + } + }, + "page": { + "type": "integer" + }, + "size": { + "type": "integer" + }, + "totalCount": { + "type": "integer" + } + } + }, + "models.ProjectSwagger": { + "type": "object", + "properties": { + "createdAt": { + "type": "string", + "example": "2025-04-04T12:00:00Z" + }, + "description": { + "type": "string", + "example": "Um criador de portfólios para devs" + }, + "id": { + "type": "string", + "example": "1" + }, + "image": { + "type": "string", + "example": "https://meuportfolio.com/capa.png" + }, + "isFavorite": { + "type": "boolean", + "example": false + }, + "link": { + "type": "string", + "example": "https://meuportfolio.com" + }, + "techStack": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "[\"React\"", + " \"TypeScript\"", + " \"Node.js\"]" + ] + }, + "title": { + "type": "string", + "example": "Portfolio Builder" + }, + "updatedAt": { + "type": "string", + "example": "2025-04-04T12:00:00Z" + } + } + }, + "models.RegisterInput": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "joao@email.com" + }, + "name": { + "type": "string", + "example": "João" + }, + "password": { + "type": "string", + "example": "minhasenha123" + } + } + }, + "models.RegisterResponse": { + "type": "object", + "properties": { + "api_key": { + "type": "string", + "example": "meu-token-api-gerado" + }, + "message": { + "type": "string", + "example": "Usuário cadastrado com sucesso" + } + } + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "1.0", + Host: "localhost:8080", + BasePath: "/api", + Schemes: []string{}, + Title: "Portfolio Builder API", + Description: "Uma API open source para o gerenciamento de portfólios de desenvolvedores.", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/docs/swagger.json b/docs/swagger.json new file mode 100644 index 0000000..639ca76 --- /dev/null +++ b/docs/swagger.json @@ -0,0 +1,583 @@ +{ + "swagger": "2.0", + "info": { + "description": "Uma API open source para o gerenciamento de portfólios de desenvolvedores.", + "title": "Portfolio Builder API", + "contact": {}, + "version": "1.0" + }, + "host": "localhost:8080", + "basePath": "/api", + "paths": { + "/health": { + "get": { + "description": "Retorna uma mensagem simples para indicar que o servidor está rodando", + "produces": [ + "application/json" + ], + "tags": [ + "health" + ], + "summary": "Verifica o status da API", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.HealthResponse" + } + } + } + } + }, + "/projects": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Retorna todos os projetos com paginação, ordenação e total de registros", + "produces": [ + "application/json" + ], + "tags": [ + "Projects" + ], + "summary": "Lista todos os projetos do usuário", + "parameters": [ + { + "type": "integer", + "description": "Página atual", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "Tamanho da página", + "name": "size", + "in": "query" + }, + { + "type": "string", + "description": "Campo para ordenar (ex: name, created_at)", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "description": "Ordem (asc ou desc)", + "name": "order", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.ProjectListResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Cria um novo projeto para o usuário autenticado", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Projects" + ], + "summary": "Cria um novo projeto", + "parameters": [ + { + "description": "Dados do projeto", + "name": "project", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.ProjectSwagger" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.ProjectSwagger" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/projects/export/csv": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Exporta todos os projetos do usuário autenticado em formato CSV", + "produces": [ + "text/csv" + ], + "tags": [ + "Projects" + ], + "summary": "Exporta projetos para CSV", + "responses": { + "200": { + "description": "Arquivo CSV", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/projects/{id}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Retorna um projeto específico do usuário autenticado", + "produces": [ + "application/json" + ], + "tags": [ + "Projects" + ], + "summary": "Busca um projeto por ID", + "parameters": [ + { + "type": "string", + "description": "ID do projeto", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.ProjectSwagger" + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Atualiza um projeto existente do usuário autenticado", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Projects" + ], + "summary": "Atualiza um projeto", + "parameters": [ + { + "type": "string", + "description": "ID do projeto", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Dados do projeto atualizado", + "name": "project", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.ProjectSwagger" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Remove um projeto do usuário autenticado", + "produces": [ + "application/json" + ], + "tags": [ + "Projects" + ], + "summary": "Deleta um projeto", + "parameters": [ + { + "type": "string", + "description": "ID do projeto", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/projects/{id}/favorite": { + "patch": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Alterna o status de favorito de um projeto do usuário", + "produces": [ + "application/json" + ], + "tags": [ + "Projects" + ], + "summary": "Favorita/Desfavorita um projeto", + "parameters": [ + { + "type": "string", + "description": "ID do projeto", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content", + "schema": { + "type": "string" + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/register": { + "post": { + "description": "Cria um novo usuário na plataforma com nome, email e senha", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Registra um novo usuário", + "parameters": [ + { + "description": "Dados para registro", + "name": "registerData", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.RegisterInput" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.RegisterResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/models.ErrorResponse" + } + }, + "409": { + "description": "Conflict", + "schema": { + "$ref": "#/definitions/models.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/models.ErrorResponse" + } + } + } + } + } + }, + "definitions": { + "models.ErrorResponse": { + "type": "object", + "properties": { + "error": { + "type": "string", + "example": "Erro ao validar os dados" + } + } + }, + "models.HealthResponse": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "ok" + } + } + }, + "models.ProjectListResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/models.ProjectSwagger" + } + }, + "page": { + "type": "integer" + }, + "size": { + "type": "integer" + }, + "totalCount": { + "type": "integer" + } + } + }, + "models.ProjectSwagger": { + "type": "object", + "properties": { + "createdAt": { + "type": "string", + "example": "2025-04-04T12:00:00Z" + }, + "description": { + "type": "string", + "example": "Um criador de portfólios para devs" + }, + "id": { + "type": "string", + "example": "1" + }, + "image": { + "type": "string", + "example": "https://meuportfolio.com/capa.png" + }, + "isFavorite": { + "type": "boolean", + "example": false + }, + "link": { + "type": "string", + "example": "https://meuportfolio.com" + }, + "techStack": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "[\"React\"", + " \"TypeScript\"", + " \"Node.js\"]" + ] + }, + "title": { + "type": "string", + "example": "Portfolio Builder" + }, + "updatedAt": { + "type": "string", + "example": "2025-04-04T12:00:00Z" + } + } + }, + "models.RegisterInput": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "joao@email.com" + }, + "name": { + "type": "string", + "example": "João" + }, + "password": { + "type": "string", + "example": "minhasenha123" + } + } + }, + "models.RegisterResponse": { + "type": "object", + "properties": { + "api_key": { + "type": "string", + "example": "meu-token-api-gerado" + }, + "message": { + "type": "string", + "example": "Usuário cadastrado com sucesso" + } + } + } + } +} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 0000000..1703ba5 --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,380 @@ +basePath: /api +definitions: + models.ErrorResponse: + properties: + error: + example: Erro ao validar os dados + type: string + type: object + models.HealthResponse: + properties: + message: + example: ok + type: string + type: object + models.ProjectListResponse: + properties: + data: + items: + $ref: '#/definitions/models.ProjectSwagger' + type: array + page: + type: integer + size: + type: integer + totalCount: + type: integer + type: object + models.ProjectSwagger: + properties: + createdAt: + example: "2025-04-04T12:00:00Z" + type: string + description: + example: Um criador de portfólios para devs + type: string + id: + example: "1" + type: string + image: + example: https://meuportfolio.com/capa.png + type: string + isFavorite: + example: false + type: boolean + link: + example: https://meuportfolio.com + type: string + techStack: + example: + - '["React"' + - ' "TypeScript"' + - ' "Node.js"]' + items: + type: string + type: array + title: + example: Portfolio Builder + type: string + updatedAt: + example: "2025-04-04T12:00:00Z" + type: string + type: object + models.RegisterInput: + properties: + email: + example: joao@email.com + type: string + name: + example: João + type: string + password: + example: minhasenha123 + type: string + type: object + models.RegisterResponse: + properties: + api_key: + example: meu-token-api-gerado + type: string + message: + example: Usuário cadastrado com sucesso + type: string + type: object +host: localhost:8080 +info: + contact: {} + description: Uma API open source para o gerenciamento de portfólios de desenvolvedores. + title: Portfolio Builder API + version: "1.0" +paths: + /health: + get: + description: Retorna uma mensagem simples para indicar que o servidor está rodando + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.HealthResponse' + summary: Verifica o status da API + tags: + - health + /projects: + get: + description: Retorna todos os projetos com paginação, ordenação e total de registros + parameters: + - description: Página atual + in: query + name: page + type: integer + - description: Tamanho da página + in: query + name: size + type: integer + - description: 'Campo para ordenar (ex: name, created_at)' + in: query + name: sort + type: string + - description: Ordem (asc ou desc) + in: query + name: order + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.ProjectListResponse' + "500": + description: Internal Server Error + schema: + additionalProperties: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: Lista todos os projetos do usuário + tags: + - Projects + post: + consumes: + - application/json + description: Cria um novo projeto para o usuário autenticado + parameters: + - description: Dados do projeto + in: body + name: project + required: true + schema: + $ref: '#/definitions/models.ProjectSwagger' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/models.ProjectSwagger' + "400": + description: Bad Request + schema: + additionalProperties: + type: string + type: object + "500": + description: Internal Server Error + schema: + additionalProperties: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: Cria um novo projeto + tags: + - Projects + /projects/{id}: + delete: + description: Remove um projeto do usuário autenticado + parameters: + - description: ID do projeto + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: + type: string + type: object + "404": + description: Not Found + schema: + additionalProperties: + type: string + type: object + "500": + description: Internal Server Error + schema: + additionalProperties: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: Deleta um projeto + tags: + - Projects + get: + description: Retorna um projeto específico do usuário autenticado + parameters: + - description: ID do projeto + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.ProjectSwagger' + "404": + description: Not Found + schema: + additionalProperties: + type: string + type: object + "500": + description: Internal Server Error + schema: + additionalProperties: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: Busca um projeto por ID + tags: + - Projects + put: + consumes: + - application/json + description: Atualiza um projeto existente do usuário autenticado + parameters: + - description: ID do projeto + in: path + name: id + required: true + type: string + - description: Dados do projeto atualizado + in: body + name: project + required: true + schema: + $ref: '#/definitions/models.ProjectSwagger' + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: + type: string + type: object + "400": + description: Bad Request + schema: + additionalProperties: + type: string + type: object + "404": + description: Not Found + schema: + additionalProperties: + type: string + type: object + "500": + description: Internal Server Error + schema: + additionalProperties: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: Atualiza um projeto + tags: + - Projects + /projects/{id}/favorite: + patch: + description: Alterna o status de favorito de um projeto do usuário + parameters: + - description: ID do projeto + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "204": + description: No Content + schema: + type: string + "404": + description: Not Found + schema: + additionalProperties: + type: string + type: object + "500": + description: Internal Server Error + schema: + additionalProperties: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: Favorita/Desfavorita um projeto + tags: + - Projects + /projects/export/csv: + get: + description: Exporta todos os projetos do usuário autenticado em formato CSV + produces: + - text/csv + responses: + "200": + description: Arquivo CSV + schema: + type: string + "500": + description: Internal Server Error + schema: + additionalProperties: + type: string + type: object + security: + - ApiKeyAuth: [] + summary: Exporta projetos para CSV + tags: + - Projects + /register: + post: + consumes: + - application/json + description: Cria um novo usuário na plataforma com nome, email e senha + parameters: + - description: Dados para registro + in: body + name: registerData + required: true + schema: + $ref: '#/definitions/models.RegisterInput' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/models.RegisterResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/models.ErrorResponse' + "409": + description: Conflict + schema: + $ref: '#/definitions/models.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/models.ErrorResponse' + summary: Registra um novo usuário + tags: + - auth +swagger: "2.0" diff --git a/go.mod b/go.mod index fca3a80..1603a45 100644 --- a/go.mod +++ b/go.mod @@ -14,14 +14,21 @@ require ( ) require ( + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/PuerkitoBio/purell v1.2.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/bytedance/sonic v1.13.2 // indirect github.com/bytedance/sonic/loader v0.2.4 // indirect github.com/cloudwego/base64x v0.1.5 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/gin-contrib/sse v1.0.0 // indirect + github.com/go-openapi/jsonpointer v0.21.1 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/spec v0.21.0 // indirect + github.com/go-openapi/swag v0.23.1 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.25.0 // indirect + github.com/go-playground/validator/v10 v10.26.0 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect @@ -29,22 +36,29 @@ require ( github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/swaggo/files v1.0.1 // indirect + github.com/swaggo/gin-swagger v1.6.0 // indirect + github.com/swaggo/swag v1.16.4 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect golang.org/x/arch v0.15.0 // indirect - golang.org/x/net v0.37.0 // indirect + golang.org/x/net v0.38.0 // indirect golang.org/x/sync v0.12.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.23.0 // indirect - google.golang.org/protobuf v1.36.5 // indirect + golang.org/x/tools v0.31.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index b88af09..9ec29f4 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,9 @@ +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/PuerkitoBio/purell v1.2.1 h1:QsZ4TjvwiMpat6gBCBxEQI0rcS9ehtkKtSpiUnd9N28= +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/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= @@ -18,6 +24,14 @@ github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= +github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= +github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= +github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -26,6 +40,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8= github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= +github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= +github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= @@ -47,6 +63,8 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -55,12 +73,15 @@ github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQe github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -86,30 +107,71 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= +github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= +github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= +github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= +github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A= +github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw= golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= +golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/handlers/auth.handler.go b/internal/handlers/auth.handler.go index c5dbba8..4e20707 100644 --- a/internal/handlers/auth.handler.go +++ b/internal/handlers/auth.handler.go @@ -8,6 +8,18 @@ import ( "github.com/gin-gonic/gin" ) +// Register godoc +// @Summary Registra um novo usuário +// @Description Cria um novo usuário na plataforma com nome, email e senha +// @Tags auth +// @Accept json +// @Produce json +// @Param registerData body models.RegisterInput true "Dados para registro" +// @Success 201 {object} models.RegisterResponse +// @Failure 400 {object} models.ErrorResponse +// @Failure 409 {object} models.ErrorResponse +// @Failure 500 {object} models.ErrorResponse +// @Router /register [post] func Register(c *gin.Context) { var input struct { Name string `json:"name"` diff --git a/internal/handlers/health.handler.go b/internal/handlers/health.handler.go index 4c3b790..3d0934f 100644 --- a/internal/handlers/health.handler.go +++ b/internal/handlers/health.handler.go @@ -6,6 +6,13 @@ import ( "github.com/gin-gonic/gin" ) +// HealthCheck godoc +// @Summary Verifica o status da API +// @Description Retorna uma mensagem simples para indicar que o servidor está rodando +// @Tags health +// @Produce json +// @Success 200 {object} models.HealthResponse +// @Router /health [get] func HealthCheck(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ diff --git a/internal/handlers/projects.go b/internal/handlers/projects.go index c077864..dd43ae8 100644 --- a/internal/handlers/projects.go +++ b/internal/handlers/projects.go @@ -24,6 +24,19 @@ func getProjectRepo() *repository.ProjectRepository { // Alterar toda lógica de negócios para arquivos de service +// GetAllProjectsHandler godoc +// @Summary Lista todos os projetos do usuário +// @Description Retorna todos os projetos com paginação, ordenação e total de registros +// @Tags Projects +// @Security ApiKeyAuth +// @Produce json +// @Param page query int false "Página atual" +// @Param size query int false "Tamanho da página" +// @Param sort query string false "Campo para ordenar (ex: name, created_at)" +// @Param order query string false "Ordem (asc ou desc)" +// @Success 200 {object} models.ProjectListResponse +// @Failure 500 {object} map[string]string +// @Router /projects [get] func GetAllProjectsHandler(c *gin.Context) { page := c.MustGet("page").(int) limit := c.MustGet("limit").(int) @@ -58,6 +71,17 @@ func GetAllProjectsHandler(c *gin.Context) { }) } +// GetOneProjectHandler godoc +// @Summary Busca um projeto por ID +// @Description Retorna um projeto específico do usuário autenticado +// @Tags Projects +// @Security ApiKeyAuth +// @Produce json +// @Param id path string true "ID do projeto" +// @Success 200 {object} models.ProjectSwagger +// @Failure 404 {object} map[string]string +// @Failure 500 {object} map[string]string +// @Router /projects/{id} [get] func GetOneProjectHandler(c *gin.Context) { projectRepo := getProjectRepo() @@ -80,6 +104,19 @@ func GetOneProjectHandler(c *gin.Context) { } // TODO: adicionar validações de campos e melhorar retorno de erros + +// CreateProjectHandler godoc +// @Summary Cria um novo projeto +// @Description Cria um novo projeto para o usuário autenticado +// @Tags Projects +// @Security ApiKeyAuth +// @Accept json +// @Produce json +// @Param project body models.ProjectSwagger true "Dados do projeto" +// @Success 201 {object} models.ProjectSwagger +// @Failure 400 {object} map[string]string +// @Failure 500 {object} map[string]string +// @Router /projects [post] func CreateProjectHandler(c *gin.Context) { var project models.Project user := c.MustGet("user").(models.User) @@ -102,6 +139,21 @@ func CreateProjectHandler(c *gin.Context) { } // TODO: adicionar validações de campos e melhorar retorno de erros + +// UpdateProjectHandler godoc +// @Summary Atualiza um projeto +// @Description Atualiza um projeto existente do usuário autenticado +// @Tags Projects +// @Security ApiKeyAuth +// @Accept json +// @Produce json +// @Param id path string true "ID do projeto" +// @Param project body models.ProjectSwagger true "Dados do projeto atualizado" +// @Success 200 {object} map[string]string +// @Failure 400 {object} map[string]string +// @Failure 404 {object} map[string]string +// @Failure 500 {object} map[string]string +// @Router /projects/{id} [put] func UpdateProjectHandler(c *gin.Context) { projectID := c.Param("id") var project models.Project @@ -128,6 +180,17 @@ func UpdateProjectHandler(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "Projeto atualizado com sucesso"}) } +// DeleteProjectHandler godoc +// @Summary Deleta um projeto +// @Description Remove um projeto do usuário autenticado +// @Tags Projects +// @Security ApiKeyAuth +// @Produce json +// @Param id path string true "ID do projeto" +// @Success 200 {object} map[string]string +// @Failure 404 {object} map[string]string +// @Failure 500 {object} map[string]string +// @Router /projects/{id} [delete] func DeleteProjectHandler(c *gin.Context) { projectID := c.Param("id") user := c.MustGet("user").(models.User) @@ -147,6 +210,17 @@ func DeleteProjectHandler(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "Projeto excluído com sucesso"}) } +// FavoriteProjectHandler godoc +// @Summary Favorita/Desfavorita um projeto +// @Description Alterna o status de favorito de um projeto do usuário +// @Tags Projects +// @Security ApiKeyAuth +// @Produce json +// @Param id path string true "ID do projeto" +// @Success 204 {string} string "No Content" +// @Failure 404 {object} map[string]string +// @Failure 500 {object} map[string]string +// @Router /projects/{id}/favorite [patch] func FavoriteProjectHandler(c *gin.Context) { projectID := c.Param("id") user := c.MustGet("user").(models.User) @@ -175,6 +249,15 @@ func FavoriteProjectHandler(c *gin.Context) { c.Status(http.StatusNoContent) } +// ExportProjectsToCSVHandler godoc +// @Summary Exporta projetos para CSV +// @Description Exporta todos os projetos do usuário autenticado em formato CSV +// @Tags Projects +// @Security ApiKeyAuth +// @Produce text/csv +// @Success 200 {string} string "Arquivo CSV" +// @Failure 500 {object} map[string]string +// @Router /projects/export/csv [get] func ExportProjectsToCSVHandler(c *gin.Context) { user := c.MustGet("user").(models.User) diff --git a/internal/models/health.go b/internal/models/health.go new file mode 100644 index 0000000..b27fa8e --- /dev/null +++ b/internal/models/health.go @@ -0,0 +1,5 @@ +package models + +type HealthResponse struct { + Message string `json:"message" example:"ok"` +} diff --git a/internal/models/project.model.go b/internal/models/project.model.go index 7873055..9047778 100644 --- a/internal/models/project.model.go +++ b/internal/models/project.model.go @@ -17,6 +17,25 @@ const ( StatusNaoIniciado Status = "Não iniciado" ) +type ProjectSwagger struct { + ID string `json:"id" example:"1"` + Title string `json:"title" example:"Portfolio Builder"` + Description string `json:"description" example:"Um criador de portfólios para devs"` + TechStack []string `json:"techStack" example:"[\"React\", \"TypeScript\", \"Node.js\"]"` + Link string `json:"link" example:"https://meuportfolio.com"` + Image string `json:"image" example:"https://meuportfolio.com/capa.png"` + IsFavorite bool `json:"isFavorite" example:"false"` + CreatedAt string `json:"createdAt" example:"2025-04-04T12:00:00Z"` + UpdatedAt string `json:"updatedAt" example:"2025-04-04T12:00:00Z"` +} + +type ProjectListResponse struct { + Data []ProjectSwagger `json:"data"` + TotalCount int `json:"totalCount"` + Page int `json:"page"` + Size int `json:"size"` +} + type Project struct { ID uuid.UUID `json:"id" gorm:"primaryKey; type:uuid; default:gen_random_uuid()"` Name string `json:"name" gorm:"not null"` diff --git a/internal/models/register.go b/internal/models/register.go new file mode 100644 index 0000000..908e5d5 --- /dev/null +++ b/internal/models/register.go @@ -0,0 +1,16 @@ +package models + +type RegisterInput struct { + Name string `json:"name" example:"João"` + Email string `json:"email" example:"joao@email.com"` + Password string `json:"password" example:"minhasenha123"` +} + +type RegisterResponse struct { + Message string `json:"message" example:"Usuário cadastrado com sucesso"` + APIKey string `json:"api_key" example:"meu-token-api-gerado"` +} + +type ErrorResponse struct { + Error string `json:"error" example:"Erro ao validar os dados"` +} diff --git a/internal/routes/routes.go b/internal/routes/routes.go index 7a8f859..fb5ec0e 100644 --- a/internal/routes/routes.go +++ b/internal/routes/routes.go @@ -4,6 +4,9 @@ import ( "portfolio-backend/internal/handlers" "portfolio-backend/internal/middlewares" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" + "github.com/gin-gonic/gin" ) @@ -12,6 +15,7 @@ func SetupRoutes(r *gin.Engine) { api := r.Group("/api") { + api.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) api.POST("/register", handlers.Register) api.GET("/health", handlers.HealthCheck) diff --git a/readme.md b/readme.md index e271b11..3e84e81 100644 --- a/readme.md +++ b/readme.md @@ -21,6 +21,15 @@ **Portfolio Builder API** is an open-source project that provides developers with a robust API to create and manage their portfolios. With RESTful endpoints, users can easily manage projects, skills, and experiences, facilitating a seamless portfolio creation process. The project is built with Golang, enhancing development efficiency. +## 📚 API Documentation + +The Portfolio Builder API is fully documented using [Swagger (OpenAPI)](https://swagger.io/), allowing developers to easily explore and test all available endpoints. + +🔗 You can access the live, interactive API documentation here: +👉 [portfolio-builder.site/docs](https://portfolio-builder.site/api/swagger/index.html) + +This provides a user-friendly interface to view all routes, request/response formats, authentication requirements, and model schemas. + ## Contributing ### Fork and clone your repository