Betna — family hub (working copy of bet-borashid)
  • TypeScript 95.9%
  • CSS 3.5%
  • JavaScript 0.2%
  • Shell 0.2%
  • Dockerfile 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
mrn 435ebcabdd
All checks were successful
CI / verify (push) Successful in 2m4s
v0.16.1: Fix the notification bell tap target in the top bar
2026-10-25 23:09:03 +03:00
.cursor/rules Split polls and draws permissions and simplify bottom nav to home, settings, and logout. 2026-07-10 01:45:20 +03:00
.gitea/workflows v0.15.1: Use the PostgreSQL service hostname in CI 2026-10-25 20:43:16 +03:00
deploy/traefik Keep Arabic only in i18n messages and move config to English. 2026-07-09 23:54:47 +03:00
e2e v0.16.1: Fix the notification bell tap target in the top bar 2026-10-25 23:09:03 +03:00
prisma v0.16.0: Add targeted in-app notifications and Web Push 2026-10-25 22:57:36 +03:00
public Rebrand the PWA as بيتنا with a house app icon. 2026-07-11 00:35:47 +03:00
scripts Fix image uploads failing in Docker by correcting upload directory permissions. 2026-07-11 19:30:34 +03:00
src v0.16.1: Fix the notification bell tap target in the top bar 2026-10-25 23:09:03 +03:00
tests v0.16.0: Add targeted in-app notifications and Web Push 2026-10-25 22:57:36 +03:00
.dockerignore v0.1.1: Docker Compose كامل بدون reverse proxy مع مثال Traefik dynamic 2026-07-09 20:07:30 +03:00
.env.example v0.16.0: Add targeted in-app notifications and Web Push 2026-10-25 22:57:36 +03:00
.gitignore v0.16.0: Add targeted in-app notifications and Web Push 2026-10-25 22:57:36 +03:00
.nvmrc Upgrade to Node 24, Next.js proxy, and latest stable toolchain. 2026-07-10 01:07:28 +03:00
AGENTS.md v0.1.0: Initial implementation of بيت بوراشد PWA 2026-07-09 20:00:43 +03:00
CLAUDE.md v0.1.0: Initial implementation of بيت بوراشد PWA 2026-07-09 20:00:43 +03:00
docker-compose.dev.yml Keep Arabic only in i18n messages and move config to English. 2026-07-09 23:54:47 +03:00
docker-compose.yml v0.16.0: Add targeted in-app notifications and Web Push 2026-10-25 22:57:36 +03:00
Dockerfile Fix image uploads failing in Docker by correcting upload directory permissions. 2026-07-11 19:30:34 +03:00
eslint.config.mjs v0.15.0: Security hardening, payment voiding, login rate limiting, docs, and E2E CI 2026-10-25 20:41:19 +03:00
next.config.ts v0.16.0: Add targeted in-app notifications and Web Push 2026-10-25 22:57:36 +03:00
package-lock.json v0.16.0: Add targeted in-app notifications and Web Push 2026-10-25 22:57:36 +03:00
package.json v0.16.1: Fix the notification bell tap target in the top bar 2026-10-25 23:09:03 +03:00
playwright.config.ts v0.5.1: اختبارات آلية وCI وتحسينات القرعة 2026-07-09 22:40:57 +03:00
postcss.config.mjs v0.1.0: Initial implementation of بيت بوراشد PWA 2026-07-09 20:00:43 +03:00
prisma.config.ts v0.1.0: Initial implementation of بيت بوراشد PWA 2026-07-09 20:00:43 +03:00
README.md v0.16.0: Add targeted in-app notifications and Web Push 2026-10-25 22:57:36 +03:00
tsconfig.json v0.1.0: Initial implementation of بيت بوراشد PWA 2026-07-09 20:00:43 +03:00
VERSION v0.16.1: Fix the notification bell tap target in the top bar 2026-10-25 23:09:03 +03:00
vitest.config.ts v0.5.1: اختبارات آلية وCI وتحسينات القرعة 2026-07-09 22:40:57 +03:00

Betna (بيتنا)

Betna is a family hub PWA: one installable app that hosts a set of small family programs (polls, draws, quizzes, shopping lists, wishes, business ideas, places, and household payroll). It is Arabic-first, mobile-first, and designed for a single family on a self-hosted server.

Programs

Program What it does
Maashat (معاشات) Tracks domestic workers, salary advances/gifts, and monthly salary payments.
Polls (تصويت) Participant-only polls with live results, close/reopen, and likes.
Draws (قرعة) Everyone adds options, marks ready, and the winner is drawn automatically or manually.
Contests (خمّن) Open-text or multiple-choice family quizzes with secret answers and winner reveal.
Jamiya (جمعية) Shared grocery / Shein-Temu / Amazon shopping lists with per-item images and purchased checks.
Fikhatri (في خاطري) Family wish board grouped by food, outings, items, and events.
Biz Ideas (أفكار بزنس) Family business idea board with threaded replies.
Places (أماكن) Collaborative list of places with member-defined categories, photos, logos, and map pins.

Cross-cutting features: per-app permissions, participant invitations, unseen badges, likes, a home activity feed, in-app notifications with per-app mutes and quiet hours, Web Push for installed devices, soft archive/restore, optional image uploads, PWA install/offline shell, and optional legacy ntfy alerts.

Stack

  • Next.js 16 (App Router, webpack build) + React 19
  • TypeScript (strict) + Zod
  • PostgreSQL 16 + Prisma 7 (@prisma/adapter-pg)
  • Auth.js / NextAuth 5 (credentials, JWT session, bcrypt password hashes)
  • Tailwind CSS 4 + a custom design system in src/app/globals.css
  • Serwist service worker + web app manifest
  • sharp (WebP conversion, HEIC support) for uploads
  • Leaflet + OpenStreetMap for place locations
  • Web Push (VAPID, web-push) + an in-app notification center
  • ntfy (optional legacy mirror)
  • Docker multi-stage image (Node 24 alpine, Next standalone output)

Local development

Requirements: Node.js 24.18 (see .nvmrc), npm, and PostgreSQL 16 (or Docker).

npm install
cp .env.example .env
# edit .env, then create the schema:
npx prisma migrate deploy
npx prisma db seed
npm run dev

The first visit opens /setup, where you create the initial admin account. The seed only registers the program catalog; it does not create users.

With Docker, start just the database and run the app on the host:

docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d db
npm run dev

Environment variables

Variable Purpose
DATABASE_URL PostgreSQL connection string used by Prisma.
POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB Database container credentials.
AUTH_SECRET Required secret used to sign session JWTs.
NEXTAUTH_URL Public app URL; must match the URL users open (HTTPS in production).
APP_HOST / APP_PORT Host port binding for the app container (default 0.0.0.0:3000).
UPLOAD_DIR Directory for processed uploads (default /app/data/uploads in Docker).
NTFY_ENABLED / NTFY_URL / NTFY_TOPIC / NTFY_TOKEN Optional legacy ntfy notifications.
VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECT Web Push keys; generate with npx web-push generate-vapid-keys. Leave empty to disable push.
NOTIFICATIONS_NTFY_MIRROR Keep mirroring events to the legacy ntfy topic (true by default).
SEED_ADMIN_ALLOW / SEED_ADMIN_USERNAME / SEED_ADMIN_PASSWORD Optional test-only admin seeding; requires SEED_ADMIN_ALLOW=1.

.env* files are git-ignored; keep production secrets on the server only.

Scripts

Script Purpose
npm run dev Development server (webpack).
npm run build / npm start Production build and standalone server.
npm run lint ESLint.
npm run typecheck TypeScript check.
npm run test Vitest unit tests.
npm run test:e2e Playwright end-to-end tests (expects a running seed DB).
npm run verify Lint + typecheck + production build.
npm run db:migrate / db:deploy / db:seed Prisma migration and seed commands.
./scripts/release.sh <major|minor|patch> "message" Bump VERSION + package.json, commit, and push.

Architecture

src/app/            Pages (server components) and API route handlers
src/components/     UI shell, feature components, and client widgets
src/lib/            Domain logic grouped by feature + shared auth/db/upload helpers
src/messages/ar.ts  All Arabic UI strings (the only place Arabic belongs)
prisma/             Schema, migrations, and seed catalog
tests/              Vitest unit tests
e2e/                Playwright smoke/auth tests
  • Pages are server components that render client widgets; data mutations go through /api/* route handlers.
  • Every API route authenticates and authorizes independently. src/proxy.ts only guards page navigation (/, /setup, /apps, /admin).
  • Permissions are per-program (UserAppPermission). Admins bypass program checks. Invited participants are required for polls, draws, contests, and jamiya lists.
  • Content uses soft delete (deletedAt) and archive (archivedAt). Inactive child content auto-archives lazily after 7 days; fulfilled wishes after 3 days.
  • Errors are stable English codes (src/lib/error-codes.ts) translated to Arabic by tError() in the UI.
  • Uploads are validated, converted to WebP (max 1200px, q82), and served only to users with access to the owning program.

Notifications

Every new wish, idea, reply, shopping list/item, poll, draw, contest, place, and like creates targeted in-app notifications for the people who should care (app members, list participants, activity participants, or the content owner). The bell in the top bar shows the unread count, and /apps/notifications keeps the history.

Delivery channels:

  • In-app is always on; per-app mutes hide both the in-app row and push.
  • Web Push goes to devices that opted in from حسابي → إعدادات الإشعارات. It requires the VAPID_* variables and HTTPS. Quiet hours suppress push but still keep the in-app row.
  • ntfy is mirrored for now (NOTIFICATIONS_NTFY_MIRROR=true). Set it to false once the family has enabled Web Push; its per-app mutes do not apply to ntfy because the legacy channel uses one shared topic.

Generate keys once with npx web-push generate-vapid-keys and keep the private key on the server only.

Database changes

npx prisma migrate dev --name <change_name>

Migrations run automatically on container start (scripts/docker-entrypoint.sh). Do not edit applied migrations; add a new one instead.

Deployment

cp .env.example .env   # fill in real secrets
docker compose up -d --build

The compose stack runs PostgreSQL and the app, with named volumes for the database and uploads. The app listens on APP_PORT (3000 by default) and is expected to sit behind a TLS reverse proxy (for example Traefik; see deploy/traefik/). Set NEXTAUTH_URL to the public HTTPS URL; secure session cookies will not work over plain HTTP.

Back up the postgres_data and uploads_data volumes together.

Testing and CI

  • npm run test runs the Vitest suite (draw logic, contest serialization, permissions, rate limiting, ntfy payloads, upload detection, relative time).
  • npm run test:e2e runs Playwright against a built app and a seeded database.
  • .gitea/workflows/ci.yml runs install, Prisma generate, migrations, unit tests, npm run verify, and Playwright on every push/PR to main.

For local E2E, create a test admin first:

SEED_ADMIN_ALLOW=1 SEED_ADMIN_USERNAME=e2e SEED_ADMIN_PASSWORD=e2e-password npx prisma db seed
E2E_ADMIN_USERNAME=e2e E2E_ADMIN_PASSWORD=e2e-password npm run test:e2e

Versioning

VERSION is the source of truth and must match package.json. The running version is shown on the home hero and the profile page, and exposed at /api/version for monitoring. Every shipped change goes through scripts/release.sh, which commits as vX.Y.Z: <description> and pushes to main. CI runs on the resulting commit.

License

Private family project. All rights reserved.