workspace.service.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. // api/workspaces/workspace.service.js
  2. import * as fs from "fs/promises";
  3. import * as path from "path";
  4. import mysql from "mysql2/promise";
  5. import mongoose from "mongoose";
  6. import BaseService from "../../services/base.service.js";
  7. import Workspace from "./workspace.model.js";
  8. class WorkspaceService extends BaseService {
  9. getModel() {
  10. return Workspace;
  11. }
  12. async create(data, userId, accountId) {
  13. data.userId = userId;
  14. data.accountId = accountId;
  15. // Generate a new ObjectId for each nested item that doesn't have one
  16. if (data.contacts && data.contacts.length > 0) {
  17. data.contacts = data.contacts.map(contact => {
  18. if (!contact.contactId) contact.contactId = new mongoose.Types.ObjectId();
  19. return contact;
  20. });
  21. }
  22. if (data.projects && data.projects.length > 0) {
  23. data.projects = data.projects.map(project => {
  24. if (!project.projectId) project.projectId = new mongoose.Types.ObjectId();
  25. return project;
  26. });
  27. }
  28. if (data.meetings && data.meetings.length > 0) {
  29. data.meetings = data.meetings.map(meeting => {
  30. if (!meeting.meetingId) meeting.meetingId = new mongoose.Types.ObjectId();
  31. return meeting;
  32. });
  33. }
  34. if (data.notes && data.notes.length > 0) {
  35. data.notes = data.notes.map(note => {
  36. if (!note.noteId) note.noteId = new mongoose.Types.ObjectId();
  37. return note;
  38. });
  39. }
  40. if (data.tasks && data.tasks.length > 0) {
  41. data.tasks = data.tasks.map(task => {
  42. if (!task.taskId) task.taskId = new mongoose.Types.ObjectId();
  43. return task;
  44. });
  45. }
  46. const workspace = new Workspace(data);
  47. await workspace.save();
  48. return workspace.toObject();
  49. }
  50. async update(id, userId, accountId, data) {
  51. // Generate ObjectIds for any new nested items that don't have IDs
  52. this._ensureNestedIds(data);
  53. // Update the workspace
  54. return this.getModel().findOneAndUpdate(
  55. {
  56. _id: id,
  57. $or: [
  58. { userId: userId },
  59. { ownerId: userId },
  60. { "members.userId": userId }
  61. ],
  62. accountId: accountId
  63. },
  64. data,
  65. { new: true }
  66. );
  67. }
  68. // Helper to ensure all nested items have proper IDs
  69. _ensureNestedIds(data) {
  70. const ensureIds = (items, idField) => {
  71. if (items && items.length > 0) {
  72. return items.map(item => {
  73. if (!item[idField]) item[idField] = new mongoose.Types.ObjectId();
  74. return item;
  75. });
  76. }
  77. return items;
  78. };
  79. if (data.contacts) data.contacts = ensureIds(data.contacts, 'contactId');
  80. if (data.projects) data.projects = ensureIds(data.projects, 'projectId');
  81. if (data.meetings) data.meetings = ensureIds(data.meetings, 'meetingId');
  82. if (data.notes) data.notes = ensureIds(data.notes, 'noteId');
  83. if (data.tasks) data.tasks = ensureIds(data.tasks, 'taskId');
  84. }
  85. async delete(id, userId, accountId) {
  86. return this.getModel().findOneAndDelete({
  87. _id: id,
  88. $or: [
  89. { userId: userId },
  90. { ownerId: userId },
  91. { "members.userId": userId, "members.role": "admin" }
  92. ],
  93. accountId: accountId
  94. });
  95. }
  96. async findByIdAndUser(id, userId, accountId) {
  97. const workspace = await this.getModel().findOne({
  98. _id: id,
  99. $or: [
  100. { userId: userId },
  101. { ownerId: userId },
  102. { "members.userId": userId },
  103. { "teams._id": { $in: userId.teams?.map(team => team._id) || [] } }
  104. ],
  105. accountId: accountId
  106. }).lean();
  107. return workspace;
  108. }
  109. // All existing methods for file handling remain the same
  110. async getWorkspaceFilePath(name) {
  111. const workspacesDir = process.env.WORKSPACES_FILES_DIRECTORY || path.join(process.cwd(), 'workspace-files');
  112. return path.join(workspacesDir, name);
  113. }
  114. async workspaceFileExists(filePath) {
  115. try {
  116. await fs.access(filePath, fs.constants.F_OK);
  117. return true;
  118. } catch (error) {
  119. return false;
  120. }
  121. }
  122. isValidWorkspaceFileName(name) {
  123. const validNameRegex = /^[a-zA-Z0-9_-]+\.(pdf|txt|csv|json|xml|zip|tar|gz|xlsx|docx|png|jpg|jpeg|svg)$/;
  124. return validNameRegex.test(name) && !name.includes('..');
  125. }
  126. getContentType(filename) {
  127. const extension = path.extname(filename).toLowerCase();
  128. const contentTypes = {
  129. '.pdf': 'application/pdf',
  130. '.txt': 'text/plain',
  131. '.csv': 'text/csv',
  132. '.json': 'application/json',
  133. '.xml': 'application/xml',
  134. '.zip': 'application/zip',
  135. '.tar': 'application/x-tar',
  136. '.gz': 'application/gzip',
  137. '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  138. '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  139. '.png': 'image/png',
  140. '.jpg': 'image/jpeg',
  141. '.jpeg': 'image/jpeg',
  142. '.svg': 'image/svg+xml'
  143. };
  144. return contentTypes[extension] || 'application/octet-stream';
  145. }
  146. // All existing MySQL database methods remain unchanged
  147. async createDatabaseConnection(workspace) {
  148. if (!workspace.mysqlHost || !workspace.mysqlUser || !workspace.mysqlDatabase) {
  149. throw new Error("Missing required database connection information");
  150. }
  151. try {
  152. const connection = await mysql.createConnection({
  153. host: workspace.mysqlHost,
  154. port: workspace.mysqlPort || 3306,
  155. user: workspace.mysqlUser,
  156. password: workspace.mysqlPassword || '',
  157. database: workspace.mysqlDatabase,
  158. connectTimeout: 10000
  159. });
  160. return connection;
  161. } catch (error) {
  162. throw new Error(`Database connection error: ${error.message}`);
  163. }
  164. }
  165. async getTableData(workspaceId, userId, accountId, limit = 100) {
  166. const workspace = await this.findByIdAndUser(workspaceId, userId, accountId);
  167. if (!workspace) {
  168. throw new Error("Workspace not found");
  169. }
  170. if (!workspace.mysqlTable) {
  171. throw new Error("No table specified in workspace");
  172. }
  173. const connection = await this.createDatabaseConnection(workspace);
  174. try {
  175. const [rows, fields] = await connection.execute(
  176. `SELECT * FROM \`${workspace.mysqlTable}\` LIMIT ?`,
  177. [limit]
  178. );
  179. await this.update(
  180. workspace._id,
  181. userId,
  182. accountId,
  183. { lastAccessed: new Date() }
  184. );
  185. return {
  186. rows,
  187. columns: fields.map(field => ({
  188. name: field.name,
  189. type: field.type,
  190. length: field.length
  191. }))
  192. };
  193. } catch (error) {
  194. throw new Error(`Database query error: ${error.message}`);
  195. } finally {
  196. await connection.end();
  197. }
  198. }
  199. async listTables(workspaceId, userId, accountId) {
  200. const workspace = await this.findByIdAndUser(workspaceId, userId, accountId);
  201. if (!workspace) {
  202. throw new Error("Workspace not found");
  203. }
  204. const connection = await this.createDatabaseConnection(workspace);
  205. try {
  206. const [rows] = await connection.execute(
  207. `SHOW TABLES FROM \`${workspace.mysqlDatabase}\``
  208. );
  209. await this.update(
  210. workspace._id,
  211. userId,
  212. accountId,
  213. { lastAccessed: new Date() }
  214. );
  215. const tableNames = rows.map(row => Object.values(row)[0]);
  216. return tableNames;
  217. } catch (error) {
  218. throw new Error(`Error listing tables: ${error.message}`);
  219. } finally {
  220. await connection.end();
  221. }
  222. }
  223. async testDatabaseConnection(workspaceId, userId, accountId) {
  224. const workspace = await this.findByIdAndUser(workspaceId, userId, accountId);
  225. if (!workspace) {
  226. throw new Error("Workspace not found");
  227. }
  228. const connection = await this.createDatabaseConnection(workspace);
  229. try {
  230. const [result] = await connection.execute('SELECT 1 AS connection_test');
  231. const [serverInfo] = await connection.execute('SELECT VERSION() AS version');
  232. return {
  233. connected: true,
  234. serverVersion: serverInfo[0].version
  235. };
  236. } catch (error) {
  237. throw new Error(`Connection test failed: ${error.message}`);
  238. } finally {
  239. await connection.end();
  240. }
  241. }
  242. }
  243. export default new WorkspaceService();