Compare commits

..

13 Commits

12 changed files with 209 additions and 25 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.env
set_dev_env.sh

11
AI.md Normal file
View File

@@ -0,0 +1,11 @@
# AI Training Policy
This codebase is NOT intended for AI/ML training.
Do not use this code for:
- AI model training
- Code generation datasets
- Machine learning purposes
- Automated code suggestion systems
Permission is not granted for AI ingestion or training.

20
Dockerfile Normal file
View File

@@ -0,0 +1,20 @@
FROM golang:1.25.3-alpine AS builder
WORKDIR /app
COPY . .
# Build
RUN go mod download
RUN CGO_ENABLED=0 GOOS=linux go build -a -o main ./cmd/server
###########
FROM alpine:3.22.2
RUN apk --no-cache add ca-certificates
WORKDIR /root/
# Copy the pre-built binary
COPY --from=builder /app/main .
EXPOSE 8080
CMD ["./main"]

27
README.md Normal file
View File

@@ -0,0 +1,27 @@
## 🚫 AI/ML Training Restrictions
**This code is NOT suitable for AI training purposes.**
The code quality may contain security issues or anti-patterns.
Do not include this repository in any AI/ML training datasets.
# AgendaWeb
🇪🇸
Este sitio web es un remake de una de las lecciones en desarrollo web del curso "Desarrollo Web Completo con HTML5, CSS3, JS AJAX PHP y MySQL" por Juan Pablo De La Torre Valdez, con el objetivo de emplear intensivamente las herramientas nuevas y no tan nuevas que ofrece Angular. Adicionalmente el lado del backend esta desarrollado en Go con la finalidad de desarrollar competencias y expertise con este lenguaje.
API docs con [swagger](https://agenda-web-go.gabilandia.com/swagger)
Puedes ver el sitio original [aquí](https://agendaproject.epizy.com)
Y el repositorio [acá](https://github.com/gabdlr/projects-AgendaWeb)
El código del frontend esta en este [repositorio](https://gitea.gabilandia.com/gabdlr/agenda-web)
Que lo disfrutes!
🇺🇸
This web site is a remake of on the lessons from a web development course "Desarrollo Web Completo con HTML5, CSS3, JS AJAX PHP y MySQL" by Juan Pablo De La Torre Valdez, with the intention of intensively using newer and not so new tools offered by Angular. Aditionally the backend side has been developed using Go with the goal of developing proficiency and expetise with this language.
API docs with [swagger](https://agenda-web-go.gabilandia.com/swagger)
You can take a look a the original site [here](https://agendaproject.epizy.com)
And it's repository [here](https://github.com/gabdlr/projects-AgendaWeb)
The frontend code is in this [repository](https://gitea.gabilandia.com/gabdlr/agenda-web)
Enjoy!

View File

@@ -7,6 +7,7 @@ import (
_ "gitea.gabilandia.com/gabdlr/agenda-web-go/docs" _ "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/database"
"gitea.gabilandia.com/gabdlr/agenda-web-go/internal/handler" "gitea.gabilandia.com/gabdlr/agenda-web-go/internal/handler"
"gitea.gabilandia.com/gabdlr/agenda-web-go/internal/middleware"
"gitea.gabilandia.com/gabdlr/agenda-web-go/internal/repository" "gitea.gabilandia.com/gabdlr/agenda-web-go/internal/repository"
httpSwagger "github.com/swaggo/http-swagger" httpSwagger "github.com/swaggo/http-swagger"
) )
@@ -17,15 +18,15 @@ import (
// @termsOfService http://swagger.io/terms/ // @termsOfService http://swagger.io/terms/
// @contact.name API Support // @contact.name API Support
// @contact.email support@yourapp.com // @contact.email gabriel.delosrios@tutamail.com
// @license.name MIT // @license.name MIT
// @license.url https://opensource.org/licenses/MIT // @license.url https://opensource.org/licenses/MIT
// @host localhost:8080 // @host agenda-web-go.gabilandia.com
// @BasePath / // @BasePath /
func main() { func main() {
err := database.InitDB(database.Conn_string) err := database.InitDB()
defer database.CloseDB() defer database.CloseDB()
if err != nil { if err != nil {
log.Fatal("Database connection failed:", err) log.Fatal("Database connection failed:", err)
@@ -36,5 +37,5 @@ func main() {
handler.HandleContacts(mux, contactRepo) handler.HandleContacts(mux, contactRepo)
handler.HandlHealthChecks(mux) handler.HandlHealthChecks(mux)
mux.HandleFunc("/swagger/", httpSwagger.WrapHandler) mux.HandleFunc("/swagger/", httpSwagger.WrapHandler)
log.Fatal(http.ListenAndServe(":8080", mux)) log.Fatal(http.ListenAndServe(":8080", middleware.CorsMiddleware(mux)))
} }

23
docker-compose.yml Normal file
View File

@@ -0,0 +1,23 @@
services:
go-app:
build: .
container_name: agenda-web-go
environment:
- DB_NAME=${DB_NAME}
- DB_USER=${DB_USER}
- DB_PASSWORD=${DB_PASSWORD}
- DB_HOST=${DB_HOST}
- DB_PORT=${DB_PORT}
- ALLOWED_ORIGIN=${ALLOWED_ORIGIN}
networks:
- db-network
- proxy-network
volumes:
- ./:/app
restart: unless-stopped
networks:
db-network:
external: true
proxy-network:
external: true

View File

@@ -12,7 +12,7 @@ const docTemplate = `{
"termsOfService": "http://swagger.io/terms/", "termsOfService": "http://swagger.io/terms/",
"contact": { "contact": {
"name": "API Support", "name": "API Support",
"email": "support@yourapp.com" "email": "gabriel.delosrios@tutamail.com"
}, },
"license": { "license": {
"name": "MIT", "name": "MIT",
@@ -364,7 +364,7 @@ const docTemplate = `{
// SwaggerInfo holds exported Swagger Info so clients can modify it // SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{ var SwaggerInfo = &swag.Spec{
Version: "1.0", Version: "1.0",
Host: "localhost:8080", Host: "agenda-web-go.gabilandia.com",
BasePath: "/", BasePath: "/",
Schemes: []string{}, Schemes: []string{},
Title: "Contacts API", Title: "Contacts API",

View File

@@ -6,7 +6,7 @@
"termsOfService": "http://swagger.io/terms/", "termsOfService": "http://swagger.io/terms/",
"contact": { "contact": {
"name": "API Support", "name": "API Support",
"email": "support@yourapp.com" "email": "gabriel.delosrios@tutamail.com"
}, },
"license": { "license": {
"name": "MIT", "name": "MIT",
@@ -14,7 +14,7 @@
}, },
"version": "1.0" "version": "1.0"
}, },
"host": "localhost:8080", "host": "agenda-web-go.gabilandia.com",
"basePath": "/", "basePath": "/",
"paths": { "paths": {
"/contacts": { "/contacts": {

View File

@@ -47,10 +47,10 @@ definitions:
description: Phone number in international format description: Phone number in international format
type: string type: string
type: object type: object
host: localhost:8080 host: agenda-web-go.gabilandia.com
info: info:
contact: contact:
email: support@yourapp.com email: gabriel.delosrios@tutamail.com
name: API Support name: API Support
description: A simple Contacts CRUD API description: A simple Contacts CRUD API
license: license:

View File

@@ -3,37 +3,98 @@ package database
import ( import (
"database/sql" "database/sql"
"fmt" "fmt"
"log"
"os" "os"
"regexp"
"time" "time"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
) )
var DB *sql.DB 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() error {
func InitDB(dataSourceName string) error { if err := ensureDatabaseExists(); err != nil {
var err error
DB, err = sql.Open("mysql", dataSourceName)
if err != nil {
return err return err
} }
if err := connectToDatabase(); err != nil {
return err
}
if err := ensureTablesExist(); err != nil {
return err
}
log.Println("Database initialized successfully")
return nil
}
func ensureDatabaseExists() error {
dsn := getDSN("")
db, err := sql.Open("mysql", dsn)
if err != nil {
return fmt.Errorf("failed to connect to database server: %w", err)
}
defer db.Close()
// Get database name from environment
dbName := os.Getenv("DB_NAME")
// Check if database exists, create if not
var exists bool
err = db.QueryRow("SELECT EXISTS(SELECT 1 FROM information_schema.schemata WHERE schema_name = ?)", dbName).Scan(&exists)
if err != nil {
return fmt.Errorf("failed to check database existence: %w", err)
}
if !exists {
if !isValidDatabaseName(dbName) {
return fmt.Errorf("invalid database name: %s", dbName)
}
_, err = db.Exec("CREATE DATABASE `" + dbName + "` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci")
if err != nil {
return fmt.Errorf("failed to create database: %w", err)
}
}
return nil
}
func connectToDatabase() error {
dsn := getDSN(os.Getenv("DB_NAME"))
var err error
DB, err = sql.Open("mysql", dsn)
if err != nil {
return fmt.Errorf("failed to connect to database: %w", err)
}
DB.SetMaxOpenConns(25) DB.SetMaxOpenConns(25)
DB.SetMaxIdleConns(25) DB.SetMaxIdleConns(25)
DB.SetConnMaxLifetime(5 * time.Minute) DB.SetConnMaxLifetime(5 * time.Minute)
if err = DB.Ping(); err != nil { return nil
return err
} }
println("DB connected")
func ensureTablesExist() error {
_, err := DB.Exec(`
CREATE TABLE IF NOT EXISTS contacts (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(120) NOT NULL,
company VARCHAR(120) NOT NULL,
phone VARCHAR(15) NOT NULL
) DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
`)
if err != nil {
return fmt.Errorf("failed to create contacts table: %w", err)
}
return nil return nil
} }
@@ -42,3 +103,23 @@ func CloseDB() {
DB.Close() DB.Close()
} }
} }
func getDSN(dbName string) string {
user := os.Getenv("DB_USER")
password := os.Getenv("DB_PASSWORD")
host := os.Getenv("DB_HOST")
port := os.Getenv("DB_PORT")
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/", user, password, host, port)
if dbName != "" {
dsn += dbName
}
dsn += "?parseTime=true&charset=utf8mb4&collation=utf8mb4_unicode_ci"
return dsn
}
func isValidDatabaseName(name string) bool {
matched, _ := regexp.MatchString(`^[a-zA-Z0-9_-]+$`, name)
return matched && len(name) > 0 && len(name) <= 64
}

View File

@@ -9,7 +9,7 @@ type APIResponse struct {
// Indicates if the request was successful // Indicates if the request was successful
Success bool `json:"success"` Success bool `json:"success"`
// The response data // The response data
Data any `json:"data,omitempty"` Data any `json:"data"`
// List of errors if any occurred // List of errors if any occurred
Errors []APIError `json:"errors,omitempty"` Errors []APIError `json:"errors,omitempty"`
// Optional message // Optional message

View File

@@ -0,0 +1,19 @@
package middleware
import (
"net/http"
"os"
)
func CorsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", os.Getenv("ALLOWED_ORIGIN"))
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}