Compare commits
9 Commits
8adf1bd4c8
...
01a7f33054
| Author | SHA1 | Date | |
|---|---|---|---|
| 01a7f33054 | |||
| 92f13fba22 | |||
| 3b1b05d5a6 | |||
| 4a28259dc8 | |||
| af511203a4 | |||
| e06c30635e | |||
| ffdaa22aa3 | |||
| 1f4711edb6 | |||
| de58856289 |
40
cmd/server/main.go
Normal file
40
cmd/server/main.go
Normal file
@@ -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))
|
||||||
|
}
|
||||||
380
docs/docs.go
Normal file
380
docs/docs.go
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
// 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"
|
||||||
|
},
|
||||||
|
"field": {
|
||||||
|
"description": "Field name if applicable",
|
||||||
|
"type": "string",
|
||||||
|
"example": "name"
|
||||||
|
},
|
||||||
|
"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)
|
||||||
|
}
|
||||||
356
docs/swagger.json
Normal file
356
docs/swagger.json
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"field": {
|
||||||
|
"description": "Field name if applicable",
|
||||||
|
"type": "string",
|
||||||
|
"example": "name"
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
231
docs/swagger.yaml
Normal file
231
docs/swagger.yaml
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
basePath: /
|
||||||
|
definitions:
|
||||||
|
handler.APIError:
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
description: Error code
|
||||||
|
type: integer
|
||||||
|
details:
|
||||||
|
description: Additional error details
|
||||||
|
type: string
|
||||||
|
field:
|
||||||
|
description: Field name if applicable
|
||||||
|
example: name
|
||||||
|
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"
|
||||||
29
go.mod
29
go.mod
@@ -1,3 +1,32 @@
|
|||||||
module gitea.gabilandia.com/gabdlr/agenda-web-go
|
module gitea.gabilandia.com/gabdlr/agenda-web-go
|
||||||
|
|
||||||
go 1.25.1
|
go 1.25.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
|
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||||
|
github.com/go-openapi/jsonpointer v0.22.1 // indirect
|
||||||
|
github.com/go-openapi/jsonreference v0.21.2 // indirect
|
||||||
|
github.com/go-openapi/spec v0.22.0 // indirect
|
||||||
|
github.com/go-openapi/swag v0.25.1 // indirect
|
||||||
|
github.com/go-openapi/swag/conv v0.25.1 // indirect
|
||||||
|
github.com/go-openapi/swag/jsonname v0.25.1 // indirect
|
||||||
|
github.com/go-openapi/swag/jsonutils v0.25.1 // indirect
|
||||||
|
github.com/go-openapi/swag/loading v0.25.1 // indirect
|
||||||
|
github.com/go-openapi/swag/stringutils v0.25.1 // indirect
|
||||||
|
github.com/go-openapi/swag/typeutils v0.25.1 // indirect
|
||||||
|
github.com/go-openapi/swag/yamlutils v0.25.1 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.9.3 // indirect
|
||||||
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/mailru/easyjson v0.9.1 // indirect
|
||||||
|
github.com/swaggo/files v1.0.1 // indirect
|
||||||
|
github.com/swaggo/http-swagger v1.3.4 // indirect
|
||||||
|
github.com/swaggo/swag v1.16.6 // indirect
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
|
golang.org/x/mod v0.29.0 // indirect
|
||||||
|
golang.org/x/net v0.46.0 // indirect
|
||||||
|
golang.org/x/sync v0.17.0 // indirect
|
||||||
|
golang.org/x/sys v0.37.0 // indirect
|
||||||
|
golang.org/x/tools v0.38.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
)
|
||||||
|
|||||||
83
go.sum
Normal file
83
go.sum
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||||
|
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk=
|
||||||
|
github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM=
|
||||||
|
github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU=
|
||||||
|
github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ=
|
||||||
|
github.com/go-openapi/spec v0.22.0 h1:xT/EsX4frL3U09QviRIZXvkh80yibxQmtoEvyqug0Tw=
|
||||||
|
github.com/go-openapi/spec v0.22.0/go.mod h1:K0FhKxkez8YNS94XzF8YKEMULbFrRw4m15i2YUht4L0=
|
||||||
|
github.com/go-openapi/swag v0.25.1 h1:6uwVsx+/OuvFVPqfQmOOPsqTcm5/GkBhNwLqIR916n8=
|
||||||
|
github.com/go-openapi/swag v0.25.1/go.mod h1:bzONdGlT0fkStgGPd3bhZf1MnuPkf2YAys6h+jZipOo=
|
||||||
|
github.com/go-openapi/swag/conv v0.25.1 h1:+9o8YUg6QuqqBM5X6rYL/p1dpWeZRhoIt9x7CCP+he0=
|
||||||
|
github.com/go-openapi/swag/conv v0.25.1/go.mod h1:Z1mFEGPfyIKPu0806khI3zF+/EUXde+fdeksUl2NiDs=
|
||||||
|
github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU=
|
||||||
|
github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo=
|
||||||
|
github.com/go-openapi/swag/jsonutils v0.25.1 h1:AihLHaD0brrkJoMqEZOBNzTLnk81Kg9cWr+SPtxtgl8=
|
||||||
|
github.com/go-openapi/swag/jsonutils v0.25.1/go.mod h1:JpEkAjxQXpiaHmRO04N1zE4qbUEg3b7Udll7AMGTNOo=
|
||||||
|
github.com/go-openapi/swag/loading v0.25.1 h1:6OruqzjWoJyanZOim58iG2vj934TysYVptyaoXS24kw=
|
||||||
|
github.com/go-openapi/swag/loading v0.25.1/go.mod h1:xoIe2EG32NOYYbqxvXgPzne989bWvSNoWoyQVWEZicc=
|
||||||
|
github.com/go-openapi/swag/stringutils v0.25.1 h1:Xasqgjvk30eUe8VKdmyzKtjkVjeiXx1Iz0zDfMNpPbw=
|
||||||
|
github.com/go-openapi/swag/stringutils v0.25.1/go.mod h1:JLdSAq5169HaiDUbTvArA2yQxmgn4D6h4A+4HqVvAYg=
|
||||||
|
github.com/go-openapi/swag/typeutils v0.25.1 h1:rD/9HsEQieewNt6/k+JBwkxuAHktFtH3I3ysiFZqukA=
|
||||||
|
github.com/go-openapi/swag/typeutils v0.25.1/go.mod h1:9McMC/oCdS4BKwk2shEB7x17P6HmMmA6dQRtAkSnNb8=
|
||||||
|
github.com/go-openapi/swag/yamlutils v0.25.1 h1:mry5ez8joJwzvMbaTGLhw8pXUnhDK91oSJLDPF1bmGk=
|
||||||
|
github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg=
|
||||||
|
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||||
|
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||||
|
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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
|
||||||
|
github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
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/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww=
|
||||||
|
github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ=
|
||||||
|
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
|
||||||
|
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
|
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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||||
|
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||||
|
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.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||||
|
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||||
|
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.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||||
|
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
|
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.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||||
|
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
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/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.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||||
|
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
44
internal/database/database.go
Normal file
44
internal/database/database.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DB *sql.DB
|
||||||
|
var dbName = os.Getenv("DB_NAME")
|
||||||
|
var dbUser = os.Getenv("DB_USER")
|
||||||
|
var dbPassword = os.Getenv("DB_PASSWORD")
|
||||||
|
var dbHost = os.Getenv("DB_HOST")
|
||||||
|
var dbPort = os.Getenv("DB_PORT")
|
||||||
|
|
||||||
|
var Conn_string = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true&charset=utf8mb4&collation=utf8mb4_unicode_ci", dbUser, dbPassword, dbHost, dbPort, dbName)
|
||||||
|
|
||||||
|
func InitDB(dataSourceName string) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
DB, err = sql.Open("mysql", dataSourceName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
DB.SetMaxOpenConns(25)
|
||||||
|
DB.SetMaxIdleConns(25)
|
||||||
|
DB.SetConnMaxLifetime(5 * time.Minute)
|
||||||
|
|
||||||
|
if err = DB.Ping(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
println("DB connected")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CloseDB() {
|
||||||
|
if DB != nil {
|
||||||
|
DB.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
47
internal/handler/base_handler.go
Normal file
47
internal/handler/base_handler.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Route struct {
|
||||||
|
pattern string
|
||||||
|
handler func(w http.ResponseWriter, r *http.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
type baseHandler struct {
|
||||||
|
mux *http.ServeMux
|
||||||
|
registeredRoutes map[string]bool
|
||||||
|
Routes []Route
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
bh *baseHandler
|
||||||
|
bhOnce sync.Once
|
||||||
|
bhMu sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewBaseHandler(mux *http.ServeMux, routes []Route) *baseHandler {
|
||||||
|
bhOnce.Do(func() {
|
||||||
|
bh = &baseHandler{
|
||||||
|
mux: mux,
|
||||||
|
registeredRoutes: make(map[string]bool),
|
||||||
|
Routes: routes,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
bhMu.Lock()
|
||||||
|
defer bhMu.Unlock()
|
||||||
|
bh.registerRoutes(routes)
|
||||||
|
return bh
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *baseHandler) registerRoutes(routes []Route) {
|
||||||
|
for _, route := range routes {
|
||||||
|
if !bh.registeredRoutes[route.pattern] {
|
||||||
|
b.mux.HandleFunc(route.pattern, route.handler)
|
||||||
|
bh.registeredRoutes[route.pattern] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
198
internal/handler/contact_handler.go
Normal file
198
internal/handler/contact_handler.go
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"gitea.gabilandia.com/gabdlr/agenda-web-go/internal/models"
|
||||||
|
"gitea.gabilandia.com/gabdlr/agenda-web-go/internal/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ContactHandler struct {
|
||||||
|
repository repository.Repository[models.Contact]
|
||||||
|
}
|
||||||
|
|
||||||
|
const required_field = "is required"
|
||||||
|
const invalid_id = "Invalid ID"
|
||||||
|
|
||||||
|
func HandleContacts(mux *http.ServeMux, repo repository.Repository[models.Contact]) {
|
||||||
|
ch := &ContactHandler{repository: repo}
|
||||||
|
routes := []Route{
|
||||||
|
{"GET /contacts", ch.getAll},
|
||||||
|
{"GET /contacts/{id}", ch.getByID},
|
||||||
|
{"POST /contacts", ch.create},
|
||||||
|
{"PUT /contacts/{id}", ch.update},
|
||||||
|
{"DELETE /contacts/{id}", ch.delete},
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
InternalError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
BadRequest(w, ErrInvalidFormat, invalid_id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
contact, err := h.repository.GetByID(id)
|
||||||
|
if err != nil {
|
||||||
|
InternalError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if contact == nil {
|
||||||
|
NotFound(w, ErrNotFound, "Contact not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&contact); err != nil {
|
||||||
|
BadRequest(w, ErrInvalidJSON, "Error parsing the contact")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if contact.Name == "" {
|
||||||
|
err := ErrMissingRequired
|
||||||
|
err.Field = "name"
|
||||||
|
BadRequest(w, err, RequiredFieldErr("name"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if contact.Company == "" {
|
||||||
|
err := ErrMissingRequired
|
||||||
|
err.Field = "company"
|
||||||
|
BadRequest(w, err, RequiredFieldErr("company"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if contact.Phone == "" {
|
||||||
|
err := ErrMissingRequired
|
||||||
|
err.Field = "phone"
|
||||||
|
BadRequest(w, err, RequiredFieldErr("phone"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := h.repository.Create(&contact)
|
||||||
|
if err != nil {
|
||||||
|
InternalError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
contact.ID = int(id)
|
||||||
|
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 {
|
||||||
|
BadRequest(w, ErrInvalidFormat, invalid_id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var contact models.Contact
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&contact); err != nil {
|
||||||
|
BadRequest(w, ErrInvalidJSON, "Error parsing the contact")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
contact.ID = id
|
||||||
|
|
||||||
|
updateErr := h.repository.Update(&contact)
|
||||||
|
|
||||||
|
if updateErr != nil {
|
||||||
|
InternalError(w, updateErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
BadRequest(w, ErrInvalidFormat, invalid_id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rowsAffected, err := h.repository.Delete(id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
InternalError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if rowsAffected == 0 {
|
||||||
|
BadRequest(w, ErrNotFound, "Contact not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
JSONSuccess(w, nil, "Contact deleted successfully", http.StatusOK)
|
||||||
|
}
|
||||||
88
internal/handler/errors.go
Normal file
88
internal/handler/errors.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Domain (First Digit)
|
||||||
|
|
||||||
|
1xxx - Request/Input Errors
|
||||||
|
|
||||||
|
2xxx - Authentication/Authorization
|
||||||
|
|
||||||
|
3xxx - Business Logic/Validation
|
||||||
|
|
||||||
|
4xxx - Data/Resource Errors
|
||||||
|
|
||||||
|
5xxx - System/External Service Errors
|
||||||
|
|
||||||
|
Category (Second Digit)
|
||||||
|
|
||||||
|
x0xx - General/Generic errors in that domain
|
||||||
|
|
||||||
|
x1xx - Format/Structure errors
|
||||||
|
|
||||||
|
x2xx - Validation/Constraint errors
|
||||||
|
|
||||||
|
x3xx - State/Workflow errors
|
||||||
|
|
||||||
|
x4xx - Permission/Access errors
|
||||||
|
|
||||||
|
Specific Error (Last Two Digits)
|
||||||
|
|
||||||
|
xx00-xx99 - Specific error instances
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Domains
|
||||||
|
const (
|
||||||
|
DomainRequest = 1000
|
||||||
|
DomainAuth = 2000
|
||||||
|
DomainData = 4000
|
||||||
|
DomainSystem = 5000
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CategoryGeneral = 0
|
||||||
|
CategoryFormat = 100
|
||||||
|
CategoryValidation = 200
|
||||||
|
CategoryState = 300
|
||||||
|
CategoryPermission = 400
|
||||||
|
)
|
||||||
|
|
||||||
|
// Err code Domain + Category + Specific
|
||||||
|
const (
|
||||||
|
CodeInvalidJSON = DomainRequest + CategoryFormat + 1
|
||||||
|
InvalidParamFormat = DomainRequest + CategoryFormat + 0
|
||||||
|
CodeValidationFailed = DomainRequest + CategoryValidation + 0
|
||||||
|
CodeMissingRequired = DomainRequest + CategoryValidation + 1
|
||||||
|
CodeNotFound = DomainData + CategoryPermission + 0
|
||||||
|
CodeInternalError = DomainSystem + CategoryGeneral + 0
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidJSON = APIError{Code: CodeInvalidJSON, Message: "Invalid JSON in request body"}
|
||||||
|
ErrValidation = APIError{Code: CodeValidationFailed, Message: "Validation failed"}
|
||||||
|
ErrMissingRequired = APIError{Code: CodeMissingRequired, Message: "Required field is missing"}
|
||||||
|
ErrNotFound = APIError{Code: CodeNotFound, Message: "Resource not found"}
|
||||||
|
ErrInternalServer = APIError{Code: CodeInternalError, Message: "Internal server error"}
|
||||||
|
ErrInvalidFormat = APIError{Code: InvalidParamFormat, Message: "The given param doesn't match the format expectations"}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Helper functions for common errors
|
||||||
|
func BadRequest(w http.ResponseWriter, err APIError, details string) {
|
||||||
|
JSONError(w, err.Message, details, err.Code, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NotFound(w http.ResponseWriter, err APIError, details string) {
|
||||||
|
JSONError(w, err.Message, details, err.Code, http.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func InternalError(w http.ResponseWriter, err error) {
|
||||||
|
JSONError(w, ErrInternalServer.Message, err.Error(), ErrInternalServer.Code, http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RequiredFieldErr(field string) string {
|
||||||
|
return (fmt.Sprintf("%s %s", field, required_field))
|
||||||
|
}
|
||||||
16
internal/handler/health_handler.go
Normal file
16
internal/handler/health_handler.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandlHealthChecks(mux *http.ServeMux) {
|
||||||
|
routes := []Route{{"GET /health", healthCheck}}
|
||||||
|
NewBaseHandler(mux, routes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func healthCheck(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
||||||
|
}
|
||||||
65
internal/handler/response.go
Normal file
65
internal/handler/response.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type APIResponse struct {
|
||||||
|
// 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 {
|
||||||
|
// Error code
|
||||||
|
Code int `json:"code"`
|
||||||
|
// Human-readable error message
|
||||||
|
Message string `json:"message"`
|
||||||
|
// Additional error details
|
||||||
|
Details string `json:"details,omitempty"`
|
||||||
|
// Field name if applicable
|
||||||
|
Field string `json:"field,omitempty" example:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const content_type = "Content-Type"
|
||||||
|
const application_json = "application/json"
|
||||||
|
|
||||||
|
func JSONSuccess(w http.ResponseWriter, data any, message string, statusCode int) {
|
||||||
|
w.Header().Set(content_type, application_json)
|
||||||
|
w.WriteHeader(statusCode)
|
||||||
|
json.NewEncoder(w).Encode(APIResponse{
|
||||||
|
Success: true,
|
||||||
|
Data: data,
|
||||||
|
Message: message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func JSONError(w http.ResponseWriter, message, details string, code, statusCode int) {
|
||||||
|
w.Header().Set(content_type, application_json)
|
||||||
|
w.WriteHeader(statusCode)
|
||||||
|
json.NewEncoder(w).Encode(APIResponse{
|
||||||
|
Success: false,
|
||||||
|
Errors: []APIError{
|
||||||
|
{
|
||||||
|
Code: code,
|
||||||
|
Message: message,
|
||||||
|
Details: details,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func JSONErrors(w http.ResponseWriter, errors []APIError, statusCode int) {
|
||||||
|
w.Header().Set(content_type, application_json)
|
||||||
|
w.WriteHeader(statusCode)
|
||||||
|
json.NewEncoder(w).Encode(APIResponse{
|
||||||
|
Success: false,
|
||||||
|
Errors: errors,
|
||||||
|
})
|
||||||
|
}
|
||||||
13
internal/models/contact.go
Normal file
13
internal/models/contact.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
// Contact represents a contact entity
|
||||||
|
type Contact struct {
|
||||||
|
// 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 number in international format
|
||||||
|
Phone string `json:"phone" db:"phone"`
|
||||||
|
}
|
||||||
74
internal/repository/base_repository.go
Normal file
74
internal/repository/base_repository.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type baseRepository[T any] struct {
|
||||||
|
db *sql.DB
|
||||||
|
tableName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBaseRepository[T any](db *sql.DB, tableName string) *baseRepository[T] {
|
||||||
|
return &baseRepository[T]{
|
||||||
|
db: db,
|
||||||
|
tableName: tableName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *baseRepository[T]) BuildQuery(baseQuery string) string {
|
||||||
|
return fmt.Sprintf(baseQuery, r.tableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *baseRepository[T]) GetDB() *sql.DB {
|
||||||
|
return r.db
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *baseRepository[T]) GetAll() ([]T, error) {
|
||||||
|
query := r.BuildQuery("SELECT * FROM %s ORDER BY id DESC")
|
||||||
|
|
||||||
|
rows, err := r.db.Query(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
entities := make([]T, 0)
|
||||||
|
rowsErr := ScanRows(rows, &entities)
|
||||||
|
|
||||||
|
if rowsErr != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return entities, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *baseRepository[T]) GetByID(id int) (*T, error) {
|
||||||
|
var entity T
|
||||||
|
|
||||||
|
query := r.BuildQuery("SELECT * FROM %s WHERE id = ?")
|
||||||
|
row := r.db.QueryRow(
|
||||||
|
query,
|
||||||
|
id,
|
||||||
|
)
|
||||||
|
err := scanRow(row, &entity)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &entity, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *baseRepository[T]) Delete(id int) (int64, error) {
|
||||||
|
query := r.BuildQuery("DELETE FROM %s WHERE id = ?")
|
||||||
|
res, err := r.db.Exec(query, id)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return res.RowsAffected()
|
||||||
|
}
|
||||||
83
internal/repository/contact_repository.go
Normal file
83
internal/repository/contact_repository.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gitea.gabilandia.com/gabdlr/agenda-web-go/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ContactRepository struct {
|
||||||
|
baseRepository[models.Contact]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContactRepository(db *sql.DB) Repository[models.Contact] {
|
||||||
|
return &ContactRepository{
|
||||||
|
baseRepository[models.Contact]{
|
||||||
|
db: db,
|
||||||
|
tableName: "contacts",
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ContactRepository) Create(contact *models.Contact) (int64, error) {
|
||||||
|
query := r.BuildQuery("INSERT INTO %s (name, company, phone) VALUES (?, ?, ?)")
|
||||||
|
|
||||||
|
result, err := r.db.Exec(
|
||||||
|
query,
|
||||||
|
contact.Name, contact.Company, contact.Phone,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := result.LastInsertId()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
contact.ID = int(id)
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ContactRepository) Update(contact *models.Contact) error {
|
||||||
|
query := r.BuildQuery("UPDATE %s SET")
|
||||||
|
fieldsToUpdate := make([]string, 0, 4)
|
||||||
|
fields := make([]any, 0)
|
||||||
|
|
||||||
|
if contact.Name != "" {
|
||||||
|
fieldsToUpdate = append(fieldsToUpdate, "name")
|
||||||
|
fields = append(fields, &contact.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if contact.Company != "" {
|
||||||
|
fieldsToUpdate = append(fieldsToUpdate, "company")
|
||||||
|
fields = append(fields, &contact.Company)
|
||||||
|
}
|
||||||
|
|
||||||
|
if contact.Phone != "" {
|
||||||
|
fieldsToUpdate = append(fieldsToUpdate, "phone")
|
||||||
|
fields = append(fields, &contact.Phone)
|
||||||
|
}
|
||||||
|
|
||||||
|
fields = append(fields, &contact.ID)
|
||||||
|
|
||||||
|
fieldsToUpdatelen := len(fieldsToUpdate)
|
||||||
|
for i, field := range fieldsToUpdate {
|
||||||
|
query += fmt.Sprintf(" %s = ?", field)
|
||||||
|
if i != fieldsToUpdatelen-1 {
|
||||||
|
query += ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query += " WHERE id = ?"
|
||||||
|
|
||||||
|
_, err := r.db.Exec(query,
|
||||||
|
fields...,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
9
internal/repository/interfaces.go
Normal file
9
internal/repository/interfaces.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
type Repository[T any] interface {
|
||||||
|
Create(T *T) (int64, error)
|
||||||
|
Delete(id int) (int64, error)
|
||||||
|
GetAll() ([]T, error)
|
||||||
|
GetByID(id int) (*T, error)
|
||||||
|
Update(contact *T) error
|
||||||
|
}
|
||||||
55
internal/repository/scanner_helper.go
Normal file
55
internal/repository/scanner_helper.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
func scanRow(row *sql.Row, dest any) error {
|
||||||
|
destValue := reflect.ValueOf(dest).Elem()
|
||||||
|
fields := make([]any, destValue.NumField())
|
||||||
|
|
||||||
|
for i := 0; i < destValue.NumField(); i++ {
|
||||||
|
fields[i] = destValue.Field(i).Addr().Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
return row.Scan(fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ScanRows(rows *sql.Rows, destSlice any) error {
|
||||||
|
sliceValue := reflect.ValueOf(destSlice)
|
||||||
|
if sliceValue.Kind() != reflect.Pointer || sliceValue.Elem().Kind() != reflect.Slice {
|
||||||
|
return fmt.Errorf("destSlice must be a pointer to a slice")
|
||||||
|
}
|
||||||
|
|
||||||
|
sliceElem := sliceValue.Elem()
|
||||||
|
structType := sliceElem.Type().Elem()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
newStruct := reflect.New(structType).Elem()
|
||||||
|
|
||||||
|
fields := make([]any, newStruct.NumField())
|
||||||
|
for i := 0; i < newStruct.NumField(); i++ {
|
||||||
|
fields[i] = newStruct.Field(i).Addr().Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Scan(fields...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sliceElem.Set(reflect.Append(sliceElem, newStruct))
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetStructFieldsPtr(object any) []any {
|
||||||
|
destValue := reflect.ValueOf(object).Elem()
|
||||||
|
fields := make([]any, destValue.NumField())
|
||||||
|
|
||||||
|
for i := 0; i < destValue.NumField(); i++ {
|
||||||
|
fields[i] = destValue.Field(i).Addr().Interface()
|
||||||
|
}
|
||||||
|
return fields
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user