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
This commit is contained in:
Valknar XXX
2025-10-28 10:29:02 +01:00
parent e891e0de0a
commit da267eb66d
3 changed files with 290 additions and 1 deletions

View File

@@ -16,7 +16,7 @@ import {
readComments,
aggregate,
} from "@directus/sdk";
import type { Article, Model, Recording, Stats, User, Video } from "$lib/types";
import type { Article, Model, Recording, Stats, User, Video, VideoLikeStatus, VideoLikeResponse, VideoPlayResponse } from "$lib/types";
import { PUBLIC_URL } from "$env/static/public";
import { logger } from "$lib/logger";
@@ -630,3 +630,95 @@ export async function getRecording(id: string, fetch?: typeof globalThis.fetch)
{ id },
);
}
export async function likeVideo(videoId: string) {
return loggedApiCall(
"likeVideo",
async () => {
const directus = getDirectusInstance(fetch);
return directus.request<VideoLikeResponse>(
customEndpoint({
method: "POST",
path: `/sexy/videos/${videoId}/like`,
})
);
},
{ videoId }
);
}
export async function unlikeVideo(videoId: string) {
return loggedApiCall(
"unlikeVideo",
async () => {
const directus = getDirectusInstance(fetch);
return directus.request<VideoLikeResponse>(
customEndpoint({
method: "DELETE",
path: `/sexy/videos/${videoId}/like`,
})
);
},
{ videoId }
);
}
export async function getVideoLikeStatus(videoId: string, fetch?: typeof globalThis.fetch) {
return loggedApiCall(
"getVideoLikeStatus",
async () => {
const directus = getDirectusInstance(fetch);
return directus.request<VideoLikeStatus>(
customEndpoint({
method: "GET",
path: `/sexy/videos/${videoId}/like-status`,
})
);
},
{ videoId }
);
}
export async function recordVideoPlay(videoId: string, sessionId?: string) {
return loggedApiCall(
"recordVideoPlay",
async () => {
const directus = getDirectusInstance(fetch);
return directus.request<VideoPlayResponse>(
customEndpoint({
method: "POST",
path: `/sexy/videos/${videoId}/play`,
body: JSON.stringify({ session_id: sessionId }),
headers: { "Content-Type": "application/json" },
})
);
},
{ videoId }
);
}
export async function updateVideoPlay(
videoId: string,
playId: string,
durationWatched: number,
completed: boolean
) {
return loggedApiCall(
"updateVideoPlay",
async () => {
const directus = getDirectusInstance(fetch);
return directus.request(
customEndpoint({
method: "PATCH",
path: `/sexy/videos/${videoId}/play/${playId}`,
body: JSON.stringify({
duration_watched: durationWatched,
completed,
}),
headers: { "Content-Type": "application/json" },
})
);
},
{ videoId, playId, durationWatched, completed }
);
}

View File

@@ -89,6 +89,9 @@ export interface Video {
upload_date: Date;
premium?: boolean;
featured?: boolean;
likes_count?: number;
plays_count?: number;
views_count?: number;
}
export interface Comment {
@@ -155,3 +158,25 @@ export interface Recording {
featured?: boolean;
public?: boolean;
}
export interface VideoLikeStatus {
liked: boolean;
}
export interface VideoPlayRecord {
id: string;
video_id: string;
duration_watched?: number;
completed: boolean;
}
export interface VideoLikeResponse {
liked: boolean;
likes_count: number;
}
export interface VideoPlayResponse {
success: boolean;
play_id: string;
plays_count: number;
}