admin: publish files to the public bucket
All checks were successful
Build and Deploy to Production / test (push) Successful in 27s
Build and Deploy to Production / build (push) Successful in 1m3s
Build and Deploy to Production / deploy (push) Successful in 40s

There was no way to put a file up for public use short of running an
S3 client with the uploader key. Add a "files" area to the admin:
upload one or more files, copy the link, edit the note, replace the
contents under the same link, delete. It has its own read/write/delete
permission flags like the other areas, and every action is audited.

The objects go to the storage stack's public bucket and are served by
Garage at s3.asxp.io/public/<key>, not by this app. The app's key can
only put and delete there, so a public_files table is the record of
what was published. Keys get a random prefix: links are not guessable
and names never collide.

That host is same-site with the admin, so a file that can carry script
must not render there. Only a fixed list of types (images, PDF, plain
text, audio, video) is served inline; the rest, HTML and SVG included,
is stored as an attachment. Image metadata is stripped by default, as
for the bingo photos, because of GPS tags.

A per-file limit and a total quota keep the small disk on asxpio-ops
from filling up.

Assisted-by: Claude:fable-5-2
This commit is contained in:
Sergei Poljanski 2026-11-12 05:52:10 +04:00
commit 4c10221727
Signed by: asxpi
GPG key ID: 4F8851660FA4121B
15 changed files with 511 additions and 7 deletions

View file

@ -24,6 +24,7 @@ require_relative 'lib/db'
require_relative 'lib/admin_auth'
require_relative 'lib/s3'
require_relative 'lib/bingo_admin'
require_relative 'lib/files_admin'
require_relative 'lib/users_admin'
# Invoice model needs a live Sequel connection at class-definition time.
@ -39,6 +40,7 @@ if ENV['DATABASE_URL']
require_relative 'lib/invoice_stats'
require_relative 'lib/bingo'
require_relative 'lib/bingo_monitor'
require_relative 'lib/public_file'
Bingo.seed!
require_relative 'lib/user'
require_relative 'lib/audit'
@ -73,6 +75,7 @@ class AsxpioWeb < Sinatra::Base
use Rack::Protection::AuthenticityToken
register AdminAuth
register BingoAdmin
register FilesAdmin
register UsersAdmin
Mailer.configure!
@ -185,6 +188,10 @@ class AsxpioWeb < Sinatra::Base
no_photo: board.cells.reject(&:photo_key)
}
end
if can?('files', 'read')
@files = { count: PublicFile.count, bytes: PublicFile.used_bytes,
recent: PublicFile.order(Sequel.desc(:updated_at)).limit(5).all }
end
erb :'admin/dashboard'
end