189 lines
8.6 KiB
JavaScript
189 lines
8.6 KiB
JavaScript
/**
|
||
* Legt ein Demo-Konto mit Ordner und drei Modellen (inkl. echter Bild-Uploads)
|
||
* an – damit sich die Anzeige in der App gegen echte Daten prüfen lässt.
|
||
*
|
||
* Läuft als Client (Cookie-Session), nicht mit dem Server-Key: so wird
|
||
* nebenbei belegt, dass die Tabellen-Rechte aus E2 wirklich ausreichen.
|
||
*/
|
||
import { readFileSync } from 'node:fs';
|
||
import { basename } from 'node:path';
|
||
|
||
const ENDPOINT = 'https://appwrite.webklar.com/v1';
|
||
const PROJECT = '6a5cee34002bb8360c34';
|
||
const DB = 'brandloop';
|
||
const UP = 'A:/developer/GitHub-desktop/videogen/uploads';
|
||
|
||
const email = process.argv[2] ?? 'demo@brandloop.test';
|
||
const password = process.argv[3] ?? 'Demo-2026-brandloop';
|
||
const label = 'Modaily';
|
||
|
||
const jar = new Map();
|
||
async function call(method, path, body, isForm = false) {
|
||
const headers = { 'X-Appwrite-Project': PROJECT };
|
||
if (!isForm) headers['Content-Type'] = 'application/json';
|
||
if (jar.size) headers.Cookie = [...jar].map(([k, v]) => `${k}=${v}`).join('; ');
|
||
const res = await fetch(`${ENDPOINT}${path}`, {
|
||
method, headers, body: isForm ? body : body ? JSON.stringify(body) : undefined,
|
||
});
|
||
for (const c of res.headers.getSetCookie?.() ?? []) {
|
||
const [pair] = c.split(';');
|
||
const i = pair.indexOf('=');
|
||
if (i > 0) jar.set(pair.slice(0, i).trim(), pair.slice(i + 1).trim());
|
||
}
|
||
const t = await res.text();
|
||
let j; try { j = JSON.parse(t); } catch { j = { message: t }; }
|
||
if (res.status >= 300) throw new Error(`${method} ${path} -> ${res.status} ${j.message}`);
|
||
return j;
|
||
}
|
||
|
||
// --- Konto -----------------------------------------------------------------
|
||
try {
|
||
await call('POST', '/account', { userId: 'unique()', email, password, name: label });
|
||
console.log('Konto angelegt:', email);
|
||
} catch (e) {
|
||
if (!String(e.message).includes('already exists')) throw e;
|
||
console.log('Konto existiert bereits:', email);
|
||
}
|
||
await call('POST', '/account/sessions/email', { email, password });
|
||
|
||
const teamsVorhanden = await call('GET', '/teams');
|
||
const team = teamsVorhanden.teams[0] ?? (await call('POST', '/teams', { teamId: 'unique()', name: label }));
|
||
const rolle = `team:${team.$id}`;
|
||
const rechte = [`read("${rolle}")`, `update("${rolle}")`, `delete("${rolle}")`];
|
||
|
||
const brandsVorhanden = await call('GET', `/tablesdb/${DB}/tables/brands/rows`);
|
||
const brand = brandsVorhanden.rows[0] ?? (await call('POST', `/tablesdb/${DB}/tables/brands/rows`, {
|
||
rowId: 'unique()',
|
||
data: { team_id: team.$id, label_name: label, status: 'trial', plan: 'trial', nische: 'beauty_kosmetik' },
|
||
permissions: rechte,
|
||
}));
|
||
console.log(`Marke: ${brand.label_name} (${brand.$id}), Team ${team.$id}`);
|
||
|
||
// --- Kategorien + Attribute + brand-weite Scores ---------------------------
|
||
// Das erzeugt später P2 aus dem Onboarding. Hier von Hand, damit ein Ordner
|
||
// mit Startwert `erben` überhaupt etwas zu erben hat.
|
||
const WISSEN = {
|
||
location: ['badezimmer morgens', 'studio grauer hintergrund', 'kueche tageslicht'],
|
||
licht: ['weiches seitenlicht', 'hartes sonnenlicht', 'flaches studiolicht'],
|
||
farben: ['warme erdtoene', 'kuehles monochrom', 'kraeftiger rotakzent'],
|
||
kamera: ['augenhoehe frontal', 'leichte aufsicht', 'detail makro'],
|
||
};
|
||
|
||
const katVorhanden = await call('GET', `/tablesdb/${DB}/tables/categories/rows`);
|
||
if (!katVorhanden.rows.length) {
|
||
let attrCount = 0;
|
||
for (const [kat, attribute] of Object.entries(WISSEN)) {
|
||
const c = await call('POST', `/tablesdb/${DB}/tables/categories/rows`, {
|
||
rowId: 'unique()',
|
||
data: { brand_id: brand.$id, name: kat, avg_score: 5000, attribute_count: attribute.length },
|
||
permissions: rechte,
|
||
});
|
||
for (const name of attribute) {
|
||
const a = await call('POST', `/tablesdb/${DB}/tables/attributes/rows`, {
|
||
rowId: 'unique()',
|
||
data: {
|
||
brand_id: brand.$id, category_id: c.$id, name,
|
||
slug: name.replace(/\s+/g, '-'), status: 'aktiv',
|
||
beschreibung_md: `Aus dem Onboarding abgeleitet: ${name}.`,
|
||
},
|
||
permissions: rechte,
|
||
});
|
||
// Brand-weite Ebene: folder_id bleibt leer.
|
||
await call('POST', `/tablesdb/${DB}/tables/attribute_scores/rows`, {
|
||
rowId: 'unique()',
|
||
data: {
|
||
brand_id: brand.$id, attribute_id: a.$id, category_id: c.$id,
|
||
score: 5000, start_value: 5000, k_factor: 32, start_quelle: 'neutral',
|
||
used_count: 0, wins: 0, losses: 0,
|
||
},
|
||
permissions: rechte,
|
||
});
|
||
attrCount++;
|
||
}
|
||
}
|
||
console.log(`Wissen: ${Object.keys(WISSEN).length} Kategorien, ${attrCount} Attribute (brand-weit)`);
|
||
} else {
|
||
console.log(`Wissen: ${katVorhanden.rows.length} Kategorien vorhanden`);
|
||
}
|
||
|
||
// --- Ordner ----------------------------------------------------------------
|
||
const ordnerVorhanden = await call('GET', `/tablesdb/${DB}/tables/folders/rows`);
|
||
if (!ordnerVorhanden.rows.length) {
|
||
for (const o of [
|
||
{ name: 'Sommerkampagne', zweck: 'wissens_scope', startwert_modus: 'erben' },
|
||
{ name: 'Cleane Studioshots', zweck: 'wissens_scope', startwert_modus: 'aus_posts' },
|
||
]) {
|
||
const r = await call('POST', `/tablesdb/${DB}/tables/folders/rows`, {
|
||
rowId: 'unique()',
|
||
data: { brand_id: brand.$id, ...o, ist_default: o.name === 'Sommerkampagne', post_count: 0, signal_count: 0 },
|
||
permissions: rechte,
|
||
});
|
||
// Gleiche Regel wie ordnerInitialisieren() im Client: nur `erben` kopiert
|
||
// die brand-weite Ebene, alles andere startet leer.
|
||
let kopiert = 0;
|
||
if (o.startwert_modus === 'erben') {
|
||
const q = encodeURIComponent(JSON.stringify({ method: 'isNull', attribute: 'folder_id' }));
|
||
const l = encodeURIComponent(JSON.stringify({ method: 'limit', values: [200] }));
|
||
const quelle = await call('GET', `/tablesdb/${DB}/tables/attribute_scores/rows?queries[]=${q}&queries[]=${l}`);
|
||
for (const z of quelle.rows) {
|
||
await call('POST', `/tablesdb/${DB}/tables/attribute_scores/rows`, {
|
||
rowId: 'unique()',
|
||
data: {
|
||
brand_id: brand.$id, attribute_id: z.attribute_id, category_id: z.category_id,
|
||
folder_id: r.$id, score: z.score, start_value: z.score, k_factor: 32,
|
||
start_quelle: 'geerbt', used_count: 0, wins: 0, losses: 0,
|
||
},
|
||
permissions: rechte,
|
||
});
|
||
kopiert++;
|
||
}
|
||
}
|
||
console.log(`Ordner: ${r.name} (${o.zweck}, ${o.startwert_modus}) – ${kopiert} Scores geerbt`);
|
||
}
|
||
} else {
|
||
console.log(`Ordner: ${ordnerVorhanden.rows.length} vorhanden`);
|
||
}
|
||
|
||
// --- Modelle mit echten Referenzbildern ------------------------------------
|
||
async function bildHochladen(datei) {
|
||
const bytes = readFileSync(`${UP}/${datei}`);
|
||
const fd = new FormData();
|
||
fd.append('fileId', 'unique()');
|
||
fd.append('file', new Blob([bytes], { type: 'image/jpeg' }), basename(datei));
|
||
for (const p of rechte) fd.append('permissions[]', p);
|
||
const f = await call('POST', '/storage/buckets/asset-references/files', fd, true);
|
||
return f.$id;
|
||
}
|
||
|
||
const MODELLE = [
|
||
{ typ: 'produkt', name: 'Refine & Renew B3 Serum', datei: 'pasted-1784663200459-0.jpg', beschreibung: 'Weiße Pumpflasche, 30 ml, rote Typo, Aufschrift MODAILY vertikal links.' },
|
||
{ typ: 'produkt', name: 'Age Decoder Essence', datei: 'pasted-1784643165286-0.jpg', beschreibung: 'Pipettenflasche, rosé-transparentes Glas, mattgraue Kappe, Schriftzug okolo.' },
|
||
{ typ: 'kulisse', name: 'U-Bahn, Türbereich', datei: 'pasted-1784643111353-0.jpg', beschreibung: 'Metallische U-Bahn-Türen, Haltestangen, weiches Kunstlicht, gedämpfte Erdtöne.' },
|
||
{ typ: 'kulisse', name: 'Halle, Metallwand', datei: 'pasted-1784663261701-0.jpg', beschreibung: 'Minimalistischer Innenraum, Stahlwand mit kühlen Reflexen, polierter Boden.' },
|
||
];
|
||
|
||
const assetsVorhanden = await call('GET', `/tablesdb/${DB}/tables/assets/rows`);
|
||
if (assetsVorhanden.rows.length) {
|
||
console.log(`Modelle: ${assetsVorhanden.rows.length} vorhanden, kein Neuanlegen`);
|
||
} else {
|
||
for (const m of MODELLE) {
|
||
const fileId = await bildHochladen(m.datei);
|
||
const asset = await call('POST', `/tablesdb/${DB}/tables/assets/rows`, {
|
||
rowId: 'unique()',
|
||
data: { brand_id: brand.$id, typ: m.typ, name: m.name, ist_teilbar: false, nische: 'beauty_kosmetik' },
|
||
permissions: rechte,
|
||
});
|
||
const version = await call('POST', `/tablesdb/${DB}/tables/asset_versions/rows`, {
|
||
rowId: 'unique()',
|
||
data: { asset_id: asset.$id, version_no: 1, status: 'freigegeben', beschreibung_md: m.beschreibung, reference_file_ids: [fileId] },
|
||
permissions: rechte,
|
||
});
|
||
await call('PATCH', `/tablesdb/${DB}/tables/assets/rows/${asset.$id}`, {
|
||
data: { released_version_id: version.$id },
|
||
});
|
||
console.log(`Modell: ${m.name} [${m.typ}] Bild ${fileId}`);
|
||
}
|
||
}
|
||
|
||
console.log(`\nFertig. Anmelden mit ${email} / ${password}`);
|