mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 06:23:06 +08:00
WIP
This commit is contained in:
@@ -97,6 +97,12 @@ export const emailTypeOptionsSchema = z
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
export const personTypeOptionsSchema = z
|
||||
.object({
|
||||
allowMultiple: z.boolean().default(true),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
export const emptyTypeOptionsSchema = z.object({}).passthrough();
|
||||
|
||||
const typeOptionsSchemaMap: Record<BasePropertyTypeValue, z.ZodType> = {
|
||||
@@ -106,7 +112,7 @@ const typeOptionsSchemaMap: Record<BasePropertyTypeValue, z.ZodType> = {
|
||||
[BasePropertyType.STATUS]: selectTypeOptionsSchema,
|
||||
[BasePropertyType.MULTI_SELECT]: selectTypeOptionsSchema,
|
||||
[BasePropertyType.DATE]: dateTypeOptionsSchema,
|
||||
[BasePropertyType.PERSON]: emptyTypeOptionsSchema,
|
||||
[BasePropertyType.PERSON]: personTypeOptionsSchema,
|
||||
[BasePropertyType.FILE]: emptyTypeOptionsSchema,
|
||||
[BasePropertyType.CHECKBOX]: checkboxTypeOptionsSchema,
|
||||
[BasePropertyType.URL]: urlTypeOptionsSchema,
|
||||
@@ -145,7 +151,7 @@ const cellValueSchemaMap: Partial<Record<BasePropertyTypeValue, z.ZodType>> = {
|
||||
[BasePropertyType.STATUS]: z.string().uuid(),
|
||||
[BasePropertyType.MULTI_SELECT]: z.array(z.string().uuid()),
|
||||
[BasePropertyType.DATE]: z.string(),
|
||||
[BasePropertyType.PERSON]: z.array(z.string().uuid()),
|
||||
[BasePropertyType.PERSON]: z.union([z.string().uuid(), z.array(z.string().uuid())]),
|
||||
[BasePropertyType.FILE]: z.array(z.object({
|
||||
id: z.string().uuid(),
|
||||
fileName: z.string(),
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import * as path from 'path';
|
||||
import * as dotenv from 'dotenv';
|
||||
import { Kysely, sql } from 'kysely';
|
||||
import { Kysely } from 'kysely';
|
||||
import { PostgresJSDialect } from 'kysely-postgres-js';
|
||||
import postgres from 'postgres';
|
||||
import { v7 as uuid7 } from 'uuid';
|
||||
import { generateJitteredKeyBetween } from 'fractional-indexing-jittered';
|
||||
|
||||
const BASE_ID = '019c69a5-1d84-7985-a7f6-8ee2871d8669';
|
||||
const TOTAL_ROWS = 100_000;
|
||||
const TOTAL_ROWS = 1500;
|
||||
const BATCH_SIZE = 2000;
|
||||
|
||||
const envFilePath = path.resolve(process.cwd(), '..', '..', '.env');
|
||||
@@ -48,6 +47,10 @@ const WORDS = [
|
||||
'Timeline', 'Milestone', 'Objective', 'Strategy', 'Initiative',
|
||||
];
|
||||
|
||||
const COLORS = [
|
||||
'red', 'orange', 'yellow', 'green', 'blue', 'purple', 'pink', 'gray',
|
||||
];
|
||||
|
||||
function randomWords(min: number, max: number): string {
|
||||
const count = min + Math.floor(Math.random() * (max - min + 1));
|
||||
const result: string[] = [];
|
||||
@@ -57,6 +60,60 @@ function randomWords(min: number, max: number): string {
|
||||
return result.join(' ');
|
||||
}
|
||||
|
||||
function makeChoices(names: string[], category?: string) {
|
||||
return names.map((name, i) => ({
|
||||
id: uuid7(),
|
||||
name,
|
||||
color: COLORS[i % COLORS.length],
|
||||
...(category ? {} : {}),
|
||||
}));
|
||||
}
|
||||
|
||||
function makeStatusChoices() {
|
||||
const todo = [{ id: uuid7(), name: 'Not Started', color: 'gray', category: 'todo' }];
|
||||
const inProgress = [
|
||||
{ id: uuid7(), name: 'In Progress', color: 'blue', category: 'inProgress' },
|
||||
{ id: uuid7(), name: 'In Review', color: 'purple', category: 'inProgress' },
|
||||
];
|
||||
const complete = [
|
||||
{ id: uuid7(), name: 'Done', color: 'green', category: 'complete' },
|
||||
{ id: uuid7(), name: 'Cancelled', color: 'red', category: 'complete' },
|
||||
];
|
||||
const all = [...todo, ...inProgress, ...complete];
|
||||
return { choices: all, choiceOrder: all.map((c) => c.id) };
|
||||
}
|
||||
|
||||
type PropertyDef = {
|
||||
name: string;
|
||||
type: string;
|
||||
isPrimary?: boolean;
|
||||
typeOptions?: any;
|
||||
};
|
||||
|
||||
function buildPropertyDefinitions(): PropertyDef[] {
|
||||
const priorityChoices = makeChoices(['Low', 'Medium', 'High', 'Critical']);
|
||||
const categoryChoices = makeChoices(['Engineering', 'Design', 'Marketing', 'Sales', 'Support', 'Operations']);
|
||||
const tagChoices = makeChoices(['Bug', 'Feature', 'Improvement', 'Documentation', 'Research']);
|
||||
const statusOpts = makeStatusChoices();
|
||||
|
||||
return [
|
||||
{ name: 'Title', type: 'text', isPrimary: true },
|
||||
{ name: 'Status', type: 'status', typeOptions: statusOpts },
|
||||
{ name: 'Priority', type: 'select', typeOptions: { choices: priorityChoices, choiceOrder: priorityChoices.map((c) => c.id) } },
|
||||
{ name: 'Category', type: 'select', typeOptions: { choices: categoryChoices, choiceOrder: categoryChoices.map((c) => c.id) } },
|
||||
{ name: 'Tags', type: 'multiSelect', typeOptions: { choices: tagChoices, choiceOrder: tagChoices.map((c) => c.id) } },
|
||||
{ name: 'Due Date', type: 'date', typeOptions: { dateFormat: 'YYYY-MM-DD', includeTime: false } },
|
||||
{ name: 'Estimate', type: 'number', typeOptions: { format: 'plain', precision: 1 } },
|
||||
{ name: 'Budget', type: 'number', typeOptions: { format: 'currency', precision: 2, currencySymbol: '$' } },
|
||||
{ name: 'Approved', type: 'checkbox' },
|
||||
{ name: 'Website', type: 'url' },
|
||||
{ name: 'Contact Email', type: 'email' },
|
||||
{ name: 'Notes', type: 'text' },
|
||||
{ name: 'Created', type: 'createdAt' },
|
||||
{ name: 'Last Edited', type: 'lastEditedAt' },
|
||||
];
|
||||
}
|
||||
|
||||
type CellGenerator = () => unknown;
|
||||
|
||||
function buildCellGenerator(property: any): CellGenerator | null {
|
||||
@@ -108,17 +165,80 @@ function buildCellGenerator(property: any): CellGenerator | null {
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log(`Seeding ${TOTAL_ROWS.toLocaleString()} rows for base ${BASE_ID}\n`);
|
||||
async function createBase(workspaceId: string, spaceId: string, creatorId: string | null): Promise<string> {
|
||||
const baseId = uuid7();
|
||||
const baseName = `Seed Base ${new Date().toISOString().slice(0, 16)}`;
|
||||
|
||||
const base = await db
|
||||
.selectFrom('bases')
|
||||
.selectAll()
|
||||
.where('id', '=', BASE_ID)
|
||||
await db.insertInto('bases').values({
|
||||
id: baseId,
|
||||
name: baseName,
|
||||
space_id: spaceId,
|
||||
workspace_id: workspaceId,
|
||||
creator_id: creatorId,
|
||||
created_at: new Date(),
|
||||
updated_at: new Date(),
|
||||
}).execute();
|
||||
|
||||
console.log(`Created base: ${baseName}`);
|
||||
console.log(`Base ID: ${baseId}\n`);
|
||||
|
||||
// Create properties
|
||||
const propertyDefs = buildPropertyDefinitions();
|
||||
let propPosition: string | null = null;
|
||||
const insertedProperties: any[] = [];
|
||||
|
||||
for (const def of propertyDefs) {
|
||||
propPosition = generateJitteredKeyBetween(propPosition, null);
|
||||
const prop = {
|
||||
id: uuid7(),
|
||||
base_id: baseId,
|
||||
name: def.name,
|
||||
type: def.type,
|
||||
position: propPosition,
|
||||
type_options: def.typeOptions ? JSON.stringify(def.typeOptions) : null,
|
||||
is_primary: def.isPrimary ?? false,
|
||||
workspace_id: workspaceId,
|
||||
created_at: new Date(),
|
||||
updated_at: new Date(),
|
||||
};
|
||||
insertedProperties.push(prop);
|
||||
}
|
||||
|
||||
await db.insertInto('base_properties').values(insertedProperties).execute();
|
||||
console.log(`Created ${insertedProperties.length} properties:`);
|
||||
for (const p of insertedProperties) {
|
||||
console.log(` - ${p.name} (${p.type})${p.is_primary ? ' [primary]' : ''}${SKIP_TYPES.has(p.type) ? ' [system]' : ''}`);
|
||||
}
|
||||
|
||||
// Create default view
|
||||
const viewId = uuid7();
|
||||
await db.insertInto('base_views').values({
|
||||
id: viewId,
|
||||
base_id: baseId,
|
||||
name: 'Table View 1',
|
||||
type: 'table',
|
||||
position: generateJitteredKeyBetween(null, null),
|
||||
config: JSON.stringify({}),
|
||||
workspace_id: workspaceId,
|
||||
creator_id: creatorId,
|
||||
created_at: new Date(),
|
||||
updated_at: new Date(),
|
||||
}).execute();
|
||||
console.log(`Created view: Table View 1\n`);
|
||||
|
||||
return baseId;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const spaceId = '019c69a3-dd47-7014-8b87-ec8f167577ee';
|
||||
|
||||
const space = await db
|
||||
.selectFrom('spaces')
|
||||
.select(['id', 'workspace_id'])
|
||||
.where('id', '=', spaceId)
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
const workspaceId = base.workspace_id;
|
||||
console.log(`Workspace: ${workspaceId}`);
|
||||
const workspaceId = space.workspace_id;
|
||||
|
||||
const user = await db
|
||||
.selectFrom('users')
|
||||
@@ -127,19 +247,21 @@ async function main() {
|
||||
.executeTakeFirst();
|
||||
|
||||
const creatorId = user?.id ?? null;
|
||||
console.log(`Creator: ${creatorId ?? '(none)'}`);
|
||||
|
||||
console.log(`Workspace: ${workspaceId}`);
|
||||
console.log(`Space: ${spaceId}`);
|
||||
console.log(`Creator: ${creatorId ?? '(none)'}\n`);
|
||||
|
||||
// Create the base with properties and view
|
||||
const baseId = await createBase(workspaceId, spaceId, creatorId);
|
||||
|
||||
// Load the created properties for cell generation
|
||||
const properties = await db
|
||||
.selectFrom('base_properties')
|
||||
.selectAll()
|
||||
.where('base_id', '=', BASE_ID)
|
||||
.where('base_id', '=', baseId)
|
||||
.execute();
|
||||
|
||||
console.log(`Properties: ${properties.length}`);
|
||||
for (const p of properties) {
|
||||
console.log(` - ${p.name} (${p.type})${SKIP_TYPES.has(p.type) ? ' [skipped]' : ''}`);
|
||||
}
|
||||
|
||||
const generators: Array<{ propertyId: string; generate: CellGenerator }> = [];
|
||||
for (const prop of properties) {
|
||||
const gen = buildCellGenerator(prop);
|
||||
@@ -148,18 +270,9 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nGenerating ${TOTAL_ROWS.toLocaleString()} positions...`);
|
||||
console.log(`Generating ${TOTAL_ROWS.toLocaleString()} positions...`);
|
||||
|
||||
const lastRow = await db
|
||||
.selectFrom('base_rows')
|
||||
.select('position')
|
||||
.where('base_id', '=', BASE_ID)
|
||||
.where('deleted_at', 'is', null)
|
||||
.orderBy(sql`position COLLATE "C"`, sql`desc`)
|
||||
.limit(1)
|
||||
.executeTakeFirst();
|
||||
|
||||
let lastPosition: string | null = lastRow?.position ?? null;
|
||||
let lastPosition: string | null = null;
|
||||
const positions: string[] = new Array(TOTAL_ROWS);
|
||||
for (let i = 0; i < TOTAL_ROWS; i++) {
|
||||
lastPosition = generateJitteredKeyBetween(lastPosition, null);
|
||||
@@ -182,7 +295,7 @@ async function main() {
|
||||
|
||||
rows.push({
|
||||
id: uuid7(),
|
||||
base_id: BASE_ID,
|
||||
base_id: baseId,
|
||||
cells,
|
||||
position: positions[i],
|
||||
creator_id: creatorId,
|
||||
@@ -201,6 +314,7 @@ async function main() {
|
||||
|
||||
const totalElapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
||||
console.log(`\nDone. Inserted ${TOTAL_ROWS.toLocaleString()} rows in ${totalElapsed}s`);
|
||||
console.log(`\nBase ID: ${baseId}`);
|
||||
|
||||
await db.destroy();
|
||||
process.exit(0);
|
||||
|
||||
Reference in New Issue
Block a user