feat: layer sqlite e repository posts con test
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import Database from 'better-sqlite3';
|
||||
import { mkdirSync } from 'node:fs';
|
||||
import { dirname } from 'node:path';
|
||||
|
||||
const SCHEMA = `
|
||||
CREATE TABLE IF NOT EXISTS posts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
slug TEXT NOT NULL UNIQUE,
|
||||
category TEXT NOT NULL,
|
||||
excerpt TEXT NOT NULL DEFAULT '',
|
||||
body_html TEXT NOT NULL DEFAULT '',
|
||||
cover TEXT,
|
||||
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'))
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
token TEXT PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
expires_at TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_posts_pub ON posts (draft, published_at DESC);
|
||||
`;
|
||||
|
||||
export function createDb(path?: string): Database.Database {
|
||||
const p = path ?? process.env.DB_PATH ?? 'data/insanitylab.db';
|
||||
if (p !== ':memory:') mkdirSync(dirname(p), { recursive: true });
|
||||
const db = new Database(p);
|
||||
db.pragma('journal_mode = WAL');
|
||||
db.pragma('foreign_keys = ON');
|
||||
db.exec(SCHEMA);
|
||||
return db;
|
||||
}
|
||||
|
||||
let singleton: Database.Database | null = null;
|
||||
|
||||
export function getDb(): Database.Database {
|
||||
if (!singleton) singleton = createDb();
|
||||
return singleton;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
CASE WHEN @draft = 0 THEN datetime('now') ELSE NULL END)`
|
||||
).run({ ...input, cover: input.cover ?? 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 });
|
||||
}
|
||||
|
||||
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').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 where = category ? 'WHERE draft = 0 AND category = @category' : 'WHERE draft = 0';
|
||||
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 };
|
||||
}
|
||||
|
||||
export function listAllPosts(db: Database.Database): Post[] {
|
||||
return db.prepare('SELECT * FROM posts ORDER BY updated_at DESC').all() 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 ?'
|
||||
).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 }[];
|
||||
}
|
||||
Reference in New Issue
Block a user