mirror of https://github.com/requarks/wiki
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
430 lines
14 KiB
430 lines
14 KiB
import { sql } from 'drizzle-orm'
|
|
import {
|
|
bigint,
|
|
boolean,
|
|
bytea,
|
|
customType,
|
|
index,
|
|
integer,
|
|
jsonb,
|
|
pgEnum,
|
|
pgTable,
|
|
primaryKey,
|
|
text,
|
|
timestamp,
|
|
uniqueIndex,
|
|
uuid,
|
|
varchar
|
|
} from 'drizzle-orm/pg-core'
|
|
|
|
// == CUSTOM TYPES =====================
|
|
|
|
const ltree = customType({
|
|
dataType() {
|
|
return 'ltree'
|
|
}
|
|
})
|
|
const tsvector = customType({
|
|
dataType() {
|
|
return 'tsvector'
|
|
}
|
|
})
|
|
|
|
// == TABLES ===========================
|
|
|
|
// API KEYS ----------------------------
|
|
export const apiKeys = pgTable('apiKeys', {
|
|
id: uuid().primaryKey().defaultRandom(),
|
|
name: varchar({ length: 255 }).notNull(),
|
|
key: text().notNull(),
|
|
expiration: timestamp().notNull().defaultNow(),
|
|
isRevoked: boolean().notNull().default(false),
|
|
createdAt: timestamp().notNull().defaultNow(),
|
|
updatedAt: timestamp().notNull().defaultNow()
|
|
})
|
|
|
|
// ASSETS ------------------------------
|
|
export const assetKindEnum = pgEnum('assetKind', ['document', 'image', 'other'])
|
|
export const assets = pgTable(
|
|
'assets',
|
|
{
|
|
id: uuid().primaryKey().defaultRandom(),
|
|
fileName: varchar({ length: 255 }).notNull(),
|
|
fileExt: varchar({ length: 255 }).notNull(),
|
|
isSystem: boolean().notNull().default(false),
|
|
kind: assetKindEnum().notNull().default('other'),
|
|
mimeType: varchar({ length: 255 }).notNull().default('application/octet-stream'),
|
|
fileSize: bigint({ mode: 'number' }), // in bytes
|
|
meta: jsonb().notNull().default({}),
|
|
createdAt: timestamp().notNull().defaultNow(),
|
|
updatedAt: timestamp().notNull().defaultNow(),
|
|
data: bytea(),
|
|
preview: bytea(),
|
|
storageInfo: jsonb(),
|
|
authorId: uuid()
|
|
.notNull()
|
|
.references(() => users.id),
|
|
siteId: uuid()
|
|
.notNull()
|
|
.references(() => sites.id)
|
|
},
|
|
(table) => [index('assets_siteId_idx').on(table.siteId)]
|
|
)
|
|
|
|
// AUTHENTICATION ----------------------
|
|
export const authentication = pgTable('authentication', {
|
|
id: uuid().primaryKey().defaultRandom(),
|
|
module: varchar({ length: 255 }).notNull(),
|
|
isEnabled: boolean().notNull().default(false),
|
|
displayName: varchar({ length: 255 }).notNull().default(''),
|
|
config: jsonb().notNull().default({}),
|
|
registration: boolean().notNull().default(false),
|
|
allowedEmailRegex: varchar({ length: 255 }).notNull().default(''),
|
|
autoEnrollGroups: uuid().array().default([])
|
|
})
|
|
|
|
// BLOCKS ------------------------------
|
|
export const blocks = pgTable(
|
|
'blocks',
|
|
{
|
|
id: uuid().primaryKey().defaultRandom(),
|
|
block: varchar({ length: 255 }).notNull(),
|
|
name: varchar({ length: 255 }).notNull(),
|
|
description: varchar({ length: 255 }).notNull(),
|
|
icon: varchar({ length: 255 }).notNull(),
|
|
isEnabled: boolean().notNull().default(false),
|
|
isCustom: boolean().notNull().default(false),
|
|
config: jsonb().notNull().default({}),
|
|
siteId: uuid()
|
|
.notNull()
|
|
.references(() => sites.id)
|
|
},
|
|
(table) => [index('blocks_siteId_idx').on(table.siteId)]
|
|
)
|
|
|
|
// GROUPS ------------------------------
|
|
export const groups = pgTable('groups', {
|
|
id: uuid().primaryKey().defaultRandom(),
|
|
name: varchar({ length: 255 }).notNull(),
|
|
permissions: jsonb().notNull(),
|
|
rules: jsonb().notNull(),
|
|
redirectOnLogin: varchar({ length: 255 }).notNull().default(''),
|
|
redirectOnFirstLogin: varchar({ length: 255 }).notNull().default(''),
|
|
redirectOnLogout: varchar({ length: 255 }).notNull().default(''),
|
|
isSystem: boolean().notNull().default(false),
|
|
createdAt: timestamp().notNull().defaultNow(),
|
|
updatedAt: timestamp().notNull().defaultNow()
|
|
})
|
|
|
|
// JOB HISTORY -------------------------
|
|
export const jobHistoryStateEnum = pgEnum('jobHistoryState', [
|
|
'active',
|
|
'completed',
|
|
'failed',
|
|
'interrupted'
|
|
])
|
|
export const jobHistory = pgTable('jobHistory', {
|
|
id: uuid().primaryKey().defaultRandom(),
|
|
task: varchar({ length: 255 }).notNull(),
|
|
state: jobHistoryStateEnum().notNull(),
|
|
useWorker: boolean().notNull().default(false),
|
|
wasScheduled: boolean().notNull().default(false),
|
|
payload: jsonb(),
|
|
attempt: integer().notNull().default(1),
|
|
maxRetries: integer().notNull().default(0),
|
|
lastErrorMessage: text(),
|
|
executedBy: varchar({ length: 255 }),
|
|
createdAt: timestamp().notNull(),
|
|
startedAt: timestamp().notNull().defaultNow(),
|
|
completedAt: timestamp()
|
|
})
|
|
|
|
// JOB SCHEDULE ------------------------
|
|
export const jobSchedule = pgTable('jobSchedule', {
|
|
id: uuid().primaryKey().defaultRandom(),
|
|
task: varchar({ length: 255 }).notNull(),
|
|
cron: varchar({ length: 255 }).notNull(),
|
|
type: varchar({ length: 255 }).notNull().default('system'),
|
|
payload: jsonb(),
|
|
createdAt: timestamp().notNull().defaultNow(),
|
|
updatedAt: timestamp().notNull().defaultNow()
|
|
})
|
|
|
|
// JOB LOCK ----------------------------
|
|
export const jobLock = pgTable('jobLock', {
|
|
key: varchar({ length: 255 }).primaryKey(),
|
|
lastCheckedBy: varchar({ length: 255 }),
|
|
lastCheckedAt: timestamp().notNull().defaultNow()
|
|
})
|
|
|
|
// JOBS --------------------------------
|
|
export const jobs = pgTable('jobs', {
|
|
id: uuid().primaryKey().defaultRandom(),
|
|
task: varchar({ length: 255 }).notNull(),
|
|
useWorker: boolean().notNull().default(false),
|
|
payload: jsonb(),
|
|
retries: integer().notNull().default(0),
|
|
maxRetries: integer().notNull().default(0),
|
|
waitUntil: timestamp(),
|
|
isScheduled: boolean().notNull().default(false),
|
|
createdBy: varchar({ length: 255 }),
|
|
createdAt: timestamp().notNull().defaultNow(),
|
|
updatedAt: timestamp().notNull().defaultNow()
|
|
})
|
|
|
|
// LOCALES -----------------------------
|
|
export const locales = pgTable(
|
|
'locales',
|
|
{
|
|
code: varchar({ length: 255 }).primaryKey(),
|
|
name: varchar({ length: 255 }).notNull(),
|
|
nativeName: varchar({ length: 255 }).notNull(),
|
|
language: varchar({ length: 8 }).notNull(), // Unicode language subtag
|
|
region: varchar({ length: 3 }).notNull(), // Unicode region subtag
|
|
script: varchar({ length: 4 }).notNull(), // Unicode script subtag
|
|
isRTL: boolean().notNull().default(false),
|
|
strings: jsonb().notNull().default([]),
|
|
completeness: integer().notNull().default(0),
|
|
createdAt: timestamp().notNull().defaultNow(),
|
|
updatedAt: timestamp().notNull().defaultNow()
|
|
},
|
|
(table) => [index('locales_language_idx').on(table.language)]
|
|
)
|
|
|
|
// NAVIGATION --------------------------
|
|
export const navigation = pgTable(
|
|
'navigation',
|
|
{
|
|
id: uuid().primaryKey().defaultRandom(),
|
|
items: jsonb().notNull().default([]),
|
|
siteId: uuid()
|
|
.notNull()
|
|
.references(() => sites.id)
|
|
},
|
|
(table) => [index('navigation_siteId_idx').on(table.siteId)]
|
|
)
|
|
|
|
// PAGES ------------------------------
|
|
export const pagePublishStateEnum = pgEnum('pagePublishState', ['draft', 'published', 'scheduled'])
|
|
export const pages = pgTable(
|
|
'pages',
|
|
{
|
|
id: uuid().primaryKey().defaultRandom(),
|
|
locale: ltree('locale').notNull(),
|
|
path: varchar({ length: 255 }).notNull(),
|
|
hash: varchar({ length: 255 }).notNull(),
|
|
alias: varchar({ length: 255 }),
|
|
title: varchar({ length: 255 }).notNull(),
|
|
description: varchar({ length: 255 }),
|
|
icon: varchar({ length: 255 }),
|
|
publishState: pagePublishStateEnum('publishState').notNull().default('draft'),
|
|
publishStartDate: timestamp(),
|
|
publishEndDate: timestamp(),
|
|
config: jsonb().notNull().default({}),
|
|
relations: jsonb().notNull().default([]),
|
|
content: text(),
|
|
render: text(),
|
|
searchContent: text(),
|
|
ts: tsvector('ts'),
|
|
tags: text()
|
|
.array()
|
|
.notNull()
|
|
.default(sql`ARRAY[]::text[]`),
|
|
toc: jsonb(),
|
|
editor: varchar({ length: 255 }).notNull(),
|
|
contentType: varchar({ length: 255 }).notNull(),
|
|
isBrowsable: boolean().notNull().default(true),
|
|
isSearchable: boolean().notNull().default(true),
|
|
isSearchableComputed: boolean('isSearchableComputed').generatedAlwaysAs(
|
|
() => sql`${pages.publishState} != 'draft' AND ${pages.isSearchable}`
|
|
),
|
|
password: varchar({ length: 255 }),
|
|
ratingScore: integer().notNull().default(0),
|
|
ratingCount: timestamp().notNull().defaultNow(),
|
|
scripts: jsonb().notNull().default({}),
|
|
historyData: jsonb().notNull().default({}),
|
|
createdAt: timestamp().notNull().defaultNow(),
|
|
updatedAt: timestamp().notNull().defaultNow(),
|
|
authorId: uuid()
|
|
.notNull()
|
|
.references(() => users.id),
|
|
creatorId: uuid()
|
|
.notNull()
|
|
.references(() => users.id),
|
|
ownerId: uuid()
|
|
.notNull()
|
|
.references(() => users.id),
|
|
siteId: uuid()
|
|
.notNull()
|
|
.references(() => sites.id)
|
|
},
|
|
(table) => [
|
|
index('pages_authorId_idx').on(table.authorId),
|
|
index('pages_creatorId_idx').on(table.creatorId),
|
|
index('pages_ownerId_idx').on(table.ownerId),
|
|
index('pages_siteId_idx').on(table.siteId),
|
|
index('pages_ts_idx').using('gin', table.ts),
|
|
index('pages_tags_idx').using('gin', table.tags),
|
|
index('pages_isSearchableComputed_idx').on(table.isSearchableComputed)
|
|
]
|
|
)
|
|
|
|
// SETTINGS ----------------------------
|
|
export const settings = pgTable('settings', {
|
|
key: varchar({ length: 255 }).notNull().primaryKey(),
|
|
value: jsonb().notNull().default({})
|
|
})
|
|
|
|
// SESSIONS ----------------------------
|
|
export const sessions = pgTable(
|
|
'sessions',
|
|
{
|
|
id: varchar({ length: 255 }).primaryKey(),
|
|
userId: uuid().references(() => users.id),
|
|
data: jsonb().notNull().default({}),
|
|
createdAt: timestamp().notNull().defaultNow(),
|
|
updatedAt: timestamp().notNull().defaultNow()
|
|
},
|
|
(table) => [index('sessions_userId_idx').on(table.userId)]
|
|
)
|
|
|
|
// SITES -------------------------------
|
|
export const sites = pgTable('sites', {
|
|
id: uuid().primaryKey().defaultRandom(),
|
|
hostname: varchar({ length: 255 }).notNull().unique(),
|
|
isEnabled: boolean().notNull().default(false),
|
|
config: jsonb().notNull(),
|
|
createdAt: timestamp().notNull().defaultNow()
|
|
})
|
|
|
|
// TAGS --------------------------------
|
|
export const tags = pgTable(
|
|
'tags',
|
|
{
|
|
id: uuid().primaryKey().defaultRandom(),
|
|
tag: varchar({ length: 255 }).notNull(),
|
|
usageCount: integer().notNull().default(0),
|
|
createdAt: timestamp().notNull().defaultNow(),
|
|
updatedAt: timestamp().notNull().defaultNow(),
|
|
siteId: uuid()
|
|
.notNull()
|
|
.references(() => sites.id)
|
|
},
|
|
(table) => [
|
|
index('tags_siteId_idx').on(table.siteId),
|
|
uniqueIndex('tags_composite_idx').on(table.siteId, table.tag)
|
|
]
|
|
)
|
|
|
|
// TREE --------------------------------
|
|
export const treeTypeEnum = pgEnum('treeType', ['folder', 'page', 'asset'])
|
|
export const treeNavigationModeEnum = pgEnum('treeNavigationMode', [
|
|
'inherit',
|
|
'override',
|
|
'overrideExact',
|
|
'hide',
|
|
'hideExact'
|
|
])
|
|
export const tree = pgTable(
|
|
'tree',
|
|
{
|
|
id: uuid().primaryKey().defaultRandom(),
|
|
folderPath: ltree('folderPath'),
|
|
fileName: varchar({ length: 255 }).notNull(),
|
|
hash: varchar({ length: 255 }).notNull(),
|
|
type: treeTypeEnum('tree').notNull(),
|
|
locale: ltree('locale').notNull(),
|
|
title: varchar({ length: 255 }).notNull(),
|
|
navigationMode: treeNavigationModeEnum('navigationMode').notNull().default('inherit'),
|
|
navigationId: uuid(),
|
|
tags: text()
|
|
.array()
|
|
.notNull()
|
|
.default(sql`ARRAY[]::text[]`),
|
|
meta: jsonb().notNull().default({}),
|
|
createdAt: timestamp().notNull().defaultNow(),
|
|
updatedAt: timestamp().notNull().defaultNow(),
|
|
siteId: uuid()
|
|
.notNull()
|
|
.references(() => sites.id)
|
|
},
|
|
(table) => [
|
|
index('tree_folderpath_idx').on(table.folderPath),
|
|
index('tree_folderpath_gist_idx').using('gist', table.folderPath),
|
|
index('tree_fileName_idx').on(table.fileName),
|
|
index('tree_hash_idx').on(table.hash),
|
|
index('tree_type_idx').on(table.type),
|
|
index('tree_locale_idx').using('gist', table.locale),
|
|
index('tree_navigationMode_idx').on(table.navigationMode),
|
|
index('tree_navigationId_idx').on(table.navigationId),
|
|
index('tree_tags_idx').using('gin', table.tags),
|
|
index('tree_siteId_idx').on(table.siteId)
|
|
]
|
|
)
|
|
|
|
// USER AVATARS ------------------------
|
|
export const userAvatars = pgTable('userAvatars', {
|
|
id: uuid().primaryKey(),
|
|
data: bytea().notNull()
|
|
})
|
|
|
|
// USER KEYS ---------------------------
|
|
export const userKeys = pgTable(
|
|
'userKeys',
|
|
{
|
|
id: uuid().primaryKey().defaultRandom(),
|
|
kind: varchar({ length: 255 }).notNull(),
|
|
token: varchar({ length: 255 }).notNull(),
|
|
meta: jsonb().notNull().default({}),
|
|
createdAt: timestamp().notNull().defaultNow(),
|
|
validUntil: timestamp().notNull(),
|
|
userId: uuid()
|
|
.notNull()
|
|
.references(() => users.id)
|
|
},
|
|
(table) => [index('userKeys_userId_idx').on(table.userId)]
|
|
)
|
|
|
|
// USERS -------------------------------
|
|
export const users = pgTable(
|
|
'users',
|
|
{
|
|
id: uuid().primaryKey().defaultRandom(),
|
|
email: varchar({ length: 255 }).notNull().unique(),
|
|
name: varchar({ length: 255 }).notNull(),
|
|
auth: jsonb().notNull().default({}),
|
|
meta: jsonb().notNull().default({}),
|
|
passkeys: jsonb().notNull().default({}),
|
|
prefs: jsonb().notNull().default({}),
|
|
hasAvatar: boolean().notNull().default(false),
|
|
isActive: boolean().notNull().default(false),
|
|
isSystem: boolean().notNull().default(false),
|
|
isVerified: boolean().notNull().default(false),
|
|
lastLoginAt: timestamp(),
|
|
createdAt: timestamp().notNull().defaultNow(),
|
|
updatedAt: timestamp().notNull().defaultNow()
|
|
},
|
|
(table) => [index('users_lastLoginAt_idx').on(table.lastLoginAt)]
|
|
)
|
|
|
|
// == RELATION TABLES ==================
|
|
|
|
// USER GROUPS -------------------------
|
|
export const userGroups = pgTable(
|
|
'userGroups',
|
|
{
|
|
userId: uuid()
|
|
.notNull()
|
|
.references(() => users.id, { onDelete: 'cascade' }),
|
|
groupId: uuid()
|
|
.notNull()
|
|
.references(() => groups.id, { onDelete: 'cascade' })
|
|
},
|
|
(table) => [
|
|
primaryKey({ columns: [table.userId, table.groupId] }),
|
|
index('userGroups_userId_idx').on(table.userId),
|
|
index('userGroups_groupId_idx').on(table.groupId),
|
|
index('userGroups_composite_idx').on(table.userId, table.groupId)
|
|
]
|
|
)
|