Commit Graph
62 Commits
Author SHA1 Message Date
yusufipek a055f4a8f0 fix(upload): give the Add Video page the upgrade link too
The trial ceiling refusal is drawn twice: the drag-and-drop uploader toasts it,
and the Add Video page writes it into the form as submitError. Only the first
one was routed through the error code, so the page that most uploads go through
printed "Upgrade to get 200 GB" with nothing to click.

submitError now carries whether the failure was the trial ceiling, set in the
same call as the message so the link cannot outlive it, and the three places
that set it hand over the failure they caught.
2026-08-18 12:13:09 +03:00
yusufipk b51e690062 fix: close the findings the test suite surfaced
The suite that landed in #43/#44 was written against existing behaviour, so a
number of tests pinned bugs rather than asserting correct behaviour. This fixes
the production code and moves each of those tests onto the fixed behaviour in
the same change.

Security:

- project-download: derive the archive entry extension from the last path
  segment and restrict it to a short alphanumeric run, so an extensionless
  allowlisted url can no longer contribute a path separator; validate the r2
  branch against the strict proxy-path pattern instead of a `startsWith`, which
  let `/api/upload/video/clip.mp4/../../etc/passwd` through verbatim.
- rate-limit: hash a key or action wider than its column instead of skipping the
  query. Both the guard and the failing INSERT used to answer "allowed", so the
  limit stopped applying entirely. Warn at startup when TRUSTED_PROXY_MODE is
  unset in production.
- video uploads: the file name decides the content type; a client-declared video
  mime no longer makes `payload.exe` acceptable.
- email templates: escape in the helpers rather than relying on every caller,
  with an explicit `rawEmailHtml()` opt-out for the one call site that builds
  markup. `escapeHtml` now covers the single quote.
- CSP: allow loopback object storage outside production only.
- route-access: reach the billing redirect only for the workspace owner. Keying
  it off the owner's billing status alone made the redirect target an oracle for
  whose subscription had lapsed, and sent members to a page they cannot act on.
- search: carry the same billing condition every other read path carries.
- logger: check `err.name` as well as `err.constructor.name`, so a re-thrown,
  deserialised or minified Prisma error is still redacted.
- upload tokens: resolve the signing secret outside the try, so a server booted
  without one fails loudly instead of reporting every grant as a forgery.
- invitations: never downgrade an existing membership, and report a scoped
  invitation that points at nothing as not_found rather than accepted.
- auth: resolve the workspace role for every signed-in caller, so
  checkProjectAccess and computeProjectAccess stop disagreeing about the owner
  who also owns the workspace. The `intent` option is gone with it.
- r2-media-proxy: validate the object key inside the proxy so the guard travels
  with the function; delete the unused, unanchored `mediaUrlToR2Key`.
- r2: sign the content type into presigned PUT grants.

Correctness:

- frame rate snapping picks the nearest standard, not the first within
  tolerance, so 24, 30 and 60 fps are reachable at all.
- a version upload registers its Bunny cleanup as soon as bunny-init answers, so
  a failed tus upload no longer leaves a billed video behind.
- deleting videos clears storage before the rows, so a refused DELETE leaves a
  retryable row rather than an orphaned object.
- an expired upload session can be cancelled, which is what releases its quota.
- `voice/` joins the delete allowlist, so a voice note can be removed by the
  module that wrote it.
- a failed CORS write propagates instead of being mistaken for an empty config
  and replacing the bucket's rules.
- filtering projects by workspace no longer hides projects the unfiltered call
  returns.
- upload retries skip aborts and permanent 4xx; progress no longer divides by
  zero.
- reply edits no longer clear the comment's tag; optimistic resolve rolls back
  to the state it replaced; the delete snapshot is captured once.
- assorted UI fixes: duplicate React keys, double-click guards reading stale
  closures, the tag list fetched twice per load, a failed member list rendering
  as an empty one, a stale "Initializing upload..." beside a failure, and a
  registration banner pointing at an email that never arrives.

Consistency and access:

- the two download routes answer 404 for an id belonging to another tenant, as
  the comment export route already did. A caller who does belong still gets 403.
- accessible names for the share-link password field, the guest name gates, the
  version dialog inputs and the comment-tag controls.

Repository health:

- the runner image installs production dependencies only.
- a setup file for the unit project restores stubbed env centrally.
- native tsconfig path resolution replaces vite-tsconfig-paths.
- `uploadBytesWithProgress` exists once.
- admin stats bill Bunny storage to the workspace owner like every other
  quota, gate on the configured flag, wire up the single-flight guard and count
  the statuses that belonged to no bucket.
- `r2Client.destroy()` releases the presign client too.
- `prepare` tolerates a production install, where husky is absent.
2026-07-26 18:53:54 +07:00
Yusuf İpek 81285681dc Merge pull request #37 from yusufipk/worktree-fix-download-notice
feat(downloads): let the download progress toast be minimized
2026-07-25 12:59:18 +03:00
yusufipk 481728b93d feat(downloads): let the download progress toast be minimized
The download progress toast sits in the bottom-right corner on top of the
comment composer, blocking the voice-recording button and the comment box for
the whole duration of a download.

Render it through toast.custom so it can be collapsed to a small pill (percent
+ spinner) and expanded again while the download keeps running. The minimized
choice sticks for the rest of the session. The sonner <li> is click-through, so
only the panel itself covers the controls underneath.

Also dismiss the panel on failure — it had duration: Infinity and used to stay
on screen forever after an error.
2026-07-25 16:56:29 +07:00
yusufipk 2bad0a249f refactor(video): share R2 playback URL resolution and guard drift resync
- move resolveR2PlaybackUrl into lib/video-upload-validation.ts so the compare
  view and the main video page cannot drift apart
- validate the resolved URL with isPlayableVideoUrl before it reaches <video src>
- add a per-player cooldown so a follower that cannot keep up is not seeked
  every second, which would stutter rather than correct
2026-07-25 16:48:39 +07:00
eehkayandClaude Fable 5 d830c9a386 fix: play r2 direct uploads in the compare versions view
The compare page predates the r2 upload provider: r2 versions fell
through to a URL-safety check that throws on app-relative upload URLs,
so their panels rendered nothing and registered no player — the shared
controls drove an empty list and nothing played.

- add an R2Panel mapping a plain video element over the app upload
  route to the shared adapter, using the same playback-url resolution
  as the main video page
- make play/pause state detection work without the YouTube API loaded
  (numeric fallback), so bunny/r2-only comparisons can pause
- re-sync panels that drift more than 350ms from the source player
  once per second so playback stays aligned, not just starts aligned

Verified against the running app: both versions play in lockstep
(0.000s measured drift), pause together, and seek together.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-19 10:18:22 -07:00
yusufipk 5821f73d38 feat: show live progress while downloading named files
Bunny/cross-origin downloads are fetched into a blob before saving, which
on large files or slow connections looked stuck (spinner only). Stream the
body through a counting transform and show real byte progress in a toast:
per-file percent for single downloads and file N/M + percent for bulk.

- Progress is measured from Content-Length + received bytes (not estimated).
- The blob is assembled by the browser from the stream (can be disk-backed),
  so we don't accumulate chunks in the JS heap.
- Only the blob path shows a toast; same-origin (R2/S3/MinIO) and the >10 GB
  fallback use the browser's native download UI.
2026-07-10 22:27:53 +07:00
yusufipk 8d7d064647 feat: make asset downloads opt-in via "Include assets" toggle
Project/selected downloads now include only videos by default. Add an
"Include assets" checkbox toggle to both download dropdowns (default off)
that adds b-rolls and other attached assets to the download when enabled.

- buildProjectDownloadManifest gains an includeAssets option (default false).
- Download route reads ?assets=1 and passes it through.
2026-07-10 21:03:34 +07:00
yusufipk 57c5a127d1 feat: move videos to another project (single + bulk)
Add a "Move to project" action in the video card dropdown and the
selection-mode toolbar. Videos (with their versions, comments, assets and
video-scoped share links) can be moved into another project in the same
workspace.

- New GET/POST /api/projects/[projectId]/videos/move: GET lists manageable
  destination projects in the workspace; POST performs the move.
- Requires canEdit on both source and destination; same-workspace only.
- Move runs in an interactive transaction that re-asserts source ownership
  atomically (updateMany guarded by projectId) to avoid a TOCTOU race, and
  returns 409 on conflict. GET is rate-limited ('api').
2026-07-10 20:55:12 +07:00
yusufipk 654d3a6bc7 style: format download dropdown item (prettier) 2026-07-10 20:08:15 +07:00
yusufipk 34e72f6cbb fix: bulk video download (original quality, latest version by default)
- Accept source=auto in the version download route (was 400 Bad Request),
  so bulk/project downloads of Bunny videos no longer fail.
- Bulk/project downloads now request the original (uncompressed) Bunny file
  so quality never drops (was source=auto which could fall back to compressed).
- Project/selected downloads default to the latest version of each video and
  add a separate "All versions" option in the download dropdowns.
2026-07-10 20:04:18 +07:00
yusufipk d500dcb042 fix: preserve newlines/paragraph breaks in rendered comments
Comment content was stored with newlines intact but rendered inside <p>
elements with default white-space, collapsing line breaks into single
spaces. Add whitespace-pre-wrap (and break-words) to the comment/reply
render wrappers in the comments pane and the compare-versions view.
2026-07-10 19:33:03 +07:00
yusufipk 82932c6b22 fix: scope select-all to current page
"Select all" previously selected every video across every page, which is
too easy to trigger by accident when the user only meant the videos
visible on the current page. Scope select/deselect to the current page's
videos and relabel the button to "Select page"/"Deselect page" when the
project spans multiple pages.
2026-06-27 13:46:05 +02:00
yusufipk 95dcf92d8b fix: clamp page after bulk video delete
When every video on the current page was bulk-deleted, router.refresh()
re-queried the same out-of-range page and rendered "No videos yet" even
though earlier pages still had videos. Clamp to the last valid page based
on the remaining video count, falling back to refresh in place.
2026-06-27 13:35:08 +02:00
yusufipk 52e4169db2 feat: add project bulk download and bulk video delete
Add a "Download project" / "Download selected" flow that builds a
server-side manifest of downloadable media, plus a selection mode with
bulk delete for project videos.

Gate viewer downloads behind a new project allowDownloads setting
(default off, opt-in). Admins can always download; enabling on a public
project allows anonymous visitors to download. Enforce the setting on
every download surface (manifest, version, asset, watch, video routes)
via canDownloadProjectMedia.

Add rate limits for the manifest endpoint, host allowlisting for direct
download URLs, and configurable file/byte caps.

Closes #16
Closes #19
2026-06-27 13:24:05 +02:00
yusufipk 00124bc7c2 feat: bulk video uploads and S3 asset video support (#18)
Add multi-file drag-and-drop queues for project videos and the assets pane, and route asset video uploads through S3/R2 when direct Bunny uploads are disabled.
2026-06-13 23:24:21 +02:00
yusufipk 4bf6e821af feat: enable S3 video uploads and update related configurations
- Added support for self-hosted S3 video uploads with new environment variables: OPENFRAME_ENABLE_S3_VIDEO_UPLOADS and OPENFRAME_MAX_VIDEO_UPLOAD_BYTES.
- Updated .env.example and .env.docker.example to reflect new configuration options.
- Enhanced Content Security Policy to include origins for S3-compatible storage.
- Updated dependencies for AWS SDK to support new features.
- Refactored upload logic to accommodate both Bunny and S3 upload providers.
- Updated documentation to clarify the usage of direct uploads and S3 configurations.
- Closes #11
2026-05-27 17:04:39 +02:00
yusufipek 7058ea9f87 style: fix prettier issues after syncing with master 2026-04-25 20:24:17 +03:00
yusufipek ef7402bc23 Merge origin/master into pr-9-review 2026-04-25 20:21:40 +03:00
Enes Köksal 3cfea40fbd refactor: eslint and prettier conflict will be resolved and formatted 2026-04-23 17:05:43 +03:00
Yusuf İpek 066c4247db feat: add sorting functionality to project videos and update search parameters 2026-04-22 15:31:09 +03:00
Yusuf İpek b1b1715578 Refactor registration and dashboard features to support invite codes and Bunny uploads
- Moved registration logic to a new client component for better separation of concerns.
- Integrated invite code requirement based on feature flags in the registration process.
- Enhanced dashboard functionality to conditionally enable Bunny uploads based on feature flags.
- Updated various components and API routes to check for Bunny uploads and Stripe billing feature flags.
- Added new feature flag utilities for managing feature toggles in the application.
2026-04-08 18:46:12 +03:00
Yusuf İpek 6f22b0bf8b feat(billing): integrate Stripe for subscription management and billing access
- Added billing-related fields to the User model in the database.
- Implemented functions for managing billing access, including trial periods and subscription statuses.
- Created new billing utility functions for Stripe integration.
- Updated onboarding page to include billing overview and workspace creation eligibility.
- Enhanced route access checks to require billing access for certain actions.
- Implemented cleanup scripts for expired billing workspaces and associated media.
- Updated header component to conditionally show app navigation based on billing access.
- Added new migrations for billing-related database changes.
2026-04-08 17:50:40 +03:00
Yusuf İpek 8a4553d351 feat(video-upload): enhance video file handling with drag-and-drop support and validation 2026-03-06 09:04:40 +03:00
Yusuf İpekandClaude Sonnet 4.6 9a86ad02a1 perf(compare): optimize timeline rendering with RAF loop and direct DOM mutation
Replace per-frame React state updates with ref-based tracking and direct DOM
mutation for the progress bar, playhead, and timecode. React state is now
throttled to ~4 commits/sec (~250ms), eliminating unnecessary re-renders on
every animation frame during playback.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-02-28 16:00:58 +03:00
Yusuf İpek 4ea6099508 feat(bunny-cdn): refactor CDN hostname resolution and update asset URLs for improved flexibility 2026-02-26 11:53:03 +03:00
Yusuf İpek 6dd44953a3 feat(video-upload): add global drag-and-drop uploader with project selection and permission gating 2026-02-26 10:31:51 +03:00
Yusuf İpek 15fa9452a4 feat(video-player): add Bunny original-source fallback and quality option, plus approval/download UI tweaks 2026-02-26 10:02:11 +03:00
Yusuf İpek 76d37d02e5 feat: make media cleanup best-effort with warning summaries and enforce video/workspace management access 2026-02-25 18:59:02 +03:00
Yusuf İpek 6eea327083 refactor(dashboard): centralize access guards and split interactive pages into client components 2026-02-25 16:42:47 +03:00
Yusuf İpek a2b07b3e19 feat: add approvals workflow and unified member invitation management across projects, workspaces, and videos 2026-02-25 16:24:45 +03:00
Yusuf İpek ffa55d7dcc feat(video): lazy-load version comments and add ETag-based comment caching 2026-02-24 16:34:16 +03:00
Yusuf İpek fe7235052e Add guest upload tokens and share-session aware permissions 2026-02-23 18:17:22 +03:00
Yusuf İpek 9058317247 feat(share): add video-level secure share links with password unlock and session-based watch/comment access 2026-02-23 17:11:32 +03:00
Yusuf İpek d15e5192ac chore: clean up lint/type issues and tighten comment media URL validation 2026-02-23 16:06:43 +03:00
Yusuf İpek e30b4a5b19 feat: harden Bunny upload flow, migrate Bunny playback to hls.js, and add Bunny storage admin stats 2026-02-22 13:03:45 +03:00
Yusuf İpek 10164069dd feat: Add URL safety check to prevent rendering iframes with non-HTTP/HTTPS protocols. 2026-02-22 09:56:18 +03:00
Yusuf İpek 0f24bcfe6c feat: Implement direct video file uploads via Bunny.net and TUS protocol, adding a new API route and UI for file selection. 2026-02-22 09:55:08 +03:00
Yusuf İpek 5547346082 feat: Implement multi-panel video comparison with shared playback controls, timeline comments, and YouTube API integration. 2026-02-22 07:56:27 +03:00
Yusuf İpek 2a20b449a8 refactor: Migrate dashboard project listing to client components, adding URL-driven filtering, sorting, and pagination. 2026-02-20 15:50:50 +03:00
Yusuf İpek 1bb1c8e574 feat: Enhance dashboard responsiveness across various pages and update project visibility icons for improved clarity. 2026-02-20 14:28:43 +03:00
Yusuf İpek 7e55c578df remove experimental vimeo video provider support 2026-02-15 12:55:36 +03:00
Yusuf İpek 2888f7de98 feat: add date sorting to projects and videos
Add toggle controls to sort projects and videos by update date in both
the dashboard project list and individual project video grids. Extract
ProjectContent to a dedicated client component to enable client-side
sorting state management.
2026-02-13 10:40:55 +03:00
Yusuf İpek ca65cf8f58 feat: Implement video version management API, enable comment tag editing, and enhance video duration display to include hours. 2026-02-10 14:48:15 +03:00
Yusuf İpek b22fd1476f feat: optimize bundle size and improve image loading
- Unify video page components into single VideoPageContent component
  - Reduces ~2300 lines of duplicated code between dashboard and watch pages
  - Uses mode prop ('dashboard' | 'watch') to handle differences

- Move shadcn to devDependencies (CLI tool, not needed at runtime)

- Remove radix-ui meta-package (components already imported individually)

- Replace <img> tags with next/image for automatic optimization
  - Add remotePatterns config for YouTube, Vimeo, Unsplash domains
  - Update video-card.tsx, videos/new/page.tsx, component-example.tsx

- Add next/dynamic for KeyboardShortcutsModal (lazy load on demand)
2026-02-10 14:08:52 +03:00
Yusuf İpek f8afec0291 refactor: Update fetch requests to disable caching for video data retrieval 2026-02-07 18:41:07 +03:00
Yusuf İpek 46d4e0c7d7 refactor: Implement optimistic UI updates for comment handling with mutation tracking 2026-02-07 18:34:00 +03:00
Yusuf İpek 7141763bc6 refactor: Optimize comment deletion handling with optimistic UI updates 2026-02-07 18:23:46 +03:00
Yusuf İpek 42839d4d69 refactor: Update response handling for comments and uploads to access nested data structure 2026-02-07 18:17:46 +03:00
Yusuf İpek f6044f3c24 refactor: Update API response handling to access nested data structure 2026-02-07 17:59:37 +03:00