Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.git
.gitignore
__pycache__
*.pyc
*.pyo
*.pyd
.Python
env/
venv/
.env
.env.*
db.sqlite3
staticfiles/
media/
*.log
.coverage
htmlcov/
.pytest_cache/
.github/
Jenkinsfile
docker-compose.yml
Dockerfile
11 changes: 11 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
DEBUG=False
SECRET_KEY=your-secret-key-here
ALLOWED_HOSTS=127.0.0.1,localhost
DATABASE_URL=postgres://user:password@host:port/dbname
GEMINI_API_KEY=your-gemini-api-key
ANTHROPIC_API_KEY=your-anthropic-api-key
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
CSRF_TRUSTED_ORIGINS=http://127.0.0.1,http://localhost
EMAIL_HOST_USER=your-email@example.com
EMAIL_HOST_PASSWORD=your-app-password
64 changes: 64 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: CI/CD Pipeline

on:
push:
branches:
- main
- 'feature/**'
pull_request:
branches:
- main

jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt

- name: Django system check
env:
SECRET_KEY: 'ci-test-secret-key-at-least-50-characters-long-for-security'
DEBUG: 'True'
DATABASE_URL: ''
run: |
python manage.py check

- name: Run Tests
env:
SECRET_KEY: 'ci-test-secret-key-at-least-50-characters-long-for-security'
DEBUG: 'True'
DATABASE_URL: ''
run: |
python manage.py test --verbosity=2

build-and-push:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ secrets.DOCKER_USERNAME }}/studyai:latest
33 changes: 33 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Use an official Python runtime as a parent image
FROM python:3.11-slim

# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

# Set work directory
WORKDIR /app

# Install system dependencies
RUN apt-get update && apt-get install -y \
build-essential \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*

# Install Python dependencies
COPY requirements.txt /app/
RUN pip install --upgrade pip && \
pip install -r requirements.txt

# Copy the project
COPY . /app/

# Collect static files (inject dummy SECRET_KEY so Django doesn't error at build time)
RUN SECRET_KEY=dummy-build-secret DEBUG=False DATABASE_URL='' \
python manage.py collectstatic --noinput

# Expose port 8000
EXPOSE 8000

# Run the application with multiple workers for better concurrency
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "3", "--timeout", "120", "config.wsgi:application"]
44 changes: 44 additions & 0 deletions Jenkinsfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
pipeline {
agent any

environment {
DOCKER_IMAGE = "${env.DOCKER_USERNAME}/studyai:latest"
COMPOSE_FILE = "docker-compose.yml"
}

stages {
stage('Pull Image') {
steps {
script {
sh "docker pull ${DOCKER_IMAGE}"
}
}
}

stage('Deploy') {
steps {
script {
sh "docker-compose -f ${COMPOSE_FILE} down || true"
sh "docker-compose -f ${COMPOSE_FILE} up -d"
}
}
}

stage('Cleanup') {
steps {
script {
sh "docker image prune -f"
}
}
}
}

post {
success {
echo 'Deployment Successful!'
}
failure {
echo 'Deployment Failed!'
}
}
}
21 changes: 13 additions & 8 deletions config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
'ai_engine',
'quizzes',
'dashboard',
'features',
# axes REMOVED — incompatible with Django 6
]

Expand Down Expand Up @@ -155,13 +156,18 @@
CSRF_TRUSTED_ORIGINS = os.getenv('CSRF_TRUSTED_ORIGINS', 'http://127.0.0.1,http://localhost').split(',')

# ── EMAIL (for OTP) ──
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', '')
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', '')

if not EMAIL_HOST_USER and DEBUG:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
else:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'

EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', '')
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', '')
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER or 'noreply@studyai.local'

MESSAGE_TAGS = {
messages_constants.DEBUG: 'secondary',
Expand Down Expand Up @@ -209,10 +215,9 @@

SITE_ID = 1

# Minimal account settings so login drops straight in
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = 'email'
# Allauth settings (allauth >= 65.x API)
ACCOUNT_LOGIN_METHODS = {'email'}
ACCOUNT_SIGNUP_FIELDS = ['email*', 'password1*', 'password2*']
ACCOUNT_EMAIL_VERIFICATION = 'none'

SOCIALACCOUNT_PROVIDERS = {
Expand Down
6 changes: 3 additions & 3 deletions config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
urlpatterns = [
path('admin/', admin.site.urls),
path('', account_views.landing_page, name='landing'),
path('accounts/', include('accounts.urls')),
path('accounts/', include('allauth.urls')), # Allauth routes
path('accounts/', include('accounts.urls')), # Custom accounts
path('accounts/', include('allauth.urls')), # Allauth (OAuth)
path('courses/', include('courses.urls')),
path('ai/', include('ai_engine.urls')),
path('quizzes/', include('quizzes.urls')),
path('', include('accounts.urls')),
path('features/', include('features.urls')),
path('dashboard/', include('dashboard.urls')),
]

Expand Down
11 changes: 11 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version: '3.8'

services:
web:
build: .
container_name: studyai_web
ports:
- "8000:8000"
env_file:
- .env
restart: unless-stopped
1 change: 1 addition & 0 deletions features/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Init file
1 change: 1 addition & 0 deletions features/adaptive_load_optimizer/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Init file
Loading
Loading