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

128 lines
4.4 KiB
Markdown

### 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`:
```typescript
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`**
```typescript
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`:
```typescript
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:
```typescript
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**
```bash
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"
```
---