d8862ba8ec
I filtri non dicevano dove ci fosse materiale: si sceglieva una categoria per scoprirla vuota. Ora ognuna porta il proprio conteggio, cestino e bozze esclusi, e il numero si aggiorna insieme ai filtri quando si cambia tematica senza ricaricare. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
114 lines
5.2 KiB
TypeScript
114 lines
5.2 KiB
TypeScript
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; author_id: number | null;
|
|
published_at: string | null; created_at: string; updated_at: string;
|
|
/** Momento in cui è finito nel cestino; null se attivo. */
|
|
deleted_at: string | null;
|
|
}
|
|
|
|
export interface PostInput {
|
|
title: string; slug: string; category: string; excerpt: string;
|
|
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, 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, 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`
|
|
).run({ ...input, id, cover: input.cover ?? null, draft: input.draft ? 1 : 0 });
|
|
}
|
|
|
|
/** Sposta nel cestino: l'articolo sparisce dal sito e dall'elenco, ma resta recuperabile. */
|
|
export function trashPost(db: Database.Database, id: number): void {
|
|
db.prepare("UPDATE posts SET deleted_at = datetime('now') WHERE id = ?").run(id);
|
|
}
|
|
|
|
export function restorePost(db: Database.Database, id: number): void {
|
|
db.prepare('UPDATE posts SET deleted_at = NULL WHERE id = ?').run(id);
|
|
}
|
|
|
|
/** Cancellazione definitiva: si usa solo dal cestino. */
|
|
export function deletePost(db: Database.Database, id: number): void {
|
|
db.prepare('DELETE FROM posts WHERE id = ?').run(id);
|
|
}
|
|
|
|
export function getPostById(db: Database.Database, id: number): Post | undefined {
|
|
return db.prepare('SELECT * FROM posts WHERE id = ?').get(id) as Post | undefined;
|
|
}
|
|
|
|
export function getPublishedBySlug(db: Database.Database, slug: string): Post | undefined {
|
|
return db.prepare('SELECT * FROM posts WHERE slug = ? AND draft = 0 AND deleted_at IS NULL').get(slug) as Post | undefined;
|
|
}
|
|
|
|
export function listPublished(
|
|
db: Database.Database,
|
|
opts: { category?: string; page?: number; perPage?: number } = {},
|
|
): { items: Post[]; total: number } {
|
|
const { category, page = 1, perPage = 9 } = opts;
|
|
const base = 'WHERE draft = 0 AND deleted_at IS NULL';
|
|
const where = category ? `${base} AND category = @category` : base;
|
|
const total = (db.prepare(`SELECT COUNT(*) AS n FROM posts ${where}`)
|
|
.get({ category }) as { n: number }).n;
|
|
const items = db.prepare(
|
|
`SELECT * FROM posts ${where} ORDER BY published_at DESC LIMIT @limit OFFSET @offset`
|
|
).all({ category, limit: perPage, offset: (page - 1) * perPage }) as Post[];
|
|
return { items, total };
|
|
}
|
|
|
|
/** Quanti articoli pubblicati ha ogni categoria, cestino escluso. */
|
|
export function countPublishedByCategory(db: Database.Database): Record<string, number> {
|
|
const righe = db.prepare(
|
|
'SELECT category, COUNT(*) AS n FROM posts WHERE draft = 0 AND deleted_at IS NULL GROUP BY category'
|
|
).all() as { category: string; n: number }[];
|
|
return Object.fromEntries(righe.map((r) => [r.category, r.n]));
|
|
}
|
|
|
|
export function listAllPosts(db: Database.Database): Post[] {
|
|
return db.prepare('SELECT * FROM posts WHERE deleted_at IS NULL ORDER BY updated_at DESC').all() as Post[];
|
|
}
|
|
|
|
/** Articoli nel cestino: tutti per chi gestisce, i propri per gli altri. */
|
|
export function listTrashed(db: Database.Database, authorId?: number): Post[] {
|
|
return authorId === undefined
|
|
? db.prepare('SELECT * FROM posts WHERE deleted_at IS NOT NULL ORDER BY deleted_at DESC').all() as Post[]
|
|
: db.prepare('SELECT * FROM posts WHERE deleted_at IS NOT NULL AND author_id = ? ORDER BY deleted_at DESC')
|
|
.all(authorId) as Post[];
|
|
}
|
|
|
|
export function listRecent(db: Database.Database, limit = 4): Post[] {
|
|
return db.prepare(
|
|
'SELECT * FROM posts WHERE draft = 0 AND deleted_at IS NULL 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 AND deleted_at IS NULL 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 = ? AND deleted_at IS NULL ORDER BY updated_at DESC')
|
|
.all(authorId) as Post[];
|
|
}
|
|
|
|
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;
|
|
}
|