aboutsummaryrefslogtreecommitdiffstats
path: root/internal/web/web_test.go
diff options
context:
space:
mode:
authorgrm <grm@eyesin.space>2026-09-14 23:51:55 +0300
committergrm <grm@eyesin.space>2026-09-14 23:51:55 +0300
commit778cb72c8a0902bd0b8159ebd3bb7eff93f28c83 (patch)
treede21228c247e735591e88acbbf3bf4c83f2142e0 /internal/web/web_test.go
parent5f9fc2a8667a9438b6336d75026f9a455fc9c4ce (diff)
downloadblogspace-778cb72c8a0902bd0b8159ebd3bb7eff93f28c83.tar.gz
blogspace-778cb72c8a0902bd0b8159ebd3bb7eff93f28c83.tar.bz2
blogspace-778cb72c8a0902bd0b8159ebd3bb7eff93f28c83.zip
Turn the image library into a file library, with a per-blog upload limit
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
Diffstat (limited to 'internal/web/web_test.go')
-rw-r--r--internal/web/web_test.go28
1 files changed, 18 insertions, 10 deletions
diff --git a/internal/web/web_test.go b/internal/web/web_test.go
index d7de43a..e2bd335 100644
--- a/internal/web/web_test.go
+++ b/internal/web/web_test.go
@@ -63,7 +63,7 @@ func TestSubdomainLength(t *testing.T) {
func TestRootRoutePrecedence(t *testing.T) {
cfg := &config.Config{BaseDomain: "example.com", JWTSecret: []byte("x"), MaxUploadBytes: 1 << 20}
s := NewServer(cfg, nil)
- for path, want := range map[string]int{"/webadmin": 200, "/healthz": 200, "/dashboard": 303, "/admin/": 303, "/b/alice/posts": 303, "/b/alice/announcements": 303, "/b/alice/layout": 303, "/b/alice/media/6ba7b810-9dad-11d1-80b4-00c04fd430c8": 303, "/account/password": 303} {
+ for path, want := range map[string]int{"/webadmin": 200, "/healthz": 200, "/dashboard": 303, "/admin/": 303, "/b/alice/posts": 303, "/b/alice/announcements": 303, "/b/alice/layout": 303, "/b/alice/media/6ba7b810-9dad-11d1-80b4-00c04fd430c8": 303, "/b/alice/files": 303, "/account/password": 303} {
rec := httptest.NewRecorder()
req := httptest.NewRequest("GET", path, nil)
req.Host = "example.com"
@@ -320,7 +320,7 @@ func TestPresets(t *testing.T) {
func TestAllTemplatesParse(t *testing.T) {
tpl := newTemplates(false)
for _, name := range []string{"auth/login.html", "dashboard/overview.html", "dashboard/pages.html", "dashboard/page_form.html",
- "dashboard/posts.html", "dashboard/post_form.html", "dashboard/design.html", "dashboard/images.html", "dashboard/settings.html",
+ "dashboard/posts.html", "dashboard/post_form.html", "dashboard/design.html", "dashboard/files.html", "dashboard/settings.html",
"dashboard/password.html", "dashboard/confirm.html", "dashboard/sections.html", "dashboard/section_form.html", "dashboard/layout.html", "dashboard/module_form.html", "admin/index.html", "admin/new_user.html", "admin/delete_user.html",
"blog/page.html", "blog/post.html", "blog/404.html"} {
for _, l := range i18n.Languages() {
@@ -365,7 +365,7 @@ func TestGreekCatalogComplete(t *testing.T) {
add(goKey, string(b))
}
// Keys that reach t/tr through a variable rather than a literal.
- for _, m := range []map[string]string{moduleNames, areaNames} {
+ for _, m := range []map[string]string{moduleNames, areaNames, fileKindNames} {
for _, v := range m {
used[v] = true
}
@@ -422,17 +422,25 @@ func TestSubdomainWebadminRedirect(t *testing.T) {
}
}
-func TestAppendImageMD(t *testing.T) {
- img := &store.Image{ID: uuid.MustParse("6ba7b810-9dad-11d1-80b4-00c04fd430c8"), Filename: "cat].png"}
+func TestFileMarkdown(t *testing.T) {
+ id := uuid.MustParse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
+ img := &store.File{ID: id, Filename: "cat].png", Kind: "image"}
+ doc := &store.File{ID: id, Filename: "notes\nv2.pdf", Kind: "document"}
line := "![cat.png](/media/6ba7b810-9dad-11d1-80b4-00c04fd430c8)"
- if got := appendImageMD("", img); got != line+"\n" {
+ if got := fileMarkdown(img); got != line {
+ t.Errorf("image: %q", got)
+ }
+ if got := fileMarkdown(doc); got != "[notes v2.pdf](/media/6ba7b810-9dad-11d1-80b4-00c04fd430c8)" {
+ t.Errorf("document should be a link: %q", got)
+ }
+ if got := appendFileMD("", img); got != line+"\n" {
t.Errorf("empty body: %q", got)
}
- if got := appendImageMD("hello\n", img); got != "hello\n\n"+line+"\n" {
- t.Errorf("with body: %q", got)
+ if got := appendFileMD("hello\n", img); got != "hello\n\n"+line+"\n" {
+ t.Errorf("appended: %q", got)
}
- if got := appendImageMD("hello", nil); got != "hello" {
- t.Errorf("nil image should not change the body: %q", got)
+ if got := appendFileMD("hello", nil); got != "hello" {
+ t.Errorf("nil file should not change the body: %q", got)
}
}