### Task 6: GUID esposto — resolve, componenti e GET singolo **Files:** - Modify: `src/lib/content.ts` - Modify: `src/components/content/T.astro` - Modify: `src/components/content/TImg.astro` - Create: `src/pages/api/admin/content/[tag]/index.ts` - Test: `tests/content.test.ts`, `tests/content-api.test.ts` **Interfaces:** - Consumes: `content_blocks.guid` (Task 1), `seedByTag` da `content-seed.ts`. - Produces: - `resolveContent(tag)` ritorna `{ value, type, classes, guid }` (`guid: string` — stringa vuota se seed-only) - `GET /api/admin/content/[tag]` → `{ tag, guid, type, value, styleOptions }` - [ ] **Step 1: Test su resolve + GET singolo** In `tests/content.test.ts` aggiungi: ```typescript it('resolveContent restituisce il guid dal DB', () => { const { resolveContent } = require('../src/lib/content'); const r = resolveContent('home.hero.title'); expect(typeof r.guid).toBe('string'); expect(r.guid.length).toBeGreaterThan(10); }); ``` In `tests/content-api.test.ts` aggiungi un test per il GET singolo seguendo il pattern già usato nel file per gli altri endpoint (stessa costruzione di `context`/`locals.user` degli altri test del file). Struttura: ```typescript it('GET /api/admin/content/[tag] ritorna tag, guid, type, value, styleOptions', async () => { const { GET } = await import('../src/pages/api/admin/content/[tag]/index'); const res = await GET({ params: { tag: 'home.hero.title' } } as any); expect(res.status).toBe(200); const body = await res.json(); expect(body.tag).toBe('home.hero.title'); expect(typeof body.guid).toBe('string'); expect(body.type).toBeDefined(); expect(Array.isArray(body.styleOptions)).toBe(true); }); ``` Se `content-api.test.ts` usa un DB di test isolato via `getDb`, riusa lo stesso setup già presente nel file (non reinventarlo). - [ ] **Step 2: Esegui e verifica il fallimento** Run: `npm test -- tests/content.test.ts tests/content-api.test.ts` Expected: FAIL — `guid` assente / modulo `[tag]/index` inesistente. - [ ] **Step 3: Espandi lo store in `content.ts`** Aggiorna `interface Entry` e la `resolve`: ```typescript interface Entry { value: string; type: ContentType; styles: Record; guid: string } ``` Nel `load()`, cambia la query e la costruzione: ```typescript const rows = dbGetter().prepare('SELECT tag, type, value, styles, guid FROM content_blocks').all() as { tag: string; type: ContentType; value: string; styles: string; guid: string | null }[]; cache = new Map(rows.map((r) => { let styles: Record = {}; try { styles = JSON.parse(r.styles); } catch { /* styles corrotto: si ignora */ } if (typeof styles !== 'object' || styles === null) styles = {}; return [r.tag, { value: r.value, type: r.type, styles, guid: r.guid ?? '' }]; })); ``` Nel metodo `resolve`, aggiorna i tre `return`: ```typescript resolve(tag: string): { value: string; type: ContentType; classes: string; guid: string } { const hit = load().get(tag); if (hit) return { value: hit.value, type: hit.type, classes: stylesToClasses(hit.styles), guid: hit.guid }; const seed = seedByTag.get(tag); if (seed) return { value: seed.value, type: seed.type, classes: '', guid: '' }; console.warn(`[content] tag sconosciuto: ${tag}`); return { value: '', type: 'text', classes: '', guid: '' }; }, ``` - [ ] **Step 4: Emetti `data-guid` nei componenti** In `src/components/content/T.astro`, dopo `const c = resolveContent(tag);` usa `c.guid`: ```astro {c.type === 'html' ? : {c.value}} ``` In `src/components/content/TImg.astro`, aggiungi `data-guid` in entrambi i rami. Il ramo override usa `getImageOverride` che non dà il guid; recupera il guid da `resolveContent`: ```astro --- import { Image } from 'astro:assets'; import type { ImageMetadata } from 'astro'; import { resolveContent } from '../../lib/content'; interface Props { tag: string; src: ImageMetadata; alt: string; class?: string; loading?: 'lazy' | 'eager'; widths?: number[]; sizes?: string; height?: number; } const { tag, src, alt, class: cls, ...rest } = Astro.props; const c = resolveContent(tag); const override = c.type === 'image' && c.value ? `/uploads/${c.value}` : null; const guid = c.guid || undefined; --- {override ? {alt} : {alt}} ``` - [ ] **Step 5: Crea l'endpoint GET singolo** Crea `src/pages/api/admin/content/[tag]/index.ts`: ```typescript import type { APIRoute } from 'astro'; import { getDb } from '../../../../../lib/db'; import { seedByTag } from '../../../../../data/content-seed'; export const prerender = false; const json = (status: number, body: object) => new Response(JSON.stringify(body), { status, headers: { 'Content-Type': 'application/json' } }); export const GET: APIRoute = ({ params }) => { const tag = params.tag ?? ''; const row = getDb().prepare('SELECT tag, type, value, guid FROM content_blocks WHERE tag = ?').get(tag) as { tag: string; type: string; value: string; guid: string | null } | undefined; if (!row) return json(404, { error: 'Tag inesistente.' }); return json(200, { tag: row.tag, guid: row.guid ?? '', type: row.type, value: row.value, styleOptions: seedByTag.get(row.tag)?.styleOptions ?? [], }); }; ``` Nota: convive con `PUT` di `src/pages/api/admin/content/[tag].ts` e con `POST/DELETE` di `[tag]/image.ts` — Astro risolve `/api/admin/content/` sull'endpoint `[tag].ts` (GET qui aggiunto come `[tag]/index.ts` risponde allo stesso path). **Verifica in Step 6** che il GET risponda; se il routing preferisce `[tag].ts`, sposta l'handler `GET` dentro `src/pages/api/admin/content/[tag].ts` accanto al `PUT` (stessa logica) ed elimina il file index. Decidi in base all'esito del test. - [ ] **Step 6: Esegui e verifica il successo** Run: `npm test -- tests/content.test.ts tests/content-api.test.ts && npm run build` Expected: PASS + build OK. Se il test del GET fallisce per routing, applica la nota dello Step 5 (sposta `GET` in `[tag].ts`) e rilancia. - [ ] **Step 7: Commit** ```bash git add src/lib/content.ts src/components/content/T.astro src/components/content/TImg.astro src/pages/api/admin/content tests/content.test.ts tests/content-api.test.ts git commit -m "feat(content): guid in resolve/componenti + GET /api/admin/content/[tag]" ``` ---