updates and fixes

* seo friendly urls
* custom client serve-static module
* database fixes
* fix recent pages
* other fixes
This commit is contained in:
Philipinho
2024-05-18 03:19:42 +01:00
parent eefe63d1cd
commit 9c7c2f1163
102 changed files with 921 additions and 536 deletions
@@ -0,0 +1,37 @@
import { Kysely, sql } from 'kysely';
import { UserRole } from '../../helpers/types/permission';
export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.createTable('workspaces')
.addColumn('id', 'uuid', (col) =>
col.primaryKey().defaultTo(sql`gen_random_uuid()`),
)
.addColumn('name', 'varchar', (col) => col)
.addColumn('description', 'varchar', (col) => col)
.addColumn('logo', 'varchar', (col) => col)
.addColumn('hostname', 'varchar', (col) => col)
.addColumn('custom_domain', 'varchar', (col) => col)
.addColumn('settings', 'jsonb', (col) => col)
.addColumn('default_role', 'varchar', (col) =>
col.defaultTo(UserRole.MEMBER).notNull(),
)
.addColumn('allowed_email_domains', sql`varchar[]`, (col) =>
col.defaultTo('{}'),
)
.addColumn('default_space_id', 'uuid', (col) => col)
.addColumn('created_at', 'timestamptz', (col) =>
col.notNull().defaultTo(sql`now()`),
)
.addColumn('updated_at', 'timestamptz', (col) =>
col.notNull().defaultTo(sql`now()`),
)
.addColumn('deleted_at', 'timestamptz', (col) => col)
.addUniqueConstraint('workspaces_hostname_unique', ['hostname'])
.addUniqueConstraint('workspaces_custom_domain_unique', ['custom_domain'])
.execute();
}
export async function down(db: Kysely<any>): Promise<void> {
await db.schema.dropTable('workspaces').execute();
}
@@ -0,0 +1,43 @@
import { Kysely, sql } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.createTable('users')
.addColumn('id', 'uuid', (col) =>
col.primaryKey().defaultTo(sql`gen_random_uuid()`),
)
.addColumn('name', 'varchar', (col) => col)
.addColumn('email', 'varchar', (col) => col.notNull())
.addColumn('email_verified_at', 'timestamptz', (col) => col)
.addColumn('password', 'varchar', (col) => col)
.addColumn('avatar_url', 'varchar', (col) => col)
.addColumn('role', 'varchar', (col) => col.notNull())
.addColumn('invited_by_id', 'uuid', (col) =>
col.references('users.id').onDelete('set null'),
)
.addColumn('workspace_id', 'uuid', (col) =>
col.references('workspaces.id').onDelete('cascade'),
)
.addColumn('locale', 'varchar', (col) => col)
.addColumn('timezone', 'varchar', (col) => col)
.addColumn('settings', 'jsonb', (col) => col)
.addColumn('last_active_at', 'timestamptz', (col) => col)
.addColumn('last_login_at', 'timestamptz', (col) => col)
.addColumn('deactivated_at', 'timestamptz', (col) => col)
.addColumn('created_at', 'timestamptz', (col) =>
col.notNull().defaultTo(sql`now()`),
)
.addColumn('updated_at', 'timestamptz', (col) =>
col.notNull().defaultTo(sql`now()`),
)
.addColumn('deleted_at', 'timestamptz', (col) => col)
.addUniqueConstraint('users_email_workspace_id_unique', [
'email',
'workspace_id',
])
.execute();
}
export async function down(db: Kysely<any>): Promise<void> {
await db.schema.dropTable('users').execute();
}
@@ -0,0 +1,56 @@
import { Kysely, sql } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.createTable('groups')
.addColumn('id', 'uuid', (col) =>
col.primaryKey().defaultTo(sql`gen_random_uuid()`),
)
.addColumn('name', 'varchar', (col) => col.notNull())
.addColumn('description', 'text', (col) => col)
.addColumn('is_default', 'boolean', (col) => col.notNull())
.addColumn('workspace_id', 'uuid', (col) =>
col.references('workspaces.id').onDelete('cascade').notNull(),
)
.addColumn('creator_id', 'uuid', (col) => col.references('users.id'))
.addColumn('created_at', 'timestamptz', (col) =>
col.notNull().defaultTo(sql`now()`),
)
.addColumn('updated_at', 'timestamptz', (col) =>
col.notNull().defaultTo(sql`now()`),
)
.addUniqueConstraint('groups_name_workspace_id_unique', [
'name',
'workspace_id',
])
.execute();
await db.schema
.createTable('group_users')
.addColumn('id', 'uuid', (col) =>
col.primaryKey().defaultTo(sql`gen_random_uuid()`),
)
.addColumn('user_id', 'uuid', (col) =>
col.references('users.id').onDelete('cascade').notNull(),
)
.addColumn('group_id', 'uuid', (col) =>
col.references('groups.id').onDelete('cascade').notNull(),
)
.addColumn('created_at', 'timestamptz', (col) =>
col.notNull().defaultTo(sql`now()`),
)
.addColumn('updated_at', 'timestamptz', (col) =>
col.notNull().defaultTo(sql`now()`),
)
.addUniqueConstraint('group_users_group_id_user_id_unique', [
'group_id',
'user_id',
])
.execute();
}
export async function down(db: Kysely<any>): Promise<void> {
await db.schema.dropTable('group_users').execute();
await db.schema.dropTable('groups').execute();
}
@@ -0,0 +1,77 @@
import { Kysely, sql } from 'kysely';
import { SpaceRole, SpaceVisibility } from '../../helpers/types/permission';
export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.createTable('spaces')
.addColumn('id', 'uuid', (col) =>
col.primaryKey().defaultTo(sql`gen_random_uuid()`),
)
.addColumn('name', 'varchar', (col) => col)
.addColumn('description', 'text', (col) => col)
.addColumn('slug', 'varchar', (col) => col)
.addColumn('icon', 'varchar', (col) => col)
.addColumn('visibility', 'varchar', (col) =>
col.defaultTo(SpaceVisibility.OPEN).notNull(),
)
.addColumn('default_role', 'varchar', (col) =>
col.defaultTo(SpaceRole.WRITER).notNull(),
)
.addColumn('creator_id', 'uuid', (col) => col.references('users.id'))
.addColumn('workspace_id', 'uuid', (col) =>
col.references('workspaces.id').onDelete('cascade').notNull(),
)
.addColumn('created_at', 'timestamptz', (col) =>
col.notNull().defaultTo(sql`now()`),
)
.addColumn('updated_at', 'timestamptz', (col) =>
col.notNull().defaultTo(sql`now()`),
)
.addColumn('deleted_at', 'timestamptz', (col) => col)
.addUniqueConstraint('spaces_slug_workspace_id_unique', [
'slug',
'workspace_id',
])
.execute();
await db.schema
.createTable('space_members')
.addColumn('id', 'uuid', (col) =>
col.primaryKey().defaultTo(sql`gen_random_uuid()`),
)
.addColumn('user_id', 'uuid', (col) =>
col.references('users.id').onDelete('cascade'),
)
.addColumn('group_id', 'uuid', (col) =>
col.references('groups.id').onDelete('cascade'),
)
.addColumn('space_id', 'uuid', (col) =>
col.references('spaces.id').onDelete('cascade').notNull(),
)
.addColumn('role', 'varchar', (col) => col.notNull())
.addColumn('added_by_id', 'uuid', (col) => col.references('users.id'))
.addColumn('created_at', 'timestamptz', (col) =>
col.notNull().defaultTo(sql`now()`),
)
.addColumn('updated_at', 'timestamptz', (col) =>
col.notNull().defaultTo(sql`now()`),
)
.addUniqueConstraint('space_members_space_id_user_id_unique', [
'space_id',
'user_id',
])
.addUniqueConstraint('space_members_space_id_group_id_unique', [
'space_id',
'group_id',
])
.addCheckConstraint(
'allow_either_user_id_or_group_id_check',
sql`(("user_id" IS NOT NULL AND "group_id" IS NULL) OR ("user_id" IS NULL AND "group_id" IS NOT NULL))`,
)
.execute();
}
export async function down(db: Kysely<any>): Promise<void> {
await db.schema.dropTable('space_members').execute();
await db.schema.dropTable('spaces').execute();
}
@@ -0,0 +1,21 @@
import { Kysely } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.alterTable('workspaces')
.addForeignKeyConstraint(
'workspaces_default_space_id_fkey',
['default_space_id'],
'spaces',
['id'],
)
.onDelete('set null')
.execute();
}
export async function down(db: Kysely<any>): Promise<void> {
await db.schema
.alterTable('workspaces')
.dropConstraint('workspaces_default_space_id_fkey')
.execute();
}
@@ -0,0 +1,32 @@
import { Kysely, sql } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.createTable('workspace_invitations')
.addColumn('id', 'uuid', (col) =>
col.primaryKey().defaultTo(sql`gen_random_uuid()`),
)
.addColumn('email', 'varchar', (col) => col)
.addColumn('role', 'varchar', (col) => col.notNull())
.addColumn('token', 'varchar', (col) => col.notNull())
.addColumn('group_ids', sql`uuid[]`, (col) => col)
.addColumn('invited_by_id', 'uuid', (col) => col.references('users.id'))
.addColumn('workspace_id', 'uuid', (col) =>
col.references('workspaces.id').onDelete('cascade').notNull(),
)
.addColumn('created_at', 'timestamptz', (col) =>
col.notNull().defaultTo(sql`now()`),
)
.addColumn('updated_at', 'timestamptz', (col) =>
col.notNull().defaultTo(sql`now()`),
)
.addUniqueConstraint('invitations_email_workspace_id_unique', [
'email',
'workspace_id',
])
.execute();
}
export async function down(db: Kysely<any>): Promise<void> {
await db.schema.dropTable('workspace_invitations').execute();
}
@@ -0,0 +1,59 @@
import { Kysely, sql } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.createTable('pages')
.addColumn('id', 'uuid', (col) =>
col.primaryKey().defaultTo(sql`gen_random_uuid()`),
)
.addColumn('slug_id', 'varchar', (col) => col.notNull())
.addColumn('title', 'varchar', (col) => col)
.addColumn('icon', 'varchar', (col) => col)
.addColumn('cover_photo', 'varchar', (col) => col)
.addColumn('position', 'varchar', (col) => col)
.addColumn('content', 'jsonb', (col) => col)
.addColumn('ydoc', 'bytea', (col) => col)
.addColumn('text_content', 'text', (col) => col)
.addColumn('tsv', sql`tsvector`, (col) => col)
.addColumn('parent_page_id', 'uuid', (col) =>
col.references('pages.id').onDelete('cascade'),
)
.addColumn('creator_id', 'uuid', (col) => col.references('users.id'))
.addColumn('last_updated_by_id', 'uuid', (col) =>
col.references('users.id'),
)
.addColumn('deleted_by_id', 'uuid', (col) => col.references('users.id'))
.addColumn('space_id', 'uuid', (col) =>
col.references('spaces.id').onDelete('cascade').notNull(),
)
.addColumn('workspace_id', 'uuid', (col) =>
col.references('workspaces.id').onDelete('cascade').notNull(),
)
.addColumn('is_locked', 'boolean', (col) => col.defaultTo(false).notNull())
.addColumn('created_at', 'timestamptz', (col) =>
col.notNull().defaultTo(sql`now()`),
)
.addColumn('updated_at', 'timestamptz', (col) =>
col.notNull().defaultTo(sql`now()`),
)
.addColumn('deleted_at', 'timestamptz', (col) => col)
.addUniqueConstraint('pages_slug_id_unique', ['slug_id'])
.execute();
await db.schema
.createIndex('pages_tsv_idx')
.on('pages')
.using('GIN')
.column('tsv')
.execute();
await db.schema
.createIndex('pages_slug_id_idx')
.on('pages')
.column('slug_id')
.execute();
}
export async function down(db: Kysely<any>): Promise<void> {
await db.schema.dropTable('pages').execute();
}
@@ -0,0 +1,39 @@
import { Kysely, sql } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.createTable('page_history')
.addColumn('id', 'uuid', (col) =>
col.primaryKey().defaultTo(sql`gen_random_uuid()`),
)
.addColumn('page_id', 'uuid', (col) =>
col.references('pages.id').onDelete('cascade').notNull(),
)
.addColumn('slug_id', 'varchar', (col) => col)
.addColumn('title', 'varchar', (col) => col)
.addColumn('content', 'jsonb', (col) => col)
.addColumn('slug', 'varchar', (col) => col)
.addColumn('icon', 'varchar', (col) => col)
.addColumn('cover_photo', 'varchar', (col) => col)
.addColumn('version', 'int4', (col) => col)
.addColumn('last_updated_by_id', 'uuid', (col) =>
col.references('users.id'),
)
.addColumn('space_id', 'uuid', (col) =>
col.references('spaces.id').onDelete('cascade').notNull(),
)
.addColumn('workspace_id', 'uuid', (col) =>
col.references('workspaces.id').onDelete('cascade').notNull(),
)
.addColumn('created_at', 'timestamptz', (col) =>
col.notNull().defaultTo(sql`now()`),
)
.addColumn('updated_at', 'timestamptz', (col) =>
col.notNull().defaultTo(sql`now()`),
)
.execute();
}
export async function down(db: Kysely<any>): Promise<void> {
await db.schema.dropTable('page_history').execute();
}
@@ -0,0 +1,32 @@
import { Kysely, sql } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.createTable('comments')
.addColumn('id', 'uuid', (col) =>
col.primaryKey().defaultTo(sql`gen_random_uuid()`),
)
.addColumn('content', 'jsonb', (col) => col)
.addColumn('selection', 'varchar', (col) => col)
.addColumn('type', 'varchar', (col) => col)
.addColumn('creator_id', 'uuid', (col) => col.references('users.id'))
.addColumn('page_id', 'uuid', (col) =>
col.references('pages.id').onDelete('cascade').notNull(),
)
.addColumn('parent_comment_id', 'uuid', (col) =>
col.references('comments.id').onDelete('cascade'),
)
.addColumn('workspace_id', 'uuid', (col) =>
col.references('workspaces.id').notNull(),
)
.addColumn('created_at', 'timestamptz', (col) =>
col.notNull().defaultTo(sql`now()`),
)
.addColumn('edited_at', 'timestamptz', (col) => col)
.addColumn('deleted_at', 'timestamptz', (col) => col)
.execute();
}
export async function down(db: Kysely<any>): Promise<void> {
await db.schema.dropTable('comments').execute();
}
@@ -0,0 +1,33 @@
import { Kysely, sql } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.createTable('attachments')
.addColumn('id', 'uuid', (col) =>
col.primaryKey().defaultTo(sql`gen_random_uuid()`),
)
.addColumn('file_name', 'varchar', (col) => col.notNull())
.addColumn('file_path', 'varchar', (col) => col.notNull())
.addColumn('file_size', 'int8', (col) => col)
.addColumn('file_ext', 'varchar', (col) => col.notNull())
.addColumn('mime_type', 'varchar', (col) => col)
.addColumn('type', 'varchar', (col) => col)
.addColumn('creator_id', 'uuid', (col) =>
col.references('users.id').notNull(),
)
.addColumn('page_id', 'uuid', (col) => col.references('pages.id'))
.addColumn('space_id', 'uuid', (col) => col.references('spaces.id'))
.addColumn('workspace_id', 'uuid', (col) => col.references('workspaces.id'))
.addColumn('created_at', 'timestamptz', (col) =>
col.notNull().defaultTo(sql`now()`),
)
.addColumn('updated_at', 'timestamptz', (col) =>
col.notNull().defaultTo(sql`now()`),
)
.addColumn('deleted_at', 'timestamptz', (col) => col)
.execute();
}
export async function down(db: Kysely<any>): Promise<void> {
await db.schema.dropTable('attachments').execute();
}
@@ -0,0 +1,22 @@
import { type Kysely, sql } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
await sql`CREATE OR REPLACE FUNCTION pages_tsvector_trigger() RETURNS trigger AS $$
begin
new.tsv :=
setweight(to_tsvector('english', coalesce(new.title, '')), 'A') ||
setweight(to_tsvector('english', coalesce(new.text_content, '')), 'B');
return new;
end;
$$ LANGUAGE plpgsql;`.execute(db);
await sql`CREATE OR REPLACE TRIGGER pages_tsvector_update BEFORE INSERT OR UPDATE
ON pages FOR EACH ROW EXECUTE FUNCTION pages_tsvector_trigger();`.execute(
db,
);
}
export async function down(db: Kysely<any>): Promise<void> {
await sql`DROP trigger pages_tsvector_update ON pages`.execute(db);
await sql`DROP FUNCTION pages_tsvector_trigger`.execute(db);
}