Self-hosted campaign tracking and marketing analytics platform. Track clicks, conversions, and revenue across any traffic source with full data ownership — your data stays on your servers.
Status: production-track (
v1.0.1) — all P0 readiness blockers delivered: distributed Redis rate limiting, Prometheus edge monitoring, K8s HPA manifests, migration rollback tooling. Seeplans/gap-analysis-p0-implementation.md. AI-agent & contributor conventions: seeAGENTS.md.
This platform now runs as a polyglot stack with three cooperating servers:
| Layer | Tech | Purpose |
|---|---|---|
| PHP 8.3+ | Laravel-style + Slim | Core tracking & APIs (v2/v3), admin SPA, offer/rotator/attribution engines, Go CLI, edge redirector |
| Node.js (Express) | server/app.js on port 3001 |
Companion services: auth, payment (Tripay), smartlinks, Telegram poster, Funnel pipeline (FB/IG video), AI content, geo, settings, postback queue |
| Go | edge/cmd/edgeredirect |
Ultra-fast redirect microservice (Redis + GeoIP + fraud + Kafka) |
| React 19 | frontend/ (Vite 8, Tailwind v4) |
Admin dashboard SPA (60+ pages: campaigns, pipeline, poster, wallet, finance, reports); builds into server/public/dist and is served by the Node server |
Key integration: The Node.js companion server now absorbs the external Telegram poster and Funnel pipeline services — they are no longer external Python scripts but native Node modules (
server/services/posterService.js,server/services/pipelineService.js). Both services mint tracked smartlinks viasmartlinkService.mintSmartlink()so every posted affiliate link becomes a/go/<slug>redirect that the PHP platform attributes for click→conversion tracking.
| Service | Was | Now | Integration |
|---|---|---|---|
| Telegram Poster | telegram-poster/poster.py (Python) |
server/services/posterService.js + posterWorker.js + /api/poster |
Native Node; mints smartlinks via mintSmartlink(); scheduled cron |
| Funnel Pipeline | funnel-pipeline/scripts/affiliate_content_pipeline.py (Python) |
server/services/pipelineService.js + pipelineWorker.js + /api/pipeline |
Native Node; mints tracked smartlinks per niche; FB/IG posting |
| Affiliate Core | affiliate-core/ (Supabase + Fly.io) |
Separate product; shared JWT contract | Not merged — runs independently on Fly.io |
| jendralbot | jendralbot/index.html (static) |
Landing page candidate | To be served as root / with tracked smartlink CTAs |
- MySQL 8 — System of record for all PHP + Node services
- Redis — Session cache, edge redirector lookup, rate limits
- ClickHouse — Cold-path click analytics (via Go consumer)
- Kafka — Click event stream (Go edge → ClickHouse)
- Prometheus + Grafana (included in docker-compose)
- BullMQ Dashboard (
/api/bull-dashboardon Node) for queue observability - Structured logging via Pino (Node) + Monolog (PHP)
The platform is designed to run behind cf-router for nginx reverse proxy, Cloudflare DNS, and SSL management. cf-router handles Cloudflare DNS, TLS (via Cloudflare proxy), and nginx reverse proxy configs automatically.
| Component | Version |
|---|---|
| Docker | 24+ |
| Docker Compose | v2+ |
| cf-router | Running separately on host/port 3002 |
| Cloudflare Account | With API Token (Zone:DNS:Edit) |
# 1. Clone and prepare
git clone https://github.com/oyi77/1ai-affiliate.git
cd 1ai-affiliate
cp .env.example .env
# Edit .env with your credentials
# 2. Build and start the stack
docker compose up -d --build
# 3. Run migrations
docker compose exec php php scripts/migrate.php
# 4. Verify services
docker compose ps
# Should show: db, redis, php, node, (phpmyadmin if profile tools)Run cf-router on a separate host/port (default port 3002):
# On cf-router host
cd ~/projects/1ai-cf-router
npm install
npm run dev# 1. Add Cloudflare account
cf-router account:add --name "BerkahKarya" --email "your@email.com" --api-key "cf_api_token_with_zone_dns_edit"
# 2. Discover zones
cf-router zone:discover --account <account_id>
# 3. Add your domain zone
cf-router zone:add --account <account_id> --zone-id <zone_id> --domain berkahkarya.org
cf-router mapping:add --domain berkahkarya.org --subdomain affiliate --port 80 --host php
cf-router mapping:add --domain berkahkarya.org --subdomain affiliate-api --port 3001 --host node
cf-router mapping:add --domain berkahkarya.org --subdomain affiliate-tools --port 80 --host phpmyadmin
cf-router mapping:add --domain berkahkarya.org --subdomain l --port 80 --host php
cf-router mapping:add --domain berkahkarya.org --subdomain docs --port 3000 --host playbookAfter adding mappings, cf-router automatically:
- Creates Cloudflare DNS records (proxied = true, SSL via Cloudflare)
- Generates nginx reverse proxy configs pointing to
php:80,node:3001, etc.- Reloads nginx
- SSL handled by Cloudflare proxy (orange cloud)
| Subdomain | Backend | Purpose |
|---|---|---|
affiliate.berkahkarya.org |
php:80 |
Main PHP app (tracking, admin, API v2/v3) |
affiliate-api.berkahkarya.org |
node:3001 |
Node companion (smartlinks, poster, pipeline, auth) |
affiliate-tools.berkahkarya.org |
phpmyadmin:80 |
phpMyAdmin (optional, tools profile) |
l.berkahkarya.org |
php:80 |
Deep link landing pages (/{slug} → deeplink.php) |
- Terminated at Cloudflare (orange cloud = proxied)
- No SSL cert management needed on your server
- Cloudflare handles TLS 1.3, automatic cert renewal
| Profile | Services | Use Case |
|---|---|---|
default |
db, redis, php, node | Production |
tools |
+ phpmyadmin, mailhog | Development/debug |
# Start with tools
docker compose --profile tools up -d
# Access phpMyAdmin at http://localhost:8080 (if local) or via cf-router tools subdomain
# Access Mailhog UI at http://localhost:8025All configuration via .env file (copy from .env.example):
| Variable | Description | Example |
|---|---|---|
DB_ROOT_PASSWORD |
MySQL root password | secure_root_pass |
DB_NAME |
Database name | Prosper1ai |
DB_USER |
DB user | affiliate |
DB_PASSWORD |
DB password | affiliate_pass |
JWT_SECRET |
Shared JWT secret (PHP + Node) | long_random_string |
CF_DOMAIN |
Base domain for smartlinks | affiliate.berkahkarya.org |
TG_BOT_TOKEN |
Telegram bot token (for poster) | 123:ABC... |
TG_CHANNEL_ID |
Telegram channel | -1001234567890 |
TRIPAY_* |
Tripay payment gateway | (see .env.example) |
FB_PAGES_JSON |
Facebook pages config | '[{"id":"...","token":"...","niche":"hijab"}]' |
IG_ACCOUNTS_JSON |
Instagram accounts | '[{"id":"...","token":"...","niche":"hijab"}]' |
SHOPEE_LINKS_JSON |
Shopee affiliate links per niche | '{"hijab":"https://lynk.id/..."}' |
REST API under /api/v3/ with bearer token authentication. Covers all 1ai-Affiliate entities: campaigns, networks, traffic sources, trackers, landing pages, text ads, clicks, conversions, rotators, attribution models, users, and system operations.
curl -H "Authorization: Bearer <api-key>" https://your-server/api/v3/campaignsSymfony Console CLI for managing remote 1ai-Affiliate installations.
# Configure
bin/p1ai config:set-url https://your-server
bin/p1ai config:set-key <api-key>
# Use
bin/p1ai campaign:list
bin/p1ai tracker:get 42
bin/p1ai rotator:create --name "My Rotator"Cross-platform Go CLI with --json output for scripting and agent consumption.
cd go-cli
make build
./p1ai config set-url https://your-server
./p1ai config set-key <api-key>
./p1ai campaign list --json
./p1ai sync allThree independent dependency trees (npm workspaces are NOT used):
npm install # repo root — provides socket.io/mysql2/dotenv required by server/app.js
cd server && npm install # Express API deps (.npmrc include=dev keeps jest/playwright)
cd frontend && npm install # React admin SPA deps
composer install # PHP legacy core depsConfiguration is loaded from the repo-root .env (server/.env is ignored even though server/.env.example exists there). Hard requirement: JWT_SECRET — the Node server exits without it.
cd server && npm run dev # Node API on :3001 (node --watch app.js)
cd frontend && npm run dev # Vite dev server, proxies /api -> localhost:3001
./start.sh --docker|--stop|--status # full stack: MySQL, Redis, php-fpm (:9002), pm2, nginx (:6969), cronscd frontend && npm run build # emits into server/public/dist (served by the Node server)node server/migrations/run_migrations.js # manifest.json array order; checksum ledger; FORCE=1 re-run
php scripts/run_rollback.php --from=NNN --to=NNN [--dry-run] # legacy-tree rollbacks only (scripts/NNN_*.sql)npm test # root alias -> cd server && npm test (Jest 30; coverage always on with hard thresholds over core paths)
cd server && npx jest tests/unit # unit suites only
cd server && npx jest tests/e2e # supertest e2e against in-process app (mocked DB — no live services needed)
cd server && npm run test:e2e # real-DB wrapper (requires live MariaDB prosper1ai_test) — opt-in
npm run test:playwright # browser E2E; start the app yourself first (config has no webServer)
# PHP tests
composer test
# Go edge + CLI
cd edge && go test ./...
cd go-cli && make test./scripts/php-lint.sh # CI also gates PHP syntax + shell via .github/workflows/pr-checks.yml
cd frontend && npm run lint # ESLint for the SPA- Main config:
1ai-config.php(created from1ai-config-sample.php) - Database: MySQL/MariaDB with optional read replica support
- Caching: Memcached integration available
Configure custom domains for branded smartlink redirects:
-
Add Domain — Via admin panel (
/admin/index.html#domains) or API:curl -X POST https://your-server/api/admin/domains \ -H "Authorization: Bearer <api-key>" \ -H "Content-Type: application/json" \ -d '{"domain":"go.yourdomain.com","is_active":true,"ssl_enabled":true}'
-
Set Default — Mark one domain as default for new smartlinks
-
DNS Configuration — Point your domain to the cf-router server:
go.yourdomain.com → CNAME → cf-router.example.com -
Generate Smartlink — Specify domain when creating:
curl -X POST https://your-server/api/smartlink/generate \ -H "Authorization: Bearer <api-key>" \ -H "Content-Type: application/json" \ -d '{"offer_id":1,"domain_id":1}'
Configure URL shorteners (Bitly, TinyURL, Rebrandly, Cutt.ly, Short.io, or custom):
-
Configure Service — Via admin panel (
/admin/index.html#shorteners) or API:curl -X POST https://your-server/api/admin/shorteners \ -H "Authorization: Bearer <api-key>" \ -H "Content-Type: application/json" \ -d '{"name":"My Bitly","service_type":"bitly","api_key":"YOUR_API_KEY","is_active":true}'
-
Generate Shortened Smartlink:
curl -X POST https://your-server/api/smartlink/generate \ -H "Authorization: Bearer <api-key>" \ -H "Content-Type: application/json" \ -d '{"offer_id":1,"shortener_service_id":1}'
-
Test Shortener:
curl -X POST https://your-server/api/admin/shorteners/1/test \ -H "Authorization: Bearer <api-key>" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com"}'
1ai_smartlink_domains— Custom domains for smartlinks1ai_url_shortener_services— URL shortener configurations1ai_short_url_logs— Analytics for shortened URLs1ai_affiliate_links— Extended withdomain_id,short_url,shortener_service_id
Business Source License 1.1 (BUSL-1.1) — see LICENSE for the full text.
- Licensor: Blue Terra LLC
- Licensed Work: 1ai-Affiliate
- Additional Use Grant: You may use the Licensed Work for any purpose, including production use, except you may not offer it as a hosted or managed service to third parties.
- Change Date: 2031-02-22
- Change License: GPL-2.0-or-later