Skip to content
Closed
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
101 changes: 101 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# ============================================================================
# Routis Environment Configuration
# ============================================================================
# Copy this file to .env and fill in your values.
# For local development, the defaults below are safe.
# For production, use strong, unique values and never commit the actual .env file.

# ============================================================================
# Application Environment
# ============================================================================

# deployment environment: development, staging, production
APP_ENV=development

# ============================================================================
# Database (PostgreSQL)
# ============================================================================

# PostgreSQL connection string
# Format: postgresql://username:password@host:port/database
# Local dev: use "postgres" hostname (Docker service name)
# Production: use managed RDS endpoint or external host
DATABASE_URL=postgresql://postgres:postgres@postgres:5432/routis

# PostgreSQL credentials (used by docker-compose for initialization)
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_DB=routis

# ============================================================================
# Cache & Rate Limiting (Redis)
# ============================================================================

# Redis connection URL
# Format: redis://[password@]host:port/[db]
# Local dev: use "redis" hostname (Docker service name)
# Production: use ElastiCache endpoint or external Redis host
REDIS_URL=redis://redis:6379/0

# ============================================================================
# Security & Anonymity
# ============================================================================

# Secret salt for email verification hashing
# Used in: SHA256(email + implementation_id + SECRET_SALT)
# Must be a long, random, secret string
# Generate: openssl rand -hex 32
SECRET_SALT=your-secret-salt-change-this-in-production-never-commit-the-real-value

# ============================================================================
# AWS S3 (Database Backups)
# ============================================================================

# S3 bucket name for database backups
# Bucket should have versioning and object lock enabled
# See: docs/runbooks/restore-db.md
S3_BACKUP_BUCKET=routis-db-backups-prod

# AWS access credentials (for db-backup sidecar container)
# DO NOT commit these to version control
# Use IAM roles in production, or AWS Secrets Manager
AWS_ACCESS_KEY_ID=your-aws-access-key-id
AWS_SECRET_ACCESS_KEY=your-aws-secret-access-key

# AWS region for S3 operations
AWS_REGION=eu-west-1

# Backup retention policy
# Daily backups retained for N days; weekly backups for N weeks
S3_BACKUP_RETENTION_DAYS=30
S3_BACKUP_RETENTION_WEEKS=26

# ============================================================================
# Frontend (Next.js)
# ============================================================================

# Public API URL (exposed to browser)
# Local dev: http://localhost:8000
# Production: https://api.yourdomain.com
NEXT_PUBLIC_API_URL=http://localhost:8000

# ============================================================================
# Email Verification (Future)
# ============================================================================

# Email provider credentials (stub for now)
# SMTP_HOST=smtp.example.com
# SMTP_PORT=587
# SMTP_USER=noreply@routis.local
# SMTP_PASSWORD=your-email-password

# Email domain whitelist (comma-separated)
# Only @student.oulu.fi addresses can verify reviews
ALLOWED_EMAIL_DOMAINS=student.oulu.fi

# ============================================================================
# Feature Flags (Future)
# ============================================================================

# ENABLE_EMAIL_VERIFICATION=false
# ENABLE_LEADERBOARD=true
17 changes: 17 additions & 0 deletions .github/ISSUE_TEMPLATE/bug.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
name: Bug
about: Something is broken
labels: type/bug
---

**What happened**
<!-- What did you do, what did you expect, what actually occurred? -->

**To reproduce**
<!-- Minimal steps. -->

**Environment**
<!-- Local / staging / prod. Browser or API. Relevant versions. -->

**Notes**
<!-- Logs, screenshots, or leave blank. -->
17 changes: 17 additions & 0 deletions .github/ISSUE_TEMPLATE/feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
name: Feature
about: A new capability or improvement
labels: type/feature
---

**What and why**
<!-- One or two sentences. What does this do and why does it matter? -->

**Milestone**
<!-- M0 / M1 / M2 / M3 / M4 / M5 -->

**Done when**
<!-- The specific, verifiable condition that closes this issue. -->

**Notes**
<!-- Implementation hints, constraints, links to ADRs, or leave blank. -->
12 changes: 12 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Closes #<!-- issue number -->

**What changed**
<!-- One paragraph. Why this approach? -->

**Checklist**
- [ ] Linked issue above
- [ ] `docker compose up` works locally
- [ ] Tests added or existing tests cover this (or N/A)
- [ ] Migration included if schema changed
- [ ] `schema.dbml` updated if schema changed
- [ ] Codegen re-run if API routes or schemas changed
72 changes: 72 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: CI

on:
pull_request:
branches: [main, develop]
push:
branches: [develop]

jobs:
web:
name: Web
runs-on: ubuntu-latest
defaults:
run:
working-directory: apps/web

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: apps/web/package-lock.json

- run: npm ci

- run: npm run lint

- run: npm run build

api:
name: API
runs-on: ubuntu-latest
defaults:
run:
working-directory: apps/api

services:
postgres:
image: postgres:18-alpine
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: routis_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432

steps:
- uses: actions/checkout@v4

- uses: astral-sh/setup-uv@v5
with:
enable-cache: true
cache-dependency-glob: apps/api/uv.lock

- run: uv sync --frozen

- run: uv run ruff check .

- run: uv run ruff format --check .

- name: Pytest
run: uv run pytest
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/routis_test
REDIS_URL: redis://localhost:6379
72 changes: 72 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Routis

Anonymous course review platform for the University of Oulu. Students can submit reviews of courses without creating an account, with optional email-based verification for added credibility.

## Tech Stack

- **Frontend:** Next.js 16, React 19, Tailwind CSS 4, TypeScript
- **Backend:** FastAPI, SQLAlchemy, Pydantic v2, Python 3.13
- **Database:** PostgreSQL 18, Alembic (migrations)
- **Cache:** Redis 7
- **Infrastructure:** Docker Compose

## Getting Started

```bash
cp .env.example .env
docker compose up
```

- Web: http://localhost:3000
- API: http://localhost:8000
- API docs: http://localhost:8000/docs

## Project Structure

```
apps/
web/ # Next.js frontend
api/ # FastAPI backend
packages/
shared/ # Shared schemas & generated types
docs/ # Architecture docs, ADRs, runbooks
infra/ # Deployment config
```

## Development

**Frontend:**

```bash
cd apps/web
npm ci
npm run dev
```

**Backend:**

```bash
cd apps/api
uv sync
uv run fastapi dev main.py
```

**Lint & Test:**

```bash
# Frontend
cd apps/web && npm run lint

# Backend
cd apps/api && ruff check . && ruff format --check . && uv run pytest
```

## Documentation

- [Architecture](docs/ARCHITECTURE.md)
- [Architecture Decision Records](docs/decisions/)
- [Runbooks](docs/runbooks/)

## License

[AGPL-3.0](COPYING)
1 change: 1 addition & 0 deletions apps/api/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.13
26 changes: 26 additions & 0 deletions apps/api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
FROM python:3.13-slim

WORKDIR /app

# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
postgresql-client \
&& rm -rf /var/lib/apt/lists/*

# Install uv
RUN pip install --no-cache-dir uv

# Copy dependency files
COPY pyproject.toml uv.lock ./

# Install dependencies
RUN uv sync --frozen --no-dev

# Copy application code
COPY . .

EXPOSE 8000

# Use uv run to execute through the virtual environment
CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Empty file added apps/api/README.md
Empty file.
13 changes: 13 additions & 0 deletions apps/api/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import uvicorn
from fastapi import FastAPI

app = FastAPI()


@app.get("/health")
def health():
return {"status": "ok", "version": "0.1.0"}


if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
28 changes: 28 additions & 0 deletions apps/api/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[project]
name = "api"
version = "0.1.0"
description = "FastAPI-based backend service providing the application's HTTP API."
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"alembic>=1.18.4",
"fastapi>=0.135.2",
"psycopg2-binary>=2.9.11",
"pydantic>=2.12.5",
"redis>=7.4.0",
"sqlalchemy>=2.0.48",
"uvicorn[standard]>=0.42.0",
]
[tool.ruff]
line-length = 88

[tool.ruff.lint]
select = ["E", "F", "I"] # pycodestyle, pyflakes, isort

[dependency-groups]
dev = [
"httpx>=0.28.1",
"pytest>=9.0.2",
"pytest-asyncio>=1.3.0",
"ruff>=0.15.8",
]
Empty file added apps/api/tests/__init__.py
Empty file.
2 changes: 2 additions & 0 deletions apps/api/tests/test_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def test_placeholder():
pass

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a placeholder test that will always pass and doesn’t validate any behavior. Since CI runs pytest, it would be better to add at least one minimal assertion (e.g., calling GET /health via TestClient and asserting the JSON/status) or remove the test until real coverage is added.

Suggested change
pass
assert True

Copilot uses AI. Check for mistakes.
Loading
Loading