fix(frontend): update frontend to match new endpoints
This commit is contained in:
@@ -58,7 +58,7 @@ const VideoCard = ({ id, title, duration, createdAt, onEdit, onDelete, className
|
|||||||
<div className={clsx("flex flex-col group cursor-pointer", className)}>
|
<div className={clsx("flex flex-col group cursor-pointer", className)}>
|
||||||
<div className="relative overflow-hidden rounded-lg">
|
<div className="relative overflow-hidden rounded-lg">
|
||||||
<img
|
<img
|
||||||
src={thumbnailAvailable ? API_URL + `/api/v1/download/thumbnail/${id}` : fallbackThumbnail}
|
src={thumbnailAvailable ? API_URL + `/api/v1/clips/${id}/thumbnail` : fallbackThumbnail}
|
||||||
alt="Video Thumbnail"
|
alt="Video Thumbnail"
|
||||||
className="w-full aspect-video object-cover group-hover:scale-105 transition-transform duration-200"
|
className="w-full aspect-video object-cover group-hover:scale-105 transition-transform duration-200"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import PlaybackSlider from "./../components/video/PlaybackSlider";
|
|||||||
import ClipRangeSlider from "./../components/video/ClipRangeSlider";
|
import ClipRangeSlider from "./../components/video/ClipRangeSlider";
|
||||||
import ConfigBox from "../components/video/ConfigBox.tsx";
|
import ConfigBox from "../components/video/ConfigBox.tsx";
|
||||||
import ExportWidget from "../components/video/ExportWidget.tsx";
|
import ExportWidget from "../components/video/ExportWidget.tsx";
|
||||||
import { editFile, processFile, getProgress, uploadFile } from "../utils/endpoints"
|
import { compress, getJob } from "../utils/endpoints"
|
||||||
import type { VideoMetadata } from "../utils/types.ts";
|
import type { VideoMetadata } from "../utils/types.ts";
|
||||||
import Box from "../components/Box.tsx";
|
import Box from "../components/Box.tsx";
|
||||||
import MetadataBox from "../components/video/MetadataBox.tsx";
|
import MetadataBox from "../components/video/MetadataBox.tsx";
|
||||||
@@ -61,33 +61,29 @@ const ClipEdit = () => {
|
|||||||
const sendData = async () => {
|
const sendData = async () => {
|
||||||
if (!localFile) return;
|
if (!localFile) return;
|
||||||
|
|
||||||
let fileId: string;
|
let jobId: string;
|
||||||
try {
|
try {
|
||||||
fileId = await uploadFile(localFile);
|
jobId = await compress(localFile, outputMetadata);
|
||||||
setUploadedId(fileId);
|
setUploadedId(jobId);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
setError(`Failed to upload file: ${err instanceof Error ? err.message : 'Unknown error'}`);
|
setError(`Failed to start compression: ${err instanceof Error ? err.message : 'Unknown error'}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
editFile(fileId, outputMetadata)
|
const interval = setInterval(async () => await pollProgress(jobId, interval), 500);
|
||||||
.then(() => processFile(fileId)
|
|
||||||
.catch((err: Error) => setError(`Failed to process file: ${err.message}`)))
|
|
||||||
.catch((err: Error) => setError(`Failed to edit file: ${err.message}`));
|
|
||||||
|
|
||||||
const interval = setInterval(async () => await pollProgress(fileId, interval), 500);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const pollProgress = async (id: string, intervalId: number) => {
|
const pollProgress = async (jobId: string, intervalId: number) => {
|
||||||
getProgress(id)
|
getJob(jobId)
|
||||||
.then((progress) => {
|
.then((job) => {
|
||||||
setProgress(progress.process.progress);
|
setProgress(job.progress);
|
||||||
|
|
||||||
if (progress.process.complete) {
|
if (job.state === 'FAILED') {
|
||||||
|
setError(`Compression failed: ${job.errorOutput ?? 'Unknown error'}`);
|
||||||
|
clearInterval(intervalId);
|
||||||
|
} else if (job.isComplete) {
|
||||||
clearInterval(intervalId);
|
clearInterval(intervalId);
|
||||||
setDownloadable(true);
|
setDownloadable(true);
|
||||||
} else {
|
|
||||||
setDownloadable(false);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err: Error) => {
|
.catch((err: Error) => {
|
||||||
@@ -99,7 +95,7 @@ const ClipEdit = () => {
|
|||||||
const handleDownload = async () => {
|
const handleDownload = async () => {
|
||||||
if (!uploadedId) return;
|
if (!uploadedId) return;
|
||||||
|
|
||||||
const response = await fetch(API_URL + `/api/v1/download/output/${uploadedId}`);
|
const response = await fetch(API_URL + `/api/v1/jobs/${uploadedId}/download`, { credentials: 'include' });
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error('Download failed');
|
console.error('Download failed');
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type {VideoMetadata, APIResponse, User, Clip, ProgressResult } from "./types.ts";
|
import type {VideoMetadata, APIResponse, User, Clip, JobResponse } from "./types.ts";
|
||||||
|
|
||||||
const API_URL = import.meta.env.VITE_API_URL;
|
const API_URL = import.meta.env.VITE_API_URL;
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ export class AuthError extends Error {
|
|||||||
* @return A promise that resolves to a JWT
|
* @return A promise that resolves to a JWT
|
||||||
*/
|
*/
|
||||||
const login = async (GoogleToken: string): Promise<string> => {
|
const login = async (GoogleToken: string): Promise<string> => {
|
||||||
const response = await fetch(API_URL + '/api/v1/auth/login', {
|
const response = await fetch(API_URL + '/api/v1/users/login', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -35,7 +35,7 @@ const login = async (GoogleToken: string): Promise<string> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const logout = async () => {
|
const logout = async () => {
|
||||||
const response = await fetch(API_URL + '/api/v1/auth/logout', {
|
const response = await fetch(API_URL + '/api/v1/users/logout', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
credentials: 'include'
|
credentials: 'include'
|
||||||
});
|
});
|
||||||
@@ -52,132 +52,57 @@ const logout = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uploads a file to the backend.
|
* Uploads a file and starts compression in one step.
|
||||||
* @param file - The file to upload.
|
* @param file - The file to compress.
|
||||||
|
* @param options - The clip/compression options.
|
||||||
|
* @returns The job UUID to poll for progress.
|
||||||
*/
|
*/
|
||||||
const uploadFile = async (file: File): Promise<string> => {
|
const compress = async (file: File, options: VideoMetadata): Promise<string> => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file);
|
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/upload', {
|
const response = await fetch(API_URL + '/api/v1/media/compress', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData,
|
body: formData,
|
||||||
|
credentials: 'include',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Compression failed: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
const result: APIResponse = await response.json();
|
const result: APIResponse = await response.json();
|
||||||
|
|
||||||
if (result.status == "error") {
|
if (result.status === 'error') {
|
||||||
throw new Error(`Failed to upload file: ${result.message}`);
|
throw new Error(`Compression failed: ${result.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.data.uuid;
|
return result.data.uuid;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Submits metadata changes to the backend.
|
* Fetches the status of a background job.
|
||||||
* @param uuid - The UUID of the video file to edit.
|
* @param uuid - The job UUID.
|
||||||
* @param videoMetadata - The metadata to update.
|
|
||||||
*/
|
*/
|
||||||
const editFile = async (uuid: string, videoMetadata: VideoMetadata) => {
|
const getJob = async (uuid: string): Promise<JobResponse> => {
|
||||||
const formData = new URLSearchParams();
|
const response = await fetch(API_URL + `/api/v1/jobs/${uuid}`, { credentials: 'include' });
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(videoMetadata)) {
|
|
||||||
if (value !== undefined && value !== null) {
|
|
||||||
formData.append(key, value.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(API_URL + `/api/v1/edit/${uuid}`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
body: formData.toString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Failed to edit file: ${response.status}`);
|
throw new Error(`Failed to fetch job: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result: APIResponse = await response.json();
|
const result: APIResponse = await response.json();
|
||||||
|
|
||||||
if (result.status === "error") {
|
if (result.status === 'error') {
|
||||||
throw new Error(`Failed to edit file: ${result.message}`);
|
throw new Error(`Failed to fetch job: ${result.message}`);
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggers file processing.
|
|
||||||
* @param uuid - The UUID of the video file to process.
|
|
||||||
*/
|
|
||||||
const processFile = async (uuid: string) => {
|
|
||||||
const response = await fetch(API_URL + `/api/v1/process/${uuid}`, {credentials: "include"});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Failed to process file: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result: APIResponse = await response.json();
|
|
||||||
|
|
||||||
if (result.status === "error") {
|
|
||||||
throw new Error("Failed to process file: " + result.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const convertFile = async (uuid: string) => {
|
|
||||||
const response = await fetch(API_URL + `/api/v1/convert/${uuid}`);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Failed to convert file: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result: APIResponse = await response.json();
|
|
||||||
|
|
||||||
if (result.status === "error") {
|
|
||||||
throw new Error("Failed to convert file: " + result.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the processing progress percentage.
|
|
||||||
* @param uuid - The UUID of the video file.
|
|
||||||
*/
|
|
||||||
const getProgress = async (uuid: string): Promise<ProgressResult> => {
|
|
||||||
const response = await fetch(API_URL + `/api/v1/progress/${uuid}`);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Failed to fetch progress: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (result.status === "error") {
|
|
||||||
throw new Error(`Failed to fetch progress: ${result.message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result.data) {
|
|
||||||
throw new Error('Invalid progress data received');
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.data;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches original metadata from the backend.
|
|
||||||
* @param uuid - The UUID of the video file.
|
|
||||||
*/
|
|
||||||
const getMetadata = async (uuid: string): Promise<VideoMetadata> => {
|
|
||||||
const response = await fetch(API_URL + `/api/v1/metadata/original/${uuid}`, {credentials: "include"});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Failed to fetch metadata: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (result.status === "error") {
|
|
||||||
throw new Error(`Failed to fetch metadata: ${result.message}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.data;
|
return result.data;
|
||||||
@@ -187,7 +112,7 @@ const getMetadata = async (uuid: string): Promise<VideoMetadata> => {
|
|||||||
* Fetches the current user information. Returns null if not authenticated.
|
* Fetches the current user information. Returns null if not authenticated.
|
||||||
*/
|
*/
|
||||||
const getUser = async (): Promise<null | User > => {
|
const getUser = async (): Promise<null | User > => {
|
||||||
const response = await fetch(API_URL + '/api/v1/auth/user', {credentials: "include"});
|
const response = await fetch(API_URL + '/api/v1/users/me', {credentials: "include"});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return null;
|
return null;
|
||||||
@@ -245,7 +170,7 @@ const getClipById = async (id: string): Promise<Clip | null> => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getVideoBlob = async(id: string): Promise<Blob> => {
|
const getVideoBlob = async(id: string): Promise<Blob> => {
|
||||||
const response = await fetch(API_URL + `/api/v1/download/clip/${id}`, {credentials: "include",});
|
const response = await fetch(API_URL + `/api/v1/clips/${id}/media`, {credentials: "include",});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
if (response.status === 401 || response.status === 403) throw new AuthError();
|
if (response.status === 401 || response.status === 403) throw new AuthError();
|
||||||
@@ -303,22 +228,16 @@ const getStreamStatus = async (): Promise<boolean> => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const isThumbnailAvailable = async (id: number): Promise<boolean> => {
|
const isThumbnailAvailable = async (id: number): Promise<boolean> => {
|
||||||
const response = await fetch(API_URL + `/api/v1/download/thumbnail/${id}`, {credentials: "include"});
|
const response = await fetch(API_URL + `/api/v1/clips/${id}/thumbnail`, {credentials: "include"});
|
||||||
return response.ok;
|
return response.ok;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
login,
|
login,
|
||||||
logout,
|
logout,
|
||||||
getStreamStatus,
|
getStreamStatus,
|
||||||
uploadFile,
|
compress,
|
||||||
editFile,
|
getJob,
|
||||||
processFile,
|
|
||||||
convertFile,
|
|
||||||
getProgress,
|
|
||||||
getMetadata,
|
|
||||||
getUser,
|
getUser,
|
||||||
getClips,
|
getClips,
|
||||||
getClipById,
|
getClipById,
|
||||||
|
|||||||
@@ -33,15 +33,13 @@ type Clip = {
|
|||||||
createdAt: string,
|
createdAt: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProgressResult = {
|
type JobResponse = {
|
||||||
process: {
|
uuid: string,
|
||||||
progress: number,
|
progress: number,
|
||||||
complete: boolean
|
isComplete: boolean,
|
||||||
};
|
state: 'READY' | 'PROCESSING' | 'SUCCEEDED' | 'FAILED',
|
||||||
conversion: {
|
errorOutput: string | null,
|
||||||
progress: number,
|
createdAt: string,
|
||||||
complete: boolean
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
@@ -49,5 +47,5 @@ export type {
|
|||||||
VideoMetadata,
|
VideoMetadata,
|
||||||
User,
|
User,
|
||||||
Clip,
|
Clip,
|
||||||
ProgressResult
|
JobResponse
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user