initial commit
This commit is contained in:
commit
e7987e88a7
70 changed files with 13661 additions and 0 deletions
405
docs/API.md
Normal file
405
docs/API.md
Normal file
|
|
@ -0,0 +1,405 @@
|
|||
# API Documentation — go-first / URPI
|
||||
|
||||
Base URL: `https://www.urpi.be/api/v1`
|
||||
Local: `http://localhost:8080/api/v1`
|
||||
|
||||
All responses follow a consistent envelope format.
|
||||
|
||||
---
|
||||
|
||||
## Response Format
|
||||
|
||||
### Success
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {},
|
||||
"meta": {
|
||||
"page": 1,
|
||||
"page_size": 20,
|
||||
"total": 100,
|
||||
"total_pages": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Error
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": {
|
||||
"code": "ERROR_CODE",
|
||||
"message": "Human readable message",
|
||||
"details": "Optional technical details"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Error Codes
|
||||
|
||||
| Code | HTTP Status | Description |
|
||||
|------|-------------|-------------|
|
||||
| `VALIDATION_ERROR` | 400 | Request body validation failed |
|
||||
| `INVALID_ID` | 400 | UUID format invalid |
|
||||
| `NOT_FOUND` | 404 | Resource not found |
|
||||
| `UNAUTHORIZED` | 401 | Authentication required |
|
||||
| `FORBIDDEN` | 403 | Insufficient permissions |
|
||||
| `CONFLICT` | 409 | Resource conflict |
|
||||
| `RATE_LIMITED` | 429 | Too many requests |
|
||||
| `INTERNAL_ERROR` | 500 | Server error |
|
||||
| `SERVICE_UNAVAILABLE` | 503 | Service temporarily unavailable |
|
||||
|
||||
---
|
||||
|
||||
## Endpoints
|
||||
|
||||
### Health Check
|
||||
|
||||
**GET** `/health`
|
||||
|
||||
No authentication required.
|
||||
|
||||
#### Response
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"status": "ok",
|
||||
"service": "go-first",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Skills
|
||||
|
||||
#### List Skills
|
||||
|
||||
**GET** `/skills`
|
||||
|
||||
| Query Param | Type | Description |
|
||||
|-------------|------|-------------|
|
||||
| `category` | string | Filter by category (Backend, Frontend, Architecture, DevOps, Database, API) |
|
||||
|
||||
#### Response
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "Go",
|
||||
"category": "Backend",
|
||||
"proficiency": 5,
|
||||
"icon": "go",
|
||||
"order": 1,
|
||||
"created_at": "2024-01-01T00:00:00Z",
|
||||
"updated_at": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Get Skill
|
||||
|
||||
**GET** `/skills/:id`
|
||||
|
||||
| Path Param | Type | Description |
|
||||
|------------|------|-------------|
|
||||
| `id` | uuid | Skill UUID |
|
||||
|
||||
#### Response
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": "uuid",
|
||||
"name": "Go",
|
||||
"category": "Backend",
|
||||
"proficiency": 5,
|
||||
"icon": "go",
|
||||
"order": 1,
|
||||
"created_at": "2024-01-01T00:00:00Z",
|
||||
"updated_at": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Projects
|
||||
|
||||
#### List Projects
|
||||
|
||||
**GET** `/projects`
|
||||
|
||||
| Query Param | Type | Description |
|
||||
|-------------|------|-------------|
|
||||
| `category` | string | Filter by category |
|
||||
| `featured` | boolean | Filter featured projects only |
|
||||
|
||||
#### Response
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"title": "E-Commerce Platform",
|
||||
"description": "Full description...",
|
||||
"short_desc": "Scalable e-commerce platform...",
|
||||
"tech_stack": ["Go", "React", "PostgreSQL", "Redis", "Kubernetes"],
|
||||
"image_url": "/images/projects/ecommerce.jpg",
|
||||
"demo_url": "https://demo.urpi.be/ecommerce",
|
||||
"repo_url": "https://github.com/urpi/ecommerce-platform",
|
||||
"category": "Full Stack",
|
||||
"featured": true,
|
||||
"order": 1,
|
||||
"created_at": "2024-01-01T00:00:00Z",
|
||||
"updated_at": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Get Featured Projects
|
||||
|
||||
**GET** `/projects/featured`
|
||||
|
||||
Returns only projects with `featured: true`.
|
||||
|
||||
#### Get Project
|
||||
|
||||
**GET** `/projects/:id`
|
||||
|
||||
| Path Param | Type | Description |
|
||||
|------------|------|-------------|
|
||||
| `id` | uuid | Project UUID |
|
||||
|
||||
---
|
||||
|
||||
### Experience
|
||||
|
||||
#### List Experience
|
||||
|
||||
**GET** `/experience`
|
||||
|
||||
Returns all work experience entries ordered by `order` field.
|
||||
|
||||
#### Response
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"company": "URPI",
|
||||
"role": "Founder & Lead Developer",
|
||||
"location": "Belgium",
|
||||
"start_date": "2020-01-01T00:00:00Z",
|
||||
"end_date": null,
|
||||
"current": true,
|
||||
"description": "Founded URPI to provide web application...",
|
||||
"tech_stack": ["Go", "Java", "React", "Kubernetes", "AWS"],
|
||||
"order": 1,
|
||||
"created_at": "2024-01-01T00:00:00Z",
|
||||
"updated_at": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Services
|
||||
|
||||
#### List Services
|
||||
|
||||
**GET** `/services`
|
||||
|
||||
Returns all active services offered.
|
||||
|
||||
#### Response
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"title": "Web Application Development",
|
||||
"description": "Custom web applications built with modern technologies...",
|
||||
"icon": "code",
|
||||
"features": [
|
||||
"React/Next.js Frontend",
|
||||
"Go/Java Backend",
|
||||
"REST/GraphQL APIs"
|
||||
],
|
||||
"order": 1,
|
||||
"active": true,
|
||||
"created_at": "2024-01-01T00:00:00Z",
|
||||
"updated_at": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Contact
|
||||
|
||||
#### Submit Contact Form
|
||||
|
||||
**POST** `/contact`
|
||||
|
||||
Content-Type: `application/json`
|
||||
|
||||
| Field | Type | Required | Validation |
|
||||
|-------|------|----------|------------|
|
||||
| `name` | string | Yes | 2-100 chars |
|
||||
| `email` | string | Yes | Valid email format |
|
||||
| `subject` | string | Yes | 5-200 chars |
|
||||
| `message` | string | Yes | 10-5000 chars |
|
||||
|
||||
#### Request
|
||||
```json
|
||||
{
|
||||
"name": "John Doe",
|
||||
"email": "john@example.com",
|
||||
"subject": "Project Inquiry",
|
||||
"message": "Hello, I'd like to discuss a potential project..."
|
||||
}
|
||||
```
|
||||
|
||||
#### Response (201 Created)
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": "uuid",
|
||||
"name": "John Doe",
|
||||
"email": "john@example.com",
|
||||
"subject": "Project Inquiry",
|
||||
"message": "Hello, I'd like to discuss a potential project...",
|
||||
"status": "new",
|
||||
"created_at": "2024-01-15T10:30:00Z",
|
||||
"updated_at": "2024-01-15T10:30:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Validation Error (400)
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": {
|
||||
"code": "VALIDATION_ERROR",
|
||||
"message": "Invalid request body",
|
||||
"details": "Key: 'ContactForm.Email' Error:Field validation for 'Email' failed on the 'email' tag"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
| Endpoint | Limit |
|
||||
|----------|-------|
|
||||
| `/api/v1/*` (general) | 10 req/s per IP |
|
||||
| `/api/v1/contact` | 5 req/min per IP |
|
||||
| `/api/v1/health` | No limit |
|
||||
|
||||
Headers returned:
|
||||
- `X-RateLimit-Limit`
|
||||
- `X-RateLimit-Remaining`
|
||||
- `X-RateLimit-Reset`
|
||||
|
||||
---
|
||||
|
||||
## CORS
|
||||
|
||||
Allowed origins (configured in `config.yaml`):
|
||||
- `https://www.urpi.be`
|
||||
- `https://urpi.be`
|
||||
- `http://localhost:3000`
|
||||
- `http://localhost:5173`
|
||||
|
||||
Allowed methods: `GET, POST, PUT, PATCH, DELETE, OPTIONS`
|
||||
Allowed headers: `Origin, Content-Type, Accept, Authorization, X-Requested-With`
|
||||
Credentials: `true`
|
||||
|
||||
---
|
||||
|
||||
## Versioning
|
||||
|
||||
API version in URL path: `/api/v1/`
|
||||
|
||||
Breaking changes will result in `/api/v2/` etc.
|
||||
Non-breaking additions (new fields, new endpoints) may be added to v1.
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
Currently **no authentication required** for public endpoints.
|
||||
|
||||
Future plans:
|
||||
- JWT-based auth for admin endpoints
|
||||
- API keys for programmatic access
|
||||
- OAuth2 for third-party integrations
|
||||
|
||||
---
|
||||
|
||||
## SDKs / Clients
|
||||
|
||||
### JavaScript/TypeScript
|
||||
```typescript
|
||||
const API_BASE = '/api/v1';
|
||||
|
||||
async function fetchAPI<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
|
||||
const res = await fetch(`${API_BASE}${endpoint}`, {
|
||||
headers: { 'Content-Type': 'application/json', ...options.headers },
|
||||
...options,
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok || !data.success) throw new Error(data.error?.message || 'Error');
|
||||
return data.data;
|
||||
}
|
||||
|
||||
// Usage
|
||||
const skills = await fetchAPI<Skill[]>('/skills');
|
||||
const project = await fetchAPI<Project>('/projects/uuid');
|
||||
await fetchAPI<ContactSubmission>('/contact', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ name, email, subject, message })
|
||||
});
|
||||
```
|
||||
|
||||
### Go
|
||||
```go
|
||||
type APIResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
Error *APIError `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func GetSkills(ctx context.Context) ([]Skill, error) {
|
||||
req, _ := http.NewRequestWithContext(ctx, "GET", "https://www.urpi.be/api/v1/skills", nil)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
// ... parse response
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
### v1.0.0 (2024-01-15)
|
||||
- Initial API release
|
||||
- Skills, Projects, Experience, Services, Contact endpoints
|
||||
- Rate limiting and CORS configuration
|
||||
- Health check endpoint
|
||||
425
docs/DEPLOYMENT.md
Normal file
425
docs/DEPLOYMENT.md
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
# Deployment Guide — go-first / URPI
|
||||
|
||||
This guide covers deploying the go-first application to production at **https://www.urpi.be**.
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Architecture Overview
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||
│ Users │────▶│ NGINX │────▶│ Go App │
|
||||
│ (HTTPS) │ │ (SSL/TLS) │ │ (Port 8080)│
|
||||
└─────────────┘ └─────────────┘ └──────┬──────┘
|
||||
│
|
||||
┌─────────────┐ ┌──────▼──────┐
|
||||
│ PostgreSQL │◀───▶│ Redis │
|
||||
│ (Port 5432)│ │ (Port 6379)│
|
||||
└─────────────┘ └─────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Prerequisites
|
||||
|
||||
### Server Requirements
|
||||
- **OS**: Ubuntu 22.04+ / Debian 12+ / Alpine Linux
|
||||
- **CPU**: 2+ vCPUs
|
||||
- **RAM**: 4GB+ (2GB minimum for app + DB)
|
||||
- **Disk**: 20GB+ SSD
|
||||
- **Network**: Public IPv4/IPv6, ports 80, 443, 22
|
||||
|
||||
### Software
|
||||
- Docker 24+ & Docker Compose 2+
|
||||
- Git
|
||||
- Certbot (Let's Encrypt)
|
||||
- (Optional) kubectl for Kubernetes
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Server Setup
|
||||
|
||||
### 1. Initial Server Hardening
|
||||
|
||||
```bash
|
||||
# Update system
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
|
||||
# Create deploy user
|
||||
sudo adduser deploy --gecos "" --disabled-password
|
||||
sudo usermod -aG docker deploy
|
||||
sudo usermod -aG sudo deploy
|
||||
|
||||
# Setup SSH keys (from your local machine)
|
||||
ssh-copy-id deploy@your-server-ip
|
||||
|
||||
# Configure firewall
|
||||
sudo ufw allow 22/tcp # SSH
|
||||
sudo ufw allow 80/tcp # HTTP
|
||||
sudo ufw allow 443/tcp # HTTPS
|
||||
sudo ufw enable
|
||||
```
|
||||
|
||||
### 2. Install Docker
|
||||
|
||||
```bash
|
||||
# Install Docker
|
||||
curl -fsSL https://get.docker.com | sh
|
||||
|
||||
# Install Docker Compose
|
||||
sudo apt install -y docker-compose-plugin
|
||||
|
||||
# Verify
|
||||
docker --version
|
||||
docker compose version
|
||||
```
|
||||
|
||||
### 3. Install Certbot
|
||||
|
||||
```bash
|
||||
sudo apt install -y certbot python3-certbot-nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 Application Deployment
|
||||
|
||||
### 1. Clone Repository
|
||||
|
||||
```bash
|
||||
cd /opt
|
||||
sudo git clone https://github.com/dnoulet/go-first.git
|
||||
sudo chown -R deploy:deploy go-first
|
||||
cd go-first
|
||||
```
|
||||
|
||||
### 2. Configure Environment
|
||||
|
||||
```bash
|
||||
# Copy production template
|
||||
cp .env.production.example .env.production
|
||||
|
||||
# Edit with your values (use strong passwords!)
|
||||
vim .env.production
|
||||
|
||||
# Required changes:
|
||||
# - DATABASE_PASSWORD: Strong random password
|
||||
# - JWT_SECRET: 32+ char random string
|
||||
# - EMAIL_*: Your SMTP credentials
|
||||
```
|
||||
|
||||
### 3. Generate SSL Certificates
|
||||
|
||||
```bash
|
||||
# Stop any service on port 80
|
||||
sudo systemctl stop nginx 2>/dev/null || true
|
||||
|
||||
# Get certificates
|
||||
sudo certbot certonly --standalone \
|
||||
-d www.urpi.be \
|
||||
-d urpi.be \
|
||||
--email contact@urpi.be \
|
||||
--agree-tos --non-interactive
|
||||
|
||||
# Certificates will be at:
|
||||
# /etc/letsencrypt/live/www.urpi.be/fullchain.pem
|
||||
# /etc/letsencrypt/live/www.urpi.be/privkey.pem
|
||||
```
|
||||
|
||||
### 4. Setup NGINX SSL Directory
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /opt/go-first/deploy/ssl
|
||||
sudo cp /etc/letsencrypt/live/www.urpi.be/fullchain.pem /opt/go-first/deploy/ssl/
|
||||
sudo cp /etc/letsencrypt/live/www.urpi.be/privkey.pem /opt/go-first/deploy/ssl/
|
||||
sudo chown -R deploy:deploy /opt/go-first/deploy/ssl
|
||||
```
|
||||
|
||||
### 5. Deploy with Docker Compose
|
||||
|
||||
```bash
|
||||
cd /opt/go-first
|
||||
|
||||
# Build and start
|
||||
docker compose --env-file .env.production up -d --build
|
||||
|
||||
# Verify
|
||||
docker compose --env-file .env.production ps
|
||||
docker compose --env-file .env.production logs -f app
|
||||
```
|
||||
|
||||
### 6. Verify Deployment
|
||||
|
||||
```bash
|
||||
# Health check
|
||||
curl -f https://www.urpi.be/api/v1/health
|
||||
|
||||
# Check frontend
|
||||
curl -f https://www.urpi.be/ | grep -c "URPI"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 SSL Certificate Renewal
|
||||
|
||||
### Automatic Renewal (systemd timer)
|
||||
|
||||
```bash
|
||||
# Check certbot timer
|
||||
sudo systemctl status certbot.timer
|
||||
|
||||
# Test renewal
|
||||
sudo certbot renew --dry-run
|
||||
```
|
||||
|
||||
### Manual Renewal Hook
|
||||
|
||||
```bash
|
||||
# Create renewal hook
|
||||
sudo tee /etc/letsencrypt/renewal-hooks/deploy/nginx-reload.sh > /dev/null <<'EOF'
|
||||
#!/bin/bash
|
||||
cp /etc/letsencrypt/live/www.urpi.be/fullchain.pem /opt/go-first/deploy/ssl/
|
||||
cp /etc/letsencrypt/live/www.urpi.be/privkey.pem /opt/go-first/deploy/ssl/
|
||||
docker compose -f /opt/go-first/docker-compose.yml --env-file /opt/go-first/.env.production exec nginx nginx -s reload
|
||||
EOF
|
||||
|
||||
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/nginx-reload.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🗄️ Database Management
|
||||
|
||||
### Backup
|
||||
|
||||
```bash
|
||||
# Create backup script
|
||||
cat > /opt/go-first/backup.sh <<'EOF'
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
BACKUP_DIR="/opt/backups/go-first"
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
docker compose -f /opt/go-first/docker-compose.yml --env-file /opt/go-first/.env.production \
|
||||
exec -T postgres pg_dump -U postgres go_first | gzip > "$BACKUP_DIR/go_first_$DATE.sql.gz"
|
||||
|
||||
# Keep last 30 days
|
||||
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete
|
||||
|
||||
echo "Backup saved to $BACKUP_DIR/go_first_$DATE.sql.gz"
|
||||
EOF
|
||||
|
||||
chmod +x /opt/go-first/backup.sh
|
||||
|
||||
# Add to crontab (daily at 2 AM)
|
||||
(crontab -l 2>/dev/null; echo "0 2 * * * /opt/go-first/backup.sh") | crontab -
|
||||
```
|
||||
|
||||
### Restore
|
||||
|
||||
```bash
|
||||
# Stop app
|
||||
docker compose -f /opt/go-first/docker-compose.yml --env-file /opt/go-first/.env.production stop app
|
||||
|
||||
# Restore
|
||||
gunzip -c /opt/backups/go-first/go_first_20240115_020000.sql.gz | \
|
||||
docker compose -f /opt/go-first/docker-compose.yml --env-file /opt/go-first/.env.production exec -T postgres psql -U postgres go_first
|
||||
|
||||
# Start app
|
||||
docker compose -f /opt/go-first/docker-compose.yml --env-file /opt/go-first/.env.production start app
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Monitoring & Logging
|
||||
|
||||
### Log Aggregation (Optional: Loki + Promtail)
|
||||
|
||||
```yaml
|
||||
# Add to docker-compose.yml
|
||||
services:
|
||||
promtail:
|
||||
image: grafana/promtail:2.9
|
||||
volumes:
|
||||
- /var/log:/var/log
|
||||
- /opt/go-first/deploy/promtail.yml:/etc/promtail/config.yml
|
||||
depends_on:
|
||||
- loki
|
||||
|
||||
loki:
|
||||
image: grafana/loki:2.9
|
||||
volumes:
|
||||
- loki_data:/loki
|
||||
```
|
||||
|
||||
### Health Checks
|
||||
|
||||
```bash
|
||||
# Application health
|
||||
curl https://www.urpi.be/api/v1/health
|
||||
|
||||
# Database
|
||||
docker compose exec postgres pg_isready -U postgres
|
||||
|
||||
# Redis
|
||||
docker compose exec redis redis-cli ping
|
||||
|
||||
# NGINX
|
||||
docker compose exec nginx nginx -t
|
||||
```
|
||||
|
||||
### Metrics (Optional: Prometheus + Grafana)
|
||||
|
||||
Add to `docker-compose.yml`:
|
||||
```yaml
|
||||
prometheus:
|
||||
image: prom/prometheus:v2.47
|
||||
volumes:
|
||||
- ./deploy/prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
- prometheus_data:/prometheus
|
||||
ports:
|
||||
- "9090:9090"
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:10.2
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- grafana_data:/var/lib/grafana
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Updates & Rollbacks
|
||||
|
||||
### Update Application
|
||||
|
||||
```bash
|
||||
cd /opt/go-first
|
||||
|
||||
# Pull latest code
|
||||
git pull origin main
|
||||
|
||||
# Rebuild and deploy
|
||||
docker compose --env-file .env.production up -d --build --remove-orphans
|
||||
|
||||
# Verify
|
||||
curl -f https://www.urpi.be/api/v1/health
|
||||
```
|
||||
|
||||
### Rollback
|
||||
|
||||
```bash
|
||||
# List images
|
||||
docker images ghcr.io/dnoulet/go-first
|
||||
|
||||
# Rollback to previous tag
|
||||
docker tag ghcr.io/dnoulet/go-first:previous-tag ghcr.io/dnoulet/go-first:latest
|
||||
docker compose --env-file .env.production up -d --no-deps app
|
||||
```
|
||||
|
||||
### Zero-Downtime Deploy (with multiple replicas)
|
||||
|
||||
```yaml
|
||||
# In docker-compose.yml, add:
|
||||
deploy:
|
||||
replicas: 2
|
||||
update_config:
|
||||
parallelism: 1
|
||||
delay: 10s
|
||||
order: start-first
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ Security Checklist
|
||||
|
||||
- [ ] Strong DATABASE_PASSWORD (32+ chars)
|
||||
- [ ] Strong JWT_SECRET (32+ chars, random)
|
||||
- [ ] SMTP credentials secured
|
||||
- [ ] Firewall: only 22, 80, 443 open
|
||||
- [ ] SSH key-only authentication
|
||||
- [ ] Fail2ban configured for SSH
|
||||
- [ ] Regular security updates (`apt update && apt upgrade`)
|
||||
- [ ] Docker images scanned for vulnerabilities (`docker scout cves`)
|
||||
- [ ] NGINX security headers enabled
|
||||
- [ ] Rate limiting configured
|
||||
- [ ] CSP headers configured
|
||||
- [ ] HSTS enabled
|
||||
- [ ] Database not exposed publicly
|
||||
- [ ] Redis not exposed publicly
|
||||
- [ ] Backup encryption at rest
|
||||
- [ ] Audit logging enabled
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Troubleshooting
|
||||
|
||||
### Application Won't Start
|
||||
|
||||
```bash
|
||||
# Check logs
|
||||
docker compose --env-file .env.production logs app
|
||||
|
||||
# Common issues:
|
||||
# - Port 8080 already in use
|
||||
# - Database connection failed
|
||||
# - Config file missing
|
||||
# - Permission denied on volumes
|
||||
```
|
||||
|
||||
### 502 Bad Gateway
|
||||
|
||||
```bash
|
||||
# Check if app is healthy
|
||||
docker compose --env-file .env.production ps app
|
||||
|
||||
# Check NGINX config
|
||||
docker compose --env-file .env.production exec nginx nginx -t
|
||||
|
||||
# Check upstream
|
||||
docker compose --env-file .env.production exec nginx curl -f http://app:8080/api/v1/health
|
||||
```
|
||||
|
||||
### SSL Certificate Issues
|
||||
|
||||
```bash
|
||||
# Check certificate validity
|
||||
openssl x509 -in /etc/letsencrypt/live/www.urpi.be/fullchain.pem -text -noout
|
||||
|
||||
# Check NGINX SSL config
|
||||
docker compose --env-file .env.production exec nginx nginx -T | grep ssl
|
||||
```
|
||||
|
||||
### Database Connection Issues
|
||||
|
||||
```bash
|
||||
# Test connection
|
||||
docker compose --env-file .env.production exec postgres psql -U postgres -d go_first -c "SELECT 1;"
|
||||
|
||||
# Check credentials in .env.production match docker-compose.yml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📞 Support
|
||||
|
||||
For deployment issues:
|
||||
- Check logs: `docker compose logs -f`
|
||||
- Review this guide
|
||||
- Contact: contact@urpi.be
|
||||
|
||||
---
|
||||
|
||||
## 📝 Maintenance Windows
|
||||
|
||||
Recommended: **Sunday 02:00-04:00 UTC**
|
||||
|
||||
- System updates
|
||||
- Docker image updates
|
||||
- Database maintenance
|
||||
- Certificate renewal verification
|
||||
596
docs/DEVELOPMENT.md
Normal file
596
docs/DEVELOPMENT.md
Normal file
|
|
@ -0,0 +1,596 @@
|
|||
# Development Guide — go-first / URPI
|
||||
|
||||
This guide covers local development setup, workflows, and best practices.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Prerequisites
|
||||
|
||||
### Required Tools
|
||||
| Tool | Version | Install |
|
||||
|------|---------|---------|
|
||||
| Go | 1.23+ | `brew install go` / `apt install golang` |
|
||||
| Node.js | 20+ | `nvm install 20` / `fnm install 20` |
|
||||
| Docker | 24+ | [docker.com](https://docker.com) |
|
||||
| Git | 2.40+ | `brew install git` / `apt install git` |
|
||||
|
||||
### Recommended Tools
|
||||
- **IDE**: VS Code with Go + TypeScript extensions
|
||||
- **API Client**: HTTPie, Postman, or VS Code REST Client
|
||||
- **Database**: TablePlus, DBeaver, or pgAdmin
|
||||
- **Terminal**: Warp, Ghostty, or Kitty
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Getting Started
|
||||
|
||||
### 1. Clone & Setup
|
||||
|
||||
```bash
|
||||
git clone https://github.com/dnoulet/go-first.git
|
||||
cd go-first
|
||||
|
||||
# Install all dependencies
|
||||
make install
|
||||
|
||||
# This runs:
|
||||
# - go mod download
|
||||
# - cd web && npm ci
|
||||
```
|
||||
|
||||
### 2. Start Development Databases
|
||||
|
||||
```bash
|
||||
# Start PostgreSQL + Redis only
|
||||
docker compose up -d postgres redis
|
||||
|
||||
# Verify
|
||||
docker compose ps
|
||||
docker compose exec postgres pg_isready -U postgres
|
||||
docker compose exec redis redis-cli ping
|
||||
```
|
||||
|
||||
### 3. Configure Local Environment
|
||||
|
||||
```bash
|
||||
cp .env.local.example .env.local
|
||||
|
||||
# Edit if needed (defaults work for Docker databases)
|
||||
vim .env.local
|
||||
```
|
||||
|
||||
### 4. Run Development Servers
|
||||
|
||||
```bash
|
||||
# Option A: Both together (recommended)
|
||||
make dev
|
||||
|
||||
# Option B: Separate terminals
|
||||
# Terminal 1 - Backend
|
||||
make dev-backend
|
||||
|
||||
# Terminal 2 - Frontend
|
||||
make dev-frontend
|
||||
```
|
||||
|
||||
### 5. Verify
|
||||
|
||||
| Service | URL |
|
||||
|---------|-----|
|
||||
| Frontend | http://localhost:5173 |
|
||||
| Backend API | http://localhost:8080/api/v1 |
|
||||
| Health Check | http://localhost:8080/api/v1/health |
|
||||
| PostgreSQL | localhost:5432 |
|
||||
| Redis | localhost:6379 |
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Development Workflow
|
||||
|
||||
### Backend Development
|
||||
|
||||
```bash
|
||||
# Hot reload with air (already configured in .air.toml)
|
||||
make dev-backend
|
||||
|
||||
# Or manually:
|
||||
go run ./cmd/server
|
||||
```
|
||||
|
||||
#### Adding a New Endpoint
|
||||
|
||||
1. **Model** — Add types in `internal/model/models.go`
|
||||
2. **Service** — Create service in `internal/service/`
|
||||
3. **Handler** — Add handler in `internal/handler/handler.go`
|
||||
4. **Routes** — Register in `handler.RegisterRoutes()`
|
||||
5. **Tests** — Add tests in `*_test.go` files
|
||||
|
||||
### Frontend Development
|
||||
|
||||
```bash
|
||||
# Hot module replacement (Vite)
|
||||
make dev-frontend
|
||||
|
||||
# Or manually:
|
||||
cd web && npm run dev
|
||||
```
|
||||
|
||||
#### Adding a New Page/Section
|
||||
|
||||
1. **Types** — Add to `web/src/types/index.ts`
|
||||
2. **API** — Add endpoint to `web/src/services/api.ts`
|
||||
3. **Hook** — Create custom hook in `web/src/hooks/`
|
||||
4. **Component** — Create in `web/src/components/sections/`
|
||||
5. **Register** — Import and add to `web/src/App.tsx`
|
||||
|
||||
### Code Generation
|
||||
|
||||
```bash
|
||||
# Generate mocks for testing
|
||||
make generate
|
||||
|
||||
# This runs: go generate ./...
|
||||
# Uses: github.com/golang/mock/mockgen
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### Backend Tests
|
||||
|
||||
```bash
|
||||
# Run all tests with coverage
|
||||
make test-backend
|
||||
|
||||
# Run specific package
|
||||
go test -v ./internal/service/...
|
||||
|
||||
# Run with race detector
|
||||
go test -race ./...
|
||||
|
||||
# Generate coverage report
|
||||
go test -coverprofile=coverage.out ./...
|
||||
go tool cover -html=coverage.out -o coverage.html
|
||||
```
|
||||
|
||||
### Frontend Tests
|
||||
|
||||
```bash
|
||||
# Run tests
|
||||
make test-frontend
|
||||
|
||||
# Or manually:
|
||||
cd web && npm test
|
||||
|
||||
# With UI
|
||||
cd web && npm test -- --ui
|
||||
|
||||
# Coverage
|
||||
cd web && npm test -- --coverage
|
||||
```
|
||||
|
||||
### E2E Tests (Optional)
|
||||
|
||||
```bash
|
||||
# Install Playwright
|
||||
cd web && npx playwright install
|
||||
|
||||
# Run E2E tests
|
||||
cd web && npm run test:e2e
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Code Quality
|
||||
|
||||
### Linting
|
||||
|
||||
```bash
|
||||
# All linters
|
||||
make lint
|
||||
|
||||
# Backend only
|
||||
make lint-backend
|
||||
# Runs: golangci-lint run ./...
|
||||
|
||||
# Frontend only
|
||||
make lint-frontend
|
||||
# Runs: eslint + prettier check
|
||||
```
|
||||
|
||||
### Formatting
|
||||
|
||||
```bash
|
||||
# Format all code
|
||||
make fmt
|
||||
|
||||
# Backend
|
||||
go fmt ./...
|
||||
goimports -w .
|
||||
|
||||
# Frontend
|
||||
cd web && npm run format
|
||||
```
|
||||
|
||||
### Pre-commit Hooks
|
||||
|
||||
```bash
|
||||
# Install pre-commit
|
||||
pip install pre-commit
|
||||
pre-commit install
|
||||
|
||||
# Run manually
|
||||
pre-commit run --all-files
|
||||
```
|
||||
|
||||
#### `.pre-commit-config.yaml`
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/golangci/golangci-lint
|
||||
rev: v1.56.0
|
||||
hooks:
|
||||
- id: golangci-lint
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v3.1.0
|
||||
hooks:
|
||||
- id: prettier
|
||||
types: [typescript, json, yaml, markdown]
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: go-fmt
|
||||
name: go fmt
|
||||
entry: go fmt ./...
|
||||
language: system
|
||||
types: [go]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🗃️ Database Migrations
|
||||
|
||||
### Creating Migrations
|
||||
|
||||
```bash
|
||||
# Create new migration
|
||||
make db-migrate-create name=add_users_table
|
||||
|
||||
# This creates:
|
||||
# migrations/000001_add_users_table.up.sql
|
||||
# migrations/000001_add_users_table.down.sql
|
||||
```
|
||||
|
||||
### Running Migrations
|
||||
|
||||
```bash
|
||||
# Apply all pending
|
||||
make db-migrate-up
|
||||
|
||||
# Rollback last
|
||||
make db-migrate-down
|
||||
|
||||
# Check status
|
||||
migrate -path ./migrations -database "$DSN" version
|
||||
```
|
||||
|
||||
### Migration Best Practices
|
||||
|
||||
- **Up migrations**: Add columns, tables, indexes
|
||||
- **Down migrations**: Reverse exactly (drop what you added)
|
||||
- **Never modify** applied migrations
|
||||
- **Test** both up and down locally
|
||||
- **Name clearly**: `add_user_email_index`, not `fix_db`
|
||||
|
||||
---
|
||||
|
||||
## 🐳 Docker Development
|
||||
|
||||
### Build Locally
|
||||
|
||||
```bash
|
||||
# Build production image
|
||||
make docker-build
|
||||
|
||||
# Run production image locally
|
||||
make docker-run
|
||||
|
||||
# Or with docker-compose (includes DB)
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
### Debugging in Container
|
||||
|
||||
```bash
|
||||
# Run with shell
|
||||
docker run -it --rm -p 8080:8080 ghcr.io/dnoulet/go-first:latest sh
|
||||
|
||||
# Or attach to running
|
||||
docker exec -it go-first-app sh
|
||||
```
|
||||
|
||||
### Development with Docker Compose Override
|
||||
|
||||
Create `docker-compose.override.yml`:
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.dev
|
||||
volumes:
|
||||
- .:/app
|
||||
- /app/web/node_modules
|
||||
command: air -c .air.toml
|
||||
environment:
|
||||
- SERVER_MODE=debug
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Frontend Architecture
|
||||
|
||||
### Component Structure
|
||||
|
||||
```
|
||||
web/src/components/
|
||||
├── ui/ # Reusable primitives (Button, Card, Input, Badge)
|
||||
├── layout/ # Page structure (Header, Footer)
|
||||
├── sections/ # Page sections (Hero, Services, Projects, etc.)
|
||||
└── forms/ # Form-specific components
|
||||
```
|
||||
|
||||
### State Management
|
||||
|
||||
- **Server state**: `useApi` hook (React Query pattern)
|
||||
- **Local state**: `useState`, `useReducer`
|
||||
- **Forms**: Controlled components with validation
|
||||
- **No global store** (Redux/Zustand) — keep it simple
|
||||
|
||||
### Styling
|
||||
|
||||
- **Tailwind CSS** utility-first
|
||||
- **Custom design system** in `tailwind.config.js`
|
||||
- **CSS variables** for theming (dark mode ready)
|
||||
- **Component variants** via `clsx` + `tailwind-merge`
|
||||
|
||||
### Adding Icons
|
||||
|
||||
```bash
|
||||
# Use lucide-react (already installed)
|
||||
import { IconName } from 'lucide-react'
|
||||
|
||||
# Or add to public/ and use <img>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Backend Architecture
|
||||
|
||||
### Clean Architecture Layers
|
||||
|
||||
```
|
||||
cmd/server/ # Entry point, wiring
|
||||
internal/
|
||||
├── config/ # Configuration (YAML + env)
|
||||
├── handler/ # HTTP layer (Gin)
|
||||
├── middleware/ # Cross-cutting concerns
|
||||
├── model/ # Domain models, DTOs
|
||||
└── service/ # Business logic
|
||||
```
|
||||
|
||||
### Dependency Injection
|
||||
|
||||
Services are instantiated in `main.go` and passed to handlers:
|
||||
|
||||
```go
|
||||
skillService := service.NewSkillService()
|
||||
h := handler.NewHandler(skillService, ...)
|
||||
```
|
||||
|
||||
### Adding Database Support
|
||||
|
||||
1. Add DB config to `config.yaml`
|
||||
2. Create `internal/repository/` package
|
||||
3. Implement interfaces in `service/`
|
||||
4. Use `database/sql` or `sqlx` or `gorm`
|
||||
5. Add migrations
|
||||
|
||||
### Middleware Chain
|
||||
|
||||
```go
|
||||
router.Use(
|
||||
middleware.Recovery(), // Panic recovery
|
||||
middleware.Logger(), // Request logging
|
||||
middleware.SecurityHeaders(), // Security headers
|
||||
middleware.CORS(...), // CORS
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 Git Workflow
|
||||
|
||||
### Branch Naming
|
||||
|
||||
| Type | Pattern | Example |
|
||||
|------|---------|---------|
|
||||
| Feature | `feat/short-description` | `feat/contact-form-validation` |
|
||||
| Fix | `fix/short-description` | `fix/cors-header-missing` |
|
||||
| Docs | `docs/short-description` | `docs/update-api-docs` |
|
||||
| Refactor | `refactor/short-description` | `refactor/service-layer` |
|
||||
| Chore | `chore/short-description` | `chore/update-dependencies` |
|
||||
|
||||
### Commit Messages
|
||||
|
||||
Follow [Conventional Commits](https://www.conventionalcommits.org/):
|
||||
|
||||
```
|
||||
feat: add contact form validation
|
||||
fix: resolve CORS issue on production
|
||||
docs: update API documentation
|
||||
refactor: extract service interfaces
|
||||
chore: update Go dependencies
|
||||
```
|
||||
|
||||
### Pull Requests
|
||||
|
||||
1. Create feature branch from `main`
|
||||
2. Make changes with tests
|
||||
3. Run `make lint && make test`
|
||||
4. Push and open PR
|
||||
5. Require: CI passing, 1 approval
|
||||
6. Squash merge to `main`
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Debugging
|
||||
|
||||
### Backend Debugging
|
||||
|
||||
```bash
|
||||
# Delve debugger
|
||||
dlv debug ./cmd/server
|
||||
|
||||
# VS Code launch.json
|
||||
{
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "debug",
|
||||
"program": "${workspaceFolder}/cmd/server",
|
||||
"env": {
|
||||
"SERVER_MODE": "debug"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Frontend Debugging
|
||||
|
||||
```bash
|
||||
# React DevTools (browser extension)
|
||||
# Redux DevTools (if using)
|
||||
|
||||
# VS Code debug config:
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"url": "http://localhost:5173",
|
||||
"webRoot": "${workspaceFolder}/web/src"
|
||||
}
|
||||
```
|
||||
|
||||
### Database Debugging
|
||||
|
||||
```bash
|
||||
# Connect to PostgreSQL
|
||||
docker compose exec postgres psql -U postgres -d go_first
|
||||
|
||||
# Common queries
|
||||
\dt # List tables
|
||||
\d users # Describe table
|
||||
SELECT * FROM skills; # Query data
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 Dependency Management
|
||||
|
||||
### Go Dependencies
|
||||
|
||||
```bash
|
||||
# Add dependency
|
||||
go get github.com/new/package
|
||||
|
||||
# Update all
|
||||
go get -u ./...
|
||||
go mod tidy
|
||||
|
||||
# Check outdated
|
||||
make check-deps
|
||||
```
|
||||
|
||||
### Frontend Dependencies
|
||||
|
||||
```bash
|
||||
# Add dependency
|
||||
cd web && npm install package-name
|
||||
|
||||
# Add dev dependency
|
||||
cd web && npm install -D package-name
|
||||
|
||||
# Update
|
||||
cd web && npm update
|
||||
|
||||
# Audit
|
||||
cd web && npm audit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Performance Tips
|
||||
|
||||
### Backend
|
||||
- Use connection pooling (configured in config)
|
||||
- Add Redis caching for expensive queries
|
||||
- Enable gzip compression (NGINX)
|
||||
- Use `pprof` for profiling: `import _ "net/http/pprof"`
|
||||
|
||||
### Frontend
|
||||
- Code splitting (Vite does this automatically)
|
||||
- Lazy load sections: `const Section = lazy(() => import('./Section'))`
|
||||
- Optimize images (WebP, proper sizing)
|
||||
- Use `React.memo` for expensive components
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Environment Variables
|
||||
|
||||
### Required for Development
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `SERVER_MODE` | `debug` or `release` | `debug` |
|
||||
| `DATABASE_HOST` | PostgreSQL host | `localhost` |
|
||||
| `DATABASE_PASSWORD` | PostgreSQL password | `postgres` |
|
||||
| `JWT_SECRET` | JWT signing key | `dev-secret` |
|
||||
| `CORS_ALLOWED_ORIGINS` | Comma-separated origins | `http://localhost:3000,http://localhost:5173` |
|
||||
|
||||
### Optional
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `EMAIL_SMTP_*` | SMTP config for contact form emails |
|
||||
| `LOGGING_LEVEL` | `debug`, `info`, `warn`, `error` |
|
||||
| `REDIS_*` | Redis connection (for caching/sessions) |
|
||||
|
||||
---
|
||||
|
||||
## 📚 Resources
|
||||
|
||||
- [Go Project Layout](https://github.com/golang-standards/project-layout)
|
||||
- [Effective Go](https://go.dev/doc/effective_go)
|
||||
- [React Beta Docs](https://react.dev)
|
||||
- [Tailwind CSS Docs](https://tailwindcss.com/docs)
|
||||
- [Gin Framework](https://gin-gonic.com/docs/)
|
||||
- [Zerolog](https://github.com/rs/zerolog)
|
||||
- [Vite Guide](https://vitejs.dev/guide/)
|
||||
|
||||
---
|
||||
|
||||
## ❓ FAQ
|
||||
|
||||
**Q: How do I add a new API field?**
|
||||
A: Update model → service → handler → frontend types → component
|
||||
|
||||
**Q: Why is my frontend not updating?**
|
||||
A: Check Vite HMR, clear browser cache, check console for errors
|
||||
|
||||
**Q: How do I test email sending locally?**
|
||||
A: Use mock email service (default) or configure Mailhog/SMTP
|
||||
|
||||
**Q: Can I use a different database?**
|
||||
A: Yes, implement repository interface in `internal/repository/`
|
||||
|
||||
**Q: How do I deploy a preview environment?**
|
||||
A: Use `make deploy-staging` or create PR preview with GitHub Actions
|
||||
Loading…
Add table
Add a link
Reference in a new issue