Refacor electron app and add migrations
This commit is contained in:
20
electron/cqrs/commands/handlers/clearAllData.ts
Normal file
20
electron/cqrs/commands/handlers/clearAllData.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import {
|
||||
MessageEntity,
|
||||
UserEntity,
|
||||
RoomEntity,
|
||||
ReactionEntity,
|
||||
BanEntity,
|
||||
AttachmentEntity,
|
||||
MetaEntity
|
||||
} from '../../../entities';
|
||||
|
||||
export async function handleClearAllData(dataSource: DataSource): Promise<void> {
|
||||
await dataSource.getRepository(MessageEntity).clear();
|
||||
await dataSource.getRepository(UserEntity).clear();
|
||||
await dataSource.getRepository(RoomEntity).clear();
|
||||
await dataSource.getRepository(ReactionEntity).clear();
|
||||
await dataSource.getRepository(BanEntity).clear();
|
||||
await dataSource.getRepository(AttachmentEntity).clear();
|
||||
await dataSource.getRepository(MetaEntity).clear();
|
||||
}
|
||||
9
electron/cqrs/commands/handlers/clearRoomMessages.ts
Normal file
9
electron/cqrs/commands/handlers/clearRoomMessages.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { MessageEntity } from '../../../entities';
|
||||
import { ClearRoomMessagesCommand } from '../../types';
|
||||
|
||||
export async function handleClearRoomMessages(command: ClearRoomMessagesCommand, dataSource: DataSource): Promise<void> {
|
||||
const repo = dataSource.getRepository(MessageEntity);
|
||||
|
||||
await repo.delete({ roomId: command.payload.roomId });
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { AttachmentEntity } from '../../../entities';
|
||||
import { DeleteAttachmentsForMessageCommand } from '../../types';
|
||||
|
||||
export async function handleDeleteAttachmentsForMessage(command: DeleteAttachmentsForMessageCommand, dataSource: DataSource): Promise<void> {
|
||||
const repo = dataSource.getRepository(AttachmentEntity);
|
||||
|
||||
await repo.delete({ messageId: command.payload.messageId });
|
||||
}
|
||||
9
electron/cqrs/commands/handlers/deleteMessage.ts
Normal file
9
electron/cqrs/commands/handlers/deleteMessage.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { MessageEntity } from '../../../entities';
|
||||
import { DeleteMessageCommand } from '../../types';
|
||||
|
||||
export async function handleDeleteMessage(command: DeleteMessageCommand, dataSource: DataSource): Promise<void> {
|
||||
const repo = dataSource.getRepository(MessageEntity);
|
||||
|
||||
await repo.delete({ id: command.payload.messageId });
|
||||
}
|
||||
9
electron/cqrs/commands/handlers/deleteRoom.ts
Normal file
9
electron/cqrs/commands/handlers/deleteRoom.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { RoomEntity, MessageEntity } from '../../../entities';
|
||||
import { DeleteRoomCommand } from '../../types';
|
||||
|
||||
export async function handleDeleteRoom(command: DeleteRoomCommand, dataSource: DataSource): Promise<void> {
|
||||
const { roomId } = command.payload;
|
||||
await dataSource.getRepository(RoomEntity).delete({ id: roomId });
|
||||
await dataSource.getRepository(MessageEntity).delete({ roomId });
|
||||
}
|
||||
9
electron/cqrs/commands/handlers/removeBan.ts
Normal file
9
electron/cqrs/commands/handlers/removeBan.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { BanEntity } from '../../../entities';
|
||||
import { RemoveBanCommand } from '../../types';
|
||||
|
||||
export async function handleRemoveBan(command: RemoveBanCommand, dataSource: DataSource): Promise<void> {
|
||||
const repo = dataSource.getRepository(BanEntity);
|
||||
|
||||
await repo.delete({ oderId: command.payload.oderId });
|
||||
}
|
||||
10
electron/cqrs/commands/handlers/removeReaction.ts
Normal file
10
electron/cqrs/commands/handlers/removeReaction.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { ReactionEntity } from '../../../entities';
|
||||
import { RemoveReactionCommand } from '../../types';
|
||||
|
||||
export async function handleRemoveReaction(command: RemoveReactionCommand, dataSource: DataSource): Promise<void> {
|
||||
const repo = dataSource.getRepository(ReactionEntity);
|
||||
const { messageId, userId, emoji } = command.payload;
|
||||
|
||||
await repo.delete({ messageId, userId, emoji });
|
||||
}
|
||||
21
electron/cqrs/commands/handlers/saveAttachment.ts
Normal file
21
electron/cqrs/commands/handlers/saveAttachment.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { AttachmentEntity } from '../../../entities';
|
||||
import { SaveAttachmentCommand } from '../../types';
|
||||
|
||||
export async function handleSaveAttachment(command: SaveAttachmentCommand, dataSource: DataSource): Promise<void> {
|
||||
const repo = dataSource.getRepository(AttachmentEntity);
|
||||
const { attachment } = command.payload;
|
||||
const entity = repo.create({
|
||||
id: attachment.id,
|
||||
messageId: attachment.messageId,
|
||||
filename: attachment.filename,
|
||||
size: attachment.size,
|
||||
mime: attachment.mime,
|
||||
isImage: attachment.isImage ? 1 : 0,
|
||||
uploaderPeerId: attachment.uploaderPeerId ?? null,
|
||||
filePath: attachment.filePath ?? null,
|
||||
savedPath: attachment.savedPath ?? null
|
||||
});
|
||||
|
||||
await repo.save(entity);
|
||||
}
|
||||
20
electron/cqrs/commands/handlers/saveBan.ts
Normal file
20
electron/cqrs/commands/handlers/saveBan.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { BanEntity } from '../../../entities';
|
||||
import { SaveBanCommand } from '../../types';
|
||||
|
||||
export async function handleSaveBan(command: SaveBanCommand, dataSource: DataSource): Promise<void> {
|
||||
const repo = dataSource.getRepository(BanEntity);
|
||||
const { ban } = command.payload;
|
||||
const entity = repo.create({
|
||||
oderId: ban.oderId,
|
||||
roomId: ban.roomId,
|
||||
userId: ban.userId ?? null,
|
||||
bannedBy: ban.bannedBy,
|
||||
displayName: ban.displayName ?? null,
|
||||
reason: ban.reason ?? null,
|
||||
expiresAt: ban.expiresAt ?? null,
|
||||
timestamp: ban.timestamp
|
||||
});
|
||||
|
||||
await repo.save(entity);
|
||||
}
|
||||
24
electron/cqrs/commands/handlers/saveMessage.ts
Normal file
24
electron/cqrs/commands/handlers/saveMessage.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { MessageEntity } from '../../../entities';
|
||||
import { SaveMessageCommand } from '../../types';
|
||||
|
||||
export async function handleSaveMessage(command: SaveMessageCommand, dataSource: DataSource): Promise<void> {
|
||||
const repo = dataSource.getRepository(MessageEntity);
|
||||
const { message } = command.payload;
|
||||
|
||||
const entity = repo.create({
|
||||
id: message.id,
|
||||
roomId: message.roomId,
|
||||
channelId: message.channelId ?? null,
|
||||
senderId: message.senderId,
|
||||
senderName: message.senderName,
|
||||
content: message.content,
|
||||
timestamp: message.timestamp,
|
||||
editedAt: message.editedAt ?? null,
|
||||
reactions: JSON.stringify(message.reactions ?? []),
|
||||
isDeleted: message.isDeleted ? 1 : 0,
|
||||
replyToId: message.replyToId ?? null,
|
||||
});
|
||||
|
||||
await repo.save(entity);
|
||||
}
|
||||
26
electron/cqrs/commands/handlers/saveReaction.ts
Normal file
26
electron/cqrs/commands/handlers/saveReaction.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { ReactionEntity } from '../../../entities';
|
||||
import { SaveReactionCommand } from '../../types';
|
||||
|
||||
export async function handleSaveReaction(command: SaveReactionCommand, dataSource: DataSource): Promise<void> {
|
||||
const repo = dataSource.getRepository(ReactionEntity);
|
||||
const { reaction } = command.payload;
|
||||
// Deduplicate: skip if same messageId + userId + emoji already exists
|
||||
const existing = await repo.findOne({
|
||||
where: { messageId: reaction.messageId, userId: reaction.userId, emoji: reaction.emoji }
|
||||
});
|
||||
|
||||
if (existing)
|
||||
return;
|
||||
|
||||
const entity = repo.create({
|
||||
id: reaction.id,
|
||||
messageId: reaction.messageId,
|
||||
oderId: reaction.oderId ?? null,
|
||||
userId: reaction.userId ?? null,
|
||||
emoji: reaction.emoji,
|
||||
timestamp: reaction.timestamp
|
||||
});
|
||||
|
||||
await repo.save(entity);
|
||||
}
|
||||
26
electron/cqrs/commands/handlers/saveRoom.ts
Normal file
26
electron/cqrs/commands/handlers/saveRoom.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { RoomEntity } from '../../../entities';
|
||||
import { SaveRoomCommand } from '../../types';
|
||||
|
||||
export async function handleSaveRoom(command: SaveRoomCommand, dataSource: DataSource): Promise<void> {
|
||||
const repo = dataSource.getRepository(RoomEntity);
|
||||
const { room } = command.payload;
|
||||
const entity = repo.create({
|
||||
id: room.id,
|
||||
name: room.name,
|
||||
description: room.description ?? null,
|
||||
topic: room.topic ?? null,
|
||||
hostId: room.hostId,
|
||||
password: room.password ?? null,
|
||||
isPrivate: room.isPrivate ? 1 : 0,
|
||||
createdAt: room.createdAt,
|
||||
userCount: room.userCount ?? 0,
|
||||
maxUsers: room.maxUsers ?? null,
|
||||
icon: room.icon ?? null,
|
||||
iconUpdatedAt: room.iconUpdatedAt ?? null,
|
||||
permissions: room.permissions != null ? JSON.stringify(room.permissions) : null,
|
||||
channels: room.channels != null ? JSON.stringify(room.channels) : null
|
||||
});
|
||||
|
||||
await repo.save(entity);
|
||||
}
|
||||
26
electron/cqrs/commands/handlers/saveUser.ts
Normal file
26
electron/cqrs/commands/handlers/saveUser.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { UserEntity } from '../../../entities';
|
||||
import { SaveUserCommand } from '../../types';
|
||||
|
||||
export async function handleSaveUser(command: SaveUserCommand, dataSource: DataSource): Promise<void> {
|
||||
const repo = dataSource.getRepository(UserEntity);
|
||||
const { user } = command.payload;
|
||||
const entity = repo.create({
|
||||
id: user.id,
|
||||
oderId: user.oderId ?? null,
|
||||
username: user.username ?? null,
|
||||
displayName: user.displayName ?? null,
|
||||
avatarUrl: user.avatarUrl ?? null,
|
||||
status: user.status ?? null,
|
||||
role: user.role ?? null,
|
||||
joinedAt: user.joinedAt ?? null,
|
||||
peerId: user.peerId ?? null,
|
||||
isOnline: user.isOnline ? 1 : 0,
|
||||
isAdmin: user.isAdmin ? 1 : 0,
|
||||
isRoomOwner: user.isRoomOwner ? 1 : 0,
|
||||
voiceState: user.voiceState != null ? JSON.stringify(user.voiceState) : null,
|
||||
screenShareState: user.screenShareState != null ? JSON.stringify(user.screenShareState) : null
|
||||
});
|
||||
|
||||
await repo.save(entity);
|
||||
}
|
||||
9
electron/cqrs/commands/handlers/setCurrentUserId.ts
Normal file
9
electron/cqrs/commands/handlers/setCurrentUserId.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { MetaEntity } from '../../../entities';
|
||||
import { SetCurrentUserIdCommand } from '../../types';
|
||||
|
||||
export async function handleSetCurrentUserId(command: SetCurrentUserIdCommand, dataSource: DataSource): Promise<void> {
|
||||
const repo = dataSource.getRepository(MetaEntity);
|
||||
|
||||
await repo.save({ key: 'currentUserId', value: command.payload.userId });
|
||||
}
|
||||
41
electron/cqrs/commands/handlers/updateMessage.ts
Normal file
41
electron/cqrs/commands/handlers/updateMessage.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { MessageEntity } from '../../../entities';
|
||||
import { UpdateMessageCommand } from '../../types';
|
||||
|
||||
export async function handleUpdateMessage(command: UpdateMessageCommand, dataSource: DataSource): Promise<void> {
|
||||
const repo = dataSource.getRepository(MessageEntity);
|
||||
const { messageId, updates } = command.payload;
|
||||
const existing = await repo.findOne({ where: { id: messageId } });
|
||||
|
||||
if (!existing)
|
||||
return;
|
||||
|
||||
if (updates.channelId !== undefined)
|
||||
existing.channelId = updates.channelId ?? null;
|
||||
|
||||
if (updates.senderId !== undefined)
|
||||
existing.senderId = updates.senderId;
|
||||
|
||||
if (updates.senderName !== undefined)
|
||||
existing.senderName = updates.senderName;
|
||||
|
||||
if (updates.content !== undefined)
|
||||
existing.content = updates.content;
|
||||
|
||||
if (updates.timestamp !== undefined)
|
||||
existing.timestamp = updates.timestamp;
|
||||
|
||||
if (updates.editedAt !== undefined)
|
||||
existing.editedAt = updates.editedAt ?? null;
|
||||
|
||||
if (updates.reactions !== undefined)
|
||||
existing.reactions = JSON.stringify(updates.reactions ?? []);
|
||||
|
||||
if (updates.isDeleted !== undefined)
|
||||
existing.isDeleted = updates.isDeleted ? 1 : 0;
|
||||
|
||||
if (updates.replyToId !== undefined)
|
||||
existing.replyToId = updates.replyToId ?? null;
|
||||
|
||||
await repo.save(existing);
|
||||
}
|
||||
29
electron/cqrs/commands/handlers/updateRoom.ts
Normal file
29
electron/cqrs/commands/handlers/updateRoom.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { RoomEntity } from '../../../entities';
|
||||
import { UpdateRoomCommand } from '../../types';
|
||||
import {
|
||||
applyUpdates,
|
||||
boolToInt,
|
||||
jsonOrNull,
|
||||
TransformMap
|
||||
} from './utils/applyUpdates';
|
||||
|
||||
const ROOM_TRANSFORMS: TransformMap = {
|
||||
isPrivate: boolToInt,
|
||||
userCount: (val) => (val ?? 0),
|
||||
permissions: jsonOrNull,
|
||||
channels: jsonOrNull
|
||||
};
|
||||
|
||||
export async function handleUpdateRoom(command: UpdateRoomCommand, dataSource: DataSource): Promise<void> {
|
||||
const repo = dataSource.getRepository(RoomEntity);
|
||||
const { roomId, updates } = command.payload;
|
||||
const existing = await repo.findOne({ where: { id: roomId } });
|
||||
|
||||
if (!existing)
|
||||
return;
|
||||
|
||||
applyUpdates(existing, updates, ROOM_TRANSFORMS);
|
||||
await repo.save(existing);
|
||||
}
|
||||
|
||||
30
electron/cqrs/commands/handlers/updateUser.ts
Normal file
30
electron/cqrs/commands/handlers/updateUser.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { UserEntity } from '../../../entities';
|
||||
import { UpdateUserCommand } from '../../types';
|
||||
import {
|
||||
applyUpdates,
|
||||
boolToInt,
|
||||
jsonOrNull,
|
||||
TransformMap
|
||||
} from './utils/applyUpdates';
|
||||
|
||||
const USER_TRANSFORMS: TransformMap = {
|
||||
isOnline: boolToInt,
|
||||
isAdmin: boolToInt,
|
||||
isRoomOwner: boolToInt,
|
||||
voiceState: jsonOrNull,
|
||||
screenShareState: jsonOrNull
|
||||
};
|
||||
|
||||
export async function handleUpdateUser(command: UpdateUserCommand, dataSource: DataSource): Promise<void> {
|
||||
const repo = dataSource.getRepository(UserEntity);
|
||||
const { userId, updates } = command.payload;
|
||||
const existing = await repo.findOne({ where: { id: userId } });
|
||||
|
||||
if (!existing)
|
||||
return;
|
||||
|
||||
applyUpdates(existing, updates, USER_TRANSFORMS);
|
||||
await repo.save(existing);
|
||||
}
|
||||
|
||||
32
electron/cqrs/commands/handlers/utils/applyUpdates.ts
Normal file
32
electron/cqrs/commands/handlers/utils/applyUpdates.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/** Converts a boolean-like value to SQLite's 0/1 integer representation. */
|
||||
export const boolToInt = (val: unknown) => (val ? 1 : 0);
|
||||
|
||||
/** Serialises an object to a JSON string, or returns null if the value is null/undefined. */
|
||||
export const jsonOrNull = (val: unknown) => (val != null ? JSON.stringify(val) : null);
|
||||
|
||||
/** A map of field names to transform functions that handle special serialisation. */
|
||||
export type TransformMap = Partial<Record<string, (val: unknown) => unknown>>;
|
||||
|
||||
/**
|
||||
* Applies a partial `updates` object onto an existing entity.
|
||||
*
|
||||
* - Fields absent from `updates` (undefined) are skipped entirely.
|
||||
* - Fields listed in `transforms` are passed through their transform function first.
|
||||
* - All other fields are written as-is, falling back to null when the value is null/undefined.
|
||||
*/
|
||||
export function applyUpdates<T extends object>(
|
||||
entity: T,
|
||||
updates: Partial<Record<string, unknown>>,
|
||||
transforms: TransformMap = {}
|
||||
): void {
|
||||
const target = entity as unknown as Record<string, unknown>;
|
||||
|
||||
for (const [key, value] of Object.entries(updates)) {
|
||||
if (value === undefined)
|
||||
continue;
|
||||
|
||||
const transform = transforms[key];
|
||||
|
||||
target[key] = transform ? transform(value) : (value ?? null);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user