CapCall is a Django REST Framework backend that simulates a Private Equity fund management system, which includes handling capital subscriptions between Limited Partners (LPs) and Fund Managers. A minimal Vue.js frontend is included for demonstration purposes.
| Component | Technology |
|---|---|
| Frontend | Vue 3 + Vite |
| Backend | Django + Django REST Framework |
| Database | PostgreSQL 16 |
| Background Jobs | Celery + Redis |
| Security | SimpleJWT + CORS/CSRF Middleware |
| Infrastructure | Docker + Docker Compose |
The application centers around three core models: Fund, Investor and Subscription.
- Investors must pass KYC (Know Your Customer) compliance.
- Funds follow a lifecycle (
FUNDRAISING→INVESTING→HARVEST→LIQUIDATED) and employ specific strategies (e.g.,BUYOUT,VENTURE). - Subscriptions represent the commitment of capital and follow a state machine:
DRAFT→SUBMITTED→UNDER_REVIEW→APPROVED→FUNDED(orREJECTED).
flowchart TD
Client(["Vue 3 SPA\nInvestor Portal"])
subgraph Security["Security & Auth Layer"]
JWT["SimpleJWT\nAccess (15m) & Refresh (7d)"]
Permissions["Role-based Permissions\nIsFundManager / IsOwnerOrFundManager"]
QuerySet["Dynamic QuerySets\nData Isolation per Role"]
end
subgraph Controllers["DRF ViewSets & Services"]
AuthView["Auth Token Endpoints\n/api/v1/auth/"]
FundView["FundViewSet\nRead-only for LPs"]
SubView["SubscriptionViewSet\nperform_create & custom actions"]
ServiceLayer["SubscriptionService\nBusiness logic & State machine"]
end
subgraph AsyncLayer["Background Processing"]
RedisBroker[("Redis Broker")]
CeleryWorker["Celery Worker\n(Email generation)"]
end
subgraph DatabaseLayer["PostgreSQL"]
DB[(CapCall DB)]
end
Client -- "HTTP + Bearer JWT" --> JWT
JWT --> Permissions
Permissions --> QuerySet
QuerySet --> FundView
QuerySet --> SubView
FundView ==> DB
SubView --> ServiceLayer
ServiceLayer ==> DB
ServiceLayer -- ".delay()" --> RedisBroker
RedisBroker --> CeleryWorker
- Investors (
is_staff=False) can only query and view their own subscriptions. Fund Managers (is_staff=True) have global visibility. - To prevent users from subscribing others, the API ignores the investor ID in POST payloads (
required=False). Instead, theperform_createmethod intercepts the request and securely auto-assigns the investor based on the validated JWT token. - Object-level permissions (
IsOwnerOrFundManager) ensure that even if querysets are bypassed, users cannot mutate data they do not own. - Authentication endpoints are rate-limited to prevent brute-force attacks.
- The Service Layer prevents illegal state transitions (e.g., skipping
UNDER_REVIEWto go straight toAPPROVED). - Institutional documents (like Capital Call notices) are served via an authenticated endpoint (
/notice/) that validates the JWT andget_querysetpermissions. - JWT tokens are stored in
sessionStorageand cleared automatically when the tab is closed.
sequenceDiagram
participant LP as Investor (LP)
participant API as CapCall API
participant Mgr as Fund Manager
participant DB as PostgreSQL
Note over LP,DB: 1. Expression of interest
LP->>API: POST /subscriptions (Amount, Fund ID)
API->>API: perform_create (extracts LP from JWT)
API->>DB: Save as DRAFT
API-->>LP: 201 Created
LP->>API: PATCH /subscriptions/{id} (status: SUBMITTED)
API->>DB: Update status
Note over Mgr,DB: 2. KYC & Compliance review
Mgr->>API: PATCH /subscriptions/{id} (status: UNDER_REVIEW)
API->>DB: Update status
alt KYC Fails
Mgr->>API: PATCH /subscriptions/{id} (status: REJECTED)
else KYC Passes
Mgr->>API: POST /subscriptions/{id}/approve/
API->>DB: Update status to APPROVED
API-)Celery: Trigger async email task
Celery-->>LP: Send HTML Capital Call Notice
Note over LP,DB: 3. Capital call & Funding
LP->>API: GET /subscriptions/{id}/notice/ (View secure document)
LP->>API: (Transfers real money offline)
Mgr->>API: PATCH /subscriptions/{id} (status: FUNDED)
API->>DB: Update status & update Fund called_capital
end
The complete API documentation is automatically generated using OpenAPI. Once the backend is running, you can explore all endpoints using your preferred UI:
- Swagger UI (for interactive testing): http://localhost:8000/api/v1/schema/swagger-ui/
- Redoc (for clean reading): http://localhost:8000/api/v1/schema/redoc/
The API implements two additional features for usability:
- Pagination: All list endpoints use
PageNumberPagination. - Filtering: Key endpoints support query parameter filtering via
django-filter(e.g.?status=SUBMITTED&fund=2). - Async Processing: Capital Call emails are sent asynchronously via Celery and Redis.
- Service Layer: Business logic and state machine validations are decoupled from ViewSets into service classes, which simplifies unit testing.
For a quick overview, these are the main endpoints that drive the application's core logic:
| Resource | Endpoints | Description |
|---|---|---|
| Auth | POST /api/v1/auth/token/* |
JWT authentication (login, refresh, verify, blacklist). |
| Funds | GET, POST, PATCH /api/v1/funds/* |
Fund catalog. Read-only for LPs & full CRUD for Managers. |
| Investors | GET, PATCH /api/v1/investors/* |
Investor KYC profiles. Isolated visibility per user. |
| Subscriptions | GET, POST, PATCH /subscriptions/* POST .../{id}/approve/ GET .../{id}/notice/ |
The core lifecycle. Investors create DRAFTs, Managers approve them (triggering async emails), and Investors fetch secure notices. |
Docker and Docker Compose installed.
- Clone the repository:
git clone https://github.com/guillemdiaz/capcall.git
cd capcall- Configure environment variables:
Create a .env file in the backend directory to store your Django SECRET_KEY and DB credentials (A sample .env.example is provided.)
- Start the infrastructure:
This command will build the images, apply migrations and also start PostgreSQL, Django, and Vite in detached mode.
docker-compose up -d --build- Access the application:
- Frontend: http://localhost:5173
- Backend API: http://localhost:8000/api/v1/
- Django Admin: http://localhost:8000/admin/
The project includes a custom command to automatically seed the database with realistic Funds, Investors and Subscriptions.
Run the following command to populate the database:
docker-compose exec web python manage.py seed_dbThis will generate the following test accounts:
| Role | Username | Password |
|---|---|---|
| Fund Manager | manager |
test12341234 |
| Investor A | investor1 |
test12341234 |
| Investor B | investor2 |
test12341234 |
The backend includes multiple tests that cover model validation and role-based authorization.
docker-compose exec web python manage.py test