diff --git a/src/backend/src/CoreModule.js b/src/backend/src/CoreModule.js index afd8f82f8..708337851 100644 --- a/src/backend/src/CoreModule.js +++ b/src/backend/src/CoreModule.js @@ -340,9 +340,6 @@ const install = async ({ context, 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 { ReferralCodeService } = require('./services/ReferralCodeService'); services.registerService('referral-code', ReferralCodeService); diff --git a/src/backend/src/services/CommentService.js b/src/backend/src/services/CommentService.js deleted file mode 100644 index 955ded1d6..000000000 --- a/src/backend/src/services/CommentService.js +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright (C) 2024-present Puter Technologies Inc. - * - * This file is part of Puter. - * - * Puter is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -// METADATA // {"ai-commented":{"service":"claude"}} -const APIError = require('../api/APIError'); -const FSNodeParam = require('../api/filesystem/FSNodeParam'); -const { get_user } = require('../helpers'); -const configurable_auth = require('../middleware/configurable_auth'); -const { Endpoint } = require('../util/expressutil'); -const BaseService = require('./BaseService'); -const { DB_WRITE } = require('./database/consts'); - -/** -* CommentService class handles all comment-related functionality in the system. -* Extends BaseService to provide comment creation, retrieval, and attachment capabilities -* for filesystem entries. Manages database operations for user comments and their -* associations with filesystem nodes. Provides REST API endpoints for comment -* operations including posting new comments and listing existing comments. -* @extends BaseService -*/ -class CommentService extends BaseService { - /** - * Static module dependencies used by the CommentService class - * @property {Function} uuidv4 - UUID v4 generator function from the uuid package - */ - 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 }) { - /** - * Installs route handlers for comment-related endpoints - * Sets up POST routes for creating and listing comments on filesystem entries - * - * @param {*} _ Unused parameter - * @param {Object} options Installation options - * @param {Express} options.app Express application instance - * @private - */ - 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 ) { - 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 ) { - 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, - user: { - username: comment.user?.username, - }, - }); - } - - res.json({ - comments: client_safe_comments, - }); - }, - }).attach(app); - - } - - /** - * Creates a new comment with the given text - * - * @param {Object} params - The parameters object - * @param {Object} params.req - Express request object containing user and body data - * @param {Object} params.res - Express response object - * @returns {Promise} The created comment object with id and uid - * @throws {APIError} If text field is missing from request body - */ - 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, - }; - } - - /** - * Attaches a comment to a filesystem entry - * - * @param {Object} params - The parameters object - * @param {Object} params.node - The filesystem node to attach the comment to - * @param {Object} params.comment - The comment object containing id and other details - * @returns {Promise} Resolves when comment is successfully attached - */ - 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')]); - } - - /** - * Retrieves all comments associated with a filesystem entry - * - * @param {Object} params - The parameters object - * @param {Object} params.node - The filesystem node to get comments for - * @returns {Promise} Array of comment objects with user info attached - */ - 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')]); - - for ( const comment of comments ) { - const user_id = comment.user_id; - const user = await get_user({ id: user_id }); - comment.user = user; - } - - return comments; - } -} - -module.exports = { CommentService }; diff --git a/src/backend/src/services/CommentService.test.ts b/src/backend/src/services/CommentService.test.ts deleted file mode 100644 index 5f6a9bec5..000000000 --- a/src/backend/src/services/CommentService.test.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { describe, expect, it, vi } from 'vitest'; -import { createTestKernel } from '../../tools/test.mjs'; -import * as config from '../config'; -import { CommentService } from './CommentService'; - -describe('CommentService', async () => { - config.load_config({ - 'services': { - 'database': { - path: ':memory:', - }, - }, - }); - - const testKernel = await createTestKernel({ - serviceMap: { - 'comment': CommentService, - }, - initLevelString: 'init', - testCore: true, - }); - - const commentService = testKernel.services!.get('comment') as any; - - it('should be instantiated', () => { - expect(commentService).toBeInstanceOf(CommentService); - }); - - it('should have db connection after init', () => { - expect(commentService.db).toBeDefined(); - }); - - it('should have uuidv4 module', () => { - expect(commentService.modules).toBeDefined(); - expect(commentService.modules.uuidv4).toBeDefined(); - expect(typeof commentService.modules.uuidv4).toBe('function'); - }); - - it('should have create_comment_ method', () => { - expect(commentService.create_comment_).toBeDefined(); - expect(typeof commentService.create_comment_).toBe('function'); - }); - - it('should have attach_comment_to_fsentry method', () => { - expect(commentService.attach_comment_to_fsentry).toBeDefined(); - expect(typeof commentService.attach_comment_to_fsentry).toBe('function'); - }); - - it('should have get_comments_for_fsentry method', () => { - expect(commentService.get_comments_for_fsentry).toBeDefined(); - expect(typeof commentService.get_comments_for_fsentry).toBe('function'); - }); - - it('should generate UUID for comments', () => { - const uuid1 = commentService.modules.uuidv4(); - const uuid2 = commentService.modules.uuidv4(); - - expect(uuid1).toBeDefined(); - expect(uuid2).toBeDefined(); - expect(typeof uuid1).toBe('string'); - expect(typeof uuid2).toBe('string'); - expect(uuid1).not.toBe(uuid2); - }); - - it('should validate UUID format', () => { - const uuid = commentService.modules.uuidv4(); - - // UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx - const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; - expect(uuid).toMatch(uuidRegex); - }); - - it('should create comment with text', async () => { - const mockReq = { - body: { text: 'Test comment text' }, - user: { id: 1 }, - }; - const mockRes = {}; - - // Mock database write - const originalWrite = commentService.db.write.bind(commentService.db); - commentService.db.write = vi.fn().mockResolvedValue({ insertId: 123 }); - - try { - const result = await commentService.create_comment_({ - req: mockReq, - res: mockRes - }); - - expect(result).toBeDefined(); - expect(result.id).toBe(123); - expect(result.uid).toBeDefined(); - expect(typeof result.uid).toBe('string'); - - // Verify database write was called with correct parameters - expect(commentService.db.write).toHaveBeenCalledWith( - expect.stringContaining('INSERT INTO `user_comments`'), - expect.arrayContaining([ - expect.any(String), // UUID - 1, // user_id - '{}', // metadata - 'Test comment text', - ]) - ); - } finally { - commentService.db.write = originalWrite; - } - }); - - it('should attach comment to fsentry', async () => { - const mockNode = { - get: vi.fn().mockResolvedValue(456), // mysql-id - }; - const comment = { - id: 123, - uid: 'comment-uuid', - }; - - const originalWrite = commentService.db.write.bind(commentService.db); - commentService.db.write = vi.fn().mockResolvedValue({}); - - try { - await commentService.attach_comment_to_fsentry({ - node: mockNode, - comment: comment, - }); - - expect(commentService.db.write).toHaveBeenCalledWith( - expect.stringContaining('INSERT INTO `user_fsentry_comments`'), - expect.arrayContaining([123, 456]) - ); - - expect(mockNode.get).toHaveBeenCalledWith('mysql-id'); - } finally { - commentService.db.write = originalWrite; - } - }); - - it('should call database to get comments for fsentry', async () => { - const mockNode = { - get: vi.fn().mockResolvedValue(789), - }; - - const originalRead = commentService.db.read.bind(commentService.db); - commentService.db.read = vi.fn().mockResolvedValue([]); - - try { - // Note: This test only verifies the database call structure - // Full integration tests would require proper user service setup - await commentService.get_comments_for_fsentry({ - node: mockNode, - }); - - expect(commentService.db.read).toHaveBeenCalledWith( - expect.stringContaining('SELECT * FROM `user_comments`'), - expect.arrayContaining([789]) - ); - - expect(mockNode.get).toHaveBeenCalledWith('mysql-id'); - } finally { - commentService.db.read = originalRead; - } - }); - - it('should handle multiple comment attachments', async () => { - const mockNode = { - get: vi.fn().mockResolvedValue(999), - }; - - const comments = [ - { id: 1, uid: 'uuid-1' }, - { id: 2, uid: 'uuid-2' }, - { id: 3, uid: 'uuid-3' }, - ]; - - const originalWrite = commentService.db.write.bind(commentService.db); - commentService.db.write = vi.fn().mockResolvedValue({}); - - try { - for (const comment of comments) { - await commentService.attach_comment_to_fsentry({ - node: mockNode, - comment, - }); - } - - expect(commentService.db.write).toHaveBeenCalledTimes(3); - } finally { - commentService.db.write = originalWrite; - } - }); -}); -