b913a2bf90
Refs gitea:Adriano/AI-OS#310 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
128 lines
4.4 KiB
Markdown
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"
|
|
```
|
|
|
|
---
|
|
|