From 92f13fba225f64a4b672be1ec55d5d77b9cc2a80 Mon Sep 17 00:00:00 2001 From: Gabriel De Los Rios Date: Sun, 2 Nov 2025 22:29:24 -0300 Subject: [PATCH] feat: add docs --- cmd/server/main.go | 40 +++ docs/docs.go | 375 ++++++++++++++++++++++++++++ docs/swagger.json | 351 ++++++++++++++++++++++++++ docs/swagger.yaml | 227 +++++++++++++++++ internal/handler/contact_handler.go | 57 +++++ internal/handler/response.go | 17 +- internal/models/contact.go | 11 +- 7 files changed, 1070 insertions(+), 8 deletions(-) create mode 100644 cmd/server/main.go create mode 100644 docs/docs.go create mode 100644 docs/swagger.json create mode 100644 docs/swagger.yaml diff --git a/cmd/server/main.go b/cmd/server/main.go new file mode 100644 index 0000000..ddfc5cc --- /dev/null +++ b/cmd/server/main.go @@ -0,0 +1,40 @@ +package main + +import ( + "log" + "net/http" + + _ "gitea.gabilandia.com/gabdlr/agenda-web-go/docs" + "gitea.gabilandia.com/gabdlr/agenda-web-go/internal/database" + "gitea.gabilandia.com/gabdlr/agenda-web-go/internal/handler" + "gitea.gabilandia.com/gabdlr/agenda-web-go/internal/repository" + httpSwagger "github.com/swaggo/http-swagger" +) + +// @title Contacts API +// @version 1.0 +// @description A simple Contacts CRUD API +// @termsOfService http://swagger.io/terms/ + +// @contact.name API Support +// @contact.email support@yourapp.com + +// @license.name MIT +// @license.url https://opensource.org/licenses/MIT + +// @host localhost:8080 +// @BasePath / +func main() { + err := database.InitDB(database.Conn_string) + defer database.CloseDB() + if err != nil { + log.Fatal("Database connection failed:", err) + } + mux := http.NewServeMux() + + contactRepo := repository.NewContactRepository(database.DB) + handler.HandleContacts(mux, contactRepo) + handler.HandlHealthChecks(mux) + mux.HandleFunc("/swagger/", httpSwagger.WrapHandler) + log.Fatal(http.ListenAndServe(":8080", mux)) +} diff --git a/docs/docs.go b/docs/docs.go new file mode 100644 index 0000000..4a3de6d --- /dev/null +++ b/docs/docs.go @@ -0,0 +1,375 @@ +// 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}}", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "name": "API Support", + "email": "support@yourapp.com" + }, + "license": { + "name": "MIT", + "url": "https://opensource.org/licenses/MIT" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/contacts": { + "get": { + "description": "Get a list of all contacts", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "contacts" + ], + "summary": "Get all contacts", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Contact" + } + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + } + } + }, + "post": { + "description": "Create a new contact with the provided data", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "contacts" + ], + "summary": "Create a new contact", + "parameters": [ + { + "description": "Contact object", + "name": "contact", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Contact" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.Contact" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + } + } + } + }, + "/contacts/{id}": { + "get": { + "description": "Get a single contact by its ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "contacts" + ], + "summary": "Get a contact by ID", + "parameters": [ + { + "type": "integer", + "description": "Contact ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.Contact" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + } + } + }, + "put": { + "description": "Update an existing contact by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "contacts" + ], + "summary": "Update a contact", + "parameters": [ + { + "type": "integer", + "description": "Contact ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Contact object", + "name": "contact", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Contact" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + } + } + }, + "delete": { + "description": "Delete a contact by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "contacts" + ], + "summary": "Delete a contact", + "parameters": [ + { + "type": "integer", + "description": "Contact ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + } + } + } + } + }, + "definitions": { + "handler.APIError": { + "type": "object", + "properties": { + "code": { + "description": "Error code", + "type": "integer" + }, + "details": { + "description": "Additional error details", + "type": "string" + }, + "message": { + "description": "Human-readable error message", + "type": "string" + } + } + }, + "handler.APIResponse": { + "type": "object", + "properties": { + "data": { + "description": "The response data" + }, + "errors": { + "description": "List of errors if any occurred", + "type": "array", + "items": { + "$ref": "#/definitions/handler.APIError" + } + }, + "message": { + "description": "Optional message", + "type": "string" + }, + "success": { + "description": "Indicates if the request was successful", + "type": "boolean" + } + } + }, + "models.Contact": { + "type": "object", + "properties": { + "company": { + "description": "Company the contact works for", + "type": "string" + }, + "id": { + "description": "ID is the unique identifier for the contact", + "type": "integer" + }, + "name": { + "description": "Name of the contact", + "type": "string" + }, + "phone": { + "description": "Phone number in international format", + "type": "string" + } + } + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "1.0", + Host: "localhost:8080", + BasePath: "/", + Schemes: []string{}, + Title: "Contacts API", + Description: "A simple Contacts CRUD API", + 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..7c84457 --- /dev/null +++ b/docs/swagger.json @@ -0,0 +1,351 @@ +{ + "swagger": "2.0", + "info": { + "description": "A simple Contacts CRUD API", + "title": "Contacts API", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "name": "API Support", + "email": "support@yourapp.com" + }, + "license": { + "name": "MIT", + "url": "https://opensource.org/licenses/MIT" + }, + "version": "1.0" + }, + "host": "localhost:8080", + "basePath": "/", + "paths": { + "/contacts": { + "get": { + "description": "Get a list of all contacts", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "contacts" + ], + "summary": "Get all contacts", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Contact" + } + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + } + } + }, + "post": { + "description": "Create a new contact with the provided data", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "contacts" + ], + "summary": "Create a new contact", + "parameters": [ + { + "description": "Contact object", + "name": "contact", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Contact" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.Contact" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + } + } + } + }, + "/contacts/{id}": { + "get": { + "description": "Get a single contact by its ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "contacts" + ], + "summary": "Get a contact by ID", + "parameters": [ + { + "type": "integer", + "description": "Contact ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.Contact" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + } + } + }, + "put": { + "description": "Update an existing contact by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "contacts" + ], + "summary": "Update a contact", + "parameters": [ + { + "type": "integer", + "description": "Contact ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Contact object", + "name": "contact", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Contact" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + } + } + }, + "delete": { + "description": "Delete a contact by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "contacts" + ], + "summary": "Delete a contact", + "parameters": [ + { + "type": "integer", + "description": "Contact ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/handler.APIResponse" + } + } + } + } + } + }, + "definitions": { + "handler.APIError": { + "type": "object", + "properties": { + "code": { + "description": "Error code", + "type": "integer" + }, + "details": { + "description": "Additional error details", + "type": "string" + }, + "message": { + "description": "Human-readable error message", + "type": "string" + } + } + }, + "handler.APIResponse": { + "type": "object", + "properties": { + "data": { + "description": "The response data" + }, + "errors": { + "description": "List of errors if any occurred", + "type": "array", + "items": { + "$ref": "#/definitions/handler.APIError" + } + }, + "message": { + "description": "Optional message", + "type": "string" + }, + "success": { + "description": "Indicates if the request was successful", + "type": "boolean" + } + } + }, + "models.Contact": { + "type": "object", + "properties": { + "company": { + "description": "Company the contact works for", + "type": "string" + }, + "id": { + "description": "ID is the unique identifier for the contact", + "type": "integer" + }, + "name": { + "description": "Name of the contact", + "type": "string" + }, + "phone": { + "description": "Phone number in international format", + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 0000000..f7f5fc2 --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,227 @@ +basePath: / +definitions: + handler.APIError: + properties: + code: + description: Error code + type: integer + details: + description: Additional error details + type: string + message: + description: Human-readable error message + type: string + type: object + handler.APIResponse: + properties: + data: + description: The response data + errors: + description: List of errors if any occurred + items: + $ref: '#/definitions/handler.APIError' + type: array + message: + description: Optional message + type: string + success: + description: Indicates if the request was successful + type: boolean + type: object + models.Contact: + properties: + company: + description: Company the contact works for + type: string + id: + description: ID is the unique identifier for the contact + type: integer + name: + description: Name of the contact + type: string + phone: + description: Phone number in international format + type: string + type: object +host: localhost:8080 +info: + contact: + email: support@yourapp.com + name: API Support + description: A simple Contacts CRUD API + license: + name: MIT + url: https://opensource.org/licenses/MIT + termsOfService: http://swagger.io/terms/ + title: Contacts API + version: "1.0" +paths: + /contacts: + get: + consumes: + - application/json + description: Get a list of all contacts + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.APIResponse' + - properties: + data: + items: + $ref: '#/definitions/models.Contact' + type: array + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/handler.APIResponse' + summary: Get all contacts + tags: + - contacts + post: + consumes: + - application/json + description: Create a new contact with the provided data + parameters: + - description: Contact object + in: body + name: contact + required: true + schema: + $ref: '#/definitions/models.Contact' + produces: + - application/json + responses: + "201": + description: Created + schema: + allOf: + - $ref: '#/definitions/handler.APIResponse' + - properties: + data: + $ref: '#/definitions/models.Contact' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/handler.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/handler.APIResponse' + summary: Create a new contact + tags: + - contacts + /contacts/{id}: + delete: + consumes: + - application/json + description: Delete a contact by ID + parameters: + - description: Contact ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.APIResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/handler.APIResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/handler.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/handler.APIResponse' + summary: Delete a contact + tags: + - contacts + get: + consumes: + - application/json + description: Get a single contact by its ID + parameters: + - description: Contact ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.APIResponse' + - properties: + data: + $ref: '#/definitions/models.Contact' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/handler.APIResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/handler.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/handler.APIResponse' + summary: Get a contact by ID + tags: + - contacts + put: + consumes: + - application/json + description: Update an existing contact by ID + parameters: + - description: Contact ID + in: path + name: id + required: true + type: integer + - description: Contact object + in: body + name: contact + required: true + schema: + $ref: '#/definitions/models.Contact' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.APIResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/handler.APIResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/handler.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/handler.APIResponse' + summary: Update a contact + tags: + - contacts +swagger: "2.0" diff --git a/internal/handler/contact_handler.go b/internal/handler/contact_handler.go index 8e941d0..01f84f6 100644 --- a/internal/handler/contact_handler.go +++ b/internal/handler/contact_handler.go @@ -28,6 +28,15 @@ func HandleContacts(mux *http.ServeMux, repo repository.Repository[models.Contac NewBaseHandler(mux, routes) } +// GetAll godoc +// @Summary Get all contacts +// @Description Get a list of all contacts +// @Tags contacts +// @Accept json +// @Produce json +// @Success 200 {object} APIResponse{data=[]models.Contact} +// @Failure 500 {object} APIResponse +// @Router /contacts [get] func (h *ContactHandler) getAll(w http.ResponseWriter, r *http.Request) { contacts, err := h.repository.GetAll() if err != nil { @@ -37,6 +46,18 @@ func (h *ContactHandler) getAll(w http.ResponseWriter, r *http.Request) { JSONSuccess(w, contacts, "Contact list retrieved successfully", http.StatusOK) } +// GetByID godoc +// @Summary Get a contact by ID +// @Description Get a single contact by its ID +// @Tags contacts +// @Accept json +// @Produce json +// @Param id path int true "Contact ID" +// @Success 200 {object} APIResponse{data=models.Contact} +// @Failure 400 {object} APIResponse +// @Failure 404 {object} APIResponse +// @Failure 500 {object} APIResponse +// @Router /contacts/{id} [get] func (h *ContactHandler) getByID(w http.ResponseWriter, r *http.Request) { id, err := strconv.Atoi(r.PathValue("id")) if err != nil { @@ -56,6 +77,17 @@ func (h *ContactHandler) getByID(w http.ResponseWriter, r *http.Request) { JSONSuccess(w, contact, "Contact retrieved successfully", http.StatusOK) } +// Create godoc +// @Summary Create a new contact +// @Description Create a new contact with the provided data +// @Tags contacts +// @Accept json +// @Produce json +// @Param contact body models.Contact true "Contact object" +// @Success 201 {object} APIResponse{data=models.Contact} +// @Failure 400 {object} APIResponse +// @Failure 500 {object} APIResponse +// @Router /contacts [post] func (h *ContactHandler) create(w http.ResponseWriter, r *http.Request) { var contact models.Contact @@ -89,6 +121,19 @@ func (h *ContactHandler) create(w http.ResponseWriter, r *http.Request) { JSONSuccess(w, contact, "Contact created successfully", http.StatusCreated) } +// Update godoc +// @Summary Update a contact +// @Description Update an existing contact by ID +// @Tags contacts +// @Accept json +// @Produce json +// @Param id path int true "Contact ID" +// @Param contact body models.Contact true "Contact object" +// @Success 200 {object} APIResponse +// @Failure 400 {object} APIResponse +// @Failure 404 {object} APIResponse +// @Failure 500 {object} APIResponse +// @Router /contacts/{id} [put] func (h *ContactHandler) update(w http.ResponseWriter, r *http.Request) { id, err := strconv.Atoi(r.PathValue("id")) if err != nil { @@ -114,6 +159,18 @@ func (h *ContactHandler) update(w http.ResponseWriter, r *http.Request) { JSONSuccess(w, nil, "Contact updated successfully", http.StatusOK) } +// Delete godoc +// @Summary Delete a contact +// @Description Delete a contact by ID +// @Tags contacts +// @Accept json +// @Produce json +// @Param id path int true "Contact ID" +// @Success 200 {object} APIResponse +// @Failure 400 {object} APIResponse +// @Failure 404 {object} APIResponse +// @Failure 500 {object} APIResponse +// @Router /contacts/{id} [delete] func (h *ContactHandler) delete(w http.ResponseWriter, r *http.Request) { id, err := strconv.Atoi(r.PathValue("id")) diff --git a/internal/handler/response.go b/internal/handler/response.go index 6f63c9c..2e6c3e0 100644 --- a/internal/handler/response.go +++ b/internal/handler/response.go @@ -6,15 +6,22 @@ import ( ) type APIResponse struct { - Success bool `json:"success"` - Data any `json:"data,omitempty"` - Errors []APIError `json:"errors,omitempty"` - Message string `json:"message,omitempty"` + // Indicates if the request was successful + Success bool `json:"success"` + // The response data + Data any `json:"data,omitempty"` + // List of errors if any occurred + Errors []APIError `json:"errors,omitempty"` + // Optional message + Message string `json:"message,omitempty"` } type APIError struct { - Code int `json:"code"` + // Error code + Code int `json:"code"` + // Human-readable error message Message string `json:"message"` + // Additional error details Details string `json:"details,omitempty"` } diff --git a/internal/models/contact.go b/internal/models/contact.go index d5496ca..985f3b1 100644 --- a/internal/models/contact.go +++ b/internal/models/contact.go @@ -1,8 +1,13 @@ package models +// Contact represents a contact entity type Contact struct { - ID int `json:"id" db:"id"` - Name string `json:"name" db:"name"` + // ID is the unique identifier for the contact + ID int `json:"id" db:"id"` + // Name of the contact + Name string `json:"name" db:"name"` + // Company the contact works for Company string `json:"company" db:"company"` - Phone string `json:"phone" db:"phone"` + // Phone number in international format + Phone string `json:"phone" db:"phone"` }