- /similar (nav: Similar): drop a file to get the exact MD5 match, the perceptual matches against the library, and e621 IQDB candidates (auto-run for images when credentials are configured). Read-only — nothing enters the library. - SimilarityCheck model + /api/similarity/ (create/list/retrieve/delete) with signed preview URLs and an expires_at timestamp. - Temp files are wiped on startup (AppConfig.ready, file-only so no database access during initialization), lazily past SIMILARITY_TTL_MINUTES (default 30, env-overridable), on delete, and by manage.py cleanup_similarity. - uploadFile() takes a target path; .env.example documents the TTL.
142 lines
3.9 KiB
TypeScript
142 lines
3.9 KiB
TypeScript
import { useEffect, useState, type ReactNode } from "react";
|
|
import {
|
|
Navigate,
|
|
Route,
|
|
Routes,
|
|
useLocation,
|
|
useParams,
|
|
} from "react-router-dom";
|
|
|
|
import { AppShell } from "@/components/AppShell";
|
|
import { FullPageSpinner } from "@/components/ui";
|
|
import AccountPage from "@/features/account/AccountPage";
|
|
import { AgeGate } from "@/features/age/AgeGate";
|
|
import LoginPage from "@/features/auth/LoginPage";
|
|
import RegisterPage from "@/features/auth/RegisterPage";
|
|
import DetailPage from "@/features/detail/DetailPage";
|
|
import DeletePage from "@/features/library/DeletePage";
|
|
import DuplicatesPage from "@/features/library/DuplicatesPage";
|
|
import FollowedPage from "@/features/follows/FollowedPage";
|
|
import LibraryPage from "@/features/library/LibraryPage";
|
|
import Md5Redirect from "@/features/library/Md5Redirect";
|
|
import OnlinePage from "@/features/online/OnlinePage";
|
|
import PoolDetailPage from "@/features/pools/PoolDetailPage";
|
|
import PoolsPage from "@/features/pools/PoolsPage";
|
|
import SimilarPage from "@/features/similar/SimilarPage";
|
|
import UploadPage from "@/features/upload/UploadPage";
|
|
import UsersPage from "@/features/users/UsersPage";
|
|
import { isAgeVerified, markAgeVerified } from "@/lib/age";
|
|
import { useAuth } from "@/store/auth";
|
|
|
|
function RequireAuth({ children }: { children: ReactNode }) {
|
|
const user = useAuth((state) => state.user);
|
|
const location = useLocation();
|
|
if (!user) {
|
|
return <Navigate to="/login" state={{ from: location.pathname }} replace />;
|
|
}
|
|
return <>{children}</>;
|
|
}
|
|
|
|
function PostRedirect() {
|
|
const { postId = "" } = useParams();
|
|
return <Navigate to={`/detail/${postId}`} replace />;
|
|
}
|
|
|
|
export default function App() {
|
|
const ready = useAuth((state) => state.ready);
|
|
const loadUser = useAuth((state) => state.loadUser);
|
|
const [ageVerified, setAgeVerified] = useState(isAgeVerified);
|
|
|
|
useEffect(() => {
|
|
void loadUser();
|
|
}, [loadUser]);
|
|
|
|
if (!ageVerified) {
|
|
return (
|
|
<AgeGate
|
|
onEnter={() => {
|
|
markAgeVerified();
|
|
setAgeVerified(true);
|
|
}}
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (!ready) {
|
|
return <FullPageSpinner />;
|
|
}
|
|
|
|
return (
|
|
<Routes>
|
|
<Route element={<AppShell />}>
|
|
<Route path="/" element={<LibraryPage />} />
|
|
<Route path="/detail/:itemId" element={<DetailPage />} />
|
|
<Route path="/view/:md5" element={<Md5Redirect />} />
|
|
<Route path="/online" element={<OnlinePage />} />
|
|
<Route path="/online/view/:postId" element={<PostRedirect />} />
|
|
<Route path="/pools" element={<PoolsPage />} />
|
|
<Route path="/pools/:poolId" element={<PoolDetailPage />} />
|
|
<Route
|
|
path="/followed"
|
|
element={
|
|
<RequireAuth>
|
|
<FollowedPage />
|
|
</RequireAuth>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/duplicates"
|
|
element={
|
|
<RequireAuth>
|
|
<DuplicatesPage />
|
|
</RequireAuth>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/delete"
|
|
element={
|
|
<RequireAuth>
|
|
<DeletePage />
|
|
</RequireAuth>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/users"
|
|
element={
|
|
<RequireAuth>
|
|
<UsersPage />
|
|
</RequireAuth>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/similar"
|
|
element={
|
|
<RequireAuth>
|
|
<SimilarPage />
|
|
</RequireAuth>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/upload"
|
|
element={
|
|
<RequireAuth>
|
|
<UploadPage />
|
|
</RequireAuth>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/account"
|
|
element={
|
|
<RequireAuth>
|
|
<AccountPage />
|
|
</RequireAuth>
|
|
}
|
|
/>
|
|
<Route path="*" element={<Navigate to="/" replace />} />
|
|
</Route>
|
|
<Route path="/login" element={<LoginPage />} />
|
|
<Route path="/register" element={<RegisterPage />} />
|
|
</Routes>
|
|
);
|
|
}
|