diff --git a/src/backend/src/CoreModule.js b/src/backend/src/CoreModule.js index 3a1f30bf..0394601f 100644 --- a/src/backend/src/CoreModule.js +++ b/src/backend/src/CoreModule.js @@ -344,6 +344,9 @@ const install = async ({ services, app, useapi, modapi }) => { const { DriverUsagePolicyService } = require('./services/drivers/DriverUsagePolicyService'); services.registerService('driver-usage-policy', DriverUsagePolicyService); + + const { CommentService } = require('./services/CommentService'); + services.registerService('comment', CommentService); } const install_legacy = async ({ services }) => { diff --git a/src/backend/src/services/CommentService.js b/src/backend/src/services/CommentService.js new file mode 100644 index 00000000..f71b1a33 --- /dev/null +++ b/src/backend/src/services/CommentService.js @@ -0,0 +1,156 @@ +const APIError = require("../api/APIError"); +const FSNodeParam = require("../api/filesystem/FSNodeParam"); +const configurable_auth = require("../middleware/configurable_auth"); +const { Endpoint } = require("../util/expressutil"); +const BaseService = require("./BaseService"); +const { DB_WRITE } = require("./database/consts"); + +class CommentService extends BaseService { + static MODULES = { + uuidv4: require('uuid').v4, + } + _init () { + const svc_database = this.services.get('database'); + this.db = svc_database.get(DB_WRITE, 'notification'); + } + ['__on_install.routes'] (_, { app }) { + const r_comment = (() => { + const require = this.require; + const express = require('express'); + return express.Router() + })(); + + app.use('/comment', r_comment); + + Endpoint({ + route: '/comment', + methods: ['POST'], + mw: [configurable_auth()], + handler: async (req, res) => { + const comment = await this.create_comment_({ req, res }); + + if ( ! req.body.on ) { + throw APIError.create('field_missing', null, { key: 'on' }); + } + + const on_ = req.body.on; + + if ( on_.startsWith('fs:') ) { + const node = await (new FSNodeParam('path')).consolidate({ + req, + getParam: () => on_.slice(3), + }); + + if ( req.body.version ) { + // this.attach_comment_to_fsentry_version({ + // node, comment, version, + // }); + res.status(400).send('not implemented yet'); + return; + } else { + this.attach_comment_to_fsentry({ + node, comment, + }); + } + } + + res.json({ + uid: comment.uid, + }); + } + }).attach(app); + + Endpoint({ + route: '/comment/list', + methods: ['POST'], + mw: [configurable_auth()], + handler: async (req, res) => { + if ( ! req.body.on ) { + throw APIError.create('field_missing', null, { key: 'on' }); + } + + const on_ = req.body.on; + + let comments; + + if ( on_.startsWith('fs:') ) { + const node = await (new FSNodeParam('path')).consolidate({ + req, + getParam: () => on_.slice(3), + }); + + if ( req.body.version ) { + // this.attach_comment_to_fsentry_version({ + // node, comment, version, + // }); + res.status(400).send('not implemented yet'); + return; + } else { + comments = await this.get_comments_for_fsentry({ + node, + }); + } + } + + const client_safe_comments = []; + for ( const comment of comments ) { + client_safe_comments.push({ + uid: comment.uid, + text: comment.text, + created: comment.created_at, + }); + } + + res.json({ + comments: client_safe_comments, + }); + } + }).attach(app); + + } + + async create_comment_ ({ req, res }) { + if ( ! req.body.text ) { + throw APIError.create('field_missing', null, { key: 'text' }); + } + + const text = req.body.text; + + const uuid = this.modules.uuidv4(); + + const result = await this.db.write( + 'INSERT INTO `user_comments` ' + + '(`uid`, `user_id`, `metadata`, `text`) ' + + 'VALUES (?, ?, ?, ?)', + [uuid, req.user.id, '{}', text], + ); + + return { + id: result.insertId, + uid: uuid, + }; + } + + async attach_comment_to_fsentry ({ node, comment }) { + await this.db.write( + 'INSERT INTO `user_fsentry_comments` ' + + '(`user_comment_id`, `fsentry_id`) ' + + 'VALUES (?, ?)', + [comment.id, await node.get('mysql-id')], + ); + } + + async get_comments_for_fsentry ({ node }) { + const comments = await this.db.read( + 'SELECT * FROM `user_comments` ' + + 'JOIN `user_fsentry_comments` ' + + 'ON `user_comments`.`id` = `user_fsentry_comments`.`user_comment_id` ' + + 'WHERE `fsentry_id` = ?', + [await node.get('mysql-id')], + ); + + return comments; + } +} + +module.exports = { CommentService }; diff --git a/src/backend/src/services/database/SqliteDatabaseAccessService.js b/src/backend/src/services/database/SqliteDatabaseAccessService.js index 6ba6ae10..2ea7649c 100644 --- a/src/backend/src/services/database/SqliteDatabaseAccessService.js +++ b/src/backend/src/services/database/SqliteDatabaseAccessService.js @@ -132,6 +132,9 @@ class SqliteDatabaseAccessService extends BaseDatabaseAccessService { [26, [ '0029_emulator_priv.sql', ]], + [27, [ + '0030_comments.sql', + ]], ]; // Database upgrade logic diff --git a/src/backend/src/services/database/sqlite_setup/0030_comments.sql b/src/backend/src/services/database/sqlite_setup/0030_comments.sql new file mode 100644 index 00000000..238572b2 --- /dev/null +++ b/src/backend/src/services/database/sqlite_setup/0030_comments.sql @@ -0,0 +1,43 @@ +CREATE TABLE `user_comments` ( + `id` INTEGER PRIMARY KEY, + `uid` TEXT NOT NULL UNIQUE, + `user_id` INTEGER NOT NULL, + `metadata` JSON DEFAULT NULL, + `text` TEXT NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY("user_id") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +CREATE INDEX `idx_user_comments_uid` ON `user_comments` (`uid`); + +CREATE TABLE `user_fsentry_comments` ( + `id` INTEGER PRIMARY KEY, + `user_comment_id` INTEGER NOT NULL, + `fsentry_id` INTEGER NOT NULL, + FOREIGN KEY("user_comment_id") REFERENCES "user_comments" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY("fsentry_id") REFERENCES "fsentries" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +CREATE TABLE `user_fsentry_version_comments` ( + `id` INTEGER PRIMARY KEY, + `user_comment_id` INTEGER NOT NULL, + `fsentry_version_id` INTEGER NOT NULL, + FOREIGN KEY("user_comment_id") REFERENCES "user_comments" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY("fsentry_version_id") REFERENCES "fsentry_versions" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +CREATE TABLE `user_group_comments` ( + `id` INTEGER PRIMARY KEY, + `user_comment_id` INTEGER NOT NULL, + `group_id` INTEGER NOT NULL, + FOREIGN KEY("user_comment_id") REFERENCES "user_comments" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY("group_id") REFERENCES "group" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +CREATE TABLE `user_user_comments` ( + `id` INTEGER PRIMARY KEY, + `user_comment_id` INTEGER NOT NULL, + `user_id` INTEGER NOT NULL, + FOREIGN KEY("user_comment_id") REFERENCES "user_comments" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY("user_id") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE +);