Files
InsanityLab-web/.superpowers/sdd/task-5-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

4.4 KiB

Task 5: API posts — ownership e assegnazione autore

Files:

  • Modify: src/pages/api/admin/posts/index.ts
  • Modify: src/pages/api/admin/posts/[id].ts
  • Test: tests/posts-ownership.test.ts (nuovo, a livello logico via funzione estratta)

Interfaces:

  • Consumes: createPost, getPostById, updatePost, deletePost (Task 4), locals.user dal middleware.

  • Produces: helper canModifyPost(role: string, userId: number, post: { author_id: number | null }): boolean in src/lib/posts.ts.

  • Step 1: Scrivi il test dell'ownership (livello logico)

Crea tests/posts-ownership.test.ts:

import { describe, it, expect } from 'vitest';
import { canModifyPost } from '../src/lib/posts';

describe('canModifyPost', () => {
  const own = { author_id: 7 };
  const other = { author_id: 8 };
  const orphan = { author_id: null };

  it('admin e superuser modificano qualsiasi post', () => {
    for (const role of ['admin', 'superuser']) {
      expect(canModifyPost(role, 7, other)).toBe(true);
      expect(canModifyPost(role, 7, orphan)).toBe(true);
    }
  });

  it('user modifica solo i propri', () => {
    expect(canModifyPost('user', 7, own)).toBe(true);
    expect(canModifyPost('user', 7, other)).toBe(false);
    expect(canModifyPost('user', 7, orphan)).toBe(false);
  });
});
  • Step 2: Esegui e verifica il fallimento

Run: npm test -- tests/posts-ownership.test.ts Expected: FAIL — canModifyPost non esiste.

  • Step 3: Aggiungi canModifyPost in posts.ts
export function canModifyPost(role: string, userId: number, post: { author_id: number | null }): boolean {
  if (role === 'admin' || role === 'superuser') return true;
  return post.author_id === userId;
}
  • Step 4: Esegui e verifica il successo

Run: npm test -- tests/posts-ownership.test.ts Expected: PASS.

  • Step 5: Applica l'autore nel POST

In src/pages/api/admin/posts/index.ts, cambia la firma per leggere locals e passa author_id:

export const POST: APIRoute = async ({ request, locals }) => {
  let data: Record<string, unknown>;
  try { data = await request.json(); } catch { return json(400, { error: 'Dati non validi.' }); }
  const parsed = parsePostBody(data);
  if ('error' in parsed) return json(400, { error: parsed.error });
  try {
    const id = createPost(getDb(), { ...parsed.value, author_id: locals.user!.id });
    return json(201, { id, slug: parsed.value.slug });
  } catch (err: any) {
    if (String(err?.message).includes('UNIQUE')) return json(400, { error: 'Slug già esistente.' });
    throw err;
  }
};
  • Step 6: Applica l'ownership su PUT e DELETE

In src/pages/api/admin/posts/[id].ts, aggiorna gli import e i due handler:

import { getPostById, updatePost, deletePost, canModifyPost } from '../../../../lib/posts';

export const PUT: APIRoute = async ({ params, request, locals }) => {
  const id = Number(params.id);
  const existing = getPostById(getDb(), id);
  if (!existing) return json(404, { error: 'Articolo non trovato.' });
  if (!canModifyPost(locals.user!.role, locals.user!.id, existing)) return json(403, { error: 'Non puoi modificare questo articolo.' });
  let data: Record<string, unknown>;
  try { data = await request.json(); } catch { return json(400, { error: 'Dati non validi.' }); }
  const parsed = parsePostBody(data);
  if ('error' in parsed) return json(400, { error: parsed.error });
  try {
    updatePost(getDb(), id, parsed.value);
    return json(200, { ok: true });
  } catch (err: any) {
    if (String(err?.message).includes('UNIQUE')) return json(400, { error: 'Slug già esistente.' });
    throw err;
  }
};

export const DELETE: APIRoute = ({ params, locals }) => {
  const id = Number(params.id);
  const existing = getPostById(getDb(), id);
  if (!existing) return json(404, { error: 'Articolo non trovato.' });
  if (!canModifyPost(locals.user!.role, locals.user!.id, existing)) return json(403, { error: 'Non puoi eliminare questo articolo.' });
  deletePost(getDb(), id);
  return json(200, { ok: true });
};
  • Step 7: Verifica build + test

Run: npm run build && npm test Expected: build OK, tutti i test verdi.

  • Step 8: Commit
git add src/pages/api/admin/posts/index.ts src/pages/api/admin/posts/[id].ts src/lib/posts.ts tests/posts-ownership.test.ts
git commit -m "feat(posts-api): assegna autore in POST, ownership su PUT/DELETE"