diff --git a/README.md b/README.md new file mode 100644 index 0000000..cfc6dad --- /dev/null +++ b/README.md @@ -0,0 +1,456 @@ +# Rabbi Gerzi + +Rabbi Gerzi is the public website and Torah media application for Rabbi +Yehoshua Gerzi. The site presents Rabbi Gerzi's work, projects, and Torah +media, with an authenticated admin area for managing media sets, nested +elements, rich text, YouTube links, icons, and PDF uploads. + +The application is split into: + +- `backend/`: Laravel 13, PHP 8.3, PostgreSQL, PHPUnit. +- `frontend/rabbi_gerzi/`: Vue 3, Vite, TypeScript, Pinia, Cypress. +- `nix/`: Nix packages, checks, upload limits, and the NixOS service module. + +Production assumptions: + +- Website: `https://rabbigerzi.com` +- API: `https://api.rabbigerzi.com` + +All local and deployment workflows are Nix-first. Run commands from +`nix develop`, or use direnv with the checked-in `.envrc`. + +## How the Application Works + +### Content and Public Browsing + +Torah media is organized into sets. Each set has a name, description, icon, +and one root element. Elements form an ordered tree: the root can have child +elements, and each child can have further children. + +Visitors browse the available sets at `/media`. Opening a set loads its root +element, where the visitor can move through parent, child, and sibling links. +An element can present an icon, formatted rich text, an embedded YouTube video, +and short or long PDFs. The PDF viewer supports page navigation, zoom, search, +download, printing, and opening the source document in a new tab. + +### Admin Editing + +Administrators sign in at `/login`. An authenticated session reveals the +management controls on `/media` and protects all content-changing API calls. + +From the media page, an administrator can: + +- Create a set with a name, description, and icon. This also creates its root + element and opens that element in edit mode. +- Edit an existing set's card details or replace its icon. +- Delete a set together with its complete element tree and managed uploads. + +The `Edit Element` action on a public element opens its admin editor. The +editor supports: + +- Editing the title, description, rich text, and YouTube URL. Rich text + includes headings, inline formatting, alignment, lists, links, and tables. +- Uploading or removing an icon, a short PDF, and a long PDF. +- Adding child elements, moving direct children up or down, and opening a + child directly in edit mode. +- Removing a child together with all of its descendants and managed uploads. + +Title, description, rich text, and YouTube changes are written by the `Save` +action. Icon and PDF changes are uploaded or removed immediately. The +`View Element` action returns to the public presentation, and `Logout` ends +the admin session. Development login credentials are listed under +[Local Development](#local-development). + +## Required Secrets and Environment + +Do not commit real secret values. Local examples can live in ignored `.env` +files, and production secrets should live outside the Nix store. + +### Required Production Secrets + +The NixOS module requires +`services.rabbi-gerzi.backend.environmentFile`. That file must contain: + +```dotenv +APP_KEY=base64:replace-with-laravel-app-key +RABBI_GERZI_INITIAL_ADMIN_EMAIL=admin@example.com +RABBI_GERZI_INITIAL_ADMIN_PASSWORD=replace-with-a-long-random-password +``` + +- `APP_KEY` encrypts and signs Laravel application data. +- `RABBI_GERZI_INITIAL_ADMIN_EMAIL` is the first admin login email. +- `RABBI_GERZI_INITIAL_ADMIN_PASSWORD` is used only to create that admin if + it does not already exist. + +Generate an app key inside the dev shell: + +```sh +cd backend +php artisan key:generate --show +``` + +### Public Frontend Config + +The frontend build needs the public API origin: + +```dotenv +VITE_API_BASE_URL=https://api.rabbigerzi.com +``` + +For NixOS deployment this is handled by +`services.rabbi-gerzi.frontend.apiBaseUrl`, which defaults to the backend +public URL. + +### NixOS Runtime Config + +The NixOS module supplies these non-secret runtime values: + +| Setting | Production value | +|---|---| +| `APP_ENV` | `production` | +| `APP_DEBUG` | `false` | +| `APP_URL` | `https://api.rabbigerzi.com` | +| `CORS_ALLOWED_ORIGINS` | `https://rabbigerzi.com` by default | +| `DB_CONNECTION` | `pgsql` | +| `DB_HOST` | `/run/postgresql` | +| `DB_DATABASE` | `rabbi-gerzi` by default | +| `DB_USERNAME` | `rabbi-gerzi` by default | +| `FILESYSTEM_DISK` | `public` | +| `LARAVEL_STORAGE_PATH` | `/var/lib/rabbi-gerzi/storage` by default | +| `SESSION_DRIVER` | `database` | +| `SESSION_DOMAIN` | empty by default, meaning host-only cookies | +| `SESSION_SAME_SITE` | `none` by default | +| `SESSION_SECURE_COOKIE` | `true` by default | +| `CACHE_STORE` | `file` | +| `QUEUE_CONNECTION` | `sync` | +| `MAIL_MAILER` | `log` | + +The module also manages PostgreSQL, php-fpm, nginx, writable storage/cache +directories, and the 6 MB upload limit for nginx and PHP. + +### Local Environment Files + +Backend local configuration starts from: + +```sh +cd backend +cp .env.example .env +php artisan key:generate +``` + +The checked-in example already targets the Nix dev PostgreSQL socket through +`PGHOST`, and uses the `postgres` local database/user. + +Frontend local configuration should point Vite at the local backend: + +```sh +cd frontend/rabbi_gerzi +printf '%s\n' 'VITE_API_BASE_URL=http://127.0.0.1:8000' > .env.local +``` + +## Local Development + +Enter the Nix dev shell before running local development commands: + +```sh +nix develop +``` + +If direnv is installed: + +```sh +direnv allow +``` + +The dev shell provides PHP, Composer, Node, PostgreSQL, Cypress, +process-compose, TypeScript tooling, formatters, linters, and Nix formatters. +It also initializes a local PostgreSQL cluster in `.postgres` when needed. + +Install dependencies: + +```sh +cd backend +composer install + +cd ../frontend/rabbi_gerzi +npm install --legacy-peer-deps +``` + +Create local environment files: + +```sh +cd ../../backend +cp .env.example .env +php artisan key:generate + +cd ../frontend/rabbi_gerzi +printf '%s\n' 'VITE_API_BASE_URL=http://127.0.0.1:8000' > .env.local +``` + +Start the local services from the repo root: + +```sh +process-compose up +``` + +This starts: + +- PostgreSQL on the local Unix socket under `.postgres`. +- Laravel at `http://127.0.0.1:8000`. +- Vite at `http://localhost:5173`. + +In another dev-shell terminal, prepare the database: + +```sh +cd backend +php artisan migrate:fresh --seed +``` + +The seed admin login is: + +```text +admin@rabbigerzi.test +password123!@# +``` + +## Testing + +Run local tests from `nix develop`. To run the entire local test suite, keep +the app stack running in one dev-shell terminal: + +```sh +nix develop +process-compose up +``` + +Then run backend, frontend, and Cypress checks from another dev-shell +terminal. `process-compose` provides the PostgreSQL database, Laravel backend, +and Vite frontend needed by the full suite. + +Backend tests live under `backend/tests`. + +- Unit tests exercise domain entities, use cases, middleware, controllers, and + fake repositories without requiring PostgreSQL. +- Feature tests exercise HTTP endpoints, Laravel routing, migrations, seed + data, and real framework integration. +- PHPUnit is configured to use an in-memory SQLite database for test runs. + +Run backend tests: + +```sh +cd backend +./vendor/bin/phpunit tests +``` + +Run backend style checks: + +```sh +cd backend +phpcs app tests routes database +``` + +Frontend quality checks live under `frontend/rabbi_gerzi`. + +- `npm run format` checks and formats Vue and TypeScript source with oxfmt. +- `npm run lint` runs oxlint and eslint. +- `npm run type-check` runs vue-tsc against the Vite application. + +Run frontend checks: + +```sh +cd frontend/rabbi_gerzi +npm run format +npm run lint +npm run type-check +``` + +Cypress E2E tests live under `frontend/rabbi_gerzi/cypress/e2e`. + +- Specs drive the UI through the Vite frontend at `http://localhost:5173`. +- The Laravel backend must be running at `http://127.0.0.1:8000`. +- The Cypress config creates a seeded PostgreSQL template database before the + run and resets the app database between specs with the `db:reset` task. +- Seeded media assets are copied into `backend/storage/app/public` for each + reset. + +Run E2E tests after `process-compose up` is healthy: + +```sh +cd frontend/rabbi_gerzi +npm run test:e2e +``` + +Open the Cypress runner: + +```sh +cd frontend/rabbi_gerzi +npm run test:e2e:open +``` + +## Verification + +Run the full local suite from a second dev-shell terminal while +`process-compose up` is running: + +```sh +cd backend +phpcs app tests routes database +./vendor/bin/phpunit tests + +cd ../frontend/rabbi_gerzi +npm run format +npm run lint +npm run type-check +npm run test:e2e +``` + +Run the full Nix package and module checks: + +```sh +nix flake check +``` + +## Deployment + +Deploy through the flake's NixOS module. The module builds the frontend with +`VITE_API_BASE_URL=https://api.rabbigerzi.com`, serves it from nginx, runs the +Laravel backend with php-fpm, provisions PostgreSQL, creates writable storage +and cache directories, runs migrations, and ensures the initial admin exists. + +Example host configuration: + +```nix +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + rabbi-gerzi.url = "git+ssh://git@example.com/rabbi-gerzi.git"; + + sops-nix = { + url = "github:Mic92/sops-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = + { + nixpkgs, + rabbi-gerzi, + sops-nix, + ... + }: + { + nixosConfigurations.rabbigerzi = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + rabbi-gerzi.nixosModules.default + sops-nix.nixosModules.sops + ( + { config, ... }: + let + appKey = config.sops.placeholder."app-key"; + initialAdminEmail = + config.sops.placeholder."admin-email"; + initialAdminPassword = + config.sops.placeholder."admin-password"; + in + { + sops = { + defaultSopsFile = ./secrets/rabbi-gerzi.yaml; + age.sshKeyPaths = [ + "/etc/ssh/ssh_host_ed25519_key" + ]; + + secrets = { + "app-key" = { }; + "admin-email" = { }; + "admin-password" = { }; + }; + + templates."rabbi-gerzi.env".content = '' + APP_KEY=${appKey} + RABBI_GERZI_INITIAL_ADMIN_EMAIL=${initialAdminEmail} + RABBI_GERZI_INITIAL_ADMIN_PASSWORD=${initialAdminPassword} + ''; + }; + + services.rabbi-gerzi = { + enable = true; + + frontend.hostName = "rabbigerzi.com"; + + backend = { + hostName = "api.rabbigerzi.com"; + environmentFile = + config.sops.templates."rabbi-gerzi.env".path; + }; + }; + } + ) + ]; + }; + }; +} +``` + +If the API origin must be set explicitly, use: + +```nix +services.rabbi-gerzi.frontend.apiBaseUrl = + "https://api.rabbigerzi.com"; +``` + +Configure `.sops.yaml` with age recipients for each administrator and the +server's SSH Ed25519 host key. Then create the encrypted secrets file: + +```sh +sops secrets/rabbi-gerzi.yaml +``` + +Add these values in the editor opened by `sops`: + +```yaml +app-key: base64:replace-with-laravel-app-key +admin-email: admin@example.com +admin-password: replace-with-a-long-random-password +``` + +Commit only the encrypted file written by `sops`. At activation time, +`sops-nix` decrypts these values and renders the runtime environment file +outside the Nix store. + +Then rebuild the host with the deployment method used by the server, for +example: + +```sh +sudo nixos-rebuild switch --flake .#rabbigerzi +``` + +During activation, `rabbi-gerzi-setup` runs: + +```sh +php artisan migrate --force +php artisan rabbi-gerzi:ensure-initial-admin +``` + +The initial admin command is idempotent. If the configured admin email already +exists, it leaves the user unchanged. + +## Useful Nix Outputs + +Build the frontend package: + +```sh +nix build .#frontend +``` + +Build the backend package: + +```sh +nix build .#backend +``` + +Evaluate all checks: + +```sh +nix flake check +```