bingo: store photos in their own bucket
The bingo photos sat under bingo/ in the invoice bucket. Store them in BINGO_S3_BUCKET (datacenterbingo-photos in prod, created and pre-filled by the storage repo with the same keys) and fall back to S3_BUCKET when unset, so dev and tests need nothing new. Assisted-by: Claude:opus-5
This commit is contained in:
parent
849980042e
commit
f1ceb4efc7
5 changed files with 28 additions and 10 deletions
|
|
@ -141,6 +141,7 @@ jobs:
|
|||
LTC_ADDRESS=${LTC_ADDRESS}
|
||||
CRYPTO_ADDRESSES=${CRYPTO_ADDRESSES}
|
||||
BINGO_HOST=datacenterbingo.xyz
|
||||
BINGO_S3_BUCKET=datacenterbingo-photos
|
||||
INDEXNOW_KEY=${INDEXNOW}
|
||||
EOF
|
||||
chmod 600 /opt/asxpio/.env
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ The public site (datacenterbingo.xyz) lives in its own repo and container: `git.
|
|||
- Status: operational | building | planned. `mw_type` (it | connected | total | design | mva, NULL = not stated) tags the MW figure; mva swaps the unit. The public default card is operational only, and so is the dashboard tile (`Board.load('operational')`).
|
||||
- Provider: `bingo_datacenters.provider` holds a `Bingo::PROVIDERS` slug (backfilled by migration 016; new rows get it from `Bingo.guess_provider`; editable in the admin). The public site filters cards by it.
|
||||
- Ordering: `position` is dense 1..N (`Bingo.place!` inserts and shifts; `renumber!` runs after deletes). Positions 1–25 are the card; the rest are reserve. Slug is fixed at creation because it names the photo objects.
|
||||
- Photos: stored in the bucket as `bingo/<slug>-<hex>.<ext>`, with the type sniffed from magic bytes (JPEG/PNG/WebP).
|
||||
- Photos: stored in `BINGO_S3_BUCKET` (`datacenterbingo-photos`, made by the storage repo; unset = `S3_BUCKET`) as `bingo/<slug>-<hex>.<ext>`, with the type sniffed from magic bytes (JPEG/PNG/WebP).
|
||||
- Limits: 8 MB cap, and metadata is stripped (GPS!). URL import is https-only.
|
||||
- On store, ImageMagick derives `-480.webp`, `-1200.webp` and `-og.jpg` (1200×630). `photo_variants` / `photo_og` record whether that succeeded.
|
||||
- "Rebuild resized photos" re-derives the variants from the stored originals.
|
||||
|
|
|
|||
18
lib/bingo.rb
18
lib/bingo.rb
|
|
@ -160,8 +160,8 @@ module Bingo
|
|||
end
|
||||
end
|
||||
|
||||
# Photos live in the app's MinIO bucket under bingo/ and are proxied by
|
||||
# the bingo site (and /admin/bingo/p here), so the bucket stays private.
|
||||
# Photos live under bingo/ in Photo.bucket and are proxied by the bingo
|
||||
# site (and /admin/bingo/p here), so the bucket stays private.
|
||||
module Photo
|
||||
PREFIX = 'bingo/'.freeze
|
||||
MAX_BYTES = 8 * 1024 * 1024
|
||||
|
|
@ -175,6 +175,10 @@ module Bingo
|
|||
|
||||
module_function
|
||||
|
||||
# Own bucket in prod (datacenterbingo-photos, shared read-only with the
|
||||
# bingo site); unset = the app bucket (dev, tests).
|
||||
def bucket = ENV['BINGO_S3_BUCKET'].to_s.empty? ? S3.bucket : ENV['BINGO_S3_BUCKET']
|
||||
|
||||
# Input may arrive tagged UTF-8 (aws-sdk bodies are plain StringIOs), and
|
||||
# comparing that with binary literals raises, so look at raw bytes.
|
||||
def sniff(bytes)
|
||||
|
|
@ -193,7 +197,7 @@ module Bingo
|
|||
ext = sniff(bytes) or raise Error, 'not a JPEG, PNG or WebP image'
|
||||
bytes = strip_metadata(bytes, ext)
|
||||
key = "#{PREFIX}#{slug}-#{SecureRandom.hex(4)}.#{ext}"
|
||||
S3.put(key, bytes, content_type: TYPES[ext])
|
||||
S3.put(key, bytes, content_type: TYPES[ext], bucket: bucket)
|
||||
key
|
||||
end
|
||||
|
||||
|
|
@ -207,7 +211,7 @@ module Bingo
|
|||
|
||||
# Re-derives the variants from the stored original.
|
||||
def rebuild(dc)
|
||||
dc.update(**build_variants(dc.photo_key, S3.get(dc.photo_key)))
|
||||
dc.update(**build_variants(dc.photo_key, S3.get(dc.photo_key, bucket: bucket)))
|
||||
end
|
||||
|
||||
def variant_key(key, width)
|
||||
|
|
@ -222,8 +226,8 @@ module Bingo
|
|||
def build_variants(key, bytes)
|
||||
bytes = bytes.b
|
||||
ext = sniff(bytes) or raise Error, 'not an image'
|
||||
WIDTHS.each { |w| S3.put(variant_key(key, w), resize(bytes, ext, w), content_type: TYPES['webp']) }
|
||||
S3.put(og_key(key), og_crop(bytes, ext), content_type: TYPES['jpg'])
|
||||
WIDTHS.each { |w| S3.put(variant_key(key, w), resize(bytes, ext, w), content_type: TYPES['webp'], bucket: bucket) }
|
||||
S3.put(og_key(key), og_crop(bytes, ext), content_type: TYPES['jpg'], bucket: bucket)
|
||||
{ photo_variants: true, photo_og: true }
|
||||
rescue Error => e
|
||||
$logger.warn("bingo: no variants for #{key}: #{e.message}")
|
||||
|
|
@ -352,7 +356,7 @@ module Bingo
|
|||
|
||||
def read(file)
|
||||
raise Error, 'bad name' unless file =~ FILE_RE
|
||||
[S3.get(PREFIX + file), TYPES[Regexp.last_match(1)]]
|
||||
[S3.get(PREFIX + file, bucket: bucket), TYPES[Regexp.last_match(1)]]
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ module S3
|
|||
ENV.fetch('S3_BUCKET')
|
||||
end
|
||||
|
||||
def put(key, bytes, content_type: 'application/pdf')
|
||||
def put(key, bytes, content_type: 'application/pdf', bucket: self.bucket)
|
||||
client.put_object(
|
||||
bucket: bucket,
|
||||
key: key,
|
||||
|
|
@ -40,7 +40,7 @@ module S3
|
|||
key
|
||||
end
|
||||
|
||||
def get(key)
|
||||
def get(key, bucket: self.bucket)
|
||||
client.get_object(bucket: bucket, key: key).body.read.b # body is a UTF-8 StringIO
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -213,6 +213,19 @@ class BingoTest < Minitest::Test
|
|||
S3.instance_variable_set(:@client, nil)
|
||||
end
|
||||
|
||||
def test_photos_use_their_own_bucket
|
||||
puts_to = []
|
||||
S3.client.stub(:put_object, ->(**o) { puts_to << o[:bucket] }) do
|
||||
ENV['BINGO_S3_BUCKET'] = 'photos-bucket'
|
||||
Bingo::Photo.store('dc-1', JPEG)
|
||||
ENV.delete('BINGO_S3_BUCKET')
|
||||
Bingo::Photo.store('dc-1', JPEG)
|
||||
end
|
||||
assert_equal ['photos-bucket', S3.bucket], puts_to
|
||||
ensure
|
||||
ENV.delete('BINGO_S3_BUCKET')
|
||||
end
|
||||
|
||||
def test_photo_rejects_non_images
|
||||
assert_raises(Bingo::Photo::Error) { Bingo::Photo.store('dc-1', '<html>') }
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue