40 lines
1.1 KiB
TypeScript
40 lines
1.1 KiB
TypeScript
import { unlink } from "node:fs/promises";
|
|
import path from "node:path";
|
|
|
|
/**
|
|
* Best-effort local file delete for camera photos hosted under /public.
|
|
* External CDN URLs are left alone (no purge credentials in this CMS).
|
|
*/
|
|
export async function tryRemoveLocalPhotoFile(url: string): Promise<boolean> {
|
|
if (!url?.trim()) return false;
|
|
let pathname = url.trim();
|
|
if (/^https?:\/\//i.test(pathname)) {
|
|
try {
|
|
const parsed = new URL(pathname);
|
|
const app = (
|
|
process.env.APP_URL ||
|
|
process.env.NEXT_PUBLIC_APP_URL ||
|
|
""
|
|
).replace(/\/$/, "");
|
|
if (!app || !pathname.startsWith(app)) return false;
|
|
pathname = parsed.pathname;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
if (!pathname.startsWith("/")) return false;
|
|
const rel = pathname.replace(/^\/+/, "");
|
|
if (!rel || rel.includes("..")) return false;
|
|
const publicRoot = path.resolve(process.cwd(), "public");
|
|
const full = path.resolve(publicRoot, rel);
|
|
if (!full.startsWith(publicRoot + path.sep) && full !== publicRoot) {
|
|
return false;
|
|
}
|
|
try {
|
|
await unlink(full);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|