From 3575c910f4e26194753041e85f9277b51ca04690 Mon Sep 17 00:00:00 2001 From: Talon Date: Sat, 24 Aug 2024 00:21:54 +0200 Subject: [PATCH] Add logging and fix channel merging and removing --- backend/src/controllers/channel-controller.ts | 7 ++- backend/src/controllers/file-controller.ts | 2 + backend/src/controllers/message-controller.ts | 7 +++ backend/src/controllers/search-controller.ts | 2 + backend/src/jobs/describe-image.ts | 1 + backend/src/services/channel-service.ts | 2 +- backend/src/services/file-service.ts | 2 +- frontend/src/api.ts | 2 +- frontend/src/dialogs/channel-dialog.ts | 29 +++++++++-- frontend/src/dialogs/create-channel.ts | 5 ++ frontend/src/dialogs/merge-dialog.ts | 51 +++++++++++++++++++ frontend/src/dialogs/remove-dialog.ts | 39 ++++++++++++++ frontend/src/dialogs/search.ts | 6 +++ frontend/src/views/main.ts | 15 ++++++ 14 files changed, 163 insertions(+), 7 deletions(-) create mode 100644 frontend/src/dialogs/merge-dialog.ts create mode 100644 frontend/src/dialogs/remove-dialog.ts diff --git a/backend/src/controllers/channel-controller.ts b/backend/src/controllers/channel-controller.ts index 8e70020..89e74a5 100644 --- a/backend/src/controllers/channel-controller.ts +++ b/backend/src/controllers/channel-controller.ts @@ -1,5 +1,6 @@ import type { Request, Response } from "express"; import * as ChannelService from "../services/channel-service"; +import { logger } from "../globals"; export const createChannel = async (req: Request, res: Response) => { const { name } = req.body; @@ -7,7 +8,7 @@ export const createChannel = async (req: Request, res: Response) => { return res.status(400).json({ error: 'Name is required' }); } const chan = await ChannelService.createChannel(name); - + logger.info(`Channel ${name} created`); res.json(chan); } @@ -19,8 +20,10 @@ export const deleteChannel = async (req: Request, res: Response) => { const result = await ChannelService.deleteChannel(channelId); if (result.changes === 0) { + logger.warn(`Channel ${channelId} not found while deleting`); return res.status(404).json({ error: 'Channel not found' }); } + logger.info(`Channel ${channelId} deleted`); res.json({ message: 'Channel deleted successfully' }); } @@ -37,6 +40,7 @@ export const mergeChannel = async (req: Request, res: Response) => { return res.status(400).json({ error: 'Channel ID and targetChannelId are required' }); } const result = await ChannelService.mergeChannel(channelId, targetChannelId); + logger.info(`Channel ${targetChannelId} merged into ${channelId}`); res.json({ message: 'Channels merged successfully' }); } @@ -52,6 +56,7 @@ export const updateChannel = async (req: Request, res: Response) => { if (result.changes === 0) { return res.status(404).json({ error: 'Channel not found' }); } + logger.info(`Channel ${channelId} updated as ${name}`); res.json({ message: 'Channel updated successfully' }); } \ No newline at end of file diff --git a/backend/src/controllers/file-controller.ts b/backend/src/controllers/file-controller.ts index 3efb838..a67c530 100644 --- a/backend/src/controllers/file-controller.ts +++ b/backend/src/controllers/file-controller.ts @@ -1,5 +1,6 @@ import type { Request, Response } from "express"; import * as FileService from "../services/file-service"; +import { logger } from "../globals"; export const uploadFile = async (req: Request, res: Response) => { const { channelId, messageId } = req.params; @@ -16,6 +17,7 @@ export const uploadFile = async (req: Request, res: Response) => { } const result = await FileService.uploadFile(channelId, messageId, filePath, fileType!, fileSize!, originalName!); + logger.info(`File ${originalName} uploaded to message ${messageId} as ${filePath}`); res.json({ id: result.lastInsertRowid, channelId, messageId, filePath, fileType }); } diff --git a/backend/src/controllers/message-controller.ts b/backend/src/controllers/message-controller.ts index b97bcd1..5f984d8 100644 --- a/backend/src/controllers/message-controller.ts +++ b/backend/src/controllers/message-controller.ts @@ -1,5 +1,6 @@ import type { Request, Response } from "express"; import * as MessageService from "../services/message-service"; +import { logger } from "../globals"; export const createMessage = async (req: Request, res: Response) => { const { content } = req.body; @@ -8,6 +9,7 @@ export const createMessage = async (req: Request, res: Response) => { return res.status(400).json({ error: 'Content and channel ID are required' }); } const messageId = await MessageService.createMessage(channelId, content); + logger.info(`Message ${messageId} created in channel ${channelId}`); res.json({ id: messageId, channelId, content, createdAt: new Date().toISOString() }); }; @@ -19,6 +21,10 @@ export const updateMessage = async (req: Request, res: Response) => { return res.status(400).json({ error: 'Content and message ID are required ' }); } const result = await MessageService.updateMessage(messageId, content); + if (result.changes === 0) { + return res.status(404).json({ error: 'Message not found' }); + } + logger.info(`Message ${messageId} updated`); res.json({ id: messageId, content }); } @@ -32,6 +38,7 @@ export const deleteMessage = async (req: Request, res: Response) => { if (result.changes === 0) { return res.status(404).json({ error: 'Message not found' }); } + logger.info(`Message ${messageId} deleted`); res.json({ message: 'Message deleted successfully' }); } diff --git a/backend/src/controllers/search-controller.ts b/backend/src/controllers/search-controller.ts index e970af8..4da531d 100644 --- a/backend/src/controllers/search-controller.ts +++ b/backend/src/controllers/search-controller.ts @@ -1,5 +1,6 @@ import type { Request, Response } from "express"; import * as SearchService from "../services/search-service"; +import { logger } from "../globals"; export const search = async (req: Request, res: Response) => { const { query, channelId } = req.query; @@ -7,5 +8,6 @@ export const search = async (req: Request, res: Response) => { return res.status(400).json({ error: 'Query is required' }); } const results = await SearchService.search(query as string, channelId as string); + logger.info(`Searched for ${query}`); res.json({ results }); } \ No newline at end of file diff --git a/backend/src/jobs/describe-image.ts b/backend/src/jobs/describe-image.ts index d67b776..ba64a1d 100644 --- a/backend/src/jobs/describe-image.ts +++ b/backend/src/jobs/describe-image.ts @@ -1,3 +1,4 @@ +import type { Message } from "../../types"; import { events, logger } from "../globals" import { describeImage } from "../services/image-description"; import { getMessage, updateMessage } from "../services/message-service"; diff --git a/backend/src/services/channel-service.ts b/backend/src/services/channel-service.ts index 6a6130c..121fba3 100644 --- a/backend/src/services/channel-service.ts +++ b/backend/src/services/channel-service.ts @@ -10,7 +10,7 @@ export const createChannel = async (name: string) => { export const deleteChannel = async (id: string) => { const query = db.query(`DELETE FROM channels WHERE id = ($channelId)`); - const result = db.run(id); + const result = query.run({$channelId: id}); // No need to manually delete messages and files as they are set to cascade on delete in the schema events .emit('channel-deleted', id); return result; diff --git a/backend/src/services/file-service.ts b/backend/src/services/file-service.ts index 85e7475..9178d10 100644 --- a/backend/src/services/file-service.ts +++ b/backend/src/services/file-service.ts @@ -3,7 +3,7 @@ import { events } from "../globals"; export const uploadFile = async (channelId: string, messageId: string, filePath: string, fileType: string, fileSize: number, originalName: string) => { const query = db.query(`INSERT INTO files (channelId, filePath, fileType, fileSize, originalName) VALUES ($channelId, $filePath, $fileType, $fileSize, $originalName)`); - const result = query.run({ $channelId: channelId, $filePath: filePath, $fileType: fileType, $fileSize: fileSize, $originalName: originalName } as any); + const result = query.run({ $channelId: channelId, $filePath: filePath, $fileType: fileType, $fileSize: fileSize, $originalName: originalName }); const fileId = result.lastInsertRowid; diff --git a/frontend/src/api.ts b/frontend/src/api.ts index a05aa2e..1d20a3b 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -85,7 +85,7 @@ export const API = { }, async mergeChannels(channelId: string, targetChannelId: string) { - await API.request("POST", "merge-channels", { channelId, targetChannelId }); + await API.request("PUT", `channels/${channelId}/merge`, { targetChannelId }); }, async search(query: string, channelId?: string) { diff --git a/frontend/src/dialogs/channel-dialog.ts b/frontend/src/dialogs/channel-dialog.ts index 7cdf1e3..8da489e 100644 --- a/frontend/src/dialogs/channel-dialog.ts +++ b/frontend/src/dialogs/channel-dialog.ts @@ -3,8 +3,10 @@ import { showToast } from "../speech"; import { state } from "../state"; import { Button, TextInput } from "../ui"; import { Dialog } from "../ui/dialog"; +import { MergeDialog } from "./merge-dialog"; +import { RemoveDialog } from "./remove-dialog"; -export class ChannelDialog extends Dialog { +export class ChannelDialog extends Dialog { private channel: IChannel; private nameField: TextInput; private makeDefault: Button; @@ -26,12 +28,15 @@ export class ChannelDialog extends Dialog { this.mergeButton = new Button("Merge"); this.mergeButton.setPosition(40, 70, 10, 10); this.mergeButton.onClick(() => { - showToast("Merge not implemented."); + this.mergeChannel(); }); + if (state.channelList.channels.length === 1) { + this.mergeButton.setDisabled(true); + } this.deleteButton = new Button("Delete"); this.deleteButton.setPosition(60, 70, 10, 10); this.deleteButton.onClick(() => { - showToast("Delete not implemented."); + this.deleteChannel(); }); this.add(this.nameField); this.add(this.makeDefault); @@ -42,4 +47,22 @@ export class ChannelDialog extends Dialog { return this.channel; }); } + + private async mergeChannel() { + const res = await new MergeDialog().open(); + if (res) { + this.choose(this.channel); + } else { + return; + } + } + + private async deleteChannel() { + const res = await new RemoveDialog(this.channel.id.toString()).open(); + if (res) { + this.choose(null); + } else { + return; + } + } } \ No newline at end of file diff --git a/frontend/src/dialogs/create-channel.ts b/frontend/src/dialogs/create-channel.ts index 50f4a2e..3e4b3f1 100644 --- a/frontend/src/dialogs/create-channel.ts +++ b/frontend/src/dialogs/create-channel.ts @@ -13,5 +13,10 @@ export class CreateChannelDialog extends Dialog { this.setOkAction(() => { return this.nameField.getValue(); }); + this.nameField.onKeyDown((key) => { + if (key === "Enter") { + this.choose(this.nameField.getValue()); + } + }); } } \ No newline at end of file diff --git a/frontend/src/dialogs/merge-dialog.ts b/frontend/src/dialogs/merge-dialog.ts new file mode 100644 index 0000000..7b04f3b --- /dev/null +++ b/frontend/src/dialogs/merge-dialog.ts @@ -0,0 +1,51 @@ +import { Button } from "../ui"; +import { Dialog } from "../ui/dialog"; +import { API } from "../api"; +import { Dropdown } from "../ui/dropdown"; +import { state } from "../state"; +import { showToast } from "../speech"; + +export class MergeDialog extends Dialog { + private channelList: Dropdown; + private mergeButton: Button; + private cancelButton: Button; + + public constructor() { + super("Merge channels", false); + this.channelList = new Dropdown("Target channel", []); + this.channelList.setPosition(10, 10, 80, 20); + this.mergeButton = new Button("Merge"); + this.mergeButton.setPosition(30, 30, 40, 30); + this.mergeButton.onClick(() => this.merge()); + this.cancelButton = new Button("Cancel"); + this.cancelButton.setPosition(30, 70, 40, 30); + this.cancelButton.onClick(() => this.cancel()); + this.add(this.channelList); + this.add(this.mergeButton); + this.add(this.cancelButton); + this.setupChannelList(); + } + + private setupChannelList() { + this.channelList.clearOptions(); + state.channelList.getChannels().forEach((channel) => { + if (channel.id !== state.currentChannel!.id) this.channelList.addOption(channel.id.toString(), channel.name); + }) + } + private async merge() { + const currentChannel = state.currentChannel; + const target = this.channelList.getSelectedValue(); + const targetChannel = state.getChannelById(parseInt(target)); + console.log(currentChannel, targetChannel); + if (!targetChannel || !currentChannel) this.cancel(); + try { + const res = await API.mergeChannels(currentChannel!.id.toString(), target); + currentChannel!.messages = []; + showToast("Channels were merged."); + this.choose(true); + } catch (e) { + showToast("Failed to merge channels: " + e); + this.choose(false); + } + } +} \ No newline at end of file diff --git a/frontend/src/dialogs/remove-dialog.ts b/frontend/src/dialogs/remove-dialog.ts new file mode 100644 index 0000000..bf3f061 --- /dev/null +++ b/frontend/src/dialogs/remove-dialog.ts @@ -0,0 +1,39 @@ +import { Button } from "../ui"; +import { Dialog } from "../ui/dialog"; +import { Text } from "../ui"; +import { API } from "../api"; +import { state } from "../state"; +import { showToast } from "../speech"; + +export class RemoveDialog extends Dialog { + private content: Text; + private confirmButton: Button; + private cancelButton: Button; + + public constructor(channelId: string) { + super("Remove channel", false); + this.content = new Text("Are you sure you want to remove this channel?"); + this.confirmButton = new Button("Remove"); + this.confirmButton.setPosition(30, 30, 40, 30); + this.confirmButton.onClick(() => this.remove()); + this.cancelButton = new Button("Cancel"); + this.cancelButton.setPosition(30, 70, 40, 30); + this.cancelButton.onClick(() => this.cancel()); + this.add(this.content); + this.add(this.confirmButton); + this.add(this.cancelButton); + } + + private async remove() { + try { + const res = await API.deleteChannel(state.currentChannel!.id.toString()); + state.removeChannel(state.currentChannel!); + showToast("Channel was removed."); + this.choose(true); + } catch (e) { + showToast("Failed to remove channel: " + e); + + this.choose(false); + } + } +} \ No newline at end of file diff --git a/frontend/src/dialogs/search.ts b/frontend/src/dialogs/search.ts index 786251e..5fa7ad7 100644 --- a/frontend/src/dialogs/search.ts +++ b/frontend/src/dialogs/search.ts @@ -13,6 +13,11 @@ export class SearchDialog extends Dialog<{channelId: number, messageId: number}> super("Search for message", false); this.searchField = new TextInput("Search query"); this.searchField.setPosition(5, 5, 80, 20); + this.searchField.onKeyDown((key) => { + if (key === "Enter") { + this.searchButton.click(); + } + }); this.searchButton = new Button("Search"); this.searchButton.setPosition(85, 5, 10, 20); this.searchButton.onClick(async () => { @@ -38,5 +43,6 @@ export class SearchDialog extends Dialog<{channelId: number, messageId: number}> itm.onClick(() => this.choose({ messageId: message.id, channelId: message.channelId! })); this.resultsList.add(itm); }); + this.resultsList.focus(); } } \ No newline at end of file diff --git a/frontend/src/views/main.ts b/frontend/src/views/main.ts index e05a3b4..701ed91 100644 --- a/frontend/src/views/main.ts +++ b/frontend/src/views/main.ts @@ -140,6 +140,21 @@ export class MainView extends View { } const d = new ChannelDialog(state.currentChannel!); d.open().then((chan) => { + if (!chan) { + state.removeChannel(state.currentChannel!); + state.currentChannel = null; + this.updateChannelList(); + state.save(); + if (state.channelList.channels.length > 0) { + return this.switchChannel(state.channelList.channels[0].id.toString()); + } else { + return this.createNewChannel(); + } + } + if (chan.messages.length < 1) { + this.renderInitialMessageList(true); + this.syncMessages(); + } state.save(); this.updateChannelList(); });