All work
MS-01 · Client project · Saudi Arabia

Ghadaq Al‑Khayr

A private portal for the Al-Shabana family

A platform to document the family tree and manage memberships, join requests, messages and content. Visitors see a landing page only; members open the tree, documents and news; admins work with precise permissions.

Visit the site
Specifications
Front end
Next.js 16 · React 19 · TypeScript · Tailwind 4
Data
PostgreSQL 16, with optional Redis for rate limits
Files
Google Drive, with chunked uploads up to 250 MB
Sign-in
JWT session cookie, bcryptjs, instant session revocation
Operations
Docker behind Traefik and Cloudflare, daily backups
My role
The whole project, solo
~2,000family members in the tree
81API routes
14pages
12admin modules
4access levels

01The problem

A large family needs one trusted tree that members update themselves, through requests that admins review. It holds sensitive data, like phone and ID numbers, that only authorized people should see.

The app and its database are hosted inside Saudi Arabia.

02How it fits together

Every request passes the proxy first. Nothing reaches the data without a valid session and the right permission.

Members’ appArabic RTL PWAwith push notifications
ProxyCSRF and JWT checksbot blocking
81 API routesNext.js route handlers4 access levels~20 fine-grained permissionsaudit log with rollback
PostgreSQLmembers and relationships
Google Drivedocuments up to 250 MB
Web Pushmember notifications
Redisoptional

03Three engineering decisions

A

Generations across in-law fathers

Children of a husband from outside the family take their generation from their mother. A recursive PostgreSQL query computes it, and a path array stops cycles.

src/lib/db/generation.ts — excerpt
WITH RECURSIVE maternal_tree AS (
  SELECT child.id, mother.generation + 1 AS generation,
         ARRAY[mother.id, child.id]::text[] AS path
  FROM family_members child
  JOIN family_members father ON father.id = child.parent_id
  JOIN family_members mother ON mother.id = child.mother_id
  WHERE father.is_external = TRUE
    AND mother.is_external = FALSE
  UNION ALL …
B

Sensitive data by permission, not by role

Phone and ID numbers are never selected unless the user can edit members. Permissions are re-read from the database on every request, so revoking one applies at once. Documents stay private unless their owner shares them with the family.

src/app/api/family-data/route.ts — excerpt
const canSeeSensitive =
  await hasAdminPermission(session, 'members:update');

const columns = canSeeSensitive
  ? `${PUBLIC_COLUMNS}, ${ADMIN_ONLY_COLUMNS}`
  : PUBLIC_COLUMNS;
C

Full names rebuild themselves

When an ancestor’s name changes, the full lineage names, “X bin/bint Y”, are rebuilt for every descendant, up to 25 levels deep.

src/lib/members/lineage.ts — excerpt
const connector = member.gender === '⁨أنثى⁩' ? '⁨بنت⁩' : '⁨بن⁩';
return `${firstName} ${connector} ${fatherName}`;

04Security and operations

  • Checks in CInpm audit, gitleaks for secrets, OSV for vulnerabilities, then lint, type checks and the build.
  • BackupsA daily pg_dump to S3-compatible storage, with a restore test.
  • MonitoringA health check every 5 minutes, with alerts on Telegram.
  • Locked-down containerA multi-stage Docker image that runs as a non-root user.

05Known limits

L-01The app and database are in Saudi Arabia, but files sit on Google Drive. This is a documented, open decision: accept it formally, or move files to in-Kingdom storage.
L-02Unit tests are still thin: 23 cases in two files.

06My role

I built the whole project alone: the interface, the API, the database, permissions, deployment and operations. It serves a family tree of about 2,000 people.

The code lives under the client’s GitHub account, so commits appear under their name.

© 2026 · Cairo, Egypt