refactor: migrate direct fetch calls to API handler pattern
Replace raw fetch() in settings, share-key, and import-shared with centralized API handlers via src/lib/api.ts. Also auto-create the data/ directory on startup so the SQLite db path is always valid. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
362f6d861b
commit
eafb44a4e9
@@ -3,6 +3,7 @@ import { Dialog, DialogTitle, DialogContent, DialogHeader, DialogTrigger, Dialog
|
||||
import { Button, ButtonProps } from '../ui/button';
|
||||
import { ShareLinkFilters } from '@/types/shareLink';
|
||||
import { generateShareLink } from '@/handlers/api/shareLink.handler';
|
||||
import { createShareKey, getShareKey } from '@/handlers/api/share-key.handler';
|
||||
import { Input } from '../ui/input';
|
||||
import { Label } from '@radix-ui/react-label';
|
||||
|
||||
@@ -28,8 +29,7 @@ export default function ShareAssetsTrigger({ filters, buttonProps }: ShareAssets
|
||||
const [keyError, setKeyError] = useState<string | null>(null);
|
||||
|
||||
const checkShareKey = async () => {
|
||||
const res = await fetch('/api/share-key');
|
||||
const data = await res.json();
|
||||
const data = await getShareKey();
|
||||
setEndpointConfigured(data.endpointConfigured);
|
||||
setShareKeyExists(data.exists);
|
||||
};
|
||||
@@ -37,12 +37,11 @@ export default function ShareAssetsTrigger({ filters, buttonProps }: ShareAssets
|
||||
const handleCreateKey = async () => {
|
||||
setCreatingKey(true);
|
||||
setKeyError(null);
|
||||
const res = await fetch('/api/share-key', { method: 'POST' });
|
||||
if (res.ok) {
|
||||
try {
|
||||
await createShareKey();
|
||||
setShareKeyExists(true);
|
||||
} else {
|
||||
const data = await res.json();
|
||||
setKeyError(data.message ?? 'Failed to create share key');
|
||||
} catch (err: any) {
|
||||
setKeyError(err.message ?? 'Failed to create share key');
|
||||
}
|
||||
setCreatingKey(false);
|
||||
};
|
||||
|
||||
+12
-1
@@ -83,4 +83,15 @@ export const SHARE_LINK_PEOPLE_PATH = (token: string) => BASE_API_ENDPOINT + "/s
|
||||
export const SHARE_LINK_DOWNLOAD_PATH = (token: string) => BASE_API_ENDPOINT + "/share-link/" + token + "/download";
|
||||
export const DOWNLOAD_ASSETS_PATH = BASE_PROXY_ENDPOINT + "/download/archive";
|
||||
|
||||
export const LIST_DUPLICATES_PATH = BASE_PROXY_ENDPOINT + "/duplicates";
|
||||
export const LIST_DUPLICATES_PATH = BASE_PROXY_ENDPOINT + "/duplicates";
|
||||
|
||||
// Settings
|
||||
export const SETTINGS_API_KEYS_PATH = BASE_API_ENDPOINT + "/settings/api-keys";
|
||||
export const SETTINGS_API_KEY_PATH = (purpose: string) => BASE_API_ENDPOINT + "/settings/api-keys/" + purpose;
|
||||
|
||||
// Share Key
|
||||
export const SHARE_KEY_PATH = BASE_API_ENDPOINT + "/share-key";
|
||||
|
||||
// Import Shared
|
||||
export const IMPORT_SHARED_PATH = BASE_API_ENDPOINT + "/import-shared";
|
||||
export const IMPORT_SHARED_UPLOAD_ALL_PATH = BASE_API_ENDPOINT + "/import-shared/upload-all";
|
||||
@@ -1,12 +1,15 @@
|
||||
import { createClient } from "@libsql/client";
|
||||
import { drizzle } from "drizzle-orm/libsql";
|
||||
import { migrate } from "drizzle-orm/libsql/migrator";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import * as schema from "./schema";
|
||||
|
||||
const DB_PATH =
|
||||
process.env.APP_DB_PATH || path.join(process.cwd(), "data", "app.db");
|
||||
|
||||
fs.mkdirSync(path.dirname(DB_PATH), { recursive: true });
|
||||
|
||||
const client = createClient({ url: `file:${DB_PATH}` });
|
||||
|
||||
export const appDb = drizzle(client, { schema });
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { IMPORT_SHARED_PATH, IMPORT_SHARED_UPLOAD_ALL_PATH } from "@/config/routes";
|
||||
import API from "@/lib/api";
|
||||
|
||||
export const importShared = async (link: string) => {
|
||||
return API.post(IMPORT_SHARED_PATH, { link });
|
||||
};
|
||||
|
||||
export const importSharedUploadAll = async (params: {
|
||||
origin: string;
|
||||
key: string;
|
||||
assets: object[];
|
||||
albumOptions: object;
|
||||
}) => {
|
||||
return API.post(IMPORT_SHARED_UPLOAD_ALL_PATH, params);
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import { SETTINGS_API_KEYS_PATH, SETTINGS_API_KEY_PATH } from "@/config/routes";
|
||||
import API from "@/lib/api";
|
||||
|
||||
export const getApiKeys = async () => {
|
||||
return API.get(SETTINGS_API_KEYS_PATH);
|
||||
};
|
||||
|
||||
export const deleteApiKey = async (purpose: string) => {
|
||||
return API.delete(SETTINGS_API_KEY_PATH(purpose));
|
||||
};
|
||||
|
||||
export const regenerateApiKey = async (purpose: string) => {
|
||||
return API.put(SETTINGS_API_KEY_PATH(purpose));
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import { SHARE_KEY_PATH } from "@/config/routes";
|
||||
import API from "@/lib/api";
|
||||
|
||||
export const getShareKey = async () => {
|
||||
return API.get(SHARE_KEY_PATH);
|
||||
};
|
||||
|
||||
export const createShareKey = async () => {
|
||||
return API.post(SHARE_KEY_PATH);
|
||||
};
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { listAlbums } from "@/handlers/api/album.handler";
|
||||
import { importShared, importSharedUploadAll } from "@/handlers/api/import-shared.handler";
|
||||
import { IAlbum } from "@/types/album";
|
||||
|
||||
interface IAlbumContributorCount {
|
||||
@@ -200,27 +201,14 @@ export default function ImportSharedPage() {
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await fetch("/api/import-shared", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ link: shareLink }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorPayload = await response.json().catch(() => ({}));
|
||||
throw new Error(errorPayload.error || "Failed to import shared album");
|
||||
}
|
||||
|
||||
const payload: IImportSharedResponse = await response.json();
|
||||
const payload: IImportSharedResponse = await importShared(shareLink);
|
||||
setSharedData(payload);
|
||||
setSubmittedLink(payload.link);
|
||||
setUploadBanner(null);
|
||||
setImportAllLoading(false);
|
||||
setAlbumImportMode("album");
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : "Unexpected error";
|
||||
} catch (err: any) {
|
||||
const message = err.message ?? "Unexpected error";
|
||||
setError(message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -309,33 +297,22 @@ export default function ImportSharedPage() {
|
||||
setUploadBanner(null);
|
||||
setImportAllLoading(true);
|
||||
try {
|
||||
const response = await fetch("/api/import-shared/upload-all", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
origin: sharedData.origin,
|
||||
key: sharedData.key,
|
||||
assets: uploadableAssets.map((asset) => ({
|
||||
id: asset.id,
|
||||
originalFileName: asset.originalFileName,
|
||||
type: asset.type,
|
||||
fileCreatedAt: asset.fileCreatedAt ?? null,
|
||||
localDateTime: asset.localDateTime ?? null,
|
||||
duration: asset.duration ?? null,
|
||||
isFavorite: asset.isFavorite ?? false,
|
||||
isArchived: asset.isArchived ?? false,
|
||||
})),
|
||||
albumOptions,
|
||||
}),
|
||||
const result = await importSharedUploadAll({
|
||||
origin: sharedData.origin,
|
||||
key: sharedData.key,
|
||||
assets: uploadableAssets.map((asset) => ({
|
||||
id: asset.id,
|
||||
originalFileName: asset.originalFileName,
|
||||
type: asset.type,
|
||||
fileCreatedAt: asset.fileCreatedAt ?? null,
|
||||
localDateTime: asset.localDateTime ?? null,
|
||||
duration: asset.duration ?? null,
|
||||
isFavorite: asset.isFavorite ?? false,
|
||||
isArchived: asset.isArchived ?? false,
|
||||
})),
|
||||
albumOptions,
|
||||
});
|
||||
|
||||
const result = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
throw new Error(result.error || "Failed to import shared archive");
|
||||
}
|
||||
|
||||
const processedAssetIds: string[] = result.processedAssetIds ?? [];
|
||||
const skippedAssetIds: string[] = result.skippedAssetIds ?? [];
|
||||
const failed: { assetId: string; reason?: string }[] = result.failed ?? [];
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PageLayout from "@/components/layouts/PageLayout";
|
||||
import Header from "@/components/shared/Header";
|
||||
import { deleteApiKey, getApiKeys, regenerateApiKey } from "@/handlers/api/settings.handler";
|
||||
import { AlertDialog } from "@/components/ui/alert-dialog";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -23,10 +24,11 @@ export default function SettingsPage() {
|
||||
|
||||
const fetchKeys = async () => {
|
||||
setLoading(true);
|
||||
const res = await fetch("/api/settings/api-keys");
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
try {
|
||||
const data = await getApiKeys();
|
||||
setKeys(data);
|
||||
} catch {
|
||||
// silently fail — keys list stays empty
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
@@ -37,26 +39,24 @@ export default function SettingsPage() {
|
||||
|
||||
const handleDelete = async (purpose: string) => {
|
||||
setDeleting(purpose);
|
||||
const res = await fetch(`/api/settings/api-keys/${purpose}`, { method: "DELETE" });
|
||||
if (res.ok) {
|
||||
try {
|
||||
await deleteApiKey(purpose);
|
||||
setKeys((prev) => prev.filter((k) => k.purpose !== purpose));
|
||||
toast.success("API key deleted");
|
||||
} else {
|
||||
const data = await res.json();
|
||||
toast.error(data.message ?? "Failed to delete API key");
|
||||
} catch (err: any) {
|
||||
toast.error(err.message ?? "Failed to delete API key");
|
||||
}
|
||||
setDeleting(null);
|
||||
};
|
||||
|
||||
const handleRegenerate = async (purpose: string) => {
|
||||
setRegenerating(purpose);
|
||||
const res = await fetch(`/api/settings/api-keys/${purpose}`, { method: "PUT" });
|
||||
if (res.ok) {
|
||||
try {
|
||||
await regenerateApiKey(purpose);
|
||||
await fetchKeys();
|
||||
toast.success("API key regenerated");
|
||||
} else {
|
||||
const data = await res.json();
|
||||
toast.error(data.message ?? "Failed to regenerate API key");
|
||||
} catch (err: any) {
|
||||
toast.error(err.message ?? "Failed to regenerate API key");
|
||||
}
|
||||
setRegenerating(null);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user