import fs from 'fs'; export async function up(knex) { const nanoidFn = await fs.promises.readFile('./migrations/nanoid.sql', 'utf8'); // from https://github.com/viascom/nanoid-postgres await knex.raw(nanoidFn); await knex.raw(` CREATE FUNCTION shack_id(length smallint DEFAULT 8) RETURNS TEXT AS $$ SELECT nanoid(length, '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'); $$ LANGUAGE SQL STABLE; `); await knex.schema.createTable('users', (table) => { table.increments('id'); table.text('username', 32) .notNullable() .unique(); table.text('email', 32) .notNullable() .unique(); table.text('password') .notNullable(); table.text('name', 32); table.text('bio', 1000); table.specificType('ip', 'inet'); table.datetime('created_at') .notNullable() .defaultTo(knex.fn.now()); }); await knex.schema.createTable('shelves', (table) => { table.increments('id'); table.text('slug') .unique() .index() .notNullable(); table.integer('founder_id') .notNullable() .references('id') .inTable('users'); table.datetime('created_at') .notNullable() .defaultTo(knex.fn.now()); }); await knex.schema.createTable('shelves_settings', (table) => { table.increments('id'); table.integer('shelf_id') .primary() .references('id') .inTable('shelves'); table.text('name'); table.text('title'); table.text('description'); table.enum('view_access', ['public', 'registered', 'private']) .notNullable() .defaultTo('public'); table.enum('post_access', ['registered', 'private']) .notNullable() .defaultTo('public'); table.boolean('is_nsfw'); }); await knex.schema.createTable('posts', (table) => { table.text('id', 8) .primary() .defaultTo(knex.raw('shack_id()')); table.text('title') .notNullable(); table.text('body'); table.text('link'); table.integer('shelf_id') .notNullable() .references('id') .inTable('shelves'); table.integer('user_id') .notNullable() .references('id') .inTable('users'); table.datetime('created_at') .notNullable() .defaultTo(knex.fn.now()); }); await knex.raw('ALTER TABLE posts ADD CONSTRAINT post_content CHECK (body IS NOT NULL OR link IS NOT NULL)'); await knex.schema.createTable('posts_votes', (table) => { table.increments('id'); table.tinyint('value') .notNullable() .defaultTo(1); table.integer('user_id') .notNullable() .references('id') .inTable('users'); table.string('post_id') .notNullable() .references('id') .inTable('posts'); table.datetime('created_at') .notNullable() .defaultTo(knex.fn.now()); table.unique(['user_id', 'post_id']); }); await knex.schema.createTable('comments', (table) => { table.text('id', 8) .primary() .defaultTo(knex.raw('shack_id()')); table.text('post_id', 8) .notNullable() .references('id') .inTable('posts'); table.text('parent_id') .references('id') .inTable('comments'); table.integer('user_id') .notNullable() .references('id') .inTable('users'); table.text('body'); table.boolean('deleted') .notNullable() .defaultTo(false); table.datetime('created_at') .notNullable() .defaultTo(knex.fn.now()); }); await knex.schema.createTable('comments_votes', (table) => { table.increments('id'); table.tinyint('value') .notNullable() .defaultTo(1); table.integer('user_id') .notNullable() .references('id') .inTable('users'); table.string('comment_id') .notNullable() .references('id') .inTable('comments'); table.datetime('created_at') .notNullable() .defaultTo(knex.fn.now()); }); } export async function down(knex) { await knex.schema.dropTableIfExists('comments_votes'); await knex.schema.dropTableIfExists('comments'); await knex.schema.dropTableIfExists('posts_votes'); await knex.schema.dropTableIfExists('posts'); await knex.schema.dropTableIfExists('shelves_settings'); await knex.schema.dropTableIfExists('shelves'); await knex.schema.dropTableIfExists('users'); await knex.raw(` DROP FUNCTION IF EXISTS shack_id; `); }