mirror of
https://github.com/colanode/colanode.git
synced 2025-12-29 00:25:03 +01:00
Improve synchronization flow
This commit is contained in:
8
desktop/package-lock.json
generated
8
desktop/package-lock.json
generated
@@ -118,7 +118,7 @@
|
||||
"prettier-plugin-tailwindcss": "^0.6.8",
|
||||
"tailwindcss": "^3.4.13",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.6.2",
|
||||
"typescript": "^5.6.3",
|
||||
"vite": "^5.4.8"
|
||||
}
|
||||
},
|
||||
@@ -13590,9 +13590,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
|
||||
"integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
|
||||
"version": "5.6.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
|
||||
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
"prettier-plugin-tailwindcss": "^0.6.8",
|
||||
"tailwindcss": "^3.4.13",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.6.2",
|
||||
"typescript": "^5.6.3",
|
||||
"vite": "^5.4.8"
|
||||
},
|
||||
"keywords": [],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { MessageHandler, MessageMap } from '@/operations/messages';
|
||||
import { ServerChangeMessageHandler } from '@/main/handlers/messages/server-change';
|
||||
import { ServerChangeAckMessageHandler } from '@/main/handlers/messages/server-change-ack';
|
||||
import { ServerChangeResultMessageHandler } from '@/main/handlers/messages/server-change-result';
|
||||
import { ServerChangeBatchMessageHandler } from '@/main/handlers/messages/server-change-batch';
|
||||
|
||||
type MessageHandlerMap = {
|
||||
[K in keyof MessageMap]: MessageHandler<MessageMap[K]>;
|
||||
@@ -8,5 +9,6 @@ type MessageHandlerMap = {
|
||||
|
||||
export const messageHandlerMap: MessageHandlerMap = {
|
||||
server_change: new ServerChangeMessageHandler(),
|
||||
server_change_ack: new ServerChangeAckMessageHandler(),
|
||||
server_change_result: new ServerChangeResultMessageHandler(),
|
||||
server_change_batch: new ServerChangeBatchMessageHandler(),
|
||||
};
|
||||
|
||||
16
desktop/src/main/handlers/messages/server-change-batch.ts
Normal file
16
desktop/src/main/handlers/messages/server-change-batch.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { MessageContext, MessageHandler } from '@/operations/messages';
|
||||
import { ServerChangeBatchMessageInput } from '@/operations/messages/server-change-batch';
|
||||
import { synchronizer } from '@/main/synchronizer';
|
||||
|
||||
export class ServerChangeBatchMessageHandler
|
||||
implements MessageHandler<ServerChangeBatchMessageInput>
|
||||
{
|
||||
public async handleMessage(
|
||||
context: MessageContext,
|
||||
input: ServerChangeBatchMessageInput,
|
||||
): Promise<void> {
|
||||
for (const change of input.changes) {
|
||||
await synchronizer.handleServerChange(context.accountId, change);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import { MessageContext, MessageHandler } from '@/operations/messages';
|
||||
import { ServerChangeAckMessageInput } from '@/operations/messages/server-change-ack';
|
||||
import { ServerChangeResultMessageInput } from '@/operations/messages/server-change-result';
|
||||
import { socketManager } from '@/main/sockets/socket-manager';
|
||||
|
||||
export class ServerChangeAckMessageHandler
|
||||
implements MessageHandler<ServerChangeAckMessageInput>
|
||||
export class ServerChangeResultMessageHandler
|
||||
implements MessageHandler<ServerChangeResultMessageInput>
|
||||
{
|
||||
public async handleMessage(
|
||||
context: MessageContext,
|
||||
input: ServerChangeAckMessageInput,
|
||||
input: ServerChangeResultMessageInput,
|
||||
): Promise<void> {
|
||||
socketManager.sendMessage(context.accountId, input);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MessageContext, MessageHandler } from '@/operations/messages';
|
||||
import { ServerChangeMessageInput } from '@/operations/messages/server-change';
|
||||
import { mediator } from '@/main/mediator';
|
||||
import { synchronizer } from '@/main/synchronizer';
|
||||
|
||||
export class ServerChangeMessageHandler
|
||||
implements MessageHandler<ServerChangeMessageInput>
|
||||
@@ -9,53 +9,6 @@ export class ServerChangeMessageHandler
|
||||
context: MessageContext,
|
||||
input: ServerChangeMessageInput,
|
||||
): Promise<void> {
|
||||
if (input.change.table === 'nodes' && input.change.workspaceId) {
|
||||
await mediator.executeMutation({
|
||||
type: 'node_sync',
|
||||
id: input.change.id,
|
||||
accountId: context.accountId,
|
||||
workspaceId: input.change.workspaceId,
|
||||
action: input.change.action,
|
||||
after: input.change.after,
|
||||
before: input.change.before,
|
||||
});
|
||||
} else if (
|
||||
input.change.table === 'node_reactions' &&
|
||||
input.change.workspaceId
|
||||
) {
|
||||
await mediator.executeMutation({
|
||||
type: 'node_reaction_sync',
|
||||
id: input.change.id,
|
||||
accountId: context.accountId,
|
||||
workspaceId: input.change.workspaceId,
|
||||
action: input.change.action,
|
||||
after: input.change.after,
|
||||
before: input.change.before,
|
||||
});
|
||||
} else if (
|
||||
input.change.table === 'node_collaborator' &&
|
||||
input.change.workspaceId
|
||||
) {
|
||||
await mediator.executeMutation({
|
||||
type: 'node_collaborator_sync',
|
||||
id: input.change.id,
|
||||
accountId: context.accountId,
|
||||
workspaceId: input.change.workspaceId,
|
||||
action: input.change.action,
|
||||
after: input.change.after,
|
||||
before: input.change.before,
|
||||
});
|
||||
}
|
||||
|
||||
await mediator.executeMessage(
|
||||
{
|
||||
accountId: context.accountId,
|
||||
deviceId: context.deviceId,
|
||||
},
|
||||
{
|
||||
type: 'server_change_ack',
|
||||
changeId: input.change.id,
|
||||
},
|
||||
);
|
||||
await synchronizer.handleServerChange(context.accountId, input.change);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,11 +26,16 @@ import { WorkspaceAccountRoleUpdateMutationHandler } from '@/main/handlers/mutat
|
||||
import { WorkspaceAccountsInviteMutationHandler } from '@/main/handlers/mutations/workspace-accounts-invite';
|
||||
import { WorkspaceCreateMutationHandler } from '@/main/handlers/mutations/workspace-create';
|
||||
import { WorkspaceUpdateMutationHandler } from '@/main/handlers/mutations/workspace-update';
|
||||
import { NodeSyncMutationHandler } from '@/main/handlers/mutations/node-sync';
|
||||
import { NodeReactionSyncMutationHandler } from '@/main/handlers/mutations/node-reaction-sync';
|
||||
import { NodeCollaboratorSyncMutationHandler } from '@/main/handlers/mutations/node-collaborator-sync';
|
||||
import { DocumentSaveMutationHandler } from '@/main/handlers/mutations/document-save';
|
||||
import { AvatarUploadMutationHandler } from '@/main/handlers/mutations/avatar-upload';
|
||||
import { NodeServerCreateMutationHandler } from '@/main/handlers/mutations/node-server-create';
|
||||
import { NodeServerUpdateMutationHandler } from '@/main/handlers/mutations/node-server-update';
|
||||
import { NodeServerDeleteMutationHandler } from '@/main/handlers/mutations/node-server-delete';
|
||||
import { NodeCollaboratorServerCreateMutationHandler } from '@/main/handlers/mutations/node-collaborator-server-create';
|
||||
import { NodeCollaboratorServerUpdateMutationHandler } from '@/main/handlers/mutations/node-collaborator-server-update';
|
||||
import { NodeCollaboratorServerDeleteMutationHandler } from '@/main/handlers/mutations/node-collaborator-server-delete';
|
||||
import { NodeReactionServerCreateMutationHandler } from '@/main/handlers/mutations/node-reaction-server-create';
|
||||
import { NodeReactionServerDeleteMutationHandler } from '@/main/handlers/mutations/node-reaction-server-delete';
|
||||
|
||||
type MutationHandlerMap = {
|
||||
[K in keyof MutationMap]: MutationHandler<MutationMap[K]['input']>;
|
||||
@@ -65,9 +70,17 @@ export const mutationHandlerMap: MutationHandlerMap = {
|
||||
workspace_accounts_invite: new WorkspaceAccountsInviteMutationHandler(),
|
||||
workspace_create: new WorkspaceCreateMutationHandler(),
|
||||
workspace_update: new WorkspaceUpdateMutationHandler(),
|
||||
node_sync: new NodeSyncMutationHandler(),
|
||||
node_reaction_sync: new NodeReactionSyncMutationHandler(),
|
||||
node_collaborator_sync: new NodeCollaboratorSyncMutationHandler(),
|
||||
document_save: new DocumentSaveMutationHandler(),
|
||||
avatar_upload: new AvatarUploadMutationHandler(),
|
||||
node_server_create: new NodeServerCreateMutationHandler(),
|
||||
node_server_update: new NodeServerUpdateMutationHandler(),
|
||||
node_server_delete: new NodeServerDeleteMutationHandler(),
|
||||
node_collaborator_server_create:
|
||||
new NodeCollaboratorServerCreateMutationHandler(),
|
||||
node_collaborator_server_update:
|
||||
new NodeCollaboratorServerUpdateMutationHandler(),
|
||||
node_collaborator_server_delete:
|
||||
new NodeCollaboratorServerDeleteMutationHandler(),
|
||||
node_reaction_server_create: new NodeReactionServerCreateMutationHandler(),
|
||||
node_reaction_server_delete: new NodeReactionServerDeleteMutationHandler(),
|
||||
};
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { databaseManager } from '@/main/data/database-manager';
|
||||
import { MutationHandler, MutationResult } from '@/operations/mutations';
|
||||
import { NodeCollaboratorServerCreateMutationInput } from '@/operations/mutations/node-collaborator-server-create';
|
||||
|
||||
export class NodeCollaboratorServerCreateMutationHandler
|
||||
implements MutationHandler<NodeCollaboratorServerCreateMutationInput>
|
||||
{
|
||||
public async handleMutation(
|
||||
input: NodeCollaboratorServerCreateMutationInput,
|
||||
): Promise<MutationResult<NodeCollaboratorServerCreateMutationInput>> {
|
||||
const workspace = await databaseManager.appDatabase
|
||||
.selectFrom('workspaces')
|
||||
.selectAll()
|
||||
.where((eb) =>
|
||||
eb.and([
|
||||
eb('account_id', '=', input.accountId),
|
||||
eb('workspace_id', '=', input.workspaceId),
|
||||
]),
|
||||
)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!workspace) {
|
||||
return {
|
||||
output: {
|
||||
success: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const userId = workspace.user_id;
|
||||
const workspaceDatabase =
|
||||
await databaseManager.getWorkspaceDatabase(userId);
|
||||
|
||||
await workspaceDatabase
|
||||
.insertInto('node_collaborators')
|
||||
.values({
|
||||
node_id: input.nodeId,
|
||||
collaborator_id: input.collaboratorId,
|
||||
role: input.role,
|
||||
created_at: input.createdAt,
|
||||
created_by: input.createdBy,
|
||||
version_id: input.versionId,
|
||||
server_created_at: input.serverCreatedAt,
|
||||
server_version_id: input.versionId,
|
||||
})
|
||||
.onConflict((cb) =>
|
||||
cb
|
||||
.doUpdateSet({
|
||||
server_created_at: input.serverCreatedAt,
|
||||
server_version_id: input.versionId,
|
||||
})
|
||||
.where('server_version_id', 'is', null),
|
||||
)
|
||||
.execute();
|
||||
|
||||
return {
|
||||
output: {
|
||||
success: true,
|
||||
},
|
||||
changes: [
|
||||
{
|
||||
type: 'workspace',
|
||||
table: 'nodes',
|
||||
userId: userId,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { databaseManager } from '@/main/data/database-manager';
|
||||
import { MutationHandler, MutationResult } from '@/operations/mutations';
|
||||
import { NodeCollaboratorServerDeleteMutationInput } from '@/operations/mutations/node-collaborator-server-delete';
|
||||
|
||||
export class NodeCollaboratorServerDeleteMutationHandler
|
||||
implements MutationHandler<NodeCollaboratorServerDeleteMutationInput>
|
||||
{
|
||||
public async handleMutation(
|
||||
input: NodeCollaboratorServerDeleteMutationInput,
|
||||
): Promise<MutationResult<NodeCollaboratorServerDeleteMutationInput>> {
|
||||
const workspace = await databaseManager.appDatabase
|
||||
.selectFrom('workspaces')
|
||||
.selectAll()
|
||||
.where((eb) =>
|
||||
eb.and([
|
||||
eb('account_id', '=', input.accountId),
|
||||
eb('workspace_id', '=', input.workspaceId),
|
||||
]),
|
||||
)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!workspace) {
|
||||
return {
|
||||
output: {
|
||||
success: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const userId = workspace.user_id;
|
||||
const workspaceDatabase =
|
||||
await databaseManager.getWorkspaceDatabase(userId);
|
||||
|
||||
await workspaceDatabase
|
||||
.deleteFrom('node_collaborators')
|
||||
.where((eb) =>
|
||||
eb.and([
|
||||
eb('node_id', '=', input.nodeId),
|
||||
eb('collaborator_id', '=', input.collaboratorId),
|
||||
]),
|
||||
)
|
||||
.execute();
|
||||
|
||||
return {
|
||||
output: {
|
||||
success: true,
|
||||
},
|
||||
changes: [
|
||||
{
|
||||
type: 'workspace',
|
||||
table: 'node_collaborators',
|
||||
userId: userId,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import { databaseManager } from '@/main/data/database-manager';
|
||||
import { MutationHandler, MutationResult } from '@/operations/mutations';
|
||||
import { NodeCollaboratorServerUpdateMutationInput } from '@/operations/mutations/node-collaborator-server-update';
|
||||
|
||||
export class NodeCollaboratorServerUpdateMutationHandler
|
||||
implements MutationHandler<NodeCollaboratorServerUpdateMutationInput>
|
||||
{
|
||||
public async handleMutation(
|
||||
input: NodeCollaboratorServerUpdateMutationInput,
|
||||
): Promise<MutationResult<NodeCollaboratorServerUpdateMutationInput>> {
|
||||
const workspace = await databaseManager.appDatabase
|
||||
.selectFrom('workspaces')
|
||||
.selectAll()
|
||||
.where((eb) =>
|
||||
eb.and([
|
||||
eb('account_id', '=', input.accountId),
|
||||
eb('workspace_id', '=', input.workspaceId),
|
||||
]),
|
||||
)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!workspace) {
|
||||
return {
|
||||
output: {
|
||||
success: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const userId = workspace.user_id;
|
||||
const workspaceDatabase =
|
||||
await databaseManager.getWorkspaceDatabase(userId);
|
||||
|
||||
const nodeCollaborator = await workspaceDatabase
|
||||
.selectFrom('node_collaborators')
|
||||
.selectAll()
|
||||
.where((eb) =>
|
||||
eb.and([
|
||||
eb('node_id', '=', input.nodeId),
|
||||
eb('collaborator_id', '=', input.collaboratorId),
|
||||
]),
|
||||
)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (nodeCollaborator.server_version_id === input.versionId) {
|
||||
return {
|
||||
output: {
|
||||
success: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (nodeCollaborator.updated_at) {
|
||||
const localUpdatedAt = new Date(nodeCollaborator.updated_at);
|
||||
const serverUpdatedAt = new Date(input.updatedAt);
|
||||
if (localUpdatedAt > serverUpdatedAt) {
|
||||
return {
|
||||
output: {
|
||||
success: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
await workspaceDatabase
|
||||
.updateTable('node_collaborators')
|
||||
.set({
|
||||
role: input.role,
|
||||
updated_at: input.updatedAt,
|
||||
updated_by: input.updatedBy,
|
||||
version_id: input.versionId,
|
||||
server_updated_at: input.serverUpdatedAt,
|
||||
server_version_id: input.versionId,
|
||||
})
|
||||
.where((eb) =>
|
||||
eb.and([
|
||||
eb('node_id', '=', input.nodeId),
|
||||
eb('collaborator_id', '=', input.collaboratorId),
|
||||
]),
|
||||
)
|
||||
.execute();
|
||||
|
||||
return {
|
||||
output: {
|
||||
success: true,
|
||||
},
|
||||
changes: [
|
||||
{
|
||||
type: 'workspace',
|
||||
table: 'node_collaborators',
|
||||
userId: userId,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
import { databaseManager } from '@/main/data/database-manager';
|
||||
import { MutationHandler, MutationResult } from '@/operations/mutations';
|
||||
import { NodeCollaboratorSyncMutationInput } from '@/operations/mutations/node-collaborator-sync';
|
||||
import { ServerNodeCollaborator, ServerNodeReaction } from '@/types/nodes';
|
||||
|
||||
export class NodeCollaboratorSyncMutationHandler
|
||||
implements MutationHandler<NodeCollaboratorSyncMutationInput>
|
||||
{
|
||||
public async handleMutation(
|
||||
input: NodeCollaboratorSyncMutationInput,
|
||||
): Promise<MutationResult<NodeCollaboratorSyncMutationInput>> {
|
||||
const workspace = await databaseManager.appDatabase
|
||||
.selectFrom('workspaces')
|
||||
.selectAll()
|
||||
.where((eb) =>
|
||||
eb.and([
|
||||
eb('account_id', '=', input.accountId),
|
||||
eb('workspace_id', '=', input.workspaceId),
|
||||
]),
|
||||
)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!workspace) {
|
||||
return {
|
||||
output: {
|
||||
success: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const userId = workspace.user_id;
|
||||
if (input.action === 'insert' && input.after) {
|
||||
await this.insertNodeCollaborator(userId, input.after);
|
||||
} else if (input.action === 'update' && input.after) {
|
||||
await this.updateNodeCollaborator(userId, input.after);
|
||||
} else if (input.action === 'delete' && input.before) {
|
||||
await this.deleteNodeReaction(userId, input.before);
|
||||
}
|
||||
|
||||
return {
|
||||
output: {
|
||||
success: true,
|
||||
},
|
||||
changes: [
|
||||
{
|
||||
type: 'workspace',
|
||||
table: 'node_collaborators',
|
||||
userId: userId,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
private async insertNodeCollaborator(
|
||||
userId: string,
|
||||
nodeCollaborator: ServerNodeCollaborator,
|
||||
): Promise<void> {
|
||||
const workspaceDatabase =
|
||||
await databaseManager.getWorkspaceDatabase(userId);
|
||||
|
||||
await workspaceDatabase
|
||||
.insertInto('node_collaborators')
|
||||
.values({
|
||||
node_id: nodeCollaborator.nodeId,
|
||||
collaborator_id: nodeCollaborator.collaboratorId,
|
||||
role: nodeCollaborator.role,
|
||||
created_at: nodeCollaborator.createdAt,
|
||||
created_by: nodeCollaborator.createdBy,
|
||||
updated_by: nodeCollaborator.updatedBy,
|
||||
updated_at: nodeCollaborator.updatedAt,
|
||||
version_id: nodeCollaborator.versionId,
|
||||
server_created_at: nodeCollaborator.serverCreatedAt,
|
||||
server_updated_at: nodeCollaborator.serverUpdatedAt,
|
||||
server_version_id: nodeCollaborator.versionId,
|
||||
})
|
||||
.onConflict((cb) =>
|
||||
cb
|
||||
.doUpdateSet({
|
||||
server_created_at: nodeCollaborator.serverCreatedAt,
|
||||
server_updated_at: nodeCollaborator.serverUpdatedAt,
|
||||
server_version_id: nodeCollaborator.versionId,
|
||||
})
|
||||
.where('version_id', '=', nodeCollaborator.versionId),
|
||||
)
|
||||
.execute();
|
||||
}
|
||||
|
||||
private async updateNodeCollaborator(
|
||||
userId: string,
|
||||
nodeCollaborator: ServerNodeCollaborator,
|
||||
): Promise<void> {
|
||||
const workspaceDatabase =
|
||||
await databaseManager.getWorkspaceDatabase(userId);
|
||||
|
||||
const existingNodeCollaborator = await workspaceDatabase
|
||||
.selectFrom('node_collaborators')
|
||||
.selectAll()
|
||||
.where((eb) =>
|
||||
eb.and([
|
||||
eb('node_id', '=', nodeCollaborator.nodeId),
|
||||
eb('collaborator_id', '=', nodeCollaborator.collaboratorId),
|
||||
]),
|
||||
)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (
|
||||
existingNodeCollaborator.server_version_id === nodeCollaborator.versionId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingNodeCollaborator.updated_at) {
|
||||
if (!nodeCollaborator.updatedAt) {
|
||||
return;
|
||||
}
|
||||
const localUpdatedAt = new Date(existingNodeCollaborator.updated_at);
|
||||
const serverUpdatedAt = new Date(nodeCollaborator.updatedAt);
|
||||
if (localUpdatedAt > serverUpdatedAt) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await workspaceDatabase
|
||||
.updateTable('node_collaborators')
|
||||
.set({
|
||||
role: nodeCollaborator.role,
|
||||
updated_at: nodeCollaborator.updatedAt,
|
||||
updated_by: nodeCollaborator.updatedBy,
|
||||
version_id: nodeCollaborator.versionId,
|
||||
server_created_at: nodeCollaborator.serverCreatedAt,
|
||||
server_updated_at: nodeCollaborator.serverUpdatedAt,
|
||||
server_version_id: nodeCollaborator.versionId,
|
||||
})
|
||||
.where((eb) =>
|
||||
eb.and([
|
||||
eb('node_id', '=', nodeCollaborator.nodeId),
|
||||
eb('collaborator_id', '=', nodeCollaborator.collaboratorId),
|
||||
]),
|
||||
)
|
||||
.execute();
|
||||
}
|
||||
|
||||
private async deleteNodeReaction(
|
||||
userId: string,
|
||||
nodeCollaborator: ServerNodeCollaborator,
|
||||
): Promise<void> {
|
||||
const workspaceDatabase =
|
||||
await databaseManager.getWorkspaceDatabase(userId);
|
||||
|
||||
await workspaceDatabase
|
||||
.deleteFrom('node_collaborators')
|
||||
.where((eb) =>
|
||||
eb.and([
|
||||
eb('node_id', '=', nodeCollaborator.nodeId),
|
||||
eb('collaborator_id', '=', nodeCollaborator.collaboratorId),
|
||||
]),
|
||||
)
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { databaseManager } from '@/main/data/database-manager';
|
||||
import { MutationHandler, MutationResult } from '@/operations/mutations';
|
||||
import { NodeReactionServerCreateMutationInput } from '@/operations/mutations/node-reaction-server-create';
|
||||
|
||||
export class NodeReactionServerCreateMutationHandler
|
||||
implements MutationHandler<NodeReactionServerCreateMutationInput>
|
||||
{
|
||||
public async handleMutation(
|
||||
input: NodeReactionServerCreateMutationInput,
|
||||
): Promise<MutationResult<NodeReactionServerCreateMutationInput>> {
|
||||
const workspace = await databaseManager.appDatabase
|
||||
.selectFrom('workspaces')
|
||||
.selectAll()
|
||||
.where((eb) =>
|
||||
eb.and([
|
||||
eb('account_id', '=', input.accountId),
|
||||
eb('workspace_id', '=', input.workspaceId),
|
||||
]),
|
||||
)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!workspace) {
|
||||
return {
|
||||
output: {
|
||||
success: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const userId = workspace.user_id;
|
||||
const workspaceDatabase =
|
||||
await databaseManager.getWorkspaceDatabase(userId);
|
||||
|
||||
await workspaceDatabase
|
||||
.insertInto('node_reactions')
|
||||
.values({
|
||||
node_id: input.nodeId,
|
||||
actor_id: input.actorId,
|
||||
reaction: input.reaction,
|
||||
created_at: input.createdAt,
|
||||
server_created_at: input.serverCreatedAt,
|
||||
})
|
||||
.onConflict((cb) =>
|
||||
cb
|
||||
.doUpdateSet({
|
||||
server_created_at: input.serverCreatedAt,
|
||||
})
|
||||
.where('server_created_at', 'is', null),
|
||||
)
|
||||
.execute();
|
||||
|
||||
return {
|
||||
output: {
|
||||
success: true,
|
||||
},
|
||||
changes: [
|
||||
{
|
||||
type: 'workspace',
|
||||
table: 'node_reactions',
|
||||
userId: userId,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { databaseManager } from '@/main/data/database-manager';
|
||||
import { MutationHandler, MutationResult } from '@/operations/mutations';
|
||||
import { NodeReactionServerDeleteMutationInput } from '@/operations/mutations/node-reaction-server-delete';
|
||||
|
||||
export class NodeReactionServerDeleteMutationHandler
|
||||
implements MutationHandler<NodeReactionServerDeleteMutationInput>
|
||||
{
|
||||
public async handleMutation(
|
||||
input: NodeReactionServerDeleteMutationInput,
|
||||
): Promise<MutationResult<NodeReactionServerDeleteMutationInput>> {
|
||||
const workspace = await databaseManager.appDatabase
|
||||
.selectFrom('workspaces')
|
||||
.selectAll()
|
||||
.where((eb) =>
|
||||
eb.and([
|
||||
eb('account_id', '=', input.accountId),
|
||||
eb('workspace_id', '=', input.workspaceId),
|
||||
]),
|
||||
)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!workspace) {
|
||||
return {
|
||||
output: {
|
||||
success: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const userId = workspace.user_id;
|
||||
const workspaceDatabase =
|
||||
await databaseManager.getWorkspaceDatabase(userId);
|
||||
|
||||
await workspaceDatabase
|
||||
.deleteFrom('node_reactions')
|
||||
.where((eb) =>
|
||||
eb.and([
|
||||
eb('node_id', '=', input.nodeId),
|
||||
eb('actor_id', '=', input.actorId),
|
||||
eb('reaction', '=', input.reaction),
|
||||
]),
|
||||
)
|
||||
.execute();
|
||||
|
||||
return {
|
||||
output: {
|
||||
success: true,
|
||||
},
|
||||
changes: [
|
||||
{
|
||||
type: 'workspace',
|
||||
table: 'node_reactions',
|
||||
userId: userId,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
import { databaseManager } from '@/main/data/database-manager';
|
||||
import { MutationHandler, MutationResult } from '@/operations/mutations';
|
||||
import { NodeReactionSyncMutationInput } from '@/operations/mutations/node-reaction-sync';
|
||||
import { ServerNodeReaction } from '@/types/nodes';
|
||||
|
||||
export class NodeReactionSyncMutationHandler
|
||||
implements MutationHandler<NodeReactionSyncMutationInput>
|
||||
{
|
||||
public async handleMutation(
|
||||
input: NodeReactionSyncMutationInput,
|
||||
): Promise<MutationResult<NodeReactionSyncMutationInput>> {
|
||||
const workspace = await databaseManager.appDatabase
|
||||
.selectFrom('workspaces')
|
||||
.selectAll()
|
||||
.where((eb) =>
|
||||
eb.and([
|
||||
eb('account_id', '=', input.accountId),
|
||||
eb('workspace_id', '=', input.workspaceId),
|
||||
]),
|
||||
)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!workspace) {
|
||||
return {
|
||||
output: {
|
||||
success: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const userId = workspace.user_id;
|
||||
if (input.action === 'insert' && input.after) {
|
||||
await this.insertNodeReaction(userId, input.after);
|
||||
} else if (input.action === 'delete' && input.before) {
|
||||
await this.deleteNodeReaction(userId, input.before);
|
||||
}
|
||||
|
||||
return {
|
||||
output: {
|
||||
success: true,
|
||||
},
|
||||
changes: [
|
||||
{
|
||||
type: 'workspace',
|
||||
table: 'node_reactions',
|
||||
userId: userId,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
private async insertNodeReaction(
|
||||
userId: string,
|
||||
nodeReaction: ServerNodeReaction,
|
||||
): Promise<void> {
|
||||
const workspaceDatabase =
|
||||
await databaseManager.getWorkspaceDatabase(userId);
|
||||
|
||||
await workspaceDatabase
|
||||
.insertInto('node_reactions')
|
||||
.values({
|
||||
node_id: nodeReaction.nodeId,
|
||||
actor_id: nodeReaction.actorId,
|
||||
reaction: nodeReaction.reaction,
|
||||
created_at: nodeReaction.createdAt,
|
||||
server_created_at: nodeReaction.serverCreatedAt,
|
||||
})
|
||||
.onConflict((ob) =>
|
||||
ob.doUpdateSet({
|
||||
server_created_at: nodeReaction.serverCreatedAt,
|
||||
}),
|
||||
)
|
||||
.execute();
|
||||
}
|
||||
|
||||
private async deleteNodeReaction(
|
||||
userId: string,
|
||||
nodeReaction: ServerNodeReaction,
|
||||
): Promise<void> {
|
||||
const workspaceDatabase =
|
||||
await databaseManager.getWorkspaceDatabase(userId);
|
||||
|
||||
await workspaceDatabase
|
||||
.deleteFrom('node_reactions')
|
||||
.where((eb) =>
|
||||
eb.and([
|
||||
eb('node_id', '=', nodeReaction.nodeId),
|
||||
eb('actor_id', '=', nodeReaction.actorId),
|
||||
eb('reaction', '=', nodeReaction.reaction),
|
||||
]),
|
||||
)
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
79
desktop/src/main/handlers/mutations/node-server-create.ts
Normal file
79
desktop/src/main/handlers/mutations/node-server-create.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { databaseManager } from '@/main/data/database-manager';
|
||||
import { MutationHandler, MutationResult } from '@/operations/mutations';
|
||||
import { NodeServerCreateMutationInput } from '@/operations/mutations/node-server-create';
|
||||
import { toUint8Array } from 'js-base64';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
export class NodeServerCreateMutationHandler
|
||||
implements MutationHandler<NodeServerCreateMutationInput>
|
||||
{
|
||||
public async handleMutation(
|
||||
input: NodeServerCreateMutationInput,
|
||||
): Promise<MutationResult<NodeServerCreateMutationInput>> {
|
||||
const workspace = await databaseManager.appDatabase
|
||||
.selectFrom('workspaces')
|
||||
.selectAll()
|
||||
.where((eb) =>
|
||||
eb.and([
|
||||
eb('account_id', '=', input.accountId),
|
||||
eb('workspace_id', '=', input.workspaceId),
|
||||
]),
|
||||
)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!workspace) {
|
||||
return {
|
||||
output: {
|
||||
success: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const userId = workspace.user_id;
|
||||
const workspaceDatabase =
|
||||
await databaseManager.getWorkspaceDatabase(userId);
|
||||
|
||||
const doc = new Y.Doc({
|
||||
guid: input.id,
|
||||
});
|
||||
Y.applyUpdate(doc, toUint8Array(input.state));
|
||||
|
||||
const attributesMap = doc.getMap('attributes');
|
||||
const attributes = JSON.stringify(attributesMap.toJSON());
|
||||
|
||||
await workspaceDatabase
|
||||
.insertInto('nodes')
|
||||
.values({
|
||||
id: input.id,
|
||||
attributes: attributes,
|
||||
state: input.state,
|
||||
created_at: input.createdAt,
|
||||
created_by: input.createdBy,
|
||||
version_id: input.versionId,
|
||||
server_created_at: input.serverCreatedAt,
|
||||
server_version_id: input.versionId,
|
||||
})
|
||||
.onConflict((cb) =>
|
||||
cb
|
||||
.doUpdateSet({
|
||||
server_created_at: input.serverCreatedAt,
|
||||
server_version_id: input.versionId,
|
||||
})
|
||||
.where('version_id', '=', input.versionId),
|
||||
)
|
||||
.execute();
|
||||
|
||||
return {
|
||||
output: {
|
||||
success: true,
|
||||
},
|
||||
changes: [
|
||||
{
|
||||
type: 'workspace',
|
||||
table: 'nodes',
|
||||
userId: userId,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
52
desktop/src/main/handlers/mutations/node-server-delete.ts
Normal file
52
desktop/src/main/handlers/mutations/node-server-delete.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { databaseManager } from '@/main/data/database-manager';
|
||||
import { MutationHandler, MutationResult } from '@/operations/mutations';
|
||||
import { NodeServerDeleteMutationInput } from '@/operations/mutations/node-server-delete';
|
||||
|
||||
export class NodeServerDeleteMutationHandler
|
||||
implements MutationHandler<NodeServerDeleteMutationInput>
|
||||
{
|
||||
public async handleMutation(
|
||||
input: NodeServerDeleteMutationInput,
|
||||
): Promise<MutationResult<NodeServerDeleteMutationInput>> {
|
||||
const workspace = await databaseManager.appDatabase
|
||||
.selectFrom('workspaces')
|
||||
.selectAll()
|
||||
.where((eb) =>
|
||||
eb.and([
|
||||
eb('account_id', '=', input.accountId),
|
||||
eb('workspace_id', '=', input.workspaceId),
|
||||
]),
|
||||
)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!workspace) {
|
||||
return {
|
||||
output: {
|
||||
success: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const userId = workspace.user_id;
|
||||
const workspaceDatabase =
|
||||
await databaseManager.getWorkspaceDatabase(userId);
|
||||
|
||||
await workspaceDatabase
|
||||
.deleteFrom('nodes')
|
||||
.where('id', '=', input.id)
|
||||
.execute();
|
||||
|
||||
return {
|
||||
output: {
|
||||
success: true,
|
||||
},
|
||||
changes: [
|
||||
{
|
||||
type: 'workspace',
|
||||
table: 'nodes',
|
||||
userId: userId,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
88
desktop/src/main/handlers/mutations/node-server-update.ts
Normal file
88
desktop/src/main/handlers/mutations/node-server-update.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { databaseManager } from '@/main/data/database-manager';
|
||||
import { MutationHandler, MutationResult } from '@/operations/mutations';
|
||||
import { NodeServerUpdateMutationInput } from '@/operations/mutations/node-server-update';
|
||||
import { fromUint8Array, toUint8Array } from 'js-base64';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
export class NodeServerUpdateMutationHandler
|
||||
implements MutationHandler<NodeServerUpdateMutationInput>
|
||||
{
|
||||
public async handleMutation(
|
||||
input: NodeServerUpdateMutationInput,
|
||||
): Promise<MutationResult<NodeServerUpdateMutationInput>> {
|
||||
const workspace = await databaseManager.appDatabase
|
||||
.selectFrom('workspaces')
|
||||
.selectAll()
|
||||
.where((eb) =>
|
||||
eb.and([
|
||||
eb('account_id', '=', input.accountId),
|
||||
eb('workspace_id', '=', input.workspaceId),
|
||||
]),
|
||||
)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!workspace) {
|
||||
return {
|
||||
output: {
|
||||
success: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const userId = workspace.user_id;
|
||||
const workspaceDatabase =
|
||||
await databaseManager.getWorkspaceDatabase(userId);
|
||||
|
||||
const node = await workspaceDatabase
|
||||
.selectFrom('nodes')
|
||||
.selectAll()
|
||||
.where('id', '=', input.id)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!node) {
|
||||
return {
|
||||
output: {
|
||||
success: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const doc = new Y.Doc({
|
||||
guid: input.id,
|
||||
});
|
||||
|
||||
Y.applyUpdate(doc, toUint8Array(node.state));
|
||||
Y.applyUpdate(doc, toUint8Array(input.update));
|
||||
|
||||
const attributesMap = doc.getMap('attributes');
|
||||
const attributes = JSON.stringify(attributesMap.toJSON());
|
||||
const state = fromUint8Array(Y.encodeStateAsUpdate(doc));
|
||||
|
||||
await workspaceDatabase
|
||||
.updateTable('nodes')
|
||||
.set({
|
||||
attributes: attributes,
|
||||
state: state,
|
||||
updated_at: input.updatedAt,
|
||||
updated_by: input.updatedBy,
|
||||
version_id: input.versionId,
|
||||
server_version_id: input.versionId,
|
||||
server_updated_at: input.serverUpdatedAt,
|
||||
})
|
||||
.where('id', '=', input.id)
|
||||
.execute();
|
||||
|
||||
return {
|
||||
output: {
|
||||
success: true,
|
||||
},
|
||||
changes: [
|
||||
{
|
||||
type: 'workspace',
|
||||
table: 'nodes',
|
||||
userId: userId,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
import { databaseManager } from '@/main/data/database-manager';
|
||||
import { MutationHandler, MutationResult } from '@/operations/mutations';
|
||||
import { NodeSyncMutationInput } from '@/operations/mutations/node-sync';
|
||||
import { ServerNode } from '@/types/nodes';
|
||||
import { fromUint8Array, toUint8Array } from 'js-base64';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
export class NodeSyncMutationHandler
|
||||
implements MutationHandler<NodeSyncMutationInput>
|
||||
{
|
||||
public async handleMutation(
|
||||
input: NodeSyncMutationInput,
|
||||
): Promise<MutationResult<NodeSyncMutationInput>> {
|
||||
const workspace = await databaseManager.appDatabase
|
||||
.selectFrom('workspaces')
|
||||
.selectAll()
|
||||
.where((eb) =>
|
||||
eb.and([
|
||||
eb('account_id', '=', input.accountId),
|
||||
eb('workspace_id', '=', input.workspaceId),
|
||||
]),
|
||||
)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!workspace) {
|
||||
return {
|
||||
output: {
|
||||
success: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const userId = workspace.user_id;
|
||||
if (input.action === 'insert' && input.after) {
|
||||
await this.insertNode(userId, input.after);
|
||||
} else if (input.action === 'update' && input.after) {
|
||||
await this.updateNode(userId, input.after);
|
||||
} else if (input.action === 'delete' && input.before) {
|
||||
await this.deleteNode(userId, input.before);
|
||||
}
|
||||
|
||||
return {
|
||||
output: {
|
||||
success: true,
|
||||
},
|
||||
changes: [
|
||||
{
|
||||
type: 'workspace',
|
||||
table: 'nodes',
|
||||
userId: userId,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
private async insertNode(userId: string, node: ServerNode): Promise<void> {
|
||||
const workspaceDatabase =
|
||||
await databaseManager.getWorkspaceDatabase(userId);
|
||||
|
||||
await workspaceDatabase
|
||||
.insertInto('nodes')
|
||||
.values({
|
||||
id: node.id,
|
||||
attributes: JSON.stringify(node.attributes),
|
||||
state: node.state,
|
||||
created_at: node.createdAt,
|
||||
created_by: node.createdBy,
|
||||
updated_by: node.updatedBy,
|
||||
updated_at: node.updatedAt,
|
||||
version_id: node.versionId,
|
||||
server_created_at: node.serverCreatedAt,
|
||||
server_updated_at: node.serverUpdatedAt,
|
||||
server_version_id: node.versionId,
|
||||
})
|
||||
.onConflict((cb) =>
|
||||
cb
|
||||
.doUpdateSet({
|
||||
server_created_at: node.serverCreatedAt,
|
||||
server_updated_at: node.serverUpdatedAt,
|
||||
server_version_id: node.versionId,
|
||||
})
|
||||
.where('version_id', '=', node.versionId),
|
||||
)
|
||||
.execute();
|
||||
}
|
||||
|
||||
private async updateNode(userId: string, node: ServerNode): Promise<void> {
|
||||
const workspaceDatabase =
|
||||
await databaseManager.getWorkspaceDatabase(userId);
|
||||
|
||||
const existingNode = await workspaceDatabase
|
||||
.selectFrom('nodes')
|
||||
.selectAll()
|
||||
.where('id', '=', node.id)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingNode.version_id === node.versionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const doc = new Y.Doc({
|
||||
guid: node.id,
|
||||
});
|
||||
|
||||
Y.applyUpdate(doc, toUint8Array(existingNode.state));
|
||||
Y.applyUpdate(doc, toUint8Array(node.state));
|
||||
|
||||
const attributesMap = doc.getMap('attributes');
|
||||
const attributes = JSON.stringify(attributesMap.toJSON());
|
||||
const encodedState = fromUint8Array(Y.encodeStateAsUpdate(doc));
|
||||
|
||||
await workspaceDatabase
|
||||
.updateTable('nodes')
|
||||
.set({
|
||||
attributes: attributes,
|
||||
state: encodedState,
|
||||
updated_at: node.updatedAt,
|
||||
updated_by: node.updatedBy,
|
||||
version_id: node.versionId,
|
||||
server_created_at: node.serverCreatedAt,
|
||||
server_updated_at: node.serverUpdatedAt,
|
||||
server_version_id: node.versionId,
|
||||
})
|
||||
.where('id', '=', node.id)
|
||||
.execute();
|
||||
}
|
||||
|
||||
private async deleteNode(userId: string, node: ServerNode): Promise<void> {
|
||||
const workspaceDatabase =
|
||||
await databaseManager.getWorkspaceDatabase(userId);
|
||||
|
||||
await workspaceDatabase
|
||||
.deleteFrom('nodes')
|
||||
.where('id', '=', node.id)
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
import { BackoffCalculator } from '@/lib/backoff-calculator';
|
||||
import { buildAxiosInstance } from '@/lib/servers';
|
||||
import { SelectWorkspace } from '@/main/data/app/schema';
|
||||
import { databaseManager } from '@/main/data/database-manager';
|
||||
import { ServerSyncResponse, WorkspaceSyncData } from '@/types/sync';
|
||||
import {
|
||||
ServerChange,
|
||||
ServerChangeData,
|
||||
ServerSyncResponse,
|
||||
} from '@/types/sync';
|
||||
import { mediator } from '@/main/mediator';
|
||||
|
||||
const EVENT_LOOP_INTERVAL = 100;
|
||||
|
||||
@@ -26,7 +30,6 @@ class Synchronizer {
|
||||
|
||||
private async executeEventLoop() {
|
||||
try {
|
||||
await this.checkForWorkspaceSyncs();
|
||||
await this.checkForWorkspaceChanges();
|
||||
} catch (error) {
|
||||
console.log('error', error);
|
||||
@@ -35,19 +38,140 @@ class Synchronizer {
|
||||
setTimeout(this.executeEventLoop, EVENT_LOOP_INTERVAL);
|
||||
}
|
||||
|
||||
private async checkForWorkspaceSyncs(): Promise<void> {
|
||||
const unsyncedWorkspaces = await databaseManager.appDatabase
|
||||
.selectFrom('workspaces')
|
||||
.selectAll()
|
||||
.where('synced', '=', 0)
|
||||
.execute();
|
||||
|
||||
if (unsyncedWorkspaces.length === 0) {
|
||||
return;
|
||||
public async handleServerChange(accountId: string, change: ServerChange) {
|
||||
const executed = await this.executeServerChange(accountId, change.data);
|
||||
if (executed) {
|
||||
await mediator.executeMessage(
|
||||
{
|
||||
accountId,
|
||||
deviceId: change.deviceId,
|
||||
},
|
||||
{
|
||||
type: 'server_change_result',
|
||||
changeId: change.id,
|
||||
success: executed,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (const workspace of unsyncedWorkspaces) {
|
||||
await this.syncWorkspace(workspace);
|
||||
private async executeServerChange(
|
||||
accountId: string,
|
||||
data: ServerChangeData,
|
||||
): Promise<boolean> {
|
||||
switch (data.type) {
|
||||
case 'node_create': {
|
||||
const result = await mediator.executeMutation({
|
||||
type: 'node_server_create',
|
||||
accountId: accountId,
|
||||
workspaceId: data.workspaceId,
|
||||
id: data.id,
|
||||
state: data.state,
|
||||
createdAt: data.createdAt,
|
||||
serverCreatedAt: data.serverCreatedAt,
|
||||
createdBy: data.createdBy,
|
||||
versionId: data.versionId,
|
||||
});
|
||||
|
||||
return result.success;
|
||||
}
|
||||
case 'node_update': {
|
||||
const result = await mediator.executeMutation({
|
||||
type: 'node_server_update',
|
||||
accountId: accountId,
|
||||
workspaceId: data.workspaceId,
|
||||
id: data.id,
|
||||
update: data.update,
|
||||
updatedAt: data.updatedAt,
|
||||
updatedBy: data.updatedBy,
|
||||
versionId: data.versionId,
|
||||
serverUpdatedAt: data.serverUpdatedAt,
|
||||
});
|
||||
|
||||
return result.success;
|
||||
}
|
||||
case 'node_delete': {
|
||||
const result = await mediator.executeMutation({
|
||||
type: 'node_server_delete',
|
||||
accountId: accountId,
|
||||
workspaceId: data.workspaceId,
|
||||
id: data.id,
|
||||
});
|
||||
|
||||
return result.success;
|
||||
}
|
||||
case 'node_collaborator_create': {
|
||||
const result = await mediator.executeMutation({
|
||||
type: 'node_collaborator_server_create',
|
||||
accountId: accountId,
|
||||
workspaceId: data.workspaceId,
|
||||
nodeId: data.nodeId,
|
||||
collaboratorId: data.collaboratorId,
|
||||
role: data.role,
|
||||
createdAt: data.createdAt,
|
||||
createdBy: data.createdBy,
|
||||
versionId: data.versionId,
|
||||
serverCreatedAt: data.serverCreatedAt,
|
||||
});
|
||||
|
||||
return result.success;
|
||||
}
|
||||
case 'node_collaborator_update': {
|
||||
const result = await mediator.executeMutation({
|
||||
type: 'node_collaborator_server_update',
|
||||
accountId: accountId,
|
||||
workspaceId: data.workspaceId,
|
||||
nodeId: data.nodeId,
|
||||
collaboratorId: data.collaboratorId,
|
||||
role: data.role,
|
||||
updatedAt: data.updatedAt,
|
||||
updatedBy: data.updatedBy,
|
||||
versionId: data.versionId,
|
||||
serverUpdatedAt: data.serverUpdatedAt,
|
||||
});
|
||||
|
||||
return result.success;
|
||||
}
|
||||
case 'node_collaborator_delete': {
|
||||
const result = await mediator.executeMutation({
|
||||
type: 'node_collaborator_server_delete',
|
||||
accountId: accountId,
|
||||
workspaceId: data.workspaceId,
|
||||
nodeId: data.nodeId,
|
||||
collaboratorId: data.collaboratorId,
|
||||
});
|
||||
|
||||
return result.success;
|
||||
}
|
||||
case 'node_reaction_create': {
|
||||
const result = await mediator.executeMutation({
|
||||
type: 'node_reaction_server_create',
|
||||
accountId: accountId,
|
||||
workspaceId: data.workspaceId,
|
||||
nodeId: data.nodeId,
|
||||
actorId: data.actorId,
|
||||
reaction: data.reaction,
|
||||
createdAt: data.createdAt,
|
||||
serverCreatedAt: data.serverCreatedAt,
|
||||
});
|
||||
|
||||
return result.success;
|
||||
}
|
||||
case 'node_reaction_delete': {
|
||||
const result = await mediator.executeMutation({
|
||||
type: 'node_reaction_server_delete',
|
||||
accountId: accountId,
|
||||
workspaceId: data.workspaceId,
|
||||
nodeId: data.nodeId,
|
||||
actorId: data.actorId,
|
||||
reaction: data.reaction,
|
||||
});
|
||||
|
||||
return result.success;
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +187,6 @@ class Synchronizer {
|
||||
'servers.domain',
|
||||
'servers.attributes',
|
||||
])
|
||||
.where('workspaces.synced', '=', 1)
|
||||
.execute();
|
||||
|
||||
for (const workspace of workspaces) {
|
||||
@@ -147,127 +270,6 @@ class Synchronizer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async syncWorkspace(workspace: SelectWorkspace): Promise<void> {
|
||||
if (this.workspaceBackoffs.has(workspace.user_id)) {
|
||||
const backoff = this.workspaceBackoffs.get(workspace.user_id);
|
||||
if (!backoff.canRetry()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const credentials = await databaseManager.appDatabase
|
||||
.selectFrom('accounts')
|
||||
.innerJoin('servers', 'accounts.server', 'servers.domain')
|
||||
.select(['domain', 'attributes', 'token'])
|
||||
.where('id', '=', workspace.account_id)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!credentials) {
|
||||
return;
|
||||
}
|
||||
|
||||
const axios = buildAxiosInstance(
|
||||
credentials.domain,
|
||||
credentials.attributes,
|
||||
credentials.token,
|
||||
);
|
||||
const { data } = await axios.get<WorkspaceSyncData>(
|
||||
`/v1/sync/${workspace.workspace_id}`,
|
||||
);
|
||||
|
||||
await databaseManager.deleteWorkspaceDatabase(
|
||||
workspace.account_id,
|
||||
workspace.workspace_id,
|
||||
workspace.user_id,
|
||||
);
|
||||
|
||||
const workspaceDatabase = await databaseManager.getWorkspaceDatabase(
|
||||
workspace.user_id,
|
||||
);
|
||||
|
||||
await workspaceDatabase.transaction().execute(async (trx) => {
|
||||
if (data.nodes.length > 0) {
|
||||
await trx
|
||||
.insertInto('nodes')
|
||||
.values(
|
||||
data.nodes.map((node) => {
|
||||
return {
|
||||
id: node.id,
|
||||
attributes: JSON.stringify(node.attributes),
|
||||
state: node.state,
|
||||
created_at: node.createdAt,
|
||||
created_by: node.createdBy,
|
||||
updated_at: node.updatedAt,
|
||||
updated_by: node.updatedBy,
|
||||
version_id: node.versionId,
|
||||
server_created_at: node.serverCreatedAt,
|
||||
server_updated_at: node.serverUpdatedAt,
|
||||
server_version_id: node.versionId,
|
||||
};
|
||||
}),
|
||||
)
|
||||
.execute();
|
||||
}
|
||||
|
||||
if (data.nodeReactions.length > 0) {
|
||||
await trx
|
||||
.insertInto('node_reactions')
|
||||
.values(
|
||||
data.nodeReactions.map((nodeReaction) => {
|
||||
return {
|
||||
node_id: nodeReaction.nodeId,
|
||||
actor_id: nodeReaction.actorId,
|
||||
reaction: nodeReaction.reaction,
|
||||
created_at: nodeReaction.createdAt,
|
||||
server_created_at: nodeReaction.serverCreatedAt,
|
||||
};
|
||||
}),
|
||||
)
|
||||
.execute();
|
||||
}
|
||||
|
||||
if (data.nodeCollaborators.length > 0) {
|
||||
await trx
|
||||
.insertInto('node_collaborators')
|
||||
.values(
|
||||
data.nodeCollaborators.map((nodeCollaborator) => {
|
||||
return {
|
||||
node_id: nodeCollaborator.nodeId,
|
||||
collaborator_id: nodeCollaborator.collaboratorId,
|
||||
role: nodeCollaborator.role,
|
||||
created_at: nodeCollaborator.createdAt,
|
||||
created_by: nodeCollaborator.createdBy,
|
||||
updated_at: nodeCollaborator.updatedAt,
|
||||
updated_by: nodeCollaborator.updatedBy,
|
||||
version_id: nodeCollaborator.versionId,
|
||||
server_created_at: nodeCollaborator.serverCreatedAt,
|
||||
server_updated_at: nodeCollaborator.serverUpdatedAt,
|
||||
server_version_id: nodeCollaborator.versionId,
|
||||
};
|
||||
}),
|
||||
)
|
||||
.execute();
|
||||
}
|
||||
});
|
||||
|
||||
await databaseManager.appDatabase
|
||||
.updateTable('workspaces')
|
||||
.set({
|
||||
synced: 1,
|
||||
})
|
||||
.where('user_id', '=', workspace.user_id)
|
||||
.execute();
|
||||
} catch (error) {
|
||||
if (!this.workspaceBackoffs.has(workspace.user_id)) {
|
||||
this.workspaceBackoffs.set(workspace.user_id, new BackoffCalculator());
|
||||
}
|
||||
|
||||
const backoff = this.workspaceBackoffs.get(workspace.user_id);
|
||||
backoff.increaseError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const synchronizer = new Synchronizer();
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
export type ServerChangeAckMessageInput = {
|
||||
type: 'server_change_ack';
|
||||
changeId: string;
|
||||
};
|
||||
|
||||
declare module '@/operations/messages' {
|
||||
interface MessageMap {
|
||||
server_change_ack: ServerChangeAckMessageInput;
|
||||
}
|
||||
}
|
||||
12
desktop/src/operations/messages/server-change-batch.ts
Normal file
12
desktop/src/operations/messages/server-change-batch.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ServerChange } from '@/types/sync';
|
||||
|
||||
export type ServerChangeBatchMessageInput = {
|
||||
type: 'server_change_batch';
|
||||
changes: ServerChange[];
|
||||
};
|
||||
|
||||
declare module '@/operations/messages' {
|
||||
interface MessageMap {
|
||||
server_change_batch: ServerChangeBatchMessageInput;
|
||||
}
|
||||
}
|
||||
11
desktop/src/operations/messages/server-change-result.ts
Normal file
11
desktop/src/operations/messages/server-change-result.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export type ServerChangeResultMessageInput = {
|
||||
type: 'server_change_result';
|
||||
changeId: string;
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
declare module '@/operations/messages' {
|
||||
interface MessageMap {
|
||||
server_change_result: ServerChangeResultMessageInput;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
export type NodeCollaboratorServerCreateMutationInput = {
|
||||
type: 'node_collaborator_server_create';
|
||||
accountId: string;
|
||||
workspaceId: string;
|
||||
nodeId: string;
|
||||
collaboratorId: string;
|
||||
role: string;
|
||||
createdAt: string;
|
||||
createdBy: string;
|
||||
versionId: string;
|
||||
serverCreatedAt: string;
|
||||
};
|
||||
|
||||
export type NodeCollaboratorServerCreateMutationOutput = {
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
declare module '@/operations/mutations' {
|
||||
interface MutationMap {
|
||||
node_collaborator_server_create: {
|
||||
input: NodeCollaboratorServerCreateMutationInput;
|
||||
output: NodeCollaboratorServerCreateMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
export type NodeCollaboratorServerDeleteMutationInput = {
|
||||
type: 'node_collaborator_server_delete';
|
||||
accountId: string;
|
||||
workspaceId: string;
|
||||
nodeId: string;
|
||||
collaboratorId: string;
|
||||
};
|
||||
|
||||
export type NodeCollaboratorServerDeleteMutationOutput = {
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
declare module '@/operations/mutations' {
|
||||
interface MutationMap {
|
||||
node_collaborator_server_delete: {
|
||||
input: NodeCollaboratorServerDeleteMutationInput;
|
||||
output: NodeCollaboratorServerDeleteMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
export type NodeCollaboratorServerUpdateMutationInput = {
|
||||
type: 'node_collaborator_server_update';
|
||||
accountId: string;
|
||||
workspaceId: string;
|
||||
nodeId: string;
|
||||
collaboratorId: string;
|
||||
role: string;
|
||||
updatedAt: string;
|
||||
updatedBy: string;
|
||||
versionId: string;
|
||||
serverUpdatedAt: string;
|
||||
};
|
||||
|
||||
export type NodeCollaboratorServerUpdateMutationOutput = {
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
declare module '@/operations/mutations' {
|
||||
interface MutationMap {
|
||||
node_collaborator_server_update: {
|
||||
input: NodeCollaboratorServerUpdateMutationInput;
|
||||
output: NodeCollaboratorServerUpdateMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
export type NodeCollaboratorSyncMutationInput = {
|
||||
type: 'node_collaborator_sync';
|
||||
accountId: string;
|
||||
workspaceId: string;
|
||||
id: string;
|
||||
action: string;
|
||||
before: any;
|
||||
after: any;
|
||||
};
|
||||
|
||||
export type NodeCollaboratorSyncMutationOutput = {
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
declare module '@/operations/mutations' {
|
||||
interface MutationMap {
|
||||
node_collaborator_sync: {
|
||||
input: NodeCollaboratorSyncMutationInput;
|
||||
output: NodeCollaboratorSyncMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
export type NodeReactionServerCreateMutationInput = {
|
||||
type: 'node_reaction_server_create';
|
||||
accountId: string;
|
||||
workspaceId: string;
|
||||
nodeId: string;
|
||||
actorId: string;
|
||||
reaction: string;
|
||||
createdAt: string;
|
||||
serverCreatedAt: string;
|
||||
};
|
||||
|
||||
export type NodeReactionServerCreateMutationOutput = {
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
declare module '@/operations/mutations' {
|
||||
interface MutationMap {
|
||||
node_reaction_server_create: {
|
||||
input: NodeReactionServerCreateMutationInput;
|
||||
output: NodeReactionServerCreateMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
export type NodeReactionServerDeleteMutationInput = {
|
||||
type: 'node_reaction_server_delete';
|
||||
accountId: string;
|
||||
workspaceId: string;
|
||||
nodeId: string;
|
||||
actorId: string;
|
||||
reaction: string;
|
||||
};
|
||||
|
||||
export type NodeReactionServerDeleteMutationOutput = {
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
declare module '@/operations/mutations' {
|
||||
interface MutationMap {
|
||||
node_reaction_server_delete: {
|
||||
input: NodeReactionServerDeleteMutationInput;
|
||||
output: NodeReactionServerDeleteMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
export type NodeReactionSyncMutationInput = {
|
||||
type: 'node_reaction_sync';
|
||||
accountId: string;
|
||||
workspaceId: string;
|
||||
id: string;
|
||||
action: string;
|
||||
before: any;
|
||||
after: any;
|
||||
};
|
||||
|
||||
export type NodeReactionSyncMutationOutput = {
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
declare module '@/operations/mutations' {
|
||||
interface MutationMap {
|
||||
node_reaction_sync: {
|
||||
input: NodeReactionSyncMutationInput;
|
||||
output: NodeReactionSyncMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
24
desktop/src/operations/mutations/node-server-create.ts
Normal file
24
desktop/src/operations/mutations/node-server-create.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export type NodeServerCreateMutationInput = {
|
||||
type: 'node_server_create';
|
||||
accountId: string;
|
||||
workspaceId: string;
|
||||
id: string;
|
||||
state: string;
|
||||
createdAt: string;
|
||||
createdBy: string;
|
||||
versionId: string;
|
||||
serverCreatedAt: string;
|
||||
};
|
||||
|
||||
export type NodeServerCreateMutationOutput = {
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
declare module '@/operations/mutations' {
|
||||
interface MutationMap {
|
||||
node_server_create: {
|
||||
input: NodeServerCreateMutationInput;
|
||||
output: NodeServerCreateMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
19
desktop/src/operations/mutations/node-server-delete.ts
Normal file
19
desktop/src/operations/mutations/node-server-delete.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export type NodeServerDeleteMutationInput = {
|
||||
type: 'node_server_delete';
|
||||
accountId: string;
|
||||
workspaceId: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type NodeServerDeleteMutationOutput = {
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
declare module '@/operations/mutations' {
|
||||
interface MutationMap {
|
||||
node_server_delete: {
|
||||
input: NodeServerDeleteMutationInput;
|
||||
output: NodeServerDeleteMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
24
desktop/src/operations/mutations/node-server-update.ts
Normal file
24
desktop/src/operations/mutations/node-server-update.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export type NodeServerUpdateMutationInput = {
|
||||
type: 'node_server_update';
|
||||
accountId: string;
|
||||
workspaceId: string;
|
||||
id: string;
|
||||
update: string;
|
||||
updatedAt: string;
|
||||
updatedBy: string;
|
||||
versionId: string;
|
||||
serverUpdatedAt: string;
|
||||
};
|
||||
|
||||
export type NodeServerUpdateMutationOutput = {
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
declare module '@/operations/mutations' {
|
||||
interface MutationMap {
|
||||
node_server_update: {
|
||||
input: NodeServerUpdateMutationInput;
|
||||
output: NodeServerUpdateMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
export type NodeSyncMutationInput = {
|
||||
type: 'node_sync';
|
||||
accountId: string;
|
||||
workspaceId: string;
|
||||
id: string;
|
||||
action: string;
|
||||
before: any;
|
||||
after: any;
|
||||
};
|
||||
|
||||
export type NodeSyncMutationOutput = {
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
declare module '@/operations/mutations' {
|
||||
interface MutationMap {
|
||||
node_sync: {
|
||||
input: NodeSyncMutationInput;
|
||||
output: NodeSyncMutationOutput;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -4,15 +4,6 @@ import {
|
||||
ServerNodeReaction,
|
||||
} from '@/types/nodes';
|
||||
|
||||
export type ServerChange = {
|
||||
id: string;
|
||||
table: string;
|
||||
action: string;
|
||||
workspaceId: string | null;
|
||||
before: any | null;
|
||||
after: any | null;
|
||||
};
|
||||
|
||||
export type ServerSyncResponse = {
|
||||
results: ServerSyncChangeResult[];
|
||||
};
|
||||
@@ -29,3 +20,98 @@ export type WorkspaceSyncData = {
|
||||
nodeReactions: ServerNodeReaction[];
|
||||
nodeCollaborators: ServerNodeCollaborator[];
|
||||
};
|
||||
|
||||
export type ServerChange = {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
deviceId: string;
|
||||
data: ServerChangeData;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
export type ServerChangeData =
|
||||
| ServerNodeCreateChangeData
|
||||
| ServerNodeUpdateChangeData
|
||||
| ServerNodeDeleteChangeData
|
||||
| ServerNodeCollaboratorCreateChangeData
|
||||
| ServerNodeCollaboratorUpdateChangeData
|
||||
| ServerNodeCollaboratorDeleteChangeData
|
||||
| ServerNodeReactionCreateChangeData
|
||||
| ServerNodeReactionDeleteChangeData;
|
||||
|
||||
export type ServerNodeCreateChangeData = {
|
||||
type: 'node_create';
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
state: string;
|
||||
createdAt: string;
|
||||
createdBy: string;
|
||||
versionId: string;
|
||||
serverCreatedAt: string;
|
||||
};
|
||||
|
||||
export type ServerNodeUpdateChangeData = {
|
||||
type: 'node_update';
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
update: string;
|
||||
updatedAt: string;
|
||||
updatedBy: string;
|
||||
versionId: string;
|
||||
serverUpdatedAt: string;
|
||||
};
|
||||
|
||||
export type ServerNodeDeleteChangeData = {
|
||||
type: 'node_delete';
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
};
|
||||
|
||||
export type ServerNodeCollaboratorCreateChangeData = {
|
||||
type: 'node_collaborator_create';
|
||||
nodeId: string;
|
||||
collaboratorId: string;
|
||||
role: string;
|
||||
workspaceId: string;
|
||||
createdAt: string;
|
||||
createdBy: string;
|
||||
versionId: string;
|
||||
serverCreatedAt: string;
|
||||
};
|
||||
|
||||
export type ServerNodeCollaboratorUpdateChangeData = {
|
||||
type: 'node_collaborator_update';
|
||||
nodeId: string;
|
||||
collaboratorId: string;
|
||||
workspaceId: string;
|
||||
role: string;
|
||||
updatedAt: string;
|
||||
updatedBy: string;
|
||||
versionId: string;
|
||||
serverUpdatedAt: string;
|
||||
};
|
||||
|
||||
export type ServerNodeCollaboratorDeleteChangeData = {
|
||||
type: 'node_collaborator_delete';
|
||||
nodeId: string;
|
||||
collaboratorId: string;
|
||||
workspaceId: string;
|
||||
};
|
||||
|
||||
export type ServerNodeReactionCreateChangeData = {
|
||||
type: 'node_reaction_create';
|
||||
nodeId: string;
|
||||
actorId: string;
|
||||
reaction: string;
|
||||
workspaceId: string;
|
||||
createdAt: string;
|
||||
serverCreatedAt: string;
|
||||
};
|
||||
|
||||
export type ServerNodeReactionDeleteChangeData = {
|
||||
type: 'node_reaction_delete';
|
||||
nodeId: string;
|
||||
actorId: string;
|
||||
reaction: string;
|
||||
workspaceId: string;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user