Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
165279a
Merge dev into main (#117)
zeemscript Aug 24, 2026
388a264
refactor(db): Create /mongo folder structure (#280)
Inspector222 Aug 24, 2026
f7a203c
feat(soroban): Implement loyalty points contract (#279)
Inspector222 Aug 24, 2026
83a1f28
feat(courses): add course analytics for creators (#193) (#287)
Ezekiel146 Aug 24, 2026
dbc8dd1
feat(db): implement cursor-based pagination utilities (#188) (#286)
Ezekiel146 Aug 24, 2026
fa29916
feat(db): implement read replica support (#190) (#284)
Ezekiel146 Aug 24, 2026
746b529
refactor(db): create BookRepository (#172) (#285)
Ezekiel146 Aug 24, 2026
2fb4e4b
feat(stellar): add fiat on-ramp integration (#166) (#295)
Heazzy500 Aug 24, 2026
f9f9eed
feat(stellar): add payment analytics dashboard data (#162) (#293)
Heazzy500 Aug 24, 2026
0cdfec1
feat(db): add full-text search abstraction (#297)
Banx17 Aug 24, 2026
17a5a58
feat(db): add MongoDB aggregation pipeline builders (#183) (#299)
ezzy156 Aug 25, 2026
325c9b0
feat(courses): add course prerequisites system (#182) (#300)
ezzy156 Aug 25, 2026
fc60835
feat(mongo): add TransactionRepository with analytics methods (#306)
Olarewaju1234 Aug 25, 2026
2714080
feat(reels): add duet/stitch response videos (#211) (#303)
rasheedayinde015-a11y Aug 25, 2026
cdaf507
feat(mongo): add SpaceRepository for member management (#305)
Olarewaju1234 Aug 25, 2026
409b7f6
feat(mongo): add QueryBuilder utility with fluent API (#304)
Olarewaju1234 Aug 25, 2026
a79b3f7
refactor(db): Create EducatorBalanceRepository (closes #179) (#302)
Banx17 Aug 25, 2026
4127105
refactor(db): Create NotificationRepository (closes #177) (#298)
Banx17 Aug 25, 2026
ae8c612
refactor(db): Create ReelRepository (closes #176) (#301)
Banx17 Aug 25, 2026
e0099ba
feat: implement live session polls, highlights & notes, content moder…
makuo567 Aug 25, 2026
88136bd
feat: course bundles, certificates PDF, db health check, course compl…
Kaycee276 Aug 25, 2026
d32301d
feat(books): implement reading progress sync (#203) (#310)
Olarewaju1234 Aug 25, 2026
866ef17
feat(db): add connection pool monitoring metrics endpoint (#192) (#309)
Meeynarh1 Aug 25, 2026
2b88d8b
feat(social): add real-time direct messaging system (closes #214)
zeemscript Aug 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 88 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,22 @@ SENDLIB_API_URL=https://sendlib.samueltuoyo.com/api/send
# SendLib. Prefer a Workspace address on your own domain (e.g. no-reply@deenbridge.app).
EMAIL_FROM=no-reply@deenbridge.com

# Stellar blockchain network (testnet or mainnet)
# Stellar blockchain network (testnet or mainnet; "public" is accepted as an
# alias for mainnet). Validated fail-fast at boot — a mainnet flag paired
# with a testnet Horizon URL or USDC issuer aborts startup with the exact
# problem named. See docs/MAINNET.md for the full mainnet switch checklist.
STELLAR_NETWORK=testnet

# Soroban loyalty points program (contracts/loyalty-points). The contract id
# (C…55-char StrKey) of the deployed program; the backend only builds
# unsigned XDR and reads state, so no secret keys belong here.
LOYALTY_CONTRACT_ID=

# Public Soroban RPC endpoint for contract queries/simulation. Optional —
# falls back to https://soroban-testnet.stellar.org on testnet; REQUIRED on
# mainnet where no public default exists.
# SOROBAN_RPC_URL=https://soroban-testnet.stellar.org

# Resilient Horizon Client Configuration (Optional)
# HORIZON_URLS=https://horizon-testnet.stellar.org,https://horizon-testnet.stellar.org (Comma-separated list of Horizon endpoints)
# HORIZON_TIMEOUT_MS=10000 (Request timeout in milliseconds)
Expand All @@ -53,6 +66,27 @@ DONATION_WALLET_PUBLIC_KEY=GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
PLATFORM_FEE_PERCENT=0
PLATFORM_WALLET_PUBLIC_KEY=GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

# ── Fee-bump sponsorship (#30) ────────────────────────────────────────────────
# The platform can pay a user's Stellar network fee by wrapping their signed
# transaction in a fee-bump. A user holding USDC but ~no XLM can then transact.
# All of these are OPTIONAL: with FEE_SPONSOR_ENABLED unset/false (the default)
# the feature is completely inert and the payment/donation flow is unchanged.
#
# Master switch. Leave false to disable sponsorship entirely.
FEE_SPONSOR_ENABLED=false
# DEDICATED fee-source secret (S…) for an account holding a small XLM float used
# ONLY to pay network fees. It MUST NOT be the donation or platform receiving
# wallet, and can never move user funds — it only signs the fee-bump wrapper.
# Required (and validated at boot) when FEE_SPONSOR_ENABLED=true. Never commit a
# real secret; keep it out of source control like the donation secret.
FEE_SPONSOR_SECRET=
# Per-transaction fee ceiling in stroops (default 1000000 = 0.1 XLM).
FEE_SPONSOR_MAX_FEE_STROOPS=1000000
# Total XLM-fee spend allowed per UTC day, in stroops (default 100000000 = 10 XLM).
FEE_SPONSOR_DAILY_CAP_STROOPS=100000000
# Max sponsored transactions per user per UTC day (default 10).
FEE_SPONSOR_PER_USER_DAILY_LIMIT=10

# SEP-1 stellar.toml (/.well-known/stellar.toml) — optional, omitted when blank
STELLAR_PLATFORM_PUBLIC_KEY=
ORG_NAME=
Expand Down Expand Up @@ -84,6 +118,31 @@ GIVING_ESCROW_CONTRACT_ID=
ACCESS_TOKEN_TTL=15m
REFRESH_TOKEN_TTL=30d

# ── Authentication abuse hardening (issue #89) ──────────────────────────────
# Progressive per-account login lockout. After LOGIN_MAX_ATTEMPTS consecutive
# failures the account is locked for an escalating backoff (base * 2^(excess)),
# capped at LOGIN_LOCKOUT_MAX_MS. Clears automatically on success/backoff.
LOGIN_MAX_ATTEMPTS=5
LOGIN_LOCKOUT_BASE_MS=60000
LOGIN_LOCKOUT_MAX_MS=86400000

# Breached-password check (HaveIBeenPwned range API). Only the 5-char SHA-1
# prefix is sent; fails OPEN on outage so signups/resets never break.
# HIBP_RANGE_URL=https://api.pwnedpasswords.com/range/
# HIBP_TIMEOUT_MS=2000

# Per-email signup / verification-resend throttle (survives IP rotation).
RATE_LIMIT_EMAIL_AUTH_MAX=20
RATE_LIMIT_EMAIL_AUTH_WINDOW_MS=900000
# RATE_LIMIT_EMAIL_AUTH_DISABLE=true

# Optional captcha gate for /register and /resend-verification. No-op when
# unset; fails OPEN on provider outage. Supports hCaptcha (default) and
# Google reCAPTCHA v2/v3 via CAPTCHA_VERIFY_URL override.
# CAPTCHA_SECRET_KEY=
# CAPTCHA_VERIFY_URL=https://hcaptcha.com/siteverify
# CAPTCHA_TIMEOUT_MS=5000

# Redis Configuration (optional - app works without Redis but with reduced performance)
# Option 1: Use REDIS_URL for full connection string (recommended for cloud services)
# REDIS_URL=redis://username:password@host:port
Expand All @@ -106,3 +165,31 @@ JOBS_ENABLED=true
QUEUE_DRIVER=mongo
JOBS_DASHBOARD_TOKEN=replace_with_a_long_random_token

# Service-to-service auth for the AI service (dnb-ai). A JSON array of signed,
# scoped, rotatable keys keyed by `kid`. REQUIRED in production (boot fails
# fast if missing); optional in dev/test. Keep >=1 entry active; to rotate,
# add a new active kid, deploy, switch dnb-ai over, then set the old kid
# "active": false. See docs/service-to-service-auth.md.
# AI_SERVICE_KEYS=[{"kid":"k1","secret":"replace_with_a_long_random_secret","scopes":["ai:read-content"],"active":true}]


# ── Outbound webhooks (issue #45) ────────────────────────────────────────────
# Webhook signing secrets are stored ENCRYPTED at rest (AES-256-GCM). This key
# derives the encryption key (SHA-256). REQUIRED in production (boot fails fast
# if missing); a fixed dev fallback is used in development/test. Rotating this
# invalidates all stored secrets — rotate per-endpoint secrets via the API.
# WEBHOOK_SECRET_ENCRYPTION_KEY=replace_with_a_long_random_value
# Enable the interval delivery worker on this process (like INGESTION_WORKER_ENABLED).
WEBHOOK_WORKER_ENABLED=false
# Delivery loop poll interval (ms).
WEBHOOK_POLL_INTERVAL_MS=5000
# Total attempts before a delivery is dead-lettered.
WEBHOOK_MAX_ATTEMPTS=6
# Consecutive dead deliveries that auto-disable an endpoint.
WEBHOOK_AUTO_DISABLE_THRESHOLD=5
# Max random jitter (ms) added to each backoff delay.
WEBHOOK_BACKOFF_JITTER_MS=30000
# Per-request HTTP timeout for delivery POSTs (ms).
WEBHOOK_HTTP_TIMEOUT_MS=10000
# Payload envelope version advertised to consumers.
WEBHOOK_API_VERSION=2025-01-01
47 changes: 42 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:

services:
mongodb:
image: mongo:7
image: mongo:6.0
ports:
- 27017:27017

Expand All @@ -24,12 +24,23 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
node-version: '22'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Wait for MongoDB
run: |
for i in $(seq 1 30); do
if nc -z localhost 27017 2>/dev/null || (exec 6<>/dev/tcp/localhost/27017) 2>/dev/null; then
echo "MongoDB is listening on port 27017"
break
fi
echo "Waiting for MongoDB..."
sleep 1
done

- name: Run tests
run: npm test
env:
Expand All @@ -41,16 +52,19 @@ jobs:
CLOUDINARY_CLOUD_NAME: ci_test_cloud
CLOUDINARY_API_KEY: ci_test_key
CLOUDINARY_API_SECRET: ci_test_secret_that_should_not_leak

build:
name: Syntax and Boot Check
runs-on: ubuntu-latest

services:
mongodb:
image: mongo:7
image: mongo:6.0
ports:
- 27017:27017
redis:
image: redis:7
ports:
- 6379:6379

steps:
- name: Checkout code
Expand All @@ -59,12 +73,34 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
node-version: '22'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Wait for MongoDB
run: |
for i in $(seq 1 30); do
if nc -z localhost 27017 2>/dev/null || (exec 6<>/dev/tcp/localhost/27017) 2>/dev/null; then
echo "MongoDB is listening on port 27017"
break
fi
echo "Waiting for MongoDB..."
sleep 1
done

- name: Wait for Redis
run: |
for i in $(seq 1 30); do
if nc -z localhost 6379 2>/dev/null || (exec 6<>/dev/tcp/localhost/6379) 2>/dev/null; then
echo "Redis is listening on port 6379"
break
fi
echo "Waiting for Redis..."
sleep 1
done

- name: Check syntax of all source files
run: |
find . -name "*.js" -not -path "./node_modules/*" -print0 \
Expand Down Expand Up @@ -94,3 +130,4 @@ jobs:
CLOUDINARY_CLOUD_NAME: ci_test_cloud
CLOUDINARY_API_KEY: ci_test_key
CLOUDINARY_API_SECRET: ci_test_secret_that_should_not_leak
REDIS_URL: redis://localhost:6379
37 changes: 37 additions & 0 deletions .github/workflows/contracts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Contracts CI

on:
pull_request:
branches: [main, dev]
paths:
- "contracts/**"
- ".github/workflows/contracts.yml"
push:
branches: [main, dev]
paths:
- "contracts/**"
- ".github/workflows/contracts.yml"

jobs:
rust:
name: Rust contract checks
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32v1-none
components: rustfmt, clippy

- name: Check formatting
run: cargo fmt --manifest-path contracts/Cargo.toml -- --check

- name: Run clippy
run: cargo clippy --manifest-path contracts/Cargo.toml --all-targets -- -D warnings

- name: Run contract tests
run: cargo test --manifest-path contracts/Cargo.toml
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ The platform is composed of three services:
- 🎓 **Course Management** — create, enroll, review, and track courses
- 📚 **Digital Library** — upload, purchase, and read Islamic books
- ⭐ **Stellar Payments** — USDC payment initialize → sign → submit → on-chain verify flow
- ⛽ **Fee Sponsorship** — optional platform-paid network fees via fee-bump, with a structural whitelist and spend caps ([docs](docs/fee-sponsorship.md))
- 👛 **Wallet Management** — connect Freighter, xBull, or Albedo; balance and trustline checks
- 💬 **Real-time** — Socket.io messaging and notifications
- ☁️ **Media** — Cloudinary uploads for avatars, covers, books, and reels
Expand Down Expand Up @@ -76,12 +77,14 @@ The API runs at `http://localhost:5000`.
| `PORT` | Server port (default `5000`) |
| `MONGO_URI` | MongoDB connection string |
| `JWT_SECRET` | Secret for signing tokens (32+ chars) |
| `STELLAR_NETWORK` | `testnet` or `mainnet` |
| `STELLAR_NETWORK` | `testnet` or `mainnet` (`public` accepted; validated at boot) |
| — | **Switching to mainnet? See [docs/MAINNET.md](docs/MAINNET.md)** — env changes, creator trustlines, smoke-test checklist |
| `CLOUDINARY_*` | Cloudinary credentials for media uploads |
| `QUEUE_DRIVER` | `mongo` (durable production default) or `inline` (tests/CI) |
| `JOBS_ENABLED` | Start background workers; defaults to `true` |
| `JOBS_DASHBOARD_TOKEN` | Bearer token protecting `/admin/jobs` |
| `STELLAR_PLATFORM_PUBLIC_KEY` | Public key published in `stellar.toml` `ACCOUNTS[]` |
| `FEE_SPONSOR_ENABLED` | Turn on platform-paid network fees (fee-bump). Off by default; when on, `FEE_SPONSOR_SECRET` is validated at boot ([docs](docs/fee-sponsorship.md)) |

See `.env.example` for the full list.

Expand Down Expand Up @@ -142,3 +145,6 @@ Read **[CONTRIBUTING.md](CONTRIBUTING.md)** for the full workflow, coding standa
- 🌐 Website: [dnb-frontend.vercel.app](https://dnb-frontend.vercel.app)
- 🐦 X/Twitter: [@deen_bridge](https://x.com/deen_bridge)
- 🏢 Organization: [github.com/Deen-Bridge](https://github.com/Deen-Bridge)
# Course categories

Seed the curated Islamic-discipline taxonomy with `npm run seed:categories`. Existing free-text course and book categories can be linked without removing their legacy string values by running `npm run migrate:categories`. Both commands are idempotent and require `MONGO_URI`.
Loading