diff --git a/.gitignore b/.gitignore index b7e2909..4542f88 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ yarn-error.log* # Environment .env +.env.prod .env.local .env.development.local .env.test.local @@ -78,3 +79,4 @@ cypress/screenshots/ cypress/reports/ cypress/results/ cypress/.cache/ +frontend/public/logo-new.png diff --git a/backend/Dockerfile b/backend/Dockerfile index b3ec9a4..bc12a52 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -34,7 +34,7 @@ CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] FROM base AS production # Create non-root user -RUN useradd -m -u 1000 appuser && \ +RUN adduser -D -u 1000 appuser && \ mkdir -p /app/media /app/staticfiles && \ chown -R appuser:appuser /app diff --git a/backend/apps/core/views/setup.py b/backend/apps/core/views/setup.py index 573db9c..b0b258f 100644 --- a/backend/apps/core/views/setup.py +++ b/backend/apps/core/views/setup.py @@ -102,6 +102,7 @@ def complete_setup_wizard(request): role="admin", is_staff=True, is_active=True, + is_approved=True, timezone=data.get("timezone", "UTC"), ) diff --git a/backend/config/settings.py b/backend/config/settings.py index 81cb193..21e3590 100644 --- a/backend/config/settings.py +++ b/backend/config/settings.py @@ -25,7 +25,7 @@ # Production Security if not DEBUG: - SECURE_SSL_REDIRECT = True + SECURE_SSL_REDIRECT = os.getenv("SECURE_SSL_REDIRECT", "False") == "True" SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True SECURE_HSTS_SECONDS = 31536000 # 1 year diff --git a/backend/config/urls.py b/backend/config/urls.py index 6c26b25..ee5d38a 100644 --- a/backend/config/urls.py +++ b/backend/config/urls.py @@ -25,5 +25,16 @@ # path('admin/', admin.site.urls), ] +from django.urls import re_path +from django.views.static import serve + if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) +else: + urlpatterns += [ + re_path( + r"^media/(?P.*)$", + serve, + {"document_root": settings.MEDIA_ROOT}, + ) + ] diff --git a/docker-compose.override.yml b/docker-compose.override.yml new file mode 100644 index 0000000..411ff1c --- /dev/null +++ b/docker-compose.override.yml @@ -0,0 +1,26 @@ +# Development overrides: Host volumes + dev servers +# This file is automatically loaded by 'docker compose up' during development. +# Do NOT use this file in production. + +services: + backend: + build: + target: development + command: python manage.py runserver 0.0.0.0:8000 + volumes: + - ./backend:/app + environment: + DEBUG: "True" + + q_cluster: + build: + target: development + volumes: + - ./backend:/app + + frontend: + command: npm run dev + volumes: + - ./frontend:/app + - frontend_node_modules:/app/node_modules + - frontend_next:/app/.next diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..9a483b7 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,25 @@ +# Production stack: Optimized images with standalone builds +# Usage: docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d + +services: + # Django Backend + backend: + build: + target: production + env_file: .env.prod + environment: + DEBUG: "False" + + # Background Task Worker + q_cluster: + build: + target: production + env_file: .env.prod + + # Next.js Frontend + frontend: + build: + target: production + command: node server.js + environment: + NODE_ENV: production diff --git a/docker-compose.yml b/docker-compose.yml index 660b551..7f6b926 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ -# Full development stack: PostgreSQL + Redis + Django-Q worker + Django + Next.js -# Usage: docker compose up (or: docker compose watch) -# See docker-compose.simple.yml for SQLite only (no Postgres/Redis/background worker) +# Base stack: PostgreSQL + Django-Q worker + Django + Next.js +# This file defines the core services and their connections. +# Use docker-compose.override.yml (dev) or docker-compose.prod.yml (prod) for specific roles. services: # PostgreSQL Database @@ -23,54 +23,39 @@ services: build: context: ./backend dockerfile: Dockerfile - target: development # Use development stage for local dev with runserver - command: daphne -b 0.0.0.0 -p 8000 config.asgi:application - volumes: - - ./backend:/app ports: - "8000:8000" + volumes: + - media_data:/app/media + - static_data:/app/staticfiles environment: - DEBUG: "True" # <-- Enabled for dev debugging & preventing SSL redirects + DEBUG: "False" DATABASE_URL: postgresql://studio_user:studio_password@db:5432/studiosync - - # Cloudflare R2 / S3 Configuration + SECRET_KEY: ${SECRET_KEY:-dev-secret-key-change-in-production} + ALLOWED_HOSTS: ${ALLOWED_HOSTS:-localhost,127.0.0.1,backend} + CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-http://localhost:3000,http://10.0.0.250:3000} + # AWS / Stripe Config AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-} AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-} AWS_STORAGE_BUCKET_NAME: ${AWS_STORAGE_BUCKET_NAME:-} AWS_S3_ENDPOINT_URL: ${AWS_S3_ENDPOINT_URL:-} AWS_S3_USE_SSL: ${AWS_S3_USE_SSL:-} - SECRET_KEY: ${SECRET_KEY:-dev-secret-key-change-in-production} - ALLOWED_HOSTS: ${ALLOWED_HOSTS:-localhost,127.0.0.1} - CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-http://localhost:3000,http://10.0.0.250:3000} - - # Stripe Payment Processing STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY:-} STRIPE_PUBLISHABLE_KEY: ${STRIPE_PUBLISHABLE_KEY:-} STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET:-} depends_on: db: condition: service_healthy - develop: - watch: - - action: sync - path: ./backend - target: /app - ignore: - - venv/ - - __pycache__/ - - .venv/ - - action: rebuild - path: ./backend/requirements.txt - # Background Task Worker (Postgres-backed) + # Background Task Worker q_cluster: build: context: ./backend dockerfile: Dockerfile - target: development command: python manage.py qcluster volumes: - - ./backend:/app + - media_data:/app/media + - static_data:/app/staticfiles environment: DATABASE_URL: postgresql://studio_user:studio_password@db:5432/studiosync SECRET_KEY: ${SECRET_KEY:-dev-secret-key-change-in-production} @@ -85,11 +70,6 @@ services: build: context: ./frontend dockerfile: Dockerfile - command: npm run dev - volumes: - - ./frontend:/app - - frontend_node_modules:/app/node_modules - - frontend_next:/app/.next ports: - "3000:3000" environment: @@ -97,18 +77,10 @@ services: INTERNAL_API_URL: http://backend:8000/api depends_on: - backend - develop: - watch: - - action: sync - path: ./frontend - target: /app - ignore: - - node_modules/ - - .next/ - - action: rebuild - path: ./frontend/package.json volumes: postgres_data: frontend_node_modules: frontend_next: + media_data: + static_data: diff --git a/frontend/Dockerfile b/frontend/Dockerfile index f6df696..467aa53 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,16 +1,47 @@ -FROM node:20-alpine - +# Base stage for dependencies +FROM node:20-alpine AS base WORKDIR /app - -# Copy package files COPY package*.json ./ - -# Install dependencies RUN npm install -# Copy application code +# Development stage +FROM base AS development +COPY . . +EXPOSE 3000 +CMD ["npm", "run", "dev"] + +# Build stage +FROM base AS builder COPY . . +# Disable telemetry during build +ENV NEXT_TELEMETRY_DISABLED=1 +ARG INTERNAL_API_URL=http://backend:8000/api +ENV INTERNAL_API_URL=$INTERNAL_API_URL +RUN npm run build + +# Production stage +FROM node:20-alpine AS production +WORKDIR /app + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +ENV INTERNAL_API_URL=http://backend:8000/api + +# Create a non-root user +RUN addgroup --system --gid 1001 nodejs && \ + adduser --system --uid 1001 nextjs + +# Copy the standalone output and static files +# Note: output 'standalone' must be enabled in next.config.js +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs EXPOSE 3000 -CMD ["npm", "run", "dev"] +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +CMD ["node", "server.js"] diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx index c1fac0e..aaf0808 100644 --- a/frontend/app/layout.tsx +++ b/frontend/app/layout.tsx @@ -10,7 +10,7 @@ export const metadata: Metadata = { title: 'StudioSync - Music Studio Management', description: 'Sync your studio, students, and schedule — all in one place', icons: { - icon: process.env.NODE_ENV === 'development' ? '/logo-dev.svg' : '/favicon.ico', + icon: process.env.NODE_ENV === 'development' ? '/logo-dev.svg' : '/logo.png', apple: '/logo.png' } } diff --git a/frontend/app/setup/page.tsx b/frontend/app/setup/page.tsx index d8d9141..926db67 100644 --- a/frontend/app/setup/page.tsx +++ b/frontend/app/setup/page.tsx @@ -76,7 +76,7 @@ export default function SetupPage() {
StudioSync Logo { - const src = '/logo-dev.svg' + const src = '/logo.png' return (
diff --git a/frontend/next.config.js b/frontend/next.config.js index 8324635..bcf6ae9 100644 --- a/frontend/next.config.js +++ b/frontend/next.config.js @@ -80,6 +80,7 @@ const nextConfig = { ]; }, trailingSlash: true, + output: 'standalone', }; module.exports = nextConfig; diff --git a/frontend/public/logo.png b/frontend/public/logo.png index a6f2c84..d07a030 100644 Binary files a/frontend/public/logo.png and b/frontend/public/logo.png differ diff --git a/frontend/public/logo.svg b/frontend/public/logo.svg new file mode 100644 index 0000000..4f1e979 --- /dev/null +++ b/frontend/public/logo.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file