refactor(frontend): split endpoint handling
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { Menu, X } from 'lucide-react';
|
||||
import { login, logout } from "../utils/endpoints.ts";
|
||||
import { login, logout } from "../utils/api/users.ts";
|
||||
import { Dropdown, DropdownItem } from "./Dropdown.tsx";
|
||||
import { GoogleLogin } from '@react-oauth/google';
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import clsx from "clsx";
|
||||
import { formatTime, stringToDate, dateToTimeAgo } from "../../utils/utils.ts";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { isThumbnailAvailable } from "../../utils/endpoints.ts";
|
||||
import { isThumbnailAvailable } from "../../utils/api/client.ts";
|
||||
import { MoreVertical, Pencil, Trash2 } from "lucide-react";
|
||||
|
||||
type VideoCardProps = {
|
||||
|
||||
@@ -4,7 +4,8 @@ import Topbar from '../components/Topbar'
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import {useEffect, useState} from "react";
|
||||
import type {User} from "../utils/types";
|
||||
import { getUser, getStreamStatus } from "../utils/endpoints";
|
||||
import { getUser } from "../utils/api/users";
|
||||
import { getStreamStatus } from "../utils/api/stream";
|
||||
|
||||
const MainLayout = () => {
|
||||
const [sidebarToggled, setSidebarToggled] = useState(false);
|
||||
|
||||
@@ -5,7 +5,8 @@ import PlaybackSlider from "./../components/video/PlaybackSlider";
|
||||
import ClipRangeSlider from "./../components/video/ClipRangeSlider";
|
||||
import ConfigBox from "../components/video/ConfigBox.tsx";
|
||||
import ExportWidget from "../components/video/ExportWidget.tsx";
|
||||
import { compress, getJob } from "../utils/endpoints"
|
||||
import { compress } from "../utils/api/media"
|
||||
import { getJob } from "../utils/api/jobs"
|
||||
import type { VideoMetadata } from "../utils/types.ts";
|
||||
import Box from "../components/Box.tsx";
|
||||
import MetadataBox from "../components/video/MetadataBox.tsx";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { ArrowLeft, Check } from "lucide-react";
|
||||
import { getClipById, patchClip } from "../utils/endpoints.ts";
|
||||
import { getClipById, patchClip } from "../utils/api/clips.ts";
|
||||
import Box from "../components/Box.tsx";
|
||||
import BlueButton from "../components/buttons/BlueButton.tsx";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { ArrowLeft, Check } from "lucide-react";
|
||||
import { getVodById, patchVod } from "../utils/endpoints.ts";
|
||||
import { getVodById, patchVod } from "../utils/api/vods.ts";
|
||||
import Box from "../components/Box.tsx";
|
||||
import BlueButton from "../components/buttons/BlueButton.tsx";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import VideoCard from "../components/video/VideoCard";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { getClips, deleteClip } from "../utils/endpoints";
|
||||
import { getClips, deleteClip } from "../utils/api/clips";
|
||||
import type { Clip } from "../utils/types";
|
||||
|
||||
const MyClips = () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import VideoCard from "../components/video/VideoCard";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { getVods, deleteVod } from "../utils/endpoints";
|
||||
import { getVods, deleteVod } from "../utils/api/vods";
|
||||
import type { Vod } from "../utils/types";
|
||||
|
||||
const MyVods = () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { Eye, EyeOff, Copy, Check } from "lucide-react";
|
||||
import Box from "../components/Box.tsx";
|
||||
import { getUser } from "../utils/endpoints.ts";
|
||||
import { getUser } from "../utils/api/users.ts";
|
||||
import type { User } from "../utils/types.ts";
|
||||
import { formatLocalDate } from "../utils/utils.ts";
|
||||
|
||||
|
||||
@@ -3,17 +3,11 @@ import { Radio, Check, Bookmark, Scissors, Film, Activity, Loader2, X } from "lu
|
||||
import clsx from "clsx";
|
||||
import Box from "../components/Box.tsx";
|
||||
import BlueButton from "../components/buttons/BlueButton.tsx";
|
||||
import {
|
||||
getUser,
|
||||
getCurrentStream,
|
||||
getStreamHistory,
|
||||
getMarkers,
|
||||
createMarker,
|
||||
deleteMarker,
|
||||
saveSectionByMarkers,
|
||||
clipSection,
|
||||
getJob,
|
||||
} from "../utils/endpoints.ts";
|
||||
import { getUser } from "../utils/api/users.ts";
|
||||
import { getCurrentStream, getStreamHistory } from "../utils/api/stream.ts";
|
||||
import { getMarkers, createMarker, deleteMarker } from "../utils/api/markers.ts";
|
||||
import { saveSectionByMarkers, clipSection } from "../utils/api/media.ts";
|
||||
import { getJob } from "../utils/api/jobs.ts";
|
||||
import type { User, StreamStatus, StreamHistoryItem, Marker, JobResponse } from "../utils/types.ts";
|
||||
import { formatTime, formatLocalDate, stringToDate } from "../utils/utils.ts";
|
||||
|
||||
@@ -134,7 +128,6 @@ const StreamControl = () => {
|
||||
refreshMarkers();
|
||||
const interval = setInterval(refreshMarkers, 5000);
|
||||
return () => clearInterval(interval);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isStreaming, streamStatus?.id]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -2,7 +2,8 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { ArrowLeft, Lock, Maximize, Minimize, Pause, Play, Volume1, Volume2, VolumeX } from "lucide-react";
|
||||
import type { Clip } from "../utils/types";
|
||||
import { AuthError, getClipById, getVideoBlob } from "../utils/endpoints.ts";
|
||||
import { AuthError } from "../utils/api/client.ts";
|
||||
import { getClipById, getVideoBlob } from "../utils/api/clips.ts";
|
||||
import Box from "../components/Box.tsx";
|
||||
import { dateToTimeAgo, formatTime, stringToDate } from "../utils/utils.ts";
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { ArrowLeft, Lock, Maximize, Minimize, Pause, Play, Volume1, Volume2, VolumeX } from "lucide-react";
|
||||
import type { Vod } from "../utils/types";
|
||||
import { AuthError, getVodById, getVodBlob } from "../utils/endpoints.ts";
|
||||
import { AuthError } from "../utils/api/client.ts";
|
||||
import { getVodById, getVodBlob } from "../utils/api/vods.ts";
|
||||
import Box from "../components/Box.tsx";
|
||||
import { dateToTimeAgo, formatTime, stringToDate } from "../utils/utils.ts";
|
||||
|
||||
|
||||
12
frontend/src/utils/api/client.ts
Normal file
12
frontend/src/utils/api/client.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
const API_URL = import.meta.env.VITE_API_URL;
|
||||
|
||||
class AuthError extends Error {
|
||||
constructor() { super("Not authenticated"); this.name = "AuthError"; }
|
||||
}
|
||||
|
||||
const isThumbnailAvailable = async (thumbnailUrl: string): Promise<boolean> => {
|
||||
const response = await fetch(thumbnailUrl, { credentials: 'include' });
|
||||
return response.ok;
|
||||
};
|
||||
|
||||
export { API_URL, AuthError, isThumbnailAvailable };
|
||||
67
frontend/src/utils/api/clips.ts
Normal file
67
frontend/src/utils/api/clips.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import type { APIResponse, Clip } from "../types.ts";
|
||||
import { API_URL, AuthError } from "./client.ts";
|
||||
|
||||
const getClips = async (): Promise<Clip[]> => {
|
||||
const response = await fetch(API_URL + '/api/v1/clips', { credentials: 'include' });
|
||||
|
||||
if (!response.ok) {
|
||||
const errorResult: APIResponse = await response.json();
|
||||
throw new Error(`Failed to fetch clips: ${errorResult.message}`);
|
||||
}
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
return result.data;
|
||||
};
|
||||
|
||||
const getClipById = async (id: string): Promise<Clip | null> => {
|
||||
const response = await fetch(API_URL + `/api/v1/clips/${id}`, { credentials: 'include' });
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 401 || response.status === 403) throw new AuthError();
|
||||
throw new Error(`Failed to fetch clip: ${response.status}`);
|
||||
}
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
return result.data;
|
||||
};
|
||||
|
||||
const getVideoBlob = async (id: string): Promise<Blob> => {
|
||||
const response = await fetch(API_URL + `/api/v1/clips/${id}/media`, { credentials: 'include' });
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 401 || response.status === 403) throw new AuthError();
|
||||
throw new Error(`Failed to fetch video ${id}: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.blob();
|
||||
};
|
||||
|
||||
const patchClip = async (id: number, data: { title?: string; description?: string }): Promise<Clip> => {
|
||||
const response = await fetch(API_URL + `/api/v1/clips/${id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error(`Failed to update clip: ${response.status}`);
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (result.status === 'error') throw new Error(`Failed to update clip: ${result.message}`);
|
||||
|
||||
return result.data;
|
||||
};
|
||||
|
||||
const deleteClip = async (id: number): Promise<void> => {
|
||||
const response = await fetch(API_URL + `/api/v1/clips/${id}`, {
|
||||
method: 'DELETE',
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error(`Failed to delete clip: ${response.status}`);
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (result.status === 'error') throw new Error(`Failed to delete clip: ${result.message}`);
|
||||
};
|
||||
|
||||
export { getClips, getClipById, getVideoBlob, patchClip, deleteClip };
|
||||
15
frontend/src/utils/api/jobs.ts
Normal file
15
frontend/src/utils/api/jobs.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { APIResponse, JobResponse } from "../types.ts";
|
||||
import { API_URL } from "./client.ts";
|
||||
|
||||
const getJob = async (uuid: string): Promise<JobResponse> => {
|
||||
const response = await fetch(API_URL + `/api/v1/jobs/${uuid}`, { credentials: 'include' });
|
||||
|
||||
if (!response.ok) throw new Error(`Failed to fetch job: ${response.status}`);
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (result.status === 'error') throw new Error(`Failed to fetch job: ${result.message}`);
|
||||
|
||||
return result.data;
|
||||
};
|
||||
|
||||
export { getJob };
|
||||
40
frontend/src/utils/api/markers.ts
Normal file
40
frontend/src/utils/api/markers.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import type { APIResponse, Marker } from "../types.ts";
|
||||
import { API_URL } from "./client.ts";
|
||||
|
||||
const getMarkers = async (): Promise<Marker[]> => {
|
||||
const response = await fetch(API_URL + '/api/v1/markers', { credentials: 'include' });
|
||||
if (!response.ok) throw new Error(`Failed to fetch markers: ${response.status}`);
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (result.status === 'error') throw new Error(`Failed to fetch markers: ${result.message}`);
|
||||
|
||||
return result.data;
|
||||
};
|
||||
|
||||
const createMarker = async (message: string): Promise<Marker> => {
|
||||
const response = await fetch(API_URL + '/api/v1/markers', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ message }),
|
||||
});
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (!response.ok || result.status === 'error') throw new Error(`Failed to add marker: ${result.message}`);
|
||||
|
||||
return result.data;
|
||||
};
|
||||
|
||||
const deleteMarker = async (id: number): Promise<void> => {
|
||||
const response = await fetch(API_URL + `/api/v1/markers/${id}`, {
|
||||
method: 'DELETE',
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error(`Failed to delete marker: ${response.status}`);
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (result.status === 'error') throw new Error(`Failed to delete marker: ${result.message}`);
|
||||
};
|
||||
|
||||
export { getMarkers, createMarker, deleteMarker };
|
||||
72
frontend/src/utils/api/media.ts
Normal file
72
frontend/src/utils/api/media.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import type { APIResponse, VideoMetadata } from "../types.ts";
|
||||
import { API_URL } from "./client.ts";
|
||||
|
||||
const compress = async (file: File, options: VideoMetadata): Promise<string> => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('title', options.title);
|
||||
if (options.description) formData.append('description', options.description);
|
||||
formData.append('startPoint', options.startPoint.toString());
|
||||
formData.append('duration', options.duration.toString());
|
||||
if (options.fps) formData.append('fps', options.fps.toString());
|
||||
if (options.width) formData.append('width', options.width.toString());
|
||||
if (options.height) formData.append('height', options.height.toString());
|
||||
if (options.fileSize) formData.append('fileSize', options.fileSize.toString());
|
||||
|
||||
const response = await fetch(API_URL + '/api/v1/media/compress', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error(`Compression failed: ${response.status}`);
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (result.status === 'error') throw new Error(`Compression failed: ${result.message}`);
|
||||
|
||||
return result.data.uuid;
|
||||
};
|
||||
|
||||
const saveSection = async (startTime: string, endTime: string, title?: string, description?: string): Promise<string> => {
|
||||
const response = await fetch(API_URL + '/api/v1/media/save', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ startTime, endTime, title, description }),
|
||||
});
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (!response.ok || result.status === 'error') throw new Error(`Failed to save section: ${result.message}`);
|
||||
|
||||
return result.data.uuid;
|
||||
};
|
||||
|
||||
const saveSectionByMarkers = async (startMarkerId: number, endMarkerId: number, title?: string, description?: string): Promise<string> => {
|
||||
const response = await fetch(API_URL + '/api/v1/media/save/markers', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ startMarkerId, endMarkerId, title, description }),
|
||||
});
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (!response.ok || result.status === 'error') throw new Error(`Failed to save section: ${result.message}`);
|
||||
|
||||
return result.data.uuid;
|
||||
};
|
||||
|
||||
const clipSection = async (duration: number, title?: string, description?: string): Promise<string> => {
|
||||
const response = await fetch(API_URL + '/api/v1/media/clip', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ duration, title, description }),
|
||||
});
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (!response.ok || result.status === 'error') throw new Error(`Failed to clip section: ${result.message}`);
|
||||
|
||||
return result.data.uuid;
|
||||
};
|
||||
|
||||
export { compress, saveSection, saveSectionByMarkers, clipSection };
|
||||
31
frontend/src/utils/api/stream.ts
Normal file
31
frontend/src/utils/api/stream.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { APIResponse, StreamHistoryItem, StreamStatus } from "../types.ts";
|
||||
import { API_URL } from "./client.ts";
|
||||
|
||||
const getStreamStatus = async (): Promise<boolean> => {
|
||||
const response = await fetch(API_URL + '/api/v1/stream/current', { credentials: 'include' });
|
||||
if (!response.ok) return false;
|
||||
const result: APIResponse = await response.json();
|
||||
return result.status === 'success' && result.data?.isStreaming === true;
|
||||
};
|
||||
|
||||
const getCurrentStream = async (): Promise<StreamStatus> => {
|
||||
const response = await fetch(API_URL + '/api/v1/stream/current', { credentials: 'include' });
|
||||
if (!response.ok) throw new Error(`Failed to fetch stream status: ${response.status}`);
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (result.status === 'error') throw new Error(`Failed to fetch stream status: ${result.message}`);
|
||||
|
||||
return result.data;
|
||||
};
|
||||
|
||||
const getStreamHistory = async (userId: number): Promise<StreamHistoryItem[]> => {
|
||||
const response = await fetch(API_URL + `/api/v1/stream/history/${userId}`, { credentials: 'include' });
|
||||
if (!response.ok) throw new Error(`Failed to fetch stream history: ${response.status}`);
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (result.status === 'error') throw new Error(`Failed to fetch stream history: ${result.message}`);
|
||||
|
||||
return result.data;
|
||||
};
|
||||
|
||||
export { getStreamStatus, getCurrentStream, getStreamHistory };
|
||||
42
frontend/src/utils/api/users.ts
Normal file
42
frontend/src/utils/api/users.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import type { APIResponse, User } from "../types.ts";
|
||||
import { API_URL } from "./client.ts";
|
||||
|
||||
const login = async (googleToken: string): Promise<string> => {
|
||||
const response = await fetch(API_URL + '/api/v1/users/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ token: googleToken }),
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error(`Login failed: ${response.status}`);
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (result.status === 'error') throw new Error(`Login failed: ${result.message}`);
|
||||
|
||||
return result.data.token;
|
||||
};
|
||||
|
||||
const logout = async (): Promise<void> => {
|
||||
const response = await fetch(API_URL + '/api/v1/users/logout', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error(`Logout failed: ${response.status}`);
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (result.status === 'error') throw new Error(`Logout failed: ${result.message}`);
|
||||
};
|
||||
|
||||
const getUser = async (): Promise<User | null> => {
|
||||
const response = await fetch(API_URL + '/api/v1/users/me', { credentials: 'include' });
|
||||
if (!response.ok) return null;
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (result.status === 'error') return null;
|
||||
|
||||
return result.data;
|
||||
};
|
||||
|
||||
export { login, logout, getUser };
|
||||
67
frontend/src/utils/api/vods.ts
Normal file
67
frontend/src/utils/api/vods.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import type { APIResponse, Vod } from "../types.ts";
|
||||
import { API_URL, AuthError } from "./client.ts";
|
||||
|
||||
const getVods = async (): Promise<Vod[]> => {
|
||||
const response = await fetch(API_URL + '/api/v1/vods', { credentials: 'include' });
|
||||
|
||||
if (!response.ok) {
|
||||
const errorResult: APIResponse = await response.json();
|
||||
throw new Error(`Failed to fetch vods: ${errorResult.message}`);
|
||||
}
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
return result.data;
|
||||
};
|
||||
|
||||
const getVodById = async (id: string): Promise<Vod | null> => {
|
||||
const response = await fetch(API_URL + `/api/v1/vods/${id}`, { credentials: 'include' });
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 401 || response.status === 403) throw new AuthError();
|
||||
throw new Error(`Failed to fetch vod: ${response.status}`);
|
||||
}
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
return result.data;
|
||||
};
|
||||
|
||||
const getVodBlob = async (id: string): Promise<Blob> => {
|
||||
const response = await fetch(API_URL + `/api/v1/vods/${id}/media`, { credentials: 'include' });
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 401 || response.status === 403) throw new AuthError();
|
||||
throw new Error(`Failed to fetch vod ${id}: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.blob();
|
||||
};
|
||||
|
||||
const patchVod = async (id: number, data: { title?: string; description?: string }): Promise<Vod> => {
|
||||
const response = await fetch(API_URL + `/api/v1/vods/${id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error(`Failed to update vod: ${response.status}`);
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (result.status === 'error') throw new Error(`Failed to update vod: ${result.message}`);
|
||||
|
||||
return result.data;
|
||||
};
|
||||
|
||||
const deleteVod = async (id: number): Promise<void> => {
|
||||
const response = await fetch(API_URL + `/api/v1/vods/${id}`, {
|
||||
method: 'DELETE',
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error(`Failed to delete vod: ${response.status}`);
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (result.status === 'error') throw new Error(`Failed to delete vod: ${result.message}`);
|
||||
};
|
||||
|
||||
export { getVods, getVodById, getVodBlob, patchVod, deleteVod };
|
||||
@@ -1,346 +0,0 @@
|
||||
import type { VideoMetadata, APIResponse, User, Clip, Vod, JobResponse, StreamStatus, StreamHistoryItem, Marker } from "./types.ts";
|
||||
|
||||
const API_URL = import.meta.env.VITE_API_URL;
|
||||
|
||||
export class AuthError extends Error {
|
||||
constructor() { super("Not authenticated"); this.name = "AuthError"; }
|
||||
}
|
||||
|
||||
const login = async (googleToken: string): Promise<string> => {
|
||||
const response = await fetch(API_URL + '/api/v1/users/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ token: googleToken }),
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error(`Login failed: ${response.status}`);
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (result.status === 'error') throw new Error(`Login failed: ${result.message}`);
|
||||
|
||||
return result.data.token;
|
||||
};
|
||||
|
||||
const logout = async (): Promise<void> => {
|
||||
const response = await fetch(API_URL + '/api/v1/users/logout', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error(`Logout failed: ${response.status}`);
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (result.status === 'error') throw new Error(`Logout failed: ${result.message}`);
|
||||
};
|
||||
|
||||
const compress = async (file: File, options: VideoMetadata): Promise<string> => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('title', options.title);
|
||||
if (options.description) formData.append('description', options.description);
|
||||
formData.append('startPoint', options.startPoint.toString());
|
||||
formData.append('duration', options.duration.toString());
|
||||
if (options.fps) formData.append('fps', options.fps.toString());
|
||||
if (options.width) formData.append('width', options.width.toString());
|
||||
if (options.height) formData.append('height', options.height.toString());
|
||||
if (options.fileSize) formData.append('fileSize', options.fileSize.toString());
|
||||
|
||||
const response = await fetch(API_URL + '/api/v1/media/compress', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error(`Compression failed: ${response.status}`);
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (result.status === 'error') throw new Error(`Compression failed: ${result.message}`);
|
||||
|
||||
return result.data.uuid;
|
||||
};
|
||||
|
||||
const getJob = async (uuid: string): Promise<JobResponse> => {
|
||||
const response = await fetch(API_URL + `/api/v1/jobs/${uuid}`, { credentials: 'include' });
|
||||
|
||||
if (!response.ok) throw new Error(`Failed to fetch job: ${response.status}`);
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (result.status === 'error') throw new Error(`Failed to fetch job: ${result.message}`);
|
||||
|
||||
return result.data;
|
||||
};
|
||||
|
||||
const getUser = async (): Promise<User | null> => {
|
||||
const response = await fetch(API_URL + '/api/v1/users/me', { credentials: 'include' });
|
||||
if (!response.ok) return null;
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (result.status === 'error') return null;
|
||||
|
||||
return result.data;
|
||||
};
|
||||
|
||||
const getClips = async (): Promise<Clip[]> => {
|
||||
const response = await fetch(API_URL + '/api/v1/clips', { credentials: 'include' });
|
||||
|
||||
if (!response.ok) {
|
||||
const errorResult: APIResponse = await response.json();
|
||||
throw new Error(`Failed to fetch clips: ${errorResult.message}`);
|
||||
}
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
return result.data;
|
||||
};
|
||||
|
||||
const getClipById = async (id: string): Promise<Clip | null> => {
|
||||
const response = await fetch(API_URL + `/api/v1/clips/${id}`, { credentials: 'include' });
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 401 || response.status === 403) throw new AuthError();
|
||||
throw new Error(`Failed to fetch clip: ${response.status}`);
|
||||
}
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
return result.data;
|
||||
};
|
||||
|
||||
const getVideoBlob = async (id: string): Promise<Blob> => {
|
||||
const response = await fetch(API_URL + `/api/v1/clips/${id}/media`, { credentials: 'include' });
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 401 || response.status === 403) throw new AuthError();
|
||||
throw new Error(`Failed to fetch video ${id}: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.blob();
|
||||
};
|
||||
|
||||
const patchClip = async (id: number, data: { title?: string; description?: string }): Promise<Clip> => {
|
||||
const response = await fetch(API_URL + `/api/v1/clips/${id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error(`Failed to update clip: ${response.status}`);
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (result.status === 'error') throw new Error(`Failed to update clip: ${result.message}`);
|
||||
|
||||
return result.data;
|
||||
};
|
||||
|
||||
const deleteClip = async (id: number): Promise<void> => {
|
||||
const response = await fetch(API_URL + `/api/v1/clips/${id}`, {
|
||||
method: 'DELETE',
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error(`Failed to delete clip: ${response.status}`);
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (result.status === 'error') throw new Error(`Failed to delete clip: ${result.message}`);
|
||||
};
|
||||
|
||||
const getVods = async (): Promise<Vod[]> => {
|
||||
const response = await fetch(API_URL + '/api/v1/vods', { credentials: 'include' });
|
||||
|
||||
if (!response.ok) {
|
||||
const errorResult: APIResponse = await response.json();
|
||||
throw new Error(`Failed to fetch vods: ${errorResult.message}`);
|
||||
}
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
return result.data;
|
||||
};
|
||||
|
||||
const getVodById = async (id: string): Promise<Vod | null> => {
|
||||
const response = await fetch(API_URL + `/api/v1/vods/${id}`, { credentials: 'include' });
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 401 || response.status === 403) throw new AuthError();
|
||||
throw new Error(`Failed to fetch vod: ${response.status}`);
|
||||
}
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
return result.data;
|
||||
};
|
||||
|
||||
const getVodBlob = async (id: string): Promise<Blob> => {
|
||||
const response = await fetch(API_URL + `/api/v1/vods/${id}/media`, { credentials: 'include' });
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 401 || response.status === 403) throw new AuthError();
|
||||
throw new Error(`Failed to fetch vod ${id}: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.blob();
|
||||
};
|
||||
|
||||
const patchVod = async (id: number, data: { title?: string; description?: string }): Promise<Vod> => {
|
||||
const response = await fetch(API_URL + `/api/v1/vods/${id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error(`Failed to update vod: ${response.status}`);
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (result.status === 'error') throw new Error(`Failed to update vod: ${result.message}`);
|
||||
|
||||
return result.data;
|
||||
};
|
||||
|
||||
const deleteVod = async (id: number): Promise<void> => {
|
||||
const response = await fetch(API_URL + `/api/v1/vods/${id}`, {
|
||||
method: 'DELETE',
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error(`Failed to delete vod: ${response.status}`);
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (result.status === 'error') throw new Error(`Failed to delete vod: ${result.message}`);
|
||||
};
|
||||
|
||||
const getStreamStatus = async (): Promise<boolean> => {
|
||||
const response = await fetch(API_URL + '/api/v1/stream/current', { credentials: 'include' });
|
||||
if (!response.ok) return false;
|
||||
const result: APIResponse = await response.json();
|
||||
return result.status === 'success' && result.data?.isStreaming === true;
|
||||
};
|
||||
|
||||
const isThumbnailAvailable = async (thumbnailUrl: string): Promise<boolean> => {
|
||||
const response = await fetch(thumbnailUrl, { credentials: 'include' });
|
||||
return response.ok;
|
||||
};
|
||||
|
||||
const getCurrentStream = async (): Promise<StreamStatus> => {
|
||||
const response = await fetch(API_URL + '/api/v1/stream/current', { credentials: 'include' });
|
||||
if (!response.ok) throw new Error(`Failed to fetch stream status: ${response.status}`);
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (result.status === 'error') throw new Error(`Failed to fetch stream status: ${result.message}`);
|
||||
|
||||
return result.data;
|
||||
};
|
||||
|
||||
const getStreamHistory = async (userId: number): Promise<StreamHistoryItem[]> => {
|
||||
const response = await fetch(API_URL + `/api/v1/stream/history/${userId}`, { credentials: 'include' });
|
||||
if (!response.ok) throw new Error(`Failed to fetch stream history: ${response.status}`);
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (result.status === 'error') throw new Error(`Failed to fetch stream history: ${result.message}`);
|
||||
|
||||
return result.data;
|
||||
};
|
||||
|
||||
const getMarkers = async (): Promise<Marker[]> => {
|
||||
const response = await fetch(API_URL + '/api/v1/markers', { credentials: 'include' });
|
||||
if (!response.ok) throw new Error(`Failed to fetch markers: ${response.status}`);
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (result.status === 'error') throw new Error(`Failed to fetch markers: ${result.message}`);
|
||||
|
||||
return result.data;
|
||||
};
|
||||
|
||||
const createMarker = async (message: string): Promise<Marker> => {
|
||||
const response = await fetch(API_URL + '/api/v1/markers', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ message }),
|
||||
});
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (!response.ok || result.status === 'error') throw new Error(`Failed to add marker: ${result.message}`);
|
||||
|
||||
return result.data;
|
||||
};
|
||||
|
||||
const deleteMarker = async (id: number): Promise<void> => {
|
||||
const response = await fetch(API_URL + `/api/v1/markers/${id}`, {
|
||||
method: 'DELETE',
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error(`Failed to delete marker: ${response.status}`);
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (result.status === 'error') throw new Error(`Failed to delete marker: ${result.message}`);
|
||||
};
|
||||
|
||||
const saveSection = async (startTime: string, endTime: string, title?: string, description?: string): Promise<string> => {
|
||||
const response = await fetch(API_URL + '/api/v1/media/save', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ startTime, endTime, title, description }),
|
||||
});
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (!response.ok || result.status === 'error') throw new Error(`Failed to save section: ${result.message}`);
|
||||
|
||||
return result.data.uuid;
|
||||
};
|
||||
|
||||
const saveSectionByMarkers = async (startMarkerId: number, endMarkerId: number, title?: string, description?: string): Promise<string> => {
|
||||
const response = await fetch(API_URL + '/api/v1/media/save/markers', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ startMarkerId, endMarkerId, title, description }),
|
||||
});
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (!response.ok || result.status === 'error') throw new Error(`Failed to save section: ${result.message}`);
|
||||
|
||||
return result.data.uuid;
|
||||
};
|
||||
|
||||
const clipSection = async (duration: number, title?: string, description?: string): Promise<string> => {
|
||||
const response = await fetch(API_URL + '/api/v1/media/clip', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ duration, title, description }),
|
||||
});
|
||||
|
||||
const result: APIResponse = await response.json();
|
||||
if (!response.ok || result.status === 'error') throw new Error(`Failed to clip section: ${result.message}`);
|
||||
|
||||
return result.data.uuid;
|
||||
};
|
||||
|
||||
export {
|
||||
login,
|
||||
logout,
|
||||
compress,
|
||||
getJob,
|
||||
getUser,
|
||||
getClips,
|
||||
getClipById,
|
||||
getVideoBlob,
|
||||
patchClip,
|
||||
deleteClip,
|
||||
getVods,
|
||||
getVodById,
|
||||
getVodBlob,
|
||||
patchVod,
|
||||
deleteVod,
|
||||
getStreamStatus,
|
||||
isThumbnailAvailable,
|
||||
getCurrentStream,
|
||||
getStreamHistory,
|
||||
getMarkers,
|
||||
createMarker,
|
||||
deleteMarker,
|
||||
saveSection,
|
||||
saveSectionByMarkers,
|
||||
clipSection,
|
||||
};
|
||||
Reference in New Issue
Block a user