co2 galvo owner test
This commit is contained in:
parent
715be11ff9
commit
a38aa4c2f9
5 changed files with 254 additions and 236 deletions
135
lib/directus.ts
135
lib/directus.ts
|
|
@ -1,10 +1,11 @@
|
|||
// lib/directus.ts
|
||||
// Central Directus helpers used by API routes. (user bearer only)
|
||||
|
||||
import { cookies, headers } from "next/headers";
|
||||
|
||||
const BASE = (process.env.DIRECTUS_URL || "").replace(/\/$/, "");
|
||||
const TOKEN_ADMIN_REGISTER = process.env.DIRECTUS_TOKEN_ADMIN_REGISTER || ""; // server-only
|
||||
const ROLE_MEMBER_NAME = process.env.DIRECTUS_ROLE_MEMBER_NAME || "Users";
|
||||
|
||||
const PROJECTS_COLLECTION = process.env.DIRECTUS_PROJECTS_COLLECTION || "projects";
|
||||
|
||||
if (!BASE) console.warn("[directus] Missing DIRECTUS_URL");
|
||||
|
|
@ -15,11 +16,45 @@ export function bytesFromMB(mb: number) {
|
|||
return Math.round(mb * 1024 * 1024);
|
||||
}
|
||||
|
||||
// Extract a user's bearer (ma_at) from a Next.js Request (server routes)
|
||||
export function getUserBearerFromRequest(req: Request): string | null {
|
||||
const cookieHeader = req.headers.get("cookie") ?? "";
|
||||
const m = cookieHeader.match(/(?:^|;\s*)ma_at=([^;]+)/);
|
||||
return m?.[1] ?? null;
|
||||
/**
|
||||
* Return the user's Directus bearer token from the request or server context.
|
||||
* Looks at:
|
||||
* 1) Authorization: Bearer ...
|
||||
* 2) Next.js server cookies (ma_at, ma_at_beta, ma_session)
|
||||
* 3) Raw Cookie header (fallback)
|
||||
*/
|
||||
export function getUserBearerFromRequest(req?: Request): string | null {
|
||||
// 1) Authorization header (supports server-to-server/proxy calls)
|
||||
const hAuth = req?.headers?.get("authorization") ?? headers().get("authorization");
|
||||
if (hAuth?.startsWith("Bearer ")) return hAuth.slice(7);
|
||||
|
||||
// 2) Next.js server cookies (App Router)
|
||||
try {
|
||||
const c = cookies();
|
||||
const t =
|
||||
c.get("ma_at")?.value ||
|
||||
c.get("ma_at_beta")?.value ||
|
||||
c.get("ma_session")?.value ||
|
||||
null;
|
||||
if (t) return t;
|
||||
} catch {
|
||||
// Not in a server context that supports cookies()
|
||||
}
|
||||
|
||||
// 3) Raw Cookie header (plain Request fallback)
|
||||
const raw = req?.headers?.get("cookie") ?? "";
|
||||
if (raw) {
|
||||
const map = Object.fromEntries(
|
||||
raw.split(/;\s*/).map((p) => {
|
||||
const [k, ...v] = p.split("=");
|
||||
return [decodeURIComponent(k), decodeURIComponent(v.join("=") || "")];
|
||||
})
|
||||
);
|
||||
const t = map["ma_at"] || map["ma_at_beta"] || map["ma_session"];
|
||||
if (t) return String(t);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
|
@ -33,7 +68,9 @@ function authHeaders(bearer: string, extra?: HeadersInit): HeadersInit {
|
|||
async function parseJsonSafe(res: Response) {
|
||||
const text = await res.text();
|
||||
let json: any = null;
|
||||
try { json = text ? JSON.parse(text) : null; } catch {}
|
||||
try {
|
||||
json = text ? JSON.parse(text) : null;
|
||||
} catch {}
|
||||
return { json, text };
|
||||
}
|
||||
|
||||
|
|
@ -49,7 +86,10 @@ async function throwIfNotOk(res: Response) {
|
|||
}
|
||||
|
||||
export async function dxGET<T = any>(path: string, bearer: string): Promise<T> {
|
||||
const res = await fetch(`${BASE}${path}`, { headers: authHeaders(bearer), cache: "no-store" });
|
||||
const res = await fetch(`${BASE}${path}`, {
|
||||
headers: authHeaders(bearer),
|
||||
cache: "no-store",
|
||||
});
|
||||
return (await throwIfNotOk(res)) as T;
|
||||
}
|
||||
|
||||
|
|
@ -83,89 +123,38 @@ export async function dxDELETE<T = any>(path: string, bearer: string): Promise<T
|
|||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
/** Server-only admin fetch (registration flows, etc.) */
|
||||
// Server-only admin fetch (registration flows, etc.)
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
export async function directusAdminFetch<T = any>(path: string, init?: RequestInit): Promise<T> {
|
||||
if (!TOKEN_ADMIN_REGISTER) throw new Error("Missing DIRECTUS_TOKEN_ADMIN_REGISTER");
|
||||
const res = await fetch(`${BASE}${path}`, {
|
||||
...init,
|
||||
headers: { Accept: "application/json", Authorization: `Bearer ${TOKEN_ADMIN_REGISTER}`, ...(init?.headers || {}) },
|
||||
cache: "no-store",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: `Bearer ${TOKEN_ADMIN_REGISTER}`,
|
||||
...(init?.headers || {}),
|
||||
},
|
||||
cache: "no-store",
|
||||
});
|
||||
return (await throwIfNotOk(res)) as T;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Optional folder lookup (server-only if using admin token)
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
type FolderItem = { id: string; name: string; parent?: { id?: string; name?: string } | null };
|
||||
const folderCache = new Map<string, string | undefined>();
|
||||
let folderListCache: FolderItem[] | null = null;
|
||||
let folderListCacheAt = 0;
|
||||
|
||||
async function fetchAllFolders(): Promise<FolderItem[] | null> {
|
||||
try {
|
||||
const q = `/folders?fields=id,name,parent.id,parent.name&limit=500`;
|
||||
const res = await directusAdminFetch<{ data: FolderItem[] }>(q);
|
||||
return res?.data ?? [];
|
||||
} catch (e: any) {
|
||||
console.warn("[directus] fetchAllFolders failed:", e?.message || e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getFolderIdByPath(path: string): Promise<string | undefined> {
|
||||
if (!path) return undefined;
|
||||
if (folderCache.has(path)) return folderCache.get(path);
|
||||
|
||||
const now = Date.now();
|
||||
const freshForMs = 60_000;
|
||||
if (!folderListCache || now - folderListCacheAt > freshForMs) {
|
||||
folderListCache = await fetchAllFolders();
|
||||
folderListCacheAt = now;
|
||||
}
|
||||
const list = folderListCache;
|
||||
if (!list) {
|
||||
folderCache.set(path, undefined);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const parts = path.split("/").map((s) => s.trim()).filter(Boolean);
|
||||
const [parentName, childName] = parts;
|
||||
const eq = (a?: string | null, b?: string | null) => String(a ?? "").toLowerCase() === String(b ?? "").toLowerCase();
|
||||
|
||||
let match: FolderItem | undefined;
|
||||
if (parts.length >= 2) {
|
||||
match = list.find((f) => eq(f.name, childName) && eq(f.parent?.name ?? "", parentName));
|
||||
} else {
|
||||
match = list.find((f) => eq(f.name, parts[0]));
|
||||
}
|
||||
|
||||
const id = match?.id ? String(match.id) : undefined;
|
||||
folderCache.set(path, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Files & items — user bearer ONLY
|
||||
// Files & items — user bearer ONLY (no folder listing/browsing)
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
export async function uploadFile(
|
||||
file: Blob | File,
|
||||
filename: string,
|
||||
bearer: string,
|
||||
options?: { folderId?: string; folderNamePath?: string; title?: string }
|
||||
options?: { folderId?: string; title?: string } // folderNamePath removed
|
||||
): Promise<{ id: string }> {
|
||||
const form = new FormData();
|
||||
form.set("file", file, filename);
|
||||
form.set("filename_download", filename);
|
||||
if (options?.title) form.set("title", options.title);
|
||||
|
||||
let folderId = options?.folderId;
|
||||
if (!folderId && options?.folderNamePath) {
|
||||
try { folderId = await getFolderIdByPath(options.folderNamePath); } catch {}
|
||||
}
|
||||
if (folderId) form.set("folder", folderId);
|
||||
if (options?.folderId) form.set("folder", options.folderId); // user-scoped
|
||||
|
||||
const res = await fetch(`${BASE}/files`, {
|
||||
method: "POST",
|
||||
|
|
@ -204,7 +193,7 @@ export async function patchProject(
|
|||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Auth helpers (registration / login support)
|
||||
/** Auth helpers (registration / login support) */
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
export async function resolveMemberRoleId(): Promise<string> {
|
||||
|
|
@ -221,7 +210,7 @@ export async function createDirectusUser(input: {
|
|||
username: string;
|
||||
password: string;
|
||||
email?: string;
|
||||
}): Promise<{ id: string }> {
|
||||
}: PromiseLike<any> extends never ? never : any): Promise<{ id: string }> {
|
||||
const role = await resolveMemberRoleId();
|
||||
|
||||
// If email is omitted, create a stable placeholder so login can still work.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue