Implement file and message deletes

This commit is contained in:
Hakan Shehu
2024-12-27 14:39:57 +01:00
parent f76c786ede
commit 41ba627f52
8 changed files with 342 additions and 4 deletions

View File

@@ -1,9 +1,13 @@
import { entryService } from '@/main/services/entry-service';
import { DeleteFileMutationData, generateId, IdType } from '@colanode/core';
import { databaseService } from '@/main/data/database-service';
import { MutationHandler } from '@/main/types';
import {
FileDeleteMutationInput,
FileDeleteMutationOutput,
} from '@/shared/mutations/files/file-delete';
import { eventBus } from '@/shared/lib/event-bus';
import { mapFile } from '@/main/utils';
export class FileDeleteMutationHandler
implements MutationHandler<FileDeleteMutationInput>
@@ -11,7 +15,55 @@ export class FileDeleteMutationHandler
async handleMutation(
input: FileDeleteMutationInput
): Promise<FileDeleteMutationOutput> {
await entryService.deleteEntry(input.fileId, input.userId);
const workspaceDatabase = await databaseService.getWorkspaceDatabase(
input.userId
);
const file = await workspaceDatabase
.selectFrom('files')
.selectAll()
.where('id', '=', input.fileId)
.executeTakeFirst();
if (!file) {
return {
success: true,
};
}
const deletedAt = new Date().toISOString();
const deleteFileMutationData: DeleteFileMutationData = {
id: input.fileId,
rootId: file.root_id,
deletedAt,
};
await workspaceDatabase.transaction().execute(async (tx) => {
await tx.deleteFrom('files').where('id', '=', input.fileId).execute();
await tx
.insertInto('mutations')
.values({
id: generateId(IdType.Mutation),
type: 'delete_file',
node_id: input.fileId,
data: JSON.stringify(deleteFileMutationData),
created_at: deletedAt,
retries: 0,
})
.execute();
});
eventBus.publish({
type: 'file_deleted',
userId: input.userId,
file: mapFile(file),
});
eventBus.publish({
type: 'mutation_created',
userId: input.userId,
});
return {
success: true,

View File

@@ -1,9 +1,13 @@
import { entryService } from '@/main/services/entry-service';
import { DeleteMessageMutationData, generateId, IdType } from '@colanode/core';
import { databaseService } from '@/main/data/database-service';
import { MutationHandler } from '@/main/types';
import {
MessageDeleteMutationInput,
MessageDeleteMutationOutput,
} from '@/shared/mutations/messages/message-delete';
import { eventBus } from '@/shared/lib/event-bus';
import { mapMessage } from '@/main/utils';
export class MessageDeleteMutationHandler
implements MutationHandler<MessageDeleteMutationInput>
@@ -11,7 +15,58 @@ export class MessageDeleteMutationHandler
async handleMutation(
input: MessageDeleteMutationInput
): Promise<MessageDeleteMutationOutput> {
await entryService.deleteEntry(input.messageId, input.userId);
const workspaceDatabase = await databaseService.getWorkspaceDatabase(
input.userId
);
const message = await workspaceDatabase
.selectFrom('messages')
.selectAll()
.where('id', '=', input.messageId)
.executeTakeFirst();
if (!message) {
return {
success: true,
};
}
const deletedAt = new Date().toISOString();
const deleteMessageMutationData: DeleteMessageMutationData = {
id: input.messageId,
rootId: message.root_id,
deletedAt,
};
await workspaceDatabase.transaction().execute(async (tx) => {
await tx
.deleteFrom('messages')
.where('id', '=', input.messageId)
.execute();
await tx
.insertInto('mutations')
.values({
id: generateId(IdType.Mutation),
type: 'delete_message',
node_id: input.messageId,
data: JSON.stringify(deleteMessageMutationData),
created_at: deletedAt,
retries: 0,
})
.execute();
});
eventBus.publish({
type: 'message_deleted',
userId: input.userId,
message: mapMessage(message),
});
eventBus.publish({
type: 'mutation_created',
userId: input.userId,
});
return {
success: true,

View File

@@ -442,6 +442,46 @@ class FileService {
const workspaceDatabase =
await databaseService.getWorkspaceDatabase(userId);
if (file.deletedAt) {
const deletedFile = await workspaceDatabase
.deleteFrom('files')
.returningAll()
.where('id', '=', file.id)
.executeTakeFirst();
if (!deletedFile) {
return;
}
await workspaceDatabase
.deleteFrom('file_interactions')
.where('file_id', '=', file.id)
.execute();
await workspaceDatabase
.deleteFrom('file_states')
.where('file_id', '=', file.id)
.execute();
// if the file exists in the workspace, we need to delete it
const filePath = path.join(
getWorkspaceFilesDirectoryPath(userId),
`${file.id}${file.extension}`
);
if (fs.existsSync(filePath)) {
fs.rmSync(filePath, { force: true });
}
eventBus.publish({
type: 'file_deleted',
userId,
file: mapFile(deletedFile),
});
return;
}
const existingFile = await workspaceDatabase
.selectFrom('files')
.selectAll()

View File

@@ -21,6 +21,41 @@ class MessageService {
const workspaceDatabase =
await databaseService.getWorkspaceDatabase(userId);
if (message.deletedAt) {
const deletedMessage = await workspaceDatabase
.deleteFrom('messages')
.returningAll()
.where('id', '=', message.id)
.executeTakeFirst();
if (!deletedMessage) {
return;
}
await workspaceDatabase
.deleteFrom('message_reactions')
.where('message_id', '=', message.id)
.execute();
await workspaceDatabase
.deleteFrom('message_interactions')
.where('message_id', '=', message.id)
.execute();
await workspaceDatabase
.deleteFrom('texts')
.where('id', '=', message.id)
.execute();
eventBus.publish({
type: 'message_deleted',
userId,
message: mapMessage(deletedMessage),
});
return;
}
const existingMessage = await workspaceDatabase
.selectFrom('messages')
.selectAll()

View File

@@ -16,6 +16,8 @@ import {
MarkMessageSeenMutation,
MarkFileSeenMutation,
MarkFileOpenedMutation,
DeleteFileMutation,
DeleteMessageMutation,
} from '@colanode/core';
import { SelectUser } from '@/data/schema';
@@ -62,8 +64,12 @@ const handleMutation = async (
return await handleDeleteTransaction(user, mutation);
} else if (mutation.type === 'create_file') {
return await handleCreateFile(user, mutation);
} else if (mutation.type === 'delete_file') {
return await handleDeleteFile(user, mutation);
} else if (mutation.type === 'create_message') {
return await handleCreateMessage(user, mutation);
} else if (mutation.type === 'delete_message') {
return await handleDeleteMessage(user, mutation);
} else if (mutation.type === 'create_message_reaction') {
return await handleCreateMessageReaction(user, mutation);
} else if (mutation.type === 'delete_message_reaction') {
@@ -148,6 +154,14 @@ const handleCreateFile = async (
return output ? 'success' : 'error';
};
const handleDeleteFile = async (
user: SelectUser,
mutation: DeleteFileMutation
): Promise<SyncMutationStatus> => {
const output = await fileService.deleteFile(user, mutation);
return output ? 'success' : 'error';
};
const handleCreateMessage = async (
user: SelectUser,
mutation: CreateMessageMutation
@@ -156,6 +170,14 @@ const handleCreateMessage = async (
return output ? 'success' : 'error';
};
const handleDeleteMessage = async (
user: SelectUser,
mutation: DeleteMessageMutation
): Promise<SyncMutationStatus> => {
const output = await messageService.deleteMessage(user, mutation);
return output ? 'success' : 'error';
};
const handleCreateMessageReaction = async (
user: SelectUser,
mutation: CreateMessageReactionMutation

View File

@@ -1,5 +1,6 @@
import {
CreateFileMutation,
DeleteFileMutation,
extractEntryRole,
FileStatus,
hasCollaboratorAccess,
@@ -78,6 +79,60 @@ class FileService {
return true;
}
public async deleteFile(
user: SelectUser,
mutation: DeleteFileMutation
): Promise<boolean> {
const file = await database
.selectFrom('files')
.selectAll()
.where('id', '=', mutation.data.id)
.executeTakeFirst();
if (!file) {
return true;
}
const root = await database
.selectFrom('entries')
.selectAll()
.where('id', '=', mutation.data.rootId)
.executeTakeFirst();
if (!root) {
return false;
}
const rootEntry = mapEntry(root);
const role = extractEntryRole(rootEntry, user.id);
if (!hasCollaboratorAccess(role)) {
return false;
}
const deletedFile = await database
.updateTable('files')
.returningAll()
.set({
deleted_at: new Date(mutation.data.deletedAt),
deleted_by: user.id,
})
.where('id', '=', mutation.data.id)
.executeTakeFirst();
if (!deletedFile) {
return false;
}
eventBus.publish({
type: 'file_deleted',
fileId: deletedFile.id,
rootId: deletedFile.root_id,
workspaceId: deletedFile.workspace_id,
});
return true;
}
public async markFileAsSeen(
user: SelectUser,
mutation: MarkFileSeenMutation

View File

@@ -1,6 +1,7 @@
import {
CreateMessageMutation,
CreateMessageReactionMutation,
DeleteMessageMutation,
DeleteMessageReactionMutation,
extractEntryRole,
hasCollaboratorAccess,
@@ -73,6 +74,60 @@ class MessageService {
return true;
}
public async deleteMessage(
user: SelectUser,
mutation: DeleteMessageMutation
): Promise<boolean> {
const message = await database
.selectFrom('messages')
.select(['id', 'root_id', 'workspace_id'])
.where('id', '=', mutation.data.id)
.executeTakeFirst();
if (!message) {
return true;
}
const root = await database
.selectFrom('entries')
.selectAll()
.where('id', '=', message.root_id)
.executeTakeFirst();
if (!root) {
return false;
}
const rootEntry = mapEntry(root);
const role = extractEntryRole(rootEntry, user.id);
if (!hasCollaboratorAccess(role)) {
return false;
}
const deletedMessage = await database
.updateTable('messages')
.returningAll()
.set({
deleted_at: new Date(mutation.data.deletedAt),
deleted_by: user.id,
})
.where('id', '=', mutation.data.id)
.executeTakeFirst();
if (!deletedMessage) {
return false;
}
eventBus.publish({
type: 'message_deleted',
messageId: deletedMessage.id,
rootId: deletedMessage.root_id,
workspaceId: deletedMessage.workspace_id,
});
return true;
}
public async createMessageReaction(
user: SelectUser,
mutation: CreateMessageReactionMutation

View File

@@ -45,6 +45,17 @@ export type CreateFileMutation = MutationBase & {
data: CreateFileMutationData;
};
export type DeleteFileMutationData = {
id: string;
rootId: string;
deletedAt: string;
};
export type DeleteFileMutation = MutationBase & {
type: 'delete_file';
data: DeleteFileMutationData;
};
export type ApplyCreateTransactionMutation = MutationBase & {
type: 'apply_create_transaction';
data: LocalCreateTransaction;
@@ -75,6 +86,17 @@ export type CreateMessageMutation = MutationBase & {
data: CreateMessageMutationData;
};
export type DeleteMessageMutationData = {
id: string;
rootId: string;
deletedAt: string;
};
export type DeleteMessageMutation = MutationBase & {
type: 'delete_message';
data: DeleteMessageMutationData;
};
export type CreateMessageReactionMutationData = {
messageId: string;
reaction: string;
@@ -156,10 +178,12 @@ export type MarkEntryOpenedMutation = MutationBase & {
export type Mutation =
| CreateFileMutation
| DeleteFileMutation
| ApplyCreateTransactionMutation
| ApplyUpdateTransactionMutation
| ApplyDeleteTransactionMutation
| CreateMessageMutation
| DeleteMessageMutation
| CreateMessageReactionMutation
| DeleteMessageReactionMutation
| MarkMessageSeenMutation