diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml new file mode 100644 index 000000000..42c7a3af0 --- /dev/null +++ b/.github/workflows/backend.yml @@ -0,0 +1,30 @@ +name: Backend CI/CD + +on: + push: + branches: + - main + paths: + - 'backend/**' + +jobs: + build-and-push-backend: + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build Backend Image + run: | + docker build -t panther6/soulpage-backend:latest ./backend + + - name: Push Backend Image + run: | + docker push panther6/soulpage-backend:latest diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml new file mode 100644 index 000000000..f75683f4d --- /dev/null +++ b/.github/workflows/frontend.yml @@ -0,0 +1,28 @@ +name: Frontend CI/CD + +on: + push: + branches: + - main + paths: + - 'frontend/**' + +jobs: + build-and-push-frontend: + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build Frontend Image + run: docker build -t panther6/soulpage-frontend:latest ./frontend + + - name: Push Frontend Image + run: docker push panther6/soulpage-frontend:latest diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 000000000..33bf5c9c5 --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,11 @@ +# Deployment Guide + +Step 1: Clone repo +git clone https://github.com/soulpage/fullstack-assignment.git + +Step 2: Start project +docker-compose up --build -d + +Step 3: Access apps +Next.js -> http://SERVER_IP:3000 +Django -> http://SERVER_IP:8000 diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 000000000..e658a4519 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.10 + +WORKDIR /app + +COPY dependencies.txt . + +RUN pip install -r dependencies.txt + +COPY . . + +EXPOSE 8000 + +CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] diff --git a/backend/backend/settings.py b/backend/backend/settings.py index 9de4f024a..1ce0d76c1 100644 --- a/backend/backend/settings.py +++ b/backend/backend/settings.py @@ -30,7 +30,7 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = ["*"] # Application definition diff --git a/backup.json b/backup.json new file mode 100644 index 000000000..09b359432 --- /dev/null +++ b/backup.json @@ -0,0 +1 @@ +[{"model": "auth.permission", "pk": 1, "fields": {"name": "Can add log entry", "content_type": 1, "codename": "add_logentry"}}, {"model": "auth.permission", "pk": 2, "fields": {"name": "Can change log entry", "content_type": 1, "codename": "change_logentry"}}, {"model": "auth.permission", "pk": 3, "fields": {"name": "Can delete log entry", "content_type": 1, "codename": "delete_logentry"}}, {"model": "auth.permission", "pk": 4, "fields": {"name": "Can view log entry", "content_type": 1, "codename": "view_logentry"}}, {"model": "auth.permission", "pk": 5, "fields": {"name": "Can add permission", "content_type": 2, "codename": "add_permission"}}, {"model": "auth.permission", "pk": 6, "fields": {"name": "Can change permission", "content_type": 2, "codename": "change_permission"}}, {"model": "auth.permission", "pk": 7, "fields": {"name": "Can delete permission", "content_type": 2, "codename": "delete_permission"}}, {"model": "auth.permission", "pk": 8, "fields": {"name": "Can view permission", "content_type": 2, "codename": "view_permission"}}, {"model": "auth.permission", "pk": 9, "fields": {"name": "Can add group", "content_type": 3, "codename": "add_group"}}, {"model": "auth.permission", "pk": 10, "fields": {"name": "Can change group", "content_type": 3, "codename": "change_group"}}, {"model": "auth.permission", "pk": 11, "fields": {"name": "Can delete group", "content_type": 3, "codename": "delete_group"}}, {"model": "auth.permission", "pk": 12, "fields": {"name": "Can view group", "content_type": 3, "codename": "view_group"}}, {"model": "auth.permission", "pk": 13, "fields": {"name": "Can add content type", "content_type": 4, "codename": "add_contenttype"}}, {"model": "auth.permission", "pk": 14, "fields": {"name": "Can change content type", "content_type": 4, "codename": "change_contenttype"}}, {"model": "auth.permission", "pk": 15, "fields": {"name": "Can delete content type", "content_type": 4, "codename": "delete_contenttype"}}, {"model": "auth.permission", "pk": 16, "fields": {"name": "Can view content type", "content_type": 4, "codename": "view_contenttype"}}, {"model": "auth.permission", "pk": 17, "fields": {"name": "Can add session", "content_type": 5, "codename": "add_session"}}, {"model": "auth.permission", "pk": 18, "fields": {"name": "Can change session", "content_type": 5, "codename": "change_session"}}, {"model": "auth.permission", "pk": 19, "fields": {"name": "Can delete session", "content_type": 5, "codename": "delete_session"}}, {"model": "auth.permission", "pk": 20, "fields": {"name": "Can view session", "content_type": 5, "codename": "view_session"}}, {"model": "auth.permission", "pk": 21, "fields": {"name": "Can add custom user", "content_type": 6, "codename": "add_customuser"}}, {"model": "auth.permission", "pk": 22, "fields": {"name": "Can change custom user", "content_type": 6, "codename": "change_customuser"}}, {"model": "auth.permission", "pk": 23, "fields": {"name": "Can delete custom user", "content_type": 6, "codename": "delete_customuser"}}, {"model": "auth.permission", "pk": 24, "fields": {"name": "Can view custom user", "content_type": 6, "codename": "view_customuser"}}, {"model": "auth.permission", "pk": 25, "fields": {"name": "Can add conversation", "content_type": 7, "codename": "add_conversation"}}, {"model": "auth.permission", "pk": 26, "fields": {"name": "Can change conversation", "content_type": 7, "codename": "change_conversation"}}, {"model": "auth.permission", "pk": 27, "fields": {"name": "Can delete conversation", "content_type": 7, "codename": "delete_conversation"}}, {"model": "auth.permission", "pk": 28, "fields": {"name": "Can view conversation", "content_type": 7, "codename": "view_conversation"}}, {"model": "auth.permission", "pk": 29, "fields": {"name": "Can add message", "content_type": 8, "codename": "add_message"}}, {"model": "auth.permission", "pk": 30, "fields": {"name": "Can change message", "content_type": 8, "codename": "change_message"}}, {"model": "auth.permission", "pk": 31, "fields": {"name": "Can delete message", "content_type": 8, "codename": "delete_message"}}, {"model": "auth.permission", "pk": 32, "fields": {"name": "Can view message", "content_type": 8, "codename": "view_message"}}, {"model": "auth.permission", "pk": 33, "fields": {"name": "Can add role", "content_type": 9, "codename": "add_role"}}, {"model": "auth.permission", "pk": 34, "fields": {"name": "Can change role", "content_type": 9, "codename": "change_role"}}, {"model": "auth.permission", "pk": 35, "fields": {"name": "Can delete role", "content_type": 9, "codename": "delete_role"}}, {"model": "auth.permission", "pk": 36, "fields": {"name": "Can view role", "content_type": 9, "codename": "view_role"}}, {"model": "auth.permission", "pk": 37, "fields": {"name": "Can add version", "content_type": 10, "codename": "add_version"}}, {"model": "auth.permission", "pk": 38, "fields": {"name": "Can change version", "content_type": 10, "codename": "change_version"}}, {"model": "auth.permission", "pk": 39, "fields": {"name": "Can delete version", "content_type": 10, "codename": "delete_version"}}, {"model": "auth.permission", "pk": 40, "fields": {"name": "Can view version", "content_type": 10, "codename": "view_version"}}, {"model": "contenttypes.contenttype", "pk": 1, "fields": {"app_label": "admin", "model": "logentry"}}, {"model": "contenttypes.contenttype", "pk": 2, "fields": {"app_label": "auth", "model": "permission"}}, {"model": "contenttypes.contenttype", "pk": 3, "fields": {"app_label": "auth", "model": "group"}}, {"model": "contenttypes.contenttype", "pk": 4, "fields": {"app_label": "contenttypes", "model": "contenttype"}}, {"model": "contenttypes.contenttype", "pk": 5, "fields": {"app_label": "sessions", "model": "session"}}, {"model": "contenttypes.contenttype", "pk": 6, "fields": {"app_label": "authentication", "model": "customuser"}}, {"model": "contenttypes.contenttype", "pk": 7, "fields": {"app_label": "chat", "model": "conversation"}}, {"model": "contenttypes.contenttype", "pk": 8, "fields": {"app_label": "chat", "model": "message"}}, {"model": "contenttypes.contenttype", "pk": 9, "fields": {"app_label": "chat", "model": "role"}}, {"model": "contenttypes.contenttype", "pk": 10, "fields": {"app_label": "chat", "model": "version"}}] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..0b0115584 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,35 @@ +version: "3.9" + +services: + django: + build: ./backend + container_name: django_app + expose: + - "8000" + environment: + DJANGO_SECRET_KEY: supersecretkey123 + FRONTEND_URL: http://localhost + ALLOWED_HOSTS: "*" + DEBUG: "True" + restart: always + + nextjs: + build: ./frontend + container_name: nextjs_app + expose: + - "3000" + depends_on: + - django + restart: always + + nginx: + image: nginx:alpine + container_name: nginx_proxy + ports: + - "80:80" + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf + depends_on: + - django + - nextjs + restart: always diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 000000000..c561844aa --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,21 @@ +# Stage 1: Build +FROM node:18-alpine AS builder + +WORKDIR /app +COPY package*.json ./ +RUN npm install + +COPY . . +RUN npm run build + +# Stage 2: Production +FROM node:18-alpine + +WORKDIR /app + +COPY --from=builder /app ./ + +ENV NODE_ENV=production +EXPOSE 3000 + +CMD ["npm", "start"] diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 000000000..e121a3000 --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,15 @@ +events {} + +http { + server { + listen 80; + + location /api/ { + proxy_pass http://django:8000/; + } + + location / { + proxy_pass http://nextjs:3000/; + } + } +} diff --git a/scripts/backup.sh b/scripts/backup.sh new file mode 100755 index 000000000..ba3f24f1a --- /dev/null +++ b/scripts/backup.sh @@ -0,0 +1,2 @@ +#!/bin/bash +docker exec django_app python manage.py dumpdata > backup.json