aboutsummaryrefslogtreecommitdiffstats
path: root/internal/web/routes.go
Commit message (Collapse)AuthorAgeFilesLines
* Give the editor a Markdown toolbar and preview, restyle the dashboardgrm2026-09-151-0/+1
| | | | | | | | | | | | | | | | | | | | The post/page/announcement editor was a bare textarea, so bloggers had to know the Markdown syntax by heart. It now has a toolbar (bold, italic, strike, heading cycle, quote, code, lists, rule, link box, insert file), Ctrl+B/I/K, list continuation on Enter and a Write/Preview toggle that renders the text through the same goldmark + bluemonday pipeline a save uses (POST /b/{sub}/preview; nothing is stored). Every edit goes through execCommand("insertText") so browser undo keeps working. The toolbar stays hidden without JavaScript, leaving the old form untouched. The dashboard moves from the "paper & ink" look to a lean one: neutral surfaces, 1px borders, one blue accent, system sans, with custom properties and flexbox/grid now allowed there (blog.css is unchanged). Class names were kept so the templates barely change; a global [hidden] rule keeps flex containers from overriding the hidden attribute. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
* Turn the image library into a file library, with a per-blog upload limitgrm2026-09-141-4/+6
| | | | | | | | | | | | | | | | | | | | | | | | | | Bloggers want to attach PDFs, archives, audio and other files to posts, not only images. The Images tab becomes Files: any type is accepted, listed by kind with search, paging, rename and multi-file upload, and the editor's paste/drop/"Insert file" takes anything (images are shown, everything else becomes a link). The default limit goes from 5 to 10 MB and the superadmin can override it per blog from /admin/. Files stay in Postgres so one pg_dump is still the whole blog. The bytea column is STORAGE EXTERNAL and /media streams it in substring() slices, so serving never holds a whole file in memory whatever limit a blog gets. Serving any type on the root domain, which carries the session cookie, needs a policy: uploads are typed by sniffing (the extension may only refine a generic sniff to an allowlisted type) and only images, PDF, plain text, audio and video render inline; HTML, SVG, XML, scripts, archives and binaries always go out as application/octet-stream with Content-Disposition: attachment. The body cap moves out of requireAuth into guardPOST, which runs after withBlog has resolved the blog and so knows its limit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
* Give every blog its own Postgres databasegrm2026-09-131-2/+4
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | A blog is now a database of its own (blog_<sub>) on the same server: one pg_dump is a complete backup of a blog, one psql restores it, and nothing a blog's queries do can reach another blog's rows. The control database (DATABASE_URL) keeps only users and the blog registry (id, owner, subdomain, db_name); title, tagline and theme move into a one-row settings table next to the content so the dump really is everything. db.Cluster holds the control pool plus small, lazily opened per-blog pools. store.Store (control) hands out a store.BlogStore per blog; every blog_id parameter and column is gone, the database is the scope. Handlers reach it through blogStore(r), which resolveBlog puts in the context next to the blog. Existing data is moved in place by control migration 00006, a Go migration that runs inside the control transaction: it creates and migrates each blog database, copies the rows preserving ids, and marks the registry; 00007 then drops the old tables. Either every blog is moved or the control database is untouched. /media/{id} now serves the host's blog only, so dashboard previews on the root domain use /b/{sub}/media/{id}. Subdomains are capped at 58 chars so "blog_" + name fits a Postgres identifier. Deleting a user drops their database. Store.Open resets a blog's pool and retries once so a database restored under a running app (dropdb --force, createdb, psql) just works. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
* Add the Layout tab: five areas with modules, columns and a menu editorgrm2026-09-131-0/+13
| | | | | | | | | | | | | | | | | | | A blog page is now header, left column, main content, right column and footer, each holding an ordered list of modules (blog title, logo, menu, archive by year/month, recent posts, custom HTML, footer text, RSS link, site map). Side columns have percentage widths, can be hidden, and can keep their space when empty; on phones they stack under the posts. The menu becomes its own ordered list mixing pages and outside links. Announcements are placed per column at its top or bottom. Modules and menu items get tables; the migration derives them from each blog's old theme (nav position, footer text, show-in-nav pages) so existing blogs look the same. Custom HTML is deliberately stored and served as-is (owner's decision; see AGENTS.md for why the blast radius is the blogger's own origin). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
* Add a favicon and rework the Design pagegrm2026-09-121-0/+4
| | | | | | | | | | | | | | | | | | | | | Blogs had no icon at all, and browsers probing /favicon.ico ended up rendering the 404 page. Ship a default paper & ink icon (svg + png) and let each blog replace it from the Design page; /favicon.ico now serves the default or redirects to the blog's own image. ICO uploads accepted. The Design page gains colour schemes, reset to defaults, thumbnail image pickers, jump links and a sticky save bar, plus new options: heading font, link underline/hover, content box style and padding, header image height, post dates and format, footer RSS toggle, menu alignment/style/hover and sidebar width. The Menu card picks the position with illustrated tiles and shows only the options that apply to it, using CSS :checked rules rather than JavaScript. All new defaults reproduce the previous look, so existing blogs are unchanged until edited. Theme is jsonb, so no migration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
* Add announcements: blog-wide notices on every page and postgrm2026-09-121-0/+9
| | | | | | | | | | | A blog can have any number of announcements ("sections" in the schema): a book club's next meeting, this month's pick, a holiday closure. Each has a placement (above or below the posts, or under the menu when the menu is a left sidebar), a look (highlighted, warning, plain), an order, and an on/off switch so recurring notices can be kept around while hidden. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
* Rename /login to /webadmin and reach it from every bloggrm2026-09-121-2/+4
| | | | | | | | | | The login URL is less guessable, bloggers can type /webadmin on their own blog and get bounced to the root login page (and back to their dashboard after logging in), and the public root blog no longer advertises the admin entry point in its footer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
* Serve the superadmin's blog on the root domaingrm2026-09-121-6/+13
| | | | | | | | | | The base domain (and www.) now serves a regular blog owned by the first superadmin, created automatically on startup, alongside the management routes. Literal management paths take precedence over the blog's page wildcards, and the slugs they would shadow are reserved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
* Initial multi-tenant blog hostgramanas2026-09-121-0/+81
Go + Postgres application serving a management dashboard on the base domain and one public blog per subdomain. Markdown posts organised in pages, form-based theme customisation, image uploads stored in Postgres, JWT cookie sessions with CSRF, superadmin user management, RSS feeds. Docker/compose deployment and a Makefile-driven dev environment with seed data. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A