mirror of
https://github.com/colanode/colanode.git
synced 2025-12-29 00:25:03 +01:00
Restructure and refactor services in desktop
This commit is contained in:
@@ -5,21 +5,20 @@ import { createDebugger } from '@colanode/core';
|
||||
import { app, BrowserWindow, ipcMain, protocol, shell } from 'electron';
|
||||
import path from 'path';
|
||||
|
||||
import { metadataService } from '@/main/services/metadata-service';
|
||||
import { notificationService } from '@/main/services/notification-service';
|
||||
import { WindowSize } from '@/shared/types/metadata';
|
||||
import { scheduler } from '@/main/scheduler';
|
||||
import { assetService } from '@/main/services/asset-service';
|
||||
import { avatarService } from '@/main/services/avatar-service';
|
||||
import { commandService } from '@/main/services/command-service';
|
||||
import { fileService } from '@/main/services/file-service';
|
||||
import { mutationService } from '@/main/services/mutation-service';
|
||||
import { queryService } from '@/main/services/query-service';
|
||||
import { mediator } from '@/main/mediator';
|
||||
import { getAppIconPath } from '@/main/utils';
|
||||
import { CommandInput, CommandMap } from '@/shared/commands';
|
||||
import { eventBus } from '@/shared/lib/event-bus';
|
||||
import { MutationInput, MutationMap } from '@/shared/mutations';
|
||||
import { QueryInput, QueryMap } from '@/shared/queries';
|
||||
import { appService } from '@/main/services/app-service';
|
||||
import {
|
||||
handleAssetRequest,
|
||||
handleAvatarRequest,
|
||||
handleFilePreviewRequest,
|
||||
handleFileRequest,
|
||||
} from '@/main/lib/protocols';
|
||||
|
||||
const debug = createDebugger('desktop:main');
|
||||
|
||||
@@ -42,11 +41,10 @@ updateElectronApp({
|
||||
});
|
||||
|
||||
const createWindow = async () => {
|
||||
await scheduler.init();
|
||||
notificationService.checkBadge();
|
||||
await appService.migrate();
|
||||
|
||||
// Create the browser window.
|
||||
let windowSize = await metadataService.get<WindowSize>('window_size');
|
||||
let windowSize = await appService.metadata.get<WindowSize>('window_size');
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: windowSize?.width ?? 1200,
|
||||
height: windowSize?.height ?? 800,
|
||||
@@ -70,7 +68,7 @@ const createWindow = async () => {
|
||||
fullscreen: false,
|
||||
};
|
||||
|
||||
metadataService.set('window_size', windowSize);
|
||||
appService.metadata.set('window_size', windowSize);
|
||||
});
|
||||
|
||||
mainWindow.on('enter-full-screen', () => {
|
||||
@@ -80,7 +78,7 @@ const createWindow = async () => {
|
||||
fullscreen: true,
|
||||
};
|
||||
|
||||
metadataService.set('window_size', windowSize);
|
||||
appService.metadata.set('window_size', windowSize);
|
||||
});
|
||||
|
||||
mainWindow.on('leave-full-screen', () => {
|
||||
@@ -90,7 +88,7 @@ const createWindow = async () => {
|
||||
fullscreen: false,
|
||||
};
|
||||
|
||||
metadataService.set('window_size', windowSize);
|
||||
appService.metadata.set('window_size', windowSize);
|
||||
});
|
||||
|
||||
// and load the index.html of the app.
|
||||
@@ -116,25 +114,25 @@ const createWindow = async () => {
|
||||
|
||||
if (!protocol.isProtocolHandled('avatar')) {
|
||||
protocol.handle('avatar', (request) => {
|
||||
return avatarService.handleAvatarRequest(request);
|
||||
return handleAvatarRequest(request);
|
||||
});
|
||||
}
|
||||
|
||||
if (!protocol.isProtocolHandled('local-file')) {
|
||||
protocol.handle('local-file', (request) => {
|
||||
return fileService.handleFileRequest(request);
|
||||
return handleFileRequest(request);
|
||||
});
|
||||
}
|
||||
|
||||
if (!protocol.isProtocolHandled('local-file-preview')) {
|
||||
protocol.handle('local-file-preview', (request) => {
|
||||
return fileService.handleFilePreviewRequest(request);
|
||||
return handleFilePreviewRequest(request);
|
||||
});
|
||||
}
|
||||
|
||||
if (!protocol.isProtocolHandled('asset')) {
|
||||
protocol.handle('asset', (request) => {
|
||||
return assetService.handleAssetRequest(request);
|
||||
return handleAssetRequest(request);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -159,7 +157,7 @@ app.on('window-all-closed', () => {
|
||||
app.quit();
|
||||
}
|
||||
|
||||
queryService.clearSubscriptions();
|
||||
mediator.clearSubscriptions();
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
@@ -173,7 +171,7 @@ app.on('activate', () => {
|
||||
// In this file you can include the rest of your app's specific main process
|
||||
// code. You can also put them in separate files and import them here.
|
||||
ipcMain.handle('init', async () => {
|
||||
await scheduler.init();
|
||||
await appService.init();
|
||||
});
|
||||
|
||||
ipcMain.handle(
|
||||
@@ -182,7 +180,7 @@ ipcMain.handle(
|
||||
_: unknown,
|
||||
input: T
|
||||
): Promise<MutationMap[T['type']]['output']> => {
|
||||
return mutationService.executeMutation(input);
|
||||
return mediator.executeMutation(input);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -192,7 +190,7 @@ ipcMain.handle(
|
||||
_: unknown,
|
||||
input: T
|
||||
): Promise<QueryMap[T['type']]['output']> => {
|
||||
return queryService.executeQuery(input);
|
||||
return mediator.executeQuery(input);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -203,12 +201,12 @@ ipcMain.handle(
|
||||
id: string,
|
||||
input: T
|
||||
): Promise<QueryMap[T['type']]['output']> => {
|
||||
return queryService.executeQueryAndSubscribe(id, input);
|
||||
return mediator.executeQueryAndSubscribe(id, input);
|
||||
}
|
||||
);
|
||||
|
||||
ipcMain.handle('unsubscribe-query', (_: unknown, id: string): void => {
|
||||
queryService.unsubscribeQuery(id);
|
||||
mediator.unsubscribeQuery(id);
|
||||
});
|
||||
|
||||
ipcMain.handle(
|
||||
@@ -217,6 +215,6 @@ ipcMain.handle(
|
||||
_: unknown,
|
||||
input: T
|
||||
): Promise<CommandMap[T['type']]['output']> => {
|
||||
return commandService.executeCommand(input);
|
||||
return mediator.executeCommand(input);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { fileService } from '@/main/services/file-service';
|
||||
import path from 'path';
|
||||
import { shell } from 'electron';
|
||||
|
||||
import { getWorkspaceFilesDirectoryPath } from '@/main/utils';
|
||||
import { CommandHandler } from '@/main/types';
|
||||
import { FileOpenCommandInput } from '@/shared/commands/file-open';
|
||||
|
||||
@@ -6,6 +9,16 @@ export class FileOpenCommandHandler
|
||||
implements CommandHandler<FileOpenCommandInput>
|
||||
{
|
||||
public async handleCommand(input: FileOpenCommandInput): Promise<void> {
|
||||
fileService.openFile(input.userId, input.fileId, input.extension);
|
||||
const workspaceFilesDir = getWorkspaceFilesDirectoryPath(
|
||||
input.accountId,
|
||||
input.workspaceId
|
||||
);
|
||||
|
||||
const filePath = path.join(
|
||||
workspaceFilesDir,
|
||||
`${input.fileId}${input.extension}`
|
||||
);
|
||||
|
||||
shell.openPath(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
import { Migration } from 'kysely';
|
||||
|
||||
const createServersTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('servers')
|
||||
.addColumn('domain', 'text', (col) => col.notNull().primaryKey())
|
||||
.addColumn('name', 'text', (col) => col.notNull())
|
||||
.addColumn('avatar', 'text', (col) => col.notNull())
|
||||
.addColumn('attributes', 'text', (col) => col.notNull())
|
||||
.addColumn('version', 'text', (col) => col.notNull())
|
||||
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||
.addColumn('last_synced_at', 'text')
|
||||
.execute();
|
||||
|
||||
await db
|
||||
.insertInto('servers')
|
||||
.values([
|
||||
{
|
||||
domain: 'eu.colanode.com',
|
||||
name: 'Colanode Cloud (EU)',
|
||||
avatar: 'https://colanode.com/assets/flags/eu.svg',
|
||||
attributes: '{}',
|
||||
version: '0.1.0',
|
||||
created_at: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
domain: 'us.colanode.com',
|
||||
name: 'Colanode Cloud (US)',
|
||||
avatar: 'https://colanode.com/assets/flags/us.svg',
|
||||
attributes: '{}',
|
||||
version: '0.1.0',
|
||||
created_at: new Date().toISOString(),
|
||||
},
|
||||
])
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('servers').execute();
|
||||
},
|
||||
};
|
||||
|
||||
const createAccountsTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('accounts')
|
||||
.addColumn('id', 'text', (col) => col.notNull().primaryKey())
|
||||
.addColumn('device_id', 'text', (col) => col.notNull())
|
||||
.addColumn('server', 'text', (col) => col.notNull())
|
||||
.addColumn('name', 'text', (col) => col.notNull())
|
||||
.addColumn('email', 'text', (col) => col.notNull())
|
||||
.addColumn('avatar', 'text')
|
||||
.addColumn('token', 'text', (col) => col.notNull())
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('accounts').execute();
|
||||
},
|
||||
};
|
||||
|
||||
const createWorkspacesTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('workspaces')
|
||||
.addColumn('user_id', 'text', (col) => col.notNull().primaryKey())
|
||||
.addColumn('workspace_id', 'text', (col) => col.notNull())
|
||||
.addColumn('account_id', 'text', (col) =>
|
||||
col.notNull().references('accounts.id').onDelete('cascade')
|
||||
)
|
||||
.addColumn('name', 'text', (col) => col.notNull())
|
||||
.addColumn('description', 'text')
|
||||
.addColumn('avatar', 'text')
|
||||
.addColumn('role', 'text', (col) => col.notNull())
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('workspaces').execute();
|
||||
},
|
||||
};
|
||||
|
||||
const createDeletedTokensTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('deleted_tokens')
|
||||
.addColumn('account_id', 'text', (col) => col.notNull())
|
||||
.addColumn('token', 'text', (col) => col.notNull().primaryKey())
|
||||
.addColumn('server', 'text', (col) => col.notNull())
|
||||
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('deleted_tokens').execute();
|
||||
},
|
||||
};
|
||||
|
||||
const createMetadataTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('metadata')
|
||||
.addColumn('key', 'text', (col) => col.notNull().primaryKey())
|
||||
.addColumn('value', 'text', (col) => col.notNull())
|
||||
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||
.addColumn('updated_at', 'text')
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('metadata').execute();
|
||||
},
|
||||
};
|
||||
|
||||
export const appDatabaseMigrations: Record<string, Migration> = {
|
||||
'00001_create_servers_table': createServersTable,
|
||||
'00002_create_accounts_table': createAccountsTable,
|
||||
'00003_create_workspaces_table': createWorkspacesTable,
|
||||
'00004_create_deleted_tokens_table': createDeletedTokensTable,
|
||||
'00005_create_metadata_table': createMetadataTable,
|
||||
};
|
||||
@@ -1,186 +0,0 @@
|
||||
import SQLite from 'better-sqlite3';
|
||||
import { Kysely, Migration, Migrator, SqliteDialect } from 'kysely';
|
||||
import { createDebugger } from '@colanode/core';
|
||||
|
||||
import fs from 'fs';
|
||||
|
||||
import { appDatabaseMigrations } from '@/main/data/app/migrations';
|
||||
import { AppDatabaseSchema } from '@/main/data/app/schema';
|
||||
import { workspaceDatabaseMigrations } from '@/main/data/workspace/migrations';
|
||||
import { WorkspaceDatabaseSchema } from '@/main/data/workspace/schema';
|
||||
import { appDatabasePath, getWorkspaceDirectoryPath } from '@/main/utils';
|
||||
import { eventBus } from '@/shared/lib/event-bus';
|
||||
|
||||
class DatabaseService {
|
||||
private initPromise: Promise<void> | null = null;
|
||||
private readonly workspaceDatabases: Map<
|
||||
string,
|
||||
Kysely<WorkspaceDatabaseSchema>
|
||||
> = new Map();
|
||||
|
||||
public readonly appDatabase: Kysely<AppDatabaseSchema>;
|
||||
private readonly debug = createDebugger('desktop:service:database');
|
||||
|
||||
constructor() {
|
||||
this.debug('Constructing database service');
|
||||
const dialect = new SqliteDialect({
|
||||
database: this.buildSqlite(appDatabasePath),
|
||||
});
|
||||
|
||||
this.appDatabase = new Kysely<AppDatabaseSchema>({ dialect });
|
||||
|
||||
eventBus.subscribe((event) => {
|
||||
if (event.type === 'workspace_created') {
|
||||
this.initWorkspaceDatabase(event.workspace.userId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async init(): Promise<void> {
|
||||
this.debug('Initializing database service');
|
||||
if (!this.initPromise) {
|
||||
this.initPromise = this.executeInit();
|
||||
}
|
||||
|
||||
await this.initPromise;
|
||||
}
|
||||
|
||||
public async getWorkspaceDatabase(
|
||||
userId: string
|
||||
): Promise<Kysely<WorkspaceDatabaseSchema>> {
|
||||
await this.waitForInit();
|
||||
|
||||
if (this.workspaceDatabases.has(userId)) {
|
||||
return this.workspaceDatabases.get(userId)!;
|
||||
}
|
||||
|
||||
//try and check if it's in database but hasn't been loaded yet
|
||||
const workspace = await this.appDatabase
|
||||
.selectFrom('workspaces')
|
||||
.selectAll()
|
||||
.where('user_id', '=', userId)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!workspace) {
|
||||
throw new Error('Workspace database not found');
|
||||
}
|
||||
|
||||
const workspaceDatabase = await this.initWorkspaceDatabase(userId);
|
||||
this.workspaceDatabases.set(userId, workspaceDatabase);
|
||||
return workspaceDatabase;
|
||||
}
|
||||
|
||||
public async getWorkspaceDatabases(): Promise<
|
||||
Map<string, Kysely<WorkspaceDatabaseSchema>>
|
||||
> {
|
||||
await this.waitForInit();
|
||||
return this.workspaceDatabases;
|
||||
}
|
||||
|
||||
public async removeWorkspaceDatabase(userId: string): Promise<void> {
|
||||
this.debug(`Deleting workspace database for user: ${userId}`);
|
||||
await this.waitForInit();
|
||||
|
||||
const workspaceDatabase = this.workspaceDatabases.get(userId);
|
||||
if (workspaceDatabase) {
|
||||
try {
|
||||
workspaceDatabase.destroy();
|
||||
} catch (error) {
|
||||
this.debug(
|
||||
`Failed to destroy workspace database for user: ${userId}`,
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.workspaceDatabases.delete(userId);
|
||||
}
|
||||
|
||||
private async waitForInit(): Promise<void> {
|
||||
if (!this.initPromise) {
|
||||
this.initPromise = this.executeInit();
|
||||
}
|
||||
|
||||
await this.initPromise;
|
||||
}
|
||||
|
||||
private async executeInit(): Promise<void> {
|
||||
await this.migrateAppDatabase();
|
||||
|
||||
const workspaces = await this.appDatabase
|
||||
.selectFrom('workspaces')
|
||||
.select('user_id')
|
||||
.execute();
|
||||
|
||||
for (const workspace of workspaces) {
|
||||
const workspaceDatabase = await this.initWorkspaceDatabase(
|
||||
workspace.user_id
|
||||
);
|
||||
|
||||
this.workspaceDatabases.set(workspace.user_id, workspaceDatabase);
|
||||
}
|
||||
}
|
||||
|
||||
private async initWorkspaceDatabase(
|
||||
userId: string
|
||||
): Promise<Kysely<WorkspaceDatabaseSchema>> {
|
||||
this.debug(`Initializing workspace database for user: ${userId}`);
|
||||
const workspaceDir = getWorkspaceDirectoryPath(userId);
|
||||
|
||||
if (!fs.existsSync(workspaceDir)) {
|
||||
fs.mkdirSync(workspaceDir, {
|
||||
recursive: true,
|
||||
});
|
||||
}
|
||||
|
||||
const dialect = new SqliteDialect({
|
||||
database: this.buildSqlite(`${workspaceDir}/workspace.db`),
|
||||
});
|
||||
|
||||
const workspaceDatabase = new Kysely<WorkspaceDatabaseSchema>({
|
||||
dialect,
|
||||
});
|
||||
|
||||
await this.migrateWorkspaceDatabase(workspaceDatabase);
|
||||
return workspaceDatabase;
|
||||
}
|
||||
|
||||
private async migrateAppDatabase(): Promise<void> {
|
||||
this.debug('Migrating app database');
|
||||
const migrator = new Migrator({
|
||||
db: this.appDatabase,
|
||||
provider: {
|
||||
getMigrations(): Promise<Record<string, Migration>> {
|
||||
return Promise.resolve(appDatabaseMigrations);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await migrator.migrateToLatest();
|
||||
}
|
||||
|
||||
private async migrateWorkspaceDatabase(
|
||||
database: Kysely<WorkspaceDatabaseSchema>
|
||||
): Promise<void> {
|
||||
this.debug('Migrating workspace database');
|
||||
const migrator = new Migrator({
|
||||
db: database,
|
||||
provider: {
|
||||
getMigrations(): Promise<Record<string, Migration>> {
|
||||
return Promise.resolve(workspaceDatabaseMigrations);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await migrator.migrateToLatest();
|
||||
}
|
||||
|
||||
private buildSqlite = (filename: string): SQLite.Database => {
|
||||
this.debug(`Building sqlite database: ${filename}`);
|
||||
const database = new SQLite(filename);
|
||||
database.pragma('journal_mode = WAL');
|
||||
return database;
|
||||
};
|
||||
}
|
||||
|
||||
export const databaseService = new DatabaseService();
|
||||
@@ -1,401 +0,0 @@
|
||||
import { Migration, sql } from 'kysely';
|
||||
|
||||
const createUsersTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('users')
|
||||
.addColumn('id', 'text', (col) => col.primaryKey().notNull())
|
||||
.addColumn('email', 'text', (col) => col.notNull())
|
||||
.addColumn('name', 'text', (col) => col.notNull())
|
||||
.addColumn('avatar', 'text')
|
||||
.addColumn('custom_name', 'text')
|
||||
.addColumn('custom_avatar', 'text')
|
||||
.addColumn('role', 'text', (col) => col.notNull())
|
||||
.addColumn('storage_limit', 'integer', (col) => col.notNull())
|
||||
.addColumn('max_file_size', 'integer', (col) => col.notNull())
|
||||
.addColumn('status', 'integer', (col) => col.notNull())
|
||||
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||
.addColumn('updated_at', 'text')
|
||||
.addColumn('version', 'integer')
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('users').execute();
|
||||
},
|
||||
};
|
||||
|
||||
const createEntriesTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('entries')
|
||||
.addColumn('id', 'text', (col) => col.primaryKey().notNull())
|
||||
.addColumn('type', 'text', (col) =>
|
||||
col
|
||||
.notNull()
|
||||
.generatedAlwaysAs(sql`json_extract(attributes, '$.type')`)
|
||||
.stored()
|
||||
)
|
||||
.addColumn('parent_id', 'text', (col) =>
|
||||
col
|
||||
.generatedAlwaysAs(sql`json_extract(attributes, '$.parentId')`)
|
||||
.stored()
|
||||
)
|
||||
.addColumn('root_id', 'text', (col) => col.notNull())
|
||||
.addColumn('attributes', 'text', (col) => col.notNull())
|
||||
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||
.addColumn('updated_at', 'text')
|
||||
.addColumn('created_by', 'text', (col) => col.notNull())
|
||||
.addColumn('updated_by', 'text')
|
||||
.addColumn('transaction_id', 'text', (col) => col.notNull())
|
||||
.execute();
|
||||
|
||||
await db.schema
|
||||
.createIndex('entries_parent_id_type_index')
|
||||
.on('entries')
|
||||
.columns(['parent_id', 'type'])
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('entries').execute();
|
||||
},
|
||||
};
|
||||
|
||||
const createEntryTransactionsTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('entry_transactions')
|
||||
.addColumn('id', 'text', (col) => col.notNull().primaryKey())
|
||||
.addColumn('entry_id', 'text', (col) => col.notNull())
|
||||
.addColumn('root_id', 'text', (col) => col.notNull())
|
||||
.addColumn('operation', 'integer', (col) => col.notNull())
|
||||
.addColumn('data', 'blob')
|
||||
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||
.addColumn('created_by', 'text', (col) => col.notNull())
|
||||
.addColumn('server_created_at', 'text')
|
||||
.addColumn('version', 'integer', (col) => col.notNull())
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('entry_transactions').execute();
|
||||
},
|
||||
};
|
||||
|
||||
const createEntryInteractionsTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('entry_interactions')
|
||||
.addColumn('entry_id', 'text', (col) => col.notNull())
|
||||
.addColumn('collaborator_id', 'text', (col) => col.notNull())
|
||||
.addColumn('root_id', 'text', (col) => col.notNull())
|
||||
.addColumn('first_seen_at', 'text')
|
||||
.addColumn('last_seen_at', 'text')
|
||||
.addColumn('first_opened_at', 'text')
|
||||
.addColumn('last_opened_at', 'text')
|
||||
.addColumn('version', 'integer', (col) => col.notNull())
|
||||
.addPrimaryKeyConstraint('entry_interactions_pkey', [
|
||||
'entry_id',
|
||||
'collaborator_id',
|
||||
])
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('entry_interactions').execute();
|
||||
},
|
||||
};
|
||||
|
||||
const createCollaborationsTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('collaborations')
|
||||
.addColumn('entry_id', 'text', (col) => col.notNull().primaryKey())
|
||||
.addColumn('role', 'text')
|
||||
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||
.addColumn('updated_at', 'text')
|
||||
.addColumn('deleted_at', 'text')
|
||||
.addColumn('version', 'integer')
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('collaborations').execute();
|
||||
},
|
||||
};
|
||||
|
||||
const createMessagesTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('messages')
|
||||
.addColumn('id', 'text', (col) => col.notNull().primaryKey())
|
||||
.addColumn('type', 'integer', (col) =>
|
||||
col
|
||||
.notNull()
|
||||
.generatedAlwaysAs(sql`json_extract(attributes, '$.type')`)
|
||||
.stored()
|
||||
)
|
||||
.addColumn('parent_id', 'text', (col) => col.notNull())
|
||||
.addColumn('entry_id', 'text', (col) => col.notNull())
|
||||
.addColumn('root_id', 'text', (col) => col.notNull())
|
||||
.addColumn('attributes', 'text', (col) => col.notNull())
|
||||
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||
.addColumn('created_by', 'text', (col) => col.notNull())
|
||||
.addColumn('updated_at', 'text')
|
||||
.addColumn('updated_by', 'text')
|
||||
.addColumn('deleted_at', 'text')
|
||||
.addColumn('version', 'integer')
|
||||
.execute();
|
||||
|
||||
await db.schema
|
||||
.createIndex('messages_parent_id_index')
|
||||
.on('messages')
|
||||
.columns(['parent_id'])
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('messages').execute();
|
||||
},
|
||||
};
|
||||
|
||||
const createMessageReactionsTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('message_reactions')
|
||||
.addColumn('message_id', 'text', (col) => col.notNull())
|
||||
.addColumn('collaborator_id', 'text', (col) => col.notNull())
|
||||
.addColumn('reaction', 'text', (col) => col.notNull())
|
||||
.addColumn('root_id', 'text', (col) => col.notNull())
|
||||
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||
.addColumn('deleted_at', 'text')
|
||||
.addColumn('version', 'integer', (col) => col.notNull())
|
||||
.addPrimaryKeyConstraint('message_reactions_pkey', [
|
||||
'message_id',
|
||||
'collaborator_id',
|
||||
'reaction',
|
||||
])
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('message_reactions').execute();
|
||||
},
|
||||
};
|
||||
|
||||
const createMessageInteractionsTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('message_interactions')
|
||||
.addColumn('message_id', 'text', (col) => col.notNull())
|
||||
.addColumn('collaborator_id', 'text', (col) => col.notNull())
|
||||
.addColumn('root_id', 'text', (col) => col.notNull())
|
||||
.addColumn('first_seen_at', 'text')
|
||||
.addColumn('last_seen_at', 'text')
|
||||
.addColumn('first_opened_at', 'text')
|
||||
.addColumn('last_opened_at', 'text')
|
||||
.addColumn('version', 'integer', (col) => col.notNull())
|
||||
.addPrimaryKeyConstraint('message_interactions_pkey', [
|
||||
'message_id',
|
||||
'collaborator_id',
|
||||
])
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('message_interactions').execute();
|
||||
},
|
||||
};
|
||||
|
||||
const createFilesTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('files')
|
||||
.addColumn('id', 'text', (col) => col.notNull().primaryKey())
|
||||
.addColumn('type', 'text', (col) => col.notNull())
|
||||
.addColumn('parent_id', 'text', (col) => col.notNull())
|
||||
.addColumn('entry_id', 'text', (col) => col.notNull())
|
||||
.addColumn('root_id', 'text', (col) => col.notNull())
|
||||
.addColumn('name', 'text', (col) => col.notNull())
|
||||
.addColumn('original_name', 'text', (col) => col.notNull())
|
||||
.addColumn('mime_type', 'text', (col) => col.notNull())
|
||||
.addColumn('extension', 'text', (col) => col.notNull())
|
||||
.addColumn('size', 'integer', (col) => col.notNull())
|
||||
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||
.addColumn('created_by', 'text', (col) => col.notNull())
|
||||
.addColumn('updated_at', 'text')
|
||||
.addColumn('updated_by', 'text')
|
||||
.addColumn('deleted_at', 'text')
|
||||
.addColumn('status', 'integer', (col) => col.notNull())
|
||||
.addColumn('version', 'integer')
|
||||
.execute();
|
||||
|
||||
await db.schema
|
||||
.createIndex('files_parent_id_index')
|
||||
.on('files')
|
||||
.columns(['parent_id'])
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('files').execute();
|
||||
},
|
||||
};
|
||||
|
||||
const createFileStatesTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('file_states')
|
||||
.addColumn('file_id', 'text', (col) => col.notNull().primaryKey())
|
||||
.addColumn('download_status', 'integer', (col) => col.notNull())
|
||||
.addColumn('download_progress', 'integer', (col) => col.notNull())
|
||||
.addColumn('download_retries', 'integer', (col) => col.notNull())
|
||||
.addColumn('upload_status', 'integer', (col) => col.notNull())
|
||||
.addColumn('upload_progress', 'integer', (col) => col.notNull())
|
||||
.addColumn('upload_retries', 'integer', (col) => col.notNull())
|
||||
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||
.addColumn('updated_at', 'text')
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('file_states').execute();
|
||||
},
|
||||
};
|
||||
|
||||
const createFileInteractionsTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('file_interactions')
|
||||
.addColumn('file_id', 'text', (col) => col.notNull())
|
||||
.addColumn('collaborator_id', 'text', (col) => col.notNull())
|
||||
.addColumn('root_id', 'text', (col) => col.notNull())
|
||||
.addColumn('seen_at', 'text')
|
||||
.addColumn('first_opened_at', 'text')
|
||||
.addColumn('last_opened_at', 'text')
|
||||
.addColumn('version', 'integer', (col) => col.notNull())
|
||||
.addPrimaryKeyConstraint('file_interactions_pkey', [
|
||||
'file_id',
|
||||
'collaborator_id',
|
||||
])
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('file_interactions').execute();
|
||||
},
|
||||
};
|
||||
|
||||
const createMutationsTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('mutations')
|
||||
.addColumn('id', 'text', (col) => col.notNull().primaryKey())
|
||||
.addColumn('type', 'text', (col) => col.notNull())
|
||||
.addColumn('data', 'text', (col) => col.notNull())
|
||||
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||
.addColumn('retries', 'integer', (col) => col.notNull())
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('mutations').execute();
|
||||
},
|
||||
};
|
||||
|
||||
const createEntryPathsTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('entry_paths')
|
||||
.addColumn('ancestor_id', 'varchar(30)', (col) =>
|
||||
col.notNull().references('entries.id').onDelete('cascade')
|
||||
)
|
||||
.addColumn('descendant_id', 'varchar(30)', (col) =>
|
||||
col.notNull().references('entries.id').onDelete('cascade')
|
||||
)
|
||||
.addColumn('level', 'integer', (col) => col.notNull())
|
||||
.addPrimaryKeyConstraint('entry_paths_pkey', [
|
||||
'ancestor_id',
|
||||
'descendant_id',
|
||||
])
|
||||
.execute();
|
||||
|
||||
await sql`
|
||||
CREATE TRIGGER trg_insert_entry_path
|
||||
AFTER INSERT ON entries
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
-- Insert direct path from the new entry to itself
|
||||
INSERT INTO entry_paths (ancestor_id, descendant_id, level)
|
||||
VALUES (NEW.id, NEW.id, 0);
|
||||
|
||||
-- Insert paths from ancestors to the new entry
|
||||
INSERT INTO entry_paths (ancestor_id, descendant_id, level)
|
||||
SELECT ancestor_id, NEW.id, level + 1
|
||||
FROM entry_paths
|
||||
WHERE descendant_id = NEW.parent_id AND ancestor_id <> NEW.id;
|
||||
END;
|
||||
`.execute(db);
|
||||
|
||||
await sql`
|
||||
CREATE TRIGGER trg_update_entry_path
|
||||
AFTER UPDATE ON entries
|
||||
FOR EACH ROW
|
||||
WHEN OLD.parent_id <> NEW.parent_id
|
||||
BEGIN
|
||||
-- Delete old paths involving the updated entry
|
||||
DELETE FROM entry_paths
|
||||
WHERE descendant_id = NEW.id AND ancestor_id <> NEW.id;
|
||||
|
||||
-- Insert new paths from ancestors to the updated entry
|
||||
INSERT INTO entry_paths (ancestor_id, descendant_id, level)
|
||||
SELECT ancestor_id, NEW.id, level + 1
|
||||
FROM entry_paths
|
||||
WHERE descendant_id = NEW.parent_id AND ancestor_id <> NEW.id;
|
||||
END;
|
||||
`.execute(db);
|
||||
},
|
||||
down: async (db) => {
|
||||
await sql`
|
||||
DROP TRIGGER IF EXISTS trg_insert_entry_path;
|
||||
DROP TRIGGER IF EXISTS trg_update_entry_path;
|
||||
`.execute(db);
|
||||
|
||||
await db.schema.dropTable('entry_paths').execute();
|
||||
},
|
||||
};
|
||||
|
||||
const createTextsTable: Migration = {
|
||||
up: async (db) => {
|
||||
await sql`
|
||||
CREATE VIRTUAL TABLE texts USING fts5(id UNINDEXED, name, text);
|
||||
`.execute(db);
|
||||
},
|
||||
down: async (db) => {
|
||||
await sql`
|
||||
DROP TABLE IF EXISTS texts;
|
||||
`.execute(db);
|
||||
},
|
||||
};
|
||||
|
||||
const createCursorsTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('cursors')
|
||||
.addColumn('key', 'text', (col) => col.notNull().primaryKey())
|
||||
.addColumn('value', 'integer', (col) => col.notNull().defaultTo(0))
|
||||
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||
.addColumn('updated_at', 'text')
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('cursors').execute();
|
||||
},
|
||||
};
|
||||
|
||||
export const workspaceDatabaseMigrations: Record<string, Migration> = {
|
||||
'00001_create_users_table': createUsersTable,
|
||||
'00002_create_entries_table': createEntriesTable,
|
||||
'00003_create_entry_interactions_table': createEntryInteractionsTable,
|
||||
'00004_create_entry_transactions_table': createEntryTransactionsTable,
|
||||
'00005_create_collaborations_table': createCollaborationsTable,
|
||||
'00006_create_messages_table': createMessagesTable,
|
||||
'00007_create_message_reactions_table': createMessageReactionsTable,
|
||||
'00008_create_message_interactions_table': createMessageInteractionsTable,
|
||||
'00009_create_files_table': createFilesTable,
|
||||
'00010_create_file_states_table': createFileStatesTable,
|
||||
'00011_create_file_interactions_table': createFileInteractionsTable,
|
||||
'00012_create_mutations_table': createMutationsTable,
|
||||
'00013_create_entry_paths_table': createEntryPathsTable,
|
||||
'00014_create_texts_table': createTextsTable,
|
||||
'00015_create_cursors_table': createCursorsTable,
|
||||
};
|
||||
2
apps/desktop/src/main/databases/account/index.ts
Normal file
2
apps/desktop/src/main/databases/account/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './schema';
|
||||
export * from './migrations';
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Migration } from 'kysely';
|
||||
|
||||
export const createWorkspacesTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('workspaces')
|
||||
.addColumn('id', 'text', (col) => col.notNull().primaryKey())
|
||||
.addColumn('user_id', 'text', (col) => col.notNull())
|
||||
.addColumn('account_id', 'text', (col) => col.notNull())
|
||||
.addColumn('name', 'text', (col) => col.notNull())
|
||||
.addColumn('description', 'text')
|
||||
.addColumn('avatar', 'text')
|
||||
.addColumn('role', 'text', (col) => col.notNull())
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('workspaces').execute();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Migration } from 'kysely';
|
||||
|
||||
import { createWorkspacesTable } from './00001-create-workspaces-table';
|
||||
|
||||
export const accountDatabaseMigrations: Record<string, Migration> = {
|
||||
'00001-create-workspaces-table': createWorkspacesTable,
|
||||
};
|
||||
20
apps/desktop/src/main/databases/account/schema.ts
Normal file
20
apps/desktop/src/main/databases/account/schema.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { WorkspaceRole } from '@colanode/core';
|
||||
import { ColumnType, Insertable, Selectable, Updateable } from 'kysely';
|
||||
|
||||
interface WorkspaceTable {
|
||||
id: ColumnType<string, string, never>;
|
||||
user_id: ColumnType<string, string, never>;
|
||||
account_id: ColumnType<string, string, never>;
|
||||
name: ColumnType<string, string, string>;
|
||||
description: ColumnType<string | null, string | null, string | null>;
|
||||
avatar: ColumnType<string | null, string | null, string | null>;
|
||||
role: ColumnType<WorkspaceRole, WorkspaceRole, WorkspaceRole>;
|
||||
}
|
||||
|
||||
export type SelectWorkspace = Selectable<WorkspaceTable>;
|
||||
export type CreateWorkspace = Insertable<WorkspaceTable>;
|
||||
export type UpdateWorkspace = Updateable<WorkspaceTable>;
|
||||
|
||||
export interface AccountDatabaseSchema {
|
||||
workspaces: WorkspaceTable;
|
||||
}
|
||||
2
apps/desktop/src/main/databases/app/index.ts
Normal file
2
apps/desktop/src/main/databases/app/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './schema';
|
||||
export * from './migrations';
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Migration } from 'kysely';
|
||||
|
||||
export const createServersTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('servers')
|
||||
.addColumn('domain', 'text', (col) => col.notNull().primaryKey())
|
||||
.addColumn('name', 'text', (col) => col.notNull())
|
||||
.addColumn('avatar', 'text', (col) => col.notNull())
|
||||
.addColumn('attributes', 'text', (col) => col.notNull())
|
||||
.addColumn('version', 'text', (col) => col.notNull())
|
||||
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||
.addColumn('last_synced_at', 'text')
|
||||
.execute();
|
||||
|
||||
await db
|
||||
.insertInto('servers')
|
||||
.values([
|
||||
{
|
||||
domain: 'eu.colanode.com',
|
||||
name: 'Colanode Cloud (EU)',
|
||||
avatar: 'https://colanode.com/assets/flags/eu.svg',
|
||||
attributes: '{}',
|
||||
version: '0.1.0',
|
||||
created_at: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
domain: 'us.colanode.com',
|
||||
name: 'Colanode Cloud (US)',
|
||||
avatar: 'https://colanode.com/assets/flags/us.svg',
|
||||
attributes: '{}',
|
||||
version: '0.1.0',
|
||||
created_at: new Date().toISOString(),
|
||||
},
|
||||
])
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('servers').execute();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Migration } from 'kysely';
|
||||
|
||||
export const createAccountsTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('accounts')
|
||||
.addColumn('id', 'text', (col) => col.notNull().primaryKey())
|
||||
.addColumn('device_id', 'text', (col) => col.notNull())
|
||||
.addColumn('server', 'text', (col) => col.notNull())
|
||||
.addColumn('name', 'text', (col) => col.notNull())
|
||||
.addColumn('email', 'text', (col) => col.notNull())
|
||||
.addColumn('avatar', 'text')
|
||||
.addColumn('token', 'text', (col) => col.notNull())
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('accounts').execute();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Migration } from 'kysely';
|
||||
|
||||
export const createDeletedTokensTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('deleted_tokens')
|
||||
.addColumn('account_id', 'text', (col) => col.notNull())
|
||||
.addColumn('token', 'text', (col) => col.notNull().primaryKey())
|
||||
.addColumn('server', 'text', (col) => col.notNull())
|
||||
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('deleted_tokens').execute();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Migration } from 'kysely';
|
||||
|
||||
export const createMetadataTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('metadata')
|
||||
.addColumn('key', 'text', (col) => col.notNull().primaryKey())
|
||||
.addColumn('value', 'text', (col) => col.notNull())
|
||||
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||
.addColumn('updated_at', 'text')
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('metadata').execute();
|
||||
},
|
||||
};
|
||||
13
apps/desktop/src/main/databases/app/migrations/index.ts
Normal file
13
apps/desktop/src/main/databases/app/migrations/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Migration } from 'kysely';
|
||||
|
||||
import { createServersTable } from './00001-create-servers-table';
|
||||
import { createAccountsTable } from './00002-create-accounts-table';
|
||||
import { createDeletedTokensTable } from './00003-create-deleted-tokens-table';
|
||||
import { createMetadataTable } from './00004-create-metadata-table';
|
||||
|
||||
export const appDatabaseMigrations: Record<string, Migration> = {
|
||||
'00001-create-servers-table': createServersTable,
|
||||
'00002-create-accounts-table': createAccountsTable,
|
||||
'00003-create-deleted-tokens-table': createDeletedTokensTable,
|
||||
'00004-create-metadata-table': createMetadataTable,
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
import { WorkspaceRole } from '@colanode/core';
|
||||
import { ColumnType, Insertable, Selectable, Updateable } from 'kysely';
|
||||
|
||||
interface ServerTable {
|
||||
@@ -29,20 +28,6 @@ export type SelectAccount = Selectable<AccountTable>;
|
||||
export type CreateAccount = Insertable<AccountTable>;
|
||||
export type UpdateAccount = Updateable<AccountTable>;
|
||||
|
||||
interface WorkspaceTable {
|
||||
user_id: ColumnType<string, string, never>;
|
||||
workspace_id: ColumnType<string, string, never>;
|
||||
account_id: ColumnType<string, string, never>;
|
||||
name: ColumnType<string, string, string>;
|
||||
description: ColumnType<string | null, string | null, string | null>;
|
||||
avatar: ColumnType<string | null, string | null, string | null>;
|
||||
role: ColumnType<WorkspaceRole, WorkspaceRole, WorkspaceRole>;
|
||||
}
|
||||
|
||||
export type SelectWorkspace = Selectable<WorkspaceTable>;
|
||||
export type CreateWorkspace = Insertable<WorkspaceTable>;
|
||||
export type UpdateWorkspace = Updateable<WorkspaceTable>;
|
||||
|
||||
interface DeletedTokenTable {
|
||||
token: ColumnType<string, string, never>;
|
||||
account_id: ColumnType<string, string, never>;
|
||||
@@ -64,7 +49,6 @@ export type UpdateMetadata = Updateable<MetadataTable>;
|
||||
export interface AppDatabaseSchema {
|
||||
servers: ServerTable;
|
||||
accounts: AccountTable;
|
||||
workspaces: WorkspaceTable;
|
||||
deleted_tokens: DeletedTokenTable;
|
||||
metadata: MetadataTable;
|
||||
}
|
||||
2
apps/desktop/src/main/databases/workspace/index.ts
Normal file
2
apps/desktop/src/main/databases/workspace/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './schema';
|
||||
export * from './migrations';
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Migration } from 'kysely';
|
||||
|
||||
export const createUsersTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('users')
|
||||
.addColumn('id', 'text', (col) => col.primaryKey().notNull())
|
||||
.addColumn('email', 'text', (col) => col.notNull())
|
||||
.addColumn('name', 'text', (col) => col.notNull())
|
||||
.addColumn('avatar', 'text')
|
||||
.addColumn('custom_name', 'text')
|
||||
.addColumn('custom_avatar', 'text')
|
||||
.addColumn('role', 'text', (col) => col.notNull())
|
||||
.addColumn('storage_limit', 'integer', (col) => col.notNull())
|
||||
.addColumn('max_file_size', 'integer', (col) => col.notNull())
|
||||
.addColumn('status', 'integer', (col) => col.notNull())
|
||||
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||
.addColumn('updated_at', 'text')
|
||||
.addColumn('version', 'integer')
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('users').execute();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Migration, sql } from 'kysely';
|
||||
|
||||
export const createEntriesTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('entries')
|
||||
.addColumn('id', 'text', (col) => col.primaryKey().notNull())
|
||||
.addColumn('type', 'text', (col) =>
|
||||
col
|
||||
.notNull()
|
||||
.generatedAlwaysAs(sql`json_extract(attributes, '$.type')`)
|
||||
.stored()
|
||||
)
|
||||
.addColumn('parent_id', 'text', (col) =>
|
||||
col
|
||||
.generatedAlwaysAs(sql`json_extract(attributes, '$.parentId')`)
|
||||
.stored()
|
||||
)
|
||||
.addColumn('root_id', 'text', (col) => col.notNull())
|
||||
.addColumn('attributes', 'text', (col) => col.notNull())
|
||||
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||
.addColumn('updated_at', 'text')
|
||||
.addColumn('created_by', 'text', (col) => col.notNull())
|
||||
.addColumn('updated_by', 'text')
|
||||
.addColumn('transaction_id', 'text', (col) => col.notNull())
|
||||
.execute();
|
||||
|
||||
await db.schema
|
||||
.createIndex('entries_parent_id_type_index')
|
||||
.on('entries')
|
||||
.columns(['parent_id', 'type'])
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('entries').execute();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Migration } from 'kysely';
|
||||
|
||||
export const createEntryTransactionsTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('entry_transactions')
|
||||
.addColumn('id', 'text', (col) => col.notNull().primaryKey())
|
||||
.addColumn('entry_id', 'text', (col) => col.notNull())
|
||||
.addColumn('root_id', 'text', (col) => col.notNull())
|
||||
.addColumn('operation', 'integer', (col) => col.notNull())
|
||||
.addColumn('data', 'blob')
|
||||
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||
.addColumn('created_by', 'text', (col) => col.notNull())
|
||||
.addColumn('server_created_at', 'text')
|
||||
.addColumn('version', 'integer', (col) => col.notNull())
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('entry_transactions').execute();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Migration } from 'kysely';
|
||||
|
||||
export const createEntryInteractionsTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('entry_interactions')
|
||||
.addColumn('entry_id', 'text', (col) => col.notNull())
|
||||
.addColumn('collaborator_id', 'text', (col) => col.notNull())
|
||||
.addColumn('root_id', 'text', (col) => col.notNull())
|
||||
.addColumn('first_seen_at', 'text')
|
||||
.addColumn('last_seen_at', 'text')
|
||||
.addColumn('first_opened_at', 'text')
|
||||
.addColumn('last_opened_at', 'text')
|
||||
.addColumn('version', 'integer', (col) => col.notNull())
|
||||
.addPrimaryKeyConstraint('entry_interactions_pkey', [
|
||||
'entry_id',
|
||||
'collaborator_id',
|
||||
])
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('entry_interactions').execute();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Migration } from 'kysely';
|
||||
|
||||
export const createCollaborationsTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('collaborations')
|
||||
.addColumn('entry_id', 'text', (col) => col.notNull().primaryKey())
|
||||
.addColumn('role', 'text')
|
||||
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||
.addColumn('updated_at', 'text')
|
||||
.addColumn('deleted_at', 'text')
|
||||
.addColumn('version', 'integer')
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('collaborations').execute();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Migration, sql } from 'kysely';
|
||||
|
||||
export const createMessagesTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('messages')
|
||||
.addColumn('id', 'text', (col) => col.notNull().primaryKey())
|
||||
.addColumn('type', 'integer', (col) =>
|
||||
col
|
||||
.notNull()
|
||||
.generatedAlwaysAs(sql`json_extract(attributes, '$.type')`)
|
||||
.stored()
|
||||
)
|
||||
.addColumn('parent_id', 'text', (col) => col.notNull())
|
||||
.addColumn('entry_id', 'text', (col) => col.notNull())
|
||||
.addColumn('root_id', 'text', (col) => col.notNull())
|
||||
.addColumn('attributes', 'text', (col) => col.notNull())
|
||||
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||
.addColumn('created_by', 'text', (col) => col.notNull())
|
||||
.addColumn('updated_at', 'text')
|
||||
.addColumn('updated_by', 'text')
|
||||
.addColumn('deleted_at', 'text')
|
||||
.addColumn('version', 'integer')
|
||||
.execute();
|
||||
|
||||
await db.schema
|
||||
.createIndex('messages_parent_id_index')
|
||||
.on('messages')
|
||||
.columns(['parent_id'])
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('messages').execute();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Migration } from 'kysely';
|
||||
|
||||
export const createMessageReactionsTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('message_reactions')
|
||||
.addColumn('message_id', 'text', (col) => col.notNull())
|
||||
.addColumn('collaborator_id', 'text', (col) => col.notNull())
|
||||
.addColumn('reaction', 'text', (col) => col.notNull())
|
||||
.addColumn('root_id', 'text', (col) => col.notNull())
|
||||
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||
.addColumn('deleted_at', 'text')
|
||||
.addColumn('version', 'integer', (col) => col.notNull())
|
||||
.addPrimaryKeyConstraint('message_reactions_pkey', [
|
||||
'message_id',
|
||||
'collaborator_id',
|
||||
'reaction',
|
||||
])
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('message_reactions').execute();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Migration } from 'kysely';
|
||||
|
||||
export const createMessageInteractionsTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('message_interactions')
|
||||
.addColumn('message_id', 'text', (col) => col.notNull())
|
||||
.addColumn('collaborator_id', 'text', (col) => col.notNull())
|
||||
.addColumn('root_id', 'text', (col) => col.notNull())
|
||||
.addColumn('first_seen_at', 'text')
|
||||
.addColumn('last_seen_at', 'text')
|
||||
.addColumn('first_opened_at', 'text')
|
||||
.addColumn('last_opened_at', 'text')
|
||||
.addColumn('version', 'integer', (col) => col.notNull())
|
||||
.addPrimaryKeyConstraint('message_interactions_pkey', [
|
||||
'message_id',
|
||||
'collaborator_id',
|
||||
])
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('message_interactions').execute();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Migration } from 'kysely';
|
||||
|
||||
export const createFilesTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('files')
|
||||
.addColumn('id', 'text', (col) => col.notNull().primaryKey())
|
||||
.addColumn('type', 'text', (col) => col.notNull())
|
||||
.addColumn('parent_id', 'text', (col) => col.notNull())
|
||||
.addColumn('entry_id', 'text', (col) => col.notNull())
|
||||
.addColumn('root_id', 'text', (col) => col.notNull())
|
||||
.addColumn('name', 'text', (col) => col.notNull())
|
||||
.addColumn('original_name', 'text', (col) => col.notNull())
|
||||
.addColumn('mime_type', 'text', (col) => col.notNull())
|
||||
.addColumn('extension', 'text', (col) => col.notNull())
|
||||
.addColumn('size', 'integer', (col) => col.notNull())
|
||||
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||
.addColumn('created_by', 'text', (col) => col.notNull())
|
||||
.addColumn('updated_at', 'text')
|
||||
.addColumn('updated_by', 'text')
|
||||
.addColumn('deleted_at', 'text')
|
||||
.addColumn('status', 'integer', (col) => col.notNull())
|
||||
.addColumn('version', 'integer')
|
||||
.execute();
|
||||
|
||||
await db.schema
|
||||
.createIndex('files_parent_id_index')
|
||||
.on('files')
|
||||
.columns(['parent_id'])
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('files').execute();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Migration } from 'kysely';
|
||||
|
||||
export const createFileStatesTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('file_states')
|
||||
.addColumn('file_id', 'text', (col) => col.notNull().primaryKey())
|
||||
.addColumn('download_status', 'integer', (col) => col.notNull())
|
||||
.addColumn('download_progress', 'integer', (col) => col.notNull())
|
||||
.addColumn('download_retries', 'integer', (col) => col.notNull())
|
||||
.addColumn('upload_status', 'integer', (col) => col.notNull())
|
||||
.addColumn('upload_progress', 'integer', (col) => col.notNull())
|
||||
.addColumn('upload_retries', 'integer', (col) => col.notNull())
|
||||
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||
.addColumn('updated_at', 'text')
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('file_states').execute();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Migration } from 'kysely';
|
||||
|
||||
export const createFileInteractionsTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('file_interactions')
|
||||
.addColumn('file_id', 'text', (col) => col.notNull())
|
||||
.addColumn('collaborator_id', 'text', (col) => col.notNull())
|
||||
.addColumn('root_id', 'text', (col) => col.notNull())
|
||||
.addColumn('seen_at', 'text')
|
||||
.addColumn('first_opened_at', 'text')
|
||||
.addColumn('last_opened_at', 'text')
|
||||
.addColumn('version', 'integer', (col) => col.notNull())
|
||||
.addPrimaryKeyConstraint('file_interactions_pkey', [
|
||||
'file_id',
|
||||
'collaborator_id',
|
||||
])
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('file_interactions').execute();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Migration } from 'kysely';
|
||||
|
||||
export const createMutationsTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('mutations')
|
||||
.addColumn('id', 'text', (col) => col.notNull().primaryKey())
|
||||
.addColumn('type', 'text', (col) => col.notNull())
|
||||
.addColumn('data', 'text', (col) => col.notNull())
|
||||
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||
.addColumn('retries', 'integer', (col) => col.notNull())
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('mutations').execute();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Migration, sql } from 'kysely';
|
||||
|
||||
export const createEntryPathsTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('entry_paths')
|
||||
.addColumn('ancestor_id', 'varchar(30)', (col) =>
|
||||
col.notNull().references('entries.id').onDelete('cascade')
|
||||
)
|
||||
.addColumn('descendant_id', 'varchar(30)', (col) =>
|
||||
col.notNull().references('entries.id').onDelete('cascade')
|
||||
)
|
||||
.addColumn('level', 'integer', (col) => col.notNull())
|
||||
.addPrimaryKeyConstraint('entry_paths_pkey', [
|
||||
'ancestor_id',
|
||||
'descendant_id',
|
||||
])
|
||||
.execute();
|
||||
|
||||
await sql`
|
||||
CREATE TRIGGER trg_insert_entry_path
|
||||
AFTER INSERT ON entries
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
-- Insert direct path from the new entry to itself
|
||||
INSERT INTO entry_paths (ancestor_id, descendant_id, level)
|
||||
VALUES (NEW.id, NEW.id, 0);
|
||||
|
||||
-- Insert paths from ancestors to the new entry
|
||||
INSERT INTO entry_paths (ancestor_id, descendant_id, level)
|
||||
SELECT ancestor_id, NEW.id, level + 1
|
||||
FROM entry_paths
|
||||
WHERE descendant_id = NEW.parent_id AND ancestor_id <> NEW.id;
|
||||
END;
|
||||
`.execute(db);
|
||||
|
||||
await sql`
|
||||
CREATE TRIGGER trg_update_entry_path
|
||||
AFTER UPDATE ON entries
|
||||
FOR EACH ROW
|
||||
WHEN OLD.parent_id <> NEW.parent_id
|
||||
BEGIN
|
||||
-- Delete old paths involving the updated entry
|
||||
DELETE FROM entry_paths
|
||||
WHERE descendant_id = NEW.id AND ancestor_id <> NEW.id;
|
||||
|
||||
-- Insert new paths from ancestors to the updated entry
|
||||
INSERT INTO entry_paths (ancestor_id, descendant_id, level)
|
||||
SELECT ancestor_id, NEW.id, level + 1
|
||||
FROM entry_paths
|
||||
WHERE descendant_id = NEW.parent_id AND ancestor_id <> NEW.id;
|
||||
END;
|
||||
`.execute(db);
|
||||
},
|
||||
down: async (db) => {
|
||||
await sql`
|
||||
DROP TRIGGER IF EXISTS trg_insert_entry_path;
|
||||
DROP TRIGGER IF EXISTS trg_update_entry_path;
|
||||
`.execute(db);
|
||||
|
||||
await db.schema.dropTable('entry_paths').execute();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Migration, sql } from 'kysely';
|
||||
|
||||
export const createTextsTable: Migration = {
|
||||
up: async (db) => {
|
||||
await sql`
|
||||
CREATE VIRTUAL TABLE texts USING fts5(id UNINDEXED, name, text);
|
||||
`.execute(db);
|
||||
},
|
||||
down: async (db) => {
|
||||
await sql`
|
||||
DROP TABLE IF EXISTS texts;
|
||||
`.execute(db);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Migration } from 'kysely';
|
||||
|
||||
export const createCursorsTable: Migration = {
|
||||
up: async (db) => {
|
||||
await db.schema
|
||||
.createTable('cursors')
|
||||
.addColumn('key', 'text', (col) => col.notNull().primaryKey())
|
||||
.addColumn('value', 'integer', (col) => col.notNull().defaultTo(0))
|
||||
.addColumn('created_at', 'text', (col) => col.notNull())
|
||||
.addColumn('updated_at', 'text')
|
||||
.execute();
|
||||
},
|
||||
down: async (db) => {
|
||||
await db.schema.dropTable('cursors').execute();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Migration } from 'kysely';
|
||||
|
||||
import { createUsersTable } from './00001-create-users-table';
|
||||
import { createEntriesTable } from './00002-create-entries-table';
|
||||
import { createEntryInteractionsTable } from './00004-create-entry-interactions-table';
|
||||
import { createEntryTransactionsTable } from './00003-create-entry-transactions-table';
|
||||
import { createCollaborationsTable } from './00005-create-collaborations-table';
|
||||
import { createMessagesTable } from './00006-create-messages-table';
|
||||
import { createMessageReactionsTable } from './00007-create-message-reactions-table';
|
||||
import { createMessageInteractionsTable } from './00008-create-message-interactions-table';
|
||||
import { createFilesTable } from './00009-create-files-table';
|
||||
import { createFileStatesTable } from './00010-create-file-states-table';
|
||||
import { createFileInteractionsTable } from './00011-create-file-interactions-table';
|
||||
import { createMutationsTable } from './00012-create-mutations-table';
|
||||
import { createEntryPathsTable } from './00013-create-entry-paths-table';
|
||||
import { createTextsTable } from './00014-create-texts-table';
|
||||
import { createCursorsTable } from './00015-create-cursors-table';
|
||||
|
||||
export const workspaceDatabaseMigrations: Record<string, Migration> = {
|
||||
'00001-create-users-table': createUsersTable,
|
||||
'00002-create-entries-table': createEntriesTable,
|
||||
'00003-create-entry-transactions-table': createEntryTransactionsTable,
|
||||
'00004-create-entry-interactions-table': createEntryInteractionsTable,
|
||||
'00005-create-collaborations-table': createCollaborationsTable,
|
||||
'00006-create-messages-table': createMessagesTable,
|
||||
'00007-create-message-reactions-table': createMessageReactionsTable,
|
||||
'00008-create-message-interactions-table': createMessageInteractionsTable,
|
||||
'00009-create-files-table': createFilesTable,
|
||||
'00010-create-file-states-table': createFileStatesTable,
|
||||
'00011-create-file-interactions-table': createFileInteractionsTable,
|
||||
'00012-create-mutations-table': createMutationsTable,
|
||||
'00013-create-entry-paths-table': createEntryPathsTable,
|
||||
'00014-create-texts-table': createTextsTable,
|
||||
'00015-create-cursors-table': createCursorsTable,
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
import { fileService } from '@/main/services/file-service';
|
||||
import { JobHandler } from '@/main/jobs';
|
||||
|
||||
export type CleanDeletedFilesInput = {
|
||||
type: 'clean_deleted_files';
|
||||
userId: string;
|
||||
};
|
||||
|
||||
declare module '@/main/jobs' {
|
||||
interface JobMap {
|
||||
clean_deleted_files: {
|
||||
input: CleanDeletedFilesInput;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class CleanDeletedFilesJobHandler
|
||||
implements JobHandler<CleanDeletedFilesInput>
|
||||
{
|
||||
public triggerDebounce = 1000;
|
||||
public interval = 1000 * 60 * 10;
|
||||
|
||||
public async handleJob(input: CleanDeletedFilesInput) {
|
||||
await fileService.cleanDeletedFiles(input.userId);
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import { fileService } from '@/main/services/file-service';
|
||||
import { JobHandler } from '@/main/jobs';
|
||||
|
||||
export type CleanTempFilesInput = {
|
||||
type: 'clean_temp_files';
|
||||
userId: string;
|
||||
};
|
||||
|
||||
declare module '@/main/jobs' {
|
||||
interface JobMap {
|
||||
clean_temp_files: {
|
||||
input: CleanTempFilesInput;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class CleanTempFilesJobHandler
|
||||
implements JobHandler<CleanTempFilesInput>
|
||||
{
|
||||
public triggerDebounce = 1000;
|
||||
public interval = 1000 * 60 * 30;
|
||||
|
||||
public async handleJob(input: CleanTempFilesInput) {
|
||||
await fileService.cleanTempFiles(input.userId);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import { createDebugger } from '@colanode/core';
|
||||
|
||||
import { databaseService } from '@/main/data/database-service';
|
||||
import { socketService } from '@/main/services/socket-service';
|
||||
import { JobHandler } from '@/main/jobs';
|
||||
|
||||
export type ConnectSocketInput = {
|
||||
type: 'connect_socket';
|
||||
accountId: string;
|
||||
};
|
||||
|
||||
declare module '@/main/jobs' {
|
||||
interface JobMap {
|
||||
connect_socket: {
|
||||
input: ConnectSocketInput;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class ConnectSocketJobHandler implements JobHandler<ConnectSocketInput> {
|
||||
public triggerDebounce = 0;
|
||||
public interval = 1000 * 30;
|
||||
|
||||
private readonly debug = createDebugger('desktop:job:connect-socket');
|
||||
|
||||
public async handleJob(input: ConnectSocketInput) {
|
||||
const account = await databaseService.appDatabase
|
||||
.selectFrom('accounts')
|
||||
.selectAll()
|
||||
.where('id', '=', input.accountId)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!account) {
|
||||
this.debug(`Account ${input.accountId} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.debug(`Checking connection to socket for account ${account.email}`);
|
||||
socketService.checkConnection(account);
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { fileService } from '@/main/services/file-service';
|
||||
import { JobHandler } from '@/main/jobs';
|
||||
|
||||
export type DownloadFilesInput = {
|
||||
type: 'download_files';
|
||||
userId: string;
|
||||
};
|
||||
|
||||
declare module '@/main/jobs' {
|
||||
interface JobMap {
|
||||
download_files: {
|
||||
input: DownloadFilesInput;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class DownloadFilesJobHandler implements JobHandler<DownloadFilesInput> {
|
||||
public triggerDebounce = 0;
|
||||
public interval = 1000 * 60;
|
||||
|
||||
public async handleJob(input: DownloadFilesInput) {
|
||||
await fileService.downloadFiles(input.userId);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
export interface JobMap {}
|
||||
|
||||
export type JobInput = JobMap[keyof JobMap]['input'];
|
||||
|
||||
export interface JobHandler<T extends JobInput> {
|
||||
triggerDebounce: number;
|
||||
interval: number;
|
||||
handleJob: (input: T) => Promise<void>;
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import { syncService } from '@/main/services/sync-service';
|
||||
import { JobHandler } from '@/main/jobs';
|
||||
|
||||
export type InitSynchronizersInput = {
|
||||
type: 'init_synchronizers';
|
||||
userId: string;
|
||||
};
|
||||
|
||||
declare module '@/main/jobs' {
|
||||
interface JobMap {
|
||||
init_synchronizers: {
|
||||
input: InitSynchronizersInput;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class InitSynchronizersJobHandler
|
||||
implements JobHandler<InitSynchronizersInput>
|
||||
{
|
||||
public triggerDebounce = 100;
|
||||
public interval = 1000 * 60;
|
||||
|
||||
public async handleJob(input: InitSynchronizersInput) {
|
||||
syncService.initSynchronizers(input.userId);
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
import { createDebugger } from '@colanode/core';
|
||||
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { fileService } from '@/main/services/file-service';
|
||||
import { messageService } from '@/main/services/message-service';
|
||||
import { databaseService } from '@/main/data/database-service';
|
||||
import { JobHandler } from '@/main/jobs';
|
||||
import { mapMutation } from '@/main/utils';
|
||||
|
||||
export type RevertInvalidMutationsInput = {
|
||||
type: 'revert_invalid_mutations';
|
||||
userId: string;
|
||||
};
|
||||
|
||||
declare module '@/main/jobs' {
|
||||
interface JobMap {
|
||||
revert_invalid_mutations: {
|
||||
input: RevertInvalidMutationsInput;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class RevertInvalidMutationsJobHandler
|
||||
implements JobHandler<RevertInvalidMutationsInput>
|
||||
{
|
||||
public triggerDebounce = 100;
|
||||
public interval = 1000 * 60 * 5;
|
||||
|
||||
private readonly debug = createDebugger(
|
||||
'desktop:job:revert-invalid-mutations'
|
||||
);
|
||||
|
||||
public async handleJob(input: RevertInvalidMutationsInput) {
|
||||
this.debug(`Reverting invalid mutations for user ${input.userId}`);
|
||||
|
||||
const workspaceDatabase = await databaseService.getWorkspaceDatabase(
|
||||
input.userId
|
||||
);
|
||||
|
||||
const invalidMutations = await workspaceDatabase
|
||||
.selectFrom('mutations')
|
||||
.selectAll()
|
||||
.where('retries', '>=', 10)
|
||||
.execute();
|
||||
|
||||
if (invalidMutations.length === 0) {
|
||||
this.debug(
|
||||
`No invalid mutations found for user ${input.userId}, skipping`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const mutationRow of invalidMutations) {
|
||||
const mutation = mapMutation(mutationRow);
|
||||
|
||||
if (mutation.type === 'create_file') {
|
||||
await fileService.revertFileCreation(input.userId, mutation.id);
|
||||
} else if (mutation.type === 'delete_file') {
|
||||
await fileService.revertFileDeletion(input.userId, mutation.id);
|
||||
} else if (mutation.type === 'apply_create_transaction') {
|
||||
await entryService.revertCreateTransaction(input.userId, mutation.data);
|
||||
} else if (mutation.type === 'apply_update_transaction') {
|
||||
await entryService.revertUpdateTransaction(input.userId, mutation.data);
|
||||
} else if (mutation.type === 'apply_delete_transaction') {
|
||||
await entryService.revertDeleteTransaction(input.userId, mutation.data);
|
||||
} else if (mutation.type === 'create_message') {
|
||||
await messageService.revertMessageCreation(
|
||||
input.userId,
|
||||
mutation.data.id
|
||||
);
|
||||
} else if (mutation.type === 'delete_message') {
|
||||
await messageService.revertMessageDeletion(
|
||||
input.userId,
|
||||
mutation.data.id
|
||||
);
|
||||
} else if (mutation.type === 'create_message_reaction') {
|
||||
await messageService.revertMessageReactionCreation(
|
||||
input.userId,
|
||||
mutation.data
|
||||
);
|
||||
} else if (mutation.type === 'delete_message_reaction') {
|
||||
await messageService.revertMessageReactionDeletion(
|
||||
input.userId,
|
||||
mutation.data
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mutationIds = invalidMutations.map((m) => m.id);
|
||||
|
||||
await workspaceDatabase
|
||||
.deleteFrom('mutations')
|
||||
.where('id', 'in', mutationIds)
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { accountService } from '@/main/services/account-service';
|
||||
import { JobHandler } from '@/main/jobs';
|
||||
|
||||
export type SyncAccountInput = {
|
||||
type: 'sync_account';
|
||||
accountId: string;
|
||||
};
|
||||
|
||||
declare module '@/main/jobs' {
|
||||
interface JobMap {
|
||||
sync_account: {
|
||||
input: SyncAccountInput;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class SyncAccountJobHandler implements JobHandler<SyncAccountInput> {
|
||||
public triggerDebounce = 0;
|
||||
public interval = 1000 * 60;
|
||||
|
||||
public async handleJob(input: SyncAccountInput) {
|
||||
await accountService.syncAccount(input.accountId);
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
import { ApiErrorCode, createDebugger } from '@colanode/core';
|
||||
|
||||
import { serverService } from '@/main/services/server-service';
|
||||
import { databaseService } from '@/main/data/database-service';
|
||||
import { JobHandler } from '@/main/jobs';
|
||||
import { httpClient } from '@/shared/lib/http-client';
|
||||
import { parseApiError } from '@/shared/lib/axios';
|
||||
|
||||
export type SyncDeletedTokensInput = {
|
||||
type: 'sync_deleted_tokens';
|
||||
};
|
||||
|
||||
declare module '@/main/jobs' {
|
||||
interface JobMap {
|
||||
sync_deleted_tokens: {
|
||||
input: SyncDeletedTokensInput;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class SyncDeletedTokensJobHandler
|
||||
implements JobHandler<SyncDeletedTokensInput>
|
||||
{
|
||||
public triggerDebounce = 100;
|
||||
public interval = 1000 * 60 * 5;
|
||||
|
||||
private readonly debug = createDebugger('desktop:job:sync-deleted-tokens');
|
||||
|
||||
public async handleJob(_: SyncDeletedTokensInput) {
|
||||
this.debug('Syncing deleted tokens');
|
||||
|
||||
const deletedTokens = await databaseService.appDatabase
|
||||
.selectFrom('deleted_tokens')
|
||||
.innerJoin('servers', 'deleted_tokens.server', 'servers.domain')
|
||||
.select([
|
||||
'deleted_tokens.token',
|
||||
'deleted_tokens.account_id',
|
||||
'servers.domain',
|
||||
'servers.attributes',
|
||||
])
|
||||
.execute();
|
||||
|
||||
if (deletedTokens.length === 0) {
|
||||
this.debug('No deleted tokens found');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const deletedToken of deletedTokens) {
|
||||
if (!serverService.isAvailable(deletedToken.domain)) {
|
||||
this.debug(
|
||||
`Server ${deletedToken.domain} is not available for logging out account ${deletedToken.account_id}`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const { status } = await httpClient.delete(`/v1/accounts/logout`, {
|
||||
domain: deletedToken.domain,
|
||||
token: deletedToken.token,
|
||||
});
|
||||
|
||||
this.debug(`Deleted token logout response status code: ${status}`);
|
||||
|
||||
if (status !== 200) {
|
||||
return;
|
||||
}
|
||||
|
||||
await databaseService.appDatabase
|
||||
.deleteFrom('deleted_tokens')
|
||||
.where('token', '=', deletedToken.token)
|
||||
.where('account_id', '=', deletedToken.account_id)
|
||||
.execute();
|
||||
|
||||
this.debug(
|
||||
`Logged out account ${deletedToken.account_id} from server ${deletedToken.domain}`
|
||||
);
|
||||
} catch (error) {
|
||||
const parsedError = parseApiError(error);
|
||||
if (
|
||||
parsedError.code === ApiErrorCode.TokenInvalid ||
|
||||
parsedError.code === ApiErrorCode.AccountNotFound ||
|
||||
parsedError.code === ApiErrorCode.DeviceNotFound
|
||||
) {
|
||||
this.debug(
|
||||
`Account ${deletedToken.account_id} is already logged out, skipping...`
|
||||
);
|
||||
|
||||
await databaseService.appDatabase
|
||||
.deleteFrom('deleted_tokens')
|
||||
.where('token', '=', deletedToken.token)
|
||||
.where('account_id', '=', deletedToken.account_id)
|
||||
.execute();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
this.debug(
|
||||
`Failed to logout account ${deletedToken.account_id} from server ${deletedToken.domain}`,
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { serverService } from '@/main/services/server-service';
|
||||
import { JobHandler } from '@/main/jobs';
|
||||
|
||||
export type SyncServersInput = {
|
||||
type: 'sync_servers';
|
||||
};
|
||||
|
||||
declare module '@/main/jobs' {
|
||||
interface JobMap {
|
||||
sync_servers: {
|
||||
input: SyncServersInput;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class SyncServersJobHandler implements JobHandler<SyncServersInput> {
|
||||
public triggerDebounce = 0;
|
||||
public interval = 1000 * 60;
|
||||
|
||||
public async handleJob(_: SyncServersInput) {
|
||||
await serverService.syncServers();
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { fileService } from '@/main/services/file-service';
|
||||
import { JobHandler } from '@/main/jobs';
|
||||
|
||||
export type UploadFilesInput = {
|
||||
type: 'upload_files';
|
||||
userId: string;
|
||||
};
|
||||
|
||||
declare module '@/main/jobs' {
|
||||
interface JobMap {
|
||||
upload_files: {
|
||||
input: UploadFilesInput;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class UploadFilesJobHandler implements JobHandler<UploadFilesInput> {
|
||||
public triggerDebounce = 0;
|
||||
public interval = 1000 * 60;
|
||||
|
||||
public async handleJob(input: UploadFilesInput) {
|
||||
await fileService.uploadFiles(input.userId);
|
||||
}
|
||||
}
|
||||
24
apps/desktop/src/main/lib/assets.ts
Normal file
24
apps/desktop/src/main/lib/assets.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { getAssetsSourcePath } from '@/main/utils';
|
||||
import { EmojiData } from '@/shared/types/emojis';
|
||||
import { IconData } from '@/shared/types/icons';
|
||||
|
||||
export const getEmojiData = (): EmojiData => {
|
||||
const emojisMetadataPath = path.join(
|
||||
getAssetsSourcePath(),
|
||||
'emojis',
|
||||
'emojis.json'
|
||||
);
|
||||
return JSON.parse(fs.readFileSync(emojisMetadataPath, 'utf8'));
|
||||
};
|
||||
|
||||
export const getIconData = (): IconData => {
|
||||
const iconsMetadataPath = path.join(
|
||||
getAssetsSourcePath(),
|
||||
'icons',
|
||||
'icons.json'
|
||||
);
|
||||
return JSON.parse(fs.readFileSync(iconsMetadataPath, 'utf8'));
|
||||
};
|
||||
102
apps/desktop/src/main/lib/protocols.ts
Normal file
102
apps/desktop/src/main/lib/protocols.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { net } from 'electron';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import {
|
||||
getAccountAvatarsDirectoryPath,
|
||||
getAssetsSourcePath,
|
||||
getWorkspaceFilesDirectoryPath,
|
||||
} from '@/main/utils';
|
||||
import { appService } from '@/main/services/app-service';
|
||||
|
||||
export const handleAssetRequest = (request: Request): Promise<Response> => {
|
||||
const url = request.url.replace('asset://', '');
|
||||
const assetPath = path.join(getAssetsSourcePath(), url);
|
||||
const localFileUrl = `file://${assetPath}`;
|
||||
return net.fetch(localFileUrl);
|
||||
};
|
||||
|
||||
export const handleAvatarRequest = async (
|
||||
request: Request
|
||||
): Promise<Response> => {
|
||||
const url = request.url.replace('avatar://', '');
|
||||
const [accountId, avatarId] = url.split('/');
|
||||
if (!accountId || !avatarId) {
|
||||
return new Response(null, { status: 400 });
|
||||
}
|
||||
|
||||
const avatarsDir = getAccountAvatarsDirectoryPath(accountId);
|
||||
const avatarPath = path.join(avatarsDir, `${avatarId}.jpeg`);
|
||||
const avatarLocalUrl = `file://${avatarPath}`;
|
||||
|
||||
// Check if the avatar file already exists
|
||||
if (fs.existsSync(avatarPath)) {
|
||||
return net.fetch(avatarLocalUrl);
|
||||
}
|
||||
|
||||
// Download the avatar file if it doesn't exist
|
||||
const account = appService.getAccount(accountId);
|
||||
|
||||
if (!account) {
|
||||
return new Response(null, { status: 404 });
|
||||
}
|
||||
|
||||
const response = await account.client.get<NodeJS.ReadableStream>(
|
||||
`/v1/avatars/${avatarId}`,
|
||||
{
|
||||
responseType: 'stream',
|
||||
}
|
||||
);
|
||||
|
||||
if (response.status !== 200 || !response.data) {
|
||||
return new Response(null, { status: 404 });
|
||||
}
|
||||
|
||||
if (!fs.existsSync(avatarsDir)) {
|
||||
fs.mkdirSync(avatarsDir, { recursive: true });
|
||||
}
|
||||
|
||||
const fileStream = fs.createWriteStream(avatarPath);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
response.data.pipe(fileStream);
|
||||
|
||||
fileStream.on('finish', async () => {
|
||||
resolve(net.fetch(avatarLocalUrl));
|
||||
});
|
||||
|
||||
fileStream.on('error', (err) => {
|
||||
reject(new Response(null, { status: 500, statusText: err.message }));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const handleFilePreviewRequest = async (
|
||||
request: Request
|
||||
): Promise<Response> => {
|
||||
const url = request.url.replace('local-file-preview://', 'file://');
|
||||
return net.fetch(url);
|
||||
};
|
||||
|
||||
export const handleFileRequest = async (
|
||||
request: Request
|
||||
): Promise<Response> => {
|
||||
const url = request.url.replace('local-file://', '');
|
||||
const [accountId, workspaceId, file] = url.split('/');
|
||||
if (!accountId || !workspaceId || !file) {
|
||||
return new Response(null, { status: 400 });
|
||||
}
|
||||
|
||||
const workspaceFilesDir = getWorkspaceFilesDirectoryPath(
|
||||
accountId,
|
||||
workspaceId
|
||||
);
|
||||
const filePath = path.join(workspaceFilesDir, file);
|
||||
|
||||
if (fs.existsSync(filePath)) {
|
||||
const fileUrl = `file://${filePath}`;
|
||||
return net.fetch(fileUrl);
|
||||
}
|
||||
|
||||
return new Response(null, { status: 404 });
|
||||
};
|
||||
@@ -1,14 +1,29 @@
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { createDebugger } from '@colanode/core';
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
import { mutationHandlerMap } from '@/main/mutations';
|
||||
import {
|
||||
MutationHandler,
|
||||
CommandHandler,
|
||||
QueryHandler,
|
||||
SubscribedQuery,
|
||||
} from '@/main/types';
|
||||
import {
|
||||
MutationError,
|
||||
MutationErrorCode,
|
||||
MutationInput,
|
||||
MutationResult,
|
||||
} from '@/shared/mutations';
|
||||
import { commandHandlerMap } from '@/main/commands';
|
||||
import { CommandInput, CommandMap } from '@/shared/commands';
|
||||
import { queryHandlerMap } from '@/main/queries';
|
||||
import { QueryHandler, SubscribedQuery } from '@/main/types';
|
||||
import { eventBus } from '@/shared/lib/event-bus';
|
||||
import { QueryInput, QueryMap } from '@/shared/queries';
|
||||
import { Event } from '@/shared/types/events';
|
||||
|
||||
class QueryService {
|
||||
private readonly debug = createDebugger('desktop:service:query');
|
||||
class Mediator {
|
||||
private readonly debug = createDebugger('desktop:mediator');
|
||||
|
||||
private readonly subscribedQueries: Map<string, SubscribedQuery<QueryInput>> =
|
||||
new Map();
|
||||
|
||||
@@ -127,6 +142,60 @@ class QueryService {
|
||||
this.processEventsQueue();
|
||||
}
|
||||
}
|
||||
|
||||
public async executeMutation<T extends MutationInput>(
|
||||
input: T
|
||||
): Promise<MutationResult<T>> {
|
||||
const handler = mutationHandlerMap[
|
||||
input.type
|
||||
] as unknown as MutationHandler<T>;
|
||||
|
||||
this.debug(`Executing mutation: ${input.type}`);
|
||||
|
||||
try {
|
||||
if (!handler) {
|
||||
throw new Error(`No handler found for mutation type: ${input.type}`);
|
||||
}
|
||||
|
||||
const output = await handler.handleMutation(input);
|
||||
return { success: true, output };
|
||||
} catch (error) {
|
||||
this.debug(`Error executing mutation: ${input.type}`, error);
|
||||
if (error instanceof MutationError) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: error.code,
|
||||
message: error.message,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: MutationErrorCode.Unknown,
|
||||
message: 'Something went wrong trying to execute the mutation.',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public async executeCommand<T extends CommandInput>(
|
||||
input: T
|
||||
): Promise<CommandMap[T['type']]['output']> {
|
||||
this.debug(`Executing command: ${input.type}`);
|
||||
|
||||
const handler = commandHandlerMap[
|
||||
input.type
|
||||
] as unknown as CommandHandler<T>;
|
||||
|
||||
if (!handler) {
|
||||
throw new Error(`No handler found for command type: ${input.type}`);
|
||||
}
|
||||
|
||||
return handler.handleCommand(input);
|
||||
}
|
||||
}
|
||||
|
||||
export const queryService = new QueryService();
|
||||
export const mediator = new Mediator();
|
||||
@@ -1,5 +1,4 @@
|
||||
import { databaseService } from '@/main/data/database-service';
|
||||
import { accountService } from '@/main/services/account-service';
|
||||
import { appService } from '@/main/services/app-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import {
|
||||
@@ -13,11 +12,7 @@ export class AccountLogoutMutationHandler
|
||||
async handleMutation(
|
||||
input: AccountLogoutMutationInput
|
||||
): Promise<AccountLogoutMutationOutput> {
|
||||
const account = await databaseService.appDatabase
|
||||
.selectFrom('accounts')
|
||||
.selectAll()
|
||||
.where('id', '=', input.accountId)
|
||||
.executeTakeFirst();
|
||||
const account = appService.getAccount(input.accountId);
|
||||
|
||||
if (!account) {
|
||||
throw new MutationError(
|
||||
@@ -26,7 +21,7 @@ export class AccountLogoutMutationHandler
|
||||
);
|
||||
}
|
||||
|
||||
await accountService.logoutAccount(account);
|
||||
await account.logout();
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { AccountUpdateOutput } from '@colanode/core';
|
||||
|
||||
import { databaseService } from '@/main/data/database-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import { eventBus } from '@/shared/lib/event-bus';
|
||||
import { httpClient } from '@/shared/lib/http-client';
|
||||
import {
|
||||
AccountUpdateMutationInput,
|
||||
AccountUpdateMutationOutput,
|
||||
} from '@/shared/mutations/accounts/account-update';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import { parseApiError } from '@/shared/lib/axios';
|
||||
import { appService } from '@/main/services/app-service';
|
||||
import { mapAccount } from '@/main/utils';
|
||||
|
||||
export class AccountUpdateMutationHandler
|
||||
implements MutationHandler<AccountUpdateMutationInput>
|
||||
@@ -17,46 +17,25 @@ export class AccountUpdateMutationHandler
|
||||
async handleMutation(
|
||||
input: AccountUpdateMutationInput
|
||||
): Promise<AccountUpdateMutationOutput> {
|
||||
const account = await databaseService.appDatabase
|
||||
.selectFrom('accounts')
|
||||
.selectAll()
|
||||
.where('id', '=', input.id)
|
||||
.executeTakeFirst();
|
||||
const accountService = appService.getAccount(input.id);
|
||||
|
||||
if (!account) {
|
||||
if (!accountService) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.AccountNotFound,
|
||||
'Account not found or has been logged out already. Try closing the app and opening it again.'
|
||||
);
|
||||
}
|
||||
|
||||
const server = await databaseService.appDatabase
|
||||
.selectFrom('servers')
|
||||
.selectAll()
|
||||
.where('domain', '=', account.server)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!server) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.ServerNotFound,
|
||||
`The server ${account.server} associated with this account was not found. Try closing the app and opening it again.`
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const { data } = await httpClient.put<AccountUpdateOutput>(
|
||||
const { data } = await accountService.client.put<AccountUpdateOutput>(
|
||||
`/v1/accounts/${input.id}`,
|
||||
{
|
||||
name: input.name,
|
||||
avatar: input.avatar,
|
||||
},
|
||||
{
|
||||
domain: server.domain,
|
||||
token: account.token,
|
||||
}
|
||||
);
|
||||
|
||||
const updatedAccount = await databaseService.appDatabase
|
||||
const updatedAccount = await appService.database
|
||||
.updateTable('accounts')
|
||||
.set({
|
||||
name: data.name,
|
||||
@@ -73,17 +52,12 @@ export class AccountUpdateMutationHandler
|
||||
);
|
||||
}
|
||||
|
||||
const account = mapAccount(updatedAccount);
|
||||
accountService.updateAccount(account);
|
||||
|
||||
eventBus.publish({
|
||||
type: 'account_updated',
|
||||
account: {
|
||||
id: updatedAccount.id,
|
||||
name: updatedAccount.name,
|
||||
email: updatedAccount.email,
|
||||
token: updatedAccount.token,
|
||||
avatar: updatedAccount.avatar,
|
||||
deviceId: updatedAccount.device_id,
|
||||
server: updatedAccount.server,
|
||||
},
|
||||
account,
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
63
apps/desktop/src/main/mutations/accounts/base.ts
Normal file
63
apps/desktop/src/main/mutations/accounts/base.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { LoginSuccessOutput } from '@colanode/core';
|
||||
|
||||
import { appService } from '@/main/services/app-service';
|
||||
import { ServerService } from '@/main/services/server-service';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import { mapAccount, mapWorkspace } from '@/main/utils';
|
||||
|
||||
export abstract class AccountMutationHandlerBase {
|
||||
protected async handleLoginSuccess(
|
||||
login: LoginSuccessOutput,
|
||||
server: ServerService
|
||||
): Promise<void> {
|
||||
const createdAccount = await appService.database
|
||||
.insertInto('accounts')
|
||||
.returningAll()
|
||||
.values({
|
||||
id: login.account.id,
|
||||
email: login.account.email,
|
||||
name: login.account.name,
|
||||
server: server.server.domain,
|
||||
token: login.token,
|
||||
device_id: login.deviceId,
|
||||
avatar: login.account.avatar,
|
||||
})
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!createdAccount) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.AccountLoginFailed,
|
||||
'Account login failed, please try again.'
|
||||
);
|
||||
}
|
||||
|
||||
const account = mapAccount(createdAccount);
|
||||
const accountService = await appService.initAccount(account);
|
||||
|
||||
if (login.workspaces.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const workspace of login.workspaces) {
|
||||
const createdWorkspace = await accountService.database
|
||||
.insertInto('workspaces')
|
||||
.returningAll()
|
||||
.values({
|
||||
id: workspace.id,
|
||||
name: workspace.name,
|
||||
user_id: workspace.user.id,
|
||||
account_id: account.id,
|
||||
role: workspace.user.role,
|
||||
avatar: workspace.avatar,
|
||||
description: workspace.description,
|
||||
})
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!createdWorkspace) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await accountService.initWorkspace(mapWorkspace(createdWorkspace));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,19 @@
|
||||
import { EmailLoginInput, LoginOutput } from '@colanode/core';
|
||||
import axios from 'axios';
|
||||
|
||||
import { app } from 'electron';
|
||||
|
||||
import { databaseService } from '@/main/data/database-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import { httpClient } from '@/shared/lib/http-client';
|
||||
import { EmailLoginMutationInput } from '@/shared/mutations/accounts/email-login';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import { parseApiError } from '@/shared/lib/axios';
|
||||
import { accountService } from '@/main/services/account-service';
|
||||
import { appService } from '@/main/services/app-service';
|
||||
import { AccountMutationHandlerBase } from '@/main/mutations/accounts/base';
|
||||
|
||||
export class EmailLoginMutationHandler
|
||||
extends AccountMutationHandlerBase
|
||||
implements MutationHandler<EmailLoginMutationInput>
|
||||
{
|
||||
async handleMutation(input: EmailLoginMutationInput): Promise<LoginOutput> {
|
||||
const server = await databaseService.appDatabase
|
||||
.selectFrom('servers')
|
||||
.selectAll()
|
||||
.where('domain', '=', input.server)
|
||||
.executeTakeFirst();
|
||||
const server = appService.getServer(input.server);
|
||||
|
||||
if (!server) {
|
||||
throw new MutationError(
|
||||
@@ -32,24 +27,23 @@ export class EmailLoginMutationHandler
|
||||
email: input.email,
|
||||
password: input.password,
|
||||
platform: process.platform,
|
||||
version: app.getVersion(),
|
||||
version: appService.version,
|
||||
};
|
||||
|
||||
const { data } = await httpClient.post<LoginOutput>(
|
||||
'/v1/accounts/emails/login',
|
||||
emailLoginInput,
|
||||
{
|
||||
domain: server.domain,
|
||||
}
|
||||
const { data } = await axios.post<LoginOutput>(
|
||||
`${server.apiBaseUrl}/v1/accounts/emails/login`,
|
||||
emailLoginInput
|
||||
);
|
||||
|
||||
if (data.type === 'verify') {
|
||||
return data;
|
||||
}
|
||||
|
||||
await accountService.initAccount(data, server.domain);
|
||||
await this.handleLoginSuccess(data, server);
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
const apiError = parseApiError(error);
|
||||
throw new MutationError(MutationErrorCode.ApiError, apiError.message);
|
||||
}
|
||||
|
||||
@@ -1,26 +1,21 @@
|
||||
import { EmailRegisterInput, LoginOutput } from '@colanode/core';
|
||||
import axios from 'axios';
|
||||
|
||||
import { app } from 'electron';
|
||||
|
||||
import { databaseService } from '@/main/data/database-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import { httpClient } from '@/shared/lib/http-client';
|
||||
import { EmailRegisterMutationInput } from '@/shared/mutations/accounts/email-register';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import { parseApiError } from '@/shared/lib/axios';
|
||||
import { accountService } from '@/main/services/account-service';
|
||||
import { appService } from '@/main/services/app-service';
|
||||
import { AccountMutationHandlerBase } from '@/main/mutations/accounts/base';
|
||||
|
||||
export class EmailRegisterMutationHandler
|
||||
extends AccountMutationHandlerBase
|
||||
implements MutationHandler<EmailRegisterMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: EmailRegisterMutationInput
|
||||
): Promise<LoginOutput> {
|
||||
const server = await databaseService.appDatabase
|
||||
.selectFrom('servers')
|
||||
.selectAll()
|
||||
.where('domain', '=', input.server)
|
||||
.executeTakeFirst();
|
||||
const server = appService.getServer(input.server);
|
||||
|
||||
if (!server) {
|
||||
throw new MutationError(
|
||||
@@ -35,22 +30,20 @@ export class EmailRegisterMutationHandler
|
||||
email: input.email,
|
||||
password: input.password,
|
||||
platform: process.platform,
|
||||
version: app.getVersion(),
|
||||
version: appService.version,
|
||||
};
|
||||
|
||||
const { data } = await httpClient.post<LoginOutput>(
|
||||
'/v1/accounts/emails/register',
|
||||
emailRegisterInput,
|
||||
{
|
||||
domain: server.domain,
|
||||
}
|
||||
const { data } = await axios.post<LoginOutput>(
|
||||
`${server.apiBaseUrl}/v1/accounts/emails/register`,
|
||||
emailRegisterInput
|
||||
);
|
||||
|
||||
if (data.type === 'verify') {
|
||||
return data;
|
||||
}
|
||||
|
||||
await accountService.initAccount(data, server.domain);
|
||||
await this.handleLoginSuccess(data, server);
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
const apiError = parseApiError(error);
|
||||
|
||||
@@ -1,24 +1,18 @@
|
||||
import { EmailVerifyInput, LoginOutput } from '@colanode/core';
|
||||
import axios from 'axios';
|
||||
|
||||
import { app } from 'electron';
|
||||
|
||||
import { databaseService } from '@/main/data/database-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import { httpClient } from '@/shared/lib/http-client';
|
||||
import { EmailVerifyMutationInput } from '@/shared/mutations/accounts/email-verify';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import { parseApiError } from '@/shared/lib/axios';
|
||||
import { accountService } from '@/main/services/account-service';
|
||||
|
||||
import { appService } from '@/main/services/app-service';
|
||||
import { AccountMutationHandlerBase } from '@/main/mutations/accounts/base';
|
||||
export class EmailVerifyMutationHandler
|
||||
extends AccountMutationHandlerBase
|
||||
implements MutationHandler<EmailVerifyMutationInput>
|
||||
{
|
||||
async handleMutation(input: EmailVerifyMutationInput): Promise<LoginOutput> {
|
||||
const server = await databaseService.appDatabase
|
||||
.selectFrom('servers')
|
||||
.selectAll()
|
||||
.where('domain', '=', input.server)
|
||||
.executeTakeFirst();
|
||||
const server = appService.getServer(input.server);
|
||||
|
||||
if (!server) {
|
||||
throw new MutationError(
|
||||
@@ -32,15 +26,12 @@ export class EmailVerifyMutationHandler
|
||||
id: input.id,
|
||||
otp: input.otp,
|
||||
platform: process.platform,
|
||||
version: app.getVersion(),
|
||||
version: appService.version,
|
||||
};
|
||||
|
||||
const { data } = await httpClient.post<LoginOutput>(
|
||||
'/v1/accounts/emails/verify',
|
||||
emailVerifyInput,
|
||||
{
|
||||
domain: server.domain,
|
||||
}
|
||||
const { data } = await axios.post<LoginOutput>(
|
||||
`${server.apiBaseUrl}/v1/accounts/emails/verify`,
|
||||
emailVerifyInput
|
||||
);
|
||||
|
||||
if (data.type === 'verify') {
|
||||
@@ -50,7 +41,7 @@ export class EmailVerifyMutationHandler
|
||||
);
|
||||
}
|
||||
|
||||
await accountService.initAccount(data, server.domain);
|
||||
await this.handleLoginSuccess(data, server);
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
|
||||
@@ -2,15 +2,14 @@ import FormData from 'form-data';
|
||||
|
||||
import fs from 'fs';
|
||||
|
||||
import { databaseService } from '@/main/data/database-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import { httpClient } from '@/shared/lib/http-client';
|
||||
import {
|
||||
AvatarUploadMutationInput,
|
||||
AvatarUploadMutationOutput,
|
||||
} from '@/shared/mutations/avatars/avatar-upload';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import { parseApiError } from '@/shared/lib/axios';
|
||||
import { appService } from '@/main/services/app-service';
|
||||
|
||||
interface AvatarUploadResponse {
|
||||
id: string;
|
||||
@@ -22,14 +21,9 @@ export class AvatarUploadMutationHandler
|
||||
async handleMutation(
|
||||
input: AvatarUploadMutationInput
|
||||
): Promise<AvatarUploadMutationOutput> {
|
||||
const credentials = await databaseService.appDatabase
|
||||
.selectFrom('accounts')
|
||||
.innerJoin('servers', 'accounts.server', 'servers.domain')
|
||||
.select(['domain', 'attributes', 'token'])
|
||||
.where('id', '=', input.accountId)
|
||||
.executeTakeFirst();
|
||||
const account = appService.getAccount(input.accountId);
|
||||
|
||||
if (!credentials) {
|
||||
if (!account) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.AccountNotFound,
|
||||
'Account not found or has been logged out already. Try closing the app and opening it again.'
|
||||
@@ -43,12 +37,10 @@ export class AvatarUploadMutationHandler
|
||||
const formData = new FormData();
|
||||
formData.append('avatar', fileStream);
|
||||
|
||||
const { data } = await httpClient.post<AvatarUploadResponse>(
|
||||
const { data } = await account.client.post<AvatarUploadResponse>(
|
||||
'/v1/avatars',
|
||||
formData,
|
||||
{
|
||||
domain: credentials.domain,
|
||||
token: credentials.token,
|
||||
headers: formData.getHeaders(),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,25 +1,23 @@
|
||||
import { ChannelAttributes, generateId, IdType } from '@colanode/core';
|
||||
|
||||
import { databaseService } from '@/main/data/database-service';
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
ChannelCreateMutationInput,
|
||||
ChannelCreateMutationOutput,
|
||||
} from '@/shared/mutations/channels/channel-create';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class ChannelCreateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<ChannelCreateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: ChannelCreateMutationInput
|
||||
): Promise<ChannelCreateMutationOutput> {
|
||||
const workspaceDatabase = await databaseService.getWorkspaceDatabase(
|
||||
input.userId
|
||||
);
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const space = await workspaceDatabase
|
||||
const space = await workspace.database
|
||||
.selectFrom('entries')
|
||||
.selectAll()
|
||||
.where('id', '=', input.spaceId)
|
||||
@@ -40,7 +38,7 @@ export class ChannelCreateMutationHandler
|
||||
parentId: input.spaceId,
|
||||
};
|
||||
|
||||
await entryService.createEntry(input.userId, {
|
||||
await workspace.entries.createEntry({
|
||||
id,
|
||||
attributes,
|
||||
parentId: input.spaceId,
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
ChannelDeleteMutationInput,
|
||||
ChannelDeleteMutationOutput,
|
||||
} from '@/shared/mutations/channels/channel-delete';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class ChannelDeleteMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<ChannelDeleteMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: ChannelDeleteMutationInput
|
||||
): Promise<ChannelDeleteMutationOutput> {
|
||||
await entryService.deleteEntry(input.channelId, input.userId);
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
await workspace.entries.deleteEntry(input.channelId);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import { ChannelAttributes } from '@colanode/core';
|
||||
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import {
|
||||
ChannelUpdateMutationInput,
|
||||
ChannelUpdateMutationOutput,
|
||||
} from '@/shared/mutations/channels/channel-update';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class ChannelUpdateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<ChannelUpdateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: ChannelUpdateMutationInput
|
||||
): Promise<ChannelUpdateMutationOutput> {
|
||||
const result = await entryService.updateEntry<ChannelAttributes>(
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const result = await workspace.entries.updateEntry<ChannelAttributes>(
|
||||
input.channelId,
|
||||
input.userId,
|
||||
(attributes) => {
|
||||
attributes.name = input.name;
|
||||
attributes.avatar = input.avatar;
|
||||
|
||||
@@ -1,37 +1,35 @@
|
||||
import { ChatAttributes, generateId, IdType } from '@colanode/core';
|
||||
import { sql } from 'kysely';
|
||||
|
||||
import { databaseService } from '@/main/data/database-service';
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
ChatCreateMutationInput,
|
||||
ChatCreateMutationOutput,
|
||||
} from '@/shared/mutations/chats/chat-create';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
interface ChatRow {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export class ChatCreateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<ChatCreateMutationInput>
|
||||
{
|
||||
public async handleMutation(
|
||||
input: ChatCreateMutationInput
|
||||
): Promise<ChatCreateMutationOutput> {
|
||||
const workspaceDatabase = await databaseService.getWorkspaceDatabase(
|
||||
input.userId
|
||||
);
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const query = sql<ChatRow>`
|
||||
SELECT id
|
||||
FROM entries
|
||||
WHERE type = 'chat'
|
||||
AND json_extract(attributes, '$.collaborators.${sql.raw(input.userId)}') is not null
|
||||
AND json_extract(attributes, '$.collaborators.${sql.raw(input.otherUserId)}') is not null
|
||||
`.compile(workspaceDatabase);
|
||||
AND json_extract(attributes, '$.collaborators.${sql.raw(workspace.userId)}') is not null
|
||||
`.compile(workspace.database);
|
||||
|
||||
const existingChats = await workspaceDatabase.executeQuery(query);
|
||||
const existingChats = await workspace.database.executeQuery(query);
|
||||
const chat = existingChats.rows?.[0];
|
||||
if (chat) {
|
||||
return {
|
||||
@@ -44,11 +42,11 @@ export class ChatCreateMutationHandler
|
||||
type: 'chat',
|
||||
collaborators: {
|
||||
[input.userId]: 'admin',
|
||||
[input.otherUserId]: 'admin',
|
||||
[workspace.userId]: 'admin',
|
||||
},
|
||||
};
|
||||
|
||||
await entryService.createEntry(input.userId, {
|
||||
await workspace.entries.createEntry({
|
||||
id,
|
||||
attributes,
|
||||
parentId: null,
|
||||
|
||||
@@ -5,19 +5,22 @@ import {
|
||||
IdType,
|
||||
} from '@colanode/core';
|
||||
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
DatabaseCreateMutationInput,
|
||||
DatabaseCreateMutationOutput,
|
||||
} from '@/shared/mutations/databases/database-create';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class DatabaseCreateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<DatabaseCreateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: DatabaseCreateMutationInput
|
||||
): Promise<DatabaseCreateMutationOutput> {
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const databaseId = generateId(IdType.Database);
|
||||
const viewId = generateId(IdType.View);
|
||||
const fieldId = generateId(IdType.Field);
|
||||
@@ -45,7 +48,7 @@ export class DatabaseCreateMutationHandler
|
||||
},
|
||||
};
|
||||
|
||||
await entryService.createEntry(input.userId, {
|
||||
await workspace.entries.createEntry({
|
||||
id: databaseId,
|
||||
attributes,
|
||||
parentId: input.spaceId,
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
DatabaseDeleteMutationInput,
|
||||
DatabaseDeleteMutationOutput,
|
||||
} from '@/shared/mutations/databases/database-delete';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class DatabaseDeleteMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<DatabaseDeleteMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: DatabaseDeleteMutationInput
|
||||
): Promise<DatabaseDeleteMutationOutput> {
|
||||
await entryService.deleteEntry(input.databaseId, input.userId);
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
await workspace.entries.deleteEntry(input.databaseId);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import { DatabaseAttributes } from '@colanode/core';
|
||||
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import {
|
||||
DatabaseUpdateMutationInput,
|
||||
DatabaseUpdateMutationOutput,
|
||||
} from '@/shared/mutations/databases/database-update';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class DatabaseUpdateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<DatabaseUpdateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: DatabaseUpdateMutationInput
|
||||
): Promise<DatabaseUpdateMutationOutput> {
|
||||
const result = await entryService.updateEntry<DatabaseAttributes>(
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
const result = await workspace.entries.updateEntry<DatabaseAttributes>(
|
||||
input.databaseId,
|
||||
input.userId,
|
||||
(attributes) => {
|
||||
attributes.name = input.name;
|
||||
attributes.avatar = input.avatar;
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@@ -7,22 +7,24 @@ import {
|
||||
IdType,
|
||||
} from '@colanode/core';
|
||||
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
FieldCreateMutationInput,
|
||||
FieldCreateMutationOutput,
|
||||
} from '@/shared/mutations/databases/field-create';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import { databaseService } from '@/main/data/database-service';
|
||||
import { fetchEntry } from '@/main/utils';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class FieldCreateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<FieldCreateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: FieldCreateMutationInput
|
||||
): Promise<FieldCreateMutationOutput> {
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
if (input.fieldType === 'relation') {
|
||||
if (!input.relationDatabaseId) {
|
||||
throw new MutationError(
|
||||
@@ -31,12 +33,8 @@ export class FieldCreateMutationHandler
|
||||
);
|
||||
}
|
||||
|
||||
const workspaceDatabase = await databaseService.getWorkspaceDatabase(
|
||||
input.userId
|
||||
);
|
||||
|
||||
const relationDatabase = await fetchEntry(
|
||||
workspaceDatabase,
|
||||
workspace.database,
|
||||
input.relationDatabaseId
|
||||
);
|
||||
|
||||
@@ -49,9 +47,8 @@ export class FieldCreateMutationHandler
|
||||
}
|
||||
|
||||
const fieldId = generateId(IdType.Field);
|
||||
const result = await entryService.updateEntry(
|
||||
const result = await workspace.entries.updateEntry(
|
||||
input.databaseId,
|
||||
input.userId,
|
||||
(attributes) => {
|
||||
if (attributes.type !== 'database') {
|
||||
throw new Error('Invalid node type');
|
||||
|
||||
@@ -1,30 +1,25 @@
|
||||
import { DatabaseAttributes } from '@colanode/core';
|
||||
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import {
|
||||
FieldDeleteMutationInput,
|
||||
FieldDeleteMutationOutput,
|
||||
} from '@/shared/mutations/databases/field-delete';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class FieldDeleteMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<FieldDeleteMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: FieldDeleteMutationInput
|
||||
): Promise<FieldDeleteMutationOutput> {
|
||||
const result = await entryService.updateEntry<DatabaseAttributes>(
|
||||
input.databaseId,
|
||||
input.userId,
|
||||
(attributes) => {
|
||||
if (!attributes.fields[input.fieldId]) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.FieldNotFound,
|
||||
'The field you are trying to delete does not exist.'
|
||||
);
|
||||
}
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const result = await workspace.entries.updateEntry<DatabaseAttributes>(
|
||||
input.databaseId,
|
||||
(attributes) => {
|
||||
delete attributes.fields[input.fieldId];
|
||||
|
||||
return attributes;
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import { DatabaseAttributes } from '@colanode/core';
|
||||
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import {
|
||||
FieldNameUpdateMutationInput,
|
||||
FieldNameUpdateMutationOutput,
|
||||
} from '@/shared/mutations/databases/field-name-update';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class FieldNameUpdateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<FieldNameUpdateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: FieldNameUpdateMutationInput
|
||||
): Promise<FieldNameUpdateMutationOutput> {
|
||||
const result = await entryService.updateEntry<DatabaseAttributes>(
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const result = await workspace.entries.updateEntry<DatabaseAttributes>(
|
||||
input.databaseId,
|
||||
input.userId,
|
||||
(attributes) => {
|
||||
const field = attributes.fields[input.fieldId];
|
||||
if (!field) {
|
||||
|
||||
@@ -6,24 +6,26 @@ import {
|
||||
IdType,
|
||||
} from '@colanode/core';
|
||||
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
SelectOptionCreateMutationInput,
|
||||
SelectOptionCreateMutationOutput,
|
||||
} from '@/shared/mutations/databases/select-option-create';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class SelectOptionCreateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<SelectOptionCreateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: SelectOptionCreateMutationInput
|
||||
): Promise<SelectOptionCreateMutationOutput> {
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const id = generateId(IdType.SelectOption);
|
||||
const result = await entryService.updateEntry<DatabaseAttributes>(
|
||||
const result = await workspace.entries.updateEntry<DatabaseAttributes>(
|
||||
input.databaseId,
|
||||
input.userId,
|
||||
(attributes) => {
|
||||
const field = attributes.fields[input.fieldId];
|
||||
if (!field) {
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import { DatabaseAttributes } from '@colanode/core';
|
||||
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import {
|
||||
SelectOptionDeleteMutationInput,
|
||||
SelectOptionDeleteMutationOutput,
|
||||
} from '@/shared/mutations/databases/select-option-delete';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class SelectOptionDeleteMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<SelectOptionDeleteMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: SelectOptionDeleteMutationInput
|
||||
): Promise<SelectOptionDeleteMutationOutput> {
|
||||
const result = await entryService.updateEntry<DatabaseAttributes>(
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const result = await workspace.entries.updateEntry<DatabaseAttributes>(
|
||||
input.databaseId,
|
||||
input.userId,
|
||||
(attributes) => {
|
||||
const field = attributes.fields[input.fieldId];
|
||||
if (!field) {
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import { DatabaseAttributes } from '@colanode/core';
|
||||
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import {
|
||||
SelectOptionUpdateMutationInput,
|
||||
SelectOptionUpdateMutationOutput,
|
||||
} from '@/shared/mutations/databases/select-option-update';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class SelectOptionUpdateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<SelectOptionUpdateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: SelectOptionUpdateMutationInput
|
||||
): Promise<SelectOptionUpdateMutationOutput> {
|
||||
const result = await entryService.updateEntry<DatabaseAttributes>(
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const result = await workspace.entries.updateEntry<DatabaseAttributes>(
|
||||
input.databaseId,
|
||||
input.userId,
|
||||
(attributes) => {
|
||||
const field = attributes.fields[input.fieldId];
|
||||
if (!field) {
|
||||
|
||||
@@ -6,24 +6,26 @@ import {
|
||||
IdType,
|
||||
} from '@colanode/core';
|
||||
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
ViewCreateMutationInput,
|
||||
ViewCreateMutationOutput,
|
||||
} from '@/shared/mutations/databases/view-create';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class ViewCreateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<ViewCreateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: ViewCreateMutationInput
|
||||
): Promise<ViewCreateMutationOutput> {
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const id = generateId(IdType.View);
|
||||
const result = await entryService.updateEntry<DatabaseAttributes>(
|
||||
const result = await workspace.entries.updateEntry<DatabaseAttributes>(
|
||||
input.databaseId,
|
||||
input.userId,
|
||||
(attributes) => {
|
||||
const maxIndex = Object.values(attributes.views)
|
||||
.map((view) => view.index)
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import { DatabaseAttributes } from '@colanode/core';
|
||||
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import {
|
||||
ViewDeleteMutationInput,
|
||||
ViewDeleteMutationOutput,
|
||||
} from '@/shared/mutations/databases/view-delete';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class ViewDeleteMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<ViewDeleteMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: ViewDeleteMutationInput
|
||||
): Promise<ViewDeleteMutationOutput> {
|
||||
const result = await entryService.updateEntry<DatabaseAttributes>(
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const result = await workspace.entries.updateEntry<DatabaseAttributes>(
|
||||
input.databaseId,
|
||||
input.userId,
|
||||
(attributes) => {
|
||||
if (!attributes.views[input.viewId]) {
|
||||
throw new MutationError(
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import { DatabaseAttributes } from '@colanode/core';
|
||||
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import {
|
||||
ViewNameUpdateMutationInput,
|
||||
ViewNameUpdateMutationOutput,
|
||||
} from '@/shared/mutations/databases/view-name-update';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class ViewNameUpdateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<ViewNameUpdateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: ViewNameUpdateMutationInput
|
||||
): Promise<ViewNameUpdateMutationOutput> {
|
||||
const result = await entryService.updateEntry<DatabaseAttributes>(
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const result = await workspace.entries.updateEntry<DatabaseAttributes>(
|
||||
input.databaseId,
|
||||
input.userId,
|
||||
(attributes) => {
|
||||
const view = attributes.views[input.viewId];
|
||||
if (!view) {
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import { DatabaseAttributes } from '@colanode/core';
|
||||
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import {
|
||||
ViewUpdateMutationInput,
|
||||
ViewUpdateMutationOutput,
|
||||
} from '@/shared/mutations/databases/view-update';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class ViewUpdateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<ViewUpdateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: ViewUpdateMutationInput
|
||||
): Promise<ViewUpdateMutationOutput> {
|
||||
const result = await entryService.updateEntry<DatabaseAttributes>(
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const result = await workspace.entries.updateEntry<DatabaseAttributes>(
|
||||
input.databaseId,
|
||||
input.userId,
|
||||
(attributes) => {
|
||||
if (!attributes.views[input.view.id]) {
|
||||
throw new MutationError(
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import { set } from 'lodash-es';
|
||||
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
EntryCollaboratorCreateMutationInput,
|
||||
EntryCollaboratorCreateMutationOutput,
|
||||
} from '@/shared/mutations/entries/entry-collaborator-create';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class EntryCollaboratorCreateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<EntryCollaboratorCreateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: EntryCollaboratorCreateMutationInput
|
||||
): Promise<EntryCollaboratorCreateMutationOutput> {
|
||||
const result = await entryService.updateEntry(
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const result = await workspace.entries.updateEntry(
|
||||
input.entryId,
|
||||
input.userId,
|
||||
(attributes) => {
|
||||
for (const collaboratorId of input.collaboratorIds) {
|
||||
set(attributes, `collaborators.${collaboratorId}`, input.role);
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import { unset } from 'lodash-es';
|
||||
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
EntryCollaboratorDeleteMutationInput,
|
||||
EntryCollaboratorDeleteMutationOutput,
|
||||
} from '@/shared/mutations/entries/entry-collaborator-delete';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class EntryCollaboratorDeleteMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<EntryCollaboratorDeleteMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: EntryCollaboratorDeleteMutationInput
|
||||
): Promise<EntryCollaboratorDeleteMutationOutput> {
|
||||
const result = await entryService.updateEntry(
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const result = await workspace.entries.updateEntry(
|
||||
input.entryId,
|
||||
input.userId,
|
||||
(attributes) => {
|
||||
unset(attributes, `collaborators.${input.collaboratorId}`);
|
||||
return attributes;
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import { set } from 'lodash-es';
|
||||
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
EntryCollaboratorUpdateMutationInput,
|
||||
EntryCollaboratorUpdateMutationOutput,
|
||||
} from '@/shared/mutations/entries/entry-collaborator-update';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class EntryCollaboratorUpdateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<EntryCollaboratorUpdateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: EntryCollaboratorUpdateMutationInput
|
||||
): Promise<EntryCollaboratorUpdateMutationOutput> {
|
||||
const result = await entryService.updateEntry(
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const result = await workspace.entries.updateEntry(
|
||||
input.entryId,
|
||||
input.userId,
|
||||
(attributes) => {
|
||||
set(attributes, `collaborators.${input.collaboratorId}`, input.role);
|
||||
return attributes;
|
||||
|
||||
@@ -1,29 +1,24 @@
|
||||
import { MarkEntryOpenedMutation, generateId, IdType } from '@colanode/core';
|
||||
|
||||
import { databaseService } from '@/main/data/database-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
EntryMarkOpenedMutationInput,
|
||||
EntryMarkOpenedMutationOutput,
|
||||
} from '@/shared/mutations/entries/entry-mark-opened';
|
||||
import { eventBus } from '@/shared/lib/event-bus';
|
||||
import { mapEntryInteraction } from '@/main/utils';
|
||||
import { fetchEntry, mapEntryInteraction } from '@/main/utils';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class EntryMarkOpenedMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<EntryMarkOpenedMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: EntryMarkOpenedMutationInput
|
||||
): Promise<EntryMarkOpenedMutationOutput> {
|
||||
const workspaceDatabase = await databaseService.getWorkspaceDatabase(
|
||||
input.userId
|
||||
);
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const entry = await workspaceDatabase
|
||||
.selectFrom('entries')
|
||||
.selectAll()
|
||||
.where('id', '=', input.entryId)
|
||||
.executeTakeFirst();
|
||||
const entry = await fetchEntry(workspace.database, input.entryId);
|
||||
|
||||
if (!entry) {
|
||||
return {
|
||||
@@ -31,11 +26,11 @@ export class EntryMarkOpenedMutationHandler
|
||||
};
|
||||
}
|
||||
|
||||
const existingInteraction = await workspaceDatabase
|
||||
const existingInteraction = await workspace.database
|
||||
.selectFrom('entry_interactions')
|
||||
.selectAll()
|
||||
.where('entry_id', '=', input.entryId)
|
||||
.where('collaborator_id', '=', input.userId)
|
||||
.where('collaborator_id', '=', workspace.userId)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingInteraction) {
|
||||
@@ -55,7 +50,7 @@ export class EntryMarkOpenedMutationHandler
|
||||
? existingInteraction.first_opened_at
|
||||
: lastOpenedAt;
|
||||
|
||||
const { createdInteraction, createdMutation } = await workspaceDatabase
|
||||
const { createdInteraction, createdMutation } = await workspace.database
|
||||
.transaction()
|
||||
.execute(async (trx) => {
|
||||
const createdInteraction = await trx
|
||||
@@ -63,7 +58,7 @@ export class EntryMarkOpenedMutationHandler
|
||||
.returningAll()
|
||||
.values({
|
||||
entry_id: input.entryId,
|
||||
collaborator_id: input.userId,
|
||||
collaborator_id: workspace.userId,
|
||||
last_opened_at: lastOpenedAt,
|
||||
first_opened_at: firstOpenedAt,
|
||||
version: 0n,
|
||||
@@ -87,7 +82,7 @@ export class EntryMarkOpenedMutationHandler
|
||||
type: 'mark_entry_opened',
|
||||
data: {
|
||||
entryId: input.entryId,
|
||||
collaboratorId: input.userId,
|
||||
collaboratorId: workspace.userId,
|
||||
openedAt: new Date().toISOString(),
|
||||
},
|
||||
};
|
||||
@@ -114,14 +109,12 @@ export class EntryMarkOpenedMutationHandler
|
||||
throw new Error('Failed to create entry interaction');
|
||||
}
|
||||
|
||||
eventBus.publish({
|
||||
type: 'mutation_created',
|
||||
userId: input.userId,
|
||||
});
|
||||
workspace.mutations.triggerSync();
|
||||
|
||||
eventBus.publish({
|
||||
type: 'entry_interaction_updated',
|
||||
userId: input.userId,
|
||||
accountId: workspace.accountId,
|
||||
workspaceId: workspace.id,
|
||||
entryInteraction: mapEntryInteraction(createdInteraction),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,29 +1,24 @@
|
||||
import { MarkEntrySeenMutation, generateId, IdType } from '@colanode/core';
|
||||
|
||||
import { databaseService } from '@/main/data/database-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
EntryMarkSeenMutationInput,
|
||||
EntryMarkSeenMutationOutput,
|
||||
} from '@/shared/mutations/entries/entry-mark-seen';
|
||||
import { eventBus } from '@/shared/lib/event-bus';
|
||||
import { mapEntryInteraction } from '@/main/utils';
|
||||
import { fetchEntry, mapEntryInteraction } from '@/main/utils';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class EntryMarkSeenMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<EntryMarkSeenMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: EntryMarkSeenMutationInput
|
||||
): Promise<EntryMarkSeenMutationOutput> {
|
||||
const workspaceDatabase = await databaseService.getWorkspaceDatabase(
|
||||
input.userId
|
||||
);
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const entry = await workspaceDatabase
|
||||
.selectFrom('entries')
|
||||
.selectAll()
|
||||
.where('id', '=', input.entryId)
|
||||
.executeTakeFirst();
|
||||
const entry = await fetchEntry(workspace.database, input.entryId);
|
||||
|
||||
if (!entry) {
|
||||
return {
|
||||
@@ -31,11 +26,11 @@ export class EntryMarkSeenMutationHandler
|
||||
};
|
||||
}
|
||||
|
||||
const existingInteraction = await workspaceDatabase
|
||||
const existingInteraction = await workspace.database
|
||||
.selectFrom('entry_interactions')
|
||||
.selectAll()
|
||||
.where('entry_id', '=', input.entryId)
|
||||
.where('collaborator_id', '=', input.userId)
|
||||
.where('collaborator_id', '=', workspace.userId)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingInteraction) {
|
||||
@@ -55,7 +50,7 @@ export class EntryMarkSeenMutationHandler
|
||||
? existingInteraction.first_seen_at
|
||||
: lastSeenAt;
|
||||
|
||||
const { createdInteraction, createdMutation } = await workspaceDatabase
|
||||
const { createdInteraction, createdMutation } = await workspace.database
|
||||
.transaction()
|
||||
.execute(async (trx) => {
|
||||
const createdInteraction = await trx
|
||||
@@ -63,7 +58,7 @@ export class EntryMarkSeenMutationHandler
|
||||
.returningAll()
|
||||
.values({
|
||||
entry_id: input.entryId,
|
||||
collaborator_id: input.userId,
|
||||
collaborator_id: workspace.userId,
|
||||
last_seen_at: lastSeenAt,
|
||||
first_seen_at: firstSeenAt,
|
||||
version: 0n,
|
||||
@@ -87,7 +82,7 @@ export class EntryMarkSeenMutationHandler
|
||||
type: 'mark_entry_seen',
|
||||
data: {
|
||||
entryId: input.entryId,
|
||||
collaboratorId: input.userId,
|
||||
collaboratorId: workspace.userId,
|
||||
seenAt: new Date().toISOString(),
|
||||
},
|
||||
};
|
||||
@@ -114,14 +109,12 @@ export class EntryMarkSeenMutationHandler
|
||||
throw new Error('Failed to create entry interaction');
|
||||
}
|
||||
|
||||
eventBus.publish({
|
||||
type: 'mutation_created',
|
||||
userId: input.userId,
|
||||
});
|
||||
workspace.mutations.triggerSync();
|
||||
|
||||
eventBus.publish({
|
||||
type: 'entry_interaction_updated',
|
||||
userId: input.userId,
|
||||
accountId: workspace.accountId,
|
||||
workspaceId: workspace.id,
|
||||
entryInteraction: mapEntryInteraction(createdInteraction),
|
||||
});
|
||||
|
||||
|
||||
@@ -6,32 +6,33 @@ import {
|
||||
IdType,
|
||||
} from '@colanode/core';
|
||||
|
||||
import { fileService } from '@/main/services/file-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
FileCreateMutationInput,
|
||||
FileCreateMutationOutput,
|
||||
} from '@/shared/mutations/files/file-create';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import { databaseService } from '@/main/data/database-service';
|
||||
import { eventBus } from '@/shared/lib/event-bus';
|
||||
import {
|
||||
fetchEntry,
|
||||
fetchUser,
|
||||
fetchUserStorageUsed,
|
||||
getFileMetadata,
|
||||
mapEntry,
|
||||
mapFile,
|
||||
} from '@/main/utils';
|
||||
import { formatBytes } from '@/shared/lib/files';
|
||||
import { DownloadStatus, UploadStatus } from '@/shared/types/files';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class FileCreateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<FileCreateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: FileCreateMutationInput
|
||||
): Promise<FileCreateMutationOutput> {
|
||||
const metadata = fileService.getFileMetadata(input.filePath);
|
||||
const metadata = getFileMetadata(input.filePath);
|
||||
if (!metadata) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.FileInvalid,
|
||||
@@ -39,11 +40,10 @@ export class FileCreateMutationHandler
|
||||
);
|
||||
}
|
||||
|
||||
const workspaceDatabase = await databaseService.getWorkspaceDatabase(
|
||||
input.userId
|
||||
);
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const user = await fetchUser(workspace.database, workspace.userId);
|
||||
|
||||
const user = await fetchUser(workspaceDatabase, input.userId);
|
||||
if (!user) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.UserNotFound,
|
||||
@@ -60,8 +60,8 @@ export class FileCreateMutationHandler
|
||||
}
|
||||
|
||||
const storageUsed = await fetchUserStorageUsed(
|
||||
workspaceDatabase,
|
||||
input.userId
|
||||
workspace.database,
|
||||
workspace.userId
|
||||
);
|
||||
|
||||
if (storageUsed + BigInt(metadata.size) > user.storage_limit) {
|
||||
@@ -76,7 +76,7 @@ export class FileCreateMutationHandler
|
||||
);
|
||||
}
|
||||
|
||||
const entry = await fetchEntry(workspaceDatabase, input.entryId);
|
||||
const entry = await fetchEntry(workspace.database, input.entryId);
|
||||
if (!entry) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.EntryNotFound,
|
||||
@@ -84,7 +84,7 @@ export class FileCreateMutationHandler
|
||||
);
|
||||
}
|
||||
|
||||
const root = await fetchEntry(workspaceDatabase, input.rootId);
|
||||
const root = await fetchEntry(workspace.database, input.rootId);
|
||||
if (!root) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.RootNotFound,
|
||||
@@ -96,8 +96,8 @@ export class FileCreateMutationHandler
|
||||
if (
|
||||
!canCreateFile({
|
||||
user: {
|
||||
userId: input.userId,
|
||||
role: user.role,
|
||||
userId: workspace.userId,
|
||||
role: workspace.role,
|
||||
},
|
||||
root: mapEntry(root),
|
||||
entry: mapEntry(entry),
|
||||
@@ -113,11 +113,10 @@ export class FileCreateMutationHandler
|
||||
);
|
||||
}
|
||||
|
||||
fileService.copyFileToWorkspace(
|
||||
workspace.files.copyFileToWorkspace(
|
||||
input.filePath,
|
||||
fileId,
|
||||
metadata.extension,
|
||||
input.userId
|
||||
metadata.extension
|
||||
);
|
||||
|
||||
const mutationData: CreateFileMutationData = {
|
||||
@@ -134,7 +133,7 @@ export class FileCreateMutationHandler
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
const createdFile = await workspaceDatabase
|
||||
const createdFile = await workspace.database
|
||||
.transaction()
|
||||
.execute(async (tx) => {
|
||||
const createdFile = await tx
|
||||
@@ -152,7 +151,7 @@ export class FileCreateMutationHandler
|
||||
size: metadata.size,
|
||||
extension: metadata.extension,
|
||||
created_at: new Date().toISOString(),
|
||||
created_by: input.userId,
|
||||
created_by: workspace.userId,
|
||||
status: FileStatus.Pending,
|
||||
version: 0n,
|
||||
})
|
||||
@@ -196,18 +195,17 @@ export class FileCreateMutationHandler
|
||||
|
||||
eventBus.publish({
|
||||
type: 'file_created',
|
||||
userId: input.userId,
|
||||
accountId: workspace.accountId,
|
||||
workspaceId: workspace.id,
|
||||
file: mapFile(createdFile),
|
||||
});
|
||||
|
||||
eventBus.publish({
|
||||
type: 'mutation_created',
|
||||
userId: input.userId,
|
||||
});
|
||||
workspace.mutations.triggerSync();
|
||||
|
||||
eventBus.publish({
|
||||
type: 'file_state_created',
|
||||
userId: input.userId,
|
||||
accountId: workspace.accountId,
|
||||
workspaceId: workspace.id,
|
||||
fileState: {
|
||||
fileId: fileId,
|
||||
downloadProgress: 100,
|
||||
|
||||
@@ -5,27 +5,26 @@ import {
|
||||
IdType,
|
||||
} from '@colanode/core';
|
||||
|
||||
import { databaseService } from '@/main/data/database-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
FileDeleteMutationInput,
|
||||
FileDeleteMutationOutput,
|
||||
} from '@/shared/mutations/files/file-delete';
|
||||
import { eventBus } from '@/shared/lib/event-bus';
|
||||
import { fetchEntry, fetchUser, mapEntry, mapFile } from '@/main/utils';
|
||||
import { fetchEntry, mapEntry, mapFile } from '@/main/utils';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class FileDeleteMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<FileDeleteMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: FileDeleteMutationInput
|
||||
): Promise<FileDeleteMutationOutput> {
|
||||
const workspaceDatabase = await databaseService.getWorkspaceDatabase(
|
||||
input.userId
|
||||
);
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const file = await workspaceDatabase
|
||||
const file = await workspace.database
|
||||
.selectFrom('files')
|
||||
.selectAll()
|
||||
.where('id', '=', input.fileId)
|
||||
@@ -38,15 +37,7 @@ export class FileDeleteMutationHandler
|
||||
);
|
||||
}
|
||||
|
||||
const user = await fetchUser(workspaceDatabase, input.userId);
|
||||
if (!user) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.UserNotFound,
|
||||
'There was an error while fetching the user. Please make sure you are logged in.'
|
||||
);
|
||||
}
|
||||
|
||||
const entry = await fetchEntry(workspaceDatabase, file.root_id);
|
||||
const entry = await fetchEntry(workspace.database, file.root_id);
|
||||
if (!entry) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.EntryNotFound,
|
||||
@@ -54,7 +45,7 @@ export class FileDeleteMutationHandler
|
||||
);
|
||||
}
|
||||
|
||||
const root = await fetchEntry(workspaceDatabase, entry.root_id);
|
||||
const root = await fetchEntry(workspace.database, entry.root_id);
|
||||
if (!root) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.RootNotFound,
|
||||
@@ -65,8 +56,8 @@ export class FileDeleteMutationHandler
|
||||
if (
|
||||
!canDeleteFile({
|
||||
user: {
|
||||
userId: input.userId,
|
||||
role: user.role,
|
||||
userId: workspace.userId,
|
||||
role: workspace.role,
|
||||
},
|
||||
root: mapEntry(root),
|
||||
entry: mapEntry(entry),
|
||||
@@ -90,7 +81,7 @@ export class FileDeleteMutationHandler
|
||||
deletedAt,
|
||||
};
|
||||
|
||||
await workspaceDatabase.transaction().execute(async (tx) => {
|
||||
await workspace.database.transaction().execute(async (tx) => {
|
||||
await tx
|
||||
.updateTable('files')
|
||||
.set({
|
||||
@@ -113,14 +104,12 @@ export class FileDeleteMutationHandler
|
||||
|
||||
eventBus.publish({
|
||||
type: 'file_deleted',
|
||||
userId: input.userId,
|
||||
accountId: workspace.accountId,
|
||||
workspaceId: workspace.id,
|
||||
file: mapFile(file),
|
||||
});
|
||||
|
||||
eventBus.publish({
|
||||
type: 'mutation_created',
|
||||
userId: input.userId,
|
||||
});
|
||||
workspace.mutations.triggerSync();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { FileStatus } from '@colanode/core';
|
||||
|
||||
import { databaseService } from '@/main/data/database-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import { mapFileState } from '@/main/utils';
|
||||
import { eventBus } from '@/shared/lib/event-bus';
|
||||
@@ -10,18 +9,18 @@ import {
|
||||
FileDownloadMutationOutput,
|
||||
} from '@/shared/mutations/files/file-download';
|
||||
import { DownloadStatus, UploadStatus } from '@/shared/types/files';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class FileDownloadMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<FileDownloadMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: FileDownloadMutationInput
|
||||
): Promise<FileDownloadMutationOutput> {
|
||||
const workspaceDatabase = await databaseService.getWorkspaceDatabase(
|
||||
input.userId
|
||||
);
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const file = await workspaceDatabase
|
||||
const file = await workspace.database
|
||||
.selectFrom('files')
|
||||
.selectAll()
|
||||
.where('id', '=', input.fileId)
|
||||
@@ -41,7 +40,7 @@ export class FileDownloadMutationHandler
|
||||
);
|
||||
}
|
||||
|
||||
const existingFileState = await workspaceDatabase
|
||||
const existingFileState = await workspace.database
|
||||
.selectFrom('file_states')
|
||||
.selectAll()
|
||||
.where('file_id', '=', input.fileId)
|
||||
@@ -56,7 +55,7 @@ export class FileDownloadMutationHandler
|
||||
};
|
||||
}
|
||||
|
||||
const fileState = await workspaceDatabase
|
||||
const fileState = await workspace.database
|
||||
.insertInto('file_states')
|
||||
.returningAll()
|
||||
.values({
|
||||
@@ -85,7 +84,8 @@ export class FileDownloadMutationHandler
|
||||
|
||||
eventBus.publish({
|
||||
type: 'file_state_created',
|
||||
userId: input.userId,
|
||||
accountId: workspace.accountId,
|
||||
workspaceId: workspace.id,
|
||||
fileState: mapFileState(fileState),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { MarkFileOpenedMutation, generateId, IdType } from '@colanode/core';
|
||||
|
||||
import { databaseService } from '@/main/data/database-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
FileMarkOpenedMutationInput,
|
||||
@@ -8,18 +7,18 @@ import {
|
||||
} from '@/shared/mutations/files/file-mark-opened';
|
||||
import { eventBus } from '@/shared/lib/event-bus';
|
||||
import { mapFileInteraction } from '@/main/utils';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class FileMarkOpenedMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<FileMarkOpenedMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: FileMarkOpenedMutationInput
|
||||
): Promise<FileMarkOpenedMutationOutput> {
|
||||
const workspaceDatabase = await databaseService.getWorkspaceDatabase(
|
||||
input.userId
|
||||
);
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const file = await workspaceDatabase
|
||||
const file = await workspace.database
|
||||
.selectFrom('files')
|
||||
.selectAll()
|
||||
.where('id', '=', input.fileId)
|
||||
@@ -31,11 +30,11 @@ export class FileMarkOpenedMutationHandler
|
||||
};
|
||||
}
|
||||
|
||||
const existingInteraction = await workspaceDatabase
|
||||
const existingInteraction = await workspace.database
|
||||
.selectFrom('file_interactions')
|
||||
.selectAll()
|
||||
.where('file_id', '=', input.fileId)
|
||||
.where('collaborator_id', '=', input.userId)
|
||||
.where('collaborator_id', '=', workspace.userId)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingInteraction) {
|
||||
@@ -55,7 +54,7 @@ export class FileMarkOpenedMutationHandler
|
||||
? existingInteraction.first_opened_at
|
||||
: lastOpenedAt;
|
||||
|
||||
const { createdInteraction, createdMutation } = await workspaceDatabase
|
||||
const { createdInteraction, createdMutation } = await workspace.database
|
||||
.transaction()
|
||||
.execute(async (trx) => {
|
||||
const createdInteraction = await trx
|
||||
@@ -63,7 +62,7 @@ export class FileMarkOpenedMutationHandler
|
||||
.returningAll()
|
||||
.values({
|
||||
file_id: input.fileId,
|
||||
collaborator_id: input.userId,
|
||||
collaborator_id: workspace.userId,
|
||||
last_opened_at: lastOpenedAt,
|
||||
first_opened_at: firstOpenedAt,
|
||||
version: 0n,
|
||||
@@ -87,7 +86,7 @@ export class FileMarkOpenedMutationHandler
|
||||
type: 'mark_file_opened',
|
||||
data: {
|
||||
fileId: input.fileId,
|
||||
collaboratorId: input.userId,
|
||||
collaboratorId: workspace.userId,
|
||||
openedAt: new Date().toISOString(),
|
||||
},
|
||||
};
|
||||
@@ -114,14 +113,12 @@ export class FileMarkOpenedMutationHandler
|
||||
throw new Error('Failed to create file interaction');
|
||||
}
|
||||
|
||||
eventBus.publish({
|
||||
type: 'mutation_created',
|
||||
userId: input.userId,
|
||||
});
|
||||
workspace.mutations.triggerSync();
|
||||
|
||||
eventBus.publish({
|
||||
type: 'file_interaction_updated',
|
||||
userId: input.userId,
|
||||
accountId: workspace.accountId,
|
||||
workspaceId: workspace.id,
|
||||
fileInteraction: mapFileInteraction(createdInteraction),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { MarkFileSeenMutation, generateId, IdType } from '@colanode/core';
|
||||
|
||||
import { databaseService } from '@/main/data/database-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
FileMarkSeenMutationInput,
|
||||
@@ -8,18 +7,18 @@ import {
|
||||
} from '@/shared/mutations/files/file-mark-seen';
|
||||
import { eventBus } from '@/shared/lib/event-bus';
|
||||
import { mapFileInteraction } from '@/main/utils';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class FileMarkSeenMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<FileMarkSeenMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: FileMarkSeenMutationInput
|
||||
): Promise<FileMarkSeenMutationOutput> {
|
||||
const workspaceDatabase = await databaseService.getWorkspaceDatabase(
|
||||
input.userId
|
||||
);
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const file = await workspaceDatabase
|
||||
const file = await workspace.database
|
||||
.selectFrom('files')
|
||||
.selectAll()
|
||||
.where('id', '=', input.fileId)
|
||||
@@ -31,11 +30,11 @@ export class FileMarkSeenMutationHandler
|
||||
};
|
||||
}
|
||||
|
||||
const existingInteraction = await workspaceDatabase
|
||||
const existingInteraction = await workspace.database
|
||||
.selectFrom('file_interactions')
|
||||
.selectAll()
|
||||
.where('file_id', '=', input.fileId)
|
||||
.where('collaborator_id', '=', input.userId)
|
||||
.where('collaborator_id', '=', workspace.userId)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingInteraction) {
|
||||
@@ -55,7 +54,7 @@ export class FileMarkSeenMutationHandler
|
||||
? existingInteraction.first_seen_at
|
||||
: lastSeenAt;
|
||||
|
||||
const { createdInteraction, createdMutation } = await workspaceDatabase
|
||||
const { createdInteraction, createdMutation } = await workspace.database
|
||||
.transaction()
|
||||
.execute(async (trx) => {
|
||||
const createdInteraction = await trx
|
||||
@@ -63,7 +62,7 @@ export class FileMarkSeenMutationHandler
|
||||
.returningAll()
|
||||
.values({
|
||||
file_id: input.fileId,
|
||||
collaborator_id: input.userId,
|
||||
collaborator_id: workspace.userId,
|
||||
last_seen_at: lastSeenAt,
|
||||
first_seen_at: firstSeenAt,
|
||||
version: 0n,
|
||||
@@ -87,7 +86,7 @@ export class FileMarkSeenMutationHandler
|
||||
type: 'mark_file_seen',
|
||||
data: {
|
||||
fileId: input.fileId,
|
||||
collaboratorId: input.userId,
|
||||
collaboratorId: workspace.userId,
|
||||
seenAt: new Date().toISOString(),
|
||||
},
|
||||
};
|
||||
@@ -114,14 +113,12 @@ export class FileMarkSeenMutationHandler
|
||||
throw new Error('Failed to create file interaction');
|
||||
}
|
||||
|
||||
eventBus.publish({
|
||||
type: 'mutation_created',
|
||||
userId: input.userId,
|
||||
});
|
||||
workspace.mutations.triggerSync();
|
||||
|
||||
eventBus.publish({
|
||||
type: 'file_interaction_updated',
|
||||
userId: input.userId,
|
||||
accountId: workspace.accountId,
|
||||
workspaceId: workspace.id,
|
||||
fileInteraction: mapFileInteraction(createdInteraction),
|
||||
});
|
||||
|
||||
|
||||
@@ -7,14 +7,19 @@ import {
|
||||
FileSaveTempMutationOutput,
|
||||
} from '@/shared/mutations/files/file-save-temp';
|
||||
import { getWorkspaceTempFilesDirectoryPath } from '@/main/utils';
|
||||
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
export class FileSaveTempMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<FileSaveTempMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: FileSaveTempMutationInput
|
||||
): Promise<FileSaveTempMutationOutput> {
|
||||
const directoryPath = getWorkspaceTempFilesDirectoryPath(input.userId);
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
const directoryPath = getWorkspaceTempFilesDirectoryPath(
|
||||
workspace.accountId,
|
||||
workspace.id
|
||||
);
|
||||
|
||||
const fileName = this.generateUniqueName(directoryPath, input.name);
|
||||
const filePath = path.join(directoryPath, fileName);
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import { FolderAttributes, generateId, IdType } from '@colanode/core';
|
||||
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
FolderCreateMutationInput,
|
||||
FolderCreateMutationOutput,
|
||||
} from '@/shared/mutations/folders/folder-create';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class FolderCreateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<FolderCreateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: FolderCreateMutationInput
|
||||
): Promise<FolderCreateMutationOutput> {
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const id = generateId(IdType.Folder);
|
||||
const attributes: FolderAttributes = {
|
||||
type: 'folder',
|
||||
@@ -21,7 +24,7 @@ export class FolderCreateMutationHandler
|
||||
avatar: input.avatar,
|
||||
};
|
||||
|
||||
await entryService.createEntry(input.userId, {
|
||||
await workspace.entries.createEntry({
|
||||
id,
|
||||
attributes,
|
||||
parentId: input.parentId,
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
FolderDeleteMutationInput,
|
||||
FolderDeleteMutationOutput,
|
||||
} from '@/shared/mutations/folders/folder-delete';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class FolderDeleteMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<FolderDeleteMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: FolderDeleteMutationInput
|
||||
): Promise<FolderDeleteMutationOutput> {
|
||||
await entryService.deleteEntry(input.folderId, input.userId);
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
await workspace.entries.deleteEntry(input.folderId);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import { FolderAttributes } from '@colanode/core';
|
||||
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import {
|
||||
FolderUpdateMutationInput,
|
||||
FolderUpdateMutationOutput,
|
||||
} from '@/shared/mutations/folders/folder-update';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class FolderUpdateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<FolderUpdateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: FolderUpdateMutationInput
|
||||
): Promise<FolderUpdateMutationOutput> {
|
||||
const result = await entryService.updateEntry<FolderAttributes>(
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const result = await workspace.entries.updateEntry<FolderAttributes>(
|
||||
input.folderId,
|
||||
input.userId,
|
||||
(attributes) => {
|
||||
attributes.name = input.name;
|
||||
attributes.avatar = input.avatar;
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
MessageType,
|
||||
} from '@colanode/core';
|
||||
|
||||
import { fileService } from '@/main/services/file-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import { mapContentsToBlocks } from '@/shared/lib/editor';
|
||||
import {
|
||||
@@ -23,13 +22,13 @@ import {
|
||||
CreateFile,
|
||||
CreateFileState,
|
||||
CreateMutation,
|
||||
} from '@/main/data/workspace/schema';
|
||||
import { databaseService } from '@/main/data/database-service';
|
||||
} from '@/main/databases/workspace';
|
||||
import { eventBus } from '@/shared/lib/event-bus';
|
||||
import {
|
||||
fetchEntry,
|
||||
fetchUser,
|
||||
fetchUserStorageUsed,
|
||||
getFileMetadata,
|
||||
mapEntry,
|
||||
mapFile,
|
||||
mapFileState,
|
||||
@@ -37,18 +36,18 @@ import {
|
||||
} from '@/main/utils';
|
||||
import { formatBytes } from '@/shared/lib/files';
|
||||
import { DownloadStatus, UploadStatus } from '@/shared/types/files';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class MessageCreateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<MessageCreateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: MessageCreateMutationInput
|
||||
): Promise<MessageCreateMutationOutput> {
|
||||
const workspaceDatabase = await databaseService.getWorkspaceDatabase(
|
||||
input.userId
|
||||
);
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const user = await fetchUser(workspaceDatabase, input.userId);
|
||||
const user = await fetchUser(workspace.database, workspace.userId);
|
||||
if (!user) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.UserNotFound,
|
||||
@@ -56,7 +55,7 @@ export class MessageCreateMutationHandler
|
||||
);
|
||||
}
|
||||
|
||||
const entry = await fetchEntry(workspaceDatabase, input.conversationId);
|
||||
const entry = await fetchEntry(workspace.database, input.conversationId);
|
||||
if (!entry) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.EntryNotFound,
|
||||
@@ -64,7 +63,7 @@ export class MessageCreateMutationHandler
|
||||
);
|
||||
}
|
||||
|
||||
const root = await fetchEntry(workspaceDatabase, input.rootId);
|
||||
const root = await fetchEntry(workspace.database, input.rootId);
|
||||
if (!root) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.RootNotFound,
|
||||
@@ -75,7 +74,7 @@ export class MessageCreateMutationHandler
|
||||
if (
|
||||
!canCreateMessage({
|
||||
user: {
|
||||
userId: input.userId,
|
||||
userId: workspace.userId,
|
||||
role: user.role,
|
||||
},
|
||||
root: mapEntry(root),
|
||||
@@ -101,7 +100,7 @@ export class MessageCreateMutationHandler
|
||||
for (const block of blocks) {
|
||||
if (block.type === EditorNodeTypes.FilePlaceholder) {
|
||||
const path = block.attrs?.path;
|
||||
const metadata = fileService.getFileMetadata(path);
|
||||
const metadata = getFileMetadata(path);
|
||||
if (!metadata) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.FileInvalid,
|
||||
@@ -122,12 +121,7 @@ export class MessageCreateMutationHandler
|
||||
block.type = 'file';
|
||||
block.attrs = null;
|
||||
|
||||
fileService.copyFileToWorkspace(
|
||||
path,
|
||||
fileId,
|
||||
metadata.extension,
|
||||
input.userId
|
||||
);
|
||||
workspace.files.copyFileToWorkspace(path, fileId, metadata.extension);
|
||||
|
||||
files.push({
|
||||
id: fileId,
|
||||
@@ -142,7 +136,7 @@ export class MessageCreateMutationHandler
|
||||
extension: metadata.extension,
|
||||
size: metadata.size,
|
||||
created_at: createdAt,
|
||||
created_by: input.userId,
|
||||
created_by: workspace.userId,
|
||||
version: 0n,
|
||||
});
|
||||
|
||||
@@ -183,8 +177,8 @@ export class MessageCreateMutationHandler
|
||||
|
||||
if (files.length > 0) {
|
||||
const storageUsed = await fetchUserStorageUsed(
|
||||
workspaceDatabase,
|
||||
input.userId
|
||||
workspace.database,
|
||||
workspace.userId
|
||||
);
|
||||
|
||||
const fileSizeSum = BigInt(
|
||||
@@ -193,7 +187,7 @@ export class MessageCreateMutationHandler
|
||||
|
||||
if (storageUsed + fileSizeSum > user.storage_limit) {
|
||||
for (const file of files) {
|
||||
fileService.deleteFile(input.userId, file.id, file.extension);
|
||||
workspace.files.deleteFile(file.id, file.extension);
|
||||
}
|
||||
|
||||
throw new MutationError(
|
||||
@@ -223,7 +217,7 @@ export class MessageCreateMutationHandler
|
||||
};
|
||||
|
||||
const { createdMessage, createdFiles, createdFileStates } =
|
||||
await workspaceDatabase.transaction().execute(async (tx) => {
|
||||
await workspace.database.transaction().execute(async (tx) => {
|
||||
const createdMessage = await tx
|
||||
.insertInto('messages')
|
||||
.returningAll()
|
||||
@@ -234,7 +228,7 @@ export class MessageCreateMutationHandler
|
||||
root_id: input.rootId,
|
||||
attributes: JSON.stringify(messageAttributes),
|
||||
created_at: createdAt,
|
||||
created_by: input.userId,
|
||||
created_by: workspace.userId,
|
||||
version: 0n,
|
||||
})
|
||||
.executeTakeFirst();
|
||||
@@ -301,7 +295,8 @@ export class MessageCreateMutationHandler
|
||||
if (createdMessage) {
|
||||
eventBus.publish({
|
||||
type: 'message_created',
|
||||
userId: input.userId,
|
||||
accountId: workspace.accountId,
|
||||
workspaceId: workspace.id,
|
||||
message: mapMessage(createdMessage),
|
||||
});
|
||||
}
|
||||
@@ -310,7 +305,8 @@ export class MessageCreateMutationHandler
|
||||
for (const file of createdFiles) {
|
||||
eventBus.publish({
|
||||
type: 'file_created',
|
||||
userId: input.userId,
|
||||
accountId: workspace.accountId,
|
||||
workspaceId: workspace.id,
|
||||
file: mapFile(file),
|
||||
});
|
||||
}
|
||||
@@ -320,16 +316,14 @@ export class MessageCreateMutationHandler
|
||||
for (const fileState of createdFileStates) {
|
||||
eventBus.publish({
|
||||
type: 'file_state_created',
|
||||
userId: input.userId,
|
||||
accountId: workspace.accountId,
|
||||
workspaceId: workspace.id,
|
||||
fileState: mapFileState(fileState),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
eventBus.publish({
|
||||
type: 'mutation_created',
|
||||
userId: input.userId,
|
||||
});
|
||||
workspace.mutations.triggerSync();
|
||||
|
||||
return {
|
||||
id: messageId,
|
||||
|
||||
@@ -5,27 +5,25 @@ import {
|
||||
IdType,
|
||||
} from '@colanode/core';
|
||||
|
||||
import { databaseService } from '@/main/data/database-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
MessageDeleteMutationInput,
|
||||
MessageDeleteMutationOutput,
|
||||
} from '@/shared/mutations/messages/message-delete';
|
||||
import { eventBus } from '@/shared/lib/event-bus';
|
||||
import { fetchEntry, fetchUser, mapEntry, mapMessage } from '@/main/utils';
|
||||
import { fetchEntry, mapEntry, mapMessage } from '@/main/utils';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
export class MessageDeleteMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<MessageDeleteMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: MessageDeleteMutationInput
|
||||
): Promise<MessageDeleteMutationOutput> {
|
||||
const workspaceDatabase = await databaseService.getWorkspaceDatabase(
|
||||
input.userId
|
||||
);
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const message = await workspaceDatabase
|
||||
const message = await workspace.database
|
||||
.selectFrom('messages')
|
||||
.selectAll()
|
||||
.where('id', '=', input.messageId)
|
||||
@@ -37,15 +35,7 @@ export class MessageDeleteMutationHandler
|
||||
};
|
||||
}
|
||||
|
||||
const user = await fetchUser(workspaceDatabase, input.userId);
|
||||
if (!user) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.UserNotFound,
|
||||
'There was an error while fetching the user. Please make sure you are logged in.'
|
||||
);
|
||||
}
|
||||
|
||||
const entry = await fetchEntry(workspaceDatabase, message.entry_id);
|
||||
const entry = await fetchEntry(workspace.database, message.entry_id);
|
||||
if (!entry) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.EntryNotFound,
|
||||
@@ -53,7 +43,7 @@ export class MessageDeleteMutationHandler
|
||||
);
|
||||
}
|
||||
|
||||
const root = await fetchEntry(workspaceDatabase, message.root_id);
|
||||
const root = await fetchEntry(workspace.database, message.root_id);
|
||||
if (!root) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.RootNotFound,
|
||||
@@ -64,8 +54,8 @@ export class MessageDeleteMutationHandler
|
||||
if (
|
||||
!canDeleteMessage({
|
||||
user: {
|
||||
userId: input.userId,
|
||||
role: user.role,
|
||||
userId: workspace.userId,
|
||||
role: workspace.role,
|
||||
},
|
||||
root: mapEntry(root),
|
||||
entry: mapEntry(entry),
|
||||
@@ -88,7 +78,7 @@ export class MessageDeleteMutationHandler
|
||||
deletedAt,
|
||||
};
|
||||
|
||||
await workspaceDatabase.transaction().execute(async (tx) => {
|
||||
await workspace.database.transaction().execute(async (tx) => {
|
||||
await tx
|
||||
.updateTable('messages')
|
||||
.set({
|
||||
@@ -111,14 +101,12 @@ export class MessageDeleteMutationHandler
|
||||
|
||||
eventBus.publish({
|
||||
type: 'message_deleted',
|
||||
userId: input.userId,
|
||||
accountId: workspace.accountId,
|
||||
workspaceId: workspace.id,
|
||||
message: mapMessage(message),
|
||||
});
|
||||
|
||||
eventBus.publish({
|
||||
type: 'mutation_created',
|
||||
userId: input.userId,
|
||||
});
|
||||
workspace.mutations.triggerSync();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { MarkMessageSeenMutation, generateId, IdType } from '@colanode/core';
|
||||
|
||||
import { databaseService } from '@/main/data/database-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
MessageMarkSeenMutationInput,
|
||||
@@ -8,18 +7,18 @@ import {
|
||||
} from '@/shared/mutations/messages/message-mark-seen';
|
||||
import { eventBus } from '@/shared/lib/event-bus';
|
||||
import { mapMessageInteraction } from '@/main/utils';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class MessageMarkSeenMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<MessageMarkSeenMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: MessageMarkSeenMutationInput
|
||||
): Promise<MessageMarkSeenMutationOutput> {
|
||||
const workspaceDatabase = await databaseService.getWorkspaceDatabase(
|
||||
input.userId
|
||||
);
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const message = await workspaceDatabase
|
||||
const message = await workspace.database
|
||||
.selectFrom('messages')
|
||||
.selectAll()
|
||||
.where('id', '=', input.messageId)
|
||||
@@ -31,11 +30,11 @@ export class MessageMarkSeenMutationHandler
|
||||
};
|
||||
}
|
||||
|
||||
const existingInteraction = await workspaceDatabase
|
||||
const existingInteraction = await workspace.database
|
||||
.selectFrom('message_interactions')
|
||||
.selectAll()
|
||||
.where('message_id', '=', input.messageId)
|
||||
.where('collaborator_id', '=', input.userId)
|
||||
.where('collaborator_id', '=', workspace.userId)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingInteraction) {
|
||||
@@ -55,7 +54,7 @@ export class MessageMarkSeenMutationHandler
|
||||
? existingInteraction.first_seen_at
|
||||
: lastSeenAt;
|
||||
|
||||
const { createdInteraction, createdMutation } = await workspaceDatabase
|
||||
const { createdInteraction, createdMutation } = await workspace.database
|
||||
.transaction()
|
||||
.execute(async (trx) => {
|
||||
const createdInteraction = await trx
|
||||
@@ -63,7 +62,7 @@ export class MessageMarkSeenMutationHandler
|
||||
.returningAll()
|
||||
.values({
|
||||
message_id: input.messageId,
|
||||
collaborator_id: input.userId,
|
||||
collaborator_id: workspace.userId,
|
||||
first_seen_at: firstSeenAt,
|
||||
last_seen_at: lastSeenAt,
|
||||
version: 0n,
|
||||
@@ -87,7 +86,7 @@ export class MessageMarkSeenMutationHandler
|
||||
type: 'mark_message_seen',
|
||||
data: {
|
||||
messageId: input.messageId,
|
||||
collaboratorId: input.userId,
|
||||
collaboratorId: workspace.userId,
|
||||
seenAt: new Date().toISOString(),
|
||||
},
|
||||
};
|
||||
@@ -114,14 +113,12 @@ export class MessageMarkSeenMutationHandler
|
||||
throw new Error('Failed to create message interaction');
|
||||
}
|
||||
|
||||
eventBus.publish({
|
||||
type: 'mutation_created',
|
||||
userId: input.userId,
|
||||
});
|
||||
workspace.mutations.triggerSync();
|
||||
|
||||
eventBus.publish({
|
||||
type: 'message_interaction_updated',
|
||||
userId: input.userId,
|
||||
accountId: workspace.accountId,
|
||||
workspaceId: workspace.id,
|
||||
messageInteraction: mapMessageInteraction(createdInteraction),
|
||||
});
|
||||
|
||||
|
||||
@@ -5,32 +5,26 @@ import {
|
||||
IdType,
|
||||
} from '@colanode/core';
|
||||
|
||||
import { databaseService } from '@/main/data/database-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
MessageReactionCreateMutationInput,
|
||||
MessageReactionCreateMutationOutput,
|
||||
} from '@/shared/mutations/messages/message-reaction-create';
|
||||
import { eventBus } from '@/shared/lib/event-bus';
|
||||
import {
|
||||
fetchEntry,
|
||||
fetchUser,
|
||||
mapEntry,
|
||||
mapMessageReaction,
|
||||
} from '@/main/utils';
|
||||
import { fetchEntry, mapEntry, mapMessageReaction } from '@/main/utils';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class MessageReactionCreateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<MessageReactionCreateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: MessageReactionCreateMutationInput
|
||||
): Promise<MessageReactionCreateMutationOutput> {
|
||||
const workspaceDatabase = await databaseService.getWorkspaceDatabase(
|
||||
input.userId
|
||||
);
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const message = await workspaceDatabase
|
||||
const message = await workspace.database
|
||||
.selectFrom('messages')
|
||||
.selectAll()
|
||||
.where('id', '=', input.messageId)
|
||||
@@ -43,11 +37,11 @@ export class MessageReactionCreateMutationHandler
|
||||
);
|
||||
}
|
||||
|
||||
const existingMessageReaction = await workspaceDatabase
|
||||
const existingMessageReaction = await workspace.database
|
||||
.selectFrom('message_reactions')
|
||||
.selectAll()
|
||||
.where('message_id', '=', input.messageId)
|
||||
.where('collaborator_id', '=', input.userId)
|
||||
.where('collaborator_id', '=', workspace.userId)
|
||||
.where('reaction', '=', input.reaction)
|
||||
.executeTakeFirst();
|
||||
|
||||
@@ -57,15 +51,7 @@ export class MessageReactionCreateMutationHandler
|
||||
};
|
||||
}
|
||||
|
||||
const user = await fetchUser(workspaceDatabase, input.userId);
|
||||
if (!user) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.UserNotFound,
|
||||
'There was an error while fetching the user. Please make sure you are logged in.'
|
||||
);
|
||||
}
|
||||
|
||||
const root = await fetchEntry(workspaceDatabase, input.rootId);
|
||||
const root = await fetchEntry(workspace.database, input.rootId);
|
||||
if (!root) {
|
||||
throw new MutationError(
|
||||
MutationErrorCode.RootNotFound,
|
||||
@@ -76,8 +62,8 @@ export class MessageReactionCreateMutationHandler
|
||||
if (
|
||||
!canCreateMessageReaction({
|
||||
user: {
|
||||
userId: input.userId,
|
||||
role: user.role,
|
||||
userId: workspace.userId,
|
||||
role: workspace.role,
|
||||
},
|
||||
root: mapEntry(root),
|
||||
message: {
|
||||
@@ -92,7 +78,7 @@ export class MessageReactionCreateMutationHandler
|
||||
);
|
||||
}
|
||||
|
||||
const { createdMessageReaction, createdMutation } = await workspaceDatabase
|
||||
const { createdMessageReaction, createdMutation } = await workspace.database
|
||||
.transaction()
|
||||
.execute(async (trx) => {
|
||||
const createdMessageReaction = await trx
|
||||
@@ -100,7 +86,7 @@ export class MessageReactionCreateMutationHandler
|
||||
.returningAll()
|
||||
.values({
|
||||
message_id: input.messageId,
|
||||
collaborator_id: input.userId,
|
||||
collaborator_id: workspace.userId,
|
||||
reaction: input.reaction,
|
||||
root_id: input.rootId,
|
||||
version: 0n,
|
||||
@@ -153,14 +139,12 @@ export class MessageReactionCreateMutationHandler
|
||||
throw new Error('Failed to create message reaction');
|
||||
}
|
||||
|
||||
eventBus.publish({
|
||||
type: 'mutation_created',
|
||||
userId: input.userId,
|
||||
});
|
||||
workspace.mutations.triggerSync();
|
||||
|
||||
eventBus.publish({
|
||||
type: 'message_reaction_created',
|
||||
userId: input.userId,
|
||||
accountId: workspace.accountId,
|
||||
workspaceId: workspace.id,
|
||||
messageReaction: mapMessageReaction(createdMessageReaction),
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
IdType,
|
||||
} from '@colanode/core';
|
||||
|
||||
import { databaseService } from '@/main/data/database-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
MessageReactionDeleteMutationInput,
|
||||
@@ -12,22 +11,22 @@ import {
|
||||
} from '@/shared/mutations/messages/message-reaction-delete';
|
||||
import { mapMessageReaction } from '@/main/utils';
|
||||
import { eventBus } from '@/shared/lib/event-bus';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class MessageReactionDeleteMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<MessageReactionDeleteMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: MessageReactionDeleteMutationInput
|
||||
): Promise<MessageReactionDeleteMutationOutput> {
|
||||
const workspaceDatabase = await databaseService.getWorkspaceDatabase(
|
||||
input.userId
|
||||
);
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const existingMessageReaction = await workspaceDatabase
|
||||
const existingMessageReaction = await workspace.database
|
||||
.selectFrom('message_reactions')
|
||||
.selectAll()
|
||||
.where('message_id', '=', input.messageId)
|
||||
.where('collaborator_id', '=', input.userId)
|
||||
.where('collaborator_id', '=', workspace.userId)
|
||||
.where('reaction', '=', input.reaction)
|
||||
.executeTakeFirst();
|
||||
|
||||
@@ -37,7 +36,7 @@ export class MessageReactionDeleteMutationHandler
|
||||
};
|
||||
}
|
||||
|
||||
const { deletedMessageReaction, createdMutation } = await workspaceDatabase
|
||||
const { deletedMessageReaction, createdMutation } = await workspace.database
|
||||
.transaction()
|
||||
.execute(async (trx) => {
|
||||
const deletedMessageReaction = await trx
|
||||
@@ -47,7 +46,7 @@ export class MessageReactionDeleteMutationHandler
|
||||
deleted_at: new Date().toISOString(),
|
||||
})
|
||||
.where('message_id', '=', input.messageId)
|
||||
.where('collaborator_id', '=', input.userId)
|
||||
.where('collaborator_id', '=', workspace.userId)
|
||||
.where('reaction', '=', input.reaction)
|
||||
.executeTakeFirst();
|
||||
|
||||
@@ -89,14 +88,12 @@ export class MessageReactionDeleteMutationHandler
|
||||
throw new Error('Failed to delete message reaction');
|
||||
}
|
||||
|
||||
eventBus.publish({
|
||||
type: 'mutation_created',
|
||||
userId: input.userId,
|
||||
});
|
||||
workspace.mutations.triggerSync();
|
||||
|
||||
eventBus.publish({
|
||||
type: 'message_reaction_deleted',
|
||||
userId: input.userId,
|
||||
accountId: workspace.accountId,
|
||||
workspaceId: workspace.id,
|
||||
messageReaction: mapMessageReaction(deletedMessageReaction),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { PageAttributes } from '@colanode/core';
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import { mapContentsToBlocks } from '@/shared/lib/editor';
|
||||
import {
|
||||
@@ -9,16 +8,19 @@ import {
|
||||
PageContentUpdateMutationOutput,
|
||||
} from '@/shared/mutations/pages/page-content-update';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class PageContentUpdateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<PageContentUpdateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: PageContentUpdateMutationInput
|
||||
): Promise<PageContentUpdateMutationOutput> {
|
||||
const result = await entryService.updateEntry<PageAttributes>(
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const result = await workspace.entries.updateEntry<PageAttributes>(
|
||||
input.pageId,
|
||||
input.userId,
|
||||
(attributes) => {
|
||||
const indexMap = new Map<string, string>();
|
||||
if (attributes.content) {
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import { generateId, IdType, PageAttributes } from '@colanode/core';
|
||||
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
PageCreateMutationInput,
|
||||
PageCreateMutationOutput,
|
||||
} from '@/shared/mutations/pages/page-create';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class PageCreateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<PageCreateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: PageCreateMutationInput
|
||||
): Promise<PageCreateMutationOutput> {
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const id = generateId(IdType.Page);
|
||||
const attributes: PageAttributes = {
|
||||
type: 'page',
|
||||
@@ -21,7 +24,7 @@ export class PageCreateMutationHandler
|
||||
name: input.name,
|
||||
};
|
||||
|
||||
await entryService.createEntry(input.userId, {
|
||||
await workspace.entries.createEntry({
|
||||
id,
|
||||
attributes,
|
||||
parentId: input.parentId,
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
PageDeleteMutationInput,
|
||||
PageDeleteMutationOutput,
|
||||
} from '@/shared/mutations/pages/page-delete';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
|
||||
export class PageDeleteMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<PageDeleteMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: PageDeleteMutationInput
|
||||
): Promise<PageDeleteMutationOutput> {
|
||||
await entryService.deleteEntry(input.pageId, input.userId);
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
await workspace.entries.deleteEntry(input.pageId);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import { PageAttributes } from '@colanode/core';
|
||||
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import {
|
||||
PageUpdateMutationInput,
|
||||
PageUpdateMutationOutput,
|
||||
} from '@/shared/mutations/pages/page-update';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class PageUpdateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<PageUpdateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: PageUpdateMutationInput
|
||||
): Promise<PageUpdateMutationOutput> {
|
||||
const result = await entryService.updateEntry<PageAttributes>(
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const result = await workspace.entries.updateEntry<PageAttributes>(
|
||||
input.pageId,
|
||||
input.userId,
|
||||
(attributes) => {
|
||||
attributes.name = input.name;
|
||||
attributes.avatar = input.avatar;
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import { RecordAttributes } from '@colanode/core';
|
||||
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import {
|
||||
RecordAvatarUpdateMutationInput,
|
||||
RecordAvatarUpdateMutationOutput,
|
||||
} from '@/shared/mutations/records/record-avatar-update';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class RecordAvatarUpdateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<RecordAvatarUpdateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: RecordAvatarUpdateMutationInput
|
||||
): Promise<RecordAvatarUpdateMutationOutput> {
|
||||
const result = await entryService.updateEntry<RecordAttributes>(
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const result = await workspace.entries.updateEntry<RecordAttributes>(
|
||||
input.recordId,
|
||||
input.userId,
|
||||
(attributes) => {
|
||||
attributes.avatar = input.avatar;
|
||||
return attributes;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { RecordAttributes } from '@colanode/core';
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import { mapContentsToBlocks } from '@/shared/lib/editor';
|
||||
import {
|
||||
@@ -9,16 +8,19 @@ import {
|
||||
RecordContentUpdateMutationOutput,
|
||||
} from '@/shared/mutations/records/record-content-update';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class RecordContentUpdateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<RecordContentUpdateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: RecordContentUpdateMutationInput
|
||||
): Promise<RecordContentUpdateMutationOutput> {
|
||||
const result = await entryService.updateEntry<RecordAttributes>(
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const result = await workspace.entries.updateEntry<RecordAttributes>(
|
||||
input.recordId,
|
||||
input.userId,
|
||||
(attributes) => {
|
||||
const indexMap = new Map<string, string>();
|
||||
if (attributes.content) {
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import { generateId, IdType, RecordAttributes } from '@colanode/core';
|
||||
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
RecordCreateMutationInput,
|
||||
RecordCreateMutationOutput,
|
||||
} from '@/shared/mutations/records/record-create';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class RecordCreateMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<RecordCreateMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: RecordCreateMutationInput
|
||||
): Promise<RecordCreateMutationOutput> {
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const id = generateId(IdType.Record);
|
||||
const attributes: RecordAttributes = {
|
||||
type: 'record',
|
||||
@@ -22,7 +25,7 @@ export class RecordCreateMutationHandler
|
||||
fields: input.fields ?? {},
|
||||
};
|
||||
|
||||
await entryService.createEntry(input.userId, {
|
||||
await workspace.entries.createEntry({
|
||||
id,
|
||||
attributes,
|
||||
parentId: input.databaseId,
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import {
|
||||
RecordDeleteMutationInput,
|
||||
RecordDeleteMutationOutput,
|
||||
} from '@/shared/mutations/records/record-delete';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class RecordDeleteMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<RecordDeleteMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: RecordDeleteMutationInput
|
||||
): Promise<RecordDeleteMutationOutput> {
|
||||
await entryService.deleteEntry(input.recordId, input.userId);
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
await workspace.entries.deleteEntry(input.recordId);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import { RecordAttributes } from '@colanode/core';
|
||||
|
||||
import { entryService } from '@/main/services/entry-service';
|
||||
import { MutationHandler } from '@/main/types';
|
||||
import { MutationError, MutationErrorCode } from '@/shared/mutations';
|
||||
import {
|
||||
RecordFieldValueDeleteMutationInput,
|
||||
RecordFieldValueDeleteMutationOutput,
|
||||
} from '@/shared/mutations/records/record-field-value-delete';
|
||||
import { WorkspaceMutationHandlerBase } from '@/main/mutations/workspace-mutation-handler-base';
|
||||
|
||||
export class RecordFieldValueDeleteMutationHandler
|
||||
extends WorkspaceMutationHandlerBase
|
||||
implements MutationHandler<RecordFieldValueDeleteMutationInput>
|
||||
{
|
||||
async handleMutation(
|
||||
input: RecordFieldValueDeleteMutationInput
|
||||
): Promise<RecordFieldValueDeleteMutationOutput> {
|
||||
const result = await entryService.updateEntry<RecordAttributes>(
|
||||
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
|
||||
|
||||
const result = await workspace.entries.updateEntry<RecordAttributes>(
|
||||
input.recordId,
|
||||
input.userId,
|
||||
(attributes) => {
|
||||
delete attributes.fields[input.fieldId];
|
||||
return attributes;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user