From 65303e655b26cf88d4803aa9a5be3c044eaaaffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kore=C5=84?= Date: Thu, 9 Jul 2026 20:14:56 +0200 Subject: [PATCH] cos tam dziala --- back/src/lib/crateClip.ts | 8 ++- back/src/lib/nextcloud.ts | 4 +- back/src/middleware/auth.ts | 31 +++------ back/src/routes/auth.ts | 37 ++++++++++ back/src/routes/protected.ts | 20 ++++-- front/bun.lock | 5 ++ front/package.json | 1 + front/src/global.d.ts | 12 ++++ front/src/hooks/useLogin.ts | 31 +++++++++ front/src/hooks/useLogout.ts | 18 +++++ front/src/hooks/useMe.ts | 23 +++++++ front/src/hooks/video/useCreateClip.ts | 18 +++++ front/src/hooks/video/useRefreshFiles.ts | 18 +++++ front/src/layouts/MainLayout.tsx | 14 ++-- front/src/main.tsx | 17 +++-- front/src/pages/Login.tsx | 14 ++-- front/src/pages/Video.tsx | 31 ++++----- front/src/providers/AuthProvider.tsx | 86 ------------------------ 18 files changed, 235 insertions(+), 153 deletions(-) create mode 100644 front/src/global.d.ts create mode 100644 front/src/hooks/useLogin.ts create mode 100644 front/src/hooks/useLogout.ts create mode 100644 front/src/hooks/useMe.ts create mode 100644 front/src/hooks/video/useCreateClip.ts create mode 100644 front/src/hooks/video/useRefreshFiles.ts delete mode 100644 front/src/providers/AuthProvider.tsx diff --git a/back/src/lib/crateClip.ts b/back/src/lib/crateClip.ts index 51272a1..a8f400b 100644 --- a/back/src/lib/crateClip.ts +++ b/back/src/lib/crateClip.ts @@ -16,17 +16,21 @@ export async function encodeFrames({ "-y", "-pattern_type", "glob", "-framerate", fps.toString(), - "-i", inputPattern, + "-i", inputPattern.toString(), "-i", path.resolve("./music.mp3"), + "-vf", + "transpose=2,scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2", + // "-autorotate", + "-c:v", "libx264", "-pix_fmt", "yuv420p", - "-c:a", "mp3", "-shortest", + "-fps_mode", "cfr", // "-preset", "medium", // "-crf", "18", output, diff --git a/back/src/lib/nextcloud.ts b/back/src/lib/nextcloud.ts index fe86b68..5fd1527 100644 --- a/back/src/lib/nextcloud.ts +++ b/back/src/lib/nextcloud.ts @@ -18,9 +18,9 @@ export async function downloadMissing(remoteDir: string = REMOTE_DIR) { const exisitngFiles = await fs.readdir(LOCAL_DIR); - const missing = contents.filter(f => !exisitngFiles.includes(f.basename)) + const missing = contents.filter(f => !exisitngFiles.includes(f.basename)); - console.log(missing) + console.log(missing); let i = 0; for (const item of missing) { diff --git a/back/src/middleware/auth.ts b/back/src/middleware/auth.ts index 2d1a147..0588a49 100644 --- a/back/src/middleware/auth.ts +++ b/back/src/middleware/auth.ts @@ -2,30 +2,17 @@ import jwt from "jsonwebtoken"; import type { Request, Response, NextFunction } from "express"; export function authMiddleware(req: Request, res: Response, next: NextFunction) { - // const header = req.headers.authorization; - // const error = "You must be logged in to access this page" - - // if (!header) { - // return res.status(401).json({ error }); - // } - - // const token = header.split(" ")[1]; // "Bearer TOKEN" - - // try { - // if (!token) throw new Error("No Bearer Token") - // const decoded = jwt.verify(token, process.env.JWT_SECRET!); - // (req as any).user = decoded; - // next(); - // } catch (err) { - // return res.status(401).json({ error }); - // } - - const token = req.cookies.token; - if (!token) return res.sendStatus(401); try { - const decoded = jwt.verify(token, process.env.JWT_SECRET!) as jwt.JwtPayload; - req.user = decoded; + const token = req.cookies.token; + + if (!token) { return res.sendStatus(401); } + + console.log("Token: ", token) + console.log("Secret: ", process.env.JWT_SECRET) + jwt.verify(token, process.env.JWT_SECRET!) + next(); + } catch { res.sendStatus(403); } diff --git a/back/src/routes/auth.ts b/back/src/routes/auth.ts index d786c1c..220b502 100644 --- a/back/src/routes/auth.ts +++ b/back/src/routes/auth.ts @@ -39,4 +39,41 @@ router.post("/login", async (req, res) => { } }); +router.post("/logout", async (req, res) => { + res.clearCookie("token", { + httpOnly: true, + secure: false, + sameSite: "lax", + }); + + res.sendStatus(204); +}); + +router.get("/me", async (req, res) => { + try { + const token = req.cookies.token; + + if (!token) { + return res.status(401).json(null); + } + + const payload = jwt.verify( + token, + process.env.JWT_SECRET! + ) as { + sub: string; + username: string; + }; + + res.json({ + username: payload.username, + dn: payload.sub, + }); + + } catch (err) { + console.log(err) + return res.status(401).json(null); + } +}); + export default router; diff --git a/back/src/routes/protected.ts b/back/src/routes/protected.ts index 7821ef1..4d757f1 100644 --- a/back/src/routes/protected.ts +++ b/back/src/routes/protected.ts @@ -3,11 +3,23 @@ import { Router } from "express"; import { downloadMissing, LOCAL_DIR } from "../lib/nextcloud"; import path from "node:path"; import { encodeFrames } from "../lib/crateClip"; +import fs from "node:fs" const router = Router(); router.get("/video", async (req, res) => { const videoPath = path.resolve("./out.mp4"); + const stat = fs.statSync(videoPath); + + // Simple ETag based on file size + modification time + const etag = `"${stat.size}-${stat.mtimeMs}"`; + + res.set("ETag", etag); + + if (req.headers["if-none-match"] === etag) { + return res.status(304).end(); + } + res.sendFile(videoPath); }); @@ -18,18 +30,18 @@ router.post("/createclip", async (req, res) => { output: path.resolve("./out.mp4"), fps: 12, }); - res.end(200); + res.status(200).end(); } catch { - res.end(501); + res.status(501).end(); } }) router.post("/refreshfiles", async (req, res) => { try { await downloadMissing(); - res.end(200); + res.status(200).end(); } catch { - res.end(501); + res.status(501).end(); } }) diff --git a/front/bun.lock b/front/bun.lock index a675186..6872cb7 100644 --- a/front/bun.lock +++ b/front/bun.lock @@ -8,6 +8,7 @@ "@fontsource-variable/figtree": "^5.2.10", "@fontsource-variable/jetbrains-mono": "^5.2.8", "@tailwindcss/vite": "^4.2.1", + "@tanstack/react-query": "4", "@types/ldapjs": "^3.0.6", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -469,6 +470,10 @@ "@tailwindcss/vite": ["@tailwindcss/vite@4.2.2", "", { "dependencies": { "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "tailwindcss": "4.2.2" }, "peerDependencies": { "vite": "7.3.2" } }, "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w=="], + "@tanstack/query-core": ["@tanstack/query-core@4.44.0", "", {}, "sha512-swSgb7OiPRR3UuIL7NuDrZNSMGmQD+wdtHxPD7j60SvBEnxbXurl5XOirtGEX2gm2hbK6mC8kMV1I+uO3l0UOw=="], + + "@tanstack/react-query": ["@tanstack/react-query@4.44.0", "", { "dependencies": { "@tanstack/query-core": "4.44.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-native": "*" }, "optionalPeers": ["react-dom", "react-native"] }, "sha512-RuIqHYrS98LrK/8kJJOJMMSQ/BCpojwsXDh7p0fBmp38ZOz6dlk+uyFRRusH+V+t3POoCsDOQ2zhomEYOeReXw=="], + "@ts-morph/common": ["@ts-morph/common@0.27.0", "", { "dependencies": { "fast-glob": "3.3.3", "minimatch": "10.2.5", "path-browserify": "1.0.1" } }, "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ=="], "@tsconfig/node10": ["@tsconfig/node10@1.0.12", "", {}, "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ=="], diff --git a/front/package.json b/front/package.json index fd75343..1a2fd96 100644 --- a/front/package.json +++ b/front/package.json @@ -15,6 +15,7 @@ "@fontsource-variable/figtree": "^5.2.10", "@fontsource-variable/jetbrains-mono": "^5.2.8", "@tailwindcss/vite": "^4.2.1", + "@tanstack/react-query": "4", "@types/ldapjs": "^3.0.6", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/front/src/global.d.ts b/front/src/global.d.ts new file mode 100644 index 0000000..656af94 --- /dev/null +++ b/front/src/global.d.ts @@ -0,0 +1,12 @@ +type User = { + username: string; + dn?: string; +}; + +type AuthContextType = { + user: User | null; + isAuthenticated: boolean; + login: (username: string, password: string) => Promise; + logout: () => void; +}; + diff --git a/front/src/hooks/useLogin.ts b/front/src/hooks/useLogin.ts new file mode 100644 index 0000000..fb0145d --- /dev/null +++ b/front/src/hooks/useLogin.ts @@ -0,0 +1,31 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +export function useLogin() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (data: { + username: string; + password: string; + }) => { + const res = await fetch(`${import.meta.env.VITE_API_URL}/auth/login`, { + method: "POST", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + + if (!res.ok) { + throw new Error("Invalid credentials"); + } + + return res.json(); + }, + + onSuccess(data) { + queryClient.setQueryData(["me"], data.user); + }, + }); +} diff --git a/front/src/hooks/useLogout.ts b/front/src/hooks/useLogout.ts new file mode 100644 index 0000000..6be2617 --- /dev/null +++ b/front/src/hooks/useLogout.ts @@ -0,0 +1,18 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +export function useLogout() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async () => { + await fetch(`${import.meta.env.VITE_API_URL}/auth/logout`, { + method: "POST", + credentials: "include", + }); + }, + + onSuccess() { + queryClient.setQueryData(["me"], null); + }, + }); +} diff --git a/front/src/hooks/useMe.ts b/front/src/hooks/useMe.ts new file mode 100644 index 0000000..2acc9fa --- /dev/null +++ b/front/src/hooks/useMe.ts @@ -0,0 +1,23 @@ +import { useQuery } from "@tanstack/react-query"; + + +export function useMe() { + return useQuery({ + queryKey: ["me"], + queryFn: async () => { + const res = await fetch(`${import.meta.env.VITE_API_URL}/auth/me`, { + credentials: "include", + }); + + if (res.status === 401) { + return null; + } + + if (!res.ok) { + throw new Error("Failed to get current user"); + } + + return res.json(); + }, + }); +} diff --git a/front/src/hooks/video/useCreateClip.ts b/front/src/hooks/video/useCreateClip.ts new file mode 100644 index 0000000..e3c46c9 --- /dev/null +++ b/front/src/hooks/video/useCreateClip.ts @@ -0,0 +1,18 @@ +import { useMutation } from "@tanstack/react-query"; + +export function useCreateClip() { + return useMutation({ + mutationFn: async () => { + const res = await fetch(`${import.meta.env.VITE_API_URL}/protected/createclip`, { + method: "POST", + credentials: "include", + }); + + if (!res.ok) { + throw new Error("Invalid credentials"); + } + + return res.json(); + }, + }); +} diff --git a/front/src/hooks/video/useRefreshFiles.ts b/front/src/hooks/video/useRefreshFiles.ts new file mode 100644 index 0000000..8a7ae49 --- /dev/null +++ b/front/src/hooks/video/useRefreshFiles.ts @@ -0,0 +1,18 @@ +import { useMutation } from "@tanstack/react-query"; + +export function useRefreshFiles() { + return useMutation({ + mutationFn: async () => { + const res = await fetch(`${import.meta.env.VITE_API_URL}/protected/refreshfiles`, { + method: "POST", + credentials: "include", + }); + + if (!res.ok) { + throw new Error("Invalid credentials"); + } + + return res.json(); + }, + }); +} diff --git a/front/src/layouts/MainLayout.tsx b/front/src/layouts/MainLayout.tsx index 7008fb7..704152a 100644 --- a/front/src/layouts/MainLayout.tsx +++ b/front/src/layouts/MainLayout.tsx @@ -1,6 +1,7 @@ import { Button } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; -import { useAuth } from "@/providers/AuthProvider"; +import { useLogout } from "@/hooks/useLogout"; +import { useMe } from "@/hooks/useMe"; import { type ReactNode } from "react"; import { Link, Outlet, useLocation } from "react-router"; @@ -18,7 +19,9 @@ function Path({ url, children }: { url: string, children: ReactNode }) { } export function MainLayout() { - const { isAuthenticated, user, logout } = useAuth() + const { data: user } = useMe(); + const isAuthenticated = !!user; + const logout = useLogout(); return ( @@ -53,7 +56,7 @@ export function MainLayout() { {/* Actions */}
- {isAuthenticated && user && + {user && <>
{user.username} @@ -61,7 +64,10 @@ export function MainLayout() { } {isAuthenticated ? - + : { /* + disabled={refreshImages.isPending} + onClick={() => { refreshImages.mutate() }} + >{ + refreshImages.isPending ? "Refreshing..." : "Refresh" + } + disabled={createClip.isPending} + onClick={() => { createClip.mutate() }} + >{ + createClip.isPending ? "creating" : "Create Clip" + }