16 Commits

Author SHA1 Message Date
khannurien
83dc44050e v4: a deployment chooses which ways federated sign-in goes — every instance took users of the others and let its own sign in to them, with no way to turn either off. GERBEUR_FEDERATION_INCOMING=0 keeps users of other instances out: the login and register pages offer no other instance, /manage no list of instances, their sign-ins are refused, and those already in are signed out until it is on again, their homes' events still applied meanwhile. GERBEUR_FEDERATION_OUTGOING=0 keeps the instance's users from signing in elsewhere: its /authorize page lets no instance sign them in, its metadata says so (gerbeur_outgoing), which other instances check before sending anyone there, the settings page lists no instances, and at startup every grant is withdrawn, which signs them out there. Both are on by default and reach the client through index.html. The dev instance gerbeur (:3000) takes no users of the others, while its own still sign in to them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 16:18:21 +00:00
khannurien
c0d03e9aaa v4: users sign in to one gerbeur instance with their account on another — topic instances, each with its own admins, domain and data, had no way to share accounts. Sign-in is federated over a subset of OpenID Connect, the authorization code flow with PKCE: each instance is an identity provider for its own users and a client for the others', and signs what it tells them with an Ed25519 key kept in federation_keys and published at /.well-known/jwks.json, beside its metadata at /.well-known/openid-configuration. Only sign-in is federated: an account from another instance is a local users row linked to (issuer, subject) in user_identities, with no password (password_hash ''), so sessions, roles, permissions, the socket and every foreign key work as before.
A remote user is name@host here, in users.username, in paths (/@alice@music.example) and in mentions, through one handle rule in shared/validation.ts that paths, MENTION_RE and Markdown all build on; Markdown undoes GFM's email autolink of a handle. Their name, email and avatar come from their home at each sign-in and by its signed events (federation_outbox, delivered by the federation job with backoff): a rename at home leaves the old handle an alias here, and a password change or a withdrawn grant ends their sessions here. Their password, email, avatar and name can't change here (assertLocalUser), nor can they sign in elsewhere from here.

An admin keeps the trusted and blocked instances on /manage (federation:manage): a trusted instance's users sign in from a button on the login page, a blocked one's can't and are signed out at once, and any other's join with an invite from the register page. The /authorize page asks the user once per instance, and /settings lists the instances their account signs in to, each disconnectable; /login?next= brings a signed-out visitor back to /authorize. Emails stay unique among local accounts only, and migration 0024 adds the tables, makes that index partial and reserves authorize and federation as category slugs. remote/gerbeur-instance.ts is the only way to other instances: https and public addresses only, no redirects, no certificate fallback, unless GERBEUR_FEDERATION_ALLOW_PRIVATE lets two instances on one machine talk.

298 backend tests and 702 frontend tests pass, and deno check, deno fmt, deno lint, tsc, eslint, doc:lint and i18n:compile --strict are clean; checked end to end with two built instances on one machine: sign-in through the other's login and consent pages, consent skipped once granted and asked again after disconnecting, a rename and a password change at home reaching the other, and blocking; screenshots of the login, consent, profile, settings and manage pages at 1280 and 390px.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 14:23:00 +00:00
khannurien
708d113013 v4: a /community page shows the site's week and month, from the same figures as the digests, and every recurring server task is a job of one scheduler — the digests' top dumps and community section (who joined, the new public playlists, the chat and the totals) existed only in emails. community-service.ts now gathers them for a period from listTopDumps, listNewUsers and listNewPublicPlaylists, each in its own service and returning the API's Dump, PublicUser and Playlist; the digest builds its sections from the same call, its email-only DumpRow and DUMP_COLS replaced by one Dump mapper that networkOf uses too, and links its community section to the page. The page, /community for the last week and /community/monthly for the month, signed in only and linked from the avatar menu, reads GET /api/community?period=, which serves figures kept in memory and recomputed every GERBEUR_COMMUNITY_REFRESH_MINUTES (15 by default). It shows the four figures, the top dumps and the new playlists as live cards, and the new members as UserCards, the follow tab's user row made shared. Its words are the emails' own: the messages both use are renamed from email.digest.* to digest.*, as digest.weekly.off was. api/lib/scheduler.ts's startJobs runs the digests, the community refresh, the WebSocket ping, the typing expiry and the rate-limit sweep, which main.ts starts, so none of them runs on import any more. PERIOD_MS moves to shared/settings.ts. community is reserved as a category slug, and migration 0022 renames a category that had it, with renameReservedCategories moved out of 0021 into reserved-slugs.ts. A group's dump or playlist cards now fill its column. 262 backend tests and 678 frontend tests pass, and deno check, tsc, eslint, doc:lint and i18n:compile --strict are clean; checked in screenshots in Smooth light at 1280 and 390px, Smooth dark with mocked playlists, Brutalist dark at 390px and Kanagawa dark at 1280px.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 11:42:01 +00:00
khannurien
89014639cb v4: settings are stored on the server, starting with the style, the color scheme and the digest switches — every setting lived in the browser's localStorage, so it didn't follow an account to another browser and the server couldn't read it, which the digests need (whether to send each, and in which style). user_settings (migration 0019) holds one row per user who changed anything, the defaults otherwise, and GET/PATCH /api/users/me/settings read and change it (settings-service, model/settings, validators/settings, shared/settings). AuthProvider reads the settings into the session alongside the user, on mount and on focus, so a change made elsewhere arrives; useSettings changes them at once and puts them back if the server refuses. ThemeProvider now sits inside AuthProvider: a signed-in user's choice comes from their settings and is mirrored to localStorage for index.html's first-paint script, a visitor's stays in the browser, and an account with no choice yet takes the one this browser had, once. Until anyone picks a style, the instance's GERBEUR_DEFAULT_STYLE applies, written into index.html's default-style tag along with email-enabled (src/config/instance.ts), and a browser no longer stores the default as if it had been chosen, so changing the instance's style reaches everyone who never picked one. The settings tab gets an Email section with the weekly digest and monthly recap switches, disabled with a note when the server has no SMTP. The style and scheme names moved to shared/theme.ts in the previous commit; the default feed tab and sounds stay in the browser for now. 223 backend tests (new: settings defaults, patches, isolation, refusals and cascade; index.html's new tags) and 658 frontend tests (new: useSettings, the account's theme winning and a browser's carried over, settings read into the session, the digest switches) pass, and tsc, eslint, doc:lint, deno check, deno lint and deno fmt are clean.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 21:20:57 +00:00
khannurien
f20ca83f0c v4: every email is rendered by its template in api/mail/ on one layout that takes the instance's style, and sent one way — the welcome email was built and sent inside the register route and the reset link in its service, each writing Markdown, running marked, and calling a nodemailer wrapper whose cc, bcc, replyTo and send result nothing used. Now api/mail/ holds one template per email (welcome.ts, password-reset.ts) on layout.ts's EmailKit, which draws each block with inline styles in a style from styles.ts: the six site styles' palettes, copied from src/themes/, and what makes each recognisable in a mail client (Brutalist's hard border and shadow, GeoCities' ridge and rainbow rules, Times' underlined ink links, Kanagawa's colored quotes, dates and wordmark, Melange's spice bar). On the "auto" scheme the light palette is inlined and the dark one swapped in by a prefers-color-scheme block; a chosen scheme sends only its own. User-written text is escaped where it lands. strings.ts holds every email's words in English and French. GERBEUR_EMAIL_LOCALE picks the language and GERBEUR_DEFAULT_STYLE the instance's style, which the welcome and reset emails are drawn in; GERBEUR_WELCOME_EMAIL_BODY still replaces the welcome's Markdown, whose blocks now take the style too. email-service sends a rendered email with its recipient and headers, refuses to load when GERBEUR_SMTPS_URL is set without GERBEUR_FROM_EMAIL (sends used to fail one by one), and takes a test transport, through which the tests now read what was sent. The welcome send moves to user-service's sendWelcomeEmail. The style and scheme names move to shared/theme.ts, which the server now needs. README, mod.ts and .env.example describe mail/ and the new variables, and drop the "notifications, invites" email that never existed. 221 backend tests pass (new: the layout's schemes, styles and escaping, both templates, the welcome on sign-up, a reset link from the email that works), and deno check, deno lint, doc:lint, deno fmt, tsc and eslint are clean.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 21:11:54 +00:00
khannurien
2606ef4ccc v3: native bandcamp playback and a queue in the global player
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 43s
The player now holds a queue instead of a single item, so albums play
through and single tracks share the same code path. Adds a "stream" item
kind for expiring, remotely-hosted sources: Bandcamp signs its mp3 URLs
for ~24h, so an item carries the page it came from and re-resolves itself
on error or on a stale restored session, falling back to the iframe embed
if that fails too.

Gated behind GERBEUR_BANDCAMP_PLAYER (default "embed"), injected into a
meta tag the same way as site-name/site-emoji.

Also: player header gains artwork and a subtitle, volume persists across
queue advances and reloads, and cross-origin streams get a plain seek bar
rather than a waveform that can never decode.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SbtjNsT5wvuABnfqhegZEJ
2026-08-22 12:14:28 +00:00
f171027673 v3: added customizable site emoji (logo), small visual tweaks
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 41s
2026-06-29 15:12:25 +02:00
khannurien
ed7695663e v3: correctly using SITE_NAME across the app, added notifications on comments
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 44s
2026-04-08 20:12:30 +00:00
khannurien
856511777c chore: smaller docker image, simplification pass on environment variables, fix direct navigation without vite
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 46s
2026-04-08 19:43:19 +00:00
khannurien
20b9bfe7b4 v3: added password change/reset feature 2026-04-06 16:30:00 +00:00
khannurien
378b3ffa46 v3: added onboarding email on account creation 2026-03-30 14:55:30 +00:00
khannurien
cbb3505139 v3: search engine, responsive header with compact user menu 2026-03-29 11:56:31 +00:00
khannurien
f0f6472db6 v3: added attachments to resources, allow users to paste images into TextEditor, strengthened WS reliability 2026-03-27 08:53:32 +00:00
khannurien
ca70bdc14b v3: added opengraph support to the app, wrote README instructions incl. a Docker image 2026-03-26 19:55:48 +00:00
khannurien
fbbbb43258 v3: code quality pass, various bug fixes 2026-03-23 07:47:49 +00:00
khannurien
6207a7549f initial commit, boilerplate stuff 2026-03-15 17:15:46 +00:00