mirror of
https://github.com/colanode/colanode.git
synced 2025-12-25 16:09:31 +01:00
Implement file and message deletes
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user