aboutsummaryrefslogtreecommitdiffstats
path: root/internal/store/blogs.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/store/blogs.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/store/blogs.go')
-rw-r--r--internal/store/blogs.go21
1 files changed, 19 insertions, 2 deletions
diff --git a/internal/store/blogs.go b/internal/store/blogs.go
index 3117154..579d73d 100644
--- a/internal/store/blogs.go
+++ b/internal/store/blogs.go
@@ -7,6 +7,7 @@ import (
"fmt"
"time"
+ "github.com/gramanas/blogspace/internal/config"
"github.com/gramanas/blogspace/internal/db"
"github.com/jackc/pgx/v5"
)
@@ -19,6 +20,8 @@ type Blog struct {
Subdomain string
DBName string
CreatedAt time.Time
+ // Per-file upload limit set by the superadmin; 0 means the server default.
+ MaxUploadBytes int64
// from the blog database
Title string
Tagline string
@@ -27,17 +30,25 @@ type Blog struct {
UpdatedAt time.Time
}
-const blogCols = `id, owner_id, subdomain, db_name, created_at`
+const blogCols = `id, owner_id, subdomain, db_name, created_at, COALESCE(max_upload_bytes, 0)`
func scanBlog(row interface{ Scan(...any) error }) (*Blog, error) {
var b Blog
- err := row.Scan(&b.ID, &b.OwnerID, &b.Subdomain, &b.DBName, &b.CreatedAt)
+ err := row.Scan(&b.ID, &b.OwnerID, &b.Subdomain, &b.DBName, &b.CreatedAt, &b.MaxUploadBytes)
if err != nil {
return nil, wrap(err)
}
return &b, nil
}
+// UploadLimit is the per-file limit that applies to this blog.
+func (b *Blog) UploadLimit(cfg *config.Config) int64 {
+ if b.MaxUploadBytes > 0 {
+ return b.MaxUploadBytes
+ }
+ return cfg.MaxUploadBytes
+}
+
func (bs *BlogStore) loadSettings(ctx context.Context, b *Blog) error {
err := bs.db.QueryRow(ctx, `SELECT title, tagline, language, theme, updated_at FROM settings`).Scan(&b.Title, &b.Tagline, &b.Language, &b.ThemeJSON, &b.UpdatedAt)
if errors.Is(err, pgx.ErrNoRows) { // registered, but the database is empty: not a 404
@@ -174,6 +185,12 @@ func (s *Store) DeleteBlog(ctx context.Context, b *Blog) error {
return s.cluster.DropBlogDB(ctx, b.DBName)
}
+// SetBlogUploadLimit overrides the upload limit of one blog; 0 restores the server default.
+func (s *Store) SetBlogUploadLimit(ctx context.Context, id int64, bytes int64) error {
+ _, err := s.db.Exec(ctx, `UPDATE blogs SET max_upload_bytes = NULLIF($2, 0) WHERE id=$1`, id, bytes)
+ return err
+}
+
func (bs *BlogStore) UpdateSettings(ctx context.Context, title, tagline, language string) error {
_, err := bs.db.Exec(ctx, `UPDATE settings SET title=$1, tagline=$2, language=$3, updated_at=now()`, title, tagline, language)
return err