feat: complete Phase 1 foundation scaffold
Some checks failed
CI / Lint (push) Has been cancelled
CI / TypeCheck (push) Has been cancelled
CI / Test (push) Has been cancelled
Deploy / Docker Build & Push (map[context:. dockerfile:apps/api/Dockerfile name:api]) (push) Has been cancelled
Deploy / Docker Build & Push (map[context:. dockerfile:apps/cms/Dockerfile name:cms]) (push) Has been cancelled
Deploy / Docker Build & Push (map[context:. dockerfile:apps/web/Dockerfile name:web]) (push) Has been cancelled
CI / Build (push) Has been cancelled
Deploy / Deploy (push) Has been cancelled

- Monorepo: Turborepo + pnpm workspaces with 7 apps/packages
- Backend: NestJS scaffold with 9 modules (auth, tenant, curriculum, simulation, portfolio, ai-coach, analytics, classroom, gamification)
- Frontend: React 18 + Vite + TailwindCSS + shadcn/ui with 10 pages, 14 UI primitives, 3 Zustand stores
- Database: Prisma schema with 19 models, 13 enums, seed script, multi-tenant ready
- Docker: Dev, production, and Portainer Compose files with Traefik reverse proxy
- Configuration: .env.example (47 vars), Zod validation, frontend-safe env exposure
- i18n: English + Greek locale files (10 files), ICU MessageFormat, react-i18next
- Shared packages: @investplay/types, @investplay/ui, @investplay/i18n, @investplay/utils
- CI/CD: GitHub Actions (lint, typecheck, test, build, deploy, PR checks)
- Documentation: CONTEXT.md, ARCHITECTURE.md (10 Mermaid diagrams), API.md, LOCALIZATION.md, DEVELOPMENT-ROADMAP.md
- Infrastructure: Dockerfiles (dev + prod), nginx configs, backup/healthcheck scripts
- Security: JWT auth guards, role-based access, rate limiting, Helmet, CORS, PII sanitization
This commit is contained in:
Lefteris Notas
2026-06-12 19:32:57 +03:00
parent fa71c60cfc
commit e75f913f1c
226 changed files with 17547 additions and 0 deletions

154
scripts/backup.sh Normal file
View File

@@ -0,0 +1,154 @@
#!/usr/bin/env bash
set -euo pipefail
# ─── Configuration ──────────────────────────────────────────────
BACKUP_DIR="${BACKUP_DIR:-./backups}"
TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
BACKUP_PATH="${BACKUP_DIR}/${TIMESTAMP}"
RETENTION_DAILY=7
RETENTION_WEEKLY=4
# S3-compatible backup bucket (optional)
S3_ENDPOINT="${S3_BACKUP_ENDPOINT:-}"
S3_BUCKET="${S3_BACKUP_BUCKET:-}"
S3_ACCESS_KEY="${S3_BACKUP_ACCESS_KEY:-}"
S3_SECRET_KEY="${S3_BACKUP_SECRET_KEY:-}"
# ─── Color output ──────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*"; }
cleanup() {
local exit_code=$?
if [ $exit_code -ne 0 ]; then
error "Backup script failed with exit code ${exit_code}"
fi
exit $exit_code
}
trap cleanup EXIT
# ─── Ensure backup directory ───────────────────────────────────
mkdir -p "${BACKUP_PATH}"
# ─── 1. PostgreSQL backup ───────────────────────────────────────
info "Backing up PostgreSQL..."
POSTGRES_CONTAINER="${POSTGRES_CONTAINER:-investplay-postgres}"
POSTGRES_USER="${POSTGRES_USER:-investplay}"
POSTGRES_DB="${POSTGRES_DB:-investplay}"
if docker ps --format '{{.Names}}' | grep -q "^${POSTGRES_CONTAINER}$"; then
docker exec "${POSTGRES_CONTAINER}" pg_dump -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" \
--no-owner --no-acl --compress=9 \
> "${BACKUP_PATH}/postgres_${TIMESTAMP}.sql.gz" 2>/dev/null || {
warn "pg_dump failed — database may be temporarily unavailable"
}
ok "PostgreSQL backup saved"
else
warn "PostgreSQL container '${POSTGRES_CONTAINER}' not running — skipping"
fi
# ─── 2. Redis backup ────────────────────────────────────────────
info "Backing up Redis..."
REDIS_CONTAINER="${REDIS_CONTAINER:-investplay-redis}"
if docker ps --format '{{.Names}}' | grep -q "^${REDIS_CONTAINER}$"; then
docker exec "${REDIS_CONTAINER}" redis-cli SAVE >/dev/null 2>&1 || {
warn "Redis SAVE command failed"
}
# Copy the dump file from the container
docker cp "${REDIS_CONTAINER}:/data/dump.rdb" "${BACKUP_PATH}/redis_${TIMESTAMP}.rdb" 2>/dev/null || {
warn "Failed to copy Redis dump"
}
ok "Redis backup saved"
else
warn "Redis container '${REDIS_CONTAINER}' not running — skipping"
fi
# ─── 3. MinIO backup ────────────────────────────────────────────
info "Backing up MinIO..."
MINIO_CONTAINER="${MINIO_CONTAINER:-investplay-minio}"
MINIO_ALIAS="${MINIO_ALIAS:-investplay}"
MINIO_USER="${MINIO_ROOT_USER:-minioadmin}"
MINIO_PASS="${MINIO_ROOT_PASSWORD:-}"
if docker ps --format '{{.Names}}' | grep -q "^${MINIO_CONTAINER}$"; then
# Configure mc alias inside the container
docker exec "${MINIO_CONTAINER}" mc alias set "${MINIO_ALIAS}" \
http://localhost:9000 "${MINIO_USER}" "${MINIO_PASS}" >/dev/null 2>&1
# Mirror all buckets to local backup directory
mkdir -p "${BACKUP_PATH}/minio"
docker exec "${MINIO_CONTAINER}" mc mirror --overwrite \
"${MINIO_ALIAS}/" "/tmp/minio-backup" >/dev/null 2>&1 || {
warn "MinIO mc mirror failed"
}
docker cp "${MINIO_CONTAINER}:/tmp/minio-backup/." "${BACKUP_PATH}/minio/" 2>/dev/null || true
docker exec "${MINIO_CONTAINER}" rm -rf /tmp/minio-backup 2>/dev/null || true
ok "MinIO backup saved"
else
warn "MinIO container '${MINIO_CONTAINER}' not running — skipping"
fi
# ─── 4. Upload to S3-compatible backup bucket (optional) ────────
if [ -n "${S3_ENDPOINT}" ] && [ -n "${S3_BUCKET}" ]; then
info "Uploading backup to S3-compatible storage..."
if command -v mc &>/dev/null; then
mc alias set backup-target "${S3_ENDPOINT}" "${S3_ACCESS_KEY}" "${S3_SECRET_KEY}" >/dev/null 2>&1
mc mirror "${BACKUP_PATH}" "backup-target/${S3_BUCKET}/investplay/${TIMESTAMP}" >/dev/null 2>&1 && {
ok "Backup uploaded to S3"
} || {
warn "S3 upload failed"
}
elif command -v aws &>/dev/null; then
AWS_ACCESS_KEY_ID="${S3_ACCESS_KEY}" AWS_SECRET_ACCESS_KEY="${S3_SECRET_KEY}" \
aws s3 cp --endpoint-url="${S3_ENDPOINT}" --recursive \
"${BACKUP_PATH}" "s3://${S3_BUCKET}/investplay/${TIMESTAMP}/" >/dev/null 2>&1 && {
ok "Backup uploaded to S3"
} || {
warn "S3 upload failed"
}
else
warn "Neither 'mc' nor 'aws' CLI found — skipping S3 upload"
fi
fi
# ─── 5. Cleanup old backups ─────────────────────────────────────
info "Cleaning up old backups..."
total_backups=$(ls -1d "${BACKUP_DIR}/"20*/ 2>/dev/null | wc -l)
# Keep last N daily backups
ls -1d "${BACKUP_DIR}/"20*/ 2>/dev/null | sort -r | tail -n +$((RETENTION_DAILY + 1)) | while read -r dir; do
rm -rf "${dir}"
info "Removed old backup: $(basename "${dir}")"
done
# Keep last N weekly backups (every Sunday)
if [ "$(date +%u)" -eq 7 ]; then
ls -1d "${BACKUP_DIR}/"20*/ 2>/dev/null | sort -r | tail -n +$((RETENTION_WEEKLY + 1)) | while read -r dir; do
rm -rf "${dir}"
info "Removed weekly backup: $(basename "${dir}")"
done
fi
ok "Cleanup complete"
# ─── Summary ────────────────────────────────────────────────────
backup_size=$(du -sh "${BACKUP_PATH}" | cut -f1)
echo ""
echo -e "${GREEN}══════════════════════════════════════════════════════════${NC}"
echo -e "${GREEN} Backup completed successfully${NC}"
echo -e "${GREEN}══════════════════════════════════════════════════════════${NC}"
echo ""
echo -e " ${CYAN}Location:${NC} ${BACKUP_PATH}"
echo -e " ${CYAN}Size:${NC} ${backup_size}"
echo -e " ${CYAN}Date:${NC} $(date)"
echo ""