diff --git a/backend/src/main.ts b/backend/src/main.ts index 160e527..8e8c73a 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -83,17 +83,25 @@ async function bootstrap() { app.useGlobalInterceptors(new DecimalInterceptor()); - const config = new DocumentBuilder() - .setTitle('Canina Iran API') - .setDescription( - 'API Documentation for Canina Iran Pet Health & Supplement Platform', - ) - .setVersion('1.0.0') - .addBearerAuth() - .build(); + const isProduction = process.env.NODE_ENV === 'production'; + const enableSwagger = process.env.ENABLE_SWAGGER === 'true'; - const document = SwaggerModule.createDocument(app, config); - SwaggerModule.setup('api/docs', app, document); + if (!isProduction || enableSwagger) { + const config = new DocumentBuilder() + .setTitle('Canina Iran API') + .setDescription( + 'API Documentation for Canina Iran Pet Health & Supplement Platform', + ) + .setVersion('1.0.0') + .addBearerAuth() + .build(); + + const document = SwaggerModule.createDocument(app, config); + SwaggerModule.setup('api/docs', app, document); + console.log('Swagger UI is ACTIVE on /api/docs'); + } else { + console.log('Swagger UI is DISABLED for security (production environment)'); + } await app.listen(process.env.PORT ?? 4001); } diff --git a/nginx.conf b/nginx.conf index 059625f..e2368d0 100644 --- a/nginx.conf +++ b/nginx.conf @@ -9,8 +9,23 @@ server { add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer-when-downgrade" always; + # Block SEO Indexing for Staging Environment + if ($host = "stage.canina.ir") { + add_header X-Robots-Tag "noindex, nofollow, nosnippet, noarchive" always; + } + # Main Application (Next.js) location / { + # Basic Authentication for Staging Environment + if ($host = "stage.canina.ir") { + set $auth_type "Staging Restricted Area"; + } + if ($host != "stage.canina.ir") { + set $auth_type "off"; + } + auth_basic $auth_type; + auth_basic_user_file /etc/nginx/.htpasswd; + proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; @@ -45,8 +60,22 @@ server { add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer-when-downgrade" always; + # Block SEO Indexing for Staging Admin + if ($host = "stageadmin.canina.ir") { + add_header X-Robots-Tag "noindex, nofollow, nosnippet, noarchive" always; + } + # SPA routing for Admin Panel location / { + if ($host = "stageadmin.canina.ir") { + set $auth_type "Staging Admin Restricted Area"; + } + if ($host != "stageadmin.canina.ir") { + set $auth_type "off"; + } + auth_basic $auth_type; + auth_basic_user_file /etc/nginx/.htpasswd; + try_files $uri $uri/ /index.html; } diff --git a/scripts/backup_db.sh b/scripts/backup_db.sh new file mode 100755 index 0000000..b39476c --- /dev/null +++ b/scripts/backup_db.sh @@ -0,0 +1,36 @@ +#!/bin/bash +set -eo pipefail + +# Configuration +CONTAINER_NAME="${DB_CONTAINER_NAME:-canino_db_prod}" +DB_USER="${POSTGRES_USER:-canino_prod}" +DB_NAME="${POSTGRES_DB:-caninodb_prod}" +BACKUP_DIR="${BACKUP_DIR:-/var/backups/canina_db}" +RETENTION_DAYS=7 + +TIMESTAMP=$(date +"%Y%m%d_%H%M%S") +BACKUP_FILE="${BACKUP_DIR}/canina_db_${TIMESTAMP}.sql.gz" + +echo "[$(date -u +'%Y-%m-%dT%H:%M:%SZ')] Starting automated PostgreSQL backup..." +echo "Target Container: ${CONTAINER_NAME}" +echo "Database: ${DB_NAME}" +echo "Backup Output: ${BACKUP_FILE}" + +# Ensure backup directory exists +mkdir -p "${BACKUP_DIR}" + +# Execute compressed database dump +if docker exec "${CONTAINER_NAME}" pg_dump -U "${DB_USER}" -d "${DB_NAME}" | gzip > "${BACKUP_FILE}"; then + BACKUP_SIZE=$(du -h "${BACKUP_FILE}" | cut -f1) + echo "[$(date -u +'%Y-%m-%dT%H:%M:%SZ')] Backup completed successfully! File size: ${BACKUP_SIZE}" +else + echo "[$(date -u +'%Y-%m-%dT%H:%M:%SZ')] ERROR: Backup failed!" >&2 + rm -f "${BACKUP_FILE}" + exit 1 +fi + +# Purge backups older than RETENTION_DAYS (7 days) +echo "[$(date -u +'%Y-%m-%dT%H:%M:%SZ')] Purging backups older than ${RETENTION_DAYS} days from ${BACKUP_DIR}..." +find "${BACKUP_DIR}" -type f -name "canina_db_*.sql.gz" -mtime +"${RETENTION_DAYS}" -exec rm -f {} \; -verbose || true + +echo "[$(date -u +'%Y-%m-%dT%H:%M:%SZ')] Backup process completed." diff --git a/scripts/compose.prod.yml b/scripts/compose.prod.yml index 05ac34f..cc1e864 100644 --- a/scripts/compose.prod.yml +++ b/scripts/compose.prod.yml @@ -34,6 +34,8 @@ services: - PORT=3000 - PRISMA_SCHEMA_ENGINE_BINARY=/app/node_modules/@prisma/engines/schema-engine-linux-musl-openssl-3.0.x - PRISMA_QUERY_ENGINE_LIBRARY=/app/node_modules/@prisma/engines/libquery_engine-linux-musl-openssl-3.0.x.so.node + volumes: + - canina_prod_uploads:/app/uploads labels: - "traefik.enable=true" - "traefik.http.routers.canina-api-prod.rule=Host(`api.canina.ir`)" @@ -72,3 +74,4 @@ networks: volumes: canina_prod_db: + canina_prod_uploads: diff --git a/scripts/compose.stage.yml b/scripts/compose.stage.yml index 287168a..3bc37bb 100644 --- a/scripts/compose.stage.yml +++ b/scripts/compose.stage.yml @@ -34,6 +34,8 @@ services: - PORT=3000 - PRISMA_SCHEMA_ENGINE_BINARY=/app/node_modules/@prisma/engines/schema-engine-linux-musl-openssl-3.0.x - PRISMA_QUERY_ENGINE_LIBRARY=/app/node_modules/@prisma/engines/libquery_engine-linux-musl-openssl-3.0.x.so.node + volumes: + - canina_stage_uploads:/app/uploads labels: - "traefik.enable=true" - "traefik.http.routers.canina-api-stage.rule=Host(`stageapi.canina.ir`)" @@ -60,6 +62,8 @@ services: - "traefik.http.routers.canina-stage.rule=Host(`stage.canina.ir`) || Host(`stageadmin.canina.ir`)" - "traefik.http.routers.canina-stage.entrypoints=websecure" - "traefik.http.routers.canina-stage.tls=true" + - "traefik.http.routers.canina-stage.middlewares=staging-robots-headers" + - "traefik.http.middlewares.staging-robots-headers.headers.customresponseheaders.X-Robots-Tag=noindex, nofollow, nosnippet, noarchive" - "traefik.http.services.canina-stage.loadbalancer.server.port=8080" depends_on: - backend_stage @@ -72,3 +76,4 @@ networks: volumes: canina_stage_db: + canina_stage_uploads: