diff --git a/.env.example b/.env.example index 9494ba0..95ed38d 100644 --- a/.env.example +++ b/.env.example @@ -15,3 +15,6 @@ JWT_REFRESH_TOKEN_SECRET='' GCLOUD_PROJECT_ID="your-gcloud-project-id" GCLOUD_BUCKET_NAME="your-bucket-name" + +GENERATIVE_AI_SERVICE_URL="" +SUGGESTION_AI_SERVICE_URL="" diff --git a/.github/workflows/google-cloudrun-docker.yml b/.github/workflows/google-cloudrun-docker.yml index ec67e0e..80e40ce 100644 --- a/.github/workflows/google-cloudrun-docker.yml +++ b/.github/workflows/google-cloudrun-docker.yml @@ -88,6 +88,8 @@ jobs: echo "JWT_REFRESH_TOKEN_SECRET=${{ secrets.JWT_REFRESH_TOKEN_SECRET }}" >> .env.production echo "GCLOUD_PROJECT_ID=${{ secrets.GCLOUD_PROJECT_ID }}" >> .env.production echo "GCLOUD_BUCKET_NAME=${{ secrets.GCLOUD_BUCKET_NAME }}" >> .env.production + echo "GENERATIVE_AI_SERVICE_URL=${{ secrets.GENERATIVE_AI_SERVICE_URL }}" >> .env.production + echo "SUGGESTION_AI_SERVICE_URL=${{ secrets.SUGGESTION_AI_SERVICE_URL }}" >> .env.production elif [[ "${GITHUB_REF##*/}" == "staging" ]]; then echo "NODE_ENV=staging" >> $GITHUB_ENV echo "DOCKER_TAG=staging-${{ github.sha }}" >> $GITHUB_ENV @@ -99,6 +101,8 @@ jobs: echo "JWT_REFRESH_TOKEN_SECRET=${{ secrets.JWT_REFRESH_TOKEN_SECRET }}" >> .env.staging echo "GCLOUD_PROJECT_ID=${{ secrets.GCLOUD_PROJECT_ID }}" >> .env.staging echo "GCLOUD_BUCKET_NAME=${{ secrets.GCLOUD_BUCKET_NAME }}" >> .env.staging + echo "GENERATIVE_AI_SERVICE_URL=${{ secrets.GENERATIVE_AI_SERVICE_URL }}" >> .env.staging + echo "SUGGESTION_AI_SERVICE_URL=${{ secrets.SUGGESTION_AI_SERVICE_URL }}" >> .env.staging elif [[ "${GITHUB_REF##*/}" == "feat/setup-node" ]]; then echo "NODE_ENV=dev" >> $GITHUB_ENV echo "DOCKER_TAG=dev-${{ github.sha }}" >> $GITHUB_ENV @@ -110,6 +114,8 @@ jobs: echo "JWT_REFRESH_TOKEN_SECRET=${{ secrets.JWT_REFRESH_TOKEN_SECRET }}" >> .env.dev echo "GCLOUD_PROJECT_ID=${{ secrets.GCLOUD_PROJECT_ID }}" >> .env.dev echo "GCLOUD_BUCKET_NAME=${{ secrets.GCLOUD_BUCKET_NAME }}" >> .env.dev + echo "GENERATIVE_AI_SERVICE_URL=${{ secrets.GENERATIVE_AI_SERVICE_URL }}" >> .env.dev + echo "SUGGESTION_AI_SERVICE_URL=${{ secrets.SUGGESTION_AI_SERVICE_URL }}" >> .env.dev else echo "NODE_ENV=dev" >> $GITHUB_ENV echo "DOCKER_TAG=dev-${{ github.sha }}" >> $GITHUB_ENV @@ -121,6 +127,8 @@ jobs: echo "JWT_REFRESH_TOKEN_SECRET=${{ secrets.JWT_REFRESH_TOKEN_SECRET }}" >> .env.dev echo "GCLOUD_PROJECT_ID=${{ secrets.GCLOUD_PROJECT_ID }}" >> .env.dev echo "GCLOUD_BUCKET_NAME=${{ secrets.GCLOUD_BUCKET_NAME }}" >> .env.dev + echo "GENERATIVE_AI_SERVICE_URL=${{ secrets.GENERATIVE_AI_SERVICE_URL }}" >> .env.dev + echo "SUGGESTION_AI_SERVICE_URL=${{ secrets.SUGGESTION_AI_SERVICE_URL }}" >> .env.dev fi - name: Clear Docker cache diff --git a/openapi.json b/openapi.json index 3ebd1b5..5380d92 100644 --- a/openapi.json +++ b/openapi.json @@ -25,6 +25,10 @@ ] } } + }, + { + "description": "Local Finboost Back-End URL", + "url": "http://localhost:3000/api/v1" } ], "tags": [ @@ -2457,135 +2461,641 @@ } } } - } - }, - "components": { - "securitySchemes": { - "bearerAuth": { - "type": "http", - "scheme": "bearer", - "bearerFormat": "JWT" - }, - "cookieAuth": { - "type": "apiKey", - "in": "cookie", - "name": "refreshToken" - } }, - "parameters": { - "UserId": { - "name": "userId", - "in": "path", - "description": "Id of the user to retrieve", - "required": true, - "schema": { - "type": "string" - }, - "examples": { - "User 1": { - "description": "Sample user id for user 1", - "value": "d969ee14-3362-46cd-ace9-af4d7b131733" + "/chat-rooms": { + "get": { + "tags": ["Chat Room"], + "summary": "Get all chat rooms", + "description": "Retrieve all chat rooms", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "Get chat rooms by user id", + "required": false, + "schema": { + "type": "string", + "nullable": true + }, + "examples": { + "User 1": { + "description": "Get chat rooms by user id 1", + "value": "d969ee14-3362-46cd-ace9-af4d7b131733" + } + } + } + ], + "responses": { + "200": { + "description": "Successfully retrieved all chat rooms", + "content": { + "application/json": { + "schema": { + "type": "object", + "$ref": "#/components/schemas/SuccessGetAllChatRooms" + }, + "examples": { + "All chat rooms": { + "description": "Successfully get all chat rooms", + "value": { + "status": "success", + "message": "Get all chat rooms", + "data": { + "chatRooms": [ + { + "id": "d969ee14-3362-46cd-ace9-af4d7b131733", + "type": "AI", + "userId": "d969ee14-3362-46cd-ace9-af4d7b131733" + }, + { + "id": "d969ee14-3362-46cd-ace9-af4d7b131733", + "type": "Expert", + "userId": "d969ee14-3362-46cd-ace9-af4d7b131733" + } + ] + } + } + } + } + } + } }, - "User 2": { - "description": "Sample user id for user 2", - "value": "f5e05c30-99ff-4693-a0aa-bc1e869dd3e5" + "500": { + "$ref": "#/components/responses/InternalServerError" } - } - }, - "RoleId": { - "name": "roleId", - "in": "path", - "description": "Id of the role to retrieve", - "required": true, - "schema": { - "type": "string" }, - "examples": { - "Role 1": { - "description": "Sample role id for role 1", - "value": "fdc3462c-96fb-4797-8341-cd2b1cce88bb" + "security": [ + { + "bearerAuth": [] } - } + ] }, - "WorkId": { - "name": "workId", - "in": "path", - "description": "Id of the work to retrieve", - "required": true, - "schema": { - "type": "string" + "post": { + "tags": ["Chat Room"], + "summary": "Create a new chat room", + "description": "Add a new chat room to the app", + "requestBody": { + "description": "Chat room object that needs to be added", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InsertAllFieldChatRoom" + }, + "examples": { + "New chat room": { + "description": "Example of a new chat room", + "value": { + "type": "AI", + "userId": "d969ee14-3362-46cd-ace9-af4d7b131733" + } + } + } + } + } }, - "examples": { - "Work 1": { - "description": "Sample work id for work 1", - "value": "6ce7e34b-93f9-406c-aa1d-09fac038b138" + "responses": { + "201": { + "description": "Successfully created a new chat room", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessSignupOrRegisUser" + }, + "examples": { + "Chat Room 1": { + "description": "Successfully created a new chat room", + "value": { + "status": "success", + "message": "Chat room created successfully", + "data": { + "id": "d969ee14-3362-46cd-ace9-af4d7b131733" + } + } + } + } + } + } + }, + "400": { + "description": "Failed add new chat room because validation error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailValidationSignupOrRegister" + }, + "examples": { + "name empty": { + "description": "Fail add new chat room because name field empty", + "value": { + "status": "fail", + "message": "Validation error", + "errors": [ + { + "field": "name", + "message": "name is required" + } + ] + } + } + } + } + } + }, + "500": { + "$ref": "#/components/responses/InternalServerError" } - } - }, - "EducationId": { - "name": "educationId", - "in": "path", - "description": "Id of the education to retrieve", - "required": true, - "schema": { - "type": "string" }, - "examples": { - "Education 1": { - "description": "Sample education id for education 1", - "value": "8a69ef80-8d1d-4832-9c6a-fba9d9f16230" + "security": [ + { + "bearerAuth": [] } - } + ] } }, - "schemas": { - "Success": { - "type": "object", - "properties": { - "status": { - "type": "string" - }, - "message": { - "type": "string" + "/chat-rooms/{chatRoomId}": { + "get": { + "tags": ["Chat Room"], + "summary": "Get a chat room by id", + "description": "Get specific chat room by id", + "parameters": [ + { + "$ref": "#/components/parameters/ChatRoomId" } - } - }, - "Fail": { - "type": "object", - "properties": { - "status": { - "type": "string" + ], + "responses": { + "200": { + "description": "Successfully retrieved the chat room", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessGetChatRoom" + }, + "examples": { + "Chat Room": { + "description": "Successfully retrieved the chat room", + "value": { + "status": "success", + "message": "Get chat room data by id", + "data": { + "chatRoom": { + "id": "d969ee14-3362-46cd-ace9-af4d7b131733", + "type": "AI", + "user": { + "id": "d969ee14-3362-46cd-ace9-af4d7b131733", + "name": "User 1" + } + } + } + } + } + } + } + } }, - "message": { - "type": "string" + "404": { + "$ref": "#/components/responses/ErrorChatRoomNotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" } - } + }, + "security": [ + { + "bearerAuth": [] + } + ] }, - "User": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "fullName": { - "type": "string" - }, - "email": { - "type": "string" - }, - "gender": { - "type": "string" - }, - "age": { - "type": "integer" - }, - "password": { - "type": "string" - }, - "role": { - "type": "string" - }, - "refreshToken": { + "put": { + "tags": ["Chat Room"], + "summary": "Update a chat room by id", + "description": "Update specific chat room by id", + "parameters": [ + { + "$ref": "#/components/parameters/ChatRoomId" + } + ], + "requestBody": { + "description": "Chat room object that needs to be updated", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateAllFieldChatRoom" + }, + "examples": { + "Update chat room": { + "description": "Example of a chat room update", + "value": { + "type": "Expert", + "userId": "d969ee14-3362-46cd-ace9-af4d7b131733", + "expertId": "f5e05c30-99ff-4693-a0aa-bc1e869dd3e5" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Successfully update the chat room", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessSignupOrRegisUser" + }, + "examples": { + "Chat Room updated": { + "description": "Successfully updated the chat room", + "value": { + "status": "success", + "message": "Chat room updated successfully", + "data": { + "id": "d969ee14-3362-46cd-ace9-af4d7b131733" + } + } + } + } + } + } + }, + "400": { + "description": "Failed to update chat room due to validation error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailValidationSignupOrRegister" + }, + "examples": { + "name empty": { + "description": "Failed to update chat room because name field is empty", + "value": { + "status": "fail", + "message": "Validation error", + "errors": [ + { + "field": "name", + "message": "name is required" + } + ] + } + } + } + } + } + }, + "404": { + "$ref": "#/components/responses/ErrorChatRoomNotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "delete": { + "tags": ["Chat Room"], + "summary": "Delete a chat room by id", + "description": "Delete a specific chat room by its ID", + "parameters": [ + { + "$ref": "#/components/parameters/ChatRoomId" + } + ], + "responses": { + "200": { + "description": "Successfully deleted the chat room", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessSignupOrRegisUser" + }, + "examples": { + "Chat Room deleted": { + "description": "Successfully deleted the chat room", + "value": { + "status": "success", + "message": "Chat room deleted successfully", + "data": { + "id": "d969ee14-3362-46cd-ace9-af4d7b131733" + } + } + } + } + } + } + }, + "404": { + "$ref": "#/components/responses/ErrorChatRoomNotFound" + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/chats/ai/predict":{ + "post": { + "tags": ["Chat Room"], + "summary": "Predict chat message", + "description": "Predict chat message", + "requestBody": { + "description": "Chat message object that needs to be predicted", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InsertAllFieldChatMessage" + }, + "examples": { + "New chat message": { + "description": "Example of a new chat message", + "value": { + "question": "Halo" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Successfully predicted the chat message", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessPredictChatMessage" + }, + "examples": { + "Chat Message": { + "description": "Successfully predicted the chat message", + "value": { + "status": "success", + "message": "Predict chat message", + "data": { + "answer": "Halo" + } + } + } + } + } + } + }, + "400": { + "description": "Failed predict chat message because validation error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailValidationPredictChatMessage" + }, + "examples": { + "message empty": { + "description": "Fail predict chat message because message field empty", + "value": { + "status": "fail", + "message": "Validation error", + "errors": [ + { + "field": "message", + "message": "message is required" + } + ] + } + } + } + } + } + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/chats/ai/suggestions" : { + "post": { + "tags": ["Chat Room"], + "summary": "Get all chat suggestions", + "description": "Retrieve all chat suggestions", + "requestBody": { + "description": "Chat suggestion object that needs to be retrieved", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InsertAllFieldChatSuggestion" + }, + "examples": { + "New chat suggestion": { + "description": "Example of a new chat suggestion", + "value": { + "user_input": "Halo", + "total_questions": 4 + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Successfully retrieved all chat suggestions", + "content": { + "application/json": { + "schema": { + "type": "object", + "$ref": "#/components/schemas/SuccessGetAllChatSuggestions" + }, + "examples": { + "All chat suggestions": { + "description": "Successfully get all chat suggestions", + "value": { + "status": "success", + "message": "Get all chat suggestions", + "data": { + "suggested_questions": [ + "Apa saja jenis reksadana yang tersedia di Indonesia?", + "Apa itu reksadana offshore dan bagaimana cara berinvestasi di dalamnya?", + "Apa itu reksadana terproteksi dan bagaimana cara kerjanya?", + "Bagaimana cara menghindari penipuan dalam investasi reksadana?" + ], + "probability" : 0.9, + "top_category": "Reksadana" + } + } + } + } + } + } + }, + "500": { + "$ref": "#/components/responses/InternalServerError" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + } + }, + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + }, + "cookieAuth": { + "type": "apiKey", + "in": "cookie", + "name": "refreshToken" + } + }, + "parameters": { + "UserId": { + "name": "userId", + "in": "path", + "description": "Id of the user to retrieve", + "required": true, + "schema": { + "type": "string" + }, + "examples": { + "User 1": { + "description": "Sample user id for user 1", + "value": "d969ee14-3362-46cd-ace9-af4d7b131733" + }, + "User 2": { + "description": "Sample user id for user 2", + "value": "f5e05c30-99ff-4693-a0aa-bc1e869dd3e5" + } + } + }, + "RoleId": { + "name": "roleId", + "in": "path", + "description": "Id of the role to retrieve", + "required": true, + "schema": { + "type": "string" + }, + "examples": { + "Role 1": { + "description": "Sample role id for role 1", + "value": "fdc3462c-96fb-4797-8341-cd2b1cce88bb" + } + } + }, + "WorkId": { + "name": "workId", + "in": "path", + "description": "Id of the work to retrieve", + "required": true, + "schema": { + "type": "string" + }, + "examples": { + "Work 1": { + "description": "Sample work id for work 1", + "value": "6ce7e34b-93f9-406c-aa1d-09fac038b138" + } + } + }, + "EducationId": { + "name": "educationId", + "in": "path", + "description": "Id of the education to retrieve", + "required": true, + "schema": { + "type": "string" + }, + "examples": { + "Education 1": { + "description": "Sample education id for education 1", + "value": "8a69ef80-8d1d-4832-9c6a-fba9d9f16230" + } + } + }, + "ChatRoomId": { + "name": "chatRoomId", + "in": "path", + "description": "Room chat ID", + "required": true, + "schema": { + "type": "string" + }, + "examples": { + "Room Chat 1": { + "description": "Sample room chat id for room chat 1", + "value": "d969ee14-3362-46cd-ace9-af4d7b131733" + } + } + } + }, + "schemas": { + "Success": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "Fail": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "fullName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "gender": { + "type": "string" + }, + "age": { + "type": "integer" + }, + "password": { + "type": "string" + }, + "role": { + "type": "string" + }, + "refreshToken": { "type": "string" }, "createdAt": { @@ -3146,6 +3656,173 @@ "type": "string" } } + }, + "GetChatRoom": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["Expert", "AI"] + }, + "user": { + "$ref": "#/components/schemas/User" + }, + "expert": { + "$ref": "#/components/schemas/User" + }, + "createdAt": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "SuccessGetChatRoom": { + "allOf": [ + { + "$ref": "#/components/schemas/Success" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "chatRoom": { + "$ref": "#/components/schemas/GetChatRoom" + } + } + } + } + } + ] + }, + "SuccessGetAllChatRooms": { + "allOf": [ + { + "$ref": "#/components/schemas/Success" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "chatRooms": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GetChatRoom" + } + } + } + } + } + } + ] + }, + "InsertAllFieldChatRoom": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["Expert", "AI"] + }, + "userId": { + "type": "string" + }, + "expertId": { + "type": "string", + "nullable": true + } + } + }, + "UpdateAllFieldChatRoom": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["Expert", "AI"] + }, + "userId": { + "type": "string" + }, + "expertId": { + "type": "string", + "nullable": true + } + } + }, + "InsertAllFieldChatMessage": { + "type": "object", + "properties": { + "question": { + "type": "string" + } + } + }, + "InsertAllFieldChatSuggestion": { + "type": "object", + "properties": { + "user_input": { + "type": "string" + }, + "total_questions": { + "type": "integer" + } + } + }, + "SuccessPredictChatAi": { + "allOf": [ + { + "$ref": "#/components/schemas/Success" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "answer": { + "type": "string" + } + } + } + } + } + ] + }, + "SuccessSuggestChatAi": { + "allOf": [ + { + "$ref": "#/components/schemas/Success" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "suggested_questions": { + "type": "array", + "items": { + "type": "string" + } + }, + "probability": { + "type": "number" + }, + "top_category": { + "type": "string" + } + } + } + } + } + ] } }, "responses": { @@ -3288,6 +3965,26 @@ } } } + }, + "ErrorChatRoomNotFound": { + "description": "Chat room not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "example": "fail" + }, + "message": { + "type": "string", + "example": "Chat room not found" + } + } + } + } + } } } } diff --git a/prisma/migrations/20240608071749_create_chat_rooms_table/migration.sql b/prisma/migrations/20240608071749_create_chat_rooms_table/migration.sql new file mode 100644 index 0000000..f821885 --- /dev/null +++ b/prisma/migrations/20240608071749_create_chat_rooms_table/migration.sql @@ -0,0 +1,17 @@ +-- CreateTable +CREATE TABLE `chat_rooms` ( + `id` VARCHAR(191) NOT NULL, + `type` ENUM('Expert', 'AI') NOT NULL, + `userId` VARCHAR(191) NOT NULL, + `expertId` VARCHAR(191) NULL, + `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updatedAt` DATETIME(3) NOT NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- AddForeignKey +ALTER TABLE `chat_rooms` ADD CONSTRAINT `chat_rooms_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `users`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `chat_rooms` ADD CONSTRAINT `chat_rooms_expertId_fkey` FOREIGN KEY (`expertId`) REFERENCES `users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 4f59c44..7bb2333 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -31,6 +31,9 @@ model User { profile Profile? + UserChatRoom ChatRoom[] @relation("UserChatRoom") + ExpertChatRoom ChatRoom[] @relation("ExpertChatRoom") + @@map("users") } @@ -87,6 +90,22 @@ model Education { @@map("educations") } +model ChatRoom { + id String @id @default(uuid()) + type ChatRoomType + + user User @relation("UserChatRoom", fields: [userId], references: [id]) + userId String + + expert User? @relation("ExpertChatRoom", fields: [expertId], references: [id]) + expertId String? + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@map("chat_rooms") +} + enum UserGender { Laki_laki @map("Laki-laki") Perempuan @@ -97,3 +116,8 @@ enum ProfileMarital { Menikah Cerai } + +enum ChatRoomType { + Expert + AI +} diff --git a/src/controller/chat-ai.controller.js b/src/controller/chat-ai.controller.js new file mode 100644 index 0000000..8f5246f --- /dev/null +++ b/src/controller/chat-ai.controller.js @@ -0,0 +1,44 @@ +import { handleServerError } from "../exceptions/server.exception.js"; +import { handleZodError } from "../exceptions/zod.exception.js"; +import { GenerativeAiInput, SugesstionQuestionInput } from "../schema/chat-ai.schema.js"; +import { getGenerativeAi, getSugesstionQuestion } from "../service/chat-ai.service.js"; + +export const getGenerativeAiHandler = async (req, res) => { + try { + const validateData = GenerativeAiInput.parse(req.body); + + const data = await getGenerativeAi(validateData); + + res.status(200).send({ + status: "success", + message: "Successfully get generative AI", + data, + }); + } catch (error) { + try { + handleZodError(error, res); + } catch (err) { + handleServerError(err, res); + } + } +}; + +export const getSugesstionQuestionHandler = async (req, res) => { + try { + const validateData = SugesstionQuestionInput.parse(req.body); + + const data = await getSugesstionQuestion(validateData); + + res.status(200).send({ + status: "success", + message: "Successfully get sugesstion question", + data, + }); + } catch (error) { + try { + handleZodError(error, res); + } catch (err) { + handleServerError(err, res); + } + } +} \ No newline at end of file diff --git a/src/controller/chat-rooms.controller.js b/src/controller/chat-rooms.controller.js new file mode 100644 index 0000000..7feb0ca --- /dev/null +++ b/src/controller/chat-rooms.controller.js @@ -0,0 +1,131 @@ +import { handleServerError } from "../exceptions/server.exception.js"; +import { handleZodError } from "../exceptions/zod.exception.js"; +import { NotFoundError } from "../exceptions/client.exception.js"; +import { + editChatRoomById, + getAllChatRooms, + getAllChatRoomsByUserId, + getChatRoomById, + insertChatRoom, + removeChatRoomById, +} from "../service/chat-rooms.service.js"; +import { + EditChatRoomSchema, + InsertChatRoomSchema, +} from "../schema/chat-rooms.schema.js"; + +export const getAllChatRoomsHandler = async (req, res) => { + try { + const userId = req.query.userId; + + let chatRooms; + if (userId) { + chatRooms = await getAllChatRoomsByUserId(userId); + } else { + chatRooms = await getAllChatRooms(); + } + + res.status(200).send({ + status: "success", + message: "Get all chat room profile user", + data: { + chatRooms, + }, + }); + } catch (error) { + console.log(error); + handleServerError(error, res); + } +}; + +export const insertChatRoomHandler = async (req, res) => { + try { + const validateData = InsertChatRoomSchema.parse(req.body); + + const newChatRoomData = await insertChatRoom(validateData); + + res.status(201).send({ + status: "success", + message: "Chat room created successfully", + data: { + id: newChatRoomData.id, + }, + }); + } catch (error) { + try { + handleZodError(error, res); + } catch (err) { + handleServerError(err, res); + } + } +}; + +export const getChatRoomByIdHandler = async (req, res) => { + try { + const chatRoomId = req.params.chatRoomId; + const chatRoom = await getChatRoomById(chatRoomId, res); + + res.status(200).send({ + status: "success", + message: "Get chat room data by id", + data: { + chatRoom, + }, + }); + } catch (error) { + if (error instanceof NotFoundError) { + return; + } + handleServerError(error, res); + } +}; + +export const editChatRoomByIdHandler = async (req, res) => { + try { + const chatRoomId = req.params.chatRoomId; + const validateData = EditChatRoomSchema.parse(req.body); + + const newChatRoomData = await editChatRoomById( + chatRoomId, + validateData, + res + ); + + res.status(200).send({ + status: "success", + message: "Chat room updated successfully", + data: { + id: newChatRoomData.id, + }, + }); + } catch (error) { + try { + handleZodError(error, res); + } catch (err) { + if (err instanceof NotFoundError) { + return; + } + handleServerError(err, res); + } + } +}; + +export const removeChatRoomByIdHandler = async (req, res) => { + try { + const chatRoomId = req.params.chatRoomId; + await removeChatRoomById(chatRoomId, res); + + res.status(200).send({ + status: "success", + message: "Chat room deleted successfully", + data: { + id: chatRoomId, + }, + }); + } catch (error) { + if (error instanceof NotFoundError) { + return; + } + handleServerError(error, res); + } +}; \ No newline at end of file diff --git a/src/repository/chat-rooms.repository.js b/src/repository/chat-rooms.repository.js new file mode 100644 index 0000000..0228dd8 --- /dev/null +++ b/src/repository/chat-rooms.repository.js @@ -0,0 +1,136 @@ +import prisma from "../../db/prisma.js"; + +export const findChatRooms = async () => { + const chatRooms = await prisma.chatRoom.findMany({ + select: { + id: true, + type: true, + userId: true, + user: { + select: { + id: true, + fullName: true, + email: true, + createdAt: true, + updatedAt: true, + }, + }, + expertId: true, + expert: { + select: { + id: true, + fullName: true, + email: true, + createdAt: true, + updatedAt: true, + }, + }, + createdAt: true, + updatedAt: true, + } + }); + + return chatRooms; +}; + +export const findChatRoomsByUserId = async (userId) => { + const chatRooms = await prisma.chatRoom.findMany({ + where: { + userId, + }, + select: { + id: true, + type: true, + userId: true, + user: { + select: { + id: true, + fullName: true, + email: true, + createdAt: true, + updatedAt: true, + }, + }, + expertId: true, + expert: { + select: { + id: true, + fullName: true, + email: true, + createdAt: true, + updatedAt: true, + }, + }, + createdAt: true, + updatedAt: true, + }, + }); + + return chatRooms; +}; + +export const createChatRoom = async (newChatRoomData) => { + const newChatRoom = await prisma.chatRoom.create({ + data: newChatRoomData, + }); + + return newChatRoom; +}; + +export const findChatRoomById = async (chatRoomId) => { + const chatRoom = await prisma.chatRoom.findUnique({ + where: { + id: chatRoomId, + }, + select: { + id: true, + type: true, + userId: true, + user: { + select: { + id: true, + fullName: true, + email: true, + createdAt: true, + updatedAt: true, + }, + }, + expertId: true, + expert: { + select: { + id: true, + fullName: true, + email: true, + createdAt: true, + updatedAt: true, + }, + }, + createdAt: true, + updatedAt: true, + }, + }); + + return chatRoom; +}; + +export const updateChatRoomById = async (chatRoomId, newChatRoomData) => { + console.log(chatRoomId, newChatRoomData); + const chatRoom = await prisma.chatRoom.update({ + where: { + id: chatRoomId, + }, + data: newChatRoomData, + }); + + return chatRoom; +}; + +export const deleteChatRoomById = async (chatRoomId) => { + const chatRoom = await prisma.chatRoom.delete({ + where: { + id: chatRoomId, + }, + }); + + return chatRoom; +}; \ No newline at end of file diff --git a/src/routes/api.js b/src/routes/api.js index e72d2f9..98609c0 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -5,6 +5,8 @@ import tokensRouter from "./router/tokens.router.js"; import usersRouter from "./router/users.router.js"; import worksRouter from "./router/works.router.js"; import educationsRouter from "./router/educations.router.js"; +import chatRoomsRouter from "./router/chat-rooms.router.js"; +import chatAiRouter from "./router/chat-ai.router.js"; const app = express(); @@ -14,5 +16,7 @@ app.use(tokensRouter); app.use(usersRouter); app.use(worksRouter); app.use(educationsRouter); +app.use(chatRoomsRouter) +app.use(chatAiRouter) export default app; diff --git a/src/routes/router/chat-ai.router.js b/src/routes/router/chat-ai.router.js new file mode 100644 index 0000000..2556db7 --- /dev/null +++ b/src/routes/router/chat-ai.router.js @@ -0,0 +1,19 @@ +import express from "express"; +import { verifyAccessTokenHandler } from "../../middlewares/tokens.middleware.js"; +import { getGenerativeAiHandler, getSugesstionQuestionHandler } from "../../controller/chat-ai.controller.js"; + +const router = express.Router(); + +router.post( + "/chats/ai/predict", + verifyAccessTokenHandler, + getGenerativeAiHandler +); + +router.post( + "/chats/ai/suggestions", + verifyAccessTokenHandler, + getSugesstionQuestionHandler +); + +export default router; \ No newline at end of file diff --git a/src/routes/router/chat-rooms.router.js b/src/routes/router/chat-rooms.router.js new file mode 100644 index 0000000..35b1f13 --- /dev/null +++ b/src/routes/router/chat-rooms.router.js @@ -0,0 +1,42 @@ +import express from "express"; +import { + editChatRoomByIdHandler, + getAllChatRoomsHandler, + getChatRoomByIdHandler, + insertChatRoomHandler, + removeChatRoomByIdHandler, +} from "../../controller/chat-rooms.controller.js"; +import { verifyAccessTokenHandler } from "../../middlewares/tokens.middleware.js"; + +const router = express.Router(); + +router.get( + "/chat-rooms", + verifyAccessTokenHandler, + getAllChatRoomsHandler +); +router.post( + "/chat-rooms", + verifyAccessTokenHandler, + insertChatRoomHandler +); + +router.get( + "/chat-rooms/:chatRoomId", + verifyAccessTokenHandler, + getChatRoomByIdHandler +); + +router.put( + "/chat-rooms/:chatRoomId", + verifyAccessTokenHandler, + editChatRoomByIdHandler +); + +router.delete( + "/chat-rooms/:chatRoomId", + verifyAccessTokenHandler, + removeChatRoomByIdHandler +); + +export default router; \ No newline at end of file diff --git a/src/schema/chat-ai.schema.js b/src/schema/chat-ai.schema.js new file mode 100644 index 0000000..a0e7fdd --- /dev/null +++ b/src/schema/chat-ai.schema.js @@ -0,0 +1,10 @@ +import { z } from "zod"; + +export const GenerativeAiInput = z.object({ + question: z.string(), +}); + +export const SugesstionQuestionInput = z.object({ + user_input: z.string(), + total_questions: z.number() +}); \ No newline at end of file diff --git a/src/schema/chat-rooms.schema.js b/src/schema/chat-rooms.schema.js new file mode 100644 index 0000000..56b4614 --- /dev/null +++ b/src/schema/chat-rooms.schema.js @@ -0,0 +1,13 @@ +import { z } from "zod"; + +export const InsertChatRoomSchema = z.object({ + type: z.enum(['Expert', 'AI']), + userId: z.string(), + expertId: z.string().optional(), +}); + +export const EditChatRoomSchema = z.object({ + type: z.enum(['Expert', 'AI']), + userId: z.string(), + expertId: z.string().optional(), +}); \ No newline at end of file diff --git a/src/service/chat-ai.service.js b/src/service/chat-ai.service.js new file mode 100644 index 0000000..e26b79c --- /dev/null +++ b/src/service/chat-ai.service.js @@ -0,0 +1,35 @@ +const GENERATIVE_AI_SERVICE_URL = process.env.GENERATIVE_AI_SERVICE_URL || ""; +const SUGGESTION_AI_SERVICE_URL = process.env.SUGGESTION_AI_SERVICE_URL || ""; + +export const getGenerativeAi = async ({question}) => { + const response = await fetch(GENERATIVE_AI_SERVICE_URL , { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + question, + }), + }); + + const data = await response.json(); + + return data; +} + +export const getSugesstionQuestion = async ({user_input, total_questions}) => { + const response = await fetch(SUGGESTION_AI_SERVICE_URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + user_input, + total_questions, + }), + }); + + const data = await response.json(); + + return data; +} diff --git a/src/service/chat-rooms.service.js b/src/service/chat-rooms.service.js new file mode 100644 index 0000000..d377ccc --- /dev/null +++ b/src/service/chat-rooms.service.js @@ -0,0 +1,52 @@ +import { + NotFoundError, + handleNotFoundError, +} from "../exceptions/client.exception.js"; +import { + createChatRoom, + deleteChatRoomById, + findChatRoomById, + findChatRooms, + findChatRoomsByUserId, + updateChatRoomById, +} from "../repository/chat-rooms.repository.js"; + +export const getAllChatRooms = async () => { + const chatRooms = await findChatRooms(); + + return chatRooms; +}; + +export const getAllChatRoomsByUserId = async (userId) => { + const chatRooms = await findChatRoomsByUserId(userId); + + return chatRooms; +}; + +export const insertChatRoom = async (newChatRoomData) => { + const newChatRoom = await createChatRoom(newChatRoomData); + + return newChatRoom; +}; + +export const getChatRoomById = async (chatRoomId, res) => { + const chatRoom = await findChatRoomById(chatRoomId); + + if (!chatRoom) { + handleNotFoundError("Chat Room not found", res); + } + + return chatRoom; +}; + +export const editChatRoomById = async (chatRoomId, newChatRoomData, res) => { + await getChatRoomById(chatRoomId, res); + const chatRoom = await updateChatRoomById(chatRoomId, newChatRoomData); + + return chatRoom; +}; + +export const removeChatRoomById = async (chatRoomId, res) => { + await getChatRoomById(chatRoomId, res); + await deleteChatRoomById(chatRoomId); +}; \ No newline at end of file