ruby rework with contact form
This commit is contained in:
parent
90402ae14a
commit
1ab09043ee
26 changed files with 932 additions and 221 deletions
86
CLAUDE.md
Normal file
86
CLAUDE.md
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# asxp.io — IE Sergei Poljanski website
|
||||
|
||||
## What this is
|
||||
The public-facing website for the user's Individual Entrepreneur registered in Georgia. Single-purpose: present the legal entity and accept contact-form submissions.
|
||||
|
||||
## Rule of thumb for this file
|
||||
**Same confidentiality bar as the rendered HTML.** If a fact isn't on the public page, it doesn't belong here. Sensitive operational details (banking, registration number, tax-regime reasoning) live in the user's private memory, not in the repo.
|
||||
|
||||
## Hard rules
|
||||
|
||||
- **Never use the words "consulting" or "advisory"** anywhere on the site or in invoicing copy. Frame work as "services", "engineering", "implementation". This is for tax-classification reasons; ask the user before deviating.
|
||||
- **Never commit `.env`** or any real credential. Production secrets come from Forgejo secrets at deploy time.
|
||||
- **Don't write Claude Code as co-author on commits.** (User-global preference.)
|
||||
- **Don't write commits at all unless asked.** (User-global preference.)
|
||||
|
||||
## Entity facts (also visible on the rendered page)
|
||||
|
||||
- Legal name: **IE Sergei Poljanski** / `SERGEI POLJANSKI` (Latin) / `ინდ. მეწარმე სერგეი პოლჯანსკი` (Georgian)
|
||||
- Legal form: Individual Entrepreneur (Georgia)
|
||||
- Tax ID: `304813343`
|
||||
- Registered: 2026-05-04
|
||||
- Activity codes: 62010 (main), 62090 (additional)
|
||||
- Address: Ilia and Nino Nakashidze St, N 1, Building N3, Apt N3, Krtsanisi, Tbilisi, Georgia
|
||||
- Public contact: `ie@asxp.io`, `t.me/ie_asxpi`, +995 595 026 471
|
||||
|
||||
Banking details and registration number are deliberately not on the public page and not in this file. They go on invoices only.
|
||||
|
||||
## Stack
|
||||
|
||||
- **Sinatra 4.2** + Puma + Rack 3 + erubi, on Ruby 3.4
|
||||
- **Mail gem** for SMTP (Fastmail, STARTTLS on 587)
|
||||
- In-memory `RateLimit` (5 req/hour/IP) — see `lib/rate_limit.rb`
|
||||
- `Rack::Protection::AuthenticityToken` for CSRF (uses session-stored token)
|
||||
- Honeypot field named `website` for spam
|
||||
- Static assets in `public/`; views in `views/`
|
||||
- Deploy: `Dockerfile` + `docker-compose.yml` (Traefik labels for asxp.io / www.asxp.io)
|
||||
- Dev shell: `flake.nix` (Ruby 3.4, bundler, openssl, zlib, libyaml). Run `nix develop`.
|
||||
|
||||
The patterns mirror `~/Code/projects/narayana/www`. Use that repo as a reference for conventions (middleware, helpers, dotenv pattern) — but don't pull in narayana-specific things this site doesn't need (no Redis, no i18n, no Prawn, no API client).
|
||||
|
||||
## Contact form behavior
|
||||
|
||||
`POST /contact` does, in order:
|
||||
1. Honeypot check — if `website` field non-empty, silently 302 to `/thanks`.
|
||||
2. Validate name, email, subject, message.
|
||||
3. Rate-limit by client IP.
|
||||
4. `Mailer.notify_owner` → message to `MAIL_TO` (`ie@asxp.io`), `Reply-To: <visitor email>`.
|
||||
5. `Mailer.confirm_visitor` → receipt to visitor, `Reply-To: ie@asxp.io`. Failure here is logged but not surfaced to the user.
|
||||
|
||||
`MAIL_FROM` must use a Fastmail-verified send-as address (currently `me@asxp.io`). The friendly name reads "IE Sergei Poljanski Contact Form".
|
||||
|
||||
## Operational notes
|
||||
|
||||
- `client_ip` reads `X-Forwarded-For` first (Traefik sets it), falling back to `X-Real-IP` and `REMOTE_ADDR`.
|
||||
- The rate limiter resets on app restart — acceptable for one-process deploys.
|
||||
- If the Ruby process is down, the site is down. There's no static fallback. (Trade-off accepted for the contact form.)
|
||||
|
||||
## SSH key comment caveat
|
||||
|
||||
`public/id_ed25519.pub` is served from the site. The comment field (currently `ie+2026@asxp.io`) is part of the key file itself; editing the website without regenerating the key file means the website and the file disagree. Keep them in sync, or regenerate the key.
|
||||
|
||||
## Common tasks
|
||||
|
||||
- **Generate a new SESSION_SECRET:** `openssl rand -hex 64` (then update Forgejo secret).
|
||||
- **Local dev:** `nix develop`, then `bundle exec rerun -- rackup -p 3000`. Visit `http://localhost:3000`.
|
||||
- **Local image build (sanity check):** `docker compose build`
|
||||
- **Production deploy:** automatic via `.forgejo/workflows/deploy.yaml` on push to `main`. Pipeline:
|
||||
1. Kaniko builds the image and pushes it to the Forgejo registry.
|
||||
2. SSH copies `docker-compose.yml` to the deploy directory on the prod host.
|
||||
3. SSH writes `.env` (mode 600) from Forgejo secrets — `SESSION_SECRET` and `SMTP_PASSWORD` are interpolated; non-secret config is hardcoded in the workflow.
|
||||
4. SSH `sed`-substitutes the image tag in `docker-compose.yml` and runs `docker compose up -d`.
|
||||
|
||||
## Where the secrets live
|
||||
|
||||
| Forgejo secret | Use | Rotate by |
|
||||
|-------------------|-------------------------------------|-------------------|
|
||||
| `SESSION_SECRET` | Rack session cookie HMAC | Update secret + push (or re-run last deploy). All sessions invalidated; nobody is logged in to this site so no user impact. |
|
||||
| `SMTP_PASSWORD` | Fastmail app password | Generate new app password in Fastmail → update secret → push. |
|
||||
| `DEPLOY_IP/USER/SSH_KEY` | SSH to prod from CI | Standard SSH key rotation. |
|
||||
| `FORGEJO_REGISTRY/USER/TOKEN` | Kaniko registry auth | Forgejo token rotation. |
|
||||
|
||||
**Non-secret config that lives in the workflow** (not in `.env.example`, not in secrets): `SMTP_ADDR`, `SMTP_PORT`, `SMTP_USER`, `MAIL_FROM`, `MAIL_TO`. Change these by editing `.forgejo/workflows/deploy.yaml`.
|
||||
|
||||
## What `.env.example` is for
|
||||
|
||||
Local development only. Copy to `.env`, fill in dummy/test SMTP creds (or real ones if you want to actually send) and a throwaway `SESSION_SECRET`. The Nix dev shell sources `.env` automatically. **Production never reads `.env.example` and never has a stale `.env`** — every deploy regenerates it.
|
||||
Loading…
Add table
Add a link
Reference in a new issue