From 5ce10da9bd9bff819e68056ce0c94a3c03f92099 Mon Sep 17 00:00:00 2001 From: AdrianoDev Date: Sun, 5 Jul 2026 21:56:53 +0200 Subject: [PATCH] feat(content): guid in resolve/componenti + GET /api/admin/content/[tag] --- src/components/content/T.astro | 4 ++-- src/components/content/TImg.astro | 10 ++++++---- src/lib/content.ts | 16 ++++++++-------- src/pages/api/admin/content/[tag].ts | 15 +++++++++++++++ tests/content-api.test.ts | 13 +++++++++++++ tests/content.test.ts | 8 +++++++- 6 files changed, 51 insertions(+), 15 deletions(-) diff --git a/src/components/content/T.astro b/src/components/content/T.astro index 41cefd3..b11d23e 100644 --- a/src/components/content/T.astro +++ b/src/components/content/T.astro @@ -7,5 +7,5 @@ const c = resolveContent(tag); const classes = [cls, c.classes].filter(Boolean).join(' ') || undefined; --- {c.type === 'html' - ? - : {c.value}} + ? + : {c.value}} diff --git a/src/components/content/TImg.astro b/src/components/content/TImg.astro index 3fcb915..b1f6ac3 100644 --- a/src/components/content/TImg.astro +++ b/src/components/content/TImg.astro @@ -1,14 +1,16 @@ --- import { Image } from 'astro:assets'; import type { ImageMetadata } from 'astro'; -import { getImageOverride } from '../../lib/content'; +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 override = getImageOverride(tag); +const c = resolveContent(tag); +const override = c.type === 'image' && c.value ? `/uploads/${c.value}` : null; +const guid = c.guid || undefined; --- {override - ? {alt} - : {alt}} + ? {alt} + : {alt}} diff --git a/src/lib/content.ts b/src/lib/content.ts index dbdafee..db2a539 100644 --- a/src/lib/content.ts +++ b/src/lib/content.ts @@ -4,7 +4,7 @@ import { getDb } from './db'; import { contentSeed, seedByTag, type ContentType } from '../data/content-seed'; import { stylesToClasses } from './style-presets'; -interface Entry { value: string; type: ContentType; styles: Record } +interface Entry { value: string; type: ContentType; styles: Record; guid: string } export function syncSeed(db: Database.Database): void { const ins = db.prepare('INSERT OR IGNORE INTO content_blocks (tag, type, value, guid) VALUES (?, ?, ?, ?)'); @@ -17,25 +17,25 @@ export function makeContentStore(dbGetter: () => Database.Database) { const load = (): Map => { if (cache) return cache; - const rows = dbGetter().prepare('SELECT tag, type, value, styles FROM content_blocks').all() as - { tag: string; type: ContentType; value: string; styles: string }[]; + 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 }]; + return [r.tag, { value: r.value, type: r.type, styles, guid: r.guid ?? '' }]; })); return cache; }; return { - resolve(tag: string): { value: string; type: ContentType; classes: string } { + 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) }; + 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: '' }; + if (seed) return { value: seed.value, type: seed.type, classes: '', guid: '' }; console.warn(`[content] tag sconosciuto: ${tag}`); - return { value: '', type: 'text', classes: '' }; + return { value: '', type: 'text', classes: '', guid: '' }; }, invalidate(): void { cache = null; }, }; diff --git a/src/pages/api/admin/content/[tag].ts b/src/pages/api/admin/content/[tag].ts index 1fd7c0b..8ad2488 100644 --- a/src/pages/api/admin/content/[tag].ts +++ b/src/pages/api/admin/content/[tag].ts @@ -2,11 +2,26 @@ import type { APIRoute } from 'astro'; import { getDb } from '../../../../lib/db'; import { applyContentUpdate } from '../../../../lib/content-update'; import { invalidateContentCache } from '../../../../lib/content'; +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 ?? [], + }); +}; + export const PUT: APIRoute = async ({ params, request, locals }) => { let data: Record; try { data = await request.json(); } catch { return json(400, { error: 'Dati non validi.' }); } diff --git a/tests/content-api.test.ts b/tests/content-api.test.ts index 6d61853..66075dc 100644 --- a/tests/content-api.test.ts +++ b/tests/content-api.test.ts @@ -35,3 +35,16 @@ describe('applyContentUpdate', () => { expect(applyContentUpdate(db, imgTag, { value: 'x' }, 'a')).toMatchObject({ ok: false, status: 400 }); }); }); + +describe('GET /api/admin/content/[tag]', () => { + it('GET /api/admin/content/[tag] ritorna tag, guid, type, value, styleOptions', async () => { + const { GET } = await import('../src/pages/api/admin/content/[tag]'); + const res = await GET({ params: { tag: 'home.hero.cta' } } as any); + expect(res.status).toBe(200); + const body = await res.json(); + expect(body.tag).toBe('home.hero.cta'); + expect(typeof body.guid).toBe('string'); + expect(body.type).toBeDefined(); + expect(Array.isArray(body.styleOptions)).toBe(true); + }); +}); diff --git a/tests/content.test.ts b/tests/content.test.ts index a5e8e53..40c8773 100644 --- a/tests/content.test.ts +++ b/tests/content.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach } from 'vitest'; import type Database from 'better-sqlite3'; import { createDb } from '../src/lib/db'; import { contentSeed } from '../src/data/content-seed'; -import { makeContentStore, syncSeed } from '../src/lib/content'; +import { makeContentStore, syncSeed, resolveContent } from '../src/lib/content'; let db: Database.Database; beforeEach(() => { db = createDb(':memory:'); }); @@ -58,4 +58,10 @@ describe('content store', () => { store.invalidate(); expect(store.resolve(contentSeed[0].tag).classes).toBe(''); }); + + it('resolveContent restituisce il guid dal DB', () => { + const r = resolveContent('home.hero.cta'); + expect(typeof r.guid).toBe('string'); + expect(r.guid.length).toBeGreaterThan(10); + }); });