fix: improve plugins functionality with server management

This commit is contained in:
2026-04-29 20:33:54 +02:00
parent b8f6d58d99
commit fa2cca6fa4
82 changed files with 1708 additions and 303 deletions

View File

@@ -1,9 +1,15 @@
import { DataSource } from 'typeorm';
import { MessageEntity } from '../../../entities';
import { ClearRoomMessagesCommand } from '../../types';
import { getCurrentUserScope } from '../../current-user-scope';
export async function handleClearRoomMessages(command: ClearRoomMessagesCommand, dataSource: DataSource): Promise<void> {
const repo = dataSource.getRepository(MessageEntity);
const currentUserId = await getCurrentUserScope(dataSource);
await repo.delete({ roomId: command.payload.roomId });
if (!currentUserId) {
return;
}
await repo.delete({ roomId: command.payload.roomId, ownerUserId: currentUserId });
}

View File

@@ -1,9 +1,15 @@
import { DataSource } from 'typeorm';
import { MessageEntity } from '../../../entities';
import { DeleteMessageCommand } from '../../types';
import { getCurrentUserScope } from '../../current-user-scope';
export async function handleDeleteMessage(command: DeleteMessageCommand, dataSource: DataSource): Promise<void> {
const repo = dataSource.getRepository(MessageEntity);
const currentUserId = await getCurrentUserScope(dataSource);
await repo.delete({ id: command.payload.messageId });
if (!currentUserId) {
return;
}
await repo.delete({ id: command.payload.messageId, ownerUserId: currentUserId });
}

View File

@@ -1,12 +1,19 @@
import { DataSource } from 'typeorm';
import { getCurrentUserScope } from '../../current-user-scope';
import { PluginDataEntity } from '../../../entities';
import { DeletePluginDataCommand } from '../../types';
export async function handleDeletePluginData(command: DeletePluginDataCommand, dataSource: DataSource): Promise<void> {
const { payload } = command;
const ownerUserId = await getCurrentUserScope(dataSource);
if (!ownerUserId) {
return;
}
await dataSource.getRepository(PluginDataEntity).delete({
key: payload.key,
ownerUserId,
pluginId: payload.pluginId,
scope: payload.scope,
serverId: payload.serverId ?? ''

View File

@@ -3,23 +3,39 @@ import {
RoomChannelPermissionEntity,
RoomChannelEntity,
RoomEntity,
RoomOwnerEntity,
RoomMemberEntity,
RoomRoleEntity,
RoomUserRoleEntity,
MessageEntity
} from '../../../entities';
import { DeleteRoomCommand } from '../../types';
import { getCurrentUserScope } from '../../current-user-scope';
export async function handleDeleteRoom(command: DeleteRoomCommand, dataSource: DataSource): Promise<void> {
const { roomId } = command.payload;
await dataSource.transaction(async (manager) => {
const currentUserId = await getCurrentUserScope(manager);
if (!currentUserId) {
return;
}
await manager.getRepository(RoomOwnerEntity).delete({ roomId, userId: currentUserId });
await manager.getRepository(MessageEntity).delete({ roomId, ownerUserId: currentUserId });
const remainingOwners = await manager.getRepository(RoomOwnerEntity).count({ where: { roomId } });
if (remainingOwners > 0) {
return;
}
await manager.getRepository(RoomChannelPermissionEntity).delete({ roomId });
await manager.getRepository(RoomChannelEntity).delete({ roomId });
await manager.getRepository(RoomMemberEntity).delete({ roomId });
await manager.getRepository(RoomRoleEntity).delete({ roomId });
await manager.getRepository(RoomUserRoleEntity).delete({ roomId });
await manager.getRepository(RoomEntity).delete({ id: roomId });
await manager.getRepository(MessageEntity).delete({ roomId });
});
}

View File

@@ -2,15 +2,18 @@ import { DataSource } from 'typeorm';
import { MessageEntity } from '../../../entities';
import { replaceMessageReactions } from '../../relations';
import { SaveMessageCommand } from '../../types';
import { getCurrentUserScope } from '../../current-user-scope';
export async function handleSaveMessage(command: SaveMessageCommand, dataSource: DataSource): Promise<void> {
const { message } = command.payload;
await dataSource.transaction(async (manager) => {
const currentUserId = await getCurrentUserScope(manager);
const repo = manager.getRepository(MessageEntity);
const entity = repo.create({
id: message.id,
roomId: message.roomId,
ownerUserId: currentUserId,
channelId: message.channelId ?? null,
senderId: message.senderId,
senderName: message.senderName,

View File

@@ -1,12 +1,19 @@
import { DataSource } from 'typeorm';
import { getCurrentUserScope } from '../../current-user-scope';
import { PluginDataEntity } from '../../../entities';
import { SavePluginDataCommand } from '../../types';
export async function handleSavePluginData(command: SavePluginDataCommand, dataSource: DataSource): Promise<void> {
const { payload } = command;
const ownerUserId = await getCurrentUserScope(dataSource);
if (!ownerUserId) {
return;
}
await dataSource.getRepository(PluginDataEntity).save({
key: payload.key,
ownerUserId,
pluginId: payload.pluginId,
scope: payload.scope,
serverId: payload.serverId ?? '',

View File

@@ -1,7 +1,8 @@
import { DataSource } from 'typeorm';
import { RoomEntity } from '../../../entities';
import { RoomEntity, RoomOwnerEntity } from '../../../entities';
import { replaceRoomRelations } from '../../relations';
import { SaveRoomCommand } from '../../types';
import { getCurrentUserScope } from '../../current-user-scope';
function extractSlowModeInterval(room: SaveRoomCommand['payload']['room']): number {
if (typeof room.slowModeInterval === 'number' && Number.isFinite(room.slowModeInterval)) {
@@ -21,6 +22,7 @@ export async function handleSaveRoom(command: SaveRoomCommand, dataSource: DataS
const { room } = command.payload;
await dataSource.transaction(async (manager) => {
const currentUserId = await getCurrentUserScope(manager);
const repo = manager.getRepository(RoomEntity);
const entity = repo.create({
id: room.id,
@@ -43,6 +45,15 @@ export async function handleSaveRoom(command: SaveRoomCommand, dataSource: DataS
});
await repo.save(entity);
if (currentUserId) {
await manager.getRepository(RoomOwnerEntity).save({
roomId: room.id,
userId: currentUserId,
savedAt: Date.now()
});
}
await replaceRoomRelations(manager, room.id, {
channels: room.channels ?? [],
members: room.members ?? [],

View File

@@ -2,13 +2,20 @@ import { DataSource } from 'typeorm';
import { MessageEntity } from '../../../entities';
import { replaceMessageReactions } from '../../relations';
import { UpdateMessageCommand } from '../../types';
import { getCurrentUserScope } from '../../current-user-scope';
export async function handleUpdateMessage(command: UpdateMessageCommand, dataSource: DataSource): Promise<void> {
const { messageId, updates } = command.payload;
await dataSource.transaction(async (manager) => {
const currentUserId = await getCurrentUserScope(manager);
if (!currentUserId) {
return;
}
const repo = manager.getRepository(MessageEntity);
const existing = await repo.findOne({ where: { id: messageId } });
const existing = await repo.findOne({ where: { id: messageId, ownerUserId: currentUserId } });
if (!existing)
return;

View File

@@ -7,6 +7,7 @@ import {
boolToInt,
TransformMap
} from './utils/applyUpdates';
import { getCurrentUserScope, userOwnsRoom } from '../../current-user-scope';
const ROOM_TRANSFORMS: TransformMap = {
hasPassword: boolToInt,
@@ -32,6 +33,12 @@ export async function handleUpdateRoom(command: UpdateRoomCommand, dataSource: D
const { roomId, updates } = command.payload;
await dataSource.transaction(async (manager) => {
const currentUserId = await getCurrentUserScope(manager);
if (!await userOwnsRoom(manager, roomId, currentUserId)) {
return;
}
const repo = manager.getRepository(RoomEntity);
const existing = await repo.findOne({ where: { id: roomId } });

View File

@@ -0,0 +1,24 @@
import { DataSource, EntityManager } from 'typeorm';
import { MetaEntity, RoomOwnerEntity } from '../entities';
export async function getCurrentUserScope(dataSourceOrManager: DataSource | EntityManager): Promise<string | null> {
const repo = dataSourceOrManager.getRepository(MetaEntity);
const meta = await repo.findOne({ where: { key: 'currentUserId' } });
return meta?.value?.trim() || null;
}
export async function userOwnsRoom(
dataSourceOrManager: DataSource | EntityManager,
roomId: string,
userId: string | null
): Promise<boolean> {
if (!userId) {
return false;
}
const repo = dataSourceOrManager.getRepository(RoomOwnerEntity);
const owner = await repo.findOne({ where: { roomId, userId } });
return !!owner;
}

View File

@@ -1,11 +1,28 @@
import { DataSource } from 'typeorm';
import { RoomEntity } from '../../../entities';
import { RoomEntity, RoomOwnerEntity } from '../../../entities';
import { rowToRoom } from '../../mappers';
import { loadRoomRelationsMap } from '../../relations';
import { getCurrentUserScope } from '../../current-user-scope';
export async function handleGetAllRooms(dataSource: DataSource) {
const currentUserId = await getCurrentUserScope(dataSource);
if (!currentUserId) {
return [];
}
const repo = dataSource.getRepository(RoomEntity);
const rows = await repo.find();
const ownershipRows = await dataSource.getRepository(RoomOwnerEntity).find({ where: { userId: currentUserId } });
const roomIds = ownershipRows.map((owner) => owner.roomId);
if (roomIds.length === 0) {
return [];
}
const rows = await repo
.createQueryBuilder('room')
.where('room.id IN (:...roomIds)', { roomIds })
.getMany();
const relationsByRoomId = await loadRoomRelationsMap(dataSource, rows.map((row) => row.id));
return rows.map((row) => rowToRoom(row, relationsByRoomId.get(row.id)));

View File

@@ -6,4 +6,4 @@ export async function handleGetCurrentUserId(dataSource: DataSource): Promise<st
const metaRow = await metaRepo.findOne({ where: { key: 'currentUserId' } });
return metaRow?.value?.trim() || null;
}
}

View File

@@ -3,10 +3,17 @@ import { MessageEntity } from '../../../entities';
import { GetMessageByIdQuery } from '../../types';
import { rowToMessage } from '../../mappers';
import { loadMessageReactionsMap } from '../../relations';
import { getCurrentUserScope } from '../../current-user-scope';
export async function handleGetMessageById(query: GetMessageByIdQuery, dataSource: DataSource) {
const repo = dataSource.getRepository(MessageEntity);
const row = await repo.findOne({ where: { id: query.payload.messageId } });
const currentUserId = await getCurrentUserScope(dataSource);
if (!currentUserId) {
return null;
}
const row = await repo.findOne({ where: { id: query.payload.messageId, ownerUserId: currentUserId } });
if (!row) {
return null;

View File

@@ -3,12 +3,19 @@ import { MessageEntity } from '../../../entities';
import { GetMessagesQuery } from '../../types';
import { rowToMessage } from '../../mappers';
import { loadMessageReactionsMap } from '../../relations';
import { getCurrentUserScope } from '../../current-user-scope';
export async function handleGetMessages(query: GetMessagesQuery, dataSource: DataSource) {
const repo = dataSource.getRepository(MessageEntity);
const { roomId, limit = 100, offset = 0 } = query.payload;
const currentUserId = await getCurrentUserScope(dataSource);
if (!currentUserId) {
return [];
}
const rows = await repo.find({
where: { roomId },
where: { roomId, ownerUserId: currentUserId },
order: { timestamp: 'ASC' },
take: limit,
skip: offset

View File

@@ -3,13 +3,21 @@ import { MessageEntity } from '../../../entities';
import { GetMessagesSinceQuery } from '../../types';
import { rowToMessage } from '../../mappers';
import { loadMessageReactionsMap } from '../../relations';
import { getCurrentUserScope } from '../../current-user-scope';
export async function handleGetMessagesSince(query: GetMessagesSinceQuery, dataSource: DataSource) {
const repo = dataSource.getRepository(MessageEntity);
const { roomId, sinceTimestamp } = query.payload;
const currentUserId = await getCurrentUserScope(dataSource);
if (!currentUserId) {
return [];
}
const rows = await repo.find({
where: {
roomId,
ownerUserId: currentUserId,
timestamp: MoreThan(sinceTimestamp)
},
order: { timestamp: 'ASC' }

View File

@@ -1,12 +1,20 @@
import { DataSource } from 'typeorm';
import { getCurrentUserScope } from '../../current-user-scope';
import { PluginDataEntity } from '../../../entities';
import { GetPluginDataQuery } from '../../types';
export async function handleGetPluginData(query: GetPluginDataQuery, dataSource: DataSource): Promise<unknown> {
const { payload } = query;
const ownerUserId = await getCurrentUserScope(dataSource);
if (!ownerUserId) {
return null;
}
const record = await dataSource.getRepository(PluginDataEntity).findOne({
where: {
key: payload.key,
ownerUserId,
pluginId: payload.pluginId,
scope: payload.scope,
serverId: payload.serverId ?? ''

View File

@@ -3,8 +3,15 @@ import { RoomEntity } from '../../../entities';
import { GetRoomQuery } from '../../types';
import { rowToRoom } from '../../mappers';
import { loadRoomRelationsMap } from '../../relations';
import { getCurrentUserScope, userOwnsRoom } from '../../current-user-scope';
export async function handleGetRoom(query: GetRoomQuery, dataSource: DataSource) {
const currentUserId = await getCurrentUserScope(dataSource);
if (!await userOwnsRoom(dataSource, query.payload.roomId, currentUserId)) {
return null;
}
const repo = dataSource.getRepository(RoomEntity);
const row = await repo.findOne({ where: { id: query.payload.roomId } });