Refactor workspaces and accounts databases and routes locally

This commit is contained in:
Hakan Shehu
2025-10-11 11:44:08 +02:00
parent aa5fac683e
commit ac54c510da
445 changed files with 2548 additions and 4261 deletions

View File

@@ -16,7 +16,7 @@ import { updateElectronApp, UpdateSourceType } from 'update-electron-app';
import { eventBus } from '@colanode/client/lib';
import { MutationInput, MutationMap } from '@colanode/client/mutations';
import { QueryInput, QueryMap } from '@colanode/client/queries';
import { TempFile } from '@colanode/client/types';
import { TempFile, ThemeMode, WindowSize } from '@colanode/client/types';
import {
createDebugger,
extractFileSubtype,
@@ -49,13 +49,19 @@ updateElectronApp({
const createWindow = async () => {
await app.migrate();
const themeMode = (await app.metadata.get('theme.mode'))?.value;
if (themeMode) {
const themeMetadata = await app.metadata.get('app', 'theme.mode');
if (themeMetadata) {
const themeMode = JSON.parse(themeMetadata.value) as ThemeMode;
nativeTheme.themeSource = themeMode;
}
let windowSize: WindowSize | undefined;
const windowSizeMetadata = await app.metadata.get('app', 'window.size');
if (windowSizeMetadata) {
windowSize = JSON.parse(windowSizeMetadata.value) as WindowSize;
}
// Create the browser window.
let windowSize = (await app.metadata.get('window.size'))?.value;
const mainWindow = new BrowserWindow({
width: windowSize?.width ?? 1200,
height: windowSize?.height ?? 800,
@@ -81,7 +87,7 @@ const createWindow = async () => {
fullscreen: false,
};
app.metadata.set('window.size', windowSize);
app.metadata.set('app', 'window.size', JSON.stringify(windowSize));
});
mainWindow.on('enter-full-screen', () => {
@@ -91,7 +97,7 @@ const createWindow = async () => {
fullscreen: true,
};
app.metadata.set('window.size', windowSize);
app.metadata.set('app', 'window.size', JSON.stringify(windowSize));
});
mainWindow.on('leave-full-screen', () => {
@@ -101,7 +107,7 @@ const createWindow = async () => {
fullscreen: false,
};
app.metadata.set('window.size', windowSize);
app.metadata.set('app', 'window.size', JSON.stringify(windowSize));
});
// and load the index.html of the app.
@@ -118,12 +124,13 @@ const createWindow = async () => {
const subscriptionId = eventBus.subscribe((event) => {
mainWindow.webContents.send('event', event);
if (
event.type === 'app.metadata.updated' &&
event.type === 'metadata.updated' &&
event.metadata.key === 'theme.mode'
) {
nativeTheme.themeSource = event.metadata.value;
const themeMode = JSON.parse(event.metadata.value) as ThemeMode;
nativeTheme.themeSource = themeMode;
} else if (
event.type === 'app.metadata.deleted' &&
event.type === 'metadata.deleted' &&
event.metadata.key === 'theme.mode'
) {
nativeTheme.themeSource = 'system';

View File

@@ -31,8 +31,8 @@ export class AppBadge {
return;
}
const accounts = this.app.getAccounts();
if (accounts.length === 0) {
const workspaces = this.app.getWorkspaces();
if (workspaces.length === 0) {
electronApp?.dock?.setBadge('');
return;
}
@@ -40,15 +40,11 @@ export class AppBadge {
let hasUnread = false;
let unreadCount = 0;
for (const account of accounts) {
const workspaces = account.getWorkspaces();
for (const workspace of workspaces) {
const radarData = workspace.radar.getData();
hasUnread = hasUnread || radarData.state.hasUnread;
unreadCount = unreadCount + radarData.state.unreadCount;
}
}
if (unreadCount > 0) {
electronApp?.dock?.setBadge(unreadCount.toString());

View File

@@ -5,48 +5,29 @@ import { PathService } from '@colanode/client/services';
export class DesktopPathService implements PathService {
private readonly nativePath = path;
private readonly appPath = app.getPath('userData');
private readonly appDatabasePath = this.nativePath.join(
this.appPath,
'app.db'
);
private readonly accountsDirectoryPath = this.nativePath.join(
private readonly avatarsPath = this.nativePath.join(this.appPath, 'avatars');
private readonly workspacesPath = this.nativePath.join(
this.appPath,
'accounts'
'workspaces'
);
private getAccountDirectoryPath(accountId: string): string {
return this.nativePath.join(this.accountsDirectoryPath, accountId);
private getWorkspaceDirectoryPath(userId: string): string {
return this.nativePath.join(this.workspacesPath, userId);
}
private getWorkspaceDirectoryPath(
accountId: string,
workspaceId: string
): string {
private getWorkspaceFilesDirectoryPath(userId: string): string {
return this.nativePath.join(
this.getAccountDirectoryPath(accountId),
'workspaces',
workspaceId
);
}
private getWorkspaceFilesDirectoryPath(
accountId: string,
workspaceId: string
): string {
return this.nativePath.join(
this.getWorkspaceDirectoryPath(accountId, workspaceId),
this.getWorkspaceDirectoryPath(userId),
'files'
);
}
private getAccountAvatarsDirectoryPath(accountId: string): string {
return this.nativePath.join(
this.getAccountDirectoryPath(accountId),
'avatars'
);
}
private getAssetsSourcePath(): string {
if (app.isPackaged) {
return this.nativePath.join(process.resourcesPath, 'assets');
@@ -62,67 +43,48 @@ export class DesktopPathService implements PathService {
return this.appDatabasePath;
}
public get accounts(): string {
return this.accountsDirectoryPath;
}
public get temp(): string {
return this.nativePath.join(this.appPath, 'temp');
}
public get avatars(): string {
return this.avatarsPath;
}
public tempFile(name: string): string {
return this.nativePath.join(this.appPath, 'temp', name);
}
public account(accountId: string): string {
return this.getAccountDirectoryPath(accountId);
public avatar(avatarId: string): string {
return this.nativePath.join(this.avatarsPath, avatarId + '.jpeg');
}
public accountDatabase(accountId: string): string {
public workspace(userId: string): string {
return this.getWorkspaceDirectoryPath(userId);
}
public workspaceDatabase(userId: string): string {
return this.nativePath.join(
this.getAccountDirectoryPath(accountId),
'account.db'
);
}
public workspace(accountId: string, workspaceId: string): string {
return this.getWorkspaceDirectoryPath(accountId, workspaceId);
}
public workspaceDatabase(accountId: string, workspaceId: string): string {
return this.nativePath.join(
this.getWorkspaceDirectoryPath(accountId, workspaceId),
this.getWorkspaceDirectoryPath(userId),
'workspace.db'
);
}
public workspaceFiles(accountId: string, workspaceId: string): string {
return this.getWorkspaceFilesDirectoryPath(accountId, workspaceId);
public workspaceFiles(userId: string): string {
return this.getWorkspaceFilesDirectoryPath(userId);
}
public workspaceFile(
accountId: string,
workspaceId: string,
userId: string,
fileId: string,
extension: string
): string {
return this.nativePath.join(
this.getWorkspaceFilesDirectoryPath(accountId, workspaceId),
this.getWorkspaceFilesDirectoryPath(userId),
fileId + extension
);
}
public accountAvatars(accountId: string): string {
return this.getAccountAvatarsDirectoryPath(accountId);
}
public accountAvatar(accountId: string, avatarId: string): string {
return this.nativePath.join(
this.getAccountAvatarsDirectoryPath(accountId),
avatarId + '.jpeg'
);
}
public dirname(dir: string): string {
return this.nativePath.dirname(dir);
}

View File

@@ -3,39 +3,22 @@ import { Paths, File, Directory } from 'expo-file-system';
import { PathService } from '@colanode/client/services';
export class MobilePathService implements PathService {
private readonly accountsDirectoryPath = new Directory(
private readonly avatarsDirectoryPath = new Directory(
Paths.document,
'accounts'
'avatars'
);
private getAccountDirectoryPath(accountId: string): string {
return new Directory(this.accountsDirectoryPath, accountId).uri;
private readonly workspacesDirectoryPath = new Directory(
Paths.document,
'workspaces'
);
private getWorkspaceDirectoryPath(userId: string): string {
return new Directory(this.workspacesDirectoryPath, userId).uri;
}
private getWorkspaceDirectoryPath(
accountId: string,
workspaceId: string
): string {
return new Directory(
this.getAccountDirectoryPath(accountId),
'workspaces',
workspaceId
).uri;
}
private getWorkspaceFilesDirectoryPath(
accountId: string,
workspaceId: string
): string {
return new Directory(
this.getWorkspaceDirectoryPath(accountId, workspaceId),
'files'
).uri;
}
private getAccountAvatarsDirectoryPath(accountId: string): string {
return new Directory(this.getAccountDirectoryPath(accountId), 'avatars')
.uri;
private getWorkspaceFilesDirectoryPath(userId: string): string {
return new Directory(this.getWorkspaceDirectoryPath(userId), 'files').uri;
}
private getAssetsSourcePath(): string {
@@ -50,64 +33,45 @@ export class MobilePathService implements PathService {
return new File(Paths.document, 'app.db').uri;
}
public get accounts(): string {
return this.accountsDirectoryPath.uri;
public get avatars(): string {
return this.avatarsDirectoryPath.uri;
}
public get temp(): string {
return new Directory(Paths.document, 'temp').uri;
}
public avatar(avatarId: string): string {
return new File(this.avatarsDirectoryPath, avatarId + '.jpeg').uri;
}
public tempFile(name: string): string {
return new File(Paths.document, 'temp', name).uri;
}
public account(accountId: string): string {
return this.getAccountDirectoryPath(accountId);
public workspace(userId: string): string {
return this.getWorkspaceDirectoryPath(userId);
}
public accountDatabase(accountId: string): string {
return new File(this.getAccountDirectoryPath(accountId), 'account.db').uri;
public workspaceDatabase(userId: string): string {
return new File(this.getWorkspaceDirectoryPath(userId), 'workspace.db').uri;
}
public workspace(accountId: string, workspaceId: string): string {
return this.getWorkspaceDirectoryPath(accountId, workspaceId);
}
public workspaceDatabase(accountId: string, workspaceId: string): string {
return new File(
this.getWorkspaceDirectoryPath(accountId, workspaceId),
'workspace.db'
).uri;
}
public workspaceFiles(accountId: string, workspaceId: string): string {
return this.getWorkspaceFilesDirectoryPath(accountId, workspaceId);
public workspaceFiles(userId: string): string {
return this.getWorkspaceFilesDirectoryPath(userId);
}
public workspaceFile(
accountId: string,
workspaceId: string,
userId: string,
fileId: string,
extension: string
): string {
return new File(
this.getWorkspaceFilesDirectoryPath(accountId, workspaceId),
this.getWorkspaceFilesDirectoryPath(userId),
fileId + extension
).uri;
}
public accountAvatars(accountId: string): string {
return this.getAccountAvatarsDirectoryPath(accountId);
}
public accountAvatar(accountId: string, avatarId: string): string {
return new File(
this.getAccountAvatarsDirectoryPath(accountId),
avatarId + '.jpeg'
).uri;
}
public dirname(path: string): string {
const info = Paths.info(path);
if (info.isDirectory) {

View File

@@ -77,7 +77,7 @@ export class WebFileSystem implements FileSystem {
: new ArrayBuffer(data.byteLength);
if (!(data.buffer instanceof ArrayBuffer)) {
const view = new Uint8Array<ArrayBuffer>(arrayBuffer);
const view = new Uint8Array(arrayBuffer);
view.set(data);
}

View File

@@ -2,9 +2,10 @@ import { PathService } from '@colanode/client/services';
export class WebPathService implements PathService {
private readonly appPath = '';
private readonly appDatabasePath = this.join(this.appPath, 'app.db');
private readonly accountsDirectoryPath = this.join(this.appPath, 'accounts');
private readonly assetsSourcePath = 'assets';
private readonly appDatabasePath = this.join(this.appPath, 'app.db');
private readonly avatarsPath = this.join(this.appPath, 'avatars');
private readonly workspacesPath = this.join(this.appPath, 'workspaces');
public get app() {
return this.appPath;
@@ -14,8 +15,8 @@ export class WebPathService implements PathService {
return this.appDatabasePath;
}
public get accounts() {
return this.accountsDirectoryPath;
public get avatars() {
return this.avatarsPath;
}
public get temp() {
@@ -42,44 +43,28 @@ export class WebPathService implements PathService {
return this.join(this.appPath, 'temp', name);
}
public account(accountId: string): string {
return this.join(this.accountsDirectoryPath, accountId);
public avatar(avatarId: string): string {
return this.join(this.avatarsPath, avatarId + '.jpeg');
}
public accountDatabase(accountId: string): string {
return this.join(this.account(accountId), 'account.db');
public workspace(userId: string): string {
return this.join(this.workspacesPath, userId);
}
public workspace(accountId: string, workspaceId: string): string {
return this.join(this.account(accountId), 'workspaces', workspaceId);
public workspaceDatabase(userId: string): string {
return this.join(this.workspace(userId), 'workspace.db');
}
public workspaceDatabase(accountId: string, workspaceId: string): string {
return this.join(this.workspace(accountId, workspaceId), 'workspace.db');
}
public workspaceFiles(accountId: string, workspaceId: string): string {
return this.join(this.workspace(accountId, workspaceId), 'files');
public workspaceFiles(userId: string): string {
return this.join(this.workspace(userId), 'files');
}
public workspaceFile(
accountId: string,
workspaceId: string,
userId: string,
fileId: string,
extension: string
): string {
return this.join(
this.workspaceFiles(accountId, workspaceId),
fileId + extension
);
}
public accountAvatars(accountId: string): string {
return this.join(this.account(accountId), 'avatars');
}
public accountAvatar(accountId: string, avatarId: string): string {
return this.join(this.accountAvatars(accountId), avatarId + '.jpeg');
return this.join(this.workspaceFiles(userId), fileId + extension);
}
public dirname(path: string): string {

34
package-lock.json generated
View File

@@ -11507,9 +11507,9 @@
}
},
"node_modules/@tanstack/db": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/@tanstack/db/-/db-0.4.4.tgz",
"integrity": "sha512-G+abJtW6jBjAwMSgbaSYuwUJFUaxTY7kb+PyT3Y6r2pr795v+QQNHe6pniQeqrLp3M5nnx3rws+XfeqoDgN/VQ==",
"version": "0.4.7",
"resolved": "https://registry.npmjs.org/@tanstack/db/-/db-0.4.7.tgz",
"integrity": "sha512-SXwbqvWfBkP7VkmOcAjvy6vgKO2IjuE4jhlj2Th94f7fhFCXf/BgcAVB5ZbD59Dygt8AkeVQKRVr/k7z/D/7RQ==",
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.0.0",
@@ -11556,13 +11556,13 @@
}
},
"node_modules/@tanstack/query-db-collection": {
"version": "0.2.25",
"resolved": "https://registry.npmjs.org/@tanstack/query-db-collection/-/query-db-collection-0.2.25.tgz",
"integrity": "sha512-Io07hIX7VLg7cO2/+Y6c9Bf2Y8xWtxt31i2lLswW2lx0sTZvK6sIpLvld8SxbMeRJHrwtXra++V0O6a1Y2ALGA==",
"version": "0.2.28",
"resolved": "https://registry.npmjs.org/@tanstack/query-db-collection/-/query-db-collection-0.2.28.tgz",
"integrity": "sha512-rD+x1lVvz8BtdpEThyQiYaU8VoqlJPhPwpQEgPGhPu913AyXVDiUKu1rhQFRP6e9Zz0tXF/0kh6bFG+khov4ag==",
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"@tanstack/db": "0.4.4"
"@tanstack/db": "0.4.7"
},
"peerDependencies": {
"@tanstack/query-core": "^5.0.0",
@@ -11570,13 +11570,13 @@
}
},
"node_modules/@tanstack/react-db": {
"version": "0.1.26",
"resolved": "https://registry.npmjs.org/@tanstack/react-db/-/react-db-0.1.26.tgz",
"integrity": "sha512-8itSp4bd+PU7/yImMn3eAcMq2AEzDVuPhJ2K5Pyqh7f9qhXlMDdxcm1K9BpZpaQJOJEcLYls6FDnaNNkyc5/hQ==",
"version": "0.1.29",
"resolved": "https://registry.npmjs.org/@tanstack/react-db/-/react-db-0.1.29.tgz",
"integrity": "sha512-57MI8dtAIoxl0xYVbMysHZewIylxi2l3LPNS3qy9nGBGxuM0y6anu5KyR8rWYL+ylFK4nSvc86ebT569O7Vu3A==",
"license": "MIT",
"dependencies": {
"@tanstack/db": "0.4.4",
"use-sync-external-store": "^1.5.0"
"@tanstack/db": "0.4.7",
"use-sync-external-store": "^1.6.0"
},
"peerDependencies": {
"react": ">=16.8.0"
@@ -29352,9 +29352,9 @@
}
},
"node_modules/use-sync-external-store": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
"integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -30886,8 +30886,8 @@
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-tooltip": "^1.2.8",
"@react-oauth/google": "^0.12.2",
"@tanstack/query-db-collection": "^0.2.25",
"@tanstack/react-db": "^0.1.26",
"@tanstack/query-db-collection": "^0.2.28",
"@tanstack/react-db": "^0.1.29",
"@tanstack/react-query": "^5.87.4",
"@tanstack/react-router": "^1.131.45",
"@tanstack/react-virtual": "^3.13.12",

View File

@@ -1,2 +0,0 @@
export * from './schema';
export * from './migrations';

View File

@@ -1,11 +0,0 @@
import { Migration } from 'kysely';
import { createWorkspacesTable } from './00001-create-workspaces-table';
import { createMetadataTable } from './00002-create-metadata-table';
import { createAvatarsTable } from './00003-create-avatars-table';
export const accountDatabaseMigrations: Record<string, Migration> = {
'00001-create-workspaces-table': createWorkspacesTable,
'00002-create-metadata-table': createMetadataTable,
'00003-create-avatars-table': createAvatarsTable,
};

View File

@@ -1,49 +0,0 @@
import { ColumnType, Insertable, Selectable, Updateable } from 'kysely';
import { WorkspaceRole } from '@colanode/core';
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>;
storage_limit: ColumnType<string, string, string>;
max_file_size: ColumnType<string, string, string>;
created_at: ColumnType<string, string, never>;
}
export type SelectWorkspace = Selectable<WorkspaceTable>;
export type CreateWorkspace = Insertable<WorkspaceTable>;
export type UpdateWorkspace = Updateable<WorkspaceTable>;
interface AccountMetadataTable {
key: ColumnType<string, string, never>;
value: ColumnType<string, string, string>;
created_at: ColumnType<string, string, never>;
updated_at: ColumnType<string | null, string | null, string | null>;
}
export type SelectAccountMetadata = Selectable<AccountMetadataTable>;
export type CreateAccountMetadata = Insertable<AccountMetadataTable>;
export type UpdateAccountMetadata = Updateable<AccountMetadataTable>;
interface AvatarTable {
id: ColumnType<string, string, never>;
path: ColumnType<string, string, string>;
size: ColumnType<number, number, number>;
created_at: ColumnType<string, string, never>;
opened_at: ColumnType<string, string, string>;
}
export type SelectAvatar = Selectable<AvatarTable>;
export type CreateAvatar = Insertable<AvatarTable>;
export type UpdateAvatar = Updateable<AvatarTable>;
export interface AccountDatabaseSchema {
workspaces: WorkspaceTable;
metadata: AccountMetadataTable;
avatars: AvatarTable;
}

View File

@@ -12,28 +12,6 @@ export const createServersTable: Migration = {
.addColumn('created_at', 'text', (col) => col.notNull())
.addColumn('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.2.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.2.0',
created_at: new Date().toISOString(),
},
])
.execute();
},
down: async (db) => {
await db.schema.dropTable('servers').execute();

View File

@@ -4,10 +4,12 @@ export const createMetadataTable: Migration = {
up: async (db) => {
await db.schema
.createTable('metadata')
.addColumn('key', 'text', (col) => col.notNull().primaryKey())
.addColumn('namespace', 'text', (col) => col.notNull())
.addColumn('key', 'text', (col) => col.notNull())
.addColumn('value', 'text', (col) => col.notNull())
.addColumn('created_at', 'text', (col) => col.notNull())
.addColumn('updated_at', 'text')
.addPrimaryKeyConstraint('pk_metadata', ['namespace', 'key'])
.execute();
},
down: async (db) => {

View File

@@ -1,16 +0,0 @@
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();
},
};

View File

@@ -1,16 +0,0 @@
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();
},
};

View File

@@ -4,8 +4,8 @@ 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('user_id', 'text', (col) => col.notNull().primaryKey())
.addColumn('workspace_id', 'text', (col) => col.notNull())
.addColumn('account_id', 'text', (col) => col.notNull())
.addColumn('name', 'text', (col) => col.notNull())
.addColumn('description', 'text')
@@ -14,6 +14,7 @@ export const createWorkspacesTable: Migration = {
.addColumn('storage_limit', 'integer', (col) => col.notNull())
.addColumn('max_file_size', 'integer', (col) => col.notNull())
.addColumn('created_at', 'text', (col) => col.notNull())
.addColumn('updated_at', 'text')
.execute();
},
down: async (db) => {

View File

@@ -1,16 +0,0 @@
import { Migration } from 'kysely';
export const dropDeletedTokensTable: Migration = {
up: async (db) => {
await db.schema.dropTable('deleted_tokens').execute();
},
down: 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();
},
};

View File

@@ -1,23 +1,23 @@
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';
import { createMetadataTable } from './00002-create-metadata-table';
import { createAccountsTable } from './00003-create-accounts-table';
import { createWorkspacesTable } from './00004-create-workspaces-table';
import { createJobsTable } from './00005-create-jobs-table';
import { createJobSchedulesTable } from './00006-create-job-schedules-table';
import { dropDeletedTokensTable } from './00007-drop-deleted-tokens-table';
import { createTempFilesTable } from './00008-create-temp-files-table';
import { createTempFilesTable } from './00007-create-temp-files-table';
import { createAvatarsTable } from './00008-create-avatars-table';
import { createTabsTable } from './00009-create-tabs-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,
'00002-create-metadata-table': createMetadataTable,
'00003-create-accounts-table': createAccountsTable,
'00004-create-workspaces-table': createWorkspacesTable,
'00005-create-jobs-table': createJobsTable,
'00006-create-job-schedules-table': createJobSchedulesTable,
'00007-drop-deleted-tokens-table': dropDeletedTokensTable,
'00008-create-temp-files-table': createTempFilesTable,
'00007-create-temp-files-table': createTempFilesTable,
'00008-create-avatars-table': createAvatarsTable,
'00009-create-tabs-table': createTabsTable,
};

View File

@@ -1,7 +1,7 @@
import { ColumnType, Insertable, Selectable, Updateable } from 'kysely';
import { JobScheduleStatus, JobStatus } from '@colanode/client/jobs';
import { FileSubtype } from '@colanode/core';
import { FileSubtype, WorkspaceRole } from '@colanode/core';
interface ServerTable {
domain: ColumnType<string, string, never>;
@@ -17,6 +17,18 @@ export type SelectServer = Selectable<ServerTable>;
export type CreateServer = Insertable<ServerTable>;
export type UpdateServer = Updateable<ServerTable>;
interface MetadataTable {
namespace: ColumnType<string, string, never>;
key: ColumnType<string, string, never>;
value: ColumnType<string, string, string>;
created_at: ColumnType<string, string, never>;
updated_at: ColumnType<string | null, string | null, string | null>;
}
export type SelectMetadata = Selectable<MetadataTable>;
export type CreateMetadata = Insertable<MetadataTable>;
export type UpdateMetadata = Updateable<MetadataTable>;
interface AccountTable {
id: ColumnType<string, string, never>;
server: ColumnType<string, string, never>;
@@ -34,17 +46,6 @@ export type SelectAccount = Selectable<AccountTable>;
export type CreateAccount = Insertable<AccountTable>;
export type UpdateAccount = Updateable<AccountTable>;
interface AppMetadataTable {
key: ColumnType<string, string, never>;
value: ColumnType<string, string, string>;
created_at: ColumnType<string, string, never>;
updated_at: ColumnType<string | null, string | null, string | null>;
}
export type SelectAppMetadata = Selectable<AppMetadataTable>;
export type CreateAppMetadata = Insertable<AppMetadataTable>;
export type UpdateAppMetadata = Updateable<AppMetadataTable>;
export interface JobTableSchema {
id: ColumnType<string, string, never>;
queue: ColumnType<string, string, never>;
@@ -109,12 +110,44 @@ export type SelectTab = Selectable<TabTable>;
export type InsertTab = Insertable<TabTable>;
export type UpdateTab = Updateable<TabTable>;
interface WorkspacesTable {
user_id: ColumnType<string, string, never>;
workspace_id: ColumnType<string, string, string>;
account_id: ColumnType<string, string, string>;
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>;
storage_limit: ColumnType<string, string, string>;
max_file_size: ColumnType<string, string, string>;
created_at: ColumnType<string, string, string>;
updated_at: ColumnType<string | null, string | null, string | null>;
}
export type SelectWorkspace = Selectable<WorkspacesTable>;
export type InsertWorkspace = Insertable<WorkspacesTable>;
export type UpdateWorkspace = Updateable<WorkspacesTable>;
interface AvatarsTable {
id: ColumnType<string, string, never>;
path: ColumnType<string, string, string>;
size: ColumnType<number, number, number>;
created_at: ColumnType<string, string, string>;
opened_at: ColumnType<string, string, string>;
}
export type SelectAvatar = Selectable<AvatarsTable>;
export type InsertAvatar = Insertable<AvatarsTable>;
export type UpdateAvatar = Updateable<AvatarsTable>;
export interface AppDatabaseSchema {
servers: ServerTable;
metadata: MetadataTable;
accounts: AccountTable;
metadata: AppMetadataTable;
workspaces: WorkspacesTable;
jobs: JobTableSchema;
job_schedules: JobScheduleTableSchema;
temp_files: TempFileTable;
avatars: AvatarsTable;
tabs: TabTable;
}

View File

@@ -1,24 +0,0 @@
import { Migration } from 'kysely';
export const createFileStatesTable: Migration = {
up: async (db) => {
await db.schema
.createTable('file_states')
.addColumn('id', 'text', (col) => col.notNull().primaryKey())
.addColumn('version', 'text', (col) => col.notNull())
.addColumn('download_status', 'integer')
.addColumn('download_progress', 'integer')
.addColumn('download_retries', 'integer')
.addColumn('download_started_at', 'text')
.addColumn('download_completed_at', 'text')
.addColumn('upload_status', 'integer')
.addColumn('upload_progress', 'integer')
.addColumn('upload_retries', 'integer')
.addColumn('upload_started_at', 'text')
.addColumn('upload_completed_at', 'text')
.execute();
},
down: async (db) => {
await db.schema.dropTable('file_states').execute();
},
};

View File

@@ -1,16 +0,0 @@
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();
},
};

View File

@@ -1,49 +0,0 @@
import { Migration } from 'kysely';
import { CreateUpload } from '@colanode/client/databases/workspace';
import { UploadStatus } from '@colanode/client/types/files';
export const dropFileStatesTable: Migration = {
up: async (db) => {
const pendingUploads = await db
.selectFrom('file_states')
.select(['id'])
.where('upload_status', '=', 1)
.execute();
if (pendingUploads.length > 0) {
const uploadsToCreate: CreateUpload[] = pendingUploads.map((upload) => ({
file_id: upload.id,
status: UploadStatus.Pending,
progress: 0,
retries: 0,
created_at: new Date().toISOString(),
}));
await db
.insertInto('uploads')
.values(uploadsToCreate)
.onConflict((oc) => oc.column('file_id').doNothing())
.execute();
}
await db.schema.dropTable('file_states').execute();
},
down: async (db) => {
await db.schema
.createTable('file_states')
.addColumn('id', 'text', (col) => col.notNull().primaryKey())
.addColumn('version', 'text', (col) => col.notNull())
.addColumn('download_status', 'integer')
.addColumn('download_progress', 'integer')
.addColumn('download_retries', 'integer')
.addColumn('download_started_at', 'text')
.addColumn('download_completed_at', 'text')
.addColumn('upload_status', 'integer')
.addColumn('upload_progress', 'integer')
.addColumn('upload_retries', 'integer')
.addColumn('upload_started_at', 'text')
.addColumn('upload_completed_at', 'text')
.execute();
},
};

View File

@@ -12,17 +12,14 @@ import { createDocumentStatesTable } from './00009-create-document-states-table'
import { createDocumentUpdatesTable } from './00010-create-document-updates-table';
import { createDocumentTextsTable } from './00011-create-document-texts-table';
import { createCollaborationsTable } from './00012-create-collaborations-table';
import { createFileStatesTable } from './00013-create-file-states-table';
import { createMutationsTable } from './00014-create-mutations-table';
import { createTombstonesTable } from './00015-create-tombstones-table';
import { createCursorsTable } from './00016-create-cursors-table';
import { createMetadataTable } from './00017-create-metadata-table';
import { createNodeReferencesTable } from './00018-create-node-references-table';
import { createNodeCountersTable } from './00019-create-node-counters-table';
import { createLocalFilesTable } from './00020-create-local-files-table';
import { createUploadsTable } from './00021-create-uploads-table';
import { createDownloadsTable } from './00022-create-downloads-table';
import { dropFileStatesTable } from './00023-drop-file-states-table';
import { createMutationsTable } from './00013-create-mutations-table';
import { createTombstonesTable } from './00014-create-tombstones-table';
import { createCursorsTable } from './00015-create-cursors-table';
import { createNodeReferencesTable } from './00016-create-node-references-table';
import { createNodeCountersTable } from './00017-create-node-counters-table';
import { createLocalFilesTable } from './00018-create-local-files-table';
import { createUploadsTable } from './00019-create-uploads-table';
import { createDownloadsTable } from './00020-create-downloads-table';
export const workspaceDatabaseMigrations: Record<string, Migration> = {
'00001-create-users-table': createUsersTable,
@@ -37,15 +34,12 @@ export const workspaceDatabaseMigrations: Record<string, Migration> = {
'00010-create-document-updates-table': createDocumentUpdatesTable,
'00011-create-document-texts-table': createDocumentTextsTable,
'00012-create-collaborations-table': createCollaborationsTable,
'00013-create-file-states-table': createFileStatesTable,
'00014-create-mutations-table': createMutationsTable,
'00015-create-tombstones-table': createTombstonesTable,
'00016-create-cursors-table': createCursorsTable,
'00017-create-metadata-table': createMetadataTable,
'00018-create-node-references-table': createNodeReferencesTable,
'00019-create-node-counters-table': createNodeCountersTable,
'00020-create-local-files-table': createLocalFilesTable,
'00021-create-uploads-table': createUploadsTable,
'00022-create-downloads-table': createDownloadsTable,
'00023-drop-file-states-table': dropFileStatesTable,
'00013-create-mutations-table': createMutationsTable,
'00014-create-tombstones-table': createTombstonesTable,
'00015-create-cursors-table': createCursorsTable,
'00016-create-node-references-table': createNodeReferencesTable,
'00017-create-node-counters-table': createNodeCountersTable,
'00018-create-local-files-table': createLocalFilesTable,
'00019-create-uploads-table': createUploadsTable,
'00020-create-downloads-table': createDownloadsTable,
};

View File

@@ -227,17 +227,6 @@ export type SelectCursor = Selectable<CursorTable>;
export type CreateCursor = Insertable<CursorTable>;
export type UpdateCursor = Updateable<CursorTable>;
interface MetadataTable {
key: ColumnType<string, string, never>;
value: ColumnType<string, string, string>;
created_at: ColumnType<string, string, never>;
updated_at: ColumnType<string | null, string | null, string | null>;
}
export type SelectWorkspaceMetadata = Selectable<MetadataTable>;
export type CreateWorkspaceMetadata = Insertable<MetadataTable>;
export type UpdateWorkspaceMetadata = Updateable<MetadataTable>;
interface LocalFileTable {
id: ColumnType<string, string, never>;
version: ColumnType<string, string, string>;
@@ -314,5 +303,4 @@ export interface WorkspaceDatabaseSchema {
mutations: MutationTable;
tombstones: TombstoneTable;
cursors: CursorTable;
metadata: MetadataTable;
}

View File

@@ -1,52 +0,0 @@
import { eventBus } from '@colanode/client/lib/event-bus';
import { mapAccountMetadata } from '@colanode/client/lib/mappers';
import { MutationHandler } from '@colanode/client/lib/types';
import {
AccountMetadataDeleteMutationInput,
AccountMetadataDeleteMutationOutput,
} from '@colanode/client/mutations/accounts/account-metadata-delete';
import { AppService } from '@colanode/client/services/app-service';
export class AccountMetadataDeleteMutationHandler
implements MutationHandler<AccountMetadataDeleteMutationInput>
{
private readonly app: AppService;
constructor(appService: AppService) {
this.app = appService;
}
async handleMutation(
input: AccountMetadataDeleteMutationInput
): Promise<AccountMetadataDeleteMutationOutput> {
const account = this.app.getAccount(input.accountId);
if (!account) {
return {
success: false,
};
}
const deletedMetadata = await account.database
.deleteFrom('metadata')
.where('key', '=', input.key)
.returningAll()
.executeTakeFirst();
if (!deletedMetadata) {
return {
success: true,
};
}
eventBus.publish({
type: 'account.metadata.deleted',
accountId: input.accountId,
metadata: mapAccountMetadata(deletedMetadata),
});
return {
success: true,
};
}
}

View File

@@ -1,62 +0,0 @@
import { eventBus } from '@colanode/client/lib/event-bus';
import { mapAccountMetadata } from '@colanode/client/lib/mappers';
import { MutationHandler } from '@colanode/client/lib/types';
import {
AccountMetadataUpdateMutationInput,
AccountMetadataUpdateMutationOutput,
} from '@colanode/client/mutations/accounts/account-metadata-update';
import { AppService } from '@colanode/client/services/app-service';
export class AccountMetadataUpdateMutationHandler
implements MutationHandler<AccountMetadataUpdateMutationInput>
{
private readonly app: AppService;
constructor(appService: AppService) {
this.app = appService;
}
public async handleMutation(
input: AccountMetadataUpdateMutationInput
): Promise<AccountMetadataUpdateMutationOutput> {
const account = this.app.getAccount(input.accountId);
if (!account) {
return {
success: false,
};
}
const updatedMetadata = await account.database
.insertInto('metadata')
.returningAll()
.values({
key: input.key,
value: JSON.stringify(input.value),
created_at: new Date().toISOString(),
})
.onConflict((cb) =>
cb.columns(['key']).doUpdateSet({
value: JSON.stringify(input.value),
updated_at: new Date().toISOString(),
})
)
.executeTakeFirst();
if (!updatedMetadata) {
return {
success: false,
};
}
eventBus.publish({
type: 'account.metadata.updated',
accountId: input.accountId,
metadata: mapAccountMetadata(updatedMetadata),
});
return {
success: true,
};
}
}

View File

@@ -16,7 +16,10 @@ export abstract class AccountMutationHandlerBase {
login: LoginSuccessOutput,
server: ServerService
): Promise<void> {
const createdAccount = await this.app.database
const { createdAccount, createdWorkspaces } = await this.app.database
.transaction()
.execute(async (trx) => {
const createdAccount = await trx
.insertInto('accounts')
.returningAll()
.values({
@@ -38,27 +41,17 @@ export abstract class AccountMutationHandlerBase {
);
}
const account = mapAccount(createdAccount);
const accountService = await this.app.initAccount(account);
eventBus.publish({
type: 'account.created',
account: account,
});
if (login.workspaces.length === 0) {
return;
}
const createdWorkspaces = [];
if (login.workspaces.length > 0) {
for (const workspace of login.workspaces) {
const createdWorkspace = await accountService.database
const createdWorkspace = await trx
.insertInto('workspaces')
.returningAll()
.values({
id: workspace.id,
workspace_id: workspace.id,
name: workspace.name,
user_id: workspace.user.id,
account_id: account.id,
account_id: createdAccount.id,
role: workspace.user.role,
storage_limit: workspace.user.storageLimit,
max_file_size: workspace.user.maxFileSize,
@@ -68,11 +61,25 @@ export abstract class AccountMutationHandlerBase {
})
.executeTakeFirst();
if (!createdWorkspace) {
continue;
if (createdWorkspace) {
createdWorkspaces.push(createdWorkspace);
}
}
}
await accountService.initWorkspace(mapWorkspace(createdWorkspace));
return { createdAccount, createdWorkspaces };
});
const account = mapAccount(createdAccount);
await this.app.initAccount(account);
eventBus.publish({
type: 'account.created',
account: account,
});
for (const createdWorkspace of createdWorkspaces) {
await this.app.initWorkspace(createdWorkspace);
eventBus.publish({
type: 'workspace.created',
workspace: mapWorkspace(createdWorkspace),

View File

@@ -1,14 +1,14 @@
import { eventBus } from '@colanode/client/lib/event-bus';
import { mapAppMetadata } from '@colanode/client/lib/mappers';
import { mapMetadata } from '@colanode/client/lib/mappers';
import { MutationHandler } from '@colanode/client/lib/types';
import {
AppMetadataDeleteMutationInput,
AppMetadataDeleteMutationOutput,
} from '@colanode/client/mutations/apps/app-metadata-delete';
MetadataDeleteMutationInput,
MetadataDeleteMutationOutput,
} from '@colanode/client/mutations/apps/metadata-delete';
import { AppService } from '@colanode/client/services/app-service';
export class AppMetadataDeleteMutationHandler
implements MutationHandler<AppMetadataDeleteMutationInput>
export class MetadataDeleteMutationHandler
implements MutationHandler<MetadataDeleteMutationInput>
{
private readonly app: AppService;
@@ -17,10 +17,11 @@ export class AppMetadataDeleteMutationHandler
}
async handleMutation(
input: AppMetadataDeleteMutationInput
): Promise<AppMetadataDeleteMutationOutput> {
input: MetadataDeleteMutationInput
): Promise<MetadataDeleteMutationOutput> {
const deletedMetadata = await this.app.database
.deleteFrom('metadata')
.where('namespace', '=', input.namespace)
.where('key', '=', input.key)
.returningAll()
.executeTakeFirst();
@@ -32,8 +33,8 @@ export class AppMetadataDeleteMutationHandler
}
eventBus.publish({
type: 'app.metadata.deleted',
metadata: mapAppMetadata(deletedMetadata),
type: 'metadata.deleted',
metadata: mapMetadata(deletedMetadata),
});
return {

View File

@@ -1,14 +1,14 @@
import { eventBus } from '@colanode/client/lib/event-bus';
import { mapAppMetadata } from '@colanode/client/lib/mappers';
import { mapMetadata } from '@colanode/client/lib/mappers';
import { MutationHandler } from '@colanode/client/lib/types';
import {
AppMetadataUpdateMutationInput,
AppMetadataUpdateMutationOutput,
} from '@colanode/client/mutations/apps/app-metadata-update';
MetadataUpdateMutationInput,
MetadataUpdateMutationOutput,
} from '@colanode/client/mutations/apps/metadata-update';
import { AppService } from '@colanode/client/services/app-service';
export class AppMetadataUpdateMutationHandler
implements MutationHandler<AppMetadataUpdateMutationInput>
export class MetadataUpdateMutationHandler
implements MutationHandler<MetadataUpdateMutationInput>
{
private readonly app: AppService;
@@ -17,19 +17,20 @@ export class AppMetadataUpdateMutationHandler
}
async handleMutation(
input: AppMetadataUpdateMutationInput
): Promise<AppMetadataUpdateMutationOutput> {
input: MetadataUpdateMutationInput
): Promise<MetadataUpdateMutationOutput> {
const updatedMetadata = await this.app.database
.insertInto('metadata')
.returningAll()
.values({
namespace: input.namespace,
key: input.key,
value: JSON.stringify(input.value),
value: input.value,
created_at: new Date().toISOString(),
})
.onConflict((cb) =>
cb.columns(['key']).doUpdateSet({
value: JSON.stringify(input.value),
cb.columns(['namespace', 'key']).doUpdateSet({
value: input.value,
updated_at: new Date().toISOString(),
})
)
@@ -42,8 +43,8 @@ export class AppMetadataUpdateMutationHandler
}
eventBus.publish({
type: 'app.metadata.updated',
metadata: mapAppMetadata(updatedMetadata),
type: 'metadata.updated',
metadata: mapMetadata(updatedMetadata),
});
return {

View File

@@ -54,7 +54,7 @@ export class AvatarUploadMutationHandler
.json<AvatarUploadResponse>();
await this.app.fs.delete(filePath);
await account.avatars.downloadAvatar(response.id);
await this.app.assets.downloadAvatar(account.id, response.id);
return {
id: response.id,

View File

@@ -14,7 +14,7 @@ export class ChannelCreateMutationHandler
async handleMutation(
input: ChannelCreateMutationInput
): Promise<ChannelCreateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const space = await workspace.database
.selectFrom('nodes')

View File

@@ -12,7 +12,7 @@ export class ChannelDeleteMutationHandler
async handleMutation(
input: ChannelDeleteMutationInput
): Promise<ChannelDeleteMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
await workspace.nodes.deleteNode(input.channelId);
return {

View File

@@ -14,7 +14,7 @@ export class ChannelUpdateMutationHandler
async handleMutation(
input: ChannelUpdateMutationInput
): Promise<ChannelUpdateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const result = await workspace.nodes.updateNode<ChannelAttributes>(
input.channelId,

View File

@@ -19,14 +19,14 @@ export class ChatCreateMutationHandler
public async handleMutation(
input: ChatCreateMutationInput
): Promise<ChatCreateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const query = sql<ChatRow>`
SELECT id
FROM nodes
WHERE type = 'chat'
AND json_extract(attributes, '$.collaborators.${sql.raw(input.collaboratorId)}') is not null
AND json_extract(attributes, '$.collaborators.${sql.raw(input.userId)}') is not null
AND json_extract(attributes, '$.collaborators.${sql.raw(workspace.userId)}') is not null
`.compile(workspace.database);
const existingChats = await workspace.database.executeQuery(query);
@@ -42,7 +42,7 @@ export class ChatCreateMutationHandler
type: 'chat',
collaborators: {
[input.userId]: 'admin',
[workspace.userId]: 'admin',
[input.collaboratorId]: 'admin',
},
};

View File

@@ -19,7 +19,7 @@ export class DatabaseCreateMutationHandler
async handleMutation(
input: DatabaseCreateMutationInput
): Promise<DatabaseCreateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const databaseId = generateId(IdType.Database);
const viewId = generateId(IdType.DatabaseView);

View File

@@ -12,7 +12,7 @@ export class DatabaseDeleteMutationHandler
async handleMutation(
input: DatabaseDeleteMutationInput
): Promise<DatabaseDeleteMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
await workspace.nodes.deleteNode(input.databaseId);
return {

View File

@@ -14,7 +14,7 @@ export class DatabaseNameFieldUpdateMutationHandler
async handleMutation(
input: DatabaseNameFieldUpdateMutationInput
): Promise<DatabaseNameFieldUpdateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const result = await workspace.nodes.updateNode<DatabaseAttributes>(
input.databaseId,
(attributes) => {

View File

@@ -14,7 +14,7 @@ export class DatabaseUpdateMutationHandler
async handleMutation(
input: DatabaseUpdateMutationInput
): Promise<DatabaseUpdateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const result = await workspace.nodes.updateNode<DatabaseAttributes>(
input.databaseId,
(attributes) => {

View File

@@ -23,7 +23,7 @@ export class FieldCreateMutationHandler
async handleMutation(
input: FieldCreateMutationInput
): Promise<FieldCreateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
if (input.fieldType === 'relation') {
if (!input.relationDatabaseId) {

View File

@@ -14,7 +14,7 @@ export class FieldDeleteMutationHandler
async handleMutation(
input: FieldDeleteMutationInput
): Promise<FieldDeleteMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const result = await workspace.nodes.updateNode<DatabaseAttributes>(
input.databaseId,

View File

@@ -14,7 +14,7 @@ export class FieldNameUpdateMutationHandler
async handleMutation(
input: FieldNameUpdateMutationInput
): Promise<FieldNameUpdateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const result = await workspace.nodes.updateNode<DatabaseAttributes>(
input.databaseId,

View File

@@ -20,7 +20,7 @@ export class SelectOptionCreateMutationHandler
async handleMutation(
input: SelectOptionCreateMutationInput
): Promise<SelectOptionCreateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const id = generateId(IdType.SelectOption);
const result = await workspace.nodes.updateNode<DatabaseAttributes>(

View File

@@ -14,7 +14,7 @@ export class SelectOptionDeleteMutationHandler
async handleMutation(
input: SelectOptionDeleteMutationInput
): Promise<SelectOptionDeleteMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const result = await workspace.nodes.updateNode<DatabaseAttributes>(
input.databaseId,

View File

@@ -14,7 +14,7 @@ export class SelectOptionUpdateMutationHandler
async handleMutation(
input: SelectOptionUpdateMutationInput
): Promise<SelectOptionUpdateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const result = await workspace.nodes.updateNode<DatabaseAttributes>(
input.databaseId,

View File

@@ -19,7 +19,7 @@ export class ViewCreateMutationHandler
async handleMutation(
input: ViewCreateMutationInput
): Promise<ViewCreateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const id = generateId(IdType.DatabaseView);
const otherViews = await workspace.database

View File

@@ -12,7 +12,7 @@ export class ViewDeleteMutationHandler
async handleMutation(
input: ViewDeleteMutationInput
): Promise<ViewDeleteMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
await workspace.nodes.deleteNode(input.viewId);
return {

View File

@@ -14,7 +14,7 @@ export class ViewNameUpdateMutationHandler
async handleMutation(
input: ViewNameUpdateMutationInput
): Promise<ViewNameUpdateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const result = await workspace.nodes.updateNode<DatabaseViewAttributes>(
input.viewId,

View File

@@ -14,7 +14,7 @@ export class ViewUpdateMutationHandler
async handleMutation(
input: ViewUpdateMutationInput
): Promise<ViewUpdateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const result = await workspace.nodes.updateNode<DatabaseViewAttributes>(
input.viewId,

View File

@@ -13,7 +13,7 @@ export class DocumentUpdateMutationHandler
async handleMutation(
input: DocumentUpdateMutationInput
): Promise<DocumentUpdateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
await workspace.documents.updateDocument(
input.documentId,
decodeState(input.update)

View File

@@ -13,7 +13,7 @@ export class FileCreateMutationHandler
async handleMutation(
input: FileCreateMutationInput
): Promise<FileCreateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const fileId = generateId(IdType.File);
await workspace.files.createFile(fileId, input.tempFileId, input.parentId);

View File

@@ -12,7 +12,7 @@ export class FileDeleteMutationHandler
async handleMutation(
input: FileDeleteMutationInput
): Promise<FileDeleteMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
await workspace.nodes.deleteNode(input.fileId);
return {

View File

@@ -12,7 +12,7 @@ export class FileDownloadMutationHandler
async handleMutation(
input: FileDownloadMutationInput
): Promise<FileDownloadMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const path = input.path;
if (!path) {

View File

@@ -13,7 +13,7 @@ export class FolderCreateMutationHandler
async handleMutation(
input: FolderCreateMutationInput
): Promise<FolderCreateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const id = generateId(IdType.Folder);
const attributes: FolderAttributes = {

View File

@@ -12,7 +12,7 @@ export class FolderDeleteMutationHandler
async handleMutation(
input: FolderDeleteMutationInput
): Promise<FolderDeleteMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
await workspace.nodes.deleteNode(input.folderId);

View File

@@ -15,7 +15,7 @@ export class FolderUpdateMutationHandler
async handleMutation(
input: FolderUpdateMutationInput
): Promise<FolderUpdateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const result = await workspace.nodes.updateNode<FolderAttributes>(
input.folderId,

View File

@@ -3,8 +3,6 @@ import { MutationMap } from '@colanode/client/mutations';
import { AppService } from '@colanode/client/services';
import { AccountLogoutMutationHandler } from './accounts/account-logout';
import { AccountMetadataDeleteMutationHandler } from './accounts/account-metadata-delete';
import { AccountMetadataUpdateMutationHandler } from './accounts/account-metadata-update';
import { AccountUpdateMutationHandler } from './accounts/account-update';
import { EmailLoginMutationHandler } from './accounts/email-login';
import { EmailPasswordResetCompleteMutationHandler } from './accounts/email-password-reset-complete';
@@ -12,8 +10,8 @@ import { EmailPasswordResetInitMutationHandler } from './accounts/email-password
import { EmailRegisterMutationHandler } from './accounts/email-register';
import { EmailVerifyMutationHandler } from './accounts/email-verify';
import { GoogleLoginMutationHandler } from './accounts/google-login';
import { AppMetadataDeleteMutationHandler } from './apps/app-metadata-delete';
import { AppMetadataUpdateMutationHandler } from './apps/app-metadata-update';
import { MetadataDeleteMutationHandler } from './apps/metadata-delete';
import { MetadataUpdateMutationHandler } from './apps/metadata-update';
import { TabCreateMutationHandler } from './apps/tab-create';
import { TabDeleteMutationHandler } from './apps/tab-delete';
import { TabUpdateMutationHandler } from './apps/tab-update';
@@ -73,8 +71,6 @@ import { UserStorageUpdateMutationHandler } from './users/user-storage-update';
import { UsersCreateMutationHandler } from './users/users-create';
import { WorkspaceCreateMutationHandler } from './workspaces/workspace-create';
import { WorkspaceDeleteMutationHandler } from './workspaces/workspace-delete';
import { WorkspaceMetadataDeleteMutationHandler } from './workspaces/workspace-metadata-delete';
import { WorkspaceMetadataUpdateMutationHandler } from './workspaces/workspace-metadata-update';
import { WorkspaceUpdateMutationHandler } from './workspaces/workspace-update';
export type MutationHandlerMap = {
@@ -146,17 +142,9 @@ export const buildMutationHandlerMap = (
'page.update': new PageUpdateMutationHandler(app),
'folder.update': new FolderUpdateMutationHandler(app),
'database.update': new DatabaseUpdateMutationHandler(app),
'workspace.metadata.update': new WorkspaceMetadataUpdateMutationHandler(
app
),
'workspace.metadata.delete': new WorkspaceMetadataDeleteMutationHandler(
app
),
'document.update': new DocumentUpdateMutationHandler(app),
'app.metadata.update': new AppMetadataUpdateMutationHandler(app),
'app.metadata.delete': new AppMetadataDeleteMutationHandler(app),
'account.metadata.update': new AccountMetadataUpdateMutationHandler(app),
'account.metadata.delete': new AccountMetadataDeleteMutationHandler(app),
'metadata.update': new MetadataUpdateMutationHandler(app),
'metadata.delete': new MetadataDeleteMutationHandler(app),
'email.password.reset.init': new EmailPasswordResetInitMutationHandler(app),
'email.password.reset.complete':
new EmailPasswordResetCompleteMutationHandler(app),

View File

@@ -26,7 +26,7 @@ export class MessageCreateMutationHandler
async handleMutation(
input: MessageCreateMutationInput
): Promise<MessageCreateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const messageId = generateId(IdType.Message);
const editorContent = input.content.content ?? [];

View File

@@ -12,7 +12,7 @@ export class MessageDeleteMutationHandler
async handleMutation(
input: MessageDeleteMutationInput
): Promise<MessageDeleteMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
await workspace.nodes.deleteNode(input.messageId);
return {

View File

@@ -16,7 +16,7 @@ export class NodeCollaboratorCreateMutationHandler
async handleMutation(
input: NodeCollaboratorCreateMutationInput
): Promise<NodeCollaboratorCreateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const result = await workspace.nodes.updateNode(
input.nodeId,

View File

@@ -16,7 +16,7 @@ export class NodeCollaboratorDeleteMutationHandler
async handleMutation(
input: NodeCollaboratorDeleteMutationInput
): Promise<NodeCollaboratorDeleteMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const result = await workspace.nodes.updateNode(
input.nodeId,

View File

@@ -16,7 +16,7 @@ export class NodeCollaboratorUpdateMutationHandler
async handleMutation(
input: NodeCollaboratorUpdateMutationInput
): Promise<NodeCollaboratorUpdateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const result = await workspace.nodes.updateNode(
input.nodeId,

View File

@@ -22,7 +22,7 @@ export class NodeInteractionOpenedMutationHandler
async handleMutation(
input: NodeInteractionOpenedMutationInput
): Promise<NodeInteractionOpenedMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const node = await fetchNode(workspace.database, input.nodeId);
if (!node) {
@@ -123,8 +123,11 @@ export class NodeInteractionOpenedMutationHandler
eventBus.publish({
type: 'node.interaction.updated',
workspace: {
workspaceId: workspace.workspaceId,
userId: workspace.userId,
accountId: workspace.accountId,
workspaceId: workspace.id,
},
nodeInteraction: mapNodeInteraction(createdInteraction),
});

View File

@@ -22,7 +22,7 @@ export class NodeInteractionSeenMutationHandler
async handleMutation(
input: NodeInteractionSeenMutationInput
): Promise<NodeInteractionSeenMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const node = await fetchNode(workspace.database, input.nodeId);
@@ -124,8 +124,11 @@ export class NodeInteractionSeenMutationHandler
eventBus.publish({
type: 'node.interaction.updated',
workspace: {
workspaceId: workspace.workspaceId,
userId: workspace.userId,
accountId: workspace.accountId,
workspaceId: workspace.id,
},
nodeInteraction: mapNodeInteraction(createdInteraction),
});

View File

@@ -12,7 +12,7 @@ export class NodeReactionCreateMutationHandler
async handleMutation(
input: NodeReactionCreateMutationInput
): Promise<NodeReactionCreateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
await workspace.nodeReactions.createNodeReaction(
input.nodeId,
input.reaction

View File

@@ -12,7 +12,7 @@ export class NodeReactionDeleteMutationHandler
async handleMutation(
input: NodeReactionDeleteMutationInput
): Promise<NodeReactionDeleteMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
await workspace.nodeReactions.deleteNodeReaction(
input.nodeId,
input.reaction

View File

@@ -13,7 +13,7 @@ export class PageCreateMutationHandler
async handleMutation(
input: PageCreateMutationInput
): Promise<PageCreateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const id = generateId(IdType.Page);
const attributes: PageAttributes = {

View File

@@ -12,7 +12,7 @@ export class PageDeleteMutationHandler
async handleMutation(
input: PageDeleteMutationInput
): Promise<PageDeleteMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
await workspace.nodes.deleteNode(input.pageId);
return {

View File

@@ -14,7 +14,7 @@ export class PageUpdateMutationHandler
async handleMutation(
input: PageUpdateMutationInput
): Promise<PageUpdateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const result = await workspace.nodes.updateNode<PageAttributes>(
input.pageId,

View File

@@ -14,7 +14,7 @@ export class RecordAvatarUpdateMutationHandler
async handleMutation(
input: RecordAvatarUpdateMutationInput
): Promise<RecordAvatarUpdateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const result = await workspace.nodes.updateNode<RecordAttributes>(
input.recordId,

View File

@@ -13,7 +13,7 @@ export class RecordCreateMutationHandler
async handleMutation(
input: RecordCreateMutationInput
): Promise<RecordCreateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const id = generateId(IdType.Record);
const attributes: RecordAttributes = {

View File

@@ -12,7 +12,7 @@ export class RecordDeleteMutationHandler
async handleMutation(
input: RecordDeleteMutationInput
): Promise<RecordDeleteMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
await workspace.nodes.deleteNode(input.recordId);
return {

View File

@@ -14,7 +14,7 @@ export class RecordFieldValueDeleteMutationHandler
async handleMutation(
input: RecordFieldValueDeleteMutationInput
): Promise<RecordFieldValueDeleteMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const result = await workspace.nodes.updateNode<RecordAttributes>(
input.recordId,

View File

@@ -14,7 +14,7 @@ export class RecordFieldValueSetMutationHandler
async handleMutation(
input: RecordFieldValueSetMutationInput
): Promise<RecordFieldValueSetMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const result = await workspace.nodes.updateNode<RecordAttributes>(
input.recordId,

View File

@@ -14,7 +14,7 @@ export class RecordNameUpdateMutationHandler
async handleMutation(
input: RecordNameUpdateMutationInput
): Promise<RecordNameUpdateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const result = await workspace.nodes.updateNode<RecordAttributes>(
input.recordId,

View File

@@ -26,7 +26,7 @@ export class SpaceChildReorderMutationHandler
async handleMutation(
input: SpaceChildReorderMutationInput
): Promise<SpaceChildReorderMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const children = await workspace.database
.selectFrom('nodes')
.where('parent_id', '=', input.spaceId)

View File

@@ -20,7 +20,7 @@ export class SpaceCreateMutationHandler
async handleMutation(
input: SpaceCreateMutationInput
): Promise<SpaceCreateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
if (!workspace) {
throw new MutationError(

View File

@@ -12,7 +12,7 @@ export class SpaceDeleteMutationHandler
async handleMutation(
input: SpaceDeleteMutationInput
): Promise<SpaceDeleteMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
await workspace.nodes.deleteNode(input.spaceId);
return {

View File

@@ -14,7 +14,7 @@ export class SpaceUpdateMutationHandler
async handleMutation(
input: SpaceUpdateMutationInput
): Promise<SpaceUpdateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
const result = await workspace.nodes.updateNode<SpaceAttributes>(
input.spaceId,

View File

@@ -15,7 +15,7 @@ export class UserRoleUpdateMutationHandler
async handleMutation(
input: UserRoleUpdateMutationInput
): Promise<UserRoleUpdateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
try {
const body: UserRoleUpdateInput = {
@@ -23,9 +23,12 @@ export class UserRoleUpdateMutationHandler
};
const output = await workspace.account.client
.patch(`v1/workspaces/${workspace.id}/users/${input.userId}/role`, {
.patch(
`v1/workspaces/${workspace.workspaceId}/users/${input.userId}/role`,
{
json: body,
})
}
)
.json<UserOutput>();
await workspace.users.upsert(output);

View File

@@ -15,7 +15,7 @@ export class UserStorageUpdateMutationHandler
async handleMutation(
input: UserStorageUpdateMutationInput
): Promise<UserStorageUpdateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
try {
const body: UserStorageUpdateInput = {
@@ -24,9 +24,12 @@ export class UserStorageUpdateMutationHandler
};
const output = await workspace.account.client
.patch(`v1/workspaces/${workspace.id}/users/${input.userId}/storage`, {
.patch(
`v1/workspaces/${workspace.workspaceId}/users/${input.userId}/storage`,
{
json: body,
})
}
)
.json<UserOutput>();
await workspace.users.upsert(output);

View File

@@ -15,7 +15,7 @@ export class UsersCreateMutationHandler
async handleMutation(
input: UsersCreateMutationInput
): Promise<UsersCreateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const workspace = this.getWorkspace(input.userId);
try {
const body: UsersCreateInput = {
@@ -23,7 +23,7 @@ export class UsersCreateMutationHandler
};
const output = await workspace.account.client
.post(`v1/workspaces/${workspace.id}/users`, {
.post(`v1/workspaces/${workspace.workspaceId}/users`, {
json: body,
})
.json<UsersCreateOutput>();

View File

@@ -9,20 +9,10 @@ export abstract class WorkspaceMutationHandlerBase {
this.app = app;
}
protected getWorkspace(
accountId: string,
workspaceId: string
): WorkspaceService {
const account = this.app.getAccount(accountId);
if (!account) {
throw new MutationError(
MutationErrorCode.AccountNotFound,
'Account not found or has been logged out already. Try closing the app and opening it again.'
);
}
const workspace = account.getWorkspace(workspaceId);
protected getWorkspace(userId: string): WorkspaceService {
const workspace = this.app.getWorkspace(userId);
if (!workspace) {
console.log('Workspace not found', userId);
throw new MutationError(
MutationErrorCode.WorkspaceNotFound,
'Workspace not found or has been deleted.'

View File

@@ -44,11 +44,12 @@ export class WorkspaceCreateMutationHandler
})
.json<WorkspaceOutput>();
const createdWorkspace = await account.database
const createdWorkspace = await this.app.database
.insertInto('workspaces')
.returningAll()
.values({
id: response.id,
user_id: response.user.id,
workspace_id: response.id,
account_id: response.user.accountId,
name: response.name,
description: response.description,
@@ -56,7 +57,6 @@ export class WorkspaceCreateMutationHandler
role: response.user.role,
storage_limit: response.user.storageLimit,
max_file_size: response.user.maxFileSize,
user_id: response.user.id,
created_at: new Date().toISOString(),
})
.onConflict((cb) => cb.doNothing())
@@ -69,16 +69,16 @@ export class WorkspaceCreateMutationHandler
);
}
const workspace = mapWorkspace(createdWorkspace);
await account.initWorkspace(workspace);
await this.app.initWorkspace(createdWorkspace);
const workspace = mapWorkspace(createdWorkspace);
eventBus.publish({
type: 'workspace.created',
workspace: workspace,
});
return {
id: createdWorkspace.id,
id: createdWorkspace.workspace_id,
userId: createdWorkspace.user_id,
};
} catch (error) {

View File

@@ -20,16 +20,7 @@ export class WorkspaceDeleteMutationHandler
async handleMutation(
input: WorkspaceDeleteMutationInput
): Promise<WorkspaceDeleteMutationOutput> {
const accountService = this.app.getAccount(input.accountId);
if (!accountService) {
throw new MutationError(
MutationErrorCode.AccountNotFound,
'Account not found or has been logged out.'
);
}
const workspaceService = accountService.getWorkspace(input.workspaceId);
const workspaceService = this.app.getWorkspace(input.userId);
if (!workspaceService) {
throw new MutationError(
MutationErrorCode.WorkspaceNotFound,
@@ -37,12 +28,20 @@ export class WorkspaceDeleteMutationHandler
);
}
const accountService = this.app.getAccount(workspaceService.accountId);
if (!accountService) {
throw new MutationError(
MutationErrorCode.AccountNotFound,
'Account not found or has been logged out.'
);
}
try {
const response = await accountService.client
.delete(`v1/workspaces/${input.workspaceId}`)
.delete(`v1/workspaces/${workspaceService.workspaceId}`)
.json<WorkspaceOutput>();
await accountService.deleteWorkspace(response.id);
await workspaceService.delete();
return {
id: response.id,

View File

@@ -1,41 +0,0 @@
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
import { eventBus } from '@colanode/client/lib/event-bus';
import { mapWorkspaceMetadata } from '@colanode/client/lib/mappers';
import { MutationHandler } from '@colanode/client/lib/types';
import {
WorkspaceMetadataDeleteMutationInput,
WorkspaceMetadataDeleteMutationOutput,
} from '@colanode/client/mutations/workspaces/workspace-metadata-delete';
export class WorkspaceMetadataDeleteMutationHandler
extends WorkspaceMutationHandlerBase
implements MutationHandler<WorkspaceMetadataDeleteMutationInput>
{
async handleMutation(
input: WorkspaceMetadataDeleteMutationInput
): Promise<WorkspaceMetadataDeleteMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const deletedMetadata = await workspace.database
.deleteFrom('metadata')
.where('key', '=', input.key)
.returningAll()
.executeTakeFirst();
if (!deletedMetadata) {
return {
success: true,
};
}
eventBus.publish({
type: 'workspace.metadata.deleted',
accountId: input.accountId,
workspaceId: input.workspaceId,
metadata: mapWorkspaceMetadata(deletedMetadata),
});
return {
success: true,
};
}
}

View File

@@ -1,52 +0,0 @@
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
import { eventBus } from '@colanode/client/lib/event-bus';
import { mapWorkspaceMetadata } from '@colanode/client/lib/mappers';
import { MutationHandler } from '@colanode/client/lib/types';
import {
WorkspaceMetadataUpdateMutationInput,
WorkspaceMetadataUpdateMutationOutput,
} from '@colanode/client/mutations/workspaces/workspace-metadata-update';
export class WorkspaceMetadataUpdateMutationHandler
extends WorkspaceMutationHandlerBase
implements MutationHandler<WorkspaceMetadataUpdateMutationInput>
{
async handleMutation(
input: WorkspaceMetadataUpdateMutationInput
): Promise<WorkspaceMetadataUpdateMutationOutput> {
const workspace = this.getWorkspace(input.accountId, input.workspaceId);
const updatedMetadata = await workspace.database
.insertInto('metadata')
.returningAll()
.values({
key: input.key,
value: JSON.stringify(input.value),
created_at: new Date().toISOString(),
})
.onConflict((cb) =>
cb.columns(['key']).doUpdateSet({
value: JSON.stringify(input.value),
updated_at: new Date().toISOString(),
})
)
.executeTakeFirst();
if (!updatedMetadata) {
return {
success: false,
};
}
eventBus.publish({
type: 'workspace.metadata.updated',
accountId: input.accountId,
workspaceId: input.workspaceId,
metadata: mapWorkspaceMetadata(updatedMetadata),
});
return {
success: true,
};
}
}

View File

@@ -24,7 +24,7 @@ export class WorkspaceUpdateMutationHandler
async handleMutation(
input: WorkspaceUpdateMutationInput
): Promise<WorkspaceUpdateMutationOutput> {
const accountService = this.app.getAccount(input.accountId);
const accountService = this.app.getAccount(input.userId);
if (!accountService) {
throw new MutationError(
@@ -33,7 +33,7 @@ export class WorkspaceUpdateMutationHandler
);
}
const workspaceService = accountService.getWorkspace(input.id);
const workspaceService = this.app.getWorkspace(input.userId);
if (!workspaceService) {
throw new MutationError(
MutationErrorCode.WorkspaceNotFound,
@@ -54,7 +54,7 @@ export class WorkspaceUpdateMutationHandler
})
.json<Workspace>();
const updatedWorkspace = await accountService.database
const updatedWorkspace = await this.app.database
.updateTable('workspaces')
.returningAll()
.set({
@@ -63,7 +63,7 @@ export class WorkspaceUpdateMutationHandler
avatar: response.avatar,
role: response.role,
})
.where((eb) => eb.and([eb('id', '=', input.id)]))
.where((eb) => eb.and([eb('user_id', '=', input.userId)]))
.executeTakeFirst();
if (!updatedWorkspace) {

View File

@@ -1,105 +0,0 @@
import { SelectAccountMetadata } from '@colanode/client/databases/account/schema';
import { mapAccountMetadata } from '@colanode/client/lib/mappers';
import { ChangeCheckResult, QueryHandler } from '@colanode/client/lib/types';
import { AccountMetadataListQueryInput } from '@colanode/client/queries/accounts/account-metadata-list';
import { AppService } from '@colanode/client/services/app-service';
import { AccountMetadata } from '@colanode/client/types/accounts';
import { Event } from '@colanode/client/types/events';
export class AccountMetadataListQueryHandler
implements QueryHandler<AccountMetadataListQueryInput>
{
private readonly app: AppService;
constructor(app: AppService) {
this.app = app;
}
public async handleQuery(
input: AccountMetadataListQueryInput
): Promise<AccountMetadata[]> {
const rows = await this.getAccountMetadata(input.accountId);
if (!rows) {
return [];
}
return rows.map(mapAccountMetadata);
}
public async checkForChanges(
event: Event,
input: AccountMetadataListQueryInput,
output: AccountMetadata[]
): Promise<ChangeCheckResult<AccountMetadataListQueryInput>> {
if (
event.type === 'account.created' &&
event.account.id === input.accountId
) {
const result = await this.handleQuery(input);
return {
hasChanges: true,
result,
};
}
if (
event.type === 'account.deleted' &&
event.account.id === input.accountId
) {
return {
hasChanges: true,
result: [],
};
}
if (
event.type === 'account.metadata.updated' &&
event.accountId === input.accountId
) {
const newOutput = [
...output.filter((metadata) => metadata.key !== event.metadata.key),
event.metadata,
];
return {
hasChanges: true,
result: newOutput,
};
}
if (
event.type === 'account.metadata.deleted' &&
event.accountId === input.accountId
) {
const newOutput = output.filter(
(metadata) => metadata.key !== event.metadata.key
);
return {
hasChanges: true,
result: newOutput,
};
}
return {
hasChanges: false,
};
}
private async getAccountMetadata(
accountId: string
): Promise<SelectAccountMetadata[] | undefined> {
const account = this.app.getAccount(accountId);
if (!account) {
return undefined;
}
const rows = await account.database
.selectFrom('metadata')
.selectAll()
.execute();
return rows;
}
}

View File

@@ -1,13 +1,13 @@
import { SelectAppMetadata } from '@colanode/client/databases/app/schema';
import { mapAppMetadata } from '@colanode/client/lib/mappers';
import { SelectMetadata } from '@colanode/client/databases/app/schema';
import { mapMetadata } from '@colanode/client/lib/mappers';
import { ChangeCheckResult, QueryHandler } from '@colanode/client/lib/types';
import { AppMetadataListQueryInput } from '@colanode/client/queries/apps/app-metadata-list';
import { MetadataListQueryInput } from '@colanode/client/queries/apps/metadata-list';
import { AppService } from '@colanode/client/services/app-service';
import { AppMetadata } from '@colanode/client/types/apps';
import { Metadata } from '@colanode/client/types/apps';
import { Event } from '@colanode/client/types/events';
export class AppMetadataListQueryHandler
implements QueryHandler<AppMetadataListQueryInput>
export class MetadataListQueryHandler
implements QueryHandler<MetadataListQueryInput>
{
private readonly app: AppService;
@@ -15,23 +15,21 @@ export class AppMetadataListQueryHandler
this.app = app;
}
public async handleQuery(
_: AppMetadataListQueryInput
): Promise<AppMetadata[]> {
public async handleQuery(_: MetadataListQueryInput): Promise<Metadata[]> {
const rows = await this.getAppMetadata();
if (!rows) {
return [];
}
return rows.map(mapAppMetadata);
return rows.map(mapMetadata);
}
public async checkForChanges(
event: Event,
_: AppMetadataListQueryInput,
output: AppMetadata[]
): Promise<ChangeCheckResult<AppMetadataListQueryInput>> {
if (event.type === 'app.metadata.updated') {
_: MetadataListQueryInput,
output: Metadata[]
): Promise<ChangeCheckResult<MetadataListQueryInput>> {
if (event.type === 'metadata.updated') {
const newOutput = [
...output.filter((metadata) => metadata.key !== event.metadata.key),
event.metadata,
@@ -43,7 +41,7 @@ export class AppMetadataListQueryHandler
};
}
if (event.type === 'app.metadata.deleted') {
if (event.type === 'metadata.deleted') {
const newOutput = output.filter(
(metadata) => metadata.key !== event.metadata.key
);
@@ -59,7 +57,7 @@ export class AppMetadataListQueryHandler
};
}
private async getAppMetadata(): Promise<SelectAppMetadata[] | undefined> {
private async getAppMetadata(): Promise<SelectMetadata[] | undefined> {
const rows = await this.app.database
.selectFrom('metadata')
.selectAll()

View File

@@ -19,29 +19,21 @@ export class AvatarGetQueryHandler
return null;
}
return account.avatars.getAvatar(input.avatarId, true);
return this.app.assets.getAvatar(account.id, input.avatarId, true);
}
public async checkForChanges(
event: Event,
input: AvatarGetQueryInput
): Promise<ChangeCheckResult<AvatarGetQueryInput>> {
if (
event.type === 'avatar.created' &&
event.accountId === input.accountId &&
event.avatar.id === input.avatarId
) {
if (event.type === 'avatar.created' && event.avatar.id === input.avatarId) {
return {
hasChanges: true,
result: event.avatar,
};
}
if (
event.type === 'avatar.deleted' &&
event.accountId === input.accountId &&
event.avatar.id === input.avatarId
) {
if (event.type === 'avatar.deleted' && event.avatar.id === input.avatarId) {
return {
hasChanges: true,
result: null,

Some files were not shown because too many files have changed in this diff Show More