Files
LogiFlow/scripts/bench/cases/fournisseurs-commandes.ts
T
LogiFlowandClaude Opus 5.5 600627c632 feat(rust) : serveur réécrit en Rust, Node retiré ; Calendrier en tête du menu (#571)
- Serveur Rust (server-rs/, axum + sqlx) : les 148 adresses de l'API,
  l'interface, les migrations de démarrage et les tâches de fond ; même
  comportement que Node (banc : 2027 cas, 0 écart hors écarts déclarés
  USERS-02, RAPPRO-21, R4, R5).
- Node retiré : server/, dépendances serveur, image Docker sans Node
  (51 Mo), docker-compose à un seul service applicatif.
- R4 : écritures d'un autre site refusées (Sec-Fetch-Site / Origin).
- R5 : « aujourd'hui » et sauvegarde automatique à l'heure de Paris.
- R6 : mainteneur dans .github/CODEOWNERS.
- Menu latéral : Accueil puis Calendrier en tête.
- Plan et état : docs/PLAN-MIGRATION-RUST.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ke2qyS8rgacP3ap5iezDbM
2026-10-15 20:31:14 +02:00

366 lines
21 KiB
TypeScript

// Lot 4, commandes et livraisons : commandes fournisseurs, livraisons (validation,
// contrôle, rapprochement automatique, échéance relue dans NocoDB), API externe
// de rapprochement (/api/ext/v1, clé d'API) et relais ffnancy (sans réseau : seul
// le refus sans session est rejoué). NocoDB est simulé par stub.ts.
import { BENCH_API_KEY } from "../config";
import type { AccountKey, ReadCase, WriteScenario } from "../types";
// Clé d'API (en-tête X-API-Key), et deux sessions qui n'y donnent pas accès
const EXT_ACCOUNTS: AccountKey[] = ["api", "anonyme", "admin"];
// Durée de vie des entrées du cache des factures : relative à l'heure du passage
const CACHE_IGNORE = ["expires_at"];
const ext = (path: string): ReadCase => ({ module: "external-api-v1", path: `/api/ext/v1${path}`, accounts: EXT_ACCOUNTS });
export const reads: ReadCase[] = [
// ---------- Commandes (Orders.tsx, Dashboard.tsx, Calendar.tsx, modales, mobile) ----------
{ module: "orders", path: "/api/orders" },
{ module: "orders", path: "/api/orders?storeId=1" },
{ module: "orders", path: "/api/orders?storeId=2" },
{ module: "orders", path: "/api/orders?storeId=3" },
{ module: "orders", path: "/api/orders?storeId=abc" },
{ module: "orders", path: "/api/orders?" },
{ module: "orders", path: "/api/orders?startDate={{today-30}}&endDate={{today+30}}" },
{ module: "orders", path: "/api/orders?startDate={{today-30}}&endDate={{today+30}}&storeId=1" },
{ module: "orders", path: "/api/orders?storeId=2&startDate={{today}}&endDate={{today}}" },
{ module: "orders", path: "/api/orders?startDate={{today-400}}&endDate={{today+400}}&storeId=2" },
{ module: "orders", path: "/api/orders?startDate={{today}}" },
{ module: "orders", path: "/api/orders?startDate=abc&endDate=xyz" },
{ module: "orders", path: "/api/orders/1" },
{ module: "orders", path: "/api/orders/2" },
{ module: "orders", path: "/api/orders/4" },
{ module: "orders", path: "/api/orders/6" },
{ module: "orders", path: "/api/orders/600" },
{ module: "orders", path: "/api/orders/999" },
{ module: "orders", path: "/api/orders/abc" },
// ---------- Livraisons (Deliveries.tsx, BLReconciliation.tsx, Calendar.tsx, mobile) ----------
{ module: "deliveries", path: "/api/deliveries" },
{ module: "deliveries", path: "/api/deliveries?storeId=1" },
{ module: "deliveries", path: "/api/deliveries?storeId=2" },
{ module: "deliveries", path: "/api/deliveries?storeId=3" },
{ module: "deliveries", path: "/api/deliveries?storeId=abc" },
{ module: "deliveries", path: "/api/deliveries?" },
{ module: "deliveries", path: "/api/deliveries?status=delivered" },
{ module: "deliveries", path: "/api/deliveries?status=delivered&storeId=2" },
{ module: "deliveries", path: "/api/deliveries?status=inconnu" },
{ module: "deliveries", path: "/api/deliveries?startDate={{today-30}}&endDate={{today+30}}" },
{ module: "deliveries", path: "/api/deliveries?startDate={{today-30}}&endDate={{today+30}}&storeId=1" },
{ module: "deliveries", path: "/api/deliveries?storeId=2&startDate={{today}}&endDate={{today}}" },
{ module: "deliveries", path: "/api/deliveries?startDate={{today-30}}&endDate={{today+30}}&status=planned" },
{ module: "deliveries", path: "/api/deliveries?startDate=abc&endDate=xyz" },
{ module: "deliveries", path: "/api/deliveries?withBL=true" },
{ module: "deliveries", path: "/api/deliveries?withBL=true&storeId=1" },
{ module: "deliveries", path: "/api/deliveries/1" },
{ module: "deliveries", path: "/api/deliveries/3" },
{ module: "deliveries", path: "/api/deliveries/5" },
{ module: "deliveries", path: "/api/deliveries/6" },
{ module: "deliveries", path: "/api/deliveries/7" },
{ module: "deliveries", path: "/api/deliveries/600" },
{ module: "deliveries", path: "/api/deliveries/601" },
{ module: "deliveries", path: "/api/deliveries/999" },
{ module: "deliveries", path: "/api/deliveries/abc" },
// ---------- API externe (docs/API-RAPPROCHEMENT.md) ----------
ext("/stores"),
ext("/suppliers"),
ext("/deliveries"),
ext("/deliveries?storeId=1"),
ext("/deliveries?storeId=2&reconciled=true"),
ext("/deliveries?supplierId=2&hasInvoice=false"),
ext("/deliveries?blNumber=%20bl-1002%20"),
ext("/deliveries?blNumber=BL-1002&blNumber=BL-1004"),
ext("/deliveries?hasBl=true&from={{today-30}}&to={{today-2}}"),
ext("/deliveries?to={{today-6}}&reconciled=false"),
ext("/deliveries?limit=2&offset=1"),
ext("/deliveries?limit=1e3&offset=0x2"),
ext("/deliveries?offset=50&limit="),
ext("/deliveries?storeId=0&supplierId=1.5&reconciled=oui&hasBl=1&hasInvoice=&from=2026-1-1&to=x&limit=0&offset=-1"),
ext("/deliveries?storeId=1&storeId=2"),
ext("/deliveries?storeId=99999999999"),
ext("/deliveries/1"),
ext("/deliveries/2"),
ext("/deliveries/3"),
ext("/deliveries/602"),
ext("/deliveries/999"),
ext("/deliveries/abc"),
ext("/deliveries/0"),
ext("/deliveries/1.5"),
ext("/deliveries/99999999999"),
ext(""),
ext("/"),
ext("/inconnue"),
ext("/stores/extra"),
// Variantes qu'Express accepte (casse, barre finale) : relayées à Node
ext("/stores/"),
ext("/SUPPLIERS"),
// ---------- Relais ffnancy : sans session seulement (les autres appels partent sur Internet) ----------
{ module: "ffnancy", path: "/api/ffnancy/articles?ean=3245678901234&limit=1", accounts: ["anonyme"] },
{ module: "ffnancy", path: "/api/ffnancy/mouvements/entrees?artNoId=1&limit=1&dateDebut=2000-01-01", accounts: ["anonyme"] },
];
const NEW_DELIVERY = { supplierId: 1, groupId: 1, scheduledDate: "{{today+4}}", quantity: 1, unit: "colis" };
export const writes: WriteScenario[] = [
// ---------- Commandes ----------
{
module: "orders",
name: "commandes : créer, modifier, supprimer",
steps: [
{ method: "POST", path: "/api/orders", body: { supplierId: 1, groupId: 1, plannedDate: "{{today+7}}", notes: "Commande du banc" } },
{ method: "PUT", path: "/api/orders/{{0.id}}", body: { plannedDate: "{{today+8}}", notes: "Modifiée", quantity: 3, unit: "palettes" } },
{ method: "GET", path: "/api/orders/{{0.id}}" },
{ method: "DELETE", path: "/api/orders/{{0.id}}" },
{ method: "POST", path: "/api/orders", body: { supplierId: 2, groupId: 2, plannedDate: "{{today+1}}" } },
{ method: "PUT", path: "/api/orders/4", body: { status: "planned" } },
{ method: "PUT", path: "/api/orders/6", body: { notes: "Épinal" } },
{ method: "DELETE", path: "/api/orders/6" },
{ method: "DELETE", path: "/api/orders/3" },
// Commande qui a des livraisons : clé étrangère
{ method: "DELETE", path: "/api/orders/2" },
{ method: "GET", path: "/api/orders?storeId=1" },
],
tables: ["orders"],
},
{
module: "orders",
name: "commandes : corps invalides et identifiants inconnus",
steps: [
{ method: "POST", path: "/api/orders", body: {} },
{ method: "POST", path: "/api/orders", body: { supplierId: "1", groupId: 1.5, plannedDate: 5, quantity: "2" } },
{ method: "POST", path: "/api/orders", body: [] },
{ method: "POST", path: "/api/orders", body: { supplierId: 1, groupId: 1, plannedDate: "pas une date" } },
{ method: "POST", path: "/api/orders", body: { supplierId: 999, groupId: 1, plannedDate: "{{today}}" } },
{ method: "POST", path: "/api/orders", body: { supplierId: 1, groupId: 1, plannedDate: "{{today}}", createdBy: "admin_local", status: "delivered", id: 999, createdAt: "{{today}}" } },
{ method: "PUT", path: "/api/orders/1", body: { groupId: "x" } },
{ method: "PUT", path: "/api/orders/1", body: [] },
{ method: "PUT", path: "/api/orders/1", body: {} },
// Commande déplacée dans un autre magasin, créateur changé
{ method: "PUT", path: "/api/orders/3", body: { groupId: 3, createdBy: "bench_employee" } },
{ method: "PUT", path: "/api/orders/999", body: {} },
{ method: "PUT", path: "/api/orders/abc", body: {} },
{ method: "DELETE", path: "/api/orders/999" },
{ method: "DELETE", path: "/api/orders/abc" },
{ method: "GET", path: "/api/orders/3" },
],
tables: ["orders"],
},
{
module: "orders",
name: "synchroniser les statuts commandes / livraisons",
steps: [
{ method: "POST", path: "/api/sync-order-delivery-status" },
{ method: "POST", path: "/api/sync-order-delivery-status" },
{ method: "GET", path: "/api/orders/600" },
],
tables: ["orders"],
},
// ---------- Livraisons ----------
{
module: "deliveries",
name: "livraisons : créer, modifier, supprimer",
steps: [
// CreateDeliveryModal : commande 3 en attente, passée à « planned »
{ method: "POST", path: "/api/deliveries", body: { orderId: "3", supplierId: 3, groupId: 1, scheduledDate: "{{today+3}}", quantity: 2, unit: "palettes", status: "planned", notes: "Livraison du banc", createdBy: "admin_local" } },
// EditDeliveryModal
{ method: "PUT", path: "/api/deliveries/{{0.id}}", body: { supplierId: 3, groupId: 1, orderId: null, scheduledDate: "{{today+5}}", quantity: 4, unit: "palettes", notes: "Modifiée" } },
{ method: "GET", path: "/api/deliveries/{{0.id}}" },
{ method: "DELETE", path: "/api/deliveries/{{0.id}}" },
{ method: "POST", path: "/api/deliveries", body: { ...NEW_DELIVERY, orderId: "none", groupId: 2 } },
{ method: "POST", path: "/api/deliveries", body: { ...NEW_DELIVERY, orderId: 4 } },
{ method: "POST", path: "/api/deliveries", body: { ...NEW_DELIVERY, orderId: 999 } },
{ method: "PUT", path: "/api/deliveries/5", body: { orderId: 1 } },
{ method: "PUT", path: "/api/deliveries/5", body: { orderId: 999 } },
{ method: "PUT", path: "/api/deliveries/5", body: { notes: "Metz" } },
{ method: "PUT", path: "/api/deliveries/601", body: { orderId: 3 } },
{ method: "DELETE", path: "/api/deliveries/7" },
// Commentaires supprimés avec la livraison (cascade)
{ method: "DELETE", path: "/api/deliveries/2" },
{ method: "GET", path: "/api/deliveries?storeId=1" },
],
tables: ["deliveries", "orders", "reconciliation_comments"],
},
{
module: "deliveries",
name: "livraisons : corps invalides et identifiants inconnus",
steps: [
{ method: "POST", path: "/api/deliveries", body: {} },
{ method: "POST", path: "/api/deliveries", body: [] },
{ method: "POST", path: "/api/deliveries", body: { ...NEW_DELIVERY, orderId: "abc" } },
{ method: "POST", path: "/api/deliveries", body: { ...NEW_DELIVERY, orderId: "12abc" } },
{ method: "POST", path: "/api/deliveries", body: { ...NEW_DELIVERY, orderId: 0 } },
{ method: "POST", path: "/api/deliveries", body: { ...NEW_DELIVERY, orderId: "" } },
{ method: "POST", path: "/api/deliveries", body: { ...NEW_DELIVERY, deliveredDate: "{{today}}" } },
{ method: "POST", path: "/api/deliveries", body: { ...NEW_DELIVERY, status: "", blAmount: "12.5", blNumber: "BL-9000" } },
{ method: "POST", path: "/api/deliveries", body: { ...NEW_DELIVERY, groupId: 3, orderId: 6 } },
{ method: "PUT", path: "/api/deliveries/1", body: { invoiceAmountTTC: 5 } },
{ method: "PUT", path: "/api/deliveries/1", body: { dueDate: "pas une date" } },
{ method: "PUT", path: "/api/deliveries/4", body: { deliveredDate: "{{today}}" } },
{ method: "PUT", path: "/api/deliveries/4", body: { validatedAt: "", blAmount: 99.5, invoiceAmount: true, notes: null } },
{ method: "PUT", path: "/api/deliveries/4", body: { blAmount: 99.5, notes: null, blNumber: "" } },
{ method: "PUT", path: "/api/deliveries/4", body: [] },
{ method: "PUT", path: "/api/deliveries/999", body: {} },
{ method: "PUT", path: "/api/deliveries/abc", body: {} },
{ method: "DELETE", path: "/api/deliveries/999" },
{ method: "DELETE", path: "/api/deliveries/abc" },
{ method: "GET", path: "/api/deliveries/4" },
],
tables: ["deliveries", "orders"],
},
{
module: "deliveries",
name: "livraisons : référence de facture et échéance (NocoDB)",
steps: [
{ method: "PUT", path: "/api/deliveries/2", body: { invoiceReference: "FAC-2002", invoiceAmount: "310.00" } },
{ method: "PUT", path: "/api/deliveries/2", body: { invoiceReference: "FAC-2002" } },
{ method: "PUT", path: "/api/deliveries/2", body: { invoiceReference: "FAC-9999" } },
{ method: "PUT", path: "/api/deliveries/2", body: { invoiceReference: "", dueDate: "{{today+60}}" } },
{ method: "PUT", path: "/api/deliveries/4", body: { invoiceReference: "AV-3001" } },
{ method: "PUT", path: "/api/deliveries/6", body: { invoiceReference: "FAC-2006" } },
{ method: "PUT", path: "/api/deliveries/6", body: { invoiceReference: "FAC-2001", dueDate: null } },
// Validation rapide et dévalidation (BLReconciliation.tsx)
{ method: "PUT", path: "/api/deliveries/4", body: { reconciled: true, validatedAt: "{{today}}T08:00:00.000Z" } },
{ method: "PUT", path: "/api/deliveries/1", body: { reconciled: false, validatedAt: null } },
{ method: "GET", path: "/api/deliveries/2" },
],
tables: ["deliveries", "invoice_verification_cache"],
ignore: CACHE_IGNORE,
},
{
module: "deliveries",
name: "livraisons : rapprochement automatique à la modification",
steps: [
// Bonduelle : livrée avec un BL, rapprochée d'office (la réponse reste la première mise à jour)
{ method: "PUT", path: "/api/deliveries/601", body: { status: "delivered", blNumber: "BL-6010" } },
{ method: "PUT", path: "/api/deliveries/3", body: { blNumber: "BL-3000" } },
// Fournisseur changé : relu dans la table
{ method: "PUT", path: "/api/deliveries/3", body: { supplierId: 1, status: "delivered" } },
{ method: "PUT", path: "/api/deliveries/600", body: { supplierId: 2, blNumber: "BL-6000" } },
{ method: "GET", path: "/api/deliveries/601" },
],
tables: ["deliveries"],
},
{
module: "deliveries",
name: "livraisons : valider",
steps: [
// Bonduelle, commande 2 : livraison rapprochée d'office, commande livrée
{ method: "POST", path: "/api/deliveries/3/validate", body: { blNumber: " BL-3001 ", blAmount: 150.5 } },
{ method: "POST", path: "/api/deliveries/5/validate", body: { blNumber: "BL-5001" } },
{ method: "POST", path: "/api/deliveries/7/validate", body: { blNumber: "BL-7001", blAmount: "" } },
// Cache : référence et BL de la livraison marqués « rapprochés »
{ method: "POST", path: "/api/deliveries/4/validate", body: { blNumber: "BL-1004" } },
{ method: "POST", path: "/api/deliveries/8/validate", body: { blNumber: "BL-0900", blAmount: null } },
{ method: "POST", path: "/api/deliveries/2/validate", body: { blNumber: " " } },
{ method: "POST", path: "/api/deliveries/2/validate", body: {} },
{ method: "POST", path: "/api/deliveries/2/validate", body: [] },
{ method: "POST", path: "/api/deliveries/2/validate", body: { blNumber: 5 } },
{ method: "POST", path: "/api/deliveries/2/validate", body: { blNumber: "BL-1002", blAmount: "abc" } },
{ method: "POST", path: "/api/deliveries/999/validate", body: { blNumber: "x" } },
{ method: "POST", path: "/api/deliveries/abc/validate", body: { blNumber: "x" } },
{ method: "GET", path: "/api/deliveries/3" },
],
tables: ["deliveries", "orders", "invoice_verification_cache"],
ignore: CACHE_IGNORE,
},
{
module: "deliveries",
name: "livraisons : contrôle effectué",
steps: [
{ method: "PUT", path: "/api/deliveries/4/control" },
{ method: "PUT", path: "/api/deliveries/6/control" },
{ method: "PUT", path: "/api/deliveries/999/control" },
{ method: "PUT", path: "/api/deliveries/abc/control" },
{ method: "PUT", path: "/api/deliveries/99999999999/control" },
{ method: "GET", path: "/api/deliveries?storeId=2" },
],
tables: ["deliveries"],
},
// ---------- API externe ----------
{
module: "external-api-v1",
name: "API externe : écrire la facture",
accounts: ["api"],
steps: [
// Bonduelle (automatique), non rapprochée : échéance reprise de NocoDB
{ method: "PATCH", path: "/api/ext/v1/deliveries/2", body: { invoiceReference: " FAC-2002 ", invoiceAmount: "1 234,56", invoiceAmountTTC: 1481.47 } },
{ method: "PATCH", path: "/api/ext/v1/deliveries/2", body: { invoiceReference: "FAC-2002" } },
{ method: "PATCH", path: "/api/ext/v1/deliveries/2", body: { dueDate: "{{today+45}}", reconciled: true } },
{ method: "PATCH", path: "/api/ext/v1/deliveries/2", body: { invoiceAmount: "10" } },
// Lactalis rapprochée : figée, sauf dévalidation dans la même requête
{ method: "PATCH", path: "/api/ext/v1/deliveries/1", body: { invoiceAmount: 5 } },
{ method: "PATCH", path: "/api/ext/v1/deliveries/1", body: { invoiceAmount: 5, reconciled: false } },
{ method: "PATCH", path: "/api/ext/v1/deliveries/1", body: { invoiceReference: "" } },
{ method: "PATCH", path: "/api/ext/v1/deliveries/1", body: { reconciled: true } },
{ method: "PATCH", path: "/api/ext/v1/deliveries/602", body: { invoiceReference: "FAC-2006", invoiceAmountTTC: null } },
{ method: "PATCH", path: "/api/ext/v1/deliveries/3", body: { invoiceReference: "X" } },
{ method: "PATCH", path: "/api/ext/v1/deliveries/4", body: { invoiceReference: "FAC-9999" } },
{ method: "PATCH", path: "/api/ext/v1/deliveries/4", body: { dueDate: "2026-02-30" } },
{ method: "PATCH", path: "/api/ext/v1/deliveries/4", body: { dueDate: "2026-13-45" } },
{ method: "PATCH", path: "/api/ext/v1/deliveries/999", body: { reconciled: true } },
{ method: "PATCH", path: "/api/ext/v1/deliveries/abc", body: { reconciled: true } },
{ method: "PATCH", path: "/api/ext/v1/deliveries/99999999999", body: { reconciled: true } },
{ method: "GET", path: "/api/ext/v1/deliveries?storeId=1&hasInvoice=true" },
],
tables: ["deliveries", "invoice_verification_cache"],
ignore: CACHE_IGNORE,
},
{
module: "external-api-v1",
name: "API externe : corps invalides et routes inconnues",
accounts: ["api"],
steps: [
{ method: "PATCH", path: "/api/ext/v1/deliveries/2", body: {} },
{ method: "PATCH", path: "/api/ext/v1/deliveries/2", body: [] },
{ method: "PATCH", path: "/api/ext/v1/deliveries/2", body: { foo: 1, 2: 3 } },
{ method: "PATCH", path: "/api/ext/v1/deliveries/2", body: { invoiceAmount: "abc", dueDate: "15/10/2026", reconciled: "oui", extra: 1 } },
{ method: "PATCH", path: "/api/ext/v1/deliveries/2", body: { invoiceAmount: true, invoiceAmountTTC: "1e9" } },
{ method: "PATCH", path: "/api/ext/v1/deliveries/2", body: { invoiceReference: "x".repeat(101) } },
{ method: "PATCH", path: "/api/ext/v1/deliveries/2", body: { invoiceReference: 5, reconciled: null } },
{ method: "PATCH", path: "/api/ext/v1/deliveries/abc", body: {} },
{ method: "POST", path: "/api/ext/v1/deliveries", body: {} },
{ method: "PUT", path: "/api/ext/v1/deliveries/2", body: {} },
{ method: "DELETE", path: "/api/ext/v1/stores" },
{ method: "POST", path: "/api/ext/v1/inconnue", body: { a: 1 } },
{ method: "GET", path: "/api/ext/v1/deliveries/2" },
],
tables: ["deliveries"],
},
{
module: "external-api-v1",
name: "API externe : clés refusées ou passées autrement",
accounts: ["api"],
steps: [
{ method: "GET", path: "/api/ext/v1/stores", headers: { "x-api-key": "lf_cle_revoquee" } },
{ method: "GET", path: "/api/ext/v1/stores", headers: { "x-api-key": "mauvaise" } },
{ method: "GET", path: "/api/ext/v1/stores", headers: { "x-api-key": " ", authorization: `Bearer ${BENCH_API_KEY}` } },
{ method: "GET", path: "/api/ext/v1/suppliers", headers: { "x-api-key": "", authorization: `bearer ${BENCH_API_KEY} ` } },
{ method: "GET", path: "/api/ext/v1/stores", headers: { "x-api-key": " ", authorization: "Bearer " } },
{ method: "GET", path: "/api/ext/v1/stores", headers: { "x-api-key": " ", authorization: `Basic ${BENCH_API_KEY}` } },
{ method: "PATCH", path: "/api/ext/v1/deliveries/2", body: { reconciled: true }, headers: { "x-api-key": "mauvaise" } },
{ method: "POST", path: "/api/ext/v1/inconnue", headers: { "x-api-key": "mauvaise" } },
],
tables: ["deliveries"],
},
{
module: "external-api-v1",
name: "API externe : désactivée sans clé active",
accounts: ["admin"],
steps: [
// La session ne donne pas accès à l'API externe
{ method: "GET", path: "/api/ext/v1/stores" },
{ method: "GET", path: "/api/ext/v1/stores", headers: { "x-api-key": BENCH_API_KEY } },
{ method: "DELETE", path: "/api/external-api/keys/1" },
{ method: "GET", path: "/api/ext/v1/stores" },
{ method: "GET", path: "/api/ext/v1/stores", headers: { "x-api-key": BENCH_API_KEY } },
{ method: "GET", path: "/api/ext/v1/inconnue" },
{ method: "PATCH", path: "/api/ext/v1/deliveries/2", body: { reconciled: true }, headers: { "x-api-key": BENCH_API_KEY } },
],
tables: ["external_api_keys"],
},
];