Articoli: cestino con ripristino, e azioni a icona come in Utenti
Eliminare un articolo non lasciava nulla di recuperabile: ora la riga finisce nel cestino (colonna deleted_at, aggiunta anche ai database già esistenti) e sparisce da sito e pannello, ma resta ripristinabile dalla nuova pagina /admin/cestino, dove sta anche la cancellazione definitiva. L'API la accetta solo su articoli già cestinati, così il contenuto non si perde per un clic distratto. Ogni lettura pubblica — elenco, recenti, archivio per mese, pagina del singolo articolo — filtra il cestino, e chi non gestisce vede nel cestino soltanto i propri articoli. Le azioni di riga diventano icone (matita e cestino) come nell'elenco utenti, con etichetta accessibile che nomina l'articolo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+9
-1
@@ -16,7 +16,9 @@ CREATE TABLE IF NOT EXISTS posts (
|
||||
draft INTEGER NOT NULL DEFAULT 1,
|
||||
published_at TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
-- Cestino: valorizzata quando l'articolo viene rimosso dal sito ma resta recuperabile.
|
||||
deleted_at TEXT
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -245,6 +247,12 @@ export function createDb(path?: string): Database.Database {
|
||||
// visibile ai soli admin): i tag che la servivano restano orfani nei DB esistenti.
|
||||
db.prepare("DELETE FROM content_blocks WHERE tag IN ('global.nav.7.label', 'promo.pagehero.title')").run();
|
||||
|
||||
// Cestino degli articoli: colonna aggiunta a posteriori sui database già esistenti.
|
||||
const colonnePost = db.prepare('PRAGMA table_info(posts)').all() as { name: string }[];
|
||||
if (!colonnePost.some((c) => c.name === 'deleted_at')) {
|
||||
db.exec('ALTER TABLE posts ADD COLUMN deleted_at TEXT');
|
||||
}
|
||||
|
||||
// Backfill: righe pre-esistenti (già presenti prima dell'introduzione del guid) restano
|
||||
// ignorate dall'INSERT OR IGNORE e hanno guid nullo → assegna un UUID a ciascuna.
|
||||
const missing = db.prepare(`SELECT tag FROM content_blocks WHERE guid IS NULL OR guid = ''`).all() as { tag: string }[];
|
||||
|
||||
+28
-6
@@ -4,6 +4,8 @@ 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 {
|
||||
@@ -30,6 +32,16 @@ export function updatePost(db: Database.Database, id: number, input: PostInput):
|
||||
).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);
|
||||
}
|
||||
@@ -39,7 +51,7 @@ export function getPostById(db: Database.Database, id: number): Post | undefined
|
||||
}
|
||||
|
||||
export function getPublishedBySlug(db: Database.Database, slug: string): Post | undefined {
|
||||
return db.prepare('SELECT * FROM posts WHERE slug = ? AND draft = 0').get(slug) as 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(
|
||||
@@ -47,7 +59,8 @@ export function listPublished(
|
||||
opts: { category?: string; page?: number; perPage?: number } = {},
|
||||
): { items: Post[]; total: number } {
|
||||
const { category, page = 1, perPage = 9 } = opts;
|
||||
const where = category ? 'WHERE draft = 0 AND category = @category' : 'WHERE draft = 0';
|
||||
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(
|
||||
@@ -57,24 +70,33 @@ export function listPublished(
|
||||
}
|
||||
|
||||
export function listAllPosts(db: Database.Database): Post[] {
|
||||
return db.prepare('SELECT * FROM posts ORDER BY updated_at DESC').all() as 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 ORDER BY published_at DESC LIMIT ?'
|
||||
'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 GROUP BY month ORDER BY month DESC`
|
||||
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 = ? ORDER BY updated_at DESC').all(authorId) as 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 {
|
||||
|
||||
Reference in New Issue
Block a user