MaskanX runs the campaigns; this stores what they cost and what they produced so the question "what did this campaign cost us per lead" is answerable next to the leads themselves. POST /integrations/campaigns is an upsert keyed on (provider, external_id), not an idempotent create like /leads. A lead is an event that happened once; a campaign's figures change every time they are read, and MaskanX re-pushes the same campaign as its spend grows. An Idempotency-Key here would pin the CRM to the first numbers it ever saw. Money is stored as integers in minor currency units, matching what MaskanX sends and what Meta uses. A Numeric would add a second convention and a rounding step between systems that currently agree exactly. Ad attribution is promoted out of crm_leads.attributes into indexed columns, so counting leads per campaign is a join rather than a JSON scan — which also keeps it working on both SQLite and PostgreSQL. Meta's lead count and the CRM's own are both kept. They routinely differ, since Meta attributes late and leads can be entered by hand, and the gap is worth seeing rather than hiding behind one number. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Maskan CRM Backend
Independent FastAPI API for Maskan CRM. This repository owns CRM contacts, organizations, leads, pipelines, activities, products, quotes, users, permissions, audit records, and first-party integration credentials.
Runtime
- Python 3.11 to 3.13
- FastAPI and SQLAlchemy
- Alembic migrations
- PostgreSQL 16 for every non-test environment
- Node.js 20+ only as a consistent command runner for DevOps
The API never shares a database or database credentials with MaskanX.
Local Setup
Copy-Item .env.local.example .env.local
npm install
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -e ".[dev]"
npm run docker:up
npm run local:seed
docker:up starts the API and PostgreSQL. To run the API directly with reload,
start only PostgreSQL and use:
npm run local:migrate
npm run local:seed
npm run local
Which PostgreSQL are you pointing at?
.env.local.example ships with DB_PORT=5433, the port Docker Compose
publishes for this repository's own maskan-crm-postgres container. That is
correct when you run npm run docker:up.
If you instead use a PostgreSQL you already run locally (usually port 5432),
set DB_PORT=5432 and DB_PASSWORD to that server's password, and create the
database first:
CREATE DATABASE maskan_crm;
Pointing at a port where nothing is listening fails with
psycopg.errors.ConnectionTimeout: connection timeout expired during
local:migrate or local:seed, and the API returns 500s on /auth/login
(which the browser then reports as a CORS error, because the error response
carries no CORS headers).
Open:
- API:
http://127.0.0.1:8091 - API docs:
http://127.0.0.1:8091/api/docs - Readiness:
http://127.0.0.1:8091/health/ready
Lifecycle Commands
The requested environment command names are available using this backend's native Python tooling. Alembic replaces Sequelize because this is a FastAPI service.
| Command | Purpose |
|---|---|
npm start |
Start using .env or injected environment variables |
npm run build |
Build the Python wheel into dist/ |
npm run local |
Start with .env.local and reload |
npm run local:migrate |
Apply local Alembic migrations |
npm run local:migrate:undo |
Undo the newest local migration |
npm run local:migrate:undo:all |
Roll back all local migrations |
npm run local:seed |
Seed the bootstrap workspace and optional demo data |
npm run local:seed:undo |
Remove the local bootstrap workspace |
npm run local:reset |
Recreate and seed the local schema |
npm run dev* |
Equivalent commands using .env.development |
npm test |
Run unit/API tests using .env.testing |
npm run test:migrate |
Apply migrations using .env.testing |
npm run test:seed |
Seed using .env.testing |
npm run test:reset |
Recreate the test schema |
npm run prod |
Start with production validation enabled |
npm run prod:migrate |
Apply production migrations |
npm run prod:seed |
Seed production without demo records |
Production
Supply secrets through the deployment platform, not committed files:
MASKAN_CRM_ENV=production
MASKAN_CRM_MODE=standalone
MASKAN_CRM_CORS_ORIGINS=https://crm.example.com
MASKAN_CRM_JWT_SECRET=<at-least-32-random-characters>
DB_HOST=<managed-postgres-host>
DB_PORT=5432
DB_NAME=maskan_crm
DB_USER=<dedicated-user>
DB_PASSWORD=<secret>
DB_SSLMODE=require
Run npm run prod:migrate as a release step, then run npm run prod.
MaskanX Integration
MaskanX connects to /api/v1/integrations with a revocable mcrm_ service key
and idempotency keys. This reliable REST path is the normal product
integration. MCP tools are an optional agent interface and do not replace the
service-to-service API.
Database Policy
PostgreSQL is the configured database for local, development, testing
migrations, staging, and production. A small in-memory SQLite fixture is used
inside isolated unit tests only and is rejected as an application runtime
database outside the explicit testing environment.