Case study · Client portal · full-stack
ForgeDesk
Client portal for freelancers and studios: projects, tasks, versioned deliverables and client approvals.
Two roles in one app: clients see only the projects they're invited to.
- Type
- Personal open-source project
- Automated tests
- 43 (pytest)
- Built with
- Python · FastAPI · SQLAlchemy
- Live demo
- Runs locally — not hosted

Overview
What it does
ForgeDesk gives each client one calm place to follow a project — progress, deliverables to approve, shared files and short updates — while the studio runs tasks, reviews and deadlines for every client from one dashboard. It deliberately covers one use case, studio ↔ client collaboration: no workflow engine, custom fields or Gantt chart.
Problem
Freelancers juggle email threads, file links and “which version did you approve?”. A portal only helps if clients can't see each other's data and approvals are recorded against a specific version.
Solution
Two roles with one access layer: every project query goes through accessible_projects(), so a client user only ever sees their own client's projects. Deliverables are versioned; a new version returns to review, and approvals or change requests are tied to it.
Screenshots
From the running application
Captured from the app running locally with its seeded demo data. Select an image to open it full size.
Architecture
How the pieces fit
- Studio app → Next.js
- Client portal → Next.js
- Next.js → Auth
- Auth → Access layer
- Access layer → Routes & services
- Routes & services → Database
- Routes & services → File storage
- Core service
- External system
- Storage / entry point
Core features
What's in it
Studio dashboard
Active projects, pending reviews, overdue tasks, deadlines in the next three weeks and recent activity from live queries.
Versioned deliverables
Uploads with version history; a new version goes back to the client, older versions stay downloadable.
Client approvals
Approve or request changes with a written note the studio sees.
Single-use invitations
Client users exist only through invitations stored as SHA-256 digests; access can be removed anytime.
Files & updates
Shared files both ways through a four-method storage protocol, and progress notes on the client's timeline.
Every state designed
Light and dark themes, mobile layouts, and loading, empty and error states on every page.
Technical decisions
Interesting problems, and how they're solved
- 01
One access layer
Authorization is a query, not an afterthought: resources outside the user's scope return 404, never a 403 that confirms they exist.
- 02
Storage behind a protocol
Local disk today; the four-method Storage protocol is the seam for S3-compatible storage.
- 03
Strict inputs
Pydantic models reject unknown fields and trim strings; timestamps are UTC and always returned with an offset.
def accessible_projects(user: User) -> Select[tuple[Project]]:
query = select(Project)
if user.role == Role.OWNER:
return query.where(Project.owner_id == user.id)
if user.client_id is None:
return query.where(false()) # a client user without a client sees nothing
return query.where(Project.client_id == user.client_id)
def load_project(db: Session, user: User, project_id: int) -> Project:
project = db.execute(accessible_projects(user).where(Project.id == project_id)).scalar_one_or_none()
if project is None:
raise not_found("Project")
return projectStack & skills
Built with
Technology
- Python
- FastAPI
- SQLAlchemy
- Next.js 16
- React 19
- TypeScript
- Tailwind CSS
- shadcn/ui
- Motion
What this project demonstrates
- Full-stack product
- Role-based access
- File handling
- Product UX
- Next.js 16 + FastAPI
Repository & demo
Run it yourself
The seed creates a studio owner and client users for three fictional companies — logins are listed in the README.
No public live demo is hosted. Everything below runs locally with Python 3.12+ and Node 20+.
$ git clone https://github.com/Moeijiro/forgedesk.git
$ cd forgedesk
$ make install
$ make seed
$ make api
$ make web
Have a project in mind?
I build APIs, automation systems, Discord integrations and full-stack applications. Tell me what it should do and I'll tell you how I'd build it.





