Commit Graph

54 Commits

Author SHA1 Message Date
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
d9b48ff490 fix: use slug field for model lookup instead of name concat
Change GET /sexy/models/:slug endpoint to query u.slug directly
instead of concatenating LOWER(first_name || ' ' || last_name).
This matches the actual slug field in the directus_users table.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 03:23:03 +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
Valknar XXX
10ab7a65df fix: ensure home page data is serializable
Fixed serialization error by converting Directus SDK response objects to
plain JSON using JSON.parse(JSON.stringify()).

This resolves the error:
'Data returned from load while rendering / is not serializable:
Cannot stringify arbitrary non-POJOs (data.models)'

Also improved performance by using Promise.all to fetch models and videos
in parallel instead of sequentially.
2025-10-28 23:41:40 +01:00
Valknar XXX
b883867b15 feat: add recording sharing and community features
Backend Changes:
- Added original_recording_id field to sexy_recordings table to track duplicates
- Added indexes for original_recording_id and public fields
- Implemented /sexy/community-recordings endpoint to list public shared recordings
- Implemented /sexy/recordings/:id/duplicate endpoint to duplicate community recordings
- Community recordings filtered by status=published AND public=true
- Duplication creates a private draft copy for the current user

Frontend Changes:
- Added leaderboard and profile quick links to play view header
- Added navigation buttons for better UX on play page
- Added translations: my_profile, anonymous, load_more

Database Schema:
- ALTER TABLE sexy_recordings ADD COLUMN original_recording_id uuid
- Created foreign key and indexes for efficient queries

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 14:32:39 +01:00
Valknar XXX
9002eb768b fix: simplify tags handling in recordings endpoint
Removed complex JSON parsing logic for tags field. Now that sexy_recordings.tags
is a json column type (matching sexy_videos), Directus/Knex handles the conversion
automatically. Simple `tags || []` is sufficient.

Related to gamification implementation where sexy_recordings.tags was changed
from text[] to json type to match videos table implementation.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 14:07:29 +01:00
Valknar XXX
47016a2d5c fix: parse tags JSON string in recording creation
Handle tags field when sent as JSON string by parsing it into
an array before insertion. Fixes "malformed array literal" error
when saving recordings with tags.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 13:44:35 +01:00
Valknar XXX
6b3d770182 feat: display gamification stats on user profile page
Add gamification card to user profile showing:
- Total weighted points and rank
- Recordings and playbacks count
- Unlocked achievements with icons and dates
- Link to leaderboard

Updates user profile page server load to fetch gamification data
from /api/sexy/gamification/user/:id endpoint.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 13:36:32 +01:00
Valknar XXX
064894b8bb feat: add gamification hooks, leaderboard UI, and translations
- Added Directus hooks for automatic point awards:
  - Recording creation/publishing (50 points)
  - Recording featured status (100 points bonus)
  - Comments on recordings (5 points)
- Created /leaderboard route with full UI
  - Server-side data loading with authentication guard
  - Responsive design with medal emojis for top 3
  - User stats display (recordings, plays, achievements)
  - Pagination support
  - "How It Works" info section
- Added comprehensive gamification translations
- Time-weighted scoring displayed for rankings
- Automatic achievement checking on point awards

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 13:29:34 +01:00
Valknar XXX
8f09244188 feat: implement recording-focused gamification system
- Database schema with 5 new tables:
  - sexy_recording_plays: Track recording playback
  - sexy_user_points: Individual point actions
  - sexy_achievements: Predefined achievement definitions
  - sexy_user_achievements: User progress tracking
  - sexy_user_stats: Cached statistics for leaderboards
- Seeded 17 achievements across 4 categories
- Backend gamification helper functions with time-weighted scoring
- Three new API endpoints:
  - GET /sexy/gamification/leaderboard
  - GET /sexy/gamification/user/:id
  - GET /sexy/gamification/achievements
- Recording play endpoints with automatic point awards
- Time-decay formula (λ=0.005) for balanced rankings

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 13:21:51 +01:00
Valknar XXX
74e68c32dc feat: implement user profile pages with comment avatar links
Added user profile feature allowing authenticated users to view profiles of other users. Key changes:

**New Routes:**
- `/users/[id]/+page.server.ts` - Server-side load function with authentication guard and user data fetching
- `/users/[id]/+page.svelte` - User profile UI component displaying avatar, stats, and bio

**Features:**
- Authentication required - redirects to /login if not authenticated
- Shows user display name (first_name + last_name or email fallback)
- Displays join date, location, and description
- Statistics: comments count and likes count
- "Edit Profile" button visible only for own profile (links to /me)
- Responsive layout with avatar placeholder for users without profile images

**Comment Integration:**
- Updated video comment section to link user avatars to their profiles
- Added hover effects on avatars (ring-primary/40 transition)
- Username in comments now clickable and links to `/users/[id]`

**Translations:**
- Added `profile` section to en.ts locales with:
  - member_since: "Member since {date}"
  - comments: "Comments"
  - likes: "Likes"
  - edit: "Edit Profile"
  - activity: "Activity"

**Design:**
- Simplified layout (no cover banner) compared to model profiles
- Peony background with card-based UI
- Primary color theme with gradient accents
- Consistent with existing site design patterns

This creates a clear distinction between:
- Model profiles (`/models/[slug]`) - public, content-focused
- User profiles (`/users/[id]`) - authenticated only, viewer-focused

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 12:54:45 +01:00
Valknar XXX
9503f8d0aa fix: add authentication guard to /me page
- Redirect unauthenticated users to /login
- Add error handling for getFolders API call
- Prevent 500 errors from accessing undefined user properties

This fixes the issue where clicking the header button to access the dashboard
would show a 500 error for unauthenticated users.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 12:17:58 +01:00
Valknar XXX
b8fdcb1193 fix: remove featured filter from models endpoint
The directus_users table doesn't have a 'featured' column, causing
SQL errors when the /sexy/models endpoint tried to filter by it.
Removed the featured parameter check to fix 500 errors on homepage.

Error was: "column u.featured does not exist"

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 11:30:14 +01:00
Valknar XXX
6232cc58f0 fix: add null safety to logout button component
Added null checks for user.name property to prevent SSR errors when
the property is undefined. Made name and avatar properties optional
in the User interface and added fallbacks to use email when name is
not available.

Fixes TypeError: Cannot read properties of undefined (reading 'split')
that was occurring during server-side rendering.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 11:21:42 +01:00
Valknar XXX
cd6e8b7b3d feat: migrate all API calls to custom bundle endpoints
This commit completes the migration of all API calls from direct Directus
SDK calls to custom bundle endpoints, bypassing Directus permissions using
direct database queries via Knex.

Backend changes (packages/bundle/src/endpoint/index.ts):
- Added /sexy/models endpoint with optional featured and limit filters
- Added /sexy/models/:slug endpoint for single model lookup
- Added /sexy/videos endpoint with optional model_id filter
- Added /sexy/articles endpoint with optional featured filter
- All endpoints use Knex for direct database access, bypassing permissions
- Endpoints handle nested relationships (photos, banner, models, movie, author)

Frontend changes (packages/frontend/src/lib/services.ts):
- Updated getVideos() to use /sexy/videos custom endpoint
- Updated getVideosForModel() to use /sexy/videos with model_id query param
- Updated getFeaturedVideos() to use /sexy/videos with limit param
- Updated getArticles() to use /sexy/articles custom endpoint
- Updated getModelBySlug() to use /sexy/models/:slug custom endpoint
- Simplified service layer by moving filtering logic to backend

Benefits:
- Complete bypass of Directus permissions layer
- Consistent API pattern across all endpoints
- Centralized business logic in backend
- Cleaner frontend service code
- All endpoints tested and working

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 11:17:51 +01:00
Valknar XXX
a2240f8315 feat: implement Knex-based /sexy/models endpoint
- Use Knex queries directly to bypass Directus permissions
- Query directus_users with role=Model using database directly
- Fetch related photos and banner using joins
- Successfully returns empty array when no models exist
- Resolves permissions issues

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 11:08:53 +01:00
Valknar XXX
0b08ce5900 wip: add custom /sexy/models endpoint to bypass permissions
- Add /sexy/models endpoint in bundle with accountability: null
- Update getModels() and getFeaturedModels() to use custom endpoint
- Still experiencing permissions issues with field access
- Need to configure Directus public role permissions

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 11:04:34 +01:00
Valknar XXX
3f38633863 feat: add comprehensive analytics dashboard for content creators
Backend changes:
- Added /sexy/analytics endpoint to fetch detailed creator analytics
- Calculates total likes, plays, completion rates, and avg watch times
- Groups analytics by date for timeline visualization
- Provides video-specific performance metrics

Frontend changes:
- Added Analytics TypeScript types and service function
- Created Analytics tab in /me dashboard (visible only for Models)
- Displays overview stats: total videos, likes, and plays
- Added detailed video performance table with:
  - Individual video metrics
  - Color-coded completion rates (green >70%, yellow >40%, red <40%)
  - Average watch time per video
  - Links to video pages

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 10:42:06 +01:00
Valknar XXX
54f0758196 feat: add sorting by likes/plays and display stats on model profiles
- Added "Most Liked" and "Most Played" sorting options to video listing
- Display total likes and plays on model profile pages
- Show individual video like/play counts on model profile video cards
- Added i18n translation keys for new sort options

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 10:37:16 +01:00
Valknar XXX
51dd7a159a feat: implement video likes and play tracking UI
Video Detail Page (/videos/[slug]):
- Load like status from server on page load
- Add functional like button with heart icon
- Show likes count with real-time updates
- Track video plays when user clicks play
- Record watch progress every 10 seconds
- Mark video as completed at 90% watched
- Show toast notifications for like/unlike actions
- Require authentication to like videos

Video Listing Page (/videos):
- Display play count badge on video thumbnails
- Show play icon with count in top-right corner

Features:
- Like/unlike videos with live count updates
- Play tracking with analytics data
- Progress tracking for completion metrics
- Authentication-gated liking functionality
- User-friendly toast feedback

All UI components working and integrated with backend API
2025-10-28 10:31:06 +01:00
Valknar XXX
da267eb66d feat: add video likes and plays tracking system
Backend (Database & API):
- Add likes_count, plays_count, views_count to sexy_videos table
- Create sexy_video_likes junction table for user-video likes
- Create sexy_video_plays table for analytics tracking
- Add POST /sexy/videos/:id/like endpoint
- Add DELETE /sexy/videos/:id/like endpoint
- Add GET /sexy/videos/:id/like-status endpoint
- Add POST /sexy/videos/:id/play endpoint
- Add PATCH /sexy/videos/:id/play/:playId endpoint

Frontend (Types & Services):
- Update Video interface with counter fields
- Add VideoLikeStatus, VideoLikeResponse, VideoPlayResponse types
- Add likeVideo() service function
- Add unlikeVideo() service function
- Add getVideoLikeStatus() service function
- Add recordVideoPlay() service function
- Add updateVideoPlay() service function

Next: Implement UI components for like button and play count display
2025-10-28 10:29:02 +01:00
Valknar XXX
e891e0de0a fix: replace bits-ui Select with native HTML select
- Remove bits-ui Select component dependency
- Use native HTML select element for device selection
- Simplify state management (single mappings Map)
- Fix selection handling with direct onchange event
- Add visual indicator for exact name matches
- Resolves selection not working issue
2025-10-28 05:51:43 +01:00
Valknar XXX
a959186de7 fix: proper state binding for Select component in device mapping
- Add separate selectedValues state map for Select component binding
- Update handleDeviceSelect to manage both mappings and selectedValues
- Bind currentSelected directly to Select.Root selected prop
- Pass full selected object in onSelectedChange callback
- Ensures Select component properly reflects user selections in Svelte 5
2025-10-28 05:48:25 +01:00
Valknar XXX
6e94ec99bc fix: select component reactivity in device mapping dialog
- Create new Map instance in handleDeviceSelect to trigger Svelte 5 reactivity
- Fixes issue where user selection wasn't updating the UI
- Ensures device mappings update correctly when user chooses from dropdown
2025-10-28 05:45:16 +01:00
Valknar XXX
9d9f72dec1 feat: add device mapping UI for playback compatibility
- Create DeviceMappingDialog component with compatibility checking
- Check actuator type compatibility between recorded and connected devices
- Auto-map devices by name with fallback to compatible devices
- Show device mapping dialog before playback starts
- Store and use device mappings during playback execution
- Update executeEvent to use mapped devices instead of name matching
- Validate all devices are mapped before starting playback

Features:
- Visual device pairing interface
- Compatibility badges showing actuator types
- Exact name match highlighting
- Auto-mapping with smart fallback
- Real-time mapping validation

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 05:41:37 +01:00
Valknar XXX
50ceda94b7 feat: add recording playback functionality
- Add getRecording service function to fetch single recording
- Update play page server to load recording from URL parameter
- Implement playback engine with event scheduling
- Add playback controls (play, pause, stop, seek)
- Display playback progress bar with clickable seek
- Show recording metadata and stats during playback
- Match recorded events to connected devices by name and actuator type
- Convert normalized values back to device scale for playback

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 05:28:04 +01:00
Valknar XXX
a252da6d9d fix: recording save functionality and authentication
- Fix artist_name null handling in header component with email fallback
- Fix authentication in recording endpoints to use req.accountability
- Change duration field type from integer to double precision for millisecond precision
- Add createRecording service function with proper authentication
- Update play page to use fetch API for recording saves

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 05:16:36 +01:00
Valknar XXX
5bd2d9c215 fix: handle undefined user name in UI components
- Added null check in getUserInitials function to return "??" for undefined names
- Fixed logout button to handle undefined user.name when displaying first name
- Prevents 500 errors when rendering components for users without names

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 04:48:41 +01:00
Valknar XXX
e226a47028 fix: Docker Compose volume mapping and schema compatibility
- Fixed Directus extension volume mapping to point to bundle root instead of dist folder
- Changed directus_users.slug field to nullable to allow existing users without slugs
- These changes enable proper loading of the bundle extension in Docker environment

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 04:38:48 +01:00
Valknar XXX
aa4e376490 feat: add buttplug device recording feature (Phase 1 & 2)
Implemented complete infrastructure for recording, saving, and managing
buttplug device patterns with precise event timing.

**Phase 1: Backend & Infrastructure**
- Added Directus schema for sexy_recordings collection with all fields
  (id, status, user_created, title, description, slug, duration, events,
  device_info, tags, linked_video, featured, public)
- Created REST API endpoints in bundle extension:
  * GET /sexy/recordings - list user recordings with filtering
  * GET /sexy/recordings/:id - get single recording
  * POST /sexy/recordings - create new recording with validation
  * PATCH /sexy/recordings/:id - update recording (owner only)
  * DELETE /sexy/recordings/:id - soft delete by archiving
- Added TypeScript types: RecordedEvent, DeviceInfo, Recording
- Created frontend services: getRecordings(), deleteRecording()
- Built RecordingCard component with stats, device info, and actions
- Added Recordings tab to /me dashboard page with grid layout
- Added i18n translations for recordings UI

**Phase 2: Recording Capture**
- Implemented recording state management in /play page
- Added Start/Stop Recording buttons with visual indicators
- Capture device events with precise timestamps during recording
- Normalize actuator values (0-100) for cross-device compatibility
- Created RecordingSaveDialog component with:
  * Recording stats display (duration, events, devices)
  * Form inputs (title, description, tags)
  * Device information preview
- Integrated save recording API call from play page
- Added success/error toast notifications
- Automatic event filtering during recording

**Technical Details**
- Events stored as JSON array with timestamp, deviceIndex, deviceName,
  actuatorIndex, actuatorType, and normalized value
- Device metadata includes name, index, and capability list
- Slug auto-generated from title for SEO-friendly URLs
- Status workflow: draft → published → archived
- Permission checks: users can only access own recordings or public ones
- Frontend uses performance.now() for millisecond precision timing

**User Flow**
1. User scans and connects devices on /play page
2. Clicks "Start Recording" to begin capturing events
3. Manipulates device sliders - all changes are recorded
4. Clicks "Stop Recording" to end capture
5. Save dialog appears with recording preview and form
6. User enters title, description, tags and saves
7. Recording appears in dashboard /me Recordings tab
8. Can play back, edit, or delete recordings

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 04:05:09 +01:00
Valknar XXX
e587552fcb feat: better logging 2025-10-26 14:48:30 +01:00
Valknar XXX
56e3bfd3ef Update frontend .env with production URLs for build
- Set PUBLIC_API_URL to https://sexy.pivoine.art/api
- Set PUBLIC_URL to https://sexy.pivoine.art
- These values will be baked into the build via GitHub Actions

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 13:09:24 +01:00
Valknar XXX
c17758b12b docs: beate uhse for freedom 2025-10-26 09:57:45 +01:00
Valknar XXX
3952868712 docs: beate uhse for freedom 2025-10-26 09:35:35 +01:00
Valknar XXX
6d8996fef1 fix: dockerfile 2025-10-26 07:47:22 +01:00
Valknar XXX
9154548cf0 fix: dockerfile 2025-10-26 03:35:29 +01:00
Valknar XXX
dc2a6a16d3 fix: dockerfile 2025-10-26 02:36:04 +02:00
Valknar XXX
4058757389 fix: dockerfile 2025-10-26 02:25:13 +02:00
Valknar XXX
392d42ce2c fix: dockerfile 2025-10-26 02:10:36 +02:00
Valknar XXX
3c04314492 fix: dockerfile 2025-10-26 01:56:26 +02:00
Valknar XXX
75d0039bed fix: pnpm version 2025-10-26 01:50:41 +02:00
Valknar XXX
762241c5e7 fix: pnpm version 2025-10-26 01:41:14 +02:00
Valknar XXX
cc19e57131 fix: newer node image 2025-10-26 00:12:58 +02:00
Valknar XXX
5df6f10968 fix: pnpm prune --prod 2025-10-26 00:00:28 +02:00
Valknar XXX
0ee4d65e7f fix: pnpm ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY 2025-10-25 23:35:32 +02:00