AFFAANhandClaude Opus 5 55d5af849b feat(campaigns): report advertising spend and cost per lead
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>
2026-08-04 10:55:46 +05:30
2026-08-01 10:28:41 +05:30
2026-08-01 10:28:41 +05:30
2026-08-01 10:28:41 +05:30
2026-08-01 10:28:41 +05:30
2026-08-01 10:28:41 +05:30

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.

S
Description
No description provided
Readme
118 KiB
Languages
Python 99%
JavaScript 0.6%
Mako 0.3%
Dockerfile 0.1%