Compare commits

..

9 Commits

17 changed files with 1811 additions and 0 deletions

40
cmd/server/main.go Normal file
View 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
View 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
View 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
View 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
View File

@@ -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
View 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=

View 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()
}
}

View 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
}
}
}

View 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)
}

View 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))
}

View 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"})
}

View 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,
})
}

View 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"`
}

View 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()
}

View 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
}

View 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
}

View 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
}