Migrate backend from PocketBase to Directus

Full cutover: auth, users, collection and friendships now run on Directus
via @directus/sdk, with owner/visibility rules enforced by Directus
permissions.

- middleware: Directus session (directus_auth cookie) with token refresh
- /login is Directus (email normalized to lowercase), new /register page
  (first/last name, username) backed by public registration
- collection, friends and profile pages + all API endpoints ported;
  ownership and addressee-only accept/decline enforced by permissions
- display names use first_name/last_name everywhere (nav, friends, profile)
- PocketBase SDK, POCKETBASE_URL and pb_schema.json removed
- README and compose updated for DIRECTUS_URL
This commit is contained in:
2026-07-28 20:33:46 +02:00
parent a3a69e0eae
commit fc51371dad
37 changed files with 986 additions and 765 deletions
+8 -19
View File
@@ -1,26 +1,14 @@
/**
* Shared helpers for the `mycards` PocketBase collection:
* Shared helpers for the `mycards` collection:
* record typing and Cardmarket price resolution via the cached TCGdex client.
*/
import { getCard } from "./tcgdex-cache";
import type { MyCard } from "./directus";
export interface MyCardRecord {
id: string;
user: string;
card_id: string;
card_name: string;
card_image: string;
variant: string;
quantity: number;
condition: string;
notes: string;
set_name: string;
created: string;
updated: string;
}
export type MyCardRecord = MyCard;
export type PricedCard = MyCardRecord & { price: number; totalPrice: number };
export type PricedCard = MyCard & { price: number; totalPrice: number };
/** Collection variant label → Cardmarket avg price field. */
export const variantPriceMap: Record<string, string> = {
@@ -37,7 +25,7 @@ export const variantPriceMap: Record<string, string> = {
* repeated page loads don't hit the TCGdex API again.
*/
export async function priceCollectionCards(
cards: MyCardRecord[],
cards: MyCard[],
): Promise<PricedCard[]> {
return Promise.all(
cards.map(async (card) => {
@@ -45,10 +33,11 @@ export async function priceCollectionCards(
const detail = await getCard(card.card_id);
const cardmarket = detail?.pricing?.cardmarket;
if (cardmarket) {
const priceKey = variantPriceMap[card.variant] ?? "avg";
const priceKey = variantPriceMap[card.variant ?? ""] ?? "avg";
price = cardmarket[priceKey] ?? cardmarket.avg ?? 0;
}
return { ...card, price, totalPrice: price * card.quantity };
const quantity = card.quantity ?? 1;
return { ...card, price, totalPrice: price * quantity };
}),
);
}