* fix: various backsync fixes, assorted changes
* automated: Backsync BMS+PMS Songs/Charts 2026-05-23T22:16:28.469Z
* fix: some patches, but all good!
---------
Co-authored-by: Test Name <test@example.com>
* fix: resize profile images on upload and add cache headers
Profile pictures were stored at original upload size (up to 1 MB) but
only ever displayed at 32–128 px, so every page load downloaded the full
original image. Additionally, no Cache-Control headers were set anywhere
in the pipeline, forcing a fresh round-trip through the API redirect and
CDN on every page view.
- Resize JPEG/PNG pfps to max 256×256 and banners to max 1920×1080 via
sharp before storing; re-encode to WebP at 85% quality (GIFs skipped
to preserve the animated-GIF easter egg)
- Hash the resized buffer so the CDN path reflects what's on disk
- Set ContentType and CacheControl: immutable on S3 PutObject so CDN
edges and browsers cache the objects indefinitely (safe because paths
are content-addressed via SHA-256)
- Add Cache-Control: public, max-age=3600, stale-while-revalidate=86400
to the GET /pfp and GET /banner redirect responses so browsers cache
the userId→CDN-URL mapping and skip the API hop on repeat visits
Fixes#1553
* chore: apply biome formatting
* chore: generate ImportTimestop kanel type
* feat: add backfill-media script to migrate existing profile images
One-off script that reads every user's existing pfp and banner from S3,
runs them through the same sharp resize/WebP pipeline as new uploads,
stores the result under the new content-addressed key with immutable
cache headers, and updates custom_pfp_location/custom_banner_location
in the DB.
Features:
- --dry-run flag to preview changes without writing anything
- Skips objects already at optimal size (idempotent)
- Gracefully handles missing S3 objects (logs + skips)
- Summary stats at the end
- Reads S3 config from CLI args or TACHI_CDN_SAVE_LOCATION_* env vars
* chore: apply biome formatting to backfill-media
* fix: resize animated GIFs to animated WebP via sharp animated:true
* test: rewrite change-pfp/banner tests as real S3 + sharp integration
Remove the CDN mock entirely. Tests now pass real image buffers through
sharp and verify the stored S3 object via CDNRetrieve:
- PNG/JPEG input → assert stored object is WebP, dimensions ≤256×256,
smaller than the original
- GIF input → assert stored object is WebP
- Hash returned from the action == SHA256 of the stored WebP bytes
(not the original upload)
- Delete tests upload a real pfp then delete it and verify CDNRetrieve
throws afterwards
Also move change-pfp.test.ts from the isolated vitest project back to
the default pool now that it no longer uses vi.mock.
* chore: apply biome formatting to test file
* fix: use POSTGRES_TEST_HOST env var in bot vitest setup
Both vitest.globalSetup.ts and vitest.setup.ts hardcoded 'tachi-postgres'
(the dev server), but just bot-db-test-template-reset creates the template
on 'tachi-postgres-test' (the tmpfs test server). Workers then failed to
clone the template because they were looking on the wrong host.
Mirrors the pattern already used in the server package:
process.env.POSTGRES_TEST_HOST ?? 'tachi-postgres-test'
* fix: update pfp router tests for WebP resize pipeline
The PUT tests were comparing the stored S3 bytes directly to the original
upload buffer. Since we now resize and re-encode to WebP, this always
fails. Also, following the mockApi redirect to fetch the bytes added a
slow supertest round-trip.
Replace with: read custom_pfp_location from the DB after PUT, fetch the
stored object directly via CDNRetrieve, and assert it is smaller than the
original (non-deterministic WebP compression makes exact byte comparison
inappropriate).
* fix: update banner router tests for WebP resize pipeline
* fix: tests
* fix: ci
* fix: update arcaea's CCs
* fix: proper ID check
* rest of the owl
* I remembered to run tests
* there is no pale purple
* add phase 12
* remove particle arts from the mobile folder
* make arcaea an honorary GCM game it always wanted to be
* songpack=genre
* move displayVersion over to chart data
* disable FR/PM score validators
* Revert "disable FR/PM score validators"
This reverts commit da1cfbaa62.
* bullshit
* bullshit 2
* bullshit 2
* [chunithm] Recover seeds overwritten by v2 merge
* unfuck song IDs
* recover search terms
* yeah just fuck my shit up
* update chart schema
* fix test data
Add PR workflows for tachi-bot (lint, typecheck, vitest with Postgres) and
tachi-github-bot (lint, typecheck). Gate image build and deploy on passing
tests. Fix github-bot path filters and add eslint scripts to the package.