{"id":24265945,"url":"https://github.com/aliosm/faktubuh","last_synced_at":"2026-04-04T21:31:27.983Z","repository":{"id":271851672,"uuid":"914727177","full_name":"AliOsm/faktubuh","owner":"AliOsm","description":".يَا أَيُّهَا الَّذِينَ آمَنُوا إِذَا تَدَايَنتُم بِدَيْنٍ إِلَىٰ أَجَلٍ مُّسَمًّى فَاكْتُبُوه","archived":false,"fork":false,"pushed_at":"2026-02-12T07:09:47.000Z","size":351,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-12T15:21:43.750Z","etag":null,"topics":["devcontainers","inertiajs","islam","kamal","pwa","react","ruby-on-rails","shadcn-ui","tailwindcss","vite"],"latest_commit_sha":null,"homepage":"https://faktubuh.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AliOsm.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-01-10T07:22:40.000Z","updated_at":"2026-02-12T07:09:50.000Z","dependencies_parsed_at":"2025-01-10T10:35:21.221Z","dependency_job_id":null,"html_url":"https://github.com/AliOsm/faktubuh","commit_stats":null,"previous_names":["aliosm/faktubuh"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/AliOsm/faktubuh","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AliOsm%2Ffaktubuh","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AliOsm%2Ffaktubuh/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AliOsm%2Ffaktubuh/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AliOsm%2Ffaktubuh/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AliOsm","download_url":"https://codeload.github.com/AliOsm/faktubuh/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AliOsm%2Ffaktubuh/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31415110,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T20:09:54.854Z","status":"ssl_error","status_checked_at":"2026-04-04T20:09:44.350Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["devcontainers","inertiajs","islam","kamal","pwa","react","ruby-on-rails","shadcn-ui","tailwindcss","vite"],"created_at":"2025-01-15T10:57:21.558Z","updated_at":"2026-04-04T21:31:27.968Z","avatar_url":"https://github.com/AliOsm.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Faktubuh (فَاكْتُبُوهُ)\n\nA debt management application inspired by Ayat al-Dayn (the Verse of Debt) from the Quran. The name means \"write it down\" in Arabic, reflecting the Islamic directive to document financial obligations.\n\nUsers can record debts, track payments, schedule installments, invite witnesses, and manage settlements — all with full Arabic (RTL) and English (LTR) support.\n\n## Tech Stack\n\n| Layer | Technology |\n|-------|-----------|\n| Backend | Ruby 3.4.7, Rails 8.1 |\n| Frontend | React 19, TypeScript, Vite 7 |\n| Bridge | Inertia.js (server-driven SPA) |\n| Database | PostgreSQL 18 |\n| Styling | Tailwind CSS v4, shadcn/ui |\n| Auth | Devise + Google OAuth |\n| Jobs | GoodJob |\n| i18n | react-i18next + Rails I18n (Arabic default) |\n| Deployment | Kamal, Docker, Thruster |\n\n## Core Features\n\n**Debt Management**\n- **Mutual debts** between two platform users (requires counterparty confirmation)\n- **Personal debts** with non-users (tracked by name, auto-active)\n- Upgrade personal debts to mutual when the counterparty joins\n\n**Payments \u0026 Installments**\n- Flexible installment schedules: lump sum, monthly, bi-weekly, quarterly, yearly\n- Payment submission with lender approval flow (mutual) or auto-approval (personal)\n- Auto-settlement when remaining balance reaches zero\n\n**Witnesses**\n- Up to 2 witnesses per debt, invited by personal ID\n- Witness confirmation/decline flow\n\n**Notifications**\n- In-app notifications for all debt lifecycle events\n- Email notifications respecting user locale\n\n**Background Jobs**\n- `OverdueDetectionJob` — daily midnight check marking overdue installments (UTC)\n- `InstallmentReminderJob` — daily 8AM reminders (3 days, 1 day, due today) (UTC)\n\n## Project Structure\n\n```\napp/\n  controllers/         # Rails controllers (Debts, Payments, Witnesses, etc.)\n  models/              # ActiveRecord models with business logic\n  mailers/             # DebtMailer for all email notifications\n  services/            # NotificationService, InstallmentScheduleGenerator\n  jobs/                # GoodJob background jobs\n  frontend/\n    components/        # Shared React components (shadcn/ui based)\n    layouts/           # PersistentLayout (all pages), AppLayout (authenticated)\n    pages/             # Inertia pages (dashboard, debts, auth, profile, etc.)\n    i18n/locales/      # ar.json, en.json frontend translations\n    lib/               # Utilities, types, hooks\nconfig/\n  locales/             # ar.yml, en.yml backend translations\n  routes.rb            # All application routes\ndb/\n  schema.rb            # Database schema\n  migrate/             # Migrations\n```\n\n### Layout System\n\n- `PersistentLayout` wraps every page (theme, flash toasts, direction provider)\n- `AppLayout` wraps authenticated pages (navbar, navigation)\n- Pages declare layouts via: `Page.layout = [AppLayout]`\n- `PersistentLayout` is auto-prepended in `inertia.tsx`\n\n## Local Development\n\n### Prerequisites\n\n- Ruby 3.4.7, Node 25.2.1, Yarn 4.12.0 (use [mise](https://mise.jdx.dev/) or `.tool-versions`)\n- Docker (for PostgreSQL)\n\n### Setup\n\n```bash\n# Start PostgreSQL\ndocker compose -f dev-docker-compose.yml up -d\n\n# Install dependencies\nbundle install\nyarn install\n\n# Setup database\nbin/rails db:create db:migrate db:seed\n\n# Start dev servers (Rails + Vite)\nbin/dev\n```\n\nThe app runs at `http://localhost:3000`. Vite dev server handles frontend HMR.\n\n### Environment\n\nDev database config uses `postgres:postgres@localhost:5432` by default (see `config/database.yml`). No `.env` file needed for local development.\n\n### Google OAuth (Optional)\n\nGoogle OAuth is configured via Rails credentials (not hardcoded in `config/initializers/devise.rb`).\n\n- Set `google.client_id` and `google.client_secret` via `bin/rails credentials:edit`\n\nRedirect URIs to add in Google Cloud Console:\n- `http://localhost:3000/users/auth/google_oauth2/callback`\n- `https://\u003cyour-domain\u003e/users/auth/google_oauth2/callback`\n\n### Running Tests\n\n```bash\n# View-rendering tests expect a built Vite manifest in test mode\nbin/rails vite:build RAILS_ENV=test\n\n# Recommended quick run (no coverage gating, serial to avoid Vite manifest flakiness)\nNO_COVERAGE=1 PARALLEL_WORKERS=1 bin/rails test\n```\n\nCoverage thresholds are configured in `test/test_helper.rb` (95% line / 90% branch). Omit `NO_COVERAGE=1` to enforce.\n\n### Linting\n\n```bash\nbundle exec rubocop          # Ruby\nyarn check                   # TypeScript\nyarn lint                    # JavaScript/TypeScript (Oxlint)\nyarn format:check            # JavaScript/TypeScript formatting (Oxfmt)\nbundle exec brakeman         # Security scan\nbundle exec bundler-audit    # Dependency audit\n```\n\n## Deployment\n\nDeployed via [Kamal](https://kamal-deploy.org/) to a single server with Docker.\n\n```bash\nkamal setup    # First-time server setup\nkamal deploy   # Deploy updates\n```\n\n### Required Secrets\n\nSet these as Kamal secrets (`.kamal/secrets`):\n\n- `RAILS_MASTER_KEY` — Rails credentials decryption key\n- `POSTGRES_PASSWORD` — Production database password\n- `KAMAL_REGISTRY_PASSWORD` — GitHub Container Registry token\n\n### Required Environment (Kamal)\n\n- `SERVER_IP` — used by `config/deploy.yml`\n- `APP_HOST` — used for mailer links (defaults to `faktubuh.com` in production)\n- `MAILER_FROM` — optional (defaults to `faktubuh@gmail.com`)\n\n### Email (Required)\n\nDevise confirmation/password reset/unlock emails are enqueued with `deliver_later`, so a GoodJob worker must be running.\n\n- Gmail SMTP: set `google.gmail_application_password` in Rails credentials\n- Other SMTP: configure `config.action_mailer.smtp_settings` in `config/environments/production.rb`\n\n### Infrastructure\n\n- **Domain**: faktubuh.com (SSL via Kamal proxy)\n- **Registry**: ghcr.io/aliosm/faktubuh\n- **Server roles**: `web` (Rails + Thruster) and `worker` (GoodJob)\n- **Accessory**: PostgreSQL 18 container managed by Kamal\n- **Storage**: Persistent volume at `/rails/storage`\n\n### Kamal Aliases\n\n```bash\nkamal console   # Rails console\nkamal shell     # Bash shell\nkamal logs      # Tail application logs\nkamal dbc       # Database console\n```\n\n## Key Design Decisions\n\n- **Inertia.js** bridges Rails and React without a separate API layer — controllers render React pages directly\n- **React Compiler** is enabled for automatic memoization\n- **RTL-first** design with `DirectionProvider` for Radix UI components\n- **Pessimistic locking** on payment creation prevents race conditions\n- **Rate limiting** on debt (20/hr) and payment (10/hr) creation\n- **Personal IDs** (3-12 char alphanumeric; generated as 6) are used for user lookup instead of exposing emails\n- **User deletion** is blocked if the user has any associated debts, payments, or witness records\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faliosm%2Ffaktubuh","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faliosm%2Ffaktubuh","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faliosm%2Ffaktubuh/lists"}