Skip to content
Open
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
38 changes: 38 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# --- STAGE 1: Builder ---
FROM debian:13-slim AS builder

WORKDIR /app

COPY . /app

RUN --mount=type=bind,source=.,target=/app \
--mount=from=ghcr.io/astral-sh/uv,source=/uv,target=/usr/bin/uv \
--mount=type=cache,target=/root/.cache/uv \
<<EOF

set -Eeux

uv python install 3.13 --install-dir=/tmp/python

(cd /tmp/python/* && tar -cf- .) | (cd /usr/local && tar -xf-)
rm -r /tmp/python

export UV_PROJECT_ENVIRONMENT=/usr/local
uv sync --project=/app --frozen --compile-bytecode --no-dev --no-editable --no-managed-python
EOF

FROM gcr.io/distroless/base-debian13:nonroot

ARG CHIPSET_ARCH=x86_64-linux-gnu

COPY --from=builder /lib/${CHIPSET_ARCH}/libz.so.1 /lib/${CHIPSET_ARCH}/

COPY --from=builder /usr/local /usr/local

COPY --from=builder /app /app

WORKDIR /app

USER nonroot

ENTRYPOINT ["python", "/app/scripts/entrypoint.py"]
21 changes: 21 additions & 0 deletions create_admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env python
import os
import django

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "website.settings")
django.setup()

from django.contrib.auth import get_user_model

User = get_user_model()

# 从环境变量获取超级用户信息,如果未设置则使用默认值
username = os.environ.get("DJANGO_SUPERUSER_USERNAME", "admin")
email = os.environ.get("DJANGO_SUPERUSER_EMAIL", "admin@example.com")
password = os.environ.get("DJANGO_SUPERUSER_PASSWORD", "admin123")

if not User.objects.filter(username=username).exists():
User.objects.create_superuser(username, email, password)
print(f"Superuser {username} created successfully!")
else:
print(f"Superuser {username} already exists!")
47 changes: 47 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
services:
db:
image: postgres:16-alpine
volumes:
- postgres_data:/var/lib/postgresql/data/
environment:
- POSTGRES_DB=coursereview
- POSTGRES_USER=admin
- POSTGRES_PASSWORD=test
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U admin -d coursereview"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped

cache:
image: valkey/valkey:7-alpine
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "valkey-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped

backend:
image: coursereview
ports:
- "8000:8000"
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
environment:
- DB_HOST=db
- DB_PORT=5432
- REDIS_URL=redis://cache:6379/0
- DEBUG=False
restart: unless-stopped

volumes:
postgres_data:
38 changes: 32 additions & 6 deletions docs/Setup.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,44 @@
# Development
## Script Setup
Environment:

- linux

- python `uv`, `docker`

- python >= 3.12

---

1. `git clone ssh://git@github.com/Tech-JI/CourseReview.git `

2. `cd CourseReview`

3. `git checkout dev`

4. `bash dev.sh`

5. fill in secret config in `.env`

## Manually Setup
Environment:

- Ubuntu Linux (most modern Linux distros and MacOS are supposedly supported.)

- Use your corresponding package manager. This guide uses ubuntu/debian's `apt`, python `uv`, modern javascript runtime and package manager `bun`.

- python 3.10 to 3.13
- python >= 3.12


---

1. `git clone git@github.com:TechJI-2023/CourseReview.git`
1. `git clone ssh://git@github.com/Tech-JI/CourseReview.git `

2. `cd CourseReview`

3. `git checkout dev`

4. `uv sync`
4. `uv venv .venv` and `uv sync`

5. `uv run pre-commit install` (for installing git hook in .git)

Expand All @@ -35,7 +57,8 @@ Environment:
DEBUG=True
OFFERINGS_THRESHOLD_FOR_TERM_UPDATE=100
```
Also cp .env.example in frontend/ and rename it .env.

Fill in other secret configs manually.

8. Build static files: `make collect`

Expand Down Expand Up @@ -112,8 +135,9 @@ Environment:
u.is_admin = True
u.save()
```
## Fetch Data(test)

13. Crawl data from JI official website:
1. Crawl data from JI official website:

1. Edit `COURSE_DETAIL_URL_PREFIX` in `apps/spider/crawlers/orc.py`: Add a number after url param `id` like this: `...?id=23`, so only course id starting from 23 (e.g. 230-239, 2300) will be crawled, so as to save time during development. Remember not to commit this change.

Expand All @@ -126,4 +150,6 @@ Environment:
crawl_and_import_data()
```

14. Run frontend (dev mode): `make dev-frontend` and visit http://127.0.0.1:5173/
# Local Development

run `make run` to run the backend.
45 changes: 45 additions & 0 deletions podman-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
services:
db:
image: postgres:16-alpine
volumes:
- postgres_data:/var/lib/postgresql/data/
environment:
- POSTGRES_DB=coursereview
- POSTGRES_USER=admin
- POSTGRES_PASSWORD=test
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U admin -d coursereview"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped

cache:
image: valkey/valkey:7-alpine
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "valkey-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped

backend:
image: coursereview
ports:
- "8000:8000"
depends_on:
- db
- cache
environment:
- DB_HOST=db
- DB_PORT=5432
- REDIS_URL=redis://cache:6379/0
- DEBUG=False
restart: unless-stopped

volumes:
postgres_data:
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
[project]
name = ""
version = "0.0.1"
requires-python = "==3.13.*"
dependencies = [
"psycopg2-binary>=2.9.10",
"beautifulsoup4>=4.13.3",
"dj-database-url>=2.3.0",
"django>=5.1.6",
"django-debug-toolbar>=5.0.1",
"httpx>=0.28.1",
"psycopg2-binary>=2.9.10",
"python-dateutil>=2.9.0",
"python-dotenv>=1.0.1",
"pytz>=2025.1",
Expand All @@ -19,8 +20,10 @@ dependencies = [
"django-cors-headers>=4.7.0",
"django-redis",
"pyyaml>=6.0.2",
"gunicorn>=23.0.0"
]


[tool.uv]
package = false

Expand Down
126 changes: 126 additions & 0 deletions scripts/dev.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#!/bin/bash

# Redirect all output to both stdout and a log file for debugging
exec > >(tee -a dev_setup.log) 2>&1

set -e

echo "Setting up the development environment..."

# Step 1-4: Create virtual environment, install dependencies, setup pre-commit hooks
echo "[INFO] Creating virtual environment..."
uv venv .venv
source .venv/bin/activate
echo "[INFO] Installing dependencies..."
uv sync
echo "[INFO] Setting up pre-commit hooks..."
uv run pre-commit install

# Step 6: Make directory for builds of static files
echo "[INFO] Creating static files directory..."
mkdir -p staticfiles
mkdir -p website/static

# Step 7: Create .env file for storing secrets
echo "[INFO] Creating .env file..."
echo "[INFO] Secret config should be manually added. "
cp .env.example .env

# Step 8: Build static files
echo "[INFO] Building static files..."
make collect

# Step 9: Configure database using Docker with lightweight images
echo "[INFO] Starting PostgreSQL container..."
# Stop and remove any existing container with the same name
docker stop coursereview-postgres 2>/dev/null || true
docker rm coursereview-postgres 2>/dev/null || true

docker run -d --name coursereview-postgres -p 5432:5432 \
-e POSTGRES_DB=coursereview \
-e POSTGRES_USER=admin \
-e POSTGRES_PASSWORD=test \
--restart unless-stopped \
postgres:16-alpine

# Wait for PostgreSQL to be ready
echo "[INFO] Waiting for PostgreSQL to be ready..."
POSTGRES_MAX_RETRIES=8
POSTGRES_WAIT_TIME=2
until docker exec coursereview-postgres pg_isready -U admin; do
POSTGRES_MAX_RETRIES=$((POSTGRES_MAX_RETRIES - 1))
if [ $POSTGRES_MAX_RETRIES -eq 0 ]; then
echo "[ERROR] PostgreSQL is not ready after multiple attempts"
exit 1
fi
echo "[INFO] Waiting for PostgreSQL... $POSTGRES_MAX_RETRIES attempts remaining"
sleep $POSTGRES_WAIT_TIME
done

# Check if PostgreSQL container is running
if docker ps | grep coursereview-postgres > /dev/null; then
echo "[INFO] PostgreSQL container is running"
else
echo "[ERROR] PostgreSQL container failed to start"
exit 1
fi

# Step 10: Run valkey using docker with a lightweight image
echo "[INFO] Starting Valkey container..."
# Stop and remove any existing container with the same name
docker stop valkey-cache 2>/dev/null || true
docker rm valkey-cache 2>/dev/null || true

docker run -d --name valkey-cache -p 6379:6379 \
--restart unless-stopped \
valkey/valkey:7-alpine

# Wait for Valkey to be ready
echo "[INFO] Waiting for Valkey to be ready..."
VALKEY_MAX_RETRIES=8
VALKEY_WAIT_TIME=2
until docker exec valkey-cache redis-cli ping; do
VALKEY_MAX_RETRIES=$((VALKEY_MAX_RETRIES - 1))
if [ $VALKEY_MAX_RETRIES -eq 0 ]; then
echo "[ERROR] Valkey is not ready after multiple attempts"
exit 1
fi
echo "[INFO] Waiting for Valkey... $VALKEY_MAX_RETRIES attempts remaining"
sleep $VALKEY_WAIT_TIME
done

# Check if Valkey container is running
if docker ps | grep valkey-cache > /dev/null; then
echo "[INFO] Valkey container is running"
else
echo "[ERROR] Valkey container failed to start"
exit 1
fi

# Step 9 continued: Auto setup database connection and static file routes in Django
echo "[INFO] Running Django migrations and creating database tables..."
make migrate

# Step 12: Add local admin
echo "[INFO] Creating superuser..."
make createsuperuser

echo "[INFO] Setting up admin permissions..."
# Execute the Python commands to make the last user an admin
make shell << 'EOF'
from django.contrib.auth.models import User
u = User.objects.last()
if u:
u.is_active = True
u.is_staff = True
u.is_admin = True
u.save()
print(f"User {u.username} has been made an admin.")
else:
print("No user found to make admin.")
EOF

echo "[INFO] Development environment setup complete!"
echo "PostgreSQL and Valkey are running in Docker containers."
echo "You can now run 'make run' to start the development server."
echo "[INFO] Log saved to dev_setup.log"
Loading