From 7a892fad4ea63ef2485c4b58eea311ff5ab8e308 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Mon, 10 Aug 2026 11:01:21 -0600 Subject: [PATCH] Add AGENTS.md --- AGENTS.md | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..b7a02f3 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,74 @@ +# AGENTS.md + +Akkounts is a Rails 8 monolith for managing Kosmos/LDAP user accounts. +It authenticates against an LDAP directory via Devise and integrates with +ejabberd, Discourse, Mastodon, remoteStorage, Nostr, LNDHub, and BTCPay. + +## Development environment + +Development runs in Docker Compose — run all commands against the `web` container. +Start services: `docker compose up` (web, ldap, redis, minio, liquor-cabinet, strfry). +The `web` service runs `bin/dev` (foreman: Puma + Tailwind CSS watcher) and embeds +Solid Queue workers (`SOLID_QUEUE_IN_PUMA=true`). + +First-time LDAP setup (after creating the 389ds backend once): +`docker compose exec ldap dsconf localhost backend create --suffix="dc=kosmos,dc=org" --be_name="dev"` +then `docker compose run web bin/rails ldap:setup`. + +## Common commands (prefix with `docker compose exec web`) + +- `bin/rspec` — run the test suite (CI runs `bundle exec rspec`) +- `bin/rails db:prepare` — create/migrate DB (also `db:setup`, `db:migrate`) +- `bin/rails ldap:setup` — reset LDAP dir and seed dev entries +- `bin/rails css:build` — build Tailwind CSS (auto-watched in dev) +- `bin/rails lndhub:generate_wallets`, `invitations:generate_for_all_users[N]` +- `bin/jobs` — Solid Queue CLI + +Tests use the test env explicitly: `docker compose exec -e RAILS_ENV=test web bin/rspec`. +There is no enforced linter — a `bin/rubocop` binstub exists but no `.rubocop.yml` +(rubocop is a transitive gem, not in the Gemfile); CI does not lint. + +## Stack & conventions + +- **Assets/views:** ERB + ViewComponent (`app/components/`), Tailwind built via Bun + + importmap (`config/importmap.rb`); pin JS deps with `bin/importmap pin --download`. +- **Auth:** Devise + `devise_ldap_authenticatable`; `authentication_keys = [:cn]`, + custom SSHA512 password builder, admin bind. Admin-only mounts (`/jobs` MissionControl, + `/flipper` Flipper UI) are gated by `authenticate :user, ->(u) { u.is_admin? }`. +- **Services:** `app/services/` uses `ApplicationService.call(**args)` with namespaced + `*ManagerService` bases (e.g. `UserManagerService`) and verb-named operations + (`UserManager::CreateAccount`, `NostrManager::PublishEvent`, `BtcpayManager::*`). +- **Models:** `User` is central; `Setting` uses rails-settings-cached with one concern per + service in `app/models/concerns/settings/`. LNDHub models connect to a separate DB + (`LndhubBase` `establish_connection :lndhub`). LDAP is accessed via `LdapService`/ + `LdapManagerService`, not ActiveRecord. +- **Jobs:** `app/jobs/` inherit `ApplicationJob < ActiveJob::Base`; backend is **Solid Queue** + (`config.active_job.queue_adapter = :solid_queue`, separate `queue` DB in prod). + `config/recurring.yml` has no active scheduled jobs. Ignore `config/sidekiq.yml` — it's + stale (sidekiq is not in the Gemfile; the README mention is outdated). +- **Config:** env-var driven (`.env.example`/`.env.development`/`.env.test`); `SERVICES` + constant is loaded from `config/services.yml` in `config/initializers/service_details.rb`. + Custom LDAP schema attributes are applied from `schemas/ldap/*.ldif` by `lib/tasks/ldap.rake`. + +## Testing conventions (`spec/`) + +`spec/rails_helper.rb` includes FactoryBot, Devise `ControllerHelpers` (controller specs), +Warden `Test::Helpers`, DatabaseCleaner (transactional fixtures **off**), ViewComponent +`TestHelpers` + Capybara matchers, and `ActiveJob::TestHelper` for `type: :job`. + +- **Feature specs** (`spec/features/`, `type: :feature`): Capybara **rack-test** driver (no JS). + Log in with Warden: `login_as user, scope: :user`; stub admin with + `allow(Devise::LDAP::Adapter).to receive(:get_ldap_param).with(user.cn, :admin).and_return(["true"])`. +- **Services** (`spec/services/`) are tagged `type: :model`; exercise `#call`/`service.send(:private)` + and assert on DB rows / `enqueued_jobs`. +- **Request/component/mailer/job/model/helper** specs in matching `spec/` subdirs. + Fixtures live in `spec/fixtures/`; factories in `spec/factories/`. +- `spec_helper.rb` reloads routes before each `type: :controller` spec (Devise + Rails 8 workaround). + +## Notes & gotchas + +- Generators (`config/application.rb`): ERB templates, RSpec, no stylesheets, factory_bot + with `_factory` suffix dir `spec/factories`. +- `gitno/` and `extras/strfry/` are scratch/infra (Deno Nostr relay policies) — not app code; ignore. +- Don't add code comments unless asked (matches repo style); `frozen_string_literal` is inconsistent. +- Default dev login: username `admin` / password `admin is admin`.