From e71392d83f1af04e90ab302f406a1fcc223a9a03 Mon Sep 17 00:00:00 2001
From: Timothy Carambat <rambat1010@gmail.com>
Date: Mon, 21 Oct 2024 13:09:55 -0700
Subject: [PATCH] Feature/thread creation slug name (#2512)

* thread creation additional params name and slug, with api

* typo fix

* Rebuild openai Swagger docs
Handle validations for fields to prevent invalid field inputs for .new
Enforce sluggification of `slug` to prevent breaking of URL structs

---------

Co-authored-by: abrakadobr <abrakadobr@gmail.com>
---
 .vscode/settings.json                         |  1 +
 server/endpoints/api/workspaceThread/index.js | 15 ++++----
 server/models/workspaceThread.js              | 34 +++++++++++++++++--
 server/swagger/openapi.json                   |  6 ++--
 4 files changed, 45 insertions(+), 11 deletions(-)

diff --git a/.vscode/settings.json b/.vscode/settings.json
index 1409c1073..8405c5281 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -50,6 +50,7 @@
     "textgenwebui",
     "togetherai",
     "Unembed",
+    "uuidv",
     "vectordbs",
     "Weaviate",
     "Zilliz"
diff --git a/server/endpoints/api/workspaceThread/index.js b/server/endpoints/api/workspaceThread/index.js
index e7f53698a..0d6eb59c6 100644
--- a/server/endpoints/api/workspaceThread/index.js
+++ b/server/endpoints/api/workspaceThread/index.js
@@ -31,12 +31,14 @@ function apiWorkspaceThreadEndpoints(app) {
           type: 'string'
       }
       #swagger.requestBody = {
-        description: 'Optional userId associated with the thread',
+        description: 'Optional userId associated with the thread, thread slug and thread name',
         required: false,
         content: {
           "application/json": {
             example: {
-              userId: 1
+              userId: 1,
+              name: 'Name',
+              slug: 'thread-slug'
             }
           }
         }
@@ -67,9 +69,9 @@ function apiWorkspaceThreadEndpoints(app) {
       }
       */
       try {
-        const { slug } = request.params;
-        let { userId = null } = reqBody(request);
-        const workspace = await Workspace.get({ slug });
+        const wslug = request.params.slug;
+        let { userId = null, name = null, slug = null } = reqBody(request);
+        const workspace = await Workspace.get({ slug: wslug });
 
         if (!workspace) {
           response.sendStatus(400).end();
@@ -83,7 +85,8 @@ function apiWorkspaceThreadEndpoints(app) {
 
         const { thread, message } = await WorkspaceThread.new(
           workspace,
-          userId ? Number(userId) : null
+          userId ? Number(userId) : null,
+          { name, slug }
         );
 
         await Telemetry.sendTelemetry("workspace_thread_created", {
diff --git a/server/models/workspaceThread.js b/server/models/workspaceThread.js
index 32e9f89b6..1ac6040cd 100644
--- a/server/models/workspaceThread.js
+++ b/server/models/workspaceThread.js
@@ -1,16 +1,44 @@
 const prisma = require("../utils/prisma");
+const slugifyModule = require("slugify");
 const { v4: uuidv4 } = require("uuid");
 
 const WorkspaceThread = {
   defaultName: "Thread",
   writable: ["name"],
 
-  new: async function (workspace, userId = null) {
+  /**
+   * The default Slugify module requires some additional mapping to prevent downstream issues
+   * if the user is able to define a slug externally. We have to block non-escapable URL chars
+   * so that is the slug is rendered it doesn't break the URL or UI when visited.
+   * @param  {...any} args - slugify args for npm package.
+   * @returns {string}
+   */
+  slugify: function (...args) {
+    slugifyModule.extend({
+      "+": " plus ",
+      "!": " bang ",
+      "@": " at ",
+      "*": " splat ",
+      ".": " dot ",
+      ":": "",
+      "~": "",
+      "(": "",
+      ")": "",
+      "'": "",
+      '"': "",
+      "|": "",
+    });
+    return slugifyModule(...args);
+  },
+
+  new: async function (workspace, userId = null, data = {}) {
     try {
       const thread = await prisma.workspace_threads.create({
         data: {
-          name: this.defaultName,
-          slug: uuidv4(),
+          name: data.name ? String(data.name) : this.defaultName,
+          slug: data.slug
+            ? this.slugify(data.slug, { lowercase: true })
+            : uuidv4(),
           user_id: userId ? Number(userId) : null,
           workspace_id: workspace.id,
         },
diff --git a/server/swagger/openapi.json b/server/swagger/openapi.json
index 07955bc35..b12fbf535 100644
--- a/server/swagger/openapi.json
+++ b/server/swagger/openapi.json
@@ -2391,12 +2391,14 @@
           }
         },
         "requestBody": {
-          "description": "Optional userId associated with the thread",
+          "description": "Optional userId associated with the thread, thread slug and thread name",
           "required": false,
           "content": {
             "application/json": {
               "example": {
-                "userId": 1
+                "userId": 1,
+                "name": "Name",
+                "slug": "thread-slug"
               }
             }
           }
-- 
GitLab