Files
core/src/modules/posts/db.js
T
hykocx a3921a0b98 feat(database): refactor CLI, add column whitelist, and SSL config
- Add `ZEN_DB_SSL_DISABLED` env variable to allow disabling SSL for database connections
- Refactor database CLI to split init logic into `initFeatures` and `initModules` for modular table initialization, with graceful fallback when modules are absent
- Extract `printHelp` and `askConfirmation` helpers for cleaner CLI structure
- Ensure `closePool` is called on both success and error paths in CLI
- Add `filterAllowedColumns` utility in `crud.js` to enforce column whitelists, preventing mass-assignment of privileged fields (e.g. `role`, `email_verified`)
- Update drop command description from "auth tables" to "all tables"
2026-04-13 16:35:23 -04:00

130 lines
4.2 KiB
JavaScript

/**
* Posts Module - Database
* Creates zen_posts and zen_posts_category tables.
*/
import { query, tableExists } from '@zen/core/database';
import { getPostsConfig } from './config.js';
import { done, info, step } from '../../shared/lib/logger.js';
async function createPostsCategoryTable() {
const tableName = 'zen_posts_category';
const exists = await tableExists(tableName);
if (exists) {
info(`Table already exists: ${tableName}`);
return { created: false, tableName };
}
await query(`
CREATE TABLE zen_posts_category (
id SERIAL PRIMARY KEY,
post_type VARCHAR(100) NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
)
`);
await query(`CREATE INDEX idx_zen_posts_category_post_type ON zen_posts_category(post_type)`);
await query(`CREATE INDEX idx_zen_posts_category_is_active ON zen_posts_category(is_active)`);
done(`Created table: ${tableName}`);
return { created: true, tableName };
}
async function createPostsTable() {
const tableName = 'zen_posts';
const exists = await tableExists(tableName);
if (exists) {
info(`Table already exists: ${tableName}`);
return { created: false, tableName };
}
await query(`
CREATE TABLE zen_posts (
id SERIAL PRIMARY KEY,
post_type VARCHAR(100) NOT NULL,
slug VARCHAR(500) NOT NULL,
data JSONB NOT NULL DEFAULT '{}',
category_id INTEGER REFERENCES zen_posts_category(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
UNIQUE(post_type, slug)
)
`);
await query(`CREATE INDEX idx_zen_posts_post_type ON zen_posts(post_type)`);
await query(`CREATE INDEX idx_zen_posts_post_type_slug ON zen_posts(post_type, slug)`);
await query(`CREATE INDEX idx_zen_posts_category_id ON zen_posts(category_id)`);
await query(`CREATE INDEX idx_zen_posts_data_gin ON zen_posts USING GIN (data)`);
done(`Created table: ${tableName}`);
return { created: true, tableName };
}
async function createPostsRelationsTable() {
const tableName = 'zen_posts_relations';
const exists = await tableExists(tableName);
if (exists) {
info(`Table already exists: ${tableName}`);
return { created: false, tableName };
}
await query(`
CREATE TABLE zen_posts_relations (
id SERIAL PRIMARY KEY,
post_id INTEGER NOT NULL REFERENCES zen_posts(id) ON DELETE CASCADE,
field_name VARCHAR(100) NOT NULL,
related_post_id INTEGER NOT NULL REFERENCES zen_posts(id) ON DELETE CASCADE,
sort_order INTEGER DEFAULT 0,
UNIQUE(post_id, field_name, related_post_id)
)
`);
await query(`CREATE INDEX idx_zen_posts_relations_post_id ON zen_posts_relations(post_id)`);
await query(`CREATE INDEX idx_zen_posts_relations_related ON zen_posts_relations(related_post_id)`);
done(`Created table: ${tableName}`);
return { created: true, tableName };
}
/**
* Create all posts-related tables.
* zen_posts_category is only created if at least one type uses the 'category' field.
* zen_posts_relations is only created if at least one type uses the 'relation' field.
* @returns {Promise<Object>}
*/
export async function createTables() {
const created = [];
const skipped = [];
const config = getPostsConfig();
const needsRelations = Object.values(config.types).some(t => t.hasRelations);
// zen_posts_category must always be created before zen_posts
// because zen_posts has a FK reference to it
step('Posts Categories');
const catResult = await createPostsCategoryTable();
if (catResult.created) created.push(catResult.tableName);
else skipped.push(catResult.tableName);
step('Posts');
const postResult = await createPostsTable();
if (postResult.created) created.push(postResult.tableName);
else skipped.push(postResult.tableName);
if (needsRelations) {
step('Posts Relations');
const relResult = await createPostsRelationsTable();
if (relResult.created) created.push(relResult.tableName);
else skipped.push(relResult.tableName);
}
return { created, skipped };
}