Files
InsanityLab-web/.superpowers/sdd/task-6-brief.md
T
Adriano b913a2bf90 registri dei piani in git (AI-OS#309)
Refs gitea:Adriano/AI-OS#310

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 10:36:26 +02:00

6.5 KiB

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:

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:

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:

interface Entry { value: string; type: ContentType; styles: Record<string, string>; guid: string }

Nel load(), cambia la query e la costruzione:

    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<string, string> = {};
      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:

    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:

{c.type === 'html'
  ? <Tag data-tag={tag} data-guid={c.guid || undefined} class={classes} set:html={c.value} />
  : <Tag data-tag={tag} data-guid={c.guid || undefined} class={classes}>{c.value}</Tag>}

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:

---
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
  ? <img src={override} alt={alt} class={cls} data-tag={tag} data-guid={guid} loading={rest.loading} />
  : <Image src={src} alt={alt} class={cls} data-tag={tag} data-guid={guid} {...rest} />}
  • Step 5: Crea l'endpoint GET singolo

Crea src/pages/api/admin/content/[tag]/index.ts:

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/<tag> 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
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]"