feat: add basic blog features
This commit is contained in:
15
db/index.js
Normal file
15
db/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Pool } from 'pg';
|
||||
|
||||
const db = new Pool({
|
||||
host: process.env.DB_HOST,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
port: process.env.PORT,
|
||||
database: process.env.DB_DATABASE,
|
||||
max: 20,
|
||||
idleTimeoutMillis: 30_000,
|
||||
connectionTimeoutMillis: 2_000,
|
||||
maxLifetimeSeconds: 60
|
||||
});
|
||||
|
||||
export default db;
|
||||
68
db/migrate.js
Normal file
68
db/migrate.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import fs from "node:fs";
|
||||
import { readdir } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import dbClient from "./index.js";
|
||||
|
||||
const __dirname = import.meta.dirname;
|
||||
|
||||
const createMigrationTable = () => {
|
||||
return dbClient.query(`
|
||||
CREATE TABLE IF NOT EXISTS migrations (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
name VARCHAR(255),
|
||||
executed_at TIMESTAMP
|
||||
)
|
||||
`);
|
||||
}
|
||||
|
||||
const getMigrationsFiles = () => {
|
||||
try {
|
||||
return readdir(path.resolve(__dirname, "./migrations"));
|
||||
} catch(err) {
|
||||
return new Error("[Migration] getMigrations err: ", err);
|
||||
}
|
||||
};
|
||||
|
||||
const runMigrations = async () => {
|
||||
try {
|
||||
// Create migration table if needed
|
||||
await createMigrationTable();
|
||||
|
||||
const migrationFiles = await getMigrationsFiles();
|
||||
const migrationsDb = await dbClient.query("SELECT name FROM migrations WHERE executed_at IS NOT NULL");
|
||||
const migrationsToRun = migrationFiles.filter(m => !migrationsDb.rows.map(m => m.name).includes(m));
|
||||
|
||||
console.log("[Migration] migrations to run: ", migrationsToRun);
|
||||
|
||||
for (const migration of migrationsToRun) {
|
||||
await dbClient.query("BEGIN");
|
||||
const sqlQuery = fs.readFileSync(path.resolve(__dirname, `./migrations/${migration}`), "utf-8");
|
||||
try {
|
||||
await dbClient.query(sqlQuery);
|
||||
|
||||
await dbClient.query({
|
||||
text: "INSERT INTO migrations(id, name, executed_at) VALUES($1, $2, $3)",
|
||||
values: [migration.slice(0, -4), migration, new Date().toISOString().slice(0, 19).replace('T', ' ')]
|
||||
});
|
||||
|
||||
await dbClient.query("COMMIT");
|
||||
} catch(err) {
|
||||
console.log(err);
|
||||
await dbClient.query("ROLLBACK");
|
||||
}
|
||||
}
|
||||
|
||||
console.log("[Migration]: SUCCESS");
|
||||
} catch (err) {
|
||||
console.error("Error", err);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Run this from CLI
|
||||
if (import.meta.main) {
|
||||
console.log("Run migrations");
|
||||
runMigrations()
|
||||
.then(() => process.exit(0))
|
||||
.catch(() => process.exit(1));
|
||||
}
|
||||
9
db/migrations/2026-02-22_18:00_create_blog_tables.sql
Normal file
9
db/migrations/2026-02-22_18:00_create_blog_tables.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
CREATE XTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
CREATE TABLE posts (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
title VARCHAR(255) NULL UNIQUE,
|
||||
content TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
29
db/migrations/2026-03-04_15:23_create_user_tables.sql
Normal file
29
db/migrations/2026-03-04_15:23_create_user_tables.sql
Normal file
@@ -0,0 +1,29 @@
|
||||
CREATE TABLE users (
|
||||
id TEXT PRIMARY KEY NOT NULL UNIQUE,
|
||||
username VARCHAR(255)
|
||||
);
|
||||
|
||||
CREATE TABLE passkeys (
|
||||
id TEXT PRIMARY KEY NOT NULL UNIQUE,
|
||||
public_key BYTEA,
|
||||
webauthn_user__id TEXT UNIQUE,
|
||||
counter BIGINT,
|
||||
device_type VARCHAR(32),
|
||||
transports VARCHAR(255)
|
||||
);
|
||||
|
||||
-- User/passkey junction table
|
||||
CREATE TABLE user_passkeys (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id TEXT NOT NULL,
|
||||
passkey_id TEXT NOT NULL,
|
||||
|
||||
-- Foreign key constraints
|
||||
CONSTRAINT fk_user_passkeys_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_user_passkeys_passkeys FOREIGN KEY (passkey_id) REFERENCES passkeys(id) ON DELETE CASCADE,
|
||||
|
||||
-- Prevent duplicates
|
||||
CONSTRAINT unique_user_passkeys UNIQUE (user_id, passkey_id)
|
||||
);
|
||||
|
||||
CREATE INDEX index_passkeys ON passkeys (id, webauthn_user__id);
|
||||
Reference in New Issue
Block a user