Commit graph

1028 commits

Author SHA1 Message Date
d2d9554356 bump GIT_HASH to f07c37b 2026-02-27 12:57:31 -05:00
f07c37b67a feat: add per-user S3 bucket for sandbox artifact uploads
Users can configure their own S3-compatible bucket credentials in User
Settings > Artifact Storage. When configured, sandbox mode shows an
"Upload to Bucket" button that pushes exported artifacts (filtered images,
video captures) directly to the user's bucket via presigned POST.

Supports DigitalOcean Spaces, AWS S3, MinIO, Backblaze B2, and any
S3-compatible service. Files never touch the MPS server — two-step
presign pattern matches existing product upload architecture.
2026-02-27 12:57:10 -05:00
d3c6a3cdd2 bump GIT_HASH to 64a71f0 2026-02-27 11:55:06 -05:00
64a71f0bb2 fix: set crossOrigin on images for sandbox canvas export
CDN already serves Access-Control-Allow-Origin: * but browsers only
send the Origin header when img.crossOrigin is set. sandbox.js now
sets crossOrigin="anonymous" on all images at init and after SPA
navigation so canvas.toBlob() can read pixels for export.
2026-02-27 11:54:52 -05:00
9dda6ca36b docs: add sandbox mode architecture and filter reference
Architecture diagrams, export pipeline, face detection pipeline,
filter preset reference table, CORS requirements, localStorage keys,
mobile behavior, and stacking with shop color filter.
2026-02-27 11:43:10 -05:00
ed108fe8f5 bump GIT_HASH to fd55a1a 2026-02-27 11:41:43 -05:00
fd55a1a5e2 feat: add sandbox mode with creative filter toolbar
Client-side creative filter system for MPS shops. When enabled, visitors
see a floating toolbar with 32 filter presets (basic, warm, cool, dramatic,
color shifts, Instagram-style, SVG), 7 adjustment sliders, image/video
canvas export, and on-demand MediaPipe face detection (eye glow, face mask).

All processing is browser-side. Server only stores the sandbox_mode toggle.
Filters apply to individual media elements (stacks with shop color_filter).
localStorage persists filter state. Watch mode SPA re-applies on navigation.
2026-02-27 11:41:21 -05:00
f0af5c564a bump GIT_HASH to 1d5b473 2026-02-26 22:32:26 -05:00
1d5b473276 fix: add SQLite busy_timeout and fix regex escape warning
Set PRAGMA busy_timeout=30000 on SQLite connections to prevent
"database is locked" errors under concurrent access. Fix invalid
escape sequence in email regex by using raw string.
2026-02-26 22:32:11 -05:00
738497ed2f bump GIT_HASH to 1c6bcbe 2026-02-26 17:28:24 -05:00
1c6bcbe94d feat: add line charts, keyword tracking, and referrer domains to analytics
- Add SVG line charts for session duration, engagement, and bounce rate trends (28 days)
- Track referrer_domain and referrer_query on PageSession (new migration)
- Refactor classify_referrer() to extract domain and search engine query params
- Surface internal search keywords (ShopSearchRequest) on shop analytics
- Show top referrer domains and search engine queries on both analytics pages
- Add wide bar row CSS modifier for longer domain labels
2026-02-26 17:28:05 -05:00
6acebd8835 bump GIT_HASH to da29e6d 2026-02-26 16:42:35 -05:00
da29e6d3e2 feat: add permalink to product analytics page 2026-02-26 16:42:11 -05:00
9e7b4c6896 bump GIT_HASH to 40828ca 2026-02-24 12:14:14 -05:00
40828ca31b fix: cap ring related products to 45 and add 502 retry in watch mode
Previously every watch JSON request and product page load called
get_ring_related_products with forward=len(ring), loading ALL products
in the shop from the DB. For large rings this caused worker memory to
spike, triggering uwsgi reload-on-rss kills and producing 502s.

Cap related items to 42 forward + 3 backward (matching the sidebar
display limit). The watch JSON endpoint now accepts a dir query param
so the client can signal travel direction — when going backward the
allocation flips to 3 forward + 42 backward.

Also adds retry with backoff (up to 2 retries, 1s/2s delay) in
fetchWatchData() for transient 502/503/504 responses during worker
recycling.
2026-02-24 12:13:45 -05:00
7145f2b313 fix: disable pytest-timeout — crashes xdist workers on CI
Both signal and thread timeout methods break pytest-xdist: signal
corrupts workers, thread kills them mid-operation. The real fix was
timeout=10 on PayPal requests. Global test timeout is not needed.
2026-02-23 15:24:00 -05:00
97f5c8fbf4 fix: use thread timeout method for pytest-xdist compatibility
signal-based timeouts corrupt xdist worker processes, causing mass
test failures on CI. thread method is the documented alternative
for parallel test execution.
2026-02-23 15:08:17 -05:00
ec32f655ed bump GIT_HASH to cdc6582 2026-02-23 14:51:43 -05:00
cdc6582a8f fix: add timeouts to PayPal sandbox tests and pytest-timeout safety net
PayPal sandbox tests used requests.post/get() with no timeout parameter.
When the CI runner at build.unturf.com cannot reach api-m.sandbox.paypal.com,
these calls block until TCP timeout (minutes), stalling all xdist workers
and causing the 1-hour CI timeout.

- Add timeout=10 to all requests calls in PayPal sandbox tests
- Add pytest-timeout with 60s default so no single test can hang the suite
2026-02-23 14:51:19 -05:00
c4926a8ab9 bump GIT_HASH to f4fc645 2026-02-23 12:50:53 -05:00
f4fc645586 feat: expand ring to all positions so fresh mode has full inventory
The related content list only showed 42 forward items. When fresh mode
hid watched items, most were filtered out leaving a sparse list with
huge gaps. Now all ring positions are included so fresh mode always
has plenty of unwatched items to display.

- Pass forward=len(ring) to get_ring_related_products in all callers
- Cap forward in the function to prevent backward/forward overlap
- Add loading="lazy" to thumbnails beyond offset 7 (template + JS)
- Override mobile overflow hiding in fresh mode so unwatched overflow
  items remain visible
2026-02-23 12:50:33 -05:00
4311a9b2b6 bump GIT_HASH to f00a43c 2026-02-21 15:57:32 -05:00
f00a43c208 fix: replace hamburger icon with dice on random button 2026-02-21 15:57:18 -05:00
3bd615c9bf bump GIT_HASH to 5b4d6ff 2026-02-21 14:25:11 -05:00
5b4d6ffc13 fix: call update_s3_acls after karaoke track upload
All three karaoke upload paths (single product upload, async backfill,
CLI backfill script) now call update_s3_acls after writing tracks to S3,
ensuring karaoke track ACLs match the product's current visibility.
2026-02-21 14:24:59 -05:00
01a69db9e7 docs: auto-push when new tests cover new paths and suite is green 2026-02-21 13:57:14 -05:00
2d5a3622a3 bump GIT_HASH to a867602 2026-02-21 13:56:47 -05:00
a8676025e4 fix: update_s3_acls now handles karaoke tracks + add tests
update_s3_acls silently skipped instrumentals/vocals because no s3_key
resolution existed for those file keys. Added generic fallback to
construct s3_path/file_key for any unhandled key.

Added 11 tests: unit (ACL parity across visibility levels, file_keys
membership, update_s3_acls call count), integration (visibility change
propagates to karaoke ACLs), functional (watch JSON and content page
karaoke URL embedding).
2026-02-21 13:56:33 -05:00
f0ca717ff9 fix: restructure ring controls into 2-row grid layout
The controls bar was overflowing — "Next ▶" wrapped vertically. Switch
from single-row grid-auto-flow to a 3-column grid so items split into
two rows: toggles (Fresh/Reverse/Autoplay) on top, action buttons
(Karaoke/Random/Next) below. Reduce gap from 16px to 6×12px.
2026-02-21 12:35:48 -05:00
5c9513daa2 feat: add karaoke toggle to watch mode ring controls
Moves vocal isolation track switching from the pop-out player into the
ring header controls so users can cycle Original/Instrumentals/Vocals
while watching. Preserves playback position on track switch, resets to
Original on SPA navigation, and adds K keyboard shortcut.
2026-02-21 11:12:02 -05:00
68e716ca74 bump GIT_HASH to 94166be 2026-02-19 20:00:36 -05:00
94166be988 fix: sign /upload with empty body — API skips body parsing for streams
The upload endpoint streams the request body in chunks without buffering,
so raw_body stays empty on the server side. The HMAC must sign against an
empty body string to match. _sign_from_disk (which hashed file contents)
produced signatures the server could never verify — every upload got 401.
2026-02-19 20:00:23 -05:00
c3072c2a6e update GIT_HASH to 2e73c09 2026-02-19 19:18:54 -05:00
2e73c09e63 fix: use upload path instead of inline base64 to prevent API OOM
The inline path loaded entire base64 payloads into API memory via
Plug.Parsers, then decoded them again for UploadStore — 4 concurrent
45MB files consumed ~420MB on a 768MB droplet, pushing BEAM into swap
and timing out all RPCs.

Now uses POST /upload (streaming 64KB chunks, constant memory) then
references upload_ids in the execute call. The JSON body drops from
~60MB to ~500 bytes. Zero base64 encoding on the request side.
2026-02-19 19:18:36 -05:00
3fbef65b20 docs: add karaoke pipeline architecture to CLAUDE.md
Disk-backed vocal isolation pipeline, 3.698GB upstream limit,
concurrency, retry, and caller documentation.
2026-02-19 17:56:23 -05:00
046fb1342d update GIT_HASH to 5736ff3 2026-02-19 14:54:57 -05:00
5736ff3330 fix: disk-backed karaoke pipeline to prevent OOM kills
process_karaoke held 4+ copies of every media file in memory
simultaneously (raw bytes, base64, JSON serialization). With 14-16
concurrent workers on large files, memory exploded past 3.2GB RSS.

Replace all in-memory buffers with a disk-backed pipeline:
- Stream S3 download to tmpfile instead of .read()
- Build JSON payload on disk with streaming base64 encoding
- Incremental HMAC-SHA256 signing from disk
- Stream HTTP request body from file, response to file
- Decode artifacts to tmpfiles one at a time for S3 upload

Peak request-side memory drops from ~1GB/worker to ~64KB/worker.
Function signature unchanged — all callers work without modification.
2026-02-19 13:49:45 -05:00
e87bb0b0c3 fix: alembic migration uses table name mps_page_session, not class name
op.add_column takes the actual SQLite table name, not the SQLAlchemy
model class name. PageSession maps to mps_page_session via CLASS_TO_TABLE.
2026-02-19 11:42:18 -05:00
a0d17cbf5f trigger deploy: run pending alembic migration 2026-02-19 10:37:40 -05:00
67e34b08ad fix: create fresh SQLite engine after fork instead of reusing dead fd
The grandchild process closes all inherited fds (3..maxfd) to detach
from uWSGI. session_factory().get_bind() returned a session bound to
the now-dead SQLite fd. Capture the DB URL before forking, create a
fresh engine in the child.
2026-02-19 10:20:38 -05:00
b8955c0459 revert: remove alembic from CI deploy, fix belongs in salt states 2026-02-19 09:28:43 -05:00
b21eb4a1e0 deploy: run alembic as uwsgi user 2026-02-19 09:27:01 -05:00
6e5bf4ad8d deploy: run alembic migrations after highstate 2026-02-19 09:26:05 -05:00
7e620f39f1 uncap concurrency, skip per-item validate_keys in backfill 2026-02-18 19:46:28 -05:00
535e57896b cap backfill concurrency at 2 — 14 simultaneous large payloads crashes API 2026-02-18 19:43:16 -05:00
696a7b853b uncap backfill concurrency — use key's actual limit 2026-02-18 19:30:46 -05:00
e1abca4a66 throttle backfill to 1 worker, increase stderr log to 2000 chars
4 concurrent workers was overwhelming the unsandbox API (429/502).
Cap at 1 worker for now. Also increase stderr truncation from 500
to 2000 chars to see full ffmpeg errors.
2026-02-18 19:16:22 -05:00
2908511a8c double-fork backfill to fully detach from uWSGI worker
Single fork left the child in the uWSGI process group with inherited
HTTP sockets, causing 502s. Double-fork with setsid() and fd cleanup
so the grandchild is fully orphaned to init.
2026-02-18 13:04:46 -05:00
0ad82d5314 add backfill vocal isolation button to shop settings 2026-02-18 11:34:08 -05:00
44e0210d5e extract audio to WAV before running voxsplit
voxsplit only accepts WAV input — use ffmpeg to extract/convert
audio from mp4/mp3/etc before splitting vocals/instrumentals.
2026-02-18 10:18:45 -05:00