Collaborative multi-tenant sprint and issue tracker built with Django REST Framework, React, PostgreSQL, JWT authentication, and optimistic concurrency control.
Repository: https://github.com/Shashank-VK/projectflow.git
ProjectFlow is a work tracking application engineered for agile software teams. Rather than building a superficial clone, ProjectFlow emphasizes architectural depth, strict tenant isolation, fractional Kanban ordering, optimistic concurrency recovery (HTTP 409), and a standardized three-role authorization policy (Owner, Member, Viewer).
Per the system design specification and review addendum:
- No Aspirational Infrastructure: Premature Celery and Redis dependencies have been eliminated in favor of a bounded in-process
TaskRunnerbacked by database audit logs. - Strict Role-Based Access Control: Viewers have enforced read-only access across all API endpoints (proven by an automated test matrix).
- Atomic Concurrency & Recovery: Card moves utilize fractional
position_keyvalues and optimisticversioncounters. Conflicting simultaneous moves yield an HTTP 409 Conflict with the server's current representation, allowing instant client rollback and retry. - Demo Safety: Reseedable, idempotent demo recovery via
POST /api/demo/reset/(active only whenDEMO_MODE=True).
ProjectFlow is designed as a modular monolith. Django and Django REST Framework enforce domain boundaries and authorization invariants. PostgreSQL serves as the relational system of record, and a responsive React single-page application interacts via versioned REST APIs.
flowchart TD
Client["React + TypeScript Client (Vite, Tailwind, TanStack Query)"]
API["Django REST Framework API (/api/v1/)"]
Auth["JWT Authentication & Permissions Layer"]
Modules["Domain Modules\n- Accounts\n- Workspaces\n- Projects\n- Issues & Comments\n- Activity Audit\n- TaskRunner"]
DB[("PostgreSQL Database")]
Worker["Bounded TaskRunner (ThreadPoolExecutor)"]
Client -->|"HTTP / REST (JWT Bearer)"| API
API --> Auth
Auth --> Modules
Modules -->|"Transactions & Constraints"| DB
Modules -->|"Async Tasks (Emails, Reminders)"| Worker
Worker -->|"Execution Status Logs"| DB
apps.accounts: Custom user model (email-based identifier), JWT issue/refresh endpoints, profile and credential management.apps.workspaces: Multi-tenant boundary, invitations with single-use atomic acceptance, role policies, and workspace metrics.apps.projects: Projects scoped to workspaces, unique alphanumeric keys (e.g.,PF), soft archiving with write-lock invariants.apps.issues: Issues and comments, sequential key generation (PF-101), fractional LexoRank-style ordering, optimistic version locking, soft deletion.apps.activity: Append-only audit log tracking actors, verbs, entity types, and before/after metadata.apps.core: Custom exception handler (standard JSON error format),TaskRunnerwithBackgroundTaskdatabase logging, health checks, and demo reset.
erDiagram
User ||--o{ Membership : has
User ||--o{ Issue : reports
User ||--o{ Issue : assigned
User ||--o{ Comment : writes
Workspace ||--o{ Membership : contains
Workspace ||--o{ Invitation : issues
Workspace ||--o{ Project : owns
Workspace ||--o{ ActivityEvent : audits
Project ||--o{ Issue : tracks
Issue ||--o{ Comment : receives
Workspace {
uuid id PK
string name
string slug UK
datetime created_at
}
Membership {
uuid id PK
uuid workspace_id FK
uuid user_id FK
string role "owner | member | viewer"
datetime joined_at
}
Invitation {
uuid id PK
uuid workspace_id FK
string email
string role
string token UK
string status "pending | accepted | revoked | expired"
datetime expires_at
}
Project {
uuid id PK
uuid workspace_id FK
string key "e.g. PF"
string name
boolean is_archived
}
Issue {
uuid id PK
uuid project_id FK
integer sequence
string key "e.g. PF-101"
string title
string status "todo | in_progress | in_review | done"
string priority "low | medium | high | urgent"
string position_key "fractional float string"
integer version "optimistic lock counter"
}
- Assignee Boundary: An assignee must be an active member of the issue's workspace.
- Archived Project Mutation Lock: Archived projects reject issue creations, modifications, and deletions.
- Owner Protection: The final workspace owner cannot be demoted or removed.
- Invitation Binding: Invitations are bound to normalized emails. Accepting while logged in as a mismatched email returns an explanatory recovery notice.
- Atomic & Idempotent Acceptance: Concurrent acceptance requests result in exactly one membership. Subsequent requests return the existing membership.
- Optimistic Locking: Mutations include client
version. If the database version has advanced, an HTTP 409 Conflict is returned along with the current server record.
Permissions are enforced at the API layer, not solely by hidden buttons in the user interface:
| Action | Owner | Member | Viewer |
|---|---|---|---|
| Workspace Settings & Name | ✅ Yes | ❌ No (403) | ❌ No (403) |
| Invite Members & Revoke/Resend | ✅ Yes | ❌ No (403) | ❌ No (403) |
| Manage Member Roles / Remove Members | ✅ Yes | ❌ No (403) | ❌ No (403) |
| Create Project | ✅ Yes | ✅ Yes | ❌ No (403) |
| Archive / Restore / Delete Project | ✅ Yes | ❌ No (403) | ❌ No (403) |
| Create Issue | ✅ Yes | ✅ Yes | ❌ No (403) |
| Move Issue / Update Issue Fields | ✅ Yes | ✅ Yes | ❌ No (403) |
| Delete Issue | ✅ Yes | ✅ If Reporter | ❌ No (403) |
| Create Comment | ✅ Yes | ✅ Yes | ❌ No (403) |
| View Board, Issues, Dashboard, Activity | ✅ Yes | ✅ Yes | ✅ Read-only |
- Python 3.12+
- Node.js 20+ & npm
- PostgreSQL 16+ running locally
sudo -u postgres psql -c "CREATE USER projectflow WITH PASSWORD 'projectflow' CREATEDB;"
sudo -u postgres psql -c "CREATE DATABASE projectflow OWNER projectflow;"
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE projectflow TO projectflow;"# Create virtual environment and install dependencies
python3 -m venv .venv
source .venv/bin/activate
pip install -r backend/requirements.txt
# Run migrations and seed deterministic demo data
python backend/manage.py migrate
python backend/manage.py seed_demo_data
# Start backend application server
python backend/manage.py runserver 0.0.0.0:8000- API root:
http://localhost:8000/api/v1/ - Health check:
http://localhost:8000/api/health/ - Interactive OpenAPI / Swagger UI:
http://localhost:8000/api/docs/
No PostgreSQL? Use SQLite. Copy
.env.exampleto.envand setDATABASE_URL=sqlite:///db.sqlite3(no Postgres server needed). SQLite connections run in WAL mode with a 5s busy timeout for serialized writes (seeapps/core/apps.py), and everything — migrations, demo seeding, tests — works identically on the SQLite path.
cd frontend
npm install
npm run build # Verify TypeScript & Vite bundle
npm run preview # Or npm run dev- Web Application:
http://localhost:5173/
| Role | Password | Intended Test | |
|---|---|---|---|
| Owner | owner@projectflow.dev |
password123 |
Full admin, invitations, role changes, project archiving. |
| Member | member@projectflow.dev |
password123 |
Create issues, drag cards, comment, cannot invite/archive. |
| Viewer | viewer@projectflow.dev |
password123 |
Read-only inspection; all mutations rejected with 403. |
- Sign In: Navigate to
http://localhost:5173/login. Click the Owner (Sarah) quick persona button. - Kanban Board: Open the board for Core Platform. Drag card PF-102 from In Progress to Done, or use the card's accessible status buttons.
- Issue Drawer & Concurrency: Click PF-103 to open the routable drawer. Add a comment. Notice the URL is shareable.
- Role Verification: Use the top navigation bar to switch personas to Viewer (Taylor). Notice mutation buttons are hidden/disabled, and direct API mutations are blocked with 403.
- Dashboard & Metrics: Open the Dashboard tab to view real-time counts by status, priority, team member workloads, and overdue issue indicators.
- Demo Recovery: Click the Reset Demo button in the header. Confirm the modal to re-seed initial data idempotently.
Run the automated test suite with pytest:
.venv/bin/pytest backend/tests/ -vMeasured Test Results:
======================== 33 passed, 1 warning in 45.31s ========================
- test_auth.py: 5/5 PASSED (Registration, JWT token obtain, token refresh, me, change-password)
- test_roles_and_permissions.py: 8/8 PASSED (Full Owner, Member, Viewer matrix across all mutating endpoints)
- test_tenant_isolation.py: 6/6 PASSED (Negative checks across foreign workspaces, projects, issues, dashboard)
- test_invitation_lifecycle.py: 5/5 PASSED (Acceptance, email mismatch rejection, expiry, revocation, concurrency)
- test_issues_concurrency.py: 3/3 PASSED (HTTP 409 conflict detection, current payload return, fractional order, archived lock)
- test_task_runner.py: 2/2 PASSED (In-process execution and BackgroundTask database audit logging)
- test_demo_reset.py: 3/3 PASSED (Auth guard, DEMO_MODE toggle, fixture reseeding)
- test_dashboard.py: 1/1 PASSED (Accurate status/priority/assignee counts, archived exclusions)
.venv/bin/python backend/manage.py spectacular --validate --fail-on-warn
# Result: Validation successful. 0 warnings, 0 errors.cd frontend && npm run build
# Result: 1,982 modules transformed. Built in 2.63s without errors.- Modular Monolith vs. Microservices:
- Chosen: A single Django backend with decoupled domain apps.
- Rationale: Avoids distributed transactions, network latency, and deployment complexity while preserving clean module boundaries and transactional integrity.
- In-Process TaskRunner vs. Celery/Redis:
- Chosen: A bounded background-thread executor logging task states directly into
BackgroundTask. - Rationale: Eliminates external Redis and worker process failure modes in MVP deployments while maintaining asynchronous email delivery and audit tracking.
- Chosen: A bounded background-thread executor logging task states directly into
- Fractional Ordering vs. Sequential Renumbering:
- Chosen: LexoRank-style floating position keys (e.g.
10000.000000,15000.000000). - Rationale: Card reordering inserts between neighboring keys without locking or updating hundreds of rows in the same column.
- Chosen: LexoRank-style floating position keys (e.g.
- Optimistic Locking (409 Conflict):
- Chosen: Every mutation checks the record's current
version. - Rationale: Prevents silent data overwrite when two teammates move or edit the same card simultaneously.
- Chosen: Every mutation checks the record's current
ProjectFlow provides a production-grade multi-container Docker configuration wiring the Django REST API, React frontend served by Nginx, and PostgreSQL database.
- Backend: Python 3.12-slim multi-stage image running Gunicorn WSGI server. Includes automated database connection wait loop and migration execution.
- Frontend: Node 20 alpine build stage compiling static assets, served by Nginx alpine with gzip compression, security headers, SPA client-side routing fallback, and reverse-proxying of
/api/and/static/endpoints. - Database: PostgreSQL 16 alpine with persistent volume storage (
postgres_data) and integrated healthcheck (pg_isready).
- Docker Engine 24.0+
- Docker Compose v2.20+
Copy .env.example to .env:
cp .env.example .envThe default values in .env.example are preconfigured for local Docker Compose runs with PostgreSQL.
Build the multi-stage images for backend and frontend:
docker compose buildStart all services in detached mode:
docker compose up -dDocker Compose will launch:
projectflow-db(PostgreSQL 16) and wait for the database health check to pass.projectflow-backend(Django + Gunicorn), execute database migrations, and listen on port 8000.projectflow-frontend(Nginx + React SPA), listening on port 80.
To populate the database with pre-seeded demo personas (owner@projectflow.dev, etc.) and sample workspace:
docker compose exec backend python manage.py seed_demo_data| Service | Container | Host URL | Description |
|---|---|---|---|
| Frontend Web App | projectflow-frontend |
http://localhost:80 | React SPA served via Nginx with API reverse proxy |
| Backend REST API | projectflow-backend |
http://localhost:8000/api/v1/ | Django REST Framework API root |
| API Health Check | projectflow-backend |
http://localhost:8000/api/health/ | JSON health status check |
| OpenAPI / Swagger | projectflow-backend |
http://localhost:8000/api/docs/ | Interactive Swagger UI documentation |
| PostgreSQL Database | projectflow-db |
localhost:5432 |
PostgreSQL relational database |
# View combined live logs
docker compose logs -f
# View backend logs only
docker compose logs -f backend
# View frontend logs only
docker compose logs -f frontendRun the complete 33-test suite inside the containerized environment against the PostgreSQL service:
# Run pytest inside the backend container
docker compose run --rm backend pytest
# Run with verbose output and test summary
docker compose run --rm backend pytest -v
# Run with in-memory SQLite override if testing without Postgres container
docker compose run --rm -e DATABASE_URL=sqlite:///:memory: backend pytest# Stop and remove containers and network (preserves database volume)
docker compose down
# Stop and wipe database volume completely (clean reset)
docker compose down -vNotice: The instructions below provide a complete, production-grade architectural guide for deploying ProjectFlow to Amazon Web Services (AWS). This guide is documentation-only; no AWS cloud resources are provisioned directly in this repository.
flowchart TD
subgraph Client["Users & Clients"]
Browser["Web Browser"]
end
subgraph Edge["AWS Edge Layer"]
Route53["Route 53 (DNS)"]
ACM["AWS Certificate Manager (SSL/TLS)"]
CF["Amazon CloudFront CDN"]
end
subgraph Static["Static Asset Hosting"]
S3["Amazon S3 Bucket (Frontend React Build)\n[Block Public Access Enabled]"]
end
subgraph VPC["AWS Virtual Private Cloud (VPC)"]
subgraph PublicSubnets["Public Subnets"]
ALB["Application Load Balancer (ALB)"]
end
subgraph PrivateSubnets["Private Application Subnets"]
EC2["Amazon EC2 (Ubuntu 24.04 LTS)\nDocker + Gunicorn WSGI\nAuto-Restart via systemd"]
end
subgraph DBSubnets["Private Database Subnets"]
RDS[("Amazon RDS PostgreSQL 16 (Multi-AZ)\nAutomated Snapshots & Encryption")]
end
end
Browser --> Route53
Route53 --> CF
CF -->|"Static Files (/*)"| S3
CF -->|"/api/* and /static/*"| ALB
ALB -->|"Port 8000"| EC2
EC2 -->|"Port 5432"| RDS
There are two primary AWS deployment patterns for ProjectFlow:
- Option 1: Cost-Effective Single EC2 with Docker Compose (Recommended for MVPs, staging, and demo deployments).
- Option 2: Production Decoupled S3 + CloudFront + EC2/ECS + RDS (Recommended for high-scale, multi-tenant production).
- In the AWS Management Console, navigate to EC2 > Launch Instance.
- Name:
projectflow-production. - AMI: Ubuntu Server 24.04 LTS (x86_64).
- Instance Type:
t3.small(2 vCPU, 2 GiB RAM) ort3.medium. - Key Pair: Select or generate an RSA key pair (
projectflow-key.pem). - Network Settings:
- Create Security Group
projectflow-ec2-sg. - Inbound Rules:
- HTTP (Port 80) from
0.0.0.0/0 - HTTPS (Port 443) from
0.0.0.0/0 - SSH (Port 22) from your IP only
- HTTP (Port 80) from
- Create Security Group
- Storage: 30 GiB gp3 EBS volume.
- Click Launch Instance.
Connect to the EC2 instance via SSH:
ssh -i projectflow-key.pem ubuntu@<EC2_PUBLIC_IP>Run host provisioning commands:
sudo apt-get update && sudo apt-get upgrade -y
sudo apt-get install -y ca-certificates curl gnupg lsb-release
# Install Docker Engine and Compose Plugin
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
"$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker ubuntuLog out and back in to apply group permissions, then:
git clone https://github.com/Shashank-VK/projectflow.git
cd projectflow/app
# Generate production environment file
cp .env.example .envEdit .env for production:
nano .envKey production values:
DEBUG=False
SECRET_KEY=<generate-strong-random-64-char-string>
ALLOWED_HOSTS=<EC2_PUBLIC_DNS>,<YOUR_DOMAIN>,localhost
DEMO_MODE=False
POSTGRES_DB=projectflow
POSTGRES_USER=projectflow_admin
POSTGRES_PASSWORD=<strong-database-password>
DATABASE_URL=postgresql://projectflow_admin:<strong-database-password>@db:5432/projectflow
FRONTEND_PORT=80
BACKEND_PORT=8000docker compose build
docker compose up -d
# Verify containers are healthy
docker compose ps
# Seed demo data if testing
docker compose exec backend python manage.py seed_demo_dataInstall Certbot on the host to obtain a free Let's Encrypt certificate:
sudo apt-get install -y certbot
sudo certbot certonly --standalone -d yourdomain.com -d www.yourdomain.comUpdate frontend/nginx.conf with SSL configuration:
server {
listen 80;
server_name yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
# ... proxy configuration matching frontend/nginx.conf
}For scalable production deployments, decouple static frontend hosting and database management from application compute:
- Create an Amazon RDS PostgreSQL 16 instance in private subnets.
- Select Multi-AZ deployment for production high availability.
- Security Group: Allow inbound port 5432 only from the EC2 backend security group (
projectflow-ec2-sg). - Set
DATABASE_URL=postgresql://<user>:<password>@<rds-endpoint>:5432/<dbname>in EC2 backend.env.
- Create an S3 bucket:
projectflow-frontend-assets. - Keep Block all public access enabled.
- Build the frontend locally or in CI/CD:
cd frontend npm ci VITE_API_URL=https://api.yourdomain.com npm run build - Sync build artifacts to S3:
aws s3 sync dist/ s3://projectflow-frontend-assets/ --delete
- Create a CloudFront Distribution:
- Default Origin: S3 bucket
projectflow-frontend-assetsusing Origin Access Control (OAC). - Secondary Origin: Application Load Balancer (ALB) pointing to EC2 backend on port 8000.
- Default Origin: S3 bucket
- Behaviors:
- Path pattern
/api/*: Route to ALB origin (Allowed Methods: GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE; Cache Policy: CachingDisabled; Origin Request Policy: AllViewer). - Path pattern
/static/*: Route to ALB origin (Django admin/swagger assets). - Default
/*: Route to S3 origin with SPA error response:- Error Code
403&404-> Custom Response Code200with Response Page Path/index.html.
- Error Code
- Path pattern
- Attach an AWS Certificate Manager (ACM) SSL certificate in
us-east-1for your custom domain. - Point Route 53 alias record (
A/AAAA) to the CloudFront distribution domain name.
- Store production secrets (
SECRET_KEY, RDS credentials) in AWS Secrets Manager. - Automate builds with GitHub Actions:
- On push to
main, run automated test suite (pytest). - Build frontend and upload to S3; invalidate CloudFront cache (
aws cloudfront create-invalidation --distribution-id <ID> --paths "/*"). - Build and deploy backend container image to Amazon ECR and trigger rolling update on EC2 / ECS.
- On push to
MIT License. Designed and maintained by Shashank-VK.