From 0d3299de9a85a3c11f910fd8bdbce1f98fa535df Mon Sep 17 00:00:00 2001 From: jonmatthis Date: Sat, 6 Jan 2024 12:26:04 -0500 Subject: [PATCH 1/6] autogen users resource --- src/main.ts | 2 +- src/main/main.controller.spec.ts | 4 +-- src/main/main.controller.ts | 2 +- src/shared/ai/langchain/langchain.service.ts | 1 + src/shared/database/database.module.ts | 6 ++-- .../database/users/dto/create-user.dto.ts | 1 + .../database/users/dto/update-user.dto.ts | 4 +++ .../database/users/entities/user.entity.ts | 1 + .../database/users/users.controller.spec.ts | 20 +++++++++++ src/shared/database/users/users.controller.ts | 34 +++++++++++++++++++ src/shared/database/users/users.module.ts | 9 +++++ .../database/users/users.service.spec.ts | 18 ++++++++++ src/shared/database/users/users.service.ts | 26 ++++++++++++++ 13 files changed, 121 insertions(+), 7 deletions(-) create mode 100644 src/shared/database/users/dto/create-user.dto.ts create mode 100644 src/shared/database/users/dto/update-user.dto.ts create mode 100644 src/shared/database/users/entities/user.entity.ts create mode 100644 src/shared/database/users/users.controller.spec.ts create mode 100644 src/shared/database/users/users.controller.ts create mode 100644 src/shared/database/users/users.module.ts create mode 100644 src/shared/database/users/users.service.spec.ts create mode 100644 src/shared/database/users/users.service.ts diff --git a/src/main.ts b/src/main.ts index 267a677..ad86cc9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -15,7 +15,7 @@ async function bootstrap() { .addBearerAuth() .build(); const document = SwaggerModule.createDocument(app, options); - SwaggerModule.setup('docs', app, document); + SwaggerModule.setup('api', app, document); await app.listen(3000); console.log(`Application is running on: ${await app.getUrl()}`); diff --git a/src/main/main.controller.spec.ts b/src/main/main.controller.spec.ts index 4dd0f33..d442a59 100644 --- a/src/main/main.controller.spec.ts +++ b/src/main/main.controller.spec.ts @@ -14,8 +14,8 @@ describe('AppController', () => { }); describe('root', () => { - it('should return "Hello World!"', () => { - expect(appController.sendHello()).toBe('Hello World!'); + it('should return "hello wow"', () => { + expect(appController.sendHello()).toBe('hello wow'); }); }); }); diff --git a/src/main/main.controller.ts b/src/main/main.controller.ts index f182899..3b23212 100644 --- a/src/main/main.controller.ts +++ b/src/main/main.controller.ts @@ -6,7 +6,7 @@ import { ApiTags } from '@nestjs/swagger'; export class MainController { @Get() sendHello() { - return 'hello'; + return 'hello wow'; } @Get('/health') diff --git a/src/shared/ai/langchain/langchain.service.ts b/src/shared/ai/langchain/langchain.service.ts index f5fbfe7..eb328ac 100644 --- a/src/shared/ai/langchain/langchain.service.ts +++ b/src/shared/ai/langchain/langchain.service.ts @@ -37,6 +37,7 @@ export class LangchainService { prompt: prompt, memory: memory, }); + } /** * Creates a chain that may be invoked later. diff --git a/src/shared/database/database.module.ts b/src/shared/database/database.module.ts index a2acd1c..8b08d39 100644 --- a/src/shared/database/database.module.ts +++ b/src/shared/database/database.module.ts @@ -2,13 +2,13 @@ import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; import { CatsModule } from './cats/cats.module'; import { DatabaseConnectionService } from './database-connection.service'; +import { UsersModule } from './users/users.module'; @Module({ imports: [ - MongooseModule.forRoot('mongodb://localhost:27017/test', { - dbName: 'testtest', - }), + MongooseModule.forRoot('mongodb://localhost:27017/test'), CatsModule, + UsersModule, ], providers: [DatabaseConnectionService], exports: [DatabaseConnectionService], diff --git a/src/shared/database/users/dto/create-user.dto.ts b/src/shared/database/users/dto/create-user.dto.ts new file mode 100644 index 0000000..0311be1 --- /dev/null +++ b/src/shared/database/users/dto/create-user.dto.ts @@ -0,0 +1 @@ +export class CreateUserDto {} diff --git a/src/shared/database/users/dto/update-user.dto.ts b/src/shared/database/users/dto/update-user.dto.ts new file mode 100644 index 0000000..dfd37fb --- /dev/null +++ b/src/shared/database/users/dto/update-user.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateUserDto } from './create-user.dto'; + +export class UpdateUserDto extends PartialType(CreateUserDto) {} diff --git a/src/shared/database/users/entities/user.entity.ts b/src/shared/database/users/entities/user.entity.ts new file mode 100644 index 0000000..4f82c14 --- /dev/null +++ b/src/shared/database/users/entities/user.entity.ts @@ -0,0 +1 @@ +export class User {} diff --git a/src/shared/database/users/users.controller.spec.ts b/src/shared/database/users/users.controller.spec.ts new file mode 100644 index 0000000..a76d310 --- /dev/null +++ b/src/shared/database/users/users.controller.spec.ts @@ -0,0 +1,20 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { UsersController } from './users.controller'; +import { UsersService } from './users.service'; + +describe('UsersController', () => { + let controller: UsersController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [UsersController], + providers: [UsersService], + }).compile(); + + controller = module.get(UsersController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/shared/database/users/users.controller.ts b/src/shared/database/users/users.controller.ts new file mode 100644 index 0000000..3eca7eb --- /dev/null +++ b/src/shared/database/users/users.controller.ts @@ -0,0 +1,34 @@ +import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; +import { UsersService } from './users.service'; +import { CreateUserDto } from './dto/create-user.dto'; +import { UpdateUserDto } from './dto/update-user.dto'; + +@Controller('users') +export class UsersController { + constructor(private readonly usersService: UsersService) {} + + @Post() + create(@Body() createUserDto: CreateUserDto) { + return this.usersService.create(createUserDto); + } + + @Get() + findAll() { + return this.usersService.findAll(); + } + + @Get(':id') + findOne(@Param('id') id: string) { + return this.usersService.findOne(+id); + } + + @Patch(':id') + update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) { + return this.usersService.update(+id, updateUserDto); + } + + @Delete(':id') + remove(@Param('id') id: string) { + return this.usersService.remove(+id); + } +} diff --git a/src/shared/database/users/users.module.ts b/src/shared/database/users/users.module.ts new file mode 100644 index 0000000..ecca17a --- /dev/null +++ b/src/shared/database/users/users.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { UsersService } from './users.service'; +import { UsersController } from './users.controller'; + +@Module({ + controllers: [UsersController], + providers: [UsersService], +}) +export class UsersModule {} diff --git a/src/shared/database/users/users.service.spec.ts b/src/shared/database/users/users.service.spec.ts new file mode 100644 index 0000000..62815ba --- /dev/null +++ b/src/shared/database/users/users.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { UsersService } from './users.service'; + +describe('UsersService', () => { + let service: UsersService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [UsersService], + }).compile(); + + service = module.get(UsersService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/shared/database/users/users.service.ts b/src/shared/database/users/users.service.ts new file mode 100644 index 0000000..0a55903 --- /dev/null +++ b/src/shared/database/users/users.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@nestjs/common'; +import { CreateUserDto } from './dto/create-user.dto'; +import { UpdateUserDto } from './dto/update-user.dto'; + +@Injectable() +export class UsersService { + create(createUserDto: CreateUserDto) { + return 'This action adds a new user'; + } + + findAll() { + return `This action returns all users`; + } + + findOne(id: number) { + return `This action returns a #${id} user`; + } + + update(id: number, updateUserDto: UpdateUserDto) { + return `This action updates a #${id} user`; + } + + remove(id: number) { + return `This action removes a #${id} user`; + } +} From 22a626af51b15961f9e7be6a9005190fef7a3278 Mon Sep 17 00:00:00 2001 From: jonmatthis Date: Sat, 6 Jan 2024 17:17:33 -0500 Subject: [PATCH 2/6] Refactor folders and naming --- .../ai/langchain/langchain-chain.service.ts | 0 .../ai/langchain/langchain.module.ts | 0 .../ai/langchain/langchain.service.ts | 2 +- .../ai/openai/openai-secrets.service.ts | 6 +- .../ai/openai/openai.module.ts | 0 src/{shared => core}/chatbot/chatbot.dto.ts | 0 .../chatbot/chatbot.module.ts | 0 .../chatbot/chatbot.service.ts | 6 +- .../database/database.module.ts | 9 +- .../database/schema}/cats/cat.schema.ts | 0 .../database/schema}/cats/cats.controller.ts | 0 .../database/schema}/cats/cats.module.ts | 0 .../database/schema}/cats/cats.service.ts | 0 .../database/schema}/cats/create-cat.dto.ts | 0 .../schema/users/dto/user-create.dto.ts | 6 + .../schema/users/dto/user-update.dto.ts | 5 + src/core/database/schema/users/user.schema.ts | 35 ++++++ .../database/schema/users/users.controller.ts | 116 ++++++++++++++++++ .../database/schema}/users/users.module.ts | 7 +- .../database/schema/users/users.service.ts | 66 ++++++++++ .../services/database-config.service.ts} | 8 +- .../services}/database-connection.service.ts | 0 .../gcp/gcp-secrets.service.ts} | 4 +- src/core/gcp/gcp.module.ts | 8 ++ .../chat/discord/discord-interface.module.ts | 26 ---- .../discord/services/discordChat.service.ts | 40 ------ src/interfaces/discord/discord.module.ts | 27 ++++ .../dto/discord-text.dto.ts} | 2 +- .../services/discord-config.service.ts | 41 +++++++ .../services/discord-ping.service.ts} | 2 +- .../services/discord-ready.service.ts} | 4 +- .../services/discord-thread.service.ts} | 15 ++- .../bolt/slack-app-factory.ts} | 2 +- .../bolt/slack-bolt.module.ts} | 6 +- .../config/slack-config.service.ts} | 6 +- .../{chat => }/slack/decorators/discovery.ts | 4 +- .../decorators/slack-command.decorator.ts} | 0 .../decorators/slack-message.decorator.ts} | 0 .../logging/slack-logger-proxy.service.ts | 0 .../slack.module.ts} | 12 +- .../slack.service.ts} | 8 +- src/main.ts | 4 +- src/main/main.module.ts | 8 +- src/shared/ai/ai.module.ts | 10 -- src/shared/database/cats/cats.service.spec.ts | 78 ------------ src/shared/database/database.service.spec.ts | 18 --- .../database/users/dto/create-user.dto.ts | 1 - .../database/users/dto/update-user.dto.ts | 4 - .../database/users/entities/user.entity.ts | 1 - .../database/users/users.controller.spec.ts | 20 --- src/shared/database/users/users.controller.ts | 34 ----- .../database/users/users.service.spec.ts | 18 --- src/shared/database/users/users.service.ts | 26 ---- src/shared/gcp/gcp.module.ts | 8 -- 54 files changed, 366 insertions(+), 337 deletions(-) rename src/{shared => core}/ai/langchain/langchain-chain.service.ts (100%) rename src/{shared => core}/ai/langchain/langchain.module.ts (100%) rename src/{shared => core}/ai/langchain/langchain.service.ts (97%) rename src/{shared => core}/ai/openai/openai-secrets.service.ts (54%) rename src/{shared => core}/ai/openai/openai.module.ts (100%) rename src/{shared => core}/chatbot/chatbot.dto.ts (100%) rename src/{shared => core}/chatbot/chatbot.module.ts (100%) rename src/{shared => core}/chatbot/chatbot.service.ts (95%) rename src/{shared => core}/database/database.module.ts (58%) rename src/{shared/database => core/database/schema}/cats/cat.schema.ts (100%) rename src/{shared/database => core/database/schema}/cats/cats.controller.ts (100%) rename src/{shared/database => core/database/schema}/cats/cats.module.ts (100%) rename src/{shared/database => core/database/schema}/cats/cats.service.ts (100%) rename src/{shared/database => core/database/schema}/cats/create-cat.dto.ts (100%) create mode 100644 src/core/database/schema/users/dto/user-create.dto.ts create mode 100644 src/core/database/schema/users/dto/user-update.dto.ts create mode 100644 src/core/database/schema/users/user.schema.ts create mode 100644 src/core/database/schema/users/users.controller.ts rename src/{shared/database => core/database/schema}/users/users.module.ts (56%) create mode 100644 src/core/database/schema/users/users.service.ts rename src/{interfaces/chat/discord/services/necordConfig.service.ts => core/database/services/database-config.service.ts} (80%) rename src/{shared/database => core/database/services}/database-connection.service.ts (100%) rename src/{shared/gcp/secretsManager.service.ts => core/gcp/gcp-secrets.service.ts} (85%) create mode 100644 src/core/gcp/gcp.module.ts delete mode 100644 src/interfaces/chat/discord/discord-interface.module.ts delete mode 100644 src/interfaces/chat/discord/services/discordChat.service.ts create mode 100644 src/interfaces/discord/discord.module.ts rename src/interfaces/{chat/discord/dto/textDto.ts => discord/dto/discord-text.dto.ts} (83%) create mode 100644 src/interfaces/discord/services/discord-config.service.ts rename src/interfaces/{chat/discord/services/discordPing.service.ts => discord/services/discord-ping.service.ts} (96%) rename src/interfaces/{chat/discord/services/discordReadyLogging.service.ts => discord/services/discord-ready.service.ts} (80%) rename src/interfaces/{chat/discord/services/discordThread.service.ts => discord/services/discord-thread.service.ts} (86%) rename src/interfaces/{chat/slack/bolt/slackAppFactory.ts => slack/bolt/slack-app-factory.ts} (87%) rename src/interfaces/{chat/slack/bolt/slackBolt.module.ts => slack/bolt/slack-bolt.module.ts} (75%) rename src/interfaces/{chat/slack/config/slackConfig.service.ts => slack/config/slack-config.service.ts} (85%) rename src/interfaces/{chat => }/slack/decorators/discovery.ts (95%) rename src/interfaces/{chat/slack/decorators/slackCommand.decorator.ts => slack/decorators/slack-command.decorator.ts} (100%) rename src/interfaces/{chat/slack/decorators/slackMessage.decorator.ts => slack/decorators/slack-message.decorator.ts} (100%) rename src/interfaces/{chat => }/slack/logging/slack-logger-proxy.service.ts (100%) rename src/interfaces/{chat/slack/slackInterface.module.ts => slack/slack.module.ts} (57%) rename src/interfaces/{chat/slack/slackInterface.service.ts => slack/slack.service.ts} (88%) delete mode 100644 src/shared/ai/ai.module.ts delete mode 100644 src/shared/database/cats/cats.service.spec.ts delete mode 100644 src/shared/database/database.service.spec.ts delete mode 100644 src/shared/database/users/dto/create-user.dto.ts delete mode 100644 src/shared/database/users/dto/update-user.dto.ts delete mode 100644 src/shared/database/users/entities/user.entity.ts delete mode 100644 src/shared/database/users/users.controller.spec.ts delete mode 100644 src/shared/database/users/users.controller.ts delete mode 100644 src/shared/database/users/users.service.spec.ts delete mode 100644 src/shared/database/users/users.service.ts delete mode 100644 src/shared/gcp/gcp.module.ts diff --git a/src/shared/ai/langchain/langchain-chain.service.ts b/src/core/ai/langchain/langchain-chain.service.ts similarity index 100% rename from src/shared/ai/langchain/langchain-chain.service.ts rename to src/core/ai/langchain/langchain-chain.service.ts diff --git a/src/shared/ai/langchain/langchain.module.ts b/src/core/ai/langchain/langchain.module.ts similarity index 100% rename from src/shared/ai/langchain/langchain.module.ts rename to src/core/ai/langchain/langchain.module.ts diff --git a/src/shared/ai/langchain/langchain.service.ts b/src/core/ai/langchain/langchain.service.ts similarity index 97% rename from src/shared/ai/langchain/langchain.service.ts rename to src/core/ai/langchain/langchain.service.ts index eb328ac..593a5ea 100644 --- a/src/shared/ai/langchain/langchain.service.ts +++ b/src/core/ai/langchain/langchain.service.ts @@ -6,9 +6,9 @@ import { BufferMemory } from 'langchain/memory'; import { OpenaiSecretsService } from '../openai/openai-secrets.service'; import { Connection } from 'mongoose'; import { getConnectionToken } from '@nestjs/mongoose'; -import { DatabaseConnectionService } from '../../database/database-connection.service'; import { MongoDBChatMessageHistory } from '@langchain/community/stores/message/mongodb'; import * as mongoose from 'mongoose'; +import { DatabaseConnectionService } from '../../database/services/database-connection.service'; @Injectable() export class LangchainService { diff --git a/src/shared/ai/openai/openai-secrets.service.ts b/src/core/ai/openai/openai-secrets.service.ts similarity index 54% rename from src/shared/ai/openai/openai-secrets.service.ts rename to src/core/ai/openai/openai-secrets.service.ts index c2ae1e1..ab5e574 100644 --- a/src/shared/ai/openai/openai-secrets.service.ts +++ b/src/core/ai/openai/openai-secrets.service.ts @@ -1,11 +1,11 @@ -import { SecretsManagerService } from '../../gcp/secretsManager.service'; +import { GcpSecretsService } from '../../gcp/gcp-secrets.service'; import { Injectable } from '@nestjs/common'; @Injectable() export class OpenaiSecretsService { - constructor(private readonly _sms: SecretsManagerService) {} + constructor(private readonly _sms: GcpSecretsService) {} async getOpenAIKey() { - const [secret] = await this._sms.getManager().accessSecretVersion({ + const [secret] = await this._sms.getSecretsManager().accessSecretVersion({ name: 'projects/588063171007/secrets/OPENAI_API_KEY/versions/latest', }); return secret.payload.data.toString(); diff --git a/src/shared/ai/openai/openai.module.ts b/src/core/ai/openai/openai.module.ts similarity index 100% rename from src/shared/ai/openai/openai.module.ts rename to src/core/ai/openai/openai.module.ts diff --git a/src/shared/chatbot/chatbot.dto.ts b/src/core/chatbot/chatbot.dto.ts similarity index 100% rename from src/shared/chatbot/chatbot.dto.ts rename to src/core/chatbot/chatbot.dto.ts diff --git a/src/shared/chatbot/chatbot.module.ts b/src/core/chatbot/chatbot.module.ts similarity index 100% rename from src/shared/chatbot/chatbot.module.ts rename to src/core/chatbot/chatbot.module.ts diff --git a/src/shared/chatbot/chatbot.service.ts b/src/core/chatbot/chatbot.service.ts similarity index 95% rename from src/shared/chatbot/chatbot.service.ts rename to src/core/chatbot/chatbot.service.ts index 4a9a3d8..fd75dc8 100644 --- a/src/shared/chatbot/chatbot.service.ts +++ b/src/core/chatbot/chatbot.service.ts @@ -19,7 +19,7 @@ export class ChatbotService { private readonly _langchainService: LangchainService, ) {} - public async createChatbot(chatbotId: string, modelName?: string) { + public async createChatbotOld(chatbotId: string, modelName?: string) { this._logger.log( `Creating chatbot with id: ${chatbotId} and model: ${modelName}`, ); @@ -34,9 +34,9 @@ export class ChatbotService { return chatbot; } - public async createChatbot2(chatbotId: string, modelName?: string) { + public async createChatbot(chatbotId: string, modelName?: string) { this._logger.log( - `Creating chatbot with id: ${chatbotId} and model: ${modelName}`, + `Creating chatbot with id: ${chatbotId} and language model (llm): ${modelName}`, ); const { chain, memory } = await this._langchainChainService.createBufferMemoryChain(modelName); diff --git a/src/shared/database/database.module.ts b/src/core/database/database.module.ts similarity index 58% rename from src/shared/database/database.module.ts rename to src/core/database/database.module.ts index 8b08d39..bb92e51 100644 --- a/src/shared/database/database.module.ts +++ b/src/core/database/database.module.ts @@ -1,13 +1,14 @@ import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; -import { CatsModule } from './cats/cats.module'; -import { DatabaseConnectionService } from './database-connection.service'; -import { UsersModule } from './users/users.module'; +import { GcpModule } from '../gcp/gcp.module'; +import { UsersModule } from './schema/users/users.module'; +import { DatabaseConnectionService } from './services/database-connection.service'; @Module({ imports: [ + GcpModule, MongooseModule.forRoot('mongodb://localhost:27017/test'), - CatsModule, + // CatsModule, UsersModule, ], providers: [DatabaseConnectionService], diff --git a/src/shared/database/cats/cat.schema.ts b/src/core/database/schema/cats/cat.schema.ts similarity index 100% rename from src/shared/database/cats/cat.schema.ts rename to src/core/database/schema/cats/cat.schema.ts diff --git a/src/shared/database/cats/cats.controller.ts b/src/core/database/schema/cats/cats.controller.ts similarity index 100% rename from src/shared/database/cats/cats.controller.ts rename to src/core/database/schema/cats/cats.controller.ts diff --git a/src/shared/database/cats/cats.module.ts b/src/core/database/schema/cats/cats.module.ts similarity index 100% rename from src/shared/database/cats/cats.module.ts rename to src/core/database/schema/cats/cats.module.ts diff --git a/src/shared/database/cats/cats.service.ts b/src/core/database/schema/cats/cats.service.ts similarity index 100% rename from src/shared/database/cats/cats.service.ts rename to src/core/database/schema/cats/cats.service.ts diff --git a/src/shared/database/cats/create-cat.dto.ts b/src/core/database/schema/cats/create-cat.dto.ts similarity index 100% rename from src/shared/database/cats/create-cat.dto.ts rename to src/core/database/schema/cats/create-cat.dto.ts diff --git a/src/core/database/schema/users/dto/user-create.dto.ts b/src/core/database/schema/users/dto/user-create.dto.ts new file mode 100644 index 0000000..b701c46 --- /dev/null +++ b/src/core/database/schema/users/dto/user-create.dto.ts @@ -0,0 +1,6 @@ +export class UserCreateDto { + readonly id: string; + readonly discordUsername?: string; + readonly discordId?: string; + readonly metadata?: object; +} diff --git a/src/core/database/schema/users/dto/user-update.dto.ts b/src/core/database/schema/users/dto/user-update.dto.ts new file mode 100644 index 0000000..b32f50c --- /dev/null +++ b/src/core/database/schema/users/dto/user-update.dto.ts @@ -0,0 +1,5 @@ +export class UserUpdateDto { + readonly discordId?: string; + readonly discordUsername?: string; + readonly metadata?: any; +} diff --git a/src/core/database/schema/users/user.schema.ts b/src/core/database/schema/users/user.schema.ts new file mode 100644 index 0000000..4ee2e96 --- /dev/null +++ b/src/core/database/schema/users/user.schema.ts @@ -0,0 +1,35 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import { HydratedDocument } from 'mongoose'; +import { ApiProperty } from '@nestjs/swagger'; + +export type UserDocument = HydratedDocument; + +@Schema({ timestamps: true }) +export class User { + @ApiProperty() + @Prop({}) + type: string; + + @ApiProperty() + @Prop({ required: true, unique: true }) + uuid: string; + + @ApiProperty() + @Prop({ required: true, unique: true }) + id: string; + + @ApiProperty() + @Prop({ unique: true }) + discordId: string; + + // TODO - Implement Slack Id + // @ApiProperty() + // @Prop() + // slackId: string; + + @ApiProperty() + @Prop({ type: Object }) + metadata: Record; +} + +export const UserSchema = SchemaFactory.createForClass(User); diff --git a/src/core/database/schema/users/users.controller.ts b/src/core/database/schema/users/users.controller.ts new file mode 100644 index 0000000..125fb1b --- /dev/null +++ b/src/core/database/schema/users/users.controller.ts @@ -0,0 +1,116 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + Patch, + Post, +} from '@nestjs/common'; +import { UsersService } from './users.service'; +import { User } from './user.schema'; +import { + ApiBearerAuth, + ApiBody, + ApiOperation, + ApiParam, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; +import { UserCreateDto } from './dto/user-create.dto'; +import { UserUpdateDto } from './dto/user-update.dto'; + +function skellyTestUser() { + return { + type: 'skelly', + name: 'Skelly FreeMoCap', + id: '1234', + discordId: '1186697433674166293', + metadata: { + email: 'info@freemocap.org', + things: ['thing1', 'thing2'], + stuff: { stuff1: 'stuff1', stuff2: 'stuff2' }, + }, + }; +} + +function skellyTestUserUpdate() { + return { + id: '1234', + metadata: { + wow: true, + }, + }; +} + +@ApiBearerAuth() +@ApiTags('users') +@Controller('users') +export class UsersController { + constructor(private readonly usersService: UsersService) {} + + @Post('create') + @ApiOperation({ summary: 'Create a user' }) + @ApiBody({ + type: UserCreateDto, + schema: { + example: skellyTestUser(), + }, + }) + @ApiResponse({ + status: 200, + description: 'The created record', + type: User, + }) + async create(@Body() createUserDto: UserCreateDto): Promise { + return this.usersService.create(createUserDto); + } + + @Get() + @ApiOperation({ summary: 'Get all users' }) + @ApiResponse({ + status: 200, + description: 'The found records', + type: User, + isArray: true, + }) + async findAll(): Promise { + return this.usersService.findAll(); + } + @Get(':id') + @ApiOperation({ summary: 'Get user by id' }) + @ApiParam({ name: 'id', type: String }) + @ApiResponse({ + status: 200, + description: 'The found record', + type: User, + }) + async findOne(@Param('id') id: string): Promise { + return this.usersService.findOne(id); + } + + @Patch(':id') + @ApiOperation({ summary: 'Update user by id' }) + @ApiBody({ type: UserUpdateDto }) + @ApiResponse({ + status: 200, + description: 'The updated record', + type: User, + }) + async update( + @Param('id') id: string, + @Body() updateUserDto: UserUpdateDto, + ): Promise { + return this.usersService.update(id, updateUserDto); + } + + @Delete(':id') + @ApiResponse({ + status: 200, + description: 'The deleted record', + type: User, + }) + async delete(@Param('id') id: string) { + return this.usersService.delete(id); + } +} diff --git a/src/shared/database/users/users.module.ts b/src/core/database/schema/users/users.module.ts similarity index 56% rename from src/shared/database/users/users.module.ts rename to src/core/database/schema/users/users.module.ts index ecca17a..3c5ae28 100644 --- a/src/shared/database/users/users.module.ts +++ b/src/core/database/schema/users/users.module.ts @@ -1,8 +1,13 @@ import { Module } from '@nestjs/common'; -import { UsersService } from './users.service'; +import { MongooseModule } from '@nestjs/mongoose'; import { UsersController } from './users.controller'; +import { UsersService } from './users.service'; +import { User, UserSchema } from './user.schema'; @Module({ + imports: [ + MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]), + ], controllers: [UsersController], providers: [UsersService], }) diff --git a/src/core/database/schema/users/users.service.ts b/src/core/database/schema/users/users.service.ts new file mode 100644 index 0000000..c9ad039 --- /dev/null +++ b/src/core/database/schema/users/users.service.ts @@ -0,0 +1,66 @@ +import { + HttpException, + HttpStatus, + Injectable, + NotFoundException, +} from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import { Model } from 'mongoose'; +import { User } from './user.schema'; +import { UserCreateDto } from './dto/user-create.dto'; +import { UserUpdateDto } from './dto/user-update.dto'; +import { v4 as uuidv4 } from 'uuid'; + +@Injectable() +export class UsersService { + constructor( + @InjectModel(User.name) + private readonly userModel: Model, + ) {} + + async findAll(): Promise { + return this.userModel.find().exec(); + } + + async findOne(id: string): Promise { + const user = await this.userModel.findOne({ name: id }).exec(); + if (!user) { + throw new NotFoundException(`User with ID ${id} not found`); + } + return user; + } + + async create(createUserDto: UserCreateDto): Promise { + const existingUser = await this.findOne(createUserDto.id); + + if (existingUser) { + throw new HttpException('User ID already exists', HttpStatus.CONFLICT); + } + + const createdUser = new this.userModel({ + ...createUserDto, + uuid: uuidv4(), + }); + return createdUser.save(); + } + + async update(id: string, updateUserDto: UserUpdateDto) { + const existingUser = await this.userModel.findOne({ id: id }).exec(); + if (!existingUser) { + throw new Error(`User with id ${id} not found`); + } + return this.userModel + .findOneAndUpdate({ id: id }, updateUserDto, { new: true }) + .exec(); + } + + async delete(id: string) { + const deletedUser = await this.userModel + .findOneAndDelete({ id: id }) + .exec(); + if (!deletedUser) { + throw new Error(`User with id ${id} not found`); + } + return deletedUser; + } +} diff --git a/src/interfaces/chat/discord/services/necordConfig.service.ts b/src/core/database/services/database-config.service.ts similarity index 80% rename from src/interfaces/chat/discord/services/necordConfig.service.ts rename to src/core/database/services/database-config.service.ts index 21cd1b5..356bf4a 100644 --- a/src/interfaces/chat/discord/services/necordConfig.service.ts +++ b/src/core/database/services/database-config.service.ts @@ -1,17 +1,17 @@ import { Injectable } from '@nestjs/common'; import { NecordModuleOptions } from 'necord'; import { IntentsBitField } from 'discord.js'; -import { SecretsManagerService } from '../../../../shared/gcp/secretsManager.service'; import { ConfigService } from '@nestjs/config'; +import { GcpSecretsService } from '../../gcp/gcp-secrets.service'; @Injectable() -export class NecordConfigService { +export class DiscordConfigService { private _tokenMap = { DISCORD_BOT_TOKEN: 'projects/588063171007/secrets/DISCORD_BOT_TOKEN/versions/latest', }; constructor( - private readonly sms: SecretsManagerService, + private readonly sms: GcpSecretsService, private readonly _cfgService: ConfigService, ) {} @@ -30,7 +30,7 @@ export class NecordConfigService { private async _createTokenByNodeEnv() { if (process.env.NODE_ENV === 'production') { const secretName = this._tokenMap.DISCORD_BOT_TOKEN; - const [secret] = await this.sms.getManager().accessSecretVersion({ + const [secret] = await this.sms.getSecretsManager().accessSecretVersion({ name: secretName, }); return secret.payload.data.toString(); diff --git a/src/shared/database/database-connection.service.ts b/src/core/database/services/database-connection.service.ts similarity index 100% rename from src/shared/database/database-connection.service.ts rename to src/core/database/services/database-connection.service.ts diff --git a/src/shared/gcp/secretsManager.service.ts b/src/core/gcp/gcp-secrets.service.ts similarity index 85% rename from src/shared/gcp/secretsManager.service.ts rename to src/core/gcp/gcp-secrets.service.ts index 275522b..6661f04 100644 --- a/src/shared/gcp/secretsManager.service.ts +++ b/src/core/gcp/gcp-secrets.service.ts @@ -7,8 +7,8 @@ import { SecretManagerServiceClient } from '@google-cloud/secret-manager'; * in the future, we'll have additional options for the secrets manager stuff from GCP. */ @Injectable() -export class SecretsManagerService { - getManager() { +export class GcpSecretsService { + getSecretsManager() { return new SecretManagerServiceClient(); } } diff --git a/src/core/gcp/gcp.module.ts b/src/core/gcp/gcp.module.ts new file mode 100644 index 0000000..0c77c60 --- /dev/null +++ b/src/core/gcp/gcp.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { GcpSecretsService } from './gcp-secrets.service'; + +@Module({ + providers: [GcpSecretsService], + exports: [GcpSecretsService], +}) +export class GcpModule {} diff --git a/src/interfaces/chat/discord/discord-interface.module.ts b/src/interfaces/chat/discord/discord-interface.module.ts deleted file mode 100644 index c9eae2c..0000000 --- a/src/interfaces/chat/discord/discord-interface.module.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Logger, Module } from '@nestjs/common'; -import { DiscordPingService } from './services/discordPing.service'; -import { NecordModule } from 'necord'; -import { GcpModule } from '../../../shared/gcp/gcp.module'; -import { NecordConfigService } from './services/necordConfig.service'; -import { DiscordReadyLoggingService } from './services/discordReadyLogging.service'; -import { DiscordThreadService } from './services/discordThread.service'; -import { ChatbotModule } from '../../../shared/chatbot/chatbot.module'; - -@Module({ - imports: [ - NecordModule.forRootAsync({ - imports: [GcpModule], - useClass: NecordConfigService, - }), - GcpModule, - ChatbotModule, - ], - providers: [ - DiscordPingService, - DiscordThreadService, - DiscordReadyLoggingService, - Logger, - ], -}) -export class DiscordInterfaceModule {} diff --git a/src/interfaces/chat/discord/services/discordChat.service.ts b/src/interfaces/chat/discord/services/discordChat.service.ts deleted file mode 100644 index f5f0255..0000000 --- a/src/interfaces/chat/discord/services/discordChat.service.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Injectable, Logger } from '@nestjs/common'; -import { Context, Options, SlashCommand, SlashCommandContext } from 'necord'; -import { TextDto } from '../dto/textDto'; -import { LangchainService } from '../../../../shared/ai/langchain/langchain.service'; - -@Injectable() -export class DiscordChatService { - constructor( - private readonly chainBuilderService: LangchainService, - private readonly _logger: Logger, - ) { - } - - @SlashCommand({ - name: 'chat', - description: 'chat service', - }) - public async onChat( - @Context() [interaction]: SlashCommandContext, - @Options() { text }: TextDto, - ) { - this._logger.log('Received chat request with text: ' + text); - await interaction.deferReply(); - this._logger.log('Deferred reply'); - - const chain = - await this.chainBuilderService.createChain('gpt-4-1106-preview'); - - // @ts-ignore - const channelDescription = interaction.channel.topic; - const result = await chain.invoke({ - topic: channelDescription, - text, - }); - - return interaction.editReply({ - content: result, - }); - } -} diff --git a/src/interfaces/discord/discord.module.ts b/src/interfaces/discord/discord.module.ts new file mode 100644 index 0000000..ff917e6 --- /dev/null +++ b/src/interfaces/discord/discord.module.ts @@ -0,0 +1,27 @@ +import { Logger, Module } from '@nestjs/common'; +import { DiscordPingService } from './services/discord-ping.service'; +import { NecordModule } from 'necord'; + +import { DiscordConfigService } from './services/discord-config.service'; +import { DiscordReadyService } from './services/discord-ready.service'; +import { DiscordThreadService } from './services/discord-thread.service'; +import { GcpModule } from '../../core/gcp/gcp.module'; +import { ChatbotModule } from '../../core/chatbot/chatbot.module'; + +@Module({ + imports: [ + NecordModule.forRootAsync({ + imports: [GcpModule], + useClass: DiscordConfigService, + }), + GcpModule, + ChatbotModule, + ], + providers: [ + DiscordPingService, + DiscordThreadService, + DiscordReadyService, + Logger, + ], +}) +export class DiscordModule {} diff --git a/src/interfaces/chat/discord/dto/textDto.ts b/src/interfaces/discord/dto/discord-text.dto.ts similarity index 83% rename from src/interfaces/chat/discord/dto/textDto.ts rename to src/interfaces/discord/dto/discord-text.dto.ts index 6321b22..fe293ef 100644 --- a/src/interfaces/chat/discord/dto/textDto.ts +++ b/src/interfaces/discord/dto/discord-text.dto.ts @@ -1,6 +1,6 @@ import { StringOption } from 'necord'; -export class TextDto { +export class DiscordTextDto { @StringOption({ name: 'text', description: 'Your text', diff --git a/src/interfaces/discord/services/discord-config.service.ts b/src/interfaces/discord/services/discord-config.service.ts new file mode 100644 index 0000000..fe8d443 --- /dev/null +++ b/src/interfaces/discord/services/discord-config.service.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@nestjs/common'; +import { NecordModuleOptions } from 'necord'; +import { IntentsBitField } from 'discord.js'; +import { ConfigService } from '@nestjs/config'; +import { GcpSecretsService } from '../../../core/gcp/gcp-secrets.service'; + +@Injectable() +export class DiscordConfigService { + private _tokenMap = { + DISCORD_BOT_TOKEN: + 'projects/588063171007/secrets/DISCORD_BOT_TOKEN/versions/latest', + }; + constructor( + private readonly sms: GcpSecretsService, + private readonly _cfgService: ConfigService, + ) {} + + async createNecordOptions(): Promise { + const token = await this._createTokenByNodeEnv(); + return { + token, + intents: [ + IntentsBitField.Flags.Guilds, + IntentsBitField.Flags.GuildMessages, + IntentsBitField.Flags.MessageContent, + ], + }; + } + + private async _createTokenByNodeEnv() { + if (process.env.NODE_ENV === 'production') { + const secretName = this._tokenMap.DISCORD_BOT_TOKEN; + const [secret] = await this.sms.getSecretsManager().accessSecretVersion({ + name: secretName, + }); + return secret.payload.data.toString(); + } + + return this._cfgService.getOrThrow('DISCORD_BOT_TOKEN'); + } +} diff --git a/src/interfaces/chat/discord/services/discordPing.service.ts b/src/interfaces/discord/services/discord-ping.service.ts similarity index 96% rename from src/interfaces/chat/discord/services/discordPing.service.ts rename to src/interfaces/discord/services/discord-ping.service.ts index ca46761..dcb46e7 100644 --- a/src/interfaces/chat/discord/services/discordPing.service.ts +++ b/src/interfaces/discord/services/discord-ping.service.ts @@ -4,7 +4,7 @@ import { Context, SlashCommand, SlashCommandContext } from 'necord'; @Injectable() export class DiscordPingService { @SlashCommand({ - name: 'hello', + name: 'ping', description: 'Ping-Pong Command', }) public async handleHelloPingCommand( diff --git a/src/interfaces/chat/discord/services/discordReadyLogging.service.ts b/src/interfaces/discord/services/discord-ready.service.ts similarity index 80% rename from src/interfaces/chat/discord/services/discordReadyLogging.service.ts rename to src/interfaces/discord/services/discord-ready.service.ts index 6aaaaf9..1bdddd8 100644 --- a/src/interfaces/chat/discord/services/discordReadyLogging.service.ts +++ b/src/interfaces/discord/services/discord-ready.service.ts @@ -3,8 +3,8 @@ import { Context, ContextOf, On, Once } from 'necord'; import { Client } from 'discord.js'; @Injectable() -export class DiscordReadyLoggingService { - private readonly logger = new Logger(DiscordReadyLoggingService.name); +export class DiscordReadyService { + private readonly logger = new Logger(DiscordReadyService.name); public constructor(private readonly client: Client) {} diff --git a/src/interfaces/chat/discord/services/discordThread.service.ts b/src/interfaces/discord/services/discord-thread.service.ts similarity index 86% rename from src/interfaces/chat/discord/services/discordThread.service.ts rename to src/interfaces/discord/services/discord-thread.service.ts index 930dfcd..f72d931 100644 --- a/src/interfaces/chat/discord/services/discordThread.service.ts +++ b/src/interfaces/discord/services/discord-thread.service.ts @@ -1,7 +1,6 @@ import { Injectable, Logger, OnModuleDestroy } from '@nestjs/common'; import { Context, Options, SlashCommand, SlashCommandContext } from 'necord'; -import { TextDto } from '../dto/textDto'; -import { ChatbotService } from '../../../../shared/chatbot/chatbot.service'; +import { DiscordTextDto } from '../dto/discord-text.dto'; import { ChatInputCommandInteraction, Client, @@ -9,6 +8,7 @@ import { TextChannel, ThreadChannel, } from 'discord.js'; +import { ChatbotService } from '../../../core/chatbot/chatbot.service'; @Injectable() export class DiscordThreadService implements OnModuleDestroy { @@ -25,7 +25,7 @@ export class DiscordThreadService implements OnModuleDestroy { }) public async onThreadCommand( @Context() [interaction]: SlashCommandContext, - @Options() startingText: TextDto, + @Options() startingText: DiscordTextDto, ) { await interaction.deferReply(); const { text } = startingText; @@ -33,14 +33,19 @@ export class DiscordThreadService implements OnModuleDestroy { `Creating thread with starting text:'${text}' in channel: name= ${interaction.channel.name}, id=${interaction.channel.id} `, ); const channel = interaction.channel as TextChannel; + const maxThreadNameLength = 100; // Discord's maximum thread name length + let threadName = text || 'new thread'; + if (threadName.length > maxThreadNameLength) { + threadName = threadName.substring(0, maxThreadNameLength); + } const thread = await channel.threads.create({ - name: text || 'new thread', + name: threadName, autoArchiveDuration: 60, reason: 'wow this is a thread', }); // await this._chatbotService.createChatbot(thread.id); - await this._chatbotService.createChatbot2(thread.id); + await this._chatbotService.createChatbot(thread.id); this._beginWatchingIncomingMessages(interaction, channel, thread); await this._sendInitialReply(interaction, channel, thread, text); diff --git a/src/interfaces/chat/slack/bolt/slackAppFactory.ts b/src/interfaces/slack/bolt/slack-app-factory.ts similarity index 87% rename from src/interfaces/chat/slack/bolt/slackAppFactory.ts rename to src/interfaces/slack/bolt/slack-app-factory.ts index e2a6ce3..6df2118 100644 --- a/src/interfaces/chat/slack/bolt/slackAppFactory.ts +++ b/src/interfaces/slack/bolt/slack-app-factory.ts @@ -1,5 +1,5 @@ import { App } from '@slack/bolt'; -import { SlackConfigService } from '../config/slackConfig.service'; +import { SlackConfigService } from '../config/slack-config.service'; import { SlackLoggerAdapter } from '../logging/slack-logger-proxy.service'; import { Provider } from '@nestjs/common'; diff --git a/src/interfaces/chat/slack/bolt/slackBolt.module.ts b/src/interfaces/slack/bolt/slack-bolt.module.ts similarity index 75% rename from src/interfaces/chat/slack/bolt/slackBolt.module.ts rename to src/interfaces/slack/bolt/slack-bolt.module.ts index 986a431..d657e86 100644 --- a/src/interfaces/chat/slack/bolt/slackBolt.module.ts +++ b/src/interfaces/slack/bolt/slack-bolt.module.ts @@ -1,9 +1,9 @@ -import { slackServiceFactory } from './slackAppFactory'; +import { slackServiceFactory } from './slack-app-factory'; import { Module, OnApplicationShutdown, OnModuleInit } from '@nestjs/common'; import { App } from '@slack/bolt'; -import { SlackConfigService } from '../config/slackConfig.service'; -import { GcpModule } from '../../../../shared/gcp/gcp.module'; +import { SlackConfigService } from '../config/slack-config.service'; import { SlackLoggerAdapter } from '../logging/slack-logger-proxy.service'; +import { GcpModule } from '../../../core/gcp/gcp.module'; @Module({ imports: [GcpModule], diff --git a/src/interfaces/chat/slack/config/slackConfig.service.ts b/src/interfaces/slack/config/slack-config.service.ts similarity index 85% rename from src/interfaces/chat/slack/config/slackConfig.service.ts rename to src/interfaces/slack/config/slack-config.service.ts index 7bbfa74..4cf5f71 100644 --- a/src/interfaces/chat/slack/config/slackConfig.service.ts +++ b/src/interfaces/slack/config/slack-config.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; -import { SecretsManagerService } from '../../../../shared/gcp/secretsManager.service'; import { ConfigService } from '@nestjs/config'; import { AppOptions } from '@slack/bolt/dist/App'; +import { GcpSecretsService } from '../../../core/gcp/gcp-secrets.service'; @Injectable() export class SlackConfigService { @@ -14,7 +14,7 @@ export class SlackConfigService { 'projects/588063171007/secrets/SLACK_SIGNING_SECRET/versions/latest', }; constructor( - private readonly _sms: SecretsManagerService, + private readonly _sms: GcpSecretsService, private readonly _cfgService: ConfigService, ) {} @@ -31,7 +31,7 @@ export class SlackConfigService { private async _createTokenByNodeEnv(tokenString: string) { if (process.env.NODE_ENV === 'production') { const secretName = this._tokenMap[tokenString]; - const [secret] = await this._sms.getManager().accessSecretVersion({ + const [secret] = await this._sms.getSecretsManager().accessSecretVersion({ name: secretName, }); return secret.payload.data.toString(); diff --git a/src/interfaces/chat/slack/decorators/discovery.ts b/src/interfaces/slack/decorators/discovery.ts similarity index 95% rename from src/interfaces/chat/slack/decorators/discovery.ts rename to src/interfaces/slack/decorators/discovery.ts index c00a604..27e024d 100644 --- a/src/interfaces/chat/slack/decorators/discovery.ts +++ b/src/interfaces/slack/decorators/discovery.ts @@ -3,11 +3,11 @@ import { Injectable } from '@nestjs/common'; import { SLACK_COMMAND_METADATA_KEY, SlackCommandArgs, -} from './slackCommand.decorator'; +} from './slack-command.decorator'; import { SLACK_MESSAGE_METADATA_KEY, SlackMessageArgs, -} from './slackMessage.decorator'; +} from './slack-message.decorator'; import { App } from '@slack/bolt'; @Injectable() diff --git a/src/interfaces/chat/slack/decorators/slackCommand.decorator.ts b/src/interfaces/slack/decorators/slack-command.decorator.ts similarity index 100% rename from src/interfaces/chat/slack/decorators/slackCommand.decorator.ts rename to src/interfaces/slack/decorators/slack-command.decorator.ts diff --git a/src/interfaces/chat/slack/decorators/slackMessage.decorator.ts b/src/interfaces/slack/decorators/slack-message.decorator.ts similarity index 100% rename from src/interfaces/chat/slack/decorators/slackMessage.decorator.ts rename to src/interfaces/slack/decorators/slack-message.decorator.ts diff --git a/src/interfaces/chat/slack/logging/slack-logger-proxy.service.ts b/src/interfaces/slack/logging/slack-logger-proxy.service.ts similarity index 100% rename from src/interfaces/chat/slack/logging/slack-logger-proxy.service.ts rename to src/interfaces/slack/logging/slack-logger-proxy.service.ts diff --git a/src/interfaces/chat/slack/slackInterface.module.ts b/src/interfaces/slack/slack.module.ts similarity index 57% rename from src/interfaces/chat/slack/slackInterface.module.ts rename to src/interfaces/slack/slack.module.ts index dd1a968..7e069a5 100644 --- a/src/interfaces/chat/slack/slackInterface.module.ts +++ b/src/interfaces/slack/slack.module.ts @@ -1,16 +1,16 @@ import { Module, OnModuleInit } from '@nestjs/common'; -import { GcpModule } from '../../../shared/gcp/gcp.module'; -import { SlackInterfaceService } from './slackInterface.service'; +import { SlackService } from './slack.service'; import { SlackCommandMethodDiscovery } from './decorators/discovery'; import { DiscoveryModule } from '@golevelup/nestjs-discovery'; -import { ChatbotModule } from '../../../shared/chatbot/chatbot.module'; -import { SlackBoltModule } from './bolt/slackBolt.module'; +import { SlackBoltModule } from './bolt/slack-bolt.module'; +import { ChatbotModule } from '../../core/chatbot/chatbot.module'; +import { GcpModule } from '../../core/gcp/gcp.module'; @Module({ imports: [SlackBoltModule, DiscoveryModule, ChatbotModule, GcpModule], - providers: [SlackInterfaceService, SlackCommandMethodDiscovery], + providers: [SlackService, SlackCommandMethodDiscovery], }) -export class SlackInterfaceModule implements OnModuleInit { +export class SlackModule implements OnModuleInit { constructor( private readonly _slackCommandDiscovery: SlackCommandMethodDiscovery, ) {} diff --git a/src/interfaces/chat/slack/slackInterface.service.ts b/src/interfaces/slack/slack.service.ts similarity index 88% rename from src/interfaces/chat/slack/slackInterface.service.ts rename to src/interfaces/slack/slack.service.ts index b50da83..9d03b92 100644 --- a/src/interfaces/chat/slack/slackInterface.service.ts +++ b/src/interfaces/slack/slack.service.ts @@ -1,6 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { SlackCommand } from './decorators/slackCommand.decorator'; -import { ChatbotService } from '../../../shared/chatbot/chatbot.service'; +import { SlackCommand } from './decorators/slack-command.decorator'; import { v4 } from 'uuid'; import { App, @@ -8,10 +7,11 @@ import { SlackCommandMiddlewareArgs, SlackEventMiddlewareArgs, } from '@slack/bolt'; -import { SlackMessage } from './decorators/slackMessage.decorator'; +import { SlackMessage } from './decorators/slack-message.decorator'; +import { ChatbotService } from '../../core/chatbot/chatbot.service'; @Injectable() -export class SlackInterfaceService { +export class SlackService { constructor( private readonly _chatbotCore: ChatbotService, private readonly _app: App, diff --git a/src/main.ts b/src/main.ts index ad86cc9..f7d17a1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,7 +3,9 @@ import { MainModule } from './main/main.module'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; async function bootstrap() { - const app = await NestFactory.create(MainModule); + const app = await NestFactory.create(MainModule, { + logger: ['error', 'warn', 'debug', 'log', 'verbose'], + }); const options = new DocumentBuilder() .setTitle('SkellyBot 💀🤖✨') diff --git a/src/main/main.module.ts b/src/main/main.module.ts index 7d91494..d3ba532 100644 --- a/src/main/main.module.ts +++ b/src/main/main.module.ts @@ -1,9 +1,7 @@ import { Module } from '@nestjs/common'; -import { SlackInterfaceModule } from '../interfaces/chat/slack/slackInterface.module'; +import { SlackModule } from '../interfaces/slack/slack.module'; import { MainController } from './main.controller'; import { ConfigModule } from '@nestjs/config'; -import { DiscordInterfaceModule } from '../interfaces/chat/discord/discord-interface.module'; -import { DatabaseModule } from '../shared/database/database.module'; @Module({ imports: [ @@ -11,9 +9,7 @@ import { DatabaseModule } from '../shared/database/database.module'; isGlobal: true, envFilePath: ['.env', '.env.slack', '.env.discord', '.env.mongo'], }), - DiscordInterfaceModule, - SlackInterfaceModule, - DatabaseModule, + SlackModule, ], controllers: [MainController], }) diff --git a/src/shared/ai/ai.module.ts b/src/shared/ai/ai.module.ts deleted file mode 100644 index 1c1d26f..0000000 --- a/src/shared/ai/ai.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { OpenaiModule } from './openai/openai.module'; -import { LangchainModule } from './langchain/langchain.module'; -import { Module } from '@nestjs/common'; - -@Module({ - imports: [OpenaiModule, LangchainModule], - providers: [], - exports: [], -}) -export class AiModule {} diff --git a/src/shared/database/cats/cats.service.spec.ts b/src/shared/database/cats/cats.service.spec.ts deleted file mode 100644 index 7ac8a91..0000000 --- a/src/shared/database/cats/cats.service.spec.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { getModelToken } from '@nestjs/mongoose'; -import { Test, TestingModule } from '@nestjs/testing'; -import { Model } from 'mongoose'; -import { CatsService } from './cats.service'; -import { Cat } from './schemas/cat.schema'; - -const mockCat = { - name: 'Cat #1', - breed: 'Breed #1', - age: 4, -}; - -describe('CatsService', () => { - let service: CatsService; - let model: Model; - - const catsArray = [ - { - name: 'Cat #1', - breed: 'Breed #1', - age: 4, - }, - { - name: 'Cat #2', - breed: 'Breed #2', - age: 2, - }, - ]; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - CatsService, - { - provide: getModelToken('Cat'), - useValue: { - new: jest.fn().mockResolvedValue(mockCat), - constructor: jest.fn().mockResolvedValue(mockCat), - find: jest.fn(), - create: jest.fn(), - exec: jest.fn(), - }, - }, - ], - }).compile(); - - service = module.get(CatsService); - model = module.get>(getModelToken('Cat')); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); - - it('should return all cats', async () => { - jest.spyOn(model, 'find').mockReturnValue({ - exec: jest.fn().mockResolvedValueOnce(catsArray), - } as any); - const cats = await service.findAll(); - expect(cats).toEqual(catsArray); - }); - - it('should insert a new cat', async () => { - jest.spyOn(model, 'create').mockImplementationOnce(() => - Promise.resolve({ - name: 'Cat #1', - breed: 'Breed #1', - age: 4, - } as any), - ); - const newCat = await service.create({ - name: 'Cat #1', - breed: 'Breed #1', - age: 4, - }); - expect(newCat).toEqual(mockCat); - }); -}); diff --git a/src/shared/database/database.service.spec.ts b/src/shared/database/database.service.spec.ts deleted file mode 100644 index f1964ef..0000000 --- a/src/shared/database/database.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { MemoryService } from './memory.service'; - -describe('DatabaseService', () => { - let service: MemoryService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [MemoryService], - }).compile(); - - service = module.get(MemoryService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/shared/database/users/dto/create-user.dto.ts b/src/shared/database/users/dto/create-user.dto.ts deleted file mode 100644 index 0311be1..0000000 --- a/src/shared/database/users/dto/create-user.dto.ts +++ /dev/null @@ -1 +0,0 @@ -export class CreateUserDto {} diff --git a/src/shared/database/users/dto/update-user.dto.ts b/src/shared/database/users/dto/update-user.dto.ts deleted file mode 100644 index dfd37fb..0000000 --- a/src/shared/database/users/dto/update-user.dto.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { PartialType } from '@nestjs/mapped-types'; -import { CreateUserDto } from './create-user.dto'; - -export class UpdateUserDto extends PartialType(CreateUserDto) {} diff --git a/src/shared/database/users/entities/user.entity.ts b/src/shared/database/users/entities/user.entity.ts deleted file mode 100644 index 4f82c14..0000000 --- a/src/shared/database/users/entities/user.entity.ts +++ /dev/null @@ -1 +0,0 @@ -export class User {} diff --git a/src/shared/database/users/users.controller.spec.ts b/src/shared/database/users/users.controller.spec.ts deleted file mode 100644 index a76d310..0000000 --- a/src/shared/database/users/users.controller.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { UsersController } from './users.controller'; -import { UsersService } from './users.service'; - -describe('UsersController', () => { - let controller: UsersController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [UsersController], - providers: [UsersService], - }).compile(); - - controller = module.get(UsersController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/src/shared/database/users/users.controller.ts b/src/shared/database/users/users.controller.ts deleted file mode 100644 index 3eca7eb..0000000 --- a/src/shared/database/users/users.controller.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; -import { UsersService } from './users.service'; -import { CreateUserDto } from './dto/create-user.dto'; -import { UpdateUserDto } from './dto/update-user.dto'; - -@Controller('users') -export class UsersController { - constructor(private readonly usersService: UsersService) {} - - @Post() - create(@Body() createUserDto: CreateUserDto) { - return this.usersService.create(createUserDto); - } - - @Get() - findAll() { - return this.usersService.findAll(); - } - - @Get(':id') - findOne(@Param('id') id: string) { - return this.usersService.findOne(+id); - } - - @Patch(':id') - update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) { - return this.usersService.update(+id, updateUserDto); - } - - @Delete(':id') - remove(@Param('id') id: string) { - return this.usersService.remove(+id); - } -} diff --git a/src/shared/database/users/users.service.spec.ts b/src/shared/database/users/users.service.spec.ts deleted file mode 100644 index 62815ba..0000000 --- a/src/shared/database/users/users.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { UsersService } from './users.service'; - -describe('UsersService', () => { - let service: UsersService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [UsersService], - }).compile(); - - service = module.get(UsersService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/shared/database/users/users.service.ts b/src/shared/database/users/users.service.ts deleted file mode 100644 index 0a55903..0000000 --- a/src/shared/database/users/users.service.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { CreateUserDto } from './dto/create-user.dto'; -import { UpdateUserDto } from './dto/update-user.dto'; - -@Injectable() -export class UsersService { - create(createUserDto: CreateUserDto) { - return 'This action adds a new user'; - } - - findAll() { - return `This action returns all users`; - } - - findOne(id: number) { - return `This action returns a #${id} user`; - } - - update(id: number, updateUserDto: UpdateUserDto) { - return `This action updates a #${id} user`; - } - - remove(id: number) { - return `This action removes a #${id} user`; - } -} diff --git a/src/shared/gcp/gcp.module.ts b/src/shared/gcp/gcp.module.ts deleted file mode 100644 index 378589a..0000000 --- a/src/shared/gcp/gcp.module.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Module } from '@nestjs/common'; -import { SecretsManagerService } from './secretsManager.service'; - -@Module({ - providers: [SecretsManagerService], - exports: [SecretsManagerService], -}) -export class GcpModule {} From a37ed5ee3a3ceebe6e9de1c60b46344b81077275 Mon Sep 17 00:00:00 2001 From: jonmatthis Date: Sat, 6 Jan 2024 18:34:06 -0500 Subject: [PATCH 3/6] building out User schema --- .../schema/users/dto/user-create.dto.ts | 6 --- .../schema/users/dto/user-update.dto.ts | 5 -- src/core/database/schema/users/user.dto.ts | 6 +++ src/core/database/schema/users/user.schema.ts | 36 +++++++-------- .../database/schema/users/users.controller.ts | 18 ++------ .../database/schema/users/users.module.ts | 2 +- .../database/schema/users/users.service.ts | 46 +++++++++++++++---- .../services/discord-thread.service.ts | 8 ++++ src/main/main.module.ts | 2 + 9 files changed, 75 insertions(+), 54 deletions(-) delete mode 100644 src/core/database/schema/users/dto/user-create.dto.ts delete mode 100644 src/core/database/schema/users/dto/user-update.dto.ts create mode 100644 src/core/database/schema/users/user.dto.ts diff --git a/src/core/database/schema/users/dto/user-create.dto.ts b/src/core/database/schema/users/dto/user-create.dto.ts deleted file mode 100644 index b701c46..0000000 --- a/src/core/database/schema/users/dto/user-create.dto.ts +++ /dev/null @@ -1,6 +0,0 @@ -export class UserCreateDto { - readonly id: string; - readonly discordUsername?: string; - readonly discordId?: string; - readonly metadata?: object; -} diff --git a/src/core/database/schema/users/dto/user-update.dto.ts b/src/core/database/schema/users/dto/user-update.dto.ts deleted file mode 100644 index b32f50c..0000000 --- a/src/core/database/schema/users/dto/user-update.dto.ts +++ /dev/null @@ -1,5 +0,0 @@ -export class UserUpdateDto { - readonly discordId?: string; - readonly discordUsername?: string; - readonly metadata?: any; -} diff --git a/src/core/database/schema/users/user.dto.ts b/src/core/database/schema/users/user.dto.ts new file mode 100644 index 0000000..1fba53f --- /dev/null +++ b/src/core/database/schema/users/user.dto.ts @@ -0,0 +1,6 @@ +export class UserDto { + readonly favoriteColor?: string; + readonly slackID?: string; + readonly discordId?: string; + readonly metadata?: object; +} diff --git a/src/core/database/schema/users/user.schema.ts b/src/core/database/schema/users/user.schema.ts index 4ee2e96..f64b24a 100644 --- a/src/core/database/schema/users/user.schema.ts +++ b/src/core/database/schema/users/user.schema.ts @@ -1,35 +1,35 @@ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; -import { HydratedDocument } from 'mongoose'; -import { ApiProperty } from '@nestjs/swagger'; - -export type UserDocument = HydratedDocument; @Schema({ timestamps: true }) export class User { - @ApiProperty() - @Prop({}) - type: string; - - @ApiProperty() @Prop({ required: true, unique: true }) uuid: string; - @ApiProperty() - @Prop({ required: true, unique: true }) - id: string; + @Prop({ required: true }) + favoriteColor: string; - @ApiProperty() @Prop({ unique: true }) discordId: string; - // TODO - Implement Slack Id - // @ApiProperty() - // @Prop() - // slackId: string; + @Prop({ unique: true }) + slackId: string; - @ApiProperty() @Prop({ type: Object }) metadata: Record; } export const UserSchema = SchemaFactory.createForClass(User); + +function validateFavoriteColor(color: string) { + const regex = /^([A-Fa-f0-9]{6})$/; + return regex.test(`${color}`); +} + +UserSchema.pre('save', function (next) { + if (!validateFavoriteColor(this.favoriteColor)) { + throw new Error( + 'User.favoriteColor should be a valid 6 digit hex color code (e.g. FF00FF)', + ); + } + next(); +}); diff --git a/src/core/database/schema/users/users.controller.ts b/src/core/database/schema/users/users.controller.ts index 125fb1b..6906bf0 100644 --- a/src/core/database/schema/users/users.controller.ts +++ b/src/core/database/schema/users/users.controller.ts @@ -17,16 +17,15 @@ import { ApiResponse, ApiTags, } from '@nestjs/swagger'; -import { UserCreateDto } from './dto/user-create.dto'; +import { UserDto } from './dto/user.dto'; import { UserUpdateDto } from './dto/user-update.dto'; function skellyTestUser() { return { - type: 'skelly', name: 'Skelly FreeMoCap', - id: '1234', discordId: '1186697433674166293', metadata: { + type: 'skelly', email: 'info@freemocap.org', things: ['thing1', 'thing2'], stuff: { stuff1: 'stuff1', stuff2: 'stuff2' }, @@ -34,15 +33,6 @@ function skellyTestUser() { }; } -function skellyTestUserUpdate() { - return { - id: '1234', - metadata: { - wow: true, - }, - }; -} - @ApiBearerAuth() @ApiTags('users') @Controller('users') @@ -52,7 +42,7 @@ export class UsersController { @Post('create') @ApiOperation({ summary: 'Create a user' }) @ApiBody({ - type: UserCreateDto, + type: UserDto, schema: { example: skellyTestUser(), }, @@ -62,7 +52,7 @@ export class UsersController { description: 'The created record', type: User, }) - async create(@Body() createUserDto: UserCreateDto): Promise { + async create(@Body() createUserDto: UserDto): Promise { return this.usersService.create(createUserDto); } diff --git a/src/core/database/schema/users/users.module.ts b/src/core/database/schema/users/users.module.ts index 3c5ae28..e34329a 100644 --- a/src/core/database/schema/users/users.module.ts +++ b/src/core/database/schema/users/users.module.ts @@ -8,7 +8,7 @@ import { User, UserSchema } from './user.schema'; imports: [ MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]), ], - controllers: [UsersController], + // controllers: [UsersController], providers: [UsersService], }) export class UsersModule {} diff --git a/src/core/database/schema/users/users.service.ts b/src/core/database/schema/users/users.service.ts index c9ad039..56da2bf 100644 --- a/src/core/database/schema/users/users.service.ts +++ b/src/core/database/schema/users/users.service.ts @@ -7,8 +7,7 @@ import { import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; import { User } from './user.schema'; -import { UserCreateDto } from './dto/user-create.dto'; -import { UserUpdateDto } from './dto/user-update.dto'; +import { UserDto } from './user.dto'; import { v4 as uuidv4 } from 'uuid'; @Injectable() @@ -22,35 +21,62 @@ export class UsersService { return this.userModel.find().exec(); } - async findOne(id: string): Promise { - const user = await this.userModel.findOne({ name: id }).exec(); + async findOne(userDto: UserDto): Promise { + let user: User; + if (userDto.discordId) { + user = await this.userModel + .findOne({ discordId: userDto.discordId }) + .exec(); + } else if (userDto.slackID) { + user = await this.userModel.findOne({ slackID: userDto.slackID }).exec(); + } + if (!user) { - throw new NotFoundException(`User with ID ${id} not found`); + throw new NotFoundException(`User not found`); } return user; } - async create(createUserDto: UserCreateDto): Promise { - const existingUser = await this.findOne(createUserDto.id); + async getOrCreate(userDto: UserDto): Promise { + const existingUser = await this.findOne(userDto); if (existingUser) { + return existingUser; + } + + return this.create(userDto); + } + + _generateHexColorId() { + const letters = '0123456789ABCDEF'; + let color = ''; + const digits = 6; + for (let i = 0; i < digits; i++) { + color += letters[Math.floor(Math.random() * 16)]; + } + return color; + } + + async create(userDto: UserDto): Promise { + if (await this.findOne(userDto)) { throw new HttpException('User ID already exists', HttpStatus.CONFLICT); } const createdUser = new this.userModel({ - ...createUserDto, + ...userDto, uuid: uuidv4(), + favoriteColor: this._generateHexColorId() || userDto.favoriteColor, }); return createdUser.save(); } - async update(id: string, updateUserDto: UserUpdateDto) { + async update(id: string, userDto: UserDto) { const existingUser = await this.userModel.findOne({ id: id }).exec(); if (!existingUser) { throw new Error(`User with id ${id} not found`); } return this.userModel - .findOneAndUpdate({ id: id }, updateUserDto, { new: true }) + .findOneAndUpdate({ id: id }, userDto, { new: true }) .exec(); } diff --git a/src/interfaces/discord/services/discord-thread.service.ts b/src/interfaces/discord/services/discord-thread.service.ts index f72d931..b1e5b31 100644 --- a/src/interfaces/discord/services/discord-thread.service.ts +++ b/src/interfaces/discord/services/discord-thread.service.ts @@ -9,10 +9,14 @@ import { ThreadChannel, } from 'discord.js'; import { ChatbotService } from '../../../core/chatbot/chatbot.service'; +import { InjectModel } from '@nestjs/mongoose'; +import { User } from '../../../core/database/schema/users/user.schema'; +import { UsersService } from '../../../core/database/schema/users/users.service'; @Injectable() export class DiscordThreadService implements OnModuleDestroy { constructor( + private readonly _usersService: UsersService, private readonly _logger: Logger, private readonly _chatbotService: ChatbotService, private readonly _client: Client, @@ -44,6 +48,10 @@ export class DiscordThreadService implements OnModuleDestroy { reason: 'wow this is a thread', }); + await this._usersService.getOrCreate({ + discordId: interaction.user.id, + }); + // await this._chatbotService.createChatbot(thread.id); await this._chatbotService.createChatbot(thread.id); diff --git a/src/main/main.module.ts b/src/main/main.module.ts index d3ba532..3286582 100644 --- a/src/main/main.module.ts +++ b/src/main/main.module.ts @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'; import { SlackModule } from '../interfaces/slack/slack.module'; import { MainController } from './main.controller'; import { ConfigModule } from '@nestjs/config'; +import { DiscordModule } from '../interfaces/discord/discord.module'; @Module({ imports: [ @@ -10,6 +11,7 @@ import { ConfigModule } from '@nestjs/config'; envFilePath: ['.env', '.env.slack', '.env.discord', '.env.mongo'], }), SlackModule, + DiscordModule, ], controllers: [MainController], }) From 66dce078d1bdbcbe5a3b83b5ebc967b86ed41c4a Mon Sep 17 00:00:00 2001 From: jonmatthis Date: Sat, 6 Jan 2024 18:34:41 -0500 Subject: [PATCH 4/6] Delete UsersController --- .../database/schema/users/users.controller.ts | 106 ------------------ .../database/schema/users/users.module.ts | 2 - 2 files changed, 108 deletions(-) delete mode 100644 src/core/database/schema/users/users.controller.ts diff --git a/src/core/database/schema/users/users.controller.ts b/src/core/database/schema/users/users.controller.ts deleted file mode 100644 index 6906bf0..0000000 --- a/src/core/database/schema/users/users.controller.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { - Body, - Controller, - Delete, - Get, - Param, - Patch, - Post, -} from '@nestjs/common'; -import { UsersService } from './users.service'; -import { User } from './user.schema'; -import { - ApiBearerAuth, - ApiBody, - ApiOperation, - ApiParam, - ApiResponse, - ApiTags, -} from '@nestjs/swagger'; -import { UserDto } from './dto/user.dto'; -import { UserUpdateDto } from './dto/user-update.dto'; - -function skellyTestUser() { - return { - name: 'Skelly FreeMoCap', - discordId: '1186697433674166293', - metadata: { - type: 'skelly', - email: 'info@freemocap.org', - things: ['thing1', 'thing2'], - stuff: { stuff1: 'stuff1', stuff2: 'stuff2' }, - }, - }; -} - -@ApiBearerAuth() -@ApiTags('users') -@Controller('users') -export class UsersController { - constructor(private readonly usersService: UsersService) {} - - @Post('create') - @ApiOperation({ summary: 'Create a user' }) - @ApiBody({ - type: UserDto, - schema: { - example: skellyTestUser(), - }, - }) - @ApiResponse({ - status: 200, - description: 'The created record', - type: User, - }) - async create(@Body() createUserDto: UserDto): Promise { - return this.usersService.create(createUserDto); - } - - @Get() - @ApiOperation({ summary: 'Get all users' }) - @ApiResponse({ - status: 200, - description: 'The found records', - type: User, - isArray: true, - }) - async findAll(): Promise { - return this.usersService.findAll(); - } - @Get(':id') - @ApiOperation({ summary: 'Get user by id' }) - @ApiParam({ name: 'id', type: String }) - @ApiResponse({ - status: 200, - description: 'The found record', - type: User, - }) - async findOne(@Param('id') id: string): Promise { - return this.usersService.findOne(id); - } - - @Patch(':id') - @ApiOperation({ summary: 'Update user by id' }) - @ApiBody({ type: UserUpdateDto }) - @ApiResponse({ - status: 200, - description: 'The updated record', - type: User, - }) - async update( - @Param('id') id: string, - @Body() updateUserDto: UserUpdateDto, - ): Promise { - return this.usersService.update(id, updateUserDto); - } - - @Delete(':id') - @ApiResponse({ - status: 200, - description: 'The deleted record', - type: User, - }) - async delete(@Param('id') id: string) { - return this.usersService.delete(id); - } -} diff --git a/src/core/database/schema/users/users.module.ts b/src/core/database/schema/users/users.module.ts index e34329a..ae7f0f2 100644 --- a/src/core/database/schema/users/users.module.ts +++ b/src/core/database/schema/users/users.module.ts @@ -1,6 +1,5 @@ import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; -import { UsersController } from './users.controller'; import { UsersService } from './users.service'; import { User, UserSchema } from './user.schema'; @@ -8,7 +7,6 @@ import { User, UserSchema } from './user.schema'; imports: [ MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]), ], - // controllers: [UsersController], providers: [UsersService], }) export class UsersModule {} From cf3482ae8b24d9735869ea0d3b622ed630498681 Mon Sep 17 00:00:00 2001 From: jonmatthis Date: Sat, 6 Jan 2024 18:44:40 -0500 Subject: [PATCH 5/6] stictching things together --- src/core/database/database.module.ts | 4 ++-- src/core/database/schema/users/users.module.ts | 1 + src/interfaces/discord/discord.module.ts | 3 +++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/core/database/database.module.ts b/src/core/database/database.module.ts index bb92e51..166c269 100644 --- a/src/core/database/database.module.ts +++ b/src/core/database/database.module.ts @@ -11,7 +11,7 @@ import { DatabaseConnectionService } from './services/database-connection.servic // CatsModule, UsersModule, ], - providers: [DatabaseConnectionService], - exports: [DatabaseConnectionService], + providers: [], + exports: [UsersModule], }) export class DatabaseModule {} diff --git a/src/core/database/schema/users/users.module.ts b/src/core/database/schema/users/users.module.ts index ae7f0f2..c81faf8 100644 --- a/src/core/database/schema/users/users.module.ts +++ b/src/core/database/schema/users/users.module.ts @@ -8,5 +8,6 @@ import { User, UserSchema } from './user.schema'; MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]), ], providers: [UsersService], + exports: [UsersService], }) export class UsersModule {} diff --git a/src/interfaces/discord/discord.module.ts b/src/interfaces/discord/discord.module.ts index ff917e6..804a96b 100644 --- a/src/interfaces/discord/discord.module.ts +++ b/src/interfaces/discord/discord.module.ts @@ -7,6 +7,8 @@ import { DiscordReadyService } from './services/discord-ready.service'; import { DiscordThreadService } from './services/discord-thread.service'; import { GcpModule } from '../../core/gcp/gcp.module'; import { ChatbotModule } from '../../core/chatbot/chatbot.module'; +import { DatabaseModule } from '../../core/database/database.module'; +import { UsersModule } from '../../core/database/schema/users/users.module'; @Module({ imports: [ @@ -15,6 +17,7 @@ import { ChatbotModule } from '../../core/chatbot/chatbot.module'; useClass: DiscordConfigService, }), GcpModule, + UsersModule, ChatbotModule, ], providers: [ From 7b7cc64d64df4d2ab54710430a1fdd0c12d7d780 Mon Sep 17 00:00:00 2001 From: jonmatthis Date: Sat, 6 Jan 2024 18:59:45 -0500 Subject: [PATCH 6/6] logs user to database on bot creation :) --- .../ai/langchain/langchain-chain.service.ts | 111 ------------- src/core/ai/langchain/langchain.module.ts | 5 +- src/core/ai/langchain/langchain.service.ts | 152 +++++++++--------- .../chatbot.dto.ts => bot/bot.dto.ts} | 0 .../chatbot.module.ts => bot/bot.module.ts} | 8 +- .../chatbot.service.ts => bot/bot.service.ts} | 26 +-- .../database/schema/users/users.service.ts | 10 +- src/interfaces/discord/discord.module.ts | 5 +- .../services/discord-thread.service.ts | 20 +-- src/interfaces/slack/slack.module.ts | 4 +- src/interfaces/slack/slack.service.ts | 12 +- 11 files changed, 108 insertions(+), 245 deletions(-) delete mode 100644 src/core/ai/langchain/langchain-chain.service.ts rename src/core/{chatbot/chatbot.dto.ts => bot/bot.dto.ts} (100%) rename src/core/{chatbot/chatbot.module.ts => bot/bot.module.ts} (51%) rename src/core/{chatbot/chatbot.service.ts => bot/bot.service.ts} (78%) diff --git a/src/core/ai/langchain/langchain-chain.service.ts b/src/core/ai/langchain/langchain-chain.service.ts deleted file mode 100644 index d029750..0000000 --- a/src/core/ai/langchain/langchain-chain.service.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { OpenAI } from 'langchain/llms/openai'; -import { Inject, Injectable, Logger } from '@nestjs/common'; -import { getConnectionToken } from '@nestjs/mongoose'; -import { Connection } from 'mongoose'; -import { OpenaiSecretsService } from '../openai/openai-secrets.service'; -import { ChatPromptTemplate, MessagesPlaceholder } from 'langchain/prompts'; -import { BufferMemory } from 'langchain/memory'; -import { RunnableSequence } from 'langchain/runnables'; - -@Injectable() -export class LangchainChainService { - private _model: OpenAI; - - constructor( - @Inject(getConnectionToken()) private connection: Connection, - private readonly _openAiSecrets: OpenaiSecretsService, - private readonly _logger: Logger, - ) {} - private async _createModel(modelName?: string) { - if (!this._model) { - this._logger.log('Creating model...'); - this._model = new OpenAI({ - modelName: modelName || 'gpt-4-1106-preview', - openAIApiKey: await this._openAiSecrets.getOpenAIKey(), - }); - } - this._logger.log('Returning model: ' + this._model.modelName); - return this._model; - } - - public async createBufferMemoryChain(modelName?: string) { - const model = await this._createModel(modelName); - const prompt = ChatPromptTemplate.fromMessages([ - // TODO: Feed in a context prompt key - ['system', 'You are a helpful chatbot'], - new MessagesPlaceholder('history'), - ['human', '{input}'], - ]); - - const memory = new BufferMemory({ - returnMessages: true, - inputKey: 'input', - outputKey: 'output', - memoryKey: 'history', - }); - - const chain = RunnableSequence.from([ - { - input: (initialInput) => initialInput.input, - memory: () => memory.loadMemoryVariables({}), - }, - { - input: (previousOutput) => previousOutput.input, - history: (previousOutput) => previousOutput.memory.history, - }, - prompt, - model, - ]); - - // await this.demo(chain, memory); - - return { chain, memory }; - } - - private async demo(chain: RunnableSequence, memory?: BufferMemory) { - if (memory) { - console.log( - `Current Memory - await memory.loadMemoryVariables({}): ${JSON.stringify( - await memory.loadMemoryVariables({}), - )}`, - ); - } - - const inputs = { - input: 'Hey, Botto! Reply with an unexpected emoji', - }; - - console.log(`HumanInput:\n\n ${inputs.input}`); - - const response = await chain.invoke(inputs); - - console.log(`AI Response:\n\n ${response}`); - - await memory.saveContext(inputs, { - output: response, - }); - - const inputs2 = { - input: 'What emoji did you just say?', - }; - - console.log(`HumanInput:\n\n ${inputs2.input}`); - - const response2 = await chain.invoke(inputs2); - - console.log(`AI Response:\n\n ${response2}`); - - await memory.saveContext(inputs2, { - output: response2, - }); - - if (memory) { - console.log( - `Current Memory - await memory.loadMemoryVariables({}): ${JSON.stringify( - await memory.loadMemoryVariables({}), - )}`, - ); - } - memory.clear(); - } -} diff --git a/src/core/ai/langchain/langchain.module.ts b/src/core/ai/langchain/langchain.module.ts index 11456f3..d76c364 100644 --- a/src/core/ai/langchain/langchain.module.ts +++ b/src/core/ai/langchain/langchain.module.ts @@ -4,11 +4,10 @@ import { GcpModule } from '../../gcp/gcp.module'; import { LangchainService } from './langchain.service'; import { OpenaiModule } from '../openai/openai.module'; import { DatabaseModule } from '../../database/database.module'; -import { LangchainChainService } from './langchain-chain.service'; @Module({ imports: [GcpModule, OpenaiModule, DatabaseModule], - providers: [LangchainService, LangchainChainService, Logger], - exports: [LangchainService, LangchainChainService], + providers: [LangchainService, Logger], + exports: [LangchainService], }) export class LangchainModule {} diff --git a/src/core/ai/langchain/langchain.service.ts b/src/core/ai/langchain/langchain.service.ts index 593a5ea..79e53de 100644 --- a/src/core/ai/langchain/langchain.service.ts +++ b/src/core/ai/langchain/langchain.service.ts @@ -1,63 +1,21 @@ -import { Inject, Injectable, Logger } from '@nestjs/common'; import { OpenAI } from 'langchain/llms/openai'; -import { ChatPromptTemplate } from 'langchain/prompts'; -import { ConversationChain } from 'langchain/chains'; -import { BufferMemory } from 'langchain/memory'; -import { OpenaiSecretsService } from '../openai/openai-secrets.service'; -import { Connection } from 'mongoose'; +import { Inject, Injectable, Logger } from '@nestjs/common'; import { getConnectionToken } from '@nestjs/mongoose'; -import { MongoDBChatMessageHistory } from '@langchain/community/stores/message/mongodb'; -import * as mongoose from 'mongoose'; -import { DatabaseConnectionService } from '../../database/services/database-connection.service'; +import { Connection } from 'mongoose'; +import { OpenaiSecretsService } from '../openai/openai-secrets.service'; +import { ChatPromptTemplate, MessagesPlaceholder } from 'langchain/prompts'; +import { BufferMemory } from 'langchain/memory'; +import { RunnableSequence } from 'langchain/runnables'; @Injectable() export class LangchainService { private _model: OpenAI; - private readonly _chatHistoryCollectionName = 'chat-history'; constructor( @Inject(getConnectionToken()) private connection: Connection, private readonly _openAiSecrets: OpenaiSecretsService, - private readonly _databaseConnectionService: DatabaseConnectionService, private readonly _logger: Logger, ) {} - - /** - * Create a conversation chain that use MongoDBChatMessageHistory - * Based on example from here: https://js.langchain.com/docs/integrations/chat_memory/mongodb#usage - * @param modelName - */ - async createMongoMemoryChatChain(modelName?: string) { - const model = await this._createModel(modelName); - const prompt = await this._createPrompt(); - const memory = await this._createMemory(); - this._logger.log(`Returning chain with model ${this._model.modelName}`); - return new ConversationChain({ - llm: model, - prompt: prompt, - memory: memory, - }); - } - - /** - * Creates a chain that may be invoked later. - * @param modelName - */ - async createChain(modelName?: string) { - const model = await this._createModel(modelName); - const promptTemplate = await this._createPrompt(); - return promptTemplate.pipe(model); - } - - /** - * Creates a model instance. - * - * The model instance is generated as a singleton. Subsequent calls reuse previously created model. - * - * @param {string} [modelName] - The name of the model. If not provided, the default model name 'gpt-4-1106-preview' will be used. - * @private - * @returns {Promise} - A Promise that resolves to the created model instance. - */ private async _createModel(modelName?: string) { if (!this._model) { this._logger.log('Creating model...'); @@ -70,34 +28,84 @@ export class LangchainService { return this._model; } - private async _createPrompt() { - const promptStructure = [ - ['system', 'You were having a conversation with a human about {topic}'], - ['human', '{text}'], - ]; + public async createBufferMemoryChain(modelName?: string) { + const model = await this._createModel(modelName); + const prompt = ChatPromptTemplate.fromMessages([ + // TODO: Feed in a context prompt key + ['system', 'You are a helpful chatbot'], + new MessagesPlaceholder('history'), + ['human', '{input}'], + ]); + + const memory = new BufferMemory({ + returnMessages: true, + inputKey: 'input', + outputKey: 'output', + memoryKey: 'history', + }); + + const chain = RunnableSequence.from([ + { + input: (initialInput) => initialInput.input, + memory: () => memory.loadMemoryVariables({}), + }, + { + input: (previousOutput) => previousOutput.input, + history: (previousOutput) => previousOutput.memory.history, + }, + prompt, + model, + ]); - // @ts-ignore - const template = ChatPromptTemplate.fromMessages(promptStructure); - this._logger.log('Creating prompt...', promptStructure); - return template; + // await this.demo(chain, memory); + + return { chain, memory }; } - private async _createMemory() { - this._logger.log(`Creating MongoDB connected chat-history for chatbot...`); + public async demo(chain: RunnableSequence, memory?: BufferMemory) { + if (memory) { + console.log( + `Current Memory - await memory.loadMemoryVariables({}): ${JSON.stringify( + await memory.loadMemoryVariables({}), + )}`, + ); + } - const sessionId = new mongoose.Types.ObjectId().toString(); - const connection = await this._databaseConnectionService.getConnection(); - const collection = connection.db.collection( - this._chatHistoryCollectionName, - ); - const memory = new BufferMemory({ - chatHistory: new MongoDBChatMessageHistory({ - // @ts-ignore - collection: collection, - sessionId: sessionId, - }), + const inputs = { + input: 'Hey, Botto! Reply with an unexpected emoji', + }; + + console.log(`HumanInput:\n\n ${inputs.input}`); + + const response = await chain.invoke(inputs); + + console.log(`AI Response:\n\n ${response}`); + + await memory.saveContext(inputs, { + output: response, }); - this._logger.log(`Chat-history created!`); - return memory; + + const inputs2 = { + input: 'What emoji did you just say?', + }; + + console.log(`HumanInput:\n\n ${inputs2.input}`); + + const response2 = await chain.invoke(inputs2); + + console.log(`AI Response:\n\n ${response2}`); + + await memory.saveContext(inputs2, { + output: response2, + }); + + if (memory) { + console.log( + `Current Memory - await memory.loadMemoryVariables({}): ${JSON.stringify( + await memory.loadMemoryVariables({}), + )}`, + ); + } + memory.clear(); } } diff --git a/src/core/chatbot/chatbot.dto.ts b/src/core/bot/bot.dto.ts similarity index 100% rename from src/core/chatbot/chatbot.dto.ts rename to src/core/bot/bot.dto.ts diff --git a/src/core/chatbot/chatbot.module.ts b/src/core/bot/bot.module.ts similarity index 51% rename from src/core/chatbot/chatbot.module.ts rename to src/core/bot/bot.module.ts index e297d15..c7e931c 100644 --- a/src/core/chatbot/chatbot.module.ts +++ b/src/core/bot/bot.module.ts @@ -1,10 +1,10 @@ import { Logger, Module } from '@nestjs/common'; import { LangchainModule } from '../ai/langchain/langchain.module'; -import { ChatbotService } from './chatbot.service'; +import { BotService } from './bot.service'; @Module({ imports: [LangchainModule], - providers: [ChatbotService, Logger], - exports: [ChatbotService], + providers: [BotService, Logger], + exports: [BotService], }) -export class ChatbotModule {} +export class BotModule {} diff --git a/src/core/chatbot/chatbot.service.ts b/src/core/bot/bot.service.ts similarity index 78% rename from src/core/chatbot/chatbot.service.ts rename to src/core/bot/bot.service.ts index fd75dc8..3b8962a 100644 --- a/src/core/chatbot/chatbot.service.ts +++ b/src/core/bot/bot.service.ts @@ -1,7 +1,6 @@ -import { Chatbot } from './chatbot.dto'; +import { Chatbot } from './bot.dto'; import { LangchainService } from '../ai/langchain/langchain.service'; import { Injectable, Logger } from '@nestjs/common'; -import { LangchainChainService } from '../ai/langchain/langchain-chain.service'; class StreamResponseOptions { /** @@ -11,37 +10,20 @@ class StreamResponseOptions { } @Injectable() -export class ChatbotService { +export class BotService { private _chatbots: Map = new Map(); constructor( private readonly _logger: Logger, - private readonly _langchainChainService: LangchainChainService, private readonly _langchainService: LangchainService, ) {} - public async createChatbotOld(chatbotId: string, modelName?: string) { - this._logger.log( - `Creating chatbot with id: ${chatbotId} and model: ${modelName}`, - ); - const chain = - await this._langchainService.createMongoMemoryChatChain(modelName); - - // @ts-ignore - const chatbot = { chain } as Chatbot; - this._chatbots.set(chatbotId, chatbot); - this._logger.log(`Chatbot with id: ${chatbotId} created successfully`); - - return chatbot; - } - - public async createChatbot(chatbotId: string, modelName?: string) { + public async createBot(chatbotId: string, modelName?: string) { this._logger.log( `Creating chatbot with id: ${chatbotId} and language model (llm): ${modelName}`, ); const { chain, memory } = - await this._langchainChainService.createBufferMemoryChain(modelName); + await this._langchainService.createBufferMemoryChain(modelName); - // @ts-ignore const chatbot = { chain, memory } as Chatbot; this._chatbots.set(chatbotId, chatbot); this._logger.log(`Chatbot with id: ${chatbotId} created successfully`); diff --git a/src/core/database/schema/users/users.service.ts b/src/core/database/schema/users/users.service.ts index 56da2bf..90bd524 100644 --- a/src/core/database/schema/users/users.service.ts +++ b/src/core/database/schema/users/users.service.ts @@ -1,9 +1,4 @@ -import { - HttpException, - HttpStatus, - Injectable, - NotFoundException, -} from '@nestjs/common'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; import { User } from './user.schema'; @@ -31,9 +26,6 @@ export class UsersService { user = await this.userModel.findOne({ slackID: userDto.slackID }).exec(); } - if (!user) { - throw new NotFoundException(`User not found`); - } return user; } diff --git a/src/interfaces/discord/discord.module.ts b/src/interfaces/discord/discord.module.ts index 804a96b..c7f182b 100644 --- a/src/interfaces/discord/discord.module.ts +++ b/src/interfaces/discord/discord.module.ts @@ -6,9 +6,8 @@ import { DiscordConfigService } from './services/discord-config.service'; import { DiscordReadyService } from './services/discord-ready.service'; import { DiscordThreadService } from './services/discord-thread.service'; import { GcpModule } from '../../core/gcp/gcp.module'; -import { ChatbotModule } from '../../core/chatbot/chatbot.module'; -import { DatabaseModule } from '../../core/database/database.module'; import { UsersModule } from '../../core/database/schema/users/users.module'; +import { BotModule } from '../../core/bot/bot.module'; @Module({ imports: [ @@ -18,7 +17,7 @@ import { UsersModule } from '../../core/database/schema/users/users.module'; }), GcpModule, UsersModule, - ChatbotModule, + BotModule, ], providers: [ DiscordPingService, diff --git a/src/interfaces/discord/services/discord-thread.service.ts b/src/interfaces/discord/services/discord-thread.service.ts index b1e5b31..61b994a 100644 --- a/src/interfaces/discord/services/discord-thread.service.ts +++ b/src/interfaces/discord/services/discord-thread.service.ts @@ -8,9 +8,7 @@ import { TextChannel, ThreadChannel, } from 'discord.js'; -import { ChatbotService } from '../../../core/chatbot/chatbot.service'; -import { InjectModel } from '@nestjs/mongoose'; -import { User } from '../../../core/database/schema/users/user.schema'; +import { BotService } from '../../../core/bot/bot.service'; import { UsersService } from '../../../core/database/schema/users/users.service'; @Injectable() @@ -18,7 +16,7 @@ export class DiscordThreadService implements OnModuleDestroy { constructor( private readonly _usersService: UsersService, private readonly _logger: Logger, - private readonly _chatbotService: ChatbotService, + private readonly _botService: BotService, private readonly _client: Client, ) {} @@ -52,8 +50,8 @@ export class DiscordThreadService implements OnModuleDestroy { discordId: interaction.user.id, }); - // await this._chatbotService.createChatbot(thread.id); - await this._chatbotService.createChatbot(thread.id); + // await this._botService.createChatbot(thread.id); + await this._botService.createBot(thread.id); this._beginWatchingIncomingMessages(interaction, channel, thread); await this._sendInitialReply(interaction, channel, thread, text); @@ -104,13 +102,9 @@ export class DiscordThreadService implements OnModuleDestroy { inputText: string, message: Message, ) { - const tokenStream = this._chatbotService.streamResponse( - thread.id, - inputText, - { - topic: channel.topic, - }, - ); + const tokenStream = this._botService.streamResponse(thread.id, inputText, { + topic: channel.topic, + }); thread.sendTyping(); let initialReply: Message = undefined; diff --git a/src/interfaces/slack/slack.module.ts b/src/interfaces/slack/slack.module.ts index 7e069a5..667faf8 100644 --- a/src/interfaces/slack/slack.module.ts +++ b/src/interfaces/slack/slack.module.ts @@ -3,11 +3,11 @@ import { SlackService } from './slack.service'; import { SlackCommandMethodDiscovery } from './decorators/discovery'; import { DiscoveryModule } from '@golevelup/nestjs-discovery'; import { SlackBoltModule } from './bolt/slack-bolt.module'; -import { ChatbotModule } from '../../core/chatbot/chatbot.module'; import { GcpModule } from '../../core/gcp/gcp.module'; +import { BotModule } from '../../core/bot/bot.module'; @Module({ - imports: [SlackBoltModule, DiscoveryModule, ChatbotModule, GcpModule], + imports: [SlackBoltModule, DiscoveryModule, BotModule, GcpModule], providers: [SlackService, SlackCommandMethodDiscovery], }) export class SlackModule implements OnModuleInit { diff --git a/src/interfaces/slack/slack.service.ts b/src/interfaces/slack/slack.service.ts index 9d03b92..30d8043 100644 --- a/src/interfaces/slack/slack.service.ts +++ b/src/interfaces/slack/slack.service.ts @@ -8,12 +8,12 @@ import { SlackEventMiddlewareArgs, } from '@slack/bolt'; import { SlackMessage } from './decorators/slack-message.decorator'; -import { ChatbotService } from '../../core/chatbot/chatbot.service'; +import { BotService } from '../../core/bot/bot.service'; @Injectable() export class SlackService { constructor( - private readonly _chatbotCore: ChatbotService, + private readonly _botService: BotService, private readonly _app: App, ) {} @@ -30,8 +30,8 @@ export class SlackService { ) { const id = message.ts; const { text } = message as GenericMessageEvent; - await this._chatbotCore.createChatbot(id); - const stream = this._chatbotCore.streamResponse(id, text, { + await this._botService.createBot(id); + const stream = this._botService.streamResponse(id, text, { topic: '', }); const response = await say({ text: 'incoming', thread_ts: message.ts }); @@ -61,9 +61,9 @@ export class SlackService { response_type: 'in_channel', }); const id = v4(); - await this._chatbotCore.createChatbot(id); + await this._botService.createBot(id); - const response = await this._chatbotCore.generateAiResponse( + const response = await this._botService.generateAiResponse( id, command.text, { topic: '' },