Commit Graph

77 Commits

Author SHA1 Message Date
434e926f77 style: use primary color for scrollbar thumbs
All checks were successful
Build and Push Backend Image / build (push) Successful in 17s
Build and Push Frontend Image / build (push) Successful in 4m0s
40% opacity at rest, 70% on hover, adapts to light/dark theme.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 14:42:15 +01:00
7a9ce0c3b1 fix: explicitly style html root scrollbar for Firefox
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 14:39:44 +01:00
ff1e1f6679 feat: style scrollbars globally using theme colors
Thin scrollbars using --border for thumb and transparent track,
with --muted-foreground on hover. Uses both scrollbar-color (Firefox)
and ::-webkit-scrollbar (Chrome/Safari).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 14:38:02 +01:00
648123fab5 feat: mobile-optimize admin section
- Layout: sidebar hidden on mobile, replaced with horizontal top nav strip
- Tables: overflow-x-auto + hide secondary columns (email/category/dates/
  plays/likes) on small screens; show email inline under name on mobile
- Forms: grid-cols-2 → grid-cols-1 sm:grid-cols-2 on all admin forms
- Markdown editor: Write/Preview tab toggle on mobile, side-by-side on sm+
- Padding: p-3 sm:p-6 on all admin pages for tighter mobile layout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 14:36:52 +01:00
a7fafaf7c5 refactor: replace native select with shadcn Select for user role in admin
All checks were successful
Build and Push Backend Image / build (push) Successful in 1m10s
Build and Push Frontend Image / build (push) Successful in 5m8s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 13:34:39 +01:00
b71d7dc559 refactor: remove duplicate utils/utils.ts, consolidate into utils.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 13:31:35 +01:00
f764e27d59 fix: shrink flyout account card, remove online indicator
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 13:29:33 +01:00
d7eb2acc6c fix: match mobile flyout header height to main header (h-16)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 13:27:31 +01:00
fb38d6b9a9 fix: constrain admin layout to container width matching rest of site
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 13:22:15 +01:00
d021acaf0b feat: add admin user edit page with avatar, banner, and photo gallery
- Backend: adminGetUser query returns user + photos; adminUpdateUser now
  accepts avatarId/bannerId; new adminAddUserPhoto and adminRemoveUserPhoto
  mutations; AdminUserDetailType added to GraphQL schema
- Frontend: /admin/users/[id] page for editing name, avatar, banner, and
  managing the model photo gallery (upload multiple, delete individually)
- Admin users list: edit button per row linking to the detail page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 13:18:43 +01:00
e06a1915f2 fix: remove backdrop-blur overlay causing blurry text site-wide
The full-screen glassmorphism overlay had backdrop-blur-[0.5px] which
triggered GPU compositing on the entire viewport, degrading subpixel
text rendering inconsistently. Also use globalThis.fetch (not SvelteKit
fetch) when forwarding session token in admin SSR calls to avoid header
stripping.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 13:11:54 +01:00
ebab3405b1 fix: forward session token in admin SSR load functions
Admin list queries (users, videos, articles) were using getGraphQLClient
without auth credentials, causing silent 403s on server-side loads. Now
extract session_token cookie and pass it to getAuthClient so the backend
sees the admin session on SSR requests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 12:56:47 +01:00
ad7ceee5f8 fix: resolve lint errors from ACL/admin implementation
- Remove unused requireOwnerOrAdmin import from videos.ts
- Remove unused requireAuth import from users.ts
- Remove unused GraphQLError import from articles.ts
- Replace URLSearchParams with SvelteURLSearchParams in admin users page
- Apply prettier formatting to all changed files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 12:35:11 +01:00
c1770ab9c9 feat: role-based ACL + admin management UI
Backend:
- Add acl.ts with requireAuth/requireRole/requireOwnerOrAdmin helpers
- Gate premium videos from unauthenticated users in videos query/resolver
- Fix updateVideoPlay to verify ownership before updating
- Add admin mutations: adminListUsers, adminUpdateUser, adminDeleteUser
- Add admin mutations: createVideo, updateVideo, deleteVideo, setVideoModels, adminListVideos
- Add admin mutations: createArticle, updateArticle, deleteArticle, adminListArticles
- Add deleteComment mutation (owner or admin only)
- Add AdminUserListType to GraphQL types
- Fix featured filter on articles query

Frontend:
- Install marked for markdown rendering
- Add /admin/* section with sidebar layout and admin-only guard
- Admin users page: paginated table with search, role filter, inline role change, delete
- Admin videos pages: list, create form, edit form with file upload and model assignment
- Admin articles pages: list, create form, edit form with split-pane markdown editor
- Add admin nav link in header (desktop + mobile) for admin users
- Render article content through marked in magazine detail page
- Add all admin GraphQL service functions to services.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 12:31:33 +01:00
e200514347 refactor: move flyout to left side, restore logo, remove close button
Some checks failed
Build and Push Backend Image / build (push) Failing after 47s
Build and Push Frontend Image / build (push) Successful in 5m13s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 12:34:57 +01:00
d7057c3681 refactor: improve mobile flyout header
- Replace inline mobile dropdown with sliding flyout panel from right
- Hide burger menu on lg breakpoint, desktop auth buttons use hidden lg:flex
- Add backdrop overlay with opacity transition
- Remove logo from flyout panel header
- Fix backdrop div accessibility with role="presentation"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 11:44:23 +01:00
9bef2469d1 refactor: rename RecordedEvent fields to snake_case
deviceIndex → device_index
deviceName → device_name
actuatorIndex → actuator_index
actuatorType → actuator_type

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 11:04:36 +01:00
97269788ee feat: add shared @sexy.pivoine.art/types package and fix type safety across frontend/backend
- Create packages/types with shared TypeScript domain model interfaces (User, Video, Model, Article, Comment, Recording, etc.)
- Wire both frontend and backend packages to use @sexy.pivoine.art/types via workspace:*
- Update backend Pothos objectRef types to use shared interfaces instead of inline types
- Update frontend $lib/types.ts to re-export from shared package
- Fix all type errors introduced by more accurate nullable types (avatar/banner as string|null UUIDs, author nullable, events/device_info as object[])
- Add artist_name to comment user select in backend resolver
- Widen utility function signatures (getAssetUrl, getUserInitials, calcReadingTime) to accept null/undefined

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 11:01:11 +01:00
fd4050a49f refactor: remove directus.ts shim, import directly from api
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 10:19:05 +01:00
efc7624ba3 style: apply prettier formatting to all files
All checks were successful
Build and Push Backend Image / build (push) Successful in 46s
Build and Push Frontend Image / build (push) Successful in 5m12s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 22:27:54 +01:00
18116072c9 feat: add formidable ESLint + Prettier linting setup
Some checks failed
Build and Push Backend Image / build (push) Successful in 47s
Build and Push Frontend Image / build (push) Has been cancelled
- Root-level eslint.config.js (flat config): typescript-eslint,
  eslint-plugin-svelte, eslint-config-prettier, @eslint/js
- Root-level prettier.config.js with prettier-plugin-svelte
- svelte-check added to frontend for Svelte/TS type checking
- lint, lint:fix, format, format:check, check scripts in root
  and both packages
- All 60 lint errors fixed across backend and frontend:
  - Consistent type imports
  - Removed unused imports/variables
  - Added keys to all {#each} blocks for Svelte performance
  - Replaced mutable Set/Map with SvelteSet/SvelteMap
  - Fixed useless assignments and empty catch blocks
- 64 remaining warnings are intentional any usages in the
  Pothos/Drizzle GraphQL resolver layer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 22:24:55 +01:00
662e3e8fe2 fix: model join date used join_date but API returns date_created
All checks were successful
Build and Push Backend Image / build (push) Successful in 17s
Build and Push Frontend Image / build (push) Successful in 4m13s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 21:42:34 +01:00
fa159feffa fix: remove black border below video controls
Some checks failed
Build and Push Backend Image / build (push) Successful in 16s
Build and Push Frontend Image / build (push) Has been cancelled
- video: inline → block w-full (eliminates baseline descender gap)
- media-controller: fill parent container with absolute inset-0 w-full h-full

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 21:41:07 +01:00
124f0bfb22 fix: video src used movie.id but movie is already the UUID string
All checks were successful
Build and Push Backend Image / build (push) Successful in 17s
Build and Push Frontend Image / build (push) Successful in 4m18s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 21:27:06 +01:00
df89cc59f5 fix: use preview transform for home page video teasers
Some checks failed
Build and Push Backend Image / build (push) Has been cancelled
Build and Push Frontend Image / build (push) Has been cancelled
thumbnail (300x300 square) was double-cropping inside the wide h-48
container. preview (800px, aspect-ratio preserved) lets object-cover
do the only crop, matching the videos and model pages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 21:24:59 +01:00
05cb6a66e3 fix: image transforms via Sharp, model photos crash, video duration
All checks were successful
Build and Push Backend Image / build (push) Successful in 46s
Build and Push Frontend Image / build (push) Successful in 5m7s
- Backend: add Sharp image transform endpoint (/assets/:id?transform=X)
  with presets: mini(64), thumbnail(200), preview(480), medium(960), banner(1280)
  Transformed images are cached as webp next to originals
- Frontend: fix model photos crash (p.directus_files_id → p)
- Frontend: fix model banner URL (data.model.banner.id → data.model.banner)
- Frontend: fix video duration display (video.movie.duration → video.movie_file?.duration)
  across models/[slug], videos, videos/[slug], and home pages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 20:56:33 +01:00
9d7afbe1b5 feat: replace Directus with custom Node.js GraphQL backend
Removes Directus 11 and replaces it with a lean, purpose-built backend:
- packages/backend/: Fastify v5 + GraphQL Yoga v5 + Pothos (code-first)
  with Drizzle ORM, Redis sessions (session_token cookie), argon2 auth,
  Nodemailer, fluent-ffmpeg, and full gamification system ported from bundle
- Frontend: @directus/sdk replaced by graphql-request v7; services.ts fully
  rewritten with identical signatures; directus.ts now re-exports from api.ts
- Cookie renamed directus_session_token → session_token
- Dev proxy target updated 8055 → 4000
- compose.yml: Directus service removed, backend service added (port 4000)
- Dockerfile.backend: new multi-stage image with ffmpeg
- Dockerfile: bundle build step and ffmpeg removed from frontend image
- data-migration.ts: one-time script to migrate all Directus/sexy_ tables

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 18:07:18 +01:00
de16b64255 fix: use env object from \$env/dynamic/public instead of named imports
All checks were successful
Build and Push Docker Image to Gitea / build-and-push (push) Successful in 5m13s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 17:19:26 +01:00
865787fb45 feat: switch PUBLIC_API_URL and PUBLIC_URL to dynamic env for runtime configurability
Some checks failed
Build and Push Docker Image to Gitea / build-and-push (push) Failing after 3m3s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 17:01:37 +01:00
3915dbc115 fix: .env inclusion
All checks were successful
Build and Push Docker Image to Gitea / build-and-push (push) Successful in 5m31s
2026-03-04 16:47:54 +01:00
83ca9d4fb5 chore: update all dependencies to latest versions
Some checks failed
Build and Push Docker Image to Gitea / build-and-push (push) Failing after 46s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 16:43:56 +01:00
225b9d41f5 chore: clean up repo and fix docker compose configuration
- Remove outdated docs (COMPOSE.md, DOCKER.md, QUICKSTART.md, REBUILD_GUIDE.md)
- Remove build.sh, compose.production.yml, gamification-schema.sql, directus.yaml
- Simplify compose.yml for local dev (remove env var indirection)
- Add directus.yml schema snapshot and schema.sql from VPS
- Add schema:export and schema:import scripts to package.json
- Ignore .env files (vars set via compose environment)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 16:36:49 +01:00
ad83fb553a feat: switch to dynamic env for Umami to allow runtime configuration
All checks were successful
Build and Push Docker Image to Gitea / build-and-push (push) Successful in 4m41s
2026-02-21 11:47:32 +01:00
13c6977e59 feat: add PUBLIC_UMAMI_SCRIPT variable and integrate into layout
Some checks failed
Build and Push Docker Image to Gitea / build-and-push (push) Failing after 2m13s
2026-02-21 11:05:30 +01:00
c85fa7798e chore: remove Letterspace newsletter integration and all LETTERSPACE_* variables 2026-02-21 10:56:43 +01:00
ce30eca574 chore: new imprint address 2026-02-21 10:37:19 +01:00
6724afa939 fix: use correct Directus preset parameter for asset transforms
All checks were successful
Build and Push Docker Image to Gitea / build-and-push (push) Successful in 6m4s
Changed ?transform= to ?key= so Directus storage asset presets
(mini, thumbnail, preview, medium, banner) are actually applied.
Previously all images were served at full resolution.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 08:42:38 +01:00
e744d1e40f fix: clean up stale sorter entries and fix battery reactivity
All checks were successful
Build and Push Docker Image to Gitea / build-and-push (push) Successful in 5m44s
ButtplugMessageSorter never deleted entries from _waitingMsgs after
resolving, causing unsolicited DeviceList messages (with reused Ids)
to be swallowed. Also fix battery level not updating in UI by accessing
the device through the Svelte $state proxy array.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 13:07:13 +01:00
27d86fff8b fix: vite wasm rollup
All checks were successful
Build and Push Docker Image to Gitea / build-and-push (push) Successful in 5m33s
2026-02-06 15:08:32 +01:00
6ea4ed1933 feat: upgrade buttplug package to protocol v4 and WASM v10
All checks were successful
Build and Push Docker Image to Gitea / build-and-push (push) Successful in 7m30s
Upgrade the buttplug TypeScript client from class-based v3 protocol to
interface-based v4 protocol, and the Rust/WASM server from the monolithic
buttplug 9.0.9 crate to the split buttplug_core/buttplug_server/
buttplug_server_device_config 10.0.0 crates.

TypeScript changes:
- Messages are now plain interfaces with msgId()/setMsgId() helpers
- ActuatorType → OutputType, SensorType → InputType
- ScalarCmd/RotateCmd/LinearCmd → OutputCmd, SensorReadCmd → InputCmd
- Client.ts → ButtplugClient.ts, new DeviceCommand/DeviceFeature files
- Devices getter returns Map instead of array
- Removed class-transformer/reflect-metadata dependencies

Rust/WASM changes:
- Split imports across buttplug_core, buttplug_server, buttplug_server_device_config
- Removed ButtplugServerDowngradeWrapper (use ButtplugServer directly)
- Replaced ButtplugFuture/ButtplugFutureStateShared with tokio::sync::oneshot
- Updated Hardware::new for new 6-arg signature
- Uses git fork (valknarthing/buttplug) to fix missing wasm deps in buttplug_core

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 14:46:47 +01:00
fed2dd65e5 feat: display play count on video detail page
All checks were successful
Build and Push Docker Image to Gitea / build-and-push (push) Successful in 4m32s
- Add play count display below video title with play icon
- Query actual plays count from sexy_video_plays table for accuracy
- Apply same pattern as likes_count for consistency
- Show singular/plural ("play" vs "plays") based on count

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-20 19:23:36 +01:00
756d767d59 fix: align sharing button height with like button
Some checks failed
Build and Push Docker Image to Gitea / build-and-push (push) Has been cancelled
Remove size="sm" from SharingPopupButton to use default button height (h-9)
instead of small size (h-8), ensuring consistent button heights across all
action buttons on video, model, and article pages.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-20 19:20:30 +01:00
Valknar XXX
eed4318e49 fix: increase models grid max-width for better layout
Changed from max-w-lg to max-w-3xl to accommodate 3-column grid
with better spacing and prevent cramped appearance.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-13 08:07:16 +01:00
Valknar XXX
f45a9678f7 fix: correct photo data structure access in model page
Photos are returned from backend as { directus_files_id: fileObject }
but frontend was trying to access p.id directly. Updated to use
p.directus_files_id.id to properly display model photos.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-13 07:21:06 +01:00
Valknar XXX
8937f71260 fix: update models grid from 2 to 3 columns on medium screens
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-13 07:03:09 +01:00
Valknar XXX
036fc35c9a fix: add custom endpoint for getVideoBySlug to bypass permissions
- Add GET /sexy/videos/:slug endpoint in bundle that bypasses Directus permissions
- Simplify getVideoBySlug to use the new custom endpoint instead of direct API call
- Fixes "Video not found" error on production for public video pages
- Custom endpoint uses database query like other public endpoints (/sexy/models, etc)
- Ensures videos are accessible to unauthenticated users while respecting upload_date

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 17:13:56 +01:00
Valknar XXX
b2aa8a57ca fix: import directusApiUrl in services.ts
Added missing import of directusApiUrl from directus module to fix
ReferenceError in getVideoBySlug function.

Fixes: 5333bfd (fix: use native fetch for getVideoBySlug)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 07:23:46 +01:00
Valknar XXX
5333bfd87a fix: use native fetch for getVideoBySlug to fix authenticated SSR
- Convert getVideoBySlug from Directus SDK to native fetch API
- Fixes serialization errors when authenticated users view video pages
- Remove unused readUsers import from @directus/sdk
- Directus SDK returns non-serializable objects with circular refs
- Native fetch returns plain JSON that works with SvelteKit SSR

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 05:36:16 +01:00
Valknar XXX
cd4c33a3f2 fix: safely handle empty/null models array in getVideoBySlug
- Added null/undefined checks before mapping models array
- Filter out invalid entries in models junction table data
- Default to empty array if models is not an array
- Prevents "Cannot read properties of undefined" errors on video pages

This fix ensures the video detail page works even when model associations
are missing or malformed in the database.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 04:33:52 +01:00
Valknar XXX
0b07da8e64 fix: replace Directus SDK with native fetch for featured content
Replace customEndpoint() with native fetch in getFeaturedModels() and
getFeaturedVideos() to return plain JSON instead of non-serializable
Directus SDK objects. This resolves the SvelteKit serialization error:
"Cannot stringify arbitrary non-POJOs".

Changes:
- Use native fetch with PUBLIC_URL instead of getDirectusInstance()
- Return plain JSON via response.json() instead of SDK request objects
- Remove JSON.parse(JSON.stringify()) serialization hack from +page.server.ts
- Rename fetch parameter to fetchFn for clarity

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 23:54:56 +01:00