A full-featured backend API for a legal services marketplace connecting clients with lawyers (and other legal professionals). Built with Django REST Framework, it handles everything from booking and escrew payments to real-time video consultations and subscription billing. The platform handles:
- Role-based user accounts (client vs. vendor)
- Multi-step vendor onboarding with 2FA
- Calendar supports for lawyer's availability and unavailability
- Appointment booking with Stripe payment processing and an escrow fund model
- Live audio/video consultations via Agora
- Lawyer subscription billing via Stripe
- Vendor wallet system with KYC verification
- Real-time in-app notifications via WebSockets
- Features
- Documentation
- Tech Stack
- Architecture
- Workflows
- Application Modules
- API Overview
- Setup & Installation
- Environment Variables
- Management Commands
- Email + password registration and login
- Email verification (link sent on signup)
- Two-factor authentication (SMS OTP via Twilio)
- Google OAuth2 social login
- JWT access tokens + HttpOnly refresh token cookie
- Token blacklisting on logout
- Login attempt throttling with account lockout
- Password reset via email link or SMS OTP
- API rate limiting (configurable per anonymous/authenticated user)
- Multi-step profile completion wizard (session-based, token-authenticated)
- Steps: category → business name → address → languages → avatar → legal info → additional info
- Cloudinary avatar upload (URL or file)
- Romanian bar association registry support
- Profile completion gated before marketplace access
- Lawyer categories and subcategories
- Pricing plans (offerings) with per-30-min rates - each plan has an independent price and can be attached to specific time slots
- Service delivery types per offering: Physical (free, in-office), Audio (paid, phone), Video (paid, Agora RTC)
- Full overlap validation on save, the API rejects any slot that would conflict with an existing one on the same day
The availability system is purpose-built to power a booking calendar on the frontend. Every read endpoint is accessible to any authenticated user so the client can render the lawyer's calendar before booking.
Lawyer sets up their schedule:
- Create weekly recurring slots: day of week + start/end time + pricing plan (e.g., Monday 09:00–17:00 on "Standard Plan")
- Bulk create: POST one set of times and apply it to multiple days at once (e.g., Mon–Fri 09:00–17:00 in one request)
- Copy template: copy all slots from one day to other days (e.g., clone Monday's schedule to Tuesday and Wednesday)
- Enable/disable individual slots without deleting them
- Clear all slots in one call, or clear only past unavailability entries
Lawyer blocks out time (unavailability):
- Block a specific date either all-day or as a time range (e.g., 14:00–16:00 on 2025-07-10)
- Typed reasons:
vacation,sick_leave,personal,court_appearance,meeting,training,other - Overlap detection prevents double-blocking the same time on the same date
- Filter by date range, type, or all-day flag
- Stats endpoint returns counts by type and the next upcoming block
Frontend reads availability to render a calendar:
The calendar API endpoint also automatically crosses out unavailability: any slot that overlaps with a block (all-day or time-range) on that date is removed from available_slots before the response is returned. The frontend never needs to do this filtering itself.
- Full lifecycle:
pending → confirmed → completed(or cancelled / no-show / rescheduled) - Stripe PaymentIntent created at booking; appointment cancelled automatically if payment doesn't arrive within 10 minutes (Celery task)
- Escrow model: funds held as
PENDINGtransaction until appointment completes - Fund release only after appointment ends and is explicitly marked complete
- Rescheduling by either party (preserves confirmed status)
- Cancellation with configurable refund eligibility window (threshold hours per lawyer)
- Automatic Stripe refund processing with webhook confirmation
- Agora RTC token generation for in-session participants
- Session state tracking (
not_started → active → ended) - Separate participant records for lawyer and client
- Per-lawyer wallet with running balance
- All fund movements tracked as typed transactions (payment, payout, refund, commission)
- Withdrawal requests require completed KYC verification
- Race-condition-safe balance deduction
- Admin approval workflow for payouts
- Category-based subscription plans synced to Stripe Products/Prices
- 15-day trial period
- Trial-to-paid transition handled via webhooks
- Voucher system (percentage discount, fixed amount) with usage limits and expiry
- Concurrent voucher redemption protected with row-level locking
- Prorated billing, cancellation and reactivation
- Stripe dispute/chargeback detection with fund freeze
- Stripe Identity sessions for lawyer identity verification
- Verified status gates wallet withdrawals
- Webhook-driven status updates (verified / requires_input / cancelled / processing)
- In-app notification model with type-based routing
- Real-time delivery via Django Channels WebSocket consumers
- Notification types: trial ending, payment failed, appointment reminders, refund approved, withdrawal approved, and more
- Email notifications via SendGrid (subscription events, password reset, email verification)
Additional guides, diagrams, and Postman collections are available in the repository beside this Readme.md
| File | Description |
|---|---|
| docs/guide/Auth.md | The auth system - JWT lifetimes, 2FA flow, rate limiting, password policy, CORS, and security middleware |
| docs/guide/Subscription.md | Step-by-step guide for testing the subscription API - trial setup, payment methods, vouchers, webhooks, and notifications |
| docs/guide/Google.md | Frontend integration guide for Google OAuth2 - how to wire up the login button and send the token to the backend |
| docs/guide/TRANSACTION.md | Appointment & transaction state machine - all status transitions for booking, escrow, completion, cancellation, and refund |
Full Mermaid diagrams (sequence, DFD, use case, ER) are in Diagrams.md
| Diagram | What it shows |
|---|---|
| Sequence Diagram | Full appointment lifecycle - registration → booking → payment → consultation → fund release |
| Data Flow Diagram | Data flows across all 10 system processes, 7 external services, and 6 data stores |
| Use Case Diagram | All actors (Client, Lawyer, Admin) and their platform capabilities |
| ER Diagram | Complete data model across all 8 Django apps (~20 entities and their relationships) |
Ready-to-import collections are in the postman/ folder. Import any file into Postman to get a pre-built request set for that area of the API.
| Collection | Covers |
|---|---|
| Authentication API | Register, login, 2FA, OTP, password reset, Google OAuth2 |
| Vendor Profile API | 9-step vendor onboarding, profile management |
| Lawyer API | Availability, appointments, wallet, withdrawals, KYC |
| Subscription Management API | Plans, payment methods, vouchers, invoices, notifications |
| Layer | Technology |
|---|---|
| Framework | Django 5.1 · Django REST Framework 3.15 |
| Language | Python 3.12 |
| Database | PostgreSQL 17 (production) · SQLite (development) |
| Cache / Sessions | Redis 7 |
| Async Tasks | Celery 5.5 + Redis broker |
| Real-time | Django Channels 4.2 · Daphne 4.1 (ASGI) |
| Payments | Stripe (appointments, subscriptions, refunds, disputes) |
| Video / Audio | Agora RTC |
| SMS / OTP | Twilio |
| SendGrid | |
| Media Storage | Cloudinary |
| Authentication | JWT (SimpleJWT) · Google OAuth2 · 2FA (SMS OTP) |
| API Docs | drf-yasg (Swagger / ReDoc) |
| Containerisation | Docker · Docker Compose |
┌──────────────────────────────────────────────────────────┐
│ Clients │
│ (Web / Mobile / Third-party) │
└────────────────────────┬─────────────────────────────────┘
│ HTTP / WebSocket
▼
┌──────────────────────────────────────────────────────────┐
│ Daphne (ASGI server) :8000 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Django + DRF (REST API) │ │
│ │ users · profiles · lawyer · lawyer_appointment │ │
│ │ lawyer_wallet · subscriptions · kyc │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Django Channels (WebSocket consumers) │ │
│ └─────────────────────────────────────────────────────┘ │
└──────┬──────────────────┬──────────────────┬─────────────┘
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌──────────────┐ ┌──────────────────────┐
│ PostgreSQL │ │ Redis 7 │ │ Celery Worker │
│ (primary DB)│ │ cache · OTP │ │ (async tasks, │
│ │ │ sessions │ │ reminders, │
│ │ │ WS layer │ │ notifications) │
└─────────────┘ └──────────────┘ └──────────────────────┘
External Services:
Stripe - payments, subscriptions, refunds, KYC identity
Agora - RTC tokens for video/audio consultations
Twilio - SMS OTP delivery
SendGrid - transactional email
Cloudinary - media/avatar storage
Client/Vendor registers (email + password)
└─ Verification email sent (SendGrid)
└─ User clicks link → email verified
Login:
└─ Credentials valid → access token + refresh cookie returned
└─ If 2FA enabled → OTP sent via Twilio → verification required before token issued
└─ Google OAuth2 → handled via social-auth-app-django
POST /api/profiles/session/start/ → returns session token
POST /api/profiles/steps/category/ → select professional category
POST /api/profiles/steps/business-name/ → set business name
POST /api/profiles/steps/address/ → set location
POST /api/profiles/steps/languages/ → select languages spoken
POST /api/profiles/steps/avatar/ → upload profile photo
POST /api/profiles/steps/legal-info/ → bar association, ID (lawyers only)
POST /api/profiles/steps/additional-info/ → bio, experience, social links
POST /api/profiles/complete/ → creates VendorProfile, unlocks dashboard
Each step validates the session token and asserts ownership using session.
Client browses lawyers → checks availability
└─ POST /api/lawyer/appointment/ → creates Appointment (status=PENDING)
→ creates Transaction (type=PAYMENT, status=PENDING)
→ Stripe PaymentIntent created
→ Celery task: auto-cancel after 10 minutes if unpaid
Client completes payment in frontend
└─ Stripe webhook: payment_intent.succeeded
→ appointment.status = CONFIRMED
→ Transaction stripe_id recorded
→ Funds held in escrow (Transaction stays PENDING)
Appointment occurs (physical / audio / video)
Either party marks appointment complete
└─ POST /api/lawyer/appointment/{id}/complete/
→ validates appointment has actually ended (end_datetime <= now)
→ Transaction status = COMPLETED
→ wallet.balance += lawyer_amount (with SELECT FOR UPDATE)
→ Lawyer can now withdraw
Appointment cancelled (within refund window)
└─ Stripe Refund created
└─ Webhook: refund.created
→ Transaction status = COMPLETED (refund transaction)
→ Client notified
Stripe dispute opened (chargeback)
└─ Webhook: charge.dispute.created
→ Pending payment transaction frozen (status = FAILED)
→ Lawyer and client notified
→ Admin alerted for manual review
Vendor completes profile
└─ Stripe Customer + Subscription created (trial_period_days=15)
└─ Webhook: customer.subscription.created → VendorSubscription saved
Trial ends:
└─ Webhook: customer.subscription.updated (status: trialing → active)
→ Listing activated, renewal date cached in Redis
Payment fails:
└─ Webhook: invoice.payment_failed
→ Vendor listing deactivated, notification sent
Voucher applied:
└─ POST /api/subscription/voucher/apply/
→ Voucher row locked (SELECT FOR UPDATE) to prevent over-use
→ Stripe coupon applied to subscription
Appointment type = VIDEO, status = CONFIRMED
└─ GET /api/lawyer/appointment/{id}/session/token/
→ Agora RTC token generated (role: publisher for both parties)
→ Session record created / updated
Client and lawyer join using Agora SDK in frontend
Session ends → session.status = ENDED
Lawyer requests KYC:
└─ POST /api/kyc/start/
→ Stripe Identity session created
→ Verification URL returned to frontend
Stripe webhook: identity.verification_session.verified
└─ KYCVerification.is_verified = True
└─ LawyerProfile.kyc_verification_status = "verified"
Lawyer requests withdrawal:
└─ POST /api/lawyer/wallet/withdraw/
→ KYC verified? → yes
→ Wallet locked? → no
→ SELECT FOR UPDATE on wallet → re-check balance
→ Transaction (type=PAYOUT, status=PENDING) created
→ wallet.balance -= amount
→ Admin approves → payout processed
- Register with email, password, first name, last name, and phone number.
- A verification email is sent via SendGrid. The client clicks the link to activate their account.
- (Optional) Sign in with Google OAuth2 - account is created automatically on first social login.
- Submit email and password.
- If 2FA is enabled: an OTP is sent to the registered phone number via Twilio. The client submits the code to receive their JWT access token and refresh cookie.
- If 2FA is disabled: tokens are issued immediately on valid credentials.
- After 5 failed attempts the account is locked for 30 minutes.
- Search and filter the lawyer listing by category, subcategory, language, rating, and price.
- View a lawyer's public profile: bio, bar association, experience, offerings, and ratings.
- Check the lawyer's availability calendar for a specific date to see open slots and their prices.
- Select a time slot and a service type:
- Physical - in-office meeting (free)
- Audio - phone consultation (paid)
- Video - video call via Agora (paid)
- Submit the booking request. The system creates the appointment with status
PENDINGand generates a Stripe PaymentIntent. - The client has 10 minutes to complete payment. If the window expires, the appointment is automatically cancelled by a Celery task.
- The client completes the Stripe payment on the frontend using the PaymentIntent client secret.
- Stripe fires
payment_intent.succeeded→ the webhook confirms the appointment, setting its status toCONFIRMED. - Money is held in escrow (the lawyer does not receive it yet).
- A reminder notification is sent 24 hours before the appointment starts.
For a Video or Audio appointment:
- The client requests an Agora RTC token from the API.
- Both the client and lawyer join the call using the Agora SDK in the frontend.
- The session is tracked (
not_started → active → ended).
For a Physical appointment:
- The client goes to the lawyer's office at the booked time.
- Either party can reschedule before the appointment starts.
- The appointment keeps its
CONFIRMEDstatus withis_rescheduled = trueand a new datetime. - The original payment is preserved; no new payment is required.
- After the appointment ends, either the client or the lawyer calls the complete endpoint.
- The system validates that
end_datetimehas passed before allowing completion. - The appointment moves to
COMPLETEDand the escrowed funds are released to the lawyer's wallet.
- The client can cancel a
CONFIRMEDappointment. - If the cancellation happens before the lawyer's cancellation threshold window (e.g., 24 hours before), the client is eligible for a refund.
- The API triggers a Stripe refund automatically.
- Stripe fires
refund.created→ the webhook confirms the refund and notifies the client. - If the cancellation is too late, no refund is issued.
- If the client raises a dispute with their bank, Stripe fires
charge.dispute.created. - The pending payment transaction is frozen immediately to protect both parties.
- The lawyer and client are notified, and the case is flagged for manual admin review.
- Register with email, password, and phone number.
- Select role: vendor and vendor type: lawyer (or notary, accountant, translator) at signup.
- Verify the email address via the link sent to their inbox.
- Same as the client flow, including optional 2FA via SMS OTP.
The platform uses a multi-step session-based method. Each step saves progress so the lawyer can resume later. All steps are authenticated and ownership-verified.
| # | Step | What is saved |
|---|---|---|
| 1 | Start session | Session token issued |
| 2 | Category | Professional category (e.g., Lawyer) |
| 3 | Business name | Display name on the marketplace |
| 4 | Address | Office location with coordinates |
| 5 | Languages | Languages spoken with clients |
| 6 | Avatar | Profile photo (URL or file upload → Cloudinary) |
| 7 | Legal info (lawyers only) | First/last name ID, email, bar association |
| 8 | Additional info | Bio, years of experience, website, social links |
| 9 | Complete | VendorProfile created, dashboard unlocked |
- After profile completion, a Stripe Customer and Subscription are created automatically.
- The lawyer receives a 15-day free trial.
- At trial end, Stripe charges the configured monthly fee. The lawyer's listing stays active while the subscription is
activeortrialing. - A voucher code can be applied at any time to get a percentage or fixed discount.
- If a payment fails, the listing is deactivated until billing is resolved.
Pricing plans (offerings):
- Create one or more named pricing plans (e.g., "Standard Plan", "Weekend Premium").
- Each plan has a
price_per_30minrate. Duration-based pricing is calculated automatically. - Activate service delivery types per plan: Physical (free), Audio (paid), Video (paid).
Availability schedule (powers the booking calendar):
- Define recurring weekly slots - each slot is a day + time window + pricing plan (e.g., Monday 09:00–17:00 on "Standard Plan").
- Use bulk create to apply the same slot to multiple days in one request (e.g., set Mon–Fri 09:00–17:00 at once).
- Use copy template to clone an entire day's schedule to other days.
- The system rejects any overlapping slot on the same day at save time.
Blocking out time (unavailability):
- Block specific calendar dates - either all-day or a time range (e.g., 14:00–16:00 for a court appearance).
- Categorise each block: vacation, sick leave, personal, court appearance, meeting, training, or other.
- Past blocks can be cleared in bulk; a stats endpoint shows counts by type and the next upcoming block.
How the frontend uses this:
- The calendar endpoint (
/availability/calendar/) returns one object per day in a date range. Each day hasavailable_slots,is_available, andtotal_hours. Unavailability blocks are already subtracted - the frontend just renders what it receives. - Tapping a day calls the single-date endpoint to show bookable slots with live pricing.
- Before submitting a booking, the frontend calls the time-check endpoint to confirm the slot is still free.
- Before withdrawing any earnings, the lawyer must complete KYC.
- Initiate a Stripe Identity verification session from the dashboard.
- Stripe sends the lawyer through a document + selfie check.
- Stripe fires
identity.verification_session.verified→ KYC status is set toverified. - Without verified KYC, all withdrawal requests are rejected.
- Clients book slots from the lawyer's availability calendar.
- On payment confirmation, the lawyer sees the appointment as
CONFIRMEDin their dashboard. - A 24-hour reminder notification is sent via WebSocket and stored in the notification centre.
Video / Audio:
- The lawyer requests an Agora RTC token.
- Both parties join using the Agora SDK.
- The session state is tracked server-side.
Physical:
- The client arrives at the office at the agreed time.
- After the appointment ends, the lawyer (or client) marks it as complete via the API.
- The system verifies the appointment has ended before releasing funds.
- The escrowed amount (minus platform commission) is credited to the lawyer's wallet instantly.
- A
COMPLETEDtransaction record is created in the wallet ledger.
- The lawyer submits a withdrawal request specifying an amount.
- Pre-conditions checked:
- KYC is verified
- Wallet is not locked
- Balance is sufficient (checked again under a row-level lock to prevent race conditions)
- A
PAYOUTtransaction is created with statusPENDING. - The balance is deducted immediately to prevent double withdrawal.
- An admin approves the payout and processes the bank transfer.
- The lawyer receives a notification when the withdrawal is approved.
- The lawyer can cancel a confirmed appointment before it starts.
- Whether the client receives an automatic refund depends on the lawyer's configured cancellation threshold.
- The lawyer is notified and the appointment is marked
CANCELLED.
| App | Responsibility |
|---|---|
users |
Custom user model, auth (JWT, 2FA, Google OAuth2), email/phone verification, password management |
profiles |
Vendor profile wizard, VendorProfile, Address, Categories, Languages, Certificates, Media |
lawyer |
LawyerProfile, LawyerOffering (pricing plans), OfferingType (physical/audio/video), categories |
lawyer_availability |
Weekly availability slots, unavailability periods, conflict detection |
lawyer_appointment |
Appointment CRUD, Stripe payment orchestration, session management, refunds |
lawyer_wallet |
Wallet model, transaction ledger, withdrawal requests |
subscriptions |
SubscriptionPlan, VendorSubscription, Voucher, Stripe webhook handler, in-app notifications |
kyc |
KYCVerification model, Stripe Identity integration |
Interactive documentation is available at runtime:
| URL | Description |
|---|---|
/swagger/ |
Swagger UI |
/redoc/ |
ReDoc UI |
/swagger.json |
Raw OpenAPI schema |
| Prefix | Module |
|---|---|
POST /api/user/register/ |
Registration |
POST /api/user/login/ |
Login (returns JWT) |
POST /api/user/token/refresh/ |
Refresh access token |
POST /api/user/logout/ |
Blacklist refresh token |
POST /api/user/2fa/toggle/ |
Enable / disable 2FA |
POST /api/user/verify-2fa/ |
Verify OTP during login or setup |
GET/PATCH /api/user/profile/ |
Current user profile |
POST /api/profiles/session/start/ |
Begin vendor onboarding |
POST /api/profiles/steps/*/ |
Onboarding steps |
POST /api/profiles/complete/ |
Finish onboarding |
GET /api/lawyer/ |
List / search lawyers |
GET /api/lawyer/availability/weekly/ |
Weekly schedule grouped by day name |
GET /api/lawyer/availability/calendar/ |
Per-day calendar for a date range (up to 90 days) |
GET /api/lawyer/availability/<lawyer_id>/<date>/ |
Available slots for a single date |
POST /api/lawyer/availability/check-time/ |
Check if a specific time window is free |
GET /api/lawyer/availability/by-offering/ |
Slots grouped by pricing plan |
GET /api/lawyer/availability/ |
CRUD - list/create availability slots |
GET/PUT/DELETE /api/lawyer/availability/<id>/ |
CRUD - single slot |
POST /api/lawyer/availability/bulk-create/ |
Create same slot across multiple days |
POST /api/lawyer/availability/copy-template/ |
Clone one day's schedule to other days |
DELETE /api/lawyer/availability/clear-all/ |
Remove all slots (lawyer only) |
GET /api/lawyer/unavailability/ |
List / create unavailability blocks |
GET/PUT/DELETE /api/lawyer/unavailability/<id>/ |
Single unavailability entry |
GET /api/lawyer/unavailability/stats/ |
Counts by type + next upcoming block |
DELETE /api/lawyer/unavailability/clear-past/ |
Remove past blocks |
POST /api/lawyer/appointment/ |
Book appointment |
GET /api/lawyer/appointment/{id}/ |
Appointment details |
POST /api/lawyer/appointment/{id}/cancel/ |
Cancel appointment |
POST /api/lawyer/appointment/{id}/reschedule/ |
Reschedule |
POST /api/lawyer/appointment/{id}/complete/ |
Mark complete & release funds |
GET /api/lawyer/appointment/{id}/session/token/ |
Agora RTC token |
GET /api/lawyer/wallet/ |
Wallet balance |
POST /api/lawyer/wallet/withdraw/ |
Request withdrawal |
GET /api/lawyer/wallet/transactions/ |
Transaction history |
POST /api/subscription/subscribe/ |
Create subscription |
POST /api/subscription/voucher/apply/ |
Apply voucher |
GET /api/subscription/status/ |
Subscription status |
POST /api/kyc/start/ |
Start KYC verification |
GET /api/kyc/status/ |
KYC status |
POST /stripe/webhook/ |
Stripe webhook receiver |
- Docker Desktop (includes Docker Compose)
- A Stripe account (test keys are fine to start)
- A Twilio account (for SMS OTP)
- A SendGrid account (for email)
git clone https://github.com/your-username/juris-backend.git
cd juris-backendcp .env.example .envOpen .env and fill in your credentials. See Environment Variables for the full reference.
Linux / macOS:
docker compose up --buildWindows (Docker Desktop with WSL2):
docker compose up --buildThis starts five containers:
| Container | Role |
|---|---|
juris_backend |
Django / Daphne ASGI server on port 8000 |
juris_postgres |
PostgreSQL 17 database |
juris_redis |
Redis 7 cache, channel layer, OTP store |
juris_celery |
Celery async worker |
juris_stripe_cli |
Stripe CLI for local webhook forwarding |
Migrations and collectstatic run automatically on container start.
# Creates Category and Language seed data
docker compose exec web python manage.py load_initial_data
# Creates Stripe Products and syncs subscription plans
docker compose exec web python manage.py setup_stripe_productsdocker compose exec web python manage.py createsuperuser| URL | Description |
|---|---|
http://localhost:8000/swagger/ |
API documentation |
http://localhost:8000/admin/ |
Django admin panel |
http://localhost:8000/api/ |
REST API root |
Copy .env.example to .env and fill in the values. Key variables:
| Variable | Description |
|---|---|
DJANGO_SECRET_KEY |
Django secret key (generate a long random string) |
DEBUG |
True for development, False for production |
DJANGO_SETTINGS_MODULE |
Must be juris.settings |
POSTGRES_DB/USER/PASSWORD |
PostgreSQL credentials |
STRIPE_SECRET_KEY |
Stripe secret key (sk_test_... or sk_live_...) |
STRIPE_WEBHOOK_SECRET |
Stripe webhook signing secret (whsec_...) |
TWILIO_ACCOUNT_SID |
Twilio Account SID |
TWILIO_AUTH_TOKEN |
Twilio Auth Token |
TWILIO_PHONE_NUMBER |
Twilio phone number for sending SMS |
SENDGRID_API_KEY |
SendGrid API key |
AGORA_APP_ID |
Agora App ID |
AGORA_APP_CERTIFICATE |
Agora App Certificate |
CLOUDINARY_CLOUD_NAME/API_KEY/API_SECRET |
Cloudinary credentials |
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY/SECRET |
Google OAuth2 credentials |
JWT_SIGNING_KEY |
Separate signing key for JWT tokens |
FRONTEND_URL |
Your frontend origin URL (used in email links) |
See .env.example for the full list with default values.
# Load seed data (categories, languages)
python manage.py load_initial_data
# Sync subscription plans to Stripe
python manage.py setup_stripe_products
# Send trial-ending and renewal notifications manually
python manage.py check_subscriptions
# Delete expired profile completion sessions
python manage.py cleanup_sessions- Stripe CLI is included in
docker-compose.ymlfor local webhook testing. It forwards Stripe events toweb:8000/stripe/webhook/automatically. - In development (
DEBUG=True), SQLite is used. Switch to PostgreSQL for staging and production by settingDEBUG=Falseand providing Postgres credentials. - The project uses
python-decoupleto read all configuration from.env. Never hardcode credentials.