Compare commits
13 Commits
01a7f33054
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 503016d5b3 | |||
| cbc2646c3f | |||
| 027101081b | |||
| a76999fc71 | |||
| 30398245e2 | |||
| 74c1e6721e | |||
| bacc3f533a | |||
| 65838bac74 | |||
| 01dc6bb462 | |||
| a82adb90c2 | |||
| 6f419ece3d | |||
| 56209011ec | |||
| 99d035c915 |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.env
|
||||
set_dev_env.sh
|
||||
11
AI.md
Normal file
11
AI.md
Normal 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
20
Dockerfile
Normal 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
27
README.md
Normal 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!
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
_ "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/middleware"
|
||||
"gitea.gabilandia.com/gabdlr/agenda-web-go/internal/repository"
|
||||
httpSwagger "github.com/swaggo/http-swagger"
|
||||
)
|
||||
@@ -17,15 +18,15 @@ import (
|
||||
// @termsOfService http://swagger.io/terms/
|
||||
|
||||
// @contact.name API Support
|
||||
// @contact.email support@yourapp.com
|
||||
// @contact.email gabriel.delosrios@tutamail.com
|
||||
|
||||
// @license.name MIT
|
||||
// @license.url https://opensource.org/licenses/MIT
|
||||
|
||||
// @host localhost:8080
|
||||
// @host agenda-web-go.gabilandia.com
|
||||
// @BasePath /
|
||||
func main() {
|
||||
err := database.InitDB(database.Conn_string)
|
||||
err := database.InitDB()
|
||||
defer database.CloseDB()
|
||||
if err != nil {
|
||||
log.Fatal("Database connection failed:", err)
|
||||
@@ -36,5 +37,5 @@ func main() {
|
||||
handler.HandleContacts(mux, contactRepo)
|
||||
handler.HandlHealthChecks(mux)
|
||||
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
23
docker-compose.yml
Normal 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
|
||||
@@ -12,7 +12,7 @@ const docTemplate = `{
|
||||
"termsOfService": "http://swagger.io/terms/",
|
||||
"contact": {
|
||||
"name": "API Support",
|
||||
"email": "support@yourapp.com"
|
||||
"email": "gabriel.delosrios@tutamail.com"
|
||||
},
|
||||
"license": {
|
||||
"name": "MIT",
|
||||
@@ -364,7 +364,7 @@ const docTemplate = `{
|
||||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||
var SwaggerInfo = &swag.Spec{
|
||||
Version: "1.0",
|
||||
Host: "localhost:8080",
|
||||
Host: "agenda-web-go.gabilandia.com",
|
||||
BasePath: "/",
|
||||
Schemes: []string{},
|
||||
Title: "Contacts API",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"termsOfService": "http://swagger.io/terms/",
|
||||
"contact": {
|
||||
"name": "API Support",
|
||||
"email": "support@yourapp.com"
|
||||
"email": "gabriel.delosrios@tutamail.com"
|
||||
},
|
||||
"license": {
|
||||
"name": "MIT",
|
||||
@@ -14,7 +14,7 @@
|
||||
},
|
||||
"version": "1.0"
|
||||
},
|
||||
"host": "localhost:8080",
|
||||
"host": "agenda-web-go.gabilandia.com",
|
||||
"basePath": "/",
|
||||
"paths": {
|
||||
"/contacts": {
|
||||
|
||||
@@ -47,10 +47,10 @@ definitions:
|
||||
description: Phone number in international format
|
||||
type: string
|
||||
type: object
|
||||
host: localhost:8080
|
||||
host: agenda-web-go.gabilandia.com
|
||||
info:
|
||||
contact:
|
||||
email: support@yourapp.com
|
||||
email: gabriel.delosrios@tutamail.com
|
||||
name: API Support
|
||||
description: A simple Contacts CRUD API
|
||||
license:
|
||||
|
||||
@@ -3,37 +3,98 @@ package database
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"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() error {
|
||||
|
||||
func InitDB(dataSourceName string) error {
|
||||
var err error
|
||||
|
||||
DB, err = sql.Open("mysql", dataSourceName)
|
||||
if err != nil {
|
||||
if err := ensureDatabaseExists(); err != nil {
|
||||
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.SetMaxIdleConns(25)
|
||||
DB.SetConnMaxLifetime(5 * time.Minute)
|
||||
|
||||
if err = DB.Ping(); err != nil {
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
println("DB connected")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -42,3 +103,23 @@ func CloseDB() {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ type APIResponse struct {
|
||||
// Indicates if the request was successful
|
||||
Success bool `json:"success"`
|
||||
// The response data
|
||||
Data any `json:"data,omitempty"`
|
||||
Data any `json:"data"`
|
||||
// List of errors if any occurred
|
||||
Errors []APIError `json:"errors,omitempty"`
|
||||
// Optional message
|
||||
|
||||
19
internal/middleware/cors.go
Normal file
19
internal/middleware/cors.go
Normal 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)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user