feat(content): guid in resolve/componenti + GET /api/admin/content/[tag]
This commit is contained in:
@@ -7,5 +7,5 @@ const c = resolveContent(tag);
|
|||||||
const classes = [cls, c.classes].filter(Boolean).join(' ') || undefined;
|
const classes = [cls, c.classes].filter(Boolean).join(' ') || undefined;
|
||||||
---
|
---
|
||||||
{c.type === 'html'
|
{c.type === 'html'
|
||||||
? <Tag data-tag={tag} class={classes} set:html={c.value} />
|
? <Tag data-tag={tag} data-guid={c.guid || undefined} class={classes} set:html={c.value} />
|
||||||
: <Tag data-tag={tag} class={classes}>{c.value}</Tag>}
|
: <Tag data-tag={tag} data-guid={c.guid || undefined} class={classes}>{c.value}</Tag>}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
---
|
---
|
||||||
import { Image } from 'astro:assets';
|
import { Image } from 'astro:assets';
|
||||||
import type { ImageMetadata } from 'astro';
|
import type { ImageMetadata } from 'astro';
|
||||||
import { getImageOverride } from '../../lib/content';
|
import { resolveContent } from '../../lib/content';
|
||||||
interface Props {
|
interface Props {
|
||||||
tag: string; src: ImageMetadata; alt: string;
|
tag: string; src: ImageMetadata; alt: string;
|
||||||
class?: string; loading?: 'lazy' | 'eager'; widths?: number[]; sizes?: string; height?: number;
|
class?: string; loading?: 'lazy' | 'eager'; widths?: number[]; sizes?: string; height?: number;
|
||||||
}
|
}
|
||||||
const { tag, src, alt, class: cls, ...rest } = Astro.props;
|
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
|
{override
|
||||||
? <img src={override} alt={alt} class={cls} data-tag={tag} loading={rest.loading} />
|
? <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} {...rest} />}
|
: <Image src={src} alt={alt} class={cls} data-tag={tag} data-guid={guid} {...rest} />}
|
||||||
|
|||||||
+8
-8
@@ -4,7 +4,7 @@ import { getDb } from './db';
|
|||||||
import { contentSeed, seedByTag, type ContentType } from '../data/content-seed';
|
import { contentSeed, seedByTag, type ContentType } from '../data/content-seed';
|
||||||
import { stylesToClasses } from './style-presets';
|
import { stylesToClasses } from './style-presets';
|
||||||
|
|
||||||
interface Entry { value: string; type: ContentType; styles: Record<string, string> }
|
interface Entry { value: string; type: ContentType; styles: Record<string, string>; guid: string }
|
||||||
|
|
||||||
export function syncSeed(db: Database.Database): void {
|
export function syncSeed(db: Database.Database): void {
|
||||||
const ins = db.prepare('INSERT OR IGNORE INTO content_blocks (tag, type, value, guid) VALUES (?, ?, ?, ?)');
|
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<string, Entry> => {
|
const load = (): Map<string, Entry> => {
|
||||||
if (cache) return cache;
|
if (cache) return cache;
|
||||||
const rows = dbGetter().prepare('SELECT tag, type, value, styles FROM content_blocks').all() as
|
const rows = dbGetter().prepare('SELECT tag, type, value, styles, guid FROM content_blocks').all() as
|
||||||
{ tag: string; type: ContentType; value: string; styles: string }[];
|
{ tag: string; type: ContentType; value: string; styles: string; guid: string | null }[];
|
||||||
cache = new Map(rows.map((r) => {
|
cache = new Map(rows.map((r) => {
|
||||||
let styles: Record<string, string> = {};
|
let styles: Record<string, string> = {};
|
||||||
try { styles = JSON.parse(r.styles); } catch { /* styles corrotto: si ignora */ }
|
try { styles = JSON.parse(r.styles); } catch { /* styles corrotto: si ignora */ }
|
||||||
if (typeof styles !== 'object' || styles === null) styles = {};
|
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 cache;
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
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);
|
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);
|
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}`);
|
console.warn(`[content] tag sconosciuto: ${tag}`);
|
||||||
return { value: '', type: 'text', classes: '' };
|
return { value: '', type: 'text', classes: '', guid: '' };
|
||||||
},
|
},
|
||||||
invalidate(): void { cache = null; },
|
invalidate(): void { cache = null; },
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,11 +2,26 @@ import type { APIRoute } from 'astro';
|
|||||||
import { getDb } from '../../../../lib/db';
|
import { getDb } from '../../../../lib/db';
|
||||||
import { applyContentUpdate } from '../../../../lib/content-update';
|
import { applyContentUpdate } from '../../../../lib/content-update';
|
||||||
import { invalidateContentCache } from '../../../../lib/content';
|
import { invalidateContentCache } from '../../../../lib/content';
|
||||||
|
import { seedByTag } from '../../../../data/content-seed';
|
||||||
export const prerender = false;
|
export const prerender = false;
|
||||||
|
|
||||||
const json = (status: number, body: object) =>
|
const json = (status: number, body: object) =>
|
||||||
new Response(JSON.stringify(body), { status, headers: { 'Content-Type': 'application/json' } });
|
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 }) => {
|
export const PUT: APIRoute = async ({ params, request, locals }) => {
|
||||||
let data: Record<string, unknown>;
|
let data: Record<string, unknown>;
|
||||||
try { data = await request.json(); } catch { return json(400, { error: 'Dati non validi.' }); }
|
try { data = await request.json(); } catch { return json(400, { error: 'Dati non validi.' }); }
|
||||||
|
|||||||
@@ -35,3 +35,16 @@ describe('applyContentUpdate', () => {
|
|||||||
expect(applyContentUpdate(db, imgTag, { value: 'x' }, 'a')).toMatchObject({ ok: false, status: 400 });
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach } from 'vitest';
|
|||||||
import type Database from 'better-sqlite3';
|
import type Database from 'better-sqlite3';
|
||||||
import { createDb } from '../src/lib/db';
|
import { createDb } from '../src/lib/db';
|
||||||
import { contentSeed } from '../src/data/content-seed';
|
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;
|
let db: Database.Database;
|
||||||
beforeEach(() => { db = createDb(':memory:'); });
|
beforeEach(() => { db = createDb(':memory:'); });
|
||||||
@@ -58,4 +58,10 @@ describe('content store', () => {
|
|||||||
store.invalidate();
|
store.invalidate();
|
||||||
expect(store.resolve(contentSeed[0].tag).classes).toBe('');
|
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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user