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>
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
# Review package: db92fc9..6d1e260
|
||||
|
||||
## Commits
|
||||
6d1e260 feat(posts): author_id nel repository + listByAuthor + helper utenti
|
||||
|
||||
## Files changed
|
||||
src/lib/auth.ts | 10 ++++++++++
|
||||
src/lib/posts.ts | 14 +++++++++-----
|
||||
tests/posts.test.ts | 27 ++++++++++++++++++++++++++-
|
||||
3 files changed, 45 insertions(+), 6 deletions(-)
|
||||
|
||||
## Diff
|
||||
diff --git a/src/lib/auth.ts b/src/lib/auth.ts
|
||||
index cd10d1e..a5146c3 100644
|
||||
--- a/src/lib/auth.ts
|
||||
+++ b/src/lib/auth.ts
|
||||
@@ -61,14 +61,24 @@ const RULES: [RegExp, Role[]][] = [
|
||||
];
|
||||
|
||||
export function canAccessAdminPath(role: string, pathname: string): boolean {
|
||||
if (role === 'admin') return true;
|
||||
for (const [re, roles] of RULES) {
|
||||
if (re.test(pathname)) return roles.includes(role as Role);
|
||||
}
|
||||
return true; // blog, upload, logout, showtags: tutti i loggati
|
||||
}
|
||||
|
||||
+export function getUsernameById(db: Database.Database, id: number): string | null {
|
||||
+ const row = db.prepare('SELECT username FROM users WHERE id = ?').get(id) as { username: string } | undefined;
|
||||
+ return row?.username ?? null;
|
||||
+}
|
||||
+
|
||||
+export function listUsers(db: Database.Database): { id: number; username: string; role: string }[] {
|
||||
+ return db.prepare('SELECT id, username, role FROM users ORDER BY username').all() as
|
||||
+ { id: number; username: string; role: string }[];
|
||||
+}
|
||||
+
|
||||
export function landingFor(role: string): string {
|
||||
if (role === 'superuser') return '/admin/content';
|
||||
return '/admin';
|
||||
}
|
||||
diff --git a/src/lib/posts.ts b/src/lib/posts.ts
|
||||
index dad9159..1c8e44a 100644
|
||||
--- a/src/lib/posts.ts
|
||||
+++ b/src/lib/posts.ts
|
||||
@@ -1,29 +1,29 @@
|
||||
import type Database from 'better-sqlite3';
|
||||
|
||||
export interface Post {
|
||||
id: number; title: string; slug: string; category: string; excerpt: string;
|
||||
- body_html: string; cover: string | null; draft: number;
|
||||
+ body_html: string; cover: string | null; draft: number; author_id: number | null;
|
||||
published_at: string | null; created_at: string; updated_at: string;
|
||||
}
|
||||
|
||||
export interface PostInput {
|
||||
title: string; slug: string; category: string; excerpt: string;
|
||||
- body_html: string; cover?: string | null; draft: boolean;
|
||||
+ body_html: string; cover?: string | null; draft: boolean; author_id?: number | null;
|
||||
}
|
||||
|
||||
export function createPost(db: Database.Database, input: PostInput): number {
|
||||
const res = db.prepare(
|
||||
- `INSERT INTO posts (title, slug, category, excerpt, body_html, cover, draft, published_at)
|
||||
- VALUES (@title, @slug, @category, @excerpt, @body_html, @cover, @draft,
|
||||
+ `INSERT INTO posts (title, slug, category, excerpt, body_html, cover, draft, author_id, published_at)
|
||||
+ VALUES (@title, @slug, @category, @excerpt, @body_html, @cover, @draft, @author_id,
|
||||
CASE WHEN @draft = 0 THEN datetime('now') ELSE NULL END)`
|
||||
- ).run({ ...input, cover: input.cover ?? null, draft: input.draft ? 1 : 0 });
|
||||
+ ).run({ ...input, cover: input.cover ?? null, author_id: input.author_id ?? null, draft: input.draft ? 1 : 0 });
|
||||
return Number(res.lastInsertRowid);
|
||||
}
|
||||
|
||||
export function updatePost(db: Database.Database, id: number, input: PostInput): void {
|
||||
db.prepare(
|
||||
`UPDATE posts SET title = @title, slug = @slug, category = @category,
|
||||
excerpt = @excerpt, body_html = @body_html, cover = @cover, draft = @draft,
|
||||
published_at = CASE WHEN @draft = 0 AND published_at IS NULL THEN datetime('now') ELSE published_at END,
|
||||
updated_at = datetime('now')
|
||||
WHERE id = @id`
|
||||
@@ -65,10 +65,14 @@ export function listRecent(db: Database.Database, limit = 4): Post[] {
|
||||
'SELECT * FROM posts WHERE draft = 0 ORDER BY published_at DESC LIMIT ?'
|
||||
).all(limit) as Post[];
|
||||
}
|
||||
|
||||
export function listArchiveMonths(db: Database.Database): { month: string; count: number }[] {
|
||||
return db.prepare(
|
||||
`SELECT strftime('%Y-%m', published_at) AS month, COUNT(*) AS count
|
||||
FROM posts WHERE draft = 0 GROUP BY month ORDER BY month DESC`
|
||||
).all() as { month: string; count: number }[];
|
||||
}
|
||||
+
|
||||
+export function listByAuthor(db: Database.Database, authorId: number): Post[] {
|
||||
+ return db.prepare('SELECT * FROM posts WHERE author_id = ? ORDER BY updated_at DESC').all(authorId) as Post[];
|
||||
+}
|
||||
diff --git a/tests/posts.test.ts b/tests/posts.test.ts
|
||||
index ad373fa..58734a7 100644
|
||||
--- a/tests/posts.test.ts
|
||||
+++ b/tests/posts.test.ts
|
||||
@@ -1,17 +1,18 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import type Database from 'better-sqlite3';
|
||||
import { createDb } from '../src/lib/db';
|
||||
import {
|
||||
createPost, updatePost, deletePost, getPostById, getPublishedBySlug,
|
||||
- listPublished, listAllPosts, listRecent, listArchiveMonths, type PostInput,
|
||||
+ listPublished, listAllPosts, listRecent, listArchiveMonths, listByAuthor, type PostInput,
|
||||
} from '../src/lib/posts';
|
||||
+import { createUser, getUsernameById, listUsers } from '../src/lib/auth';
|
||||
|
||||
let db: Database.Database;
|
||||
beforeEach(() => { db = createDb(':memory:'); });
|
||||
|
||||
const base: PostInput = {
|
||||
title: 'Titolo', slug: 'titolo', category: 'Contenuti educativi',
|
||||
excerpt: 'estratto', body_html: '<p>corpo</p>', cover: null, draft: false,
|
||||
};
|
||||
|
||||
describe('posts repository', () => {
|
||||
@@ -65,10 +66,34 @@ describe('posts repository', () => {
|
||||
|
||||
it('listRecent e archivio mesi', () => {
|
||||
createPost(db, base);
|
||||
expect(listRecent(db, 4)).toHaveLength(1);
|
||||
const months = listArchiveMonths(db);
|
||||
expect(months).toHaveLength(1);
|
||||
expect(months[0].count).toBe(1);
|
||||
expect(months[0].month).toMatch(/^\d{4}-\d{2}$/);
|
||||
});
|
||||
});
|
||||
+
|
||||
+describe('autore articoli', () => {
|
||||
+ it('createPost salva author_id e listByAuthor filtra', () => {
|
||||
+ const aliceId = createUser(db, 'alice', 'segreta123', 'user');
|
||||
+ const bobId = createUser(db, 'bob', 'segreta123', 'user');
|
||||
+ createPost(db, { ...base, slug: 'a1', author_id: aliceId });
|
||||
+ createPost(db, { ...base, slug: 'a2', author_id: aliceId });
|
||||
+ createPost(db, { ...base, slug: 'b1', author_id: bobId });
|
||||
+ expect(listByAuthor(db, aliceId).length).toBe(2);
|
||||
+ expect(listByAuthor(db, bobId).length).toBe(1);
|
||||
+ });
|
||||
+
|
||||
+ it('post senza author_id ha author_id null', () => {
|
||||
+ const id = createPost(db, { ...base, slug: 'orfano' });
|
||||
+ expect(getPostById(db, id)!.author_id).toBeNull();
|
||||
+ });
|
||||
+
|
||||
+ it('getUsernameById e listUsers', () => {
|
||||
+ const id = createUser(db, 'carla', 'segreta123', 'superuser');
|
||||
+ expect(getUsernameById(db, id)).toBe('carla');
|
||||
+ expect(getUsernameById(db, 99999)).toBeNull();
|
||||
+ expect(listUsers(db).some((u) => u.username === 'carla' && u.role === 'superuser')).toBe(true);
|
||||
+ });
|
||||
+});
|
||||
Reference in New Issue
Block a user