├── src ├── modules │ ├── auth │ │ ├── constants.ts │ │ ├── decorators │ │ │ └── public.decorator.ts │ │ ├── local-auth.guard.ts │ │ ├── auth.service.spec.ts │ │ ├── auth.controller.spec.ts │ │ ├── jwt.strategy.ts │ │ ├── auth.guard.ts │ │ ├── local.strategy.ts │ │ ├── auth.module.ts │ │ └── auth.controller.ts │ ├── qas │ │ ├── qas.module.ts │ │ ├── qas.service.ts │ │ ├── qas.service.spec.ts │ │ ├── qas.controller.spec.ts │ │ └── qas.controller.ts │ ├── magic │ │ ├── magic.module.ts │ │ ├── magic.service.ts │ │ ├── magic.service.spec.ts │ │ ├── magic.controller.spec.ts │ │ └── magic.controller.ts │ ├── copilot │ │ ├── copilot.module.ts │ │ ├── copilot.service.ts │ │ ├── copilot.service.spec.ts │ │ └── copilot.controller.spec.ts │ ├── groups │ │ ├── groups.module.ts │ │ ├── groups.service.ts │ │ ├── groups.service.spec.ts │ │ ├── groups.controller.spec.ts │ │ └── groups.controller.ts │ ├── medias │ │ ├── medias.module.ts │ │ ├── medias.service.ts │ │ ├── medias.service.spec.ts │ │ ├── medias.controller.spec.ts │ │ └── medias.controller.ts │ ├── notices │ │ ├── notices.module.ts │ │ ├── notices.service.spec.ts │ │ ├── notices.controller.spec.ts │ │ ├── notices.service.ts │ │ └── notices.controller.ts │ ├── orders │ │ ├── orders.module.ts │ │ ├── orders.service.ts │ │ ├── orders.service.spec.ts │ │ ├── orders.controller.spec.ts │ │ └── orders.controller.ts │ ├── chatbots │ │ ├── chatbots.module.ts │ │ ├── chatbots.service.ts │ │ ├── chatbots.service.spec.ts │ │ └── chatbots.controller.spec.ts │ ├── keywords │ │ ├── keywords.module.ts │ │ ├── keywords.service.ts │ │ ├── keywords.service.spec.ts │ │ ├── keywords.controller.spec.ts │ │ └── keywords.controller.ts │ ├── welcomes │ │ ├── welcomes.module.ts │ │ ├── welcomes.service.ts │ │ ├── welcomes.service.spec.ts │ │ ├── welcomes.controller.spec.ts │ │ └── welcomes.controller.ts │ ├── contacts │ │ ├── contacts.service.ts │ │ ├── contacts.module.ts │ │ ├── contacts.service.spec.ts │ │ └── contacts.controller.spec.ts │ ├── statistics │ │ ├── statistics.module.ts │ │ ├── statistics.service.ts │ │ ├── statistics.service.spec.ts │ │ ├── statistics.controller.spec.ts │ │ └── statistics.controller.ts │ ├── whitelists │ │ ├── whitelists.module.ts │ │ ├── whitelists.service.ts │ │ ├── whitelists.service.spec.ts │ │ └── whitelists.controller.spec.ts │ ├── carpoolings │ │ ├── carpoolings.module.ts │ │ ├── carpoolings.service.ts │ │ ├── carpoolings.service.spec.ts │ │ ├── carpoolings.controller.spec.ts │ │ └── carpoolings.controller.ts │ ├── emoticons │ │ ├── emoticons.service.ts │ │ ├── emoticon.module.ts │ │ ├── emoticon.controller.ts │ │ ├── emoticons.service.spec.ts │ │ └── emoticon.controller.spec.ts │ ├── groupnotices │ │ ├── groupnotices.module.ts │ │ ├── groupnotices.service.ts │ │ ├── groupnotices.service.spec.ts │ │ ├── groupnotices.controller.spec.ts │ │ └── groupnotices.controller.ts │ ├── chats │ │ ├── chats.module.ts │ │ ├── chats.service.spec.ts │ │ ├── chats.controller.spec.ts │ │ └── chats.service.ts │ ├── rooms │ │ ├── rooms.module.ts │ │ ├── rooms.service.ts │ │ ├── rooms.service.spec.ts │ │ └── rooms.controller.spec.ts │ ├── upload │ │ ├── upload.module.ts │ │ ├── upload.service.ts │ │ ├── upload.service.spec.ts │ │ └── upload.controller.spec.ts │ └── users │ │ ├── users.module.ts │ │ ├── users.service.ts │ │ ├── users.service.spec.ts │ │ └── users.controller.spec.ts ├── db │ ├── vikaModel │ │ ├── ChatBot │ │ │ ├── records.json │ │ │ ├── db.ts │ │ │ ├── fields.json │ │ │ └── mod.ts │ │ ├── Contact │ │ │ ├── records.json │ │ │ ├── db.ts │ │ │ ├── mod.ts │ │ │ └── fields.json │ │ ├── Message │ │ │ ├── records.json │ │ │ ├── fields.json │ │ │ ├── db.ts │ │ │ └── mod.ts │ │ ├── Order │ │ │ ├── records.json │ │ │ ├── fields.json │ │ │ ├── db.ts │ │ │ └── mod.ts │ │ ├── Room │ │ │ ├── records.ts │ │ │ ├── mod.ts │ │ │ ├── db.ts │ │ │ └── fields.ts │ │ ├── Group │ │ │ ├── records.ts │ │ │ ├── mod.ts │ │ │ ├── db.ts │ │ │ └── fields.ts │ │ ├── Carpooling │ │ │ ├── records.ts │ │ │ ├── mod.ts │ │ │ ├── db.ts │ │ │ └── fields.ts │ │ ├── GroupNotice │ │ │ ├── records.json │ │ │ ├── fields.json │ │ │ └── db.ts │ │ ├── Notice │ │ │ ├── records.json │ │ │ ├── db.ts │ │ │ └── fields.json │ │ ├── Env │ │ │ ├── fields.json │ │ │ └── db.ts │ │ ├── Stock │ │ │ ├── mod.ts │ │ │ ├── records.ts │ │ │ ├── db.ts │ │ │ └── fields.ts │ │ ├── Qa │ │ │ ├── mod.ts │ │ │ ├── types.ts │ │ │ ├── db.ts │ │ │ ├── typeGenerator.ts │ │ │ └── fields.ts │ │ ├── Media │ │ │ ├── mod.ts │ │ │ ├── records.ts │ │ │ ├── db.ts │ │ │ └── fields.ts │ │ ├── Welcome │ │ │ ├── mod.ts │ │ │ ├── records.ts │ │ │ ├── db.ts │ │ │ └── fields.ts │ │ ├── WhiteList │ │ │ ├── mod.ts │ │ │ ├── records.ts │ │ │ ├── db.ts │ │ │ └── fields.ts │ │ ├── Statistic │ │ │ ├── mod.ts │ │ │ ├── records.ts │ │ │ └── db.ts │ │ ├── Keyword │ │ │ ├── fields.json │ │ │ ├── db.ts │ │ │ └── records.json │ │ ├── Model.ts │ │ ├── ChatBotUser │ │ │ ├── db.ts │ │ │ └── mod.ts │ │ ├── index.ts │ │ ├── actionBar.ts │ │ └── acitonFields.ts │ ├── store.ts │ └── mod.ts ├── app.service.ts ├── types │ └── index.ts ├── main.ts ├── app.controller.spec.ts ├── utils │ ├── crypto-use-crypto-js.ts │ └── utils.ts ├── mqtt-broker.ts └── app.controller.ts ├── .prettierrc ├── tsconfig.build.json ├── temp └── 44f743db3d514657bd0648de9ca5869e.png ├── .env.example ├── nest-cli.json ├── test ├── jest-e2e.json ├── app.e2e-spec.ts └── vika-orm-test.ts ├── .gitignore ├── Dockerfile ├── tsconfig.json ├── .eslintrc.js ├── scripts └── createModule.ts ├── README.md ├── .github └── workflows │ └── docker-image.yml ├── index.html ├── nginx.conf └── package.json /src/modules/auth/constants.ts: -------------------------------------------------------------------------------- 1 | export const jwtConstants = { 2 | secret: 'secretKey', 3 | }; 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "endOfLine": "auto", 4 | "trailingComma": "all" 5 | } -------------------------------------------------------------------------------- /src/modules/qas/qas.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | @Module({}) 4 | export class QasModule {} 5 | -------------------------------------------------------------------------------- /src/modules/magic/magic.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | @Module({}) 4 | export class MagicModule {} 5 | -------------------------------------------------------------------------------- /src/modules/qas/qas.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class QasService {} 5 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /src/modules/copilot/copilot.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | @Module({}) 4 | export class CopilotModule {} 5 | -------------------------------------------------------------------------------- /src/modules/groups/groups.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | @Module({}) 4 | export class GroupsModule {} 5 | -------------------------------------------------------------------------------- /src/modules/medias/medias.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | @Module({}) 4 | export class MediasModule {} 5 | -------------------------------------------------------------------------------- /src/modules/notices/notices.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | @Module({}) 4 | export class NoticesModule {} 5 | -------------------------------------------------------------------------------- /src/modules/orders/orders.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | @Module({}) 4 | export class OrdersModule {} 5 | -------------------------------------------------------------------------------- /src/db/vikaModel/ChatBot/records.json: -------------------------------------------------------------------------------- 1 | {"code":200,"success":true,"data":{"total":0,"records":[],"pageNum":1,"pageSize":0},"message":"SUCCESS"} -------------------------------------------------------------------------------- /src/db/vikaModel/Contact/records.json: -------------------------------------------------------------------------------- 1 | {"code":200,"success":true,"data":{"total":0,"records":[],"pageNum":1,"pageSize":0},"message":"SUCCESS"} -------------------------------------------------------------------------------- /src/db/vikaModel/Message/records.json: -------------------------------------------------------------------------------- 1 | {"code":200,"success":true,"data":{"total":0,"records":[],"pageNum":1,"pageSize":0},"message":"SUCCESS"} -------------------------------------------------------------------------------- /src/db/vikaModel/Order/records.json: -------------------------------------------------------------------------------- 1 | {"code":200,"success":true,"data":{"total":0,"records":[],"pageNum":1,"pageSize":0},"message":"SUCCESS"} -------------------------------------------------------------------------------- /src/modules/chatbots/chatbots.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | @Module({}) 4 | export class ChatbotsModule {} 5 | -------------------------------------------------------------------------------- /src/modules/groups/groups.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class GroupsService {} 5 | -------------------------------------------------------------------------------- /src/modules/keywords/keywords.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | @Module({}) 4 | export class KeywordsModule {} 5 | -------------------------------------------------------------------------------- /src/modules/magic/magic.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class MagicService {} 5 | -------------------------------------------------------------------------------- /src/modules/medias/medias.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class MediasService {} 5 | -------------------------------------------------------------------------------- /src/modules/orders/orders.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class OrdersService {} 5 | -------------------------------------------------------------------------------- /src/modules/welcomes/welcomes.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | @Module({}) 4 | export class WelcomesModule {} 5 | -------------------------------------------------------------------------------- /temp/44f743db3d514657bd0648de9ca5869e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chatflow-admin/HEAD/temp/44f743db3d514657bd0648de9ca5869e.png -------------------------------------------------------------------------------- /src/modules/contacts/contacts.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class ContactsService {} 5 | -------------------------------------------------------------------------------- /src/modules/copilot/copilot.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class CopilotService {} 5 | -------------------------------------------------------------------------------- /src/modules/keywords/keywords.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class KeywordsService {} 5 | -------------------------------------------------------------------------------- /src/modules/statistics/statistics.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | @Module({}) 4 | export class StatisticsModule {} 5 | -------------------------------------------------------------------------------- /src/modules/welcomes/welcomes.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class WelcomesService {} 5 | -------------------------------------------------------------------------------- /src/modules/whitelists/whitelists.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | @Module({}) 4 | export class WhitelistsModule {} 5 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | accessKeyId='' 2 | secretAccessKey='' 3 | region='cn-north-3' 4 | endpoint='oss.cn-north-3.inspurcloudoss.com' 5 | bucketName='poem-poster' -------------------------------------------------------------------------------- /src/modules/carpoolings/carpoolings.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | @Module({}) 4 | export class CarpoolingsModule {} 5 | -------------------------------------------------------------------------------- /src/modules/emoticons/emoticons.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class EmoticonsService {} 5 | -------------------------------------------------------------------------------- /src/modules/groupnotices/groupnotices.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | @Module({}) 4 | export class GroupnoticesModule {} 5 | -------------------------------------------------------------------------------- /src/modules/carpoolings/carpoolings.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class CarpoolingsService {} 5 | -------------------------------------------------------------------------------- /src/modules/groupnotices/groupnotices.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | @Injectable() 3 | export class GroupnoticesService {} 4 | -------------------------------------------------------------------------------- /src/modules/statistics/statistics.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class StatisticsService {} 5 | -------------------------------------------------------------------------------- /src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/db/vikaModel/Room/records.ts: -------------------------------------------------------------------------------- 1 | export const defaultRecords: any = { 2 | code: 200, 3 | success: true, 4 | data: { total: 0, records: [], pageNum: 1, pageSize: 0 }, 5 | message: 'SUCCESS', 6 | }; 7 | -------------------------------------------------------------------------------- /src/db/vikaModel/Group/records.ts: -------------------------------------------------------------------------------- 1 | export const defaultRecords: any = { 2 | code: 200, 3 | success: true, 4 | data: { total: 0, records: [], pageNum: 1, pageSize: 0 }, 5 | message: 'SUCCESS', 6 | }; 7 | -------------------------------------------------------------------------------- /src/modules/auth/decorators/public.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | 3 | export const IS_PUBLIC_KEY = 'isPublic'; 4 | export const Public = () => SetMetadata(IS_PUBLIC_KEY, true); 5 | -------------------------------------------------------------------------------- /src/modules/chatbots/chatbots.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class ChatbotsService {} 5 | 6 | @Injectable() 7 | export class ChatbotUserService {} 8 | -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/modules/auth/local-auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | 4 | @Injectable() 5 | export class LocalAuthGuard extends AuthGuard('local') {} 6 | -------------------------------------------------------------------------------- /test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/db/vikaModel/Carpooling/records.ts: -------------------------------------------------------------------------------- 1 | export const defaultRecords: any = { 2 | code: 200, 3 | success: true, 4 | message: 'Request successful', 5 | data: { 6 | total: 1, 7 | pageNum: 1, 8 | pageSize: 1, 9 | records: [], 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /src/db/vikaModel/GroupNotice/records.json: -------------------------------------------------------------------------------- 1 | {"code":200,"success":true,"data":{"total":1,"records":[{"recordId":"rec5rHxqNeWNw","createdAt":1693750559000,"updatedAt":1694055083000,"fields":{"状态|state":"待发送|waiting","类型|type":"好友|contact","发送时间|pubTime":1693750559067}}],"pageNum":1,"pageSize":1},"message":"SUCCESS"} -------------------------------------------------------------------------------- /src/modules/chats/chats.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ChatsController } from './chats.controller'; 3 | import { ChatsService } from './chats.service'; 4 | 5 | @Module({ 6 | controllers: [ChatsController], 7 | providers: [ChatsService], 8 | }) 9 | export class ChatsModule {} 10 | -------------------------------------------------------------------------------- /src/modules/rooms/rooms.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { RoomsController } from './rooms.controller'; 3 | import { RoomsService } from './rooms.service'; 4 | 5 | @Module({ 6 | controllers: [RoomsController], 7 | providers: [RoomsService], 8 | }) 9 | export class RoomsModule {} 10 | -------------------------------------------------------------------------------- /src/modules/upload/upload.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { UploadController } from './upload.controller'; 3 | import { UploadService } from './upload.service'; 4 | 5 | @Module({ 6 | controllers: [UploadController], 7 | providers: [UploadService], 8 | }) 9 | export class UploadModule {} 10 | -------------------------------------------------------------------------------- /src/modules/whitelists/whitelists.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { ContactWhiteList, RoomWhiteList } from '../../types/index'; 3 | @Injectable() 4 | export class WhitelistsService {} 5 | export type WhiteList = { 6 | contactWhiteList: ContactWhiteList; 7 | roomWhiteList: RoomWhiteList; 8 | }; 9 | -------------------------------------------------------------------------------- /src/db/vikaModel/Notice/records.json: -------------------------------------------------------------------------------- 1 | {"code":200,"success":true,"data":{"total":1,"records":[{"recordId":"recpHWSXRDKlT","createdAt":1693750783000,"updatedAt":1694055402000,"fields":{"内容|desc":"测试5分钟提醒","时间|time":1692597600000,"周期|cycle":"每5分钟","启用状态|state":"开启","昵称/群名称|name":"luyuchao","通知目标类型|type":"好友"}}],"pageNum":1,"pageSize":1},"message":"SUCCESS"} -------------------------------------------------------------------------------- /src/modules/contacts/contacts.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ContactsController } from './contacts.controller'; 3 | import { ContactsService } from './contacts.service'; 4 | 5 | @Module({ 6 | controllers: [ContactsController], 7 | providers: [ContactsService], 8 | }) 9 | export class ContactsModule {} 10 | -------------------------------------------------------------------------------- /src/modules/emoticons/emoticon.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { EmoticonController } from './emoticon.controller'; 3 | import { EmoticonsService } from './emoticons.service'; 4 | 5 | @Module({ 6 | controllers: [EmoticonController], 7 | providers: [EmoticonsService], 8 | }) 9 | export class EmoticonModule {} 10 | -------------------------------------------------------------------------------- /src/modules/users/users.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { UsersService } from './users.service'; 3 | import { UsersController } from './users.controller'; 4 | 5 | @Module({ 6 | controllers: [UsersController], 7 | providers: [UsersService], 8 | exports: [UsersService], 9 | }) 10 | export class UsersModule {} 11 | -------------------------------------------------------------------------------- /src/db/vikaModel/Group/mod.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sort-keys */ 2 | 3 | import type { 4 | Sheet, 5 | // Field, 6 | } from '../Model'; 7 | import { vikaFields } from './fields.js'; 8 | import { defaultRecords } from './records.js'; 9 | 10 | export const sheet: Sheet = { 11 | fields: vikaFields.data.fields, 12 | name: '分组|Group', 13 | defaultRecords: defaultRecords.data.records, 14 | }; 15 | -------------------------------------------------------------------------------- /src/db/vikaModel/Room/mod.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sort-keys */ 2 | 3 | import type { 4 | Sheet, 5 | // Field, 6 | } from '../Model'; 7 | import { vikaFields } from './fields.js'; 8 | import { defaultRecords } from './records.js'; 9 | 10 | export const roomSheet: Sheet = { 11 | fields: vikaFields.data.fields, 12 | name: '群列表|Room', 13 | defaultRecords: defaultRecords.data.records, 14 | }; 15 | -------------------------------------------------------------------------------- /src/modules/emoticons/emoticon.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | 3 | @Controller('/api/v1/emoticon') 4 | export class EmoticonController { 5 | @Get('list') 6 | getEmoticon() { 7 | return { 8 | code: 200, 9 | message: 'success', 10 | data: { 11 | collect_emoticon: [], 12 | sys_emoticon: [], 13 | }, 14 | }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/db/vikaModel/Env/fields.json: -------------------------------------------------------------------------------- 1 | {"code":200,"success":true,"data":{"fields":[{"id":"fldNTOtH9LDvN","name":"配置项|name","type":"SingleText","property":{},"editable":true,"isPrimary":true},{"id":"fld4hEZqDpWck","name":"标识|key","type":"SingleText","property":{},"editable":true},{"id":"fldN7PzpNtC2V","name":"值|value","type":"Text","editable":true},{"id":"fldTmdV4rFU0v","name":"说明|desc","type":"Text","editable":true}]},"message":"SUCCESS"} -------------------------------------------------------------------------------- /src/modules/users/users.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { Store } from '../../db/store'; 3 | 4 | export type User = any; 5 | 6 | @Injectable() 7 | export class UsersService { 8 | async findUser(username: string): Promise { 9 | return Store.users.find( 10 | (user) => user.username === username || user.spaceName === username, 11 | ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/db/vikaModel/Stock/mod.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sort-keys */ 2 | 3 | /* eslint-disable sort-keys */ 4 | 5 | import type { 6 | Sheet, 7 | // Field, 8 | } from '../Model'; 9 | import { vikaFields } from './fields.js'; 10 | import { defaultRecords } from './records.js'; 11 | 12 | export const stockSheet: Sheet = { 13 | fields: vikaFields.data.fields, 14 | name: '股票提醒|Stock', 15 | defaultRecords: defaultRecords.data.records, 16 | }; 17 | -------------------------------------------------------------------------------- /src/modules/upload/upload.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { S3 } from 'aws-sdk'; 3 | 4 | @Injectable() 5 | export class UploadService { 6 | static async uploadToS3( 7 | s3: S3, 8 | bucketName: string, 9 | file: any, 10 | file_name: string, 11 | ): Promise { 12 | return s3 13 | .upload({ 14 | Bucket: bucketName, 15 | Key: file_name, 16 | Body: file.buffer, 17 | }) 18 | .promise(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/modules/rooms/rooms.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { v4 } from 'uuid'; 3 | 4 | @Injectable() 5 | export class RoomsService { 6 | static formatMsgToWechaty(roomid: string) { 7 | const msg = { 8 | reqId: v4(), 9 | method: 'thing.command.invoke', 10 | version: '1.0', 11 | timestamp: new Date().getTime(), 12 | name: 'memberAllGet', 13 | params: { 14 | roomid: roomid, 15 | }, 16 | }; 17 | return JSON.stringify(msg); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/modules/qas/qas.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { QasService } from './qas.service'; 3 | 4 | describe('QasService', () => { 5 | let service: QasService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [QasService], 10 | }).compile(); 11 | 12 | service = module.get(QasService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/auth/auth.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AuthService } from './auth.service'; 3 | 4 | describe('AuthService', () => { 5 | let service: AuthService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [AuthService], 10 | }).compile(); 11 | 12 | service = module.get(AuthService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/chats/chats.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { ChatsService } from './chats.service'; 3 | 4 | describe('ChatsService', () => { 5 | let service: ChatsService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [ChatsService], 10 | }).compile(); 11 | 12 | service = module.get(ChatsService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/magic/magic.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { MagicService } from './magic.service'; 3 | 4 | describe('MagicService', () => { 5 | let service: MagicService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [MagicService], 10 | }).compile(); 11 | 12 | service = module.get(MagicService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/rooms/rooms.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { RoomsService } from './rooms.service'; 3 | 4 | describe('RoomsService', () => { 5 | let service: RoomsService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [RoomsService], 10 | }).compile(); 11 | 12 | service = module.get(RoomsService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/users/users.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { UsersService } from './users.service'; 3 | 4 | describe('UsersService', () => { 5 | let service: UsersService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [UsersService], 10 | }).compile(); 11 | 12 | service = module.get(UsersService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/groups/groups.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { GroupsService } from './groups.service'; 3 | 4 | describe('GroupsService', () => { 5 | let service: GroupsService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [GroupsService], 10 | }).compile(); 11 | 12 | service = module.get(GroupsService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/medias/medias.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { MediasService } from './medias.service'; 3 | 4 | describe('MediasService', () => { 5 | let service: MediasService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [MediasService], 10 | }).compile(); 11 | 12 | service = module.get(MediasService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/orders/orders.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { OrdersService } from './orders.service'; 3 | 4 | describe('OrdersService', () => { 5 | let service: OrdersService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [OrdersService], 10 | }).compile(); 11 | 12 | service = module.get(OrdersService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/upload/upload.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { UploadService } from './upload.service'; 3 | 4 | describe('UploadService', () => { 5 | let service: UploadService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [UploadService], 10 | }).compile(); 11 | 12 | service = module.get(UploadService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export interface RoomWhiteList { 2 | qa: BusinessRoom[]; 3 | msg: BusinessRoom[]; 4 | act: BusinessRoom[]; 5 | gpt: BusinessRoom[]; 6 | } 7 | 8 | export interface ContactWhiteList { 9 | qa: BusinessUser[]; 10 | msg: BusinessUser[]; 11 | act: BusinessUser[]; 12 | gpt: BusinessUser[]; 13 | } 14 | 15 | export type BusinessRoom = { 16 | id?: string; 17 | luckyDog?: string; 18 | memberAlias?: string; 19 | topic: string; 20 | }; 21 | 22 | export type BusinessUser = { 23 | alias?: string; 24 | id?: string; 25 | name: string; 26 | }; 27 | -------------------------------------------------------------------------------- /src/modules/copilot/copilot.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { CopilotService } from './copilot.service'; 3 | 4 | describe('CopilotService', () => { 5 | let service: CopilotService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [CopilotService], 10 | }).compile(); 11 | 12 | service = module.get(CopilotService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/notices/notices.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { NoticesService } from './notices.service'; 3 | 4 | describe('NoticesService', () => { 5 | let service: NoticesService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [NoticesService], 10 | }).compile(); 11 | 12 | service = module.get(NoticesService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/qas/qas.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { QasController } from './qas.controller'; 3 | 4 | describe('QasController', () => { 5 | let controller: QasController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [QasController], 10 | }).compile(); 11 | 12 | controller = module.get(QasController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/auth/auth.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AuthController } from './auth.controller'; 3 | 4 | describe('AuthController', () => { 5 | let controller: AuthController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [AuthController], 10 | }).compile(); 11 | 12 | controller = module.get(AuthController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/chatbots/chatbots.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { ChatbotsService } from './chatbots.service'; 3 | 4 | describe('ChatbotsService', () => { 5 | let service: ChatbotsService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [ChatbotsService], 10 | }).compile(); 11 | 12 | service = module.get(ChatbotsService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/contacts/contacts.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { ContactsService } from './contacts.service'; 3 | 4 | describe('ContactsService', () => { 5 | let service: ContactsService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [ContactsService], 10 | }).compile(); 11 | 12 | service = module.get(ContactsService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/keywords/keywords.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { KeywordsService } from './keywords.service'; 3 | 4 | describe('KeywordsService', () => { 5 | let service: KeywordsService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [KeywordsService], 10 | }).compile(); 11 | 12 | service = module.get(KeywordsService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/welcomes/welcomes.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { WelcomesService } from './welcomes.service'; 3 | 4 | describe('WelcomesService', () => { 5 | let service: WelcomesService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [WelcomesService], 10 | }).compile(); 11 | 12 | service = module.get(WelcomesService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/chats/chats.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { ChatsController } from './chats.controller'; 3 | 4 | describe('ChatsController', () => { 5 | let controller: ChatsController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [ChatsController], 10 | }).compile(); 11 | 12 | controller = module.get(ChatsController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/magic/magic.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { MagicController } from './magic.controller'; 3 | 4 | describe('MagicController', () => { 5 | let controller: MagicController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [MagicController], 10 | }).compile(); 11 | 12 | controller = module.get(MagicController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/rooms/rooms.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { RoomsController } from './rooms.controller'; 3 | 4 | describe('RoomsController', () => { 5 | let controller: RoomsController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [RoomsController], 10 | }).compile(); 11 | 12 | controller = module.get(RoomsController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/users/users.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { UsersController } from './users.controller'; 3 | 4 | describe('UsersController', () => { 5 | let controller: UsersController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [UsersController], 10 | }).compile(); 11 | 12 | controller = module.get(UsersController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/emoticons/emoticons.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { EmoticonsService } from './emoticons.service'; 3 | 4 | describe('EmoticonsService', () => { 5 | let service: EmoticonsService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [EmoticonsService], 10 | }).compile(); 11 | 12 | service = module.get(EmoticonsService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | pnpm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # OS 15 | .DS_Store 16 | 17 | # Tests 18 | /coverage 19 | /.nyc_output 20 | 21 | # IDEs and editors 22 | /.idea 23 | .project 24 | .classpath 25 | .c9/ 26 | *.launch 27 | .settings/ 28 | *.sublime-workspace 29 | 30 | # IDE - VSCode 31 | .vscode/* 32 | !.vscode/settings.json 33 | !.vscode/tasks.json 34 | !.vscode/launch.json 35 | !.vscode/extensions.json 36 | .env 37 | package-lock.json 38 | dist-pkg/* 39 | -------------------------------------------------------------------------------- /src/modules/groups/groups.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { GroupsController } from './groups.controller'; 3 | 4 | describe('GroupsController', () => { 5 | let controller: GroupsController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [GroupsController], 10 | }).compile(); 11 | 12 | controller = module.get(GroupsController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/medias/medias.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { MediasController } from './medias.controller'; 3 | 4 | describe('MediasController', () => { 5 | let controller: MediasController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [MediasController], 10 | }).compile(); 11 | 12 | controller = module.get(MediasController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/orders/orders.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { OrdersController } from './orders.controller'; 3 | 4 | describe('OrdersController', () => { 5 | let controller: OrdersController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [OrdersController], 10 | }).compile(); 11 | 12 | controller = module.get(OrdersController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/statistics/statistics.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { StatisticsService } from './statistics.service'; 3 | 4 | describe('StatisticsService', () => { 5 | let service: StatisticsService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [StatisticsService], 10 | }).compile(); 11 | 12 | service = module.get(StatisticsService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/upload/upload.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { UploadController } from './upload.controller'; 3 | 4 | describe('UploadController', () => { 5 | let controller: UploadController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [UploadController], 10 | }).compile(); 11 | 12 | controller = module.get(UploadController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/whitelists/whitelists.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { WhitelistsService } from './whitelists.service'; 3 | 4 | describe('WhitelistsService', () => { 5 | let service: WhitelistsService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [WhitelistsService], 10 | }).compile(); 11 | 12 | service = module.get(WhitelistsService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/carpoolings/carpoolings.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { CarpoolingsService } from './carpoolings.service'; 3 | 4 | describe('CarpoolingsService', () => { 5 | let service: CarpoolingsService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [CarpoolingsService], 10 | }).compile(); 11 | 12 | service = module.get(CarpoolingsService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/copilot/copilot.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { CopilotController } from './copilot.controller'; 3 | 4 | describe('CopilotController', () => { 5 | let controller: CopilotController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [CopilotController], 10 | }).compile(); 11 | 12 | controller = module.get(CopilotController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/notices/notices.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { NoticesController } from './notices.controller'; 3 | 4 | describe('NoticesController', () => { 5 | let controller: NoticesController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [NoticesController], 10 | }).compile(); 11 | 12 | controller = module.get(NoticesController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # 使用更小的基础镜像 2 | FROM node:18.20.2 3 | # FROM node:18.17.0-alpine 4 | 5 | # 设置工作目录 6 | WORKDIR /usr/src/app 7 | 8 | # 复制 package.json 和 package-lock.json 并执行 npm install 9 | COPY package.json ./ 10 | 11 | # 安装 pkg-config 工具 12 | RUN apt-get update && apt-get install -y pkg-config 13 | # RUN apk add --update pkgconfig 14 | # RUN apk add --update python3 make g++ && \ 15 | # ln -sf python3 /usr/bin/python 16 | 17 | # 对于Debian或Ubuntu基础镜像 18 | RUN apt-get update && apt-get install -y fonts-noto-cjk 19 | 20 | RUN npm install 21 | 22 | # 复制应用程序文件 23 | COPY . . 24 | 25 | # 设置启动命令 26 | CMD [ "npm", "run", "dev" ] 27 | -------------------------------------------------------------------------------- /src/modules/chatbots/chatbots.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { ChatbotsController } from './chatbots.controller'; 3 | 4 | describe('ChatbotsController', () => { 5 | let controller: ChatbotsController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [ChatbotsController], 10 | }).compile(); 11 | 12 | controller = module.get(ChatbotsController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/contacts/contacts.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { ContactsController } from './contacts.controller'; 3 | 4 | describe('ContactsController', () => { 5 | let controller: ContactsController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [ContactsController], 10 | }).compile(); 11 | 12 | controller = module.get(ContactsController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/emoticons/emoticon.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { EmoticonController } from './emoticon.controller'; 3 | 4 | describe('EmoticonController', () => { 5 | let controller: EmoticonController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [EmoticonController], 10 | }).compile(); 11 | 12 | controller = module.get(EmoticonController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/groupnotices/groupnotices.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { GroupnoticesService } from './groupnotices.service'; 3 | 4 | describe('GroupnoticesService', () => { 5 | let service: GroupnoticesService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [GroupnoticesService], 10 | }).compile(); 11 | 12 | service = module.get(GroupnoticesService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/keywords/keywords.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { KeywordsController } from './keywords.controller'; 3 | 4 | describe('KeywordsController', () => { 5 | let controller: KeywordsController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [KeywordsController], 10 | }).compile(); 11 | 12 | controller = module.get(KeywordsController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/welcomes/welcomes.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { WelcomesController } from './welcomes.controller'; 3 | 4 | describe('WelcomesController', () => { 5 | let controller: WelcomesController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [WelcomesController], 10 | }).compile(); 11 | 12 | controller = module.get(WelcomesController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/db/vikaModel/Qa/mod.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sort-keys */ 2 | 3 | import type { 4 | Sheet, 5 | // Field, 6 | } from '../Model'; 7 | 8 | import { vikaFields } from './fields.js'; 9 | import { defaultRecords } from './records.js'; 10 | 11 | import { replaceSyncStatus, actionState } from '../actionBar.js'; 12 | 13 | const name = '问答列表|Qa'; 14 | const code = 'qaSheet'; 15 | 16 | let fields: any = vikaFields.data.fields; 17 | 18 | if (actionState[code]) { 19 | fields = replaceSyncStatus(fields); 20 | } 21 | 22 | export const sheet: Sheet = { 23 | fields, 24 | name, 25 | defaultRecords: defaultRecords.data.records, 26 | }; 27 | -------------------------------------------------------------------------------- /src/db/vikaModel/Media/mod.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sort-keys */ 2 | 3 | import type { 4 | Sheet, 5 | // Field, 6 | } from '../Model'; 7 | import { vikaFields } from './fields.js'; 8 | import { defaultRecords } from './records.js'; 9 | 10 | import { replaceSyncStatus, actionState } from '../actionBar.js'; 11 | 12 | const name = '媒体资源|Media'; 13 | const code = 'mediaSheet'; 14 | 15 | let fields: any = vikaFields.data.fields; 16 | 17 | if (actionState[code]) { 18 | fields = replaceSyncStatus(fields); 19 | } 20 | 21 | export const sheet: Sheet = { 22 | fields, 23 | name, 24 | defaultRecords: defaultRecords.data.records, 25 | }; 26 | -------------------------------------------------------------------------------- /src/modules/statistics/statistics.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { StatisticsController } from './statistics.controller'; 3 | 4 | describe('StatisticsController', () => { 5 | let controller: StatisticsController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [StatisticsController], 10 | }).compile(); 11 | 12 | controller = module.get(StatisticsController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/whitelists/whitelists.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { WhitelistsController } from './whitelists.controller'; 3 | 4 | describe('WhitelistsController', () => { 5 | let controller: WhitelistsController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [WhitelistsController], 10 | }).compile(); 11 | 12 | controller = module.get(WhitelistsController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "ES2021", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": true, 16 | "noImplicitAny": true, 17 | "strictBindCallApply": true, 18 | "forceConsistentCasingInFileNames": true, 19 | "noFallthroughCasesInSwitch": true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/db/vikaModel/Welcome/mod.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sort-keys */ 2 | 3 | import type { 4 | Sheet, 5 | // Field, 6 | } from '../Model'; 7 | import { vikaFields } from './fields.js'; 8 | import { defaultRecords } from './records.js'; 9 | 10 | import { replaceSyncStatus, actionState } from '../actionBar.js'; 11 | 12 | const name = '进群欢迎语|Welcome'; 13 | const code = 'welcomeSheet'; 14 | 15 | let fields: any = vikaFields.data.fields; 16 | 17 | if (actionState[code]) { 18 | fields = replaceSyncStatus(fields); 19 | } 20 | 21 | export const sheet: Sheet = { 22 | fields, 23 | name, 24 | defaultRecords: defaultRecords.data.records, 25 | }; 26 | -------------------------------------------------------------------------------- /src/db/vikaModel/WhiteList/mod.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sort-keys */ 2 | 3 | import type { 4 | Sheet, 5 | // Field, 6 | } from '../Model'; 7 | import { vikaFields } from './fields.js'; 8 | import { defaultRecords } from './records.js'; 9 | 10 | import { replaceSyncStatus, actionState } from '../actionBar.js'; 11 | 12 | const name = '白名单|WhiteList'; 13 | const code = 'whiteListSheet'; 14 | 15 | let fields: any = vikaFields.data.fields; 16 | 17 | if (actionState[code]) { 18 | fields = replaceSyncStatus(fields); 19 | } 20 | 21 | export const sheet: Sheet = { 22 | fields, 23 | name, 24 | defaultRecords: defaultRecords.data.records, 25 | }; 26 | -------------------------------------------------------------------------------- /src/modules/carpoolings/carpoolings.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { CarpoolingsController } from './carpoolings.controller'; 3 | 4 | describe('CarpoolingsController', () => { 5 | let controller: CarpoolingsController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [CarpoolingsController], 10 | }).compile(); 11 | 12 | controller = module.get(CarpoolingsController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/db/vikaModel/Carpooling/mod.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sort-keys */ 2 | 3 | import type { 4 | Sheet, 5 | // Field, 6 | } from '../Model'; 7 | import { vikaFields } from './fields.js'; 8 | import { defaultRecords } from './records.js'; 9 | 10 | import { replaceSyncStatus, actionState } from '../actionBar.js'; 11 | 12 | const name = '顺风车|Carpooling'; 13 | const code = 'carpoolingSheet'; 14 | 15 | let fields: any = vikaFields.data.fields; 16 | 17 | if (actionState[code]) { 18 | fields = replaceSyncStatus(fields); 19 | } 20 | 21 | export const sheet: Sheet = { 22 | fields, 23 | name, 24 | defaultRecords: defaultRecords.data.records, 25 | }; 26 | -------------------------------------------------------------------------------- /src/db/vikaModel/Statistic/mod.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sort-keys */ 2 | 3 | import type { 4 | Sheet, 5 | // Field, 6 | } from '../Model'; 7 | 8 | import { vikaFields } from './fields.js'; 9 | import { defaultRecords } from './records.js'; 10 | 11 | import { replaceSyncStatus, actionState } from '../actionBar.js'; 12 | 13 | const name = '统计打卡|Statistic'; 14 | const code = 'statisticSheet'; 15 | 16 | let fields: any = vikaFields.data.fields; 17 | 18 | if (actionState[code]) { 19 | fields = replaceSyncStatus(fields); 20 | } 21 | 22 | export const sheet: Sheet = { 23 | fields, 24 | name, 25 | defaultRecords: defaultRecords.data.records, 26 | }; 27 | -------------------------------------------------------------------------------- /src/modules/auth/jwt.strategy.ts: -------------------------------------------------------------------------------- 1 | import { ExtractJwt, Strategy } from 'passport-jwt'; 2 | import { PassportStrategy } from '@nestjs/passport'; 3 | import { Injectable } from '@nestjs/common'; 4 | import { jwtConstants } from './constants'; 5 | 6 | @Injectable() 7 | export class JwtStrategy extends PassportStrategy(Strategy) { 8 | constructor() { 9 | super({ 10 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 11 | ignoreExpiration: false, 12 | secretOrKey: jwtConstants.secret, 13 | }); 14 | } 15 | 16 | async validate(payload: any) { 17 | return { userId: payload.sub, username: payload.username }; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/modules/groupnotices/groupnotices.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { GroupnoticesController } from './groupnotices.controller'; 3 | 4 | describe('GroupnoticesController', () => { 5 | let controller: GroupnoticesController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [GroupnoticesController], 10 | }).compile(); 11 | 12 | controller = module.get(GroupnoticesController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/db/vikaModel/Media/records.ts: -------------------------------------------------------------------------------- 1 | export const defaultRecords: any = { 2 | code: 200, 3 | success: true, 4 | message: 'Request successful', 5 | data: { 6 | total: 1, 7 | pageNum: 1, 8 | pageSize: 1, 9 | records: [ 10 | { 11 | recordId: 'recLYuFv3RgiB', 12 | fields: { 13 | 名字: '世界第一初恋', 14 | 类型: '动漫番剧', 15 | 链接: '链接:https://xxx.com/abc 提取码:29me', 16 | 链接1: '', 17 | '启用状态|state': '开启', 18 | '同步状态|syncStatus': '未同步', 19 | '最后操作时间|lastOperationTime': 1702519560557, 20 | '操作|action': '选择操作', 21 | }, 22 | }, 23 | ], 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /src/db/vikaModel/Welcome/records.ts: -------------------------------------------------------------------------------- 1 | export const defaultRecords: any = { 2 | code: 200, 3 | success: true, 4 | message: 'Request successful', 5 | data: { 6 | total: 1, 7 | pageNum: 1, 8 | pageSize: 1, 9 | records: [ 10 | { 11 | recordId: 'recLYuFv3RgiB', 12 | fields: { 13 | '群ID|id': '21491434759@chatroom', 14 | '群名称|topic': '测试群组', 15 | '欢迎语|text': '欢迎加入测试群组,有问题请联系管理员', 16 | '启用状态|state': '开启', 17 | '同步状态|syncStatus': '未同步', 18 | '最后操作时间|lastOperationTime': 1702519560557, 19 | '操作|action': '选择操作', 20 | }, 21 | }, 22 | ], 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /src/db/store.ts: -------------------------------------------------------------------------------- 1 | import { BiTable } from './mod.js'; 2 | 3 | export class Store { 4 | static users: BiTable[] = []; 5 | 6 | // 注册用户 7 | static addUser(user: BiTable) { 8 | if (user.userId) { 9 | Store.users.push(user); 10 | } 11 | return { 12 | spaceName: user.spaceName, 13 | token: user.token, 14 | }; 15 | } 16 | 17 | // 移除用户 18 | static removeUser(spaceId: string) { 19 | // 移除用户 20 | this.users = this.users.filter((user) => user.userId !== spaceId); 21 | return true; 22 | } 23 | 24 | // 查询用户 25 | static findUser(spaceId: string) { 26 | return this.users.find((user) => user.userId === spaceId); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/db/vikaModel/Qa/types.ts: -------------------------------------------------------------------------------- 1 | export type FieldsData = { 2 | skillname?: string; 3 | title?: string; 4 | question1?: string; 5 | question2?: string; 6 | question3?: string; 7 | answer?: string; 8 | state?: string; 9 | syncStatus?: string; 10 | lastOperationTime?: string; 11 | action?: string; 12 | }; 13 | 14 | export type NamesData = { 15 | '分类|skillname?': string; 16 | '标准问题|title?': string; 17 | '相似问题1(选填)|question1?': string; 18 | '相似问题2(选填)|question2?': string; 19 | '相似问题3(选填)|question3?': string; 20 | '机器人回答|answer?': string; 21 | '启用状态|state?': string; 22 | '同步状态|syncStatus?': string; 23 | '最后操作时间|lastOperationTime?': string; 24 | '操作|action?': string; 25 | }; 26 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | import { mqttServerWs } from './mqtt-broker'; 4 | 5 | console.log('mqttServerWs', mqttServerWs); 6 | 7 | process.on('uncaughtException', (err) => { 8 | console.error('未捕获的异常:', err); 9 | // 可以在这里添加一些清理逻辑 10 | // process.exit(1); // 强制退出程序 11 | }); 12 | 13 | process.on('unhandledRejection', (reason, promise) => { 14 | console.error('未处理的拒绝:', promise, '原因:', reason); 15 | // 应用的逻辑 16 | }); 17 | 18 | async function bootstrap() { 19 | const app = await NestFactory.create(AppModule); 20 | app.enableCors({ origin: '*', credentials: true }); 21 | await app.listen(9503); 22 | } 23 | bootstrap(); 24 | -------------------------------------------------------------------------------- /src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppController', () => { 6 | let appController: AppController; 7 | 8 | beforeEach(async () => { 9 | const app: TestingModule = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }).compile(); 13 | 14 | appController = app.get(AppController); 15 | }); 16 | 17 | describe('root', () => { 18 | it('should return "Hello World!"', () => { 19 | expect(appController.getHello()).toBe('Hello World!'); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/modules/auth/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { ExecutionContext, Injectable } from '@nestjs/common'; 2 | import { Reflector } from '@nestjs/core'; 3 | import { AuthGuard } from '@nestjs/passport'; 4 | import { IS_PUBLIC_KEY } from './decorators/public.decorator'; 5 | 6 | @Injectable() 7 | export class JwtAuthGuard extends AuthGuard('jwt') { 8 | constructor(private reflector: Reflector) { 9 | super(); 10 | } 11 | 12 | canActivate(context: ExecutionContext) { 13 | const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ 14 | context.getHandler(), 15 | context.getClass(), 16 | ]); 17 | if (isPublic) { 18 | return true; 19 | } 20 | return super.canActivate(context); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir: __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin'], 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /src/db/vikaModel/Keyword/fields.json: -------------------------------------------------------------------------------- 1 | {"code":200,"success":true,"data":{"fields":[{"id":"flduc4igEtLoQ","name":"指令名称|name","type":"SingleText","property":{"defaultValue":""},"editable":true,"isPrimary":true},{"id":"fldDb690A0L1E","name":"说明|desc","type":"Text","editable":true},{"id":"fld4yqz6uYHkG","name":"类型|type","type":"SingleSelect","property":{"options":[{"id":"optYt6uOH8l0d","name":"系统指令","color":{"name":"deepPurple_0","value":"#E5E1FC"}},{"id":"optWlKdyBNqAu","name":"群指令","color":{"name":"indigo_0","value":"#DDE7FF"}},{"id":"optHxBUHWIqEM","name":"包含关键字","color":{"name":"blue_0","value":"#DDF5FF"}},{"id":"optyuKwsQmtwj","name":"等于关键字","color":{"name":"teal_0","value":"#D6F3E8"}}]},"editable":true},{"id":"fldx5tKkVIDj7","name":"详细说明|details","type":"Text","editable":true}]},"message":"SUCCESS"} -------------------------------------------------------------------------------- /src/db/vikaModel/WhiteList/records.ts: -------------------------------------------------------------------------------- 1 | export const defaultRecords: any = { 2 | code: 200, 3 | success: true, 4 | message: 'Request successful', 5 | data: { 6 | total: 1, 7 | pageNum: 1, 8 | pageSize: 1, 9 | records: [ 10 | { 11 | recordId: 'recLYuFv3RgiB', 12 | fields: { 13 | '所属应用|app': '智能问答|qa', 14 | '类型|type': '好友', 15 | '昵称/群名称|name': '大师', 16 | '好友ID/群ID(选填)|id': 'ledongmao', 17 | '好友备注(选填)|alias': 'chatflow作者', 18 | '备注说明(选填)|info': '示例白名单', 19 | '启用状态|state': '开启', 20 | '配额(选填)|quota': 20, 21 | '同步状态|syncStatus': '未同步', 22 | '最后操作时间|lastOperationTime': 1702519560557, 23 | '操作|action': '选择操作', 24 | }, 25 | }, 26 | ], 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /src/modules/auth/local.strategy.ts: -------------------------------------------------------------------------------- 1 | import { Strategy } from 'passport-local'; 2 | import { PassportStrategy } from '@nestjs/passport'; 3 | import { Injectable, UnauthorizedException } from '@nestjs/common'; 4 | import { AuthService } from './auth.service'; 5 | import { ModuleRef } from '@nestjs/core'; 6 | 7 | @Injectable() 8 | export class LocalStrategy extends PassportStrategy(Strategy) { 9 | constructor( 10 | private readonly authService: AuthService, 11 | private moduleRef: ModuleRef, 12 | ) { 13 | super({ 14 | passReqToCallback: true, 15 | }); 16 | } 17 | 18 | async validate(username: string, password: string): Promise { 19 | const user = await this.authService.validateUser(username, password); 20 | if (!user) { 21 | throw new UnauthorizedException(); 22 | } 23 | return user; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/db/vikaModel/Statistic/records.ts: -------------------------------------------------------------------------------- 1 | export const defaultRecords: any = { 2 | code: 200, 3 | success: true, 4 | data: { 5 | total: 1, 6 | records: [ 7 | { 8 | recordId: 'recy3sWhvBiwu', 9 | createdAt: 1694140104000, 10 | updatedAt: 1694140128000, 11 | fields: { 12 | '开始时间(选填)|startTime': 1694140103590, 13 | '周期(选填)|cycle': '周一', 14 | '同步状态|syncStatus': '未同步', 15 | '类型|type': '活动报名', 16 | '限制人数(选填)|maximum': 99, 17 | '启用状态|active': '开启', 18 | '关联群名称|topic': '瓦力是群主', 19 | '描述|desc': '测试接龙活动', 20 | '最后操作时间|lastOperationTime': 1694140103590, 21 | '时长(小时,选填)|duration': 1, 22 | '操作|action': '选择操作', 23 | }, 24 | }, 25 | ], 26 | pageNum: 1, 27 | pageSize: 1, 28 | }, 29 | message: 'SUCCESS', 30 | }; 31 | -------------------------------------------------------------------------------- /src/db/vikaModel/Stock/records.ts: -------------------------------------------------------------------------------- 1 | export const defaultRecords: any = { 2 | code: 200, 3 | success: true, 4 | data: { 5 | total: 2, 6 | records: [ 7 | { 8 | recordId: 'recO9FJnjL7V2', 9 | createdAt: 1692721897000, 10 | updatedAt: 1692721897000, 11 | fields: { 12 | 更新日期: 1692633600000, 13 | 持仓数量: 1000, 14 | 代码: '000725', 15 | 名称: '京东方A', 16 | 成本: 3.5, 17 | }, 18 | }, 19 | { 20 | recordId: 'recKUt2tkDyNP', 21 | createdAt: 1692721897000, 22 | updatedAt: 1692721897000, 23 | fields: { 24 | 更新日期: 1692633600000, 25 | 持仓数量: 100, 26 | 代码: '601360', 27 | 名称: '三六零', 28 | 成本: 20, 29 | }, 30 | }, 31 | ], 32 | pageNum: 1, 33 | pageSize: 2, 34 | }, 35 | message: 'SUCCESS', 36 | }; 37 | -------------------------------------------------------------------------------- /src/db/vikaModel/Group/db.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, MappingOptions } from '../../mod.js'; 2 | 3 | export class Groups extends BaseEntity { 4 | groupName: string; // 定义名字属性,可选 5 | name: string; 6 | id: string; 7 | alias: string; 8 | 9 | // protected static override recordId: string = '' // 定义记录ID,初始为空字符串 10 | 11 | protected override mappingOptions: MappingOptions = { 12 | // 定义字段映射选项 13 | fieldMapping: { 14 | // 字段映射 15 | groupName: '分组名称|groupName', 16 | name: '好友昵称|name', 17 | alias: '备注名称|alias', 18 | id: '好友ID|id', 19 | }, 20 | tableName: '分组|Group', // 表名 21 | }; 22 | 23 | protected override getMappingOptions(): MappingOptions { 24 | // 获取映射选项的方法 25 | return this.mappingOptions; // 返回当前类的映射选项 26 | } 27 | 28 | override setMappingOptions(options: MappingOptions) { 29 | // 设置映射选项的方法 30 | this.mappingOptions = options; // 更新当前类的映射选项 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/db/vikaModel/Keyword/db.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, MappingOptions } from '../../mod.js'; 2 | 3 | export class Keywords extends BaseEntity { 4 | desc?: string; // 定义名字属性,可选 5 | name?: string; 6 | type?: string; 7 | details?: string; 8 | 9 | // protected static override recordId: string = '' // 定义记录ID,初始为空字符串 10 | 11 | protected override mappingOptions: MappingOptions = { 12 | // 定义字段映射选项 13 | fieldMapping: { 14 | // 字段映射 15 | name: '指令名称|name', 16 | desc: '说明|desc', 17 | type: '类型|type', 18 | details: '详细说明|details', 19 | }, 20 | tableName: '关键词|Keyword', // 表名 21 | }; 22 | 23 | protected override getMappingOptions(): MappingOptions { 24 | // 获取映射选项的方法 25 | return this.mappingOptions; // 返回当前类的映射选项 26 | } 27 | 28 | override setMappingOptions(options: MappingOptions) { 29 | // 设置映射选项的方法 30 | this.mappingOptions = options; // 更新当前类的映射选项 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/db/vikaModel/Order/fields.json: -------------------------------------------------------------------------------- 1 | {"code":200,"success":true,"data":{"fields":[{"id":"fldfVJETOZGFh","name":"编号|serialNumber","type":"SingleText","property":{},"editable":true,"isPrimary":true},{"id":"fldg36krPnZRM","name":"活动编号|code","type":"SingleText","property":{},"editable":true},{"id":"fldjWxmF6I6z8","name":"活动描述|desc","type":"Text","editable":true},{"id":"fld0RUDgKs2lo","name":"昵称|name","type":"SingleText","property":{},"editable":true},{"id":"fldBL0jp8pFuS","name":"备注名称(选填)|alias","type":"SingleText","property":{},"editable":true},{"id":"fldXO7uQw6c1L","name":"好友ID(选填)|wxid","type":"SingleText","property":{},"editable":true},{"id":"fldWsOpoiX9r4","name":"群名称|topic","type":"SingleText","property":{},"editable":true},{"id":"fldKsSCeStEsI","name":"创建时间|createdAt","type":"DateTime","property":{"format":"YYYY-MM-DD HH:mm","includeTime":true,"autoFill":true},"editable":true},{"id":"fldfP5qQKvF24","name":"备注|info","type":"Text","editable":true}]},"message":"SUCCESS"} -------------------------------------------------------------------------------- /src/db/vikaModel/Group/fields.ts: -------------------------------------------------------------------------------- 1 | export const vikaFields = { 2 | code: 200, 3 | success: true, 4 | data: { 5 | fields: [ 6 | { 7 | id: 'fld3QEdSdPya2', 8 | name: '分组名称|groupName', 9 | type: 'SingleText', 10 | property: {}, 11 | editable: true, 12 | isPrimary: true, 13 | }, 14 | { 15 | id: 'fldKykglJwuHz', 16 | name: '好友昵称|name', 17 | type: 'SingleText', 18 | property: { defaultValue: '' }, 19 | editable: true, 20 | }, 21 | { 22 | id: 'fldKykglJwuHz', 23 | name: '备注名称|alias', 24 | type: 'SingleText', 25 | property: { defaultValue: '' }, 26 | editable: true, 27 | }, 28 | { 29 | id: 'fld7cPH8MN7ej', 30 | name: '好友ID|id', 31 | type: 'SingleText', 32 | property: { defaultValue: '' }, 33 | editable: true, 34 | }, 35 | ], 36 | }, 37 | message: 'SUCCESS', 38 | }; 39 | -------------------------------------------------------------------------------- /src/db/vikaModel/Model.ts: -------------------------------------------------------------------------------- 1 | enum FieldType { 2 | SingleText = 'SingleText', 3 | SingleSelect = 'SingleSelect', 4 | Text = 'Text', 5 | Attachment = 'Attachment', 6 | } 7 | 8 | type Field = { 9 | id?: string; 10 | name: string; 11 | type: string; 12 | property?: any; 13 | desc?: string; 14 | editable?: boolean; 15 | isPrimary?: boolean; 16 | }; 17 | 18 | type FieldSingleText = Field & { type: FieldType.SingleText }; 19 | type FieldSingleSelect = Field & { type: FieldType.SingleSelect }; 20 | type FieldText = Field & { type: FieldType.Text }; 21 | 22 | type Record = { 23 | fields: { 24 | [key: string]: string; 25 | }; 26 | }; 27 | 28 | type Sheet = { 29 | fields: Field[]; 30 | name: string; 31 | defaultRecords: Record[]; 32 | }; 33 | 34 | type Sheets = { 35 | [key: string]: Sheet; 36 | }; 37 | 38 | export { 39 | FieldType, 40 | type Field, 41 | type FieldSingleText, 42 | type FieldSingleSelect, 43 | type FieldText, 44 | type Record, 45 | type Sheet, 46 | type Sheets, 47 | }; 48 | -------------------------------------------------------------------------------- /src/db/vikaModel/Room/db.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, MappingOptions } from '../../mod.js'; 2 | 3 | export class Rooms extends BaseEntity { 4 | topic?: string; // 定义名字属性,可选 5 | 6 | id?: string; 7 | 8 | alias?: string; 9 | 10 | updated?: string; 11 | 12 | avatar?: string; 13 | 14 | file?: string; 15 | 16 | // protected static override recordId: string = '' // 定义记录ID,初始为空字符串 17 | 18 | protected override mappingOptions: MappingOptions = { 19 | // 定义字段映射选项 20 | fieldMapping: { 21 | // 字段映射 22 | id: '群ID|id', 23 | topic: '群名称|topic', 24 | ownerId: '群主ID|ownerId', 25 | updated: '更新时间|updated', 26 | avatar: '头像|avatar', 27 | file: '头像图片|file', 28 | }, 29 | tableName: '群列表|Room', // 表名 30 | }; // 设置映射选项为上面定义的 mappingOptions 31 | 32 | protected override getMappingOptions(): MappingOptions { 33 | // 获取映射选项的方法 34 | return this.mappingOptions; // 返回当前类的映射选项 35 | } 36 | 37 | override setMappingOptions(options: MappingOptions) { 38 | // 设置映射选项的方法 39 | this.mappingOptions = options; // 更新当前类的映射选项 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/modules/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AuthService } from './auth.service'; 3 | import { LocalStrategy } from './local.strategy'; 4 | import { UsersModule } from '../users/users.module'; 5 | import { PassportModule } from '@nestjs/passport'; 6 | import { JwtModule } from '@nestjs/jwt'; 7 | import { jwtConstants } from './constants'; 8 | import { JwtStrategy } from './jwt.strategy'; 9 | import { APP_GUARD } from '@nestjs/core'; 10 | import { JwtAuthGuard } from './auth.guard'; 11 | import { AuthController } from './auth.controller'; 12 | 13 | @Module({ 14 | controllers: [AuthController], 15 | imports: [ 16 | PassportModule.register({ defaultStrategy: 'jwt' }), 17 | JwtModule.register({ 18 | secret: jwtConstants.secret, 19 | signOptions: { expiresIn: '3600s' }, 20 | }), 21 | UsersModule, 22 | ], 23 | providers: [ 24 | AuthService, 25 | LocalStrategy, 26 | JwtStrategy, 27 | { 28 | provide: APP_GUARD, 29 | useClass: JwtAuthGuard, 30 | }, 31 | ], 32 | exports: [AuthService], 33 | }) 34 | export class AuthModule {} 35 | -------------------------------------------------------------------------------- /src/db/vikaModel/Welcome/db.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, MappingOptions } from '../../mod.js'; 2 | 3 | export class Welcomes extends BaseEntity { 4 | id: string; 5 | topic: string; 6 | text: string; 7 | state: string; 8 | 9 | syncStatus: string; 10 | lastOperationTime: number; 11 | action: string; 12 | 13 | // protected static override recordId: string = '' // 定义记录ID,初始为空字符串 14 | 15 | protected override mappingOptions: MappingOptions = { 16 | // 定义字段映射选项 17 | fieldMapping: { 18 | // 字段映射 19 | id: '群ID|id', 20 | topic: '群名称|topic', 21 | text: '欢迎语|text', 22 | state: '启用状态|state', 23 | 24 | syncStatus: '同步状态|syncStatus', 25 | lastOperationTime: '最后操作时间|lastOperationTime', 26 | action: '操作|action', 27 | }, 28 | tableName: '进群欢迎语|Welcome', // 表名 29 | }; 30 | 31 | protected override getMappingOptions(): MappingOptions { 32 | // 获取映射选项的方法 33 | return this.mappingOptions; // 返回当前类的映射选项 34 | } 35 | 36 | override setMappingOptions(options: MappingOptions) { 37 | // 设置映射选项的方法 38 | this.mappingOptions = options; // 更新当前类的映射选项 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/db/mod.ts: -------------------------------------------------------------------------------- 1 | // 根据DB类型导入对应的DB模块,默认使用Vika,更好的方式时根据环境变量来判断自动选择 2 | import { BiTable } from './vika-db.js'; 3 | import { BaseEntity } from './vika-orm.js'; 4 | 5 | // 如果使用Lark,可以使用以下方式导入 6 | // import { BiTable } from './lark-db.js'; 7 | // import { BaseEntity } from './lark-orm.js'; 8 | 9 | export { BiTable, BaseEntity }; 10 | 11 | /** 12 | * 实体类映射选项 13 | */ 14 | export interface MappingOptions { 15 | // 表名 16 | tableName: string; 17 | 18 | // 字段映射 19 | fieldMapping: Record; 20 | } 21 | 22 | export interface DateBase { 23 | messageSheet: string; 24 | keywordSheet: string; 25 | contactSheet: string; 26 | roomSheet: string; 27 | envSheet: string; 28 | whiteListSheet: string; 29 | noticeSheet: string; 30 | statisticSheet: string; 31 | orderSheet: string; 32 | stockSheet: string; 33 | groupNoticeSheet: string; 34 | qaSheet: string; 35 | chatBotSheet: string; 36 | chatBotUserSheet: string; 37 | groupSheet: string; 38 | welcomeSheet: string; 39 | mediaSheet: string; 40 | carpoolingSheet: string; 41 | } 42 | 43 | export type BiTableConfig = { 44 | spaceId: string; 45 | token: string; 46 | }; 47 | -------------------------------------------------------------------------------- /src/db/vikaModel/Media/db.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, MappingOptions } from '../../mod.js'; 2 | 3 | export class Medias extends BaseEntity { 4 | name: string; 5 | type: string; 6 | link: string; 7 | link1: string; 8 | state: string; 9 | 10 | syncStatus: string; 11 | lastOperationTime: number; 12 | action: string; 13 | 14 | // protected static override recordId: string = '' // 定义记录ID,初始为空字符串 15 | 16 | protected override mappingOptions: MappingOptions = { 17 | // 定义字段映射选项 18 | fieldMapping: { 19 | // 字段映射 20 | name: '名字', 21 | type: '类型', 22 | link: '链接', 23 | link1: '链接1', 24 | state: '启用状态|state', 25 | 26 | syncStatus: '同步状态|syncStatus', 27 | lastOperationTime: '最后操作时间|lastOperationTime', 28 | action: '操作|action', 29 | }, 30 | tableName: '媒体资源|Media', // 表名 31 | }; 32 | 33 | protected override getMappingOptions(): MappingOptions { 34 | // 获取映射选项的方法 35 | return this.mappingOptions; // 返回当前类的映射选项 36 | } 37 | 38 | override setMappingOptions(options: MappingOptions) { 39 | // 设置映射选项的方法 40 | this.mappingOptions = options; // 更新当前类的映射选项 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/db/vikaModel/Env/db.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, MappingOptions } from '../../mod.js'; 2 | 3 | export class Env extends BaseEntity { 4 | name?: string; 5 | 6 | key?: string; 7 | 8 | value?: string; 9 | 10 | desc?: string; 11 | 12 | syncStatus?: string; 13 | 14 | lastOperationTime?: string; 15 | 16 | action?: string; 17 | 18 | // protected static override recordId: string = '' // 定义记录ID,初始为空字符串 19 | 20 | protected override mappingOptions: MappingOptions = { 21 | // 定义字段映射选项 22 | fieldMapping: { 23 | // 字段映射 24 | name: '配置项|name', 25 | key: '标识|key', 26 | value: '值|value', 27 | desc: '说明|desc', 28 | syncStatus: '同步状态|syncStatus', 29 | lastOperationTime: '最后操作时间|lastOperationTime', 30 | action: '操作|action', 31 | }, 32 | tableName: '环境变量|Env', // 表名 33 | }; // 设置映射选项为上面定义的 mappingOptions 34 | 35 | protected override getMappingOptions(): MappingOptions { 36 | // 获取映射选项的方法 37 | return this.mappingOptions; // 返回当前类的映射选项 38 | } 39 | 40 | override setMappingOptions(options: MappingOptions) { 41 | // 设置映射选项的方法 42 | this.mappingOptions = options; // 更新当前类的映射选项 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/db/vikaModel/Stock/db.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, MappingOptions } from '../../mod.js'; 2 | 3 | export class Env extends BaseEntity { 4 | name?: string; 5 | 6 | key?: string; 7 | 8 | value?: string; 9 | 10 | desc?: string; 11 | 12 | syncStatus?: string; 13 | 14 | lastOperationTime?: string; 15 | 16 | action?: string; 17 | 18 | // protected static override recordId: string = '' // 定义记录ID,初始为空字符串 19 | 20 | protected override mappingOptions: MappingOptions = { 21 | // 定义字段映射选项 22 | fieldMapping: { 23 | // 字段映射 24 | name: '配置项|name', 25 | key: '标识|key', 26 | value: '值|value', 27 | desc: '说明|desc', 28 | syncStatus: '同步状态|syncStatus', 29 | lastOperationTime: '最后操作时间|lastOperationTime', 30 | action: '操作|action', 31 | }, 32 | tableName: '环境变量|Env', // 表名 33 | }; // 设置映射选项为上面定义的 mappingOptions 34 | 35 | protected override getMappingOptions(): MappingOptions { 36 | // 获取映射选项的方法 37 | return this.mappingOptions; // 返回当前类的映射选项 38 | } 39 | 40 | override setMappingOptions(options: MappingOptions) { 41 | // 设置映射选项的方法 42 | this.mappingOptions = options; // 更新当前类的映射选项 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /scripts/createModule.ts: -------------------------------------------------------------------------------- 1 | // createModule.ts 2 | import { execSync } from 'child_process'; 3 | import { existsSync, mkdirSync, renameSync } from 'fs'; 4 | import { join } from 'path'; 5 | 6 | const moduleName = process.argv[2]; // 获取传递的模块名称 7 | 8 | if (!moduleName) { 9 | console.error('Please provide a module name.'); 10 | process.exit(1); 11 | } 12 | 13 | try { 14 | execSync(`nest g service ${moduleName}`, { stdio: 'inherit' }); 15 | execSync(`nest g controller ${moduleName}`, { stdio: 'inherit' }); 16 | execSync(`nest g module ${moduleName}`, { stdio: 'inherit' }); 17 | console.log(`Module ${moduleName} created successfully.`); 18 | // 路径设置 19 | // 获取当前目录的上一层目录 20 | const parentDir = join(__dirname, '..'); 21 | const sourceDir = join(parentDir, `/src/${moduleName}`); 22 | const targetDir = join(parentDir, '/src/modules', moduleName); 23 | 24 | // 确保目标目录存在 25 | if (!existsSync(targetDir)) { 26 | mkdirSync(targetDir, { recursive: true }); 27 | } 28 | 29 | // 移动文件夹 30 | renameSync(sourceDir, targetDir); 31 | console.log(`Module ${moduleName} created and moved successfully.`); 32 | } catch (error) { 33 | console.error('Error creating module:', error); 34 | } 35 | -------------------------------------------------------------------------------- /src/db/vikaModel/ChatBotUser/db.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, MappingOptions } from '../../mod.js'; 2 | 3 | export class ChatbotUsers extends BaseEntity { 4 | id: string; // 定义名字属性,可选 5 | botname: string; 6 | wxid: string; 7 | name: string; 8 | alias: string; 9 | prompt: string; 10 | quota: string; 11 | state: string; 12 | info: string; 13 | // protected static override recordId: string = '' // 定义记录ID,初始为空字符串 14 | 15 | protected override mappingOptions: MappingOptions = { 16 | // 定义字段映射选项 17 | fieldMapping: { 18 | // 字段映射 19 | id: '机器人ID|id', 20 | botname: '昵称|botname', 21 | wxid: '用户ID|wxid', 22 | name: '用户名称|name', 23 | alias: '好友备注(选填)|alias', 24 | prompt: '用户提示词|prompt', 25 | quota: '配额|quota', 26 | state: '启用状态|state', 27 | info: '备注|info', 28 | }, 29 | tableName: '智聊用户|ChatbotUser', // 表名 30 | }; 31 | 32 | protected override getMappingOptions(): MappingOptions { 33 | // 获取映射选项的方法 34 | return this.mappingOptions; // 返回当前类的映射选项 35 | } 36 | 37 | override setMappingOptions(options: MappingOptions) { 38 | // 设置映射选项的方法 39 | this.mappingOptions = options; // 更新当前类的映射选项 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/db/vikaModel/ChatBot/db.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config.js'; 2 | import { BaseEntity, MappingOptions } from '../../mod.js'; 3 | 4 | export class Chatbots extends BaseEntity { 5 | id: string; // 定义名字属性,可选 6 | name: string; 7 | desc: string; 8 | type: string; 9 | model: string; 10 | prompt: string; 11 | quota: string; 12 | endpoint: string; 13 | key: string; 14 | 15 | // protected static override recordId: string = '' // 定义记录ID,初始为空字符串 16 | 17 | protected override mappingOptions: MappingOptions = { 18 | // 定义字段映射选项 19 | fieldMapping: { 20 | // 字段映射 21 | id: '机器人ID|id', 22 | name: '昵称|name', 23 | desc: '描述|desc', 24 | type: '类型|type', 25 | model: '模型|model', 26 | prompt: '系统提示词|prompt', 27 | quota: '配额|quota', 28 | endpoint: '接入点|endpoint', 29 | key: '密钥|key', 30 | }, 31 | tableName: '智聊|Chatbot', // 表名 32 | }; 33 | 34 | protected override getMappingOptions(): MappingOptions { 35 | // 获取映射选项的方法 36 | return this.mappingOptions; // 返回当前类的映射选项 37 | } 38 | 39 | override setMappingOptions(options: MappingOptions) { 40 | // 设置映射选项的方法 41 | this.mappingOptions = options; // 更新当前类的映射选项 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/db/vikaModel/Order/db.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, MappingOptions } from '../../mod.js'; 2 | 3 | export class Orders extends BaseEntity { 4 | serialNumber: string; // 定义名字属性,可选 5 | code: string; 6 | desc: string; 7 | name: string; 8 | alias: string; 9 | wxid: string; 10 | topic: string; 11 | createdAt: string; 12 | info: string; 13 | 14 | // protected static override recordId: string = '' // 定义记录ID,初始为空字符串 15 | 16 | protected override mappingOptions: MappingOptions = { 17 | // 定义字段映射选项 18 | fieldMapping: { 19 | // 字段映射 20 | serialNumber: '编号|serialNumber', 21 | code: '活动编号|code', 22 | desc: '活动描述|desc', 23 | name: '昵称|name', 24 | alias: '备注名称(选填)|alias', 25 | wxid: '好友ID(选填)|wxid', 26 | topic: '群名称|topic', 27 | createdAt: '创建时间|createdAt', 28 | info: '备注|info', 29 | }, 30 | tableName: '记录单|Order', // 表名 31 | }; 32 | 33 | protected override getMappingOptions(): MappingOptions { 34 | // 获取映射选项的方法 35 | return this.mappingOptions; // 返回当前类的映射选项 36 | } 37 | 38 | override setMappingOptions(options: MappingOptions) { 39 | // 设置映射选项的方法 40 | this.mappingOptions = options; // 更新当前类的映射选项 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/db/vikaModel/Stock/fields.ts: -------------------------------------------------------------------------------- 1 | export const vikaFields = { 2 | code: 200, 3 | success: true, 4 | data: { 5 | fields: [ 6 | { 7 | id: 'fldWkMiBW8gpa', 8 | name: '代码', 9 | type: 'SingleText', 10 | property: { defaultValue: '' }, 11 | editable: true, 12 | isPrimary: true, 13 | }, 14 | { 15 | id: 'fldcGTEVtS42p', 16 | name: '名称', 17 | type: 'SingleText', 18 | property: {}, 19 | editable: true, 20 | }, 21 | { 22 | id: 'fldH9hsTEq90i', 23 | name: '持仓数量', 24 | type: 'Number', 25 | property: { precision: 0 }, 26 | editable: true, 27 | }, 28 | { 29 | id: 'fldshCPyNEac9', 30 | name: '成本', 31 | type: 'Number', 32 | property: { precision: 2 }, 33 | editable: true, 34 | }, 35 | { 36 | id: 'fldGB44mUw4tz', 37 | name: '更新日期', 38 | type: 'DateTime', 39 | property: { 40 | format: 'YYYY-MM-DD HH:mm', 41 | includeTime: true, 42 | autoFill: true, 43 | }, 44 | editable: true, 45 | }, 46 | ], 47 | }, 48 | message: 'SUCCESS', 49 | }; 50 | -------------------------------------------------------------------------------- /src/db/vikaModel/Welcome/fields.ts: -------------------------------------------------------------------------------- 1 | export const vikaFields = { 2 | code: 200, 3 | success: true, 4 | data: { 5 | fields: [ 6 | { 7 | id: 'fldkTrMcb63oY', 8 | name: '群ID|id', 9 | type: 'SingleText', 10 | property: {}, 11 | editable: true, 12 | isPrimary: true, 13 | }, 14 | { 15 | id: 'fldzD9S6sq9QR', 16 | name: '群名称|topic', 17 | type: 'SingleText', 18 | property: {}, 19 | editable: true, 20 | }, 21 | { 22 | id: 'fldAj0kCgDIDT', 23 | name: '欢迎语|text', 24 | type: 'Text', 25 | editable: true, 26 | }, 27 | { 28 | id: 'fldXrv3ioaJgK', 29 | name: '启用状态|state', 30 | type: 'SingleSelect', 31 | property: { 32 | options: [ 33 | { 34 | id: 'optzOM0Xof9Ln', 35 | name: '开启', 36 | color: { name: 'deepPurple_0', value: '#E5E1FC' }, 37 | }, 38 | { 39 | id: 'opt8qyV17YhtJ', 40 | name: '关闭', 41 | color: { name: 'indigo_0', value: '#DDE7FF' }, 42 | }, 43 | ], 44 | }, 45 | editable: true, 46 | }, 47 | ], 48 | }, 49 | message: 'SUCCESS', 50 | }; 51 | -------------------------------------------------------------------------------- /src/db/vikaModel/Qa/db.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, MappingOptions } from '../../mod.js'; 2 | 3 | export class Qas extends BaseEntity { 4 | skillname?: string; // 定义名字属性,可选 5 | title?: string; 6 | question1?: string; 7 | question2?: number; 8 | answer?: string; 9 | 10 | state?: string; 11 | syncStatus?: string; 12 | lastOperationTime?: string; 13 | action?: string; 14 | 15 | // protected static override recordId: string = '' // 定义记录ID,初始为空字符串 16 | 17 | protected override mappingOptions: MappingOptions = { 18 | // 定义字段映射选项 19 | fieldMapping: { 20 | // 字段映射 21 | skillname: '分类|skillname', 22 | title: '标准问题|title', 23 | question1: '相似问题1(选填)|question1', 24 | question2: '相似问题2(选填)|question2', 25 | answer: '机器人回答|answer', 26 | state: '启用状态|state', 27 | syncStatus: '同步状态|syncStatus', 28 | lastOperationTime: '最后操作时间|lastOperationTime', 29 | action: '操作|action', 30 | }, 31 | tableName: '问答列表|Qa', // 表名 32 | }; 33 | 34 | protected override getMappingOptions(): MappingOptions { 35 | // 获取映射选项的方法 36 | return this.mappingOptions; // 返回当前类的映射选项 37 | } 38 | 39 | override setMappingOptions(options: MappingOptions) { 40 | // 设置映射选项的方法 41 | this.mappingOptions = options; // 更新当前类的映射选项 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/modules/medias/medias.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Get, 4 | Query, 5 | Request, 6 | UnauthorizedException, 7 | } from '@nestjs/common'; 8 | import { Store } from '../../db/store.js'; 9 | 10 | @Controller('api/v1/media') 11 | export class MediasController { 12 | @Get('list') 13 | async findAll(@Request() req: any, @Query() query: any): Promise { 14 | const user = req.user; 15 | // console.debug(user); 16 | // console.debug(Store.users); 17 | const db = Store.findUser(user.userId); 18 | if (!db) { 19 | throw new UnauthorizedException(); 20 | } 21 | // console.debug(db); 22 | let data; 23 | if (query.name) { 24 | data = await db.db.media.findByField('name', query.name); 25 | } else { 26 | data = await db.db.media.findAll(); 27 | } 28 | const res: any = { 29 | code: 400, 30 | message: 'error', 31 | data, 32 | }; 33 | if (data.data) { 34 | const items = data.data.map((value: any) => { 35 | const fields = value.fields; 36 | fields.recordId = value.recordId; 37 | return fields; 38 | }); 39 | res.data = { 40 | page: 1, 41 | pageSize: 1000, 42 | pageCount: 1, 43 | itemCount: data.data.length, 44 | items: items, 45 | }; 46 | } 47 | return res; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/db/vikaModel/Contact/db.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, MappingOptions } from '../../mod.js'; 2 | 3 | export class Contacts extends BaseEntity { 4 | name?: string; // 定义名字属性,可选 5 | 6 | id?: string; 7 | 8 | alias?: string; 9 | 10 | gender?: string; 11 | 12 | updated?: string; 13 | 14 | friend?: string; 15 | 16 | type?: string; 17 | 18 | avatar?: string; 19 | 20 | phone?: string; 21 | 22 | file?: string; 23 | 24 | groupName?: string; 25 | 26 | // protected static override recordId: string = '' // 定义记录ID,初始为空字符串 27 | 28 | protected override mappingOptions: MappingOptions = { 29 | // 定义字段映射选项 30 | fieldMapping: { 31 | // 字段映射 32 | alias: '备注名称|alias', 33 | id: '好友ID|id', 34 | name: '好友昵称|name', 35 | gender: '性别|gender', 36 | updated: '更新时间|updated', 37 | friend: '是否好友|friend', 38 | type: '类型|type', 39 | avatar: '头像|avatar', 40 | phone: '手机号|phone', 41 | file: '头像图片|file', 42 | groupName: '分组名称|groupName', 43 | }, 44 | tableName: '好友列表|Contact', // 表名 45 | }; 46 | 47 | protected override getMappingOptions(): MappingOptions { 48 | // 获取映射选项的方法 49 | return this.mappingOptions; // 返回当前类的映射选项 50 | } 51 | 52 | override setMappingOptions(options: MappingOptions) { 53 | // 设置映射选项的方法 54 | this.mappingOptions = options; // 更新当前类的映射选项 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/modules/welcomes/welcomes.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Query, 4 | Request, 5 | Get, 6 | UnauthorizedException, 7 | } from '@nestjs/common'; 8 | import { Store } from '../../db/store.js'; 9 | 10 | @Controller('api/v1/welcome') 11 | export class WelcomesController { 12 | @Get('list') 13 | async findAll(@Request() req: any, @Query() query: any): Promise { 14 | const user = req.user; 15 | // console.debug(user); 16 | // console.debug(Store.users); 17 | const db = Store.findUser(user.userId); 18 | if (!db) { 19 | throw new UnauthorizedException(); 20 | } 21 | // console.debug(db); 22 | let data; 23 | if (query.keyword) { 24 | data = await db.db.welcome.findByQuery(query.keyword); 25 | } else { 26 | data = await db.db.welcome.findAll(); 27 | } 28 | const res: any = { 29 | code: 400, 30 | message: 'error', 31 | data, 32 | }; 33 | if (data.data.length) { 34 | const items = data.data.map((value: any) => { 35 | const fields = value.fields; 36 | fields.recordId = value.recordId; 37 | return fields; 38 | }); 39 | res.data = { 40 | page: 1, 41 | pageSize: 1000, 42 | pageCount: 1, 43 | itemCount: data.data.length, 44 | items: items, 45 | }; 46 | } 47 | return res; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/db/vikaModel/GroupNotice/fields.json: -------------------------------------------------------------------------------- 1 | {"code":200,"success":true,"data":{"fields":[{"id":"fld0UHVRdiQf7","name":"内容|text","type":"Text","editable":true,"isPrimary":true},{"id":"fldVlR8cvlPiD","name":"类型|type","type":"SingleSelect","property":{"options":[{"id":"optkCf0YMOZBb","name":"好友|contact","color":{"name":"deepPurple_0","value":"#E5E1FC"}},{"id":"optkM07kKsmvd","name":"群|room","color":{"name":"indigo_0","value":"#DDE7FF"}}]},"editable":true},{"id":"fldbWOCxhoByL","name":"好友备注(选填)|alias","type":"SingleText","property":{},"editable":true},{"id":"fldUoqVS4N8Rt","name":"昵称/群名称|name","type":"SingleText","property":{},"editable":true},{"id":"fldbuuw8Ga1H1","name":"好友ID/群ID(选填)|id","type":"SingleText","property":{},"editable":true},{"id":"fldFZOABfIQFV","name":"状态|state","type":"SingleSelect","property":{"options":[{"id":"optyYRK9V2I3P","name":"待发送|waiting","color":{"name":"deepPurple_0","value":"#E5E1FC"}},{"id":"optdZXivbniAi","name":"暂存|staging","color":{"name":"indigo_0","value":"#DDE7FF"}},{"id":"optYwbJenY36O","name":"发送成功|success","color":{"name":"blue_0","value":"#DDF5FF"}},{"id":"opt42EyDXhlfh","name":"发送失败|fail","color":{"name":"teal_0","value":"#D6F3E8"}}]},"editable":true},{"id":"fldyr9g6lTMaW","name":"发送时间|pubTime","type":"DateTime","property":{"format":"YYYY-MM-DD HH:mm","includeTime":true,"autoFill":true},"editable":true},{"id":"fldJgbb00qqoE","name":"信息|info","type":"Text","editable":true}]},"message":"SUCCESS"} -------------------------------------------------------------------------------- /src/utils/crypto-use-crypto-js.ts: -------------------------------------------------------------------------------- 1 | import * as CryptoJS from 'crypto-js'; 2 | 3 | // 加密函数 4 | export function encrypt(payload: string, keyBase64: string) { 5 | const key = CryptoJS.enc.Base64.parse(keyBase64); 6 | const iv = CryptoJS.lib.WordArray.random(16); // 生成一个16字节的随机IV 7 | const encrypted = CryptoJS.AES.encrypt(payload, key, { iv }); 8 | return JSON.stringify({ 9 | data: encrypted.ciphertext.toString(CryptoJS.enc.Hex), 10 | iv: iv.toString(CryptoJS.enc.Hex), 11 | }); 12 | } 13 | 14 | // 解密函数 15 | export function decrypt(message: string | any, keyBase64: string) { 16 | message = JSON.parse(message); 17 | const key = CryptoJS.enc.Base64.parse(keyBase64); 18 | const iv = CryptoJS.enc.Hex.parse(message.iv); 19 | const encryptedText = CryptoJS.enc.Hex.parse(message.data); 20 | const cipherParams = CryptoJS.lib.CipherParams.create({ 21 | ciphertext: encryptedText, 22 | }); 23 | const decrypted = CryptoJS.AES.decrypt(cipherParams, key, { iv }); 24 | return decrypted.toString(CryptoJS.enc.Utf8); 25 | } 26 | 27 | // 生成密钥 28 | export function getKey() { 29 | return CryptoJS.lib.WordArray.random(32).toString(CryptoJS.enc.Base64); 30 | } 31 | 32 | // 使用基础字符串生成密钥 33 | export function getKeyByBasicString(basicString: string) { 34 | // console.log('basicString', basicString); 35 | const hash = CryptoJS.SHA256(basicString); 36 | return hash.toString(CryptoJS.enc.Base64); 37 | } 38 | -------------------------------------------------------------------------------- /src/db/vikaModel/GroupNotice/db.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, MappingOptions } from '../../mod.js'; 2 | 3 | export class Groupnotices extends BaseEntity { 4 | text: string; // 内容 5 | type: string; // 类型 6 | alias: string; // 好友备注(选填) 7 | name: string; // 昵称/群名称 8 | id: string; // 好友ID/群ID(选填) 9 | state: string; // 状态 10 | pubTime: string; // 发送时间 11 | info: string; // 信息 12 | syncStatus: string; // 同步状态 13 | lastOperationTime: string; // 最后操作时间 14 | action: string; // 操作 15 | 16 | // protected static override recordId: string = '' // 定义记录ID,初始为空字符串 17 | 18 | protected override mappingOptions: MappingOptions = { 19 | // 定义字段映射选项 20 | fieldMapping: { 21 | // 字段映射 22 | text: '内容|text', 23 | type: '类型|type', 24 | alias: '好友备注(选填)|alias', 25 | name: '昵称/群名称|name', 26 | id: '好友ID/群ID(选填)|id', 27 | state: '状态|state', 28 | pubTime: '发送时间|pubTime', 29 | info: '信息|info', 30 | syncStatus: '同步状态|syncStatus', 31 | lastOperationTime: '最后操作时间|lastOperationTime', 32 | action: '操作|action', 33 | }, 34 | tableName: '群发通知|GroupNotice', // 表名 35 | }; 36 | 37 | protected override getMappingOptions(): MappingOptions { 38 | // 获取映射选项的方法 39 | return this.mappingOptions; // 返回当前类的映射选项 40 | } 41 | 42 | override setMappingOptions(options: MappingOptions) { 43 | // 设置映射选项的方法 44 | this.mappingOptions = options; // 更新当前类的映射选项 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/db/vikaModel/Notice/db.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, MappingOptions } from '../../mod.js'; 2 | 3 | export class Notices extends BaseEntity { 4 | desc?: string; // 定义名字属性,可选 5 | 6 | id?: string; 7 | 8 | name?: string; 9 | 10 | type?: string; 11 | 12 | alias?: string; 13 | 14 | time?: number; 15 | 16 | cycle?: string; 17 | 18 | state?: string; 19 | 20 | syncStatus?: string; 21 | 22 | lastOperationTime?: string; 23 | 24 | action?: string; 25 | 26 | // protected static override recordId: string = '' // 定义记录ID,初始为空字符串 27 | 28 | protected override mappingOptions: MappingOptions = { 29 | // 定义字段映射选项 30 | fieldMapping: { 31 | // 字段映射 32 | id: '好友ID/群ID(选填)|id', // 将 id 映射到 '好友ID/群ID(选填)' 字段 33 | name: '昵称/群名称|name', 34 | type: '通知目标类型|type', 35 | desc: '内容|desc', 36 | alias: '好友备注(选填)|alias', 37 | time: '时间|time', 38 | cycle: '周期|cycle', 39 | state: '启用状态|state', 40 | syncStatus: '同步状态|syncStatus', 41 | lastOperationTime: '最后操作时间|lastOperationTime', 42 | action: '操作|action', 43 | }, 44 | tableName: '定时提醒|Notice', // 表名 45 | }; 46 | 47 | protected override getMappingOptions(): MappingOptions { 48 | // 获取映射选项的方法 49 | return this.mappingOptions; // 返回当前类的映射选项 50 | } 51 | 52 | override setMappingOptions(options: MappingOptions) { 53 | // 设置映射选项的方法 54 | this.mappingOptions = options; // 更新当前类的映射选项 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChatFlowAdmin 2 | 3 | GitHub stars badge GitHub forks badge GitHub license badge 4 | 5 | ## 项目介绍 6 | 7 | ChatAdmin是ChatStudio的后端API项目。基于nest开发,ChatFlow项目UI的后端。 8 | 9 | 配套前端项目 Chat Studio [https://github.com/atorber/chat-studio](https://github.com/atorber/chat-studio) 10 | 11 | 访问[项目语雀文档](https://www.yuque.com/atorber/chatflow)了解更多信息 12 | 13 | ## 功能模块 14 | 15 | - TBD 16 | 17 | ## 项目安装(部署) 18 | 19 | ### 下载安装 20 | 21 | ```bash 22 | ## 克隆项目源码包 23 | git clone https://github.com/atorber/chatflow-admin.git 24 | 25 | ## 安装项目依赖扩展组件 26 | npm install 27 | 28 | # 启动本地开发环境 29 | npm run dev 30 | ``` 31 | 32 | API地址及端口: 127.0.0.1:9503 33 | 34 | ## 联系方式 35 | 36 | QQ群 : 583830241 37 | 38 | 微信 : ledongmao 39 | 40 | ## 如果你觉得还不错,请 Star , Fork 给作者鼓励一下。 41 | 42 | [![Star History Chart](https://api.star-history.com/svg?repos=atorber/chatflow-admin&type=Date)](https://star-history.com/#atorber/chatflow-admin&Date) 43 | 44 | ## 更新日志 45 | 46 | ### 3.0.19-vika 47 | 48 | - 修复聊天记录查询接口bug 49 | - 初始化electron客户端 50 | 51 | ### 3.0.0-vika-Beta-8/3.0.0-lark-Beta-8 52 | 53 | - 新增媒体资源接口 54 | - 新增进群欢迎语接口 55 | - 新增顺风车接口 56 | 57 | ### 3.0.0-6 58 | 59 | - 支持chatflow客户端,web端与bot客户端共享后端 60 | 61 | ### 3.0.0-5 62 | 63 | - 适配飞书多维表格,初步测试通过 64 | -------------------------------------------------------------------------------- /src/mqtt-broker.ts: -------------------------------------------------------------------------------- 1 | import * as Aedes from 'aedes'; 2 | import { createServer } from 'net'; 3 | import { createServer as createAedesServer } from 'aedes-server-factory'; 4 | 5 | const portMqtt = 11883; 6 | const aedes = Aedes.createBroker(); 7 | const mqttServerMqtt = createServer(aedes.handle); 8 | 9 | mqttServerMqtt.on('connection', function () { 10 | console.log('client connected'); 11 | }); 12 | 13 | mqttServerMqtt.on('close', function () { 14 | console.log('client disconnected'); 15 | }); 16 | 17 | mqttServerMqtt.on('error', function (err) { 18 | console.log('mqttServerMqtt error:', err); 19 | }); 20 | 21 | mqttServerMqtt.listen(portMqtt, function () { 22 | console.log('server started and listening on port ', portMqtt); 23 | }); 24 | 25 | const mqttServerWs = createAedesServer(aedes, { ws: true }); 26 | const portWs = 18888; 27 | 28 | mqttServerWs.on('connectionError', function (client) { 29 | console.log('client connection error', client); 30 | }); 31 | 32 | mqttServerWs.on('client', function (client) { 33 | console.log('client connected', client); 34 | }); 35 | 36 | mqttServerWs.on('clientDisconnect', function (client) { 37 | console.log('client disconnected', client); 38 | }); 39 | 40 | mqttServerWs.on('close', function () { 41 | console.log('server closed'); 42 | }); 43 | 44 | mqttServerWs.listen(portWs, function () { 45 | console.log('websocket server listening on port ', portWs); 46 | }); 47 | 48 | export { mqttServerMqtt, mqttServerWs }; 49 | -------------------------------------------------------------------------------- /src/db/vikaModel/Message/fields.json: -------------------------------------------------------------------------------- 1 | {"code":200,"success":true,"data":{"fields":[{"id":"fld7J6pZsrNat","name":"时间|timeHms","type":"SingleText","property":{"defaultValue":""},"editable":true,"isPrimary":true},{"id":"fldYaWgbe2iEG","name":"发送者|name","type":"SingleText","property":{"defaultValue":""},"editable":true},{"id":"fldaLPuahrn7S","name":"好友备注|alias","type":"SingleText","property":{"defaultValue":""},"editable":true},{"id":"fld5E5KJmzUow","name":"群名称|topic","type":"SingleText","property":{"defaultValue":""},"editable":true},{"id":"fld07r2ePsUiV","name":"接收人|listener","type":"SingleText","property":{},"editable":true},{"id":"fldQJl6upgfYd","name":"消息内容|messagePayload","type":"Text","editable":true},{"id":"fldnxD3QoudNs","name":"文件图片|file","type":"Attachment","editable":true},{"id":"fldVBdInXiBr3","name":"消息类型|messageType","type":"SingleText","property":{"defaultValue":""},"editable":true},{"id":"fldAivHA8anFP","name":"好友ID|wxid","type":"SingleText","property":{"defaultValue":""},"editable":true},{"id":"fldOCt60zYh8f","name":"接收人ID|listenerid","type":"SingleText","property":{},"editable":true},{"id":"fldXswnj8SFxo","name":"群ID|roomid","type":"SingleText","property":{"defaultValue":""},"editable":true},{"id":"fldkTLOkFxGdP","name":"发送者头像|wxAvatar","type":"Text","editable":true},{"id":"fldkTLOkFxGdP2","name":"群头像|roomAvatar","type":"Text","editable":true},{"id":"fldkTLOkFxGdP3","name":"接收人头像|listenerAvatar","type":"Text","editable":true},{"id":"fldEavgqazETa","name":"消息ID|messageId","type":"SingleText","property":{"defaultValue":""},"editable":true}]},"message":"SUCCESS"} -------------------------------------------------------------------------------- /src/db/vikaModel/Statistic/db.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, MappingOptions } from '../../mod.js'; 2 | 3 | export class Statistics extends BaseEntity { 4 | _id?: string; // 定义名字属性,可选 5 | type?: string; 6 | desc?: string; 7 | startTime?: number; 8 | duration?: number; 9 | maximum?: number; 10 | location?: string; 11 | cycle?: number; 12 | topic?: string; 13 | roomid?: string; 14 | active?: string; 15 | 16 | syncStatus?: string; 17 | lastOperationTime?: string; 18 | action?: string; 19 | // protected static override recordId: string = '' // 定义记录ID,初始为空字符串 20 | 21 | protected override mappingOptions: MappingOptions = { 22 | // 定义字段映射选项 23 | fieldMapping: { 24 | // 字段映射 25 | _id: '编号|_id', 26 | type: '类型|type', 27 | desc: '描述|desc', 28 | startTime: '开始时间(选填)|startTime', 29 | duration: '时长(小时,选填)|duration', 30 | maximum: '限制人数(选填)|maximum', 31 | location: '地点(选填)|location', 32 | cycle: '周期(选填)|cycle', 33 | topic: '关联群名称|topic', 34 | roomid: '关联群ID(选填)|roomid', 35 | active: '启用状态|active', 36 | syncStatus: '同步状态|syncStatus', 37 | lastOperationTime: '最后操作时间|lastOperationTime', 38 | action: '操作|action', 39 | }, 40 | tableName: '问答列表|Qa', // 表名 41 | }; 42 | 43 | protected override getMappingOptions(): MappingOptions { 44 | // 获取映射选项的方法 45 | return this.mappingOptions; // 返回当前类的映射选项 46 | } 47 | 48 | override setMappingOptions(options: MappingOptions) { 49 | // 设置映射选项的方法 50 | this.mappingOptions = options; // 更新当前类的映射选项 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/db/vikaModel/Room/fields.ts: -------------------------------------------------------------------------------- 1 | export const vikaFields = { 2 | code: 200, 3 | success: true, 4 | data: { 5 | fields: [ 6 | { 7 | id: 'fld3QEdSdPya2', 8 | name: '群ID|id', 9 | type: 'SingleText', 10 | property: {}, 11 | editable: true, 12 | isPrimary: true, 13 | }, 14 | { 15 | id: 'fldKykglJwuHz', 16 | name: '群名称|topic', 17 | type: 'SingleText', 18 | property: { defaultValue: '' }, 19 | editable: true, 20 | }, 21 | { 22 | id: 'fld7cPH8MN7ej', 23 | name: '群主ID|ownerId', 24 | type: 'SingleText', 25 | property: { defaultValue: '' }, 26 | editable: true, 27 | }, 28 | { 29 | id: 'fldX3yNNRlBXM', 30 | name: '更新时间|updated', 31 | type: 'SingleText', 32 | property: { defaultValue: '' }, 33 | editable: true, 34 | }, 35 | { 36 | id: 'fldlk9peECDG8', 37 | name: '头像|avatar', 38 | type: 'Text', 39 | editable: true, 40 | }, 41 | { 42 | id: 'fldCbUF1ki8hn', 43 | name: '管理员|adminIdList', 44 | type: 'Text', 45 | editable: true, 46 | }, 47 | { 48 | id: 'fld4YqkuQCRHH', 49 | name: '成员|memberIdList', 50 | type: 'Text', 51 | editable: true, 52 | }, 53 | { 54 | id: 'fldFKNRFRqHEt', 55 | name: '头像图片|file', 56 | type: 'Attachment', 57 | editable: true, 58 | }, 59 | ], 60 | }, 61 | message: 'SUCCESS', 62 | }; 63 | -------------------------------------------------------------------------------- /src/db/vikaModel/WhiteList/db.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, MappingOptions } from '../../mod.js'; 2 | 3 | export class Whitelists extends BaseEntity { 4 | serialNumber: string; // 定义名字属性,可选 5 | app: string; 6 | type: string; 7 | name: string; 8 | id: string; 9 | alias: string; 10 | info: string; 11 | state: string; 12 | quota: number; 13 | adminName: string; 14 | adminAlias: string; 15 | adminId: string; 16 | 17 | syncStatus: string; 18 | lastOperationTime: number; 19 | action: string; 20 | 21 | // protected static override recordId: string = '' // 定义记录ID,初始为空字符串 22 | 23 | protected override mappingOptions: MappingOptions = { 24 | // 定义字段映射选项 25 | fieldMapping: { 26 | // 字段映射 27 | serialNumber: '编号|serialNumber', 28 | app: '所属应用|app', 29 | type: '类型|type', 30 | name: '昵称/群名称|name', 31 | id: '好友ID/群ID(选填)|id', 32 | alias: '好友备注(选填)|alias', 33 | info: '备注说明(选填)|info', 34 | state: '启用状态|state', 35 | quota: '配额(选填)|quota', 36 | adminName: '管理员昵称|adminName', 37 | adminAlias: '管理员好友备注(选填)|adminAlias', 38 | adminId: '管理员ID(选填)|adminId', 39 | 40 | syncStatus: '同步状态|syncStatus', 41 | lastOperationTime: '最后操作时间|lastOperationTime', 42 | action: '操作|action', 43 | }, 44 | tableName: '白名单|WhiteList', // 表名 45 | }; 46 | 47 | protected override getMappingOptions(): MappingOptions { 48 | // 获取映射选项的方法 49 | return this.mappingOptions; // 返回当前类的映射选项 50 | } 51 | 52 | override setMappingOptions(options: MappingOptions) { 53 | // 设置映射选项的方法 54 | this.mappingOptions = options; // 更新当前类的映射选项 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | # docker-image.yml 2 | name: Publish Docker image # workflow名称,可以在Github项目主页的【Actions】中看到所有的workflow 3 | 4 | on: # 配置触发workflow的事件 5 | push: 6 | branches: 7 | - main 8 | tags: # tag更新时触发此workflow 9 | - '*' 10 | 11 | jobs: # workflow中的job 12 | 13 | push_to_registry: # job的名字 14 | name: Push Docker image to Docker Hub 15 | runs-on: ubuntu-latest # job运行的基础环境 16 | 17 | steps: # 一个job由一个或多个step组成 18 | - name: Check out the repo 19 | uses: actions/checkout@v2 # 官方的action,获取代码 20 | 21 | - name: Log in to Docker Hub 22 | uses: docker/login-action@v1 # 三方的action操作, 执行docker login 23 | with: 24 | username: ${{ secrets.DOCKERHUB_USERNAME }} # 配置dockerhub的认证,在Github项目主页 【Settings】 -> 【Secrets】 添加对应变量 25 | password: ${{ secrets.DOCKERHUB_TOKEN }} 26 | 27 | - name: Extract metadata (tags, labels) for Docker 28 | id: meta 29 | uses: docker/metadata-action@v3 # 抽取项目信息,主要是镜像的tag 30 | with: 31 | images: atorber/chatflow-admin 32 | 33 | - name: Extract package version 34 | id: package_version 35 | run: echo "::set-output name=VERSION::$(jq -r '.version' package.json)" 36 | 37 | - name: Build and push Docker image 38 | uses: docker/build-push-action@v2 # docker build & push 39 | with: 40 | context: . 41 | push: true 42 | tags: | 43 | atorber/chatflow-admin:${{ steps.package_version.outputs.VERSION }} 44 | atorber/chatflow-admin:latest 45 | labels: ${{ steps.meta.outputs.labels }} 46 | -------------------------------------------------------------------------------- /src/db/vikaModel/Message/db.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, MappingOptions } from '../../mod.js'; 2 | 3 | export class Messages extends BaseEntity { 4 | timeHms?: string; 5 | 6 | name?: string; 7 | 8 | alias?: string; 9 | 10 | topic?: string; 11 | 12 | listener?: string; 13 | 14 | messagePayload?: string; 15 | 16 | file?: any; 17 | 18 | messageType?: string; 19 | 20 | wxid?: string; 21 | 22 | roomid?: string; 23 | 24 | messageId?: string; 25 | 26 | wxAvatar?: string; 27 | 28 | roomAvatar?: string; 29 | 30 | listenerAvatar?: string; 31 | 32 | // protected static override recordId: string = '' // 定义记录ID,初始为空字符串 33 | 34 | protected override mappingOptions: MappingOptions = { 35 | // 定义字段映射选项 36 | fieldMapping: { 37 | // 字段映射 38 | timeHms: '时间|timeHms', 39 | name: '发送者|name', 40 | alias: '好友备注|alias', 41 | topic: '群名称|topic', 42 | listener: '接收人|listener', 43 | messagePayload: '消息内容|messagePayload', 44 | file: '文件图片|file', 45 | messageType: '消息类型|messageType', 46 | wxid: '好友ID|wxid', 47 | listenerid: '接收人ID|listenerid', 48 | roomid: '群ID|roomid', 49 | messageId: '消息ID|messageId', 50 | wxAvatar: '发送者头像|wxAvatar', 51 | roomAvatar: '群头像|roomAvatar', 52 | listenerAvatar: '接收人头像|listenerAvatar', 53 | }, 54 | tableName: '消息记录|Message', // 表名 55 | }; // 设置映射选项为上面定义的 mappingOptions 56 | 57 | protected override getMappingOptions(): MappingOptions { 58 | // 获取映射选项的方法 59 | return this.mappingOptions; // 返回当前类的映射选项 60 | } 61 | 62 | override setMappingOptions(options: MappingOptions) { 63 | // 设置映射选项的方法 64 | this.mappingOptions = options; // 更新当前类的映射选项 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/db/vikaModel/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sort-keys */ 2 | import type { Sheets } from './Model'; 3 | import { messageSheet } from './Message/mod'; 4 | import { keywordSheet } from './Keyword/mod'; 5 | import { sheet as envSheet } from './Env/mod'; 6 | import { sheet as statisticSheet } from './Statistic/mod'; 7 | import { sheet as contactSheet } from './Contact/mod'; 8 | import { sheet as qaSheet } from './Qa/mod'; 9 | import { roomSheet } from './Room/mod'; 10 | import { orderSheet } from './Order/mod'; 11 | // import contactWhiteListSheet from './ContactWhiteList' 12 | import { sheet as noticeSheet } from './Notice/mod'; 13 | // import groupSheet from './ContactGroup' 14 | import { sheet as whiteListSheet } from './WhiteList/mod'; 15 | import { stockSheet } from './Stock/mod'; 16 | import { sheet as groupNoticeSheet } from './GroupNotice/mod'; 17 | import { sheet as chatBotSheet } from './ChatBot/mod'; 18 | import { sheet as chatBotUserSheet } from './ChatBotUser/mod'; 19 | import { sheet as groupSheet } from './Group/mod'; 20 | import { sheet as welcomeSheet } from './Welcome/mod'; 21 | import { sheet as mediaSheet } from './Media/mod'; 22 | import { sheet as carpoolingSheet } from './Carpooling/mod'; 23 | 24 | const sheets: Sheets = { 25 | qaSheet, 26 | orderSheet, 27 | keywordSheet, 28 | envSheet, 29 | contactSheet, 30 | roomSheet, 31 | whiteListSheet, 32 | noticeSheet, 33 | statisticSheet, 34 | groupNoticeSheet, 35 | messageSheet, 36 | chatBotSheet, 37 | chatBotUserSheet, 38 | groupSheet, 39 | welcomeSheet, 40 | mediaSheet, 41 | carpoolingSheet, 42 | // stockSheet, 43 | // switchSheet, 44 | // roomWhiteListSheet, 45 | // contactWhiteListSheet, 46 | }; 47 | 48 | export { sheets, stockSheet }; 49 | 50 | export default sheets; 51 | -------------------------------------------------------------------------------- /src/db/vikaModel/Qa/typeGenerator.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import fs from 'fs'; 3 | import path from 'path'; 4 | 5 | // 创建一个获取当前目录的函数 6 | const getCurrentDir = () => { 7 | const err = new Error(); 8 | const stack = err.stack?.split('\n'); 9 | // 寻找调用这个函数的位置 10 | const callerLine = stack?.find((line) => line.includes('at')); 11 | const match = callerLine?.match(/\((.*):[0-9]+:[0-9]+\)/); 12 | return match ? path.dirname(match[1] || '') : undefined; 13 | }; 14 | 15 | // 使用函数获取当前目录 16 | const currentDir = getCurrentDir() || ''; 17 | 18 | // 读取qa.json文件 19 | const modelFields = JSON.parse( 20 | fs.readFileSync(path.join(currentDir, 'fields.json'), 'utf-8'), 21 | ); 22 | 23 | // 初始化一个空字符串来保存生成的TypeScript类型 24 | let tsType = 'export type FieldsData = {\n'; 25 | 26 | // 遍历字段生成TypeScript类型 27 | modelFields.data.fields.forEach((field: { name: string }) => { 28 | const nameParts: any = field.name.split('|'); 29 | if (nameParts.length === 2) { 30 | const tsKey: string = nameParts[1]; 31 | const isRequired = nameParts[0].includes('必填'); 32 | tsType += ` ${tsKey}${isRequired ? '' : '?'}: string;\n`; 33 | } 34 | }); 35 | 36 | tsType += '};\n'; 37 | 38 | tsType += '\nexport type NamesData = {\n'; 39 | 40 | // 遍历字段生成TypeScript类型 41 | modelFields.data.fields.forEach((field: { name: string }) => { 42 | const tsKey: string = field.name; 43 | const isRequired = tsKey.includes('必填'); 44 | tsType += `'${tsKey}${isRequired ? '' : '?'}': string;\n`; 45 | }); 46 | 47 | tsType += '};\n'; 48 | 49 | // 将生成的类型写入到qa.ts文件 50 | fs.writeFile(path.join(currentDir, 'types.ts'), tsType, 'utf8', (writeErr) => { 51 | if (writeErr) { 52 | console.error('Error writing the file:', writeErr); 53 | } else { 54 | console.log('Successfully wrote to types.ts'); 55 | } 56 | }); 57 | -------------------------------------------------------------------------------- /src/db/vikaModel/actionBar.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sort-keys */ 2 | import { acitonFields } from './acitonFields'; 3 | const fieldsOnly = acitonFields.data.fields; 4 | 5 | interface CustomObject { 6 | name: string; 7 | value?: string; 8 | [key: string]: any; 9 | } 10 | 11 | export const actionState = { 12 | qaSheet: true, 13 | orderSheet: false, 14 | keywordSheet: false, 15 | envSheet: true, 16 | contactSheet: false, 17 | roomSheet: false, 18 | whiteListSheet: true, 19 | noticeSheet: true, 20 | statisticSheet: true, 21 | groupNoticeSheet: true, 22 | messageSheet: false, 23 | chatBotSheet: false, 24 | chatBotUserSheet: false, 25 | groupSheet: false, 26 | welcomeSheet: true, 27 | mediaSheet: true, 28 | carpoolingSheet: false, 29 | }; 30 | 31 | export function replaceSyncStatus(fields: CustomObject[]): CustomObject[] { 32 | // 替换已定义的属性 33 | const newfields: any = fields.map((item) => { 34 | if (item.name === '同步状态|syncStatus') { 35 | const replacement = fieldsOnly.find( 36 | (bItem: { name: string }) => bItem.name === '同步状态|syncStatus', 37 | ); 38 | return replacement; 39 | } 40 | 41 | if (item.name === '最后操作时间|lastOperationTime') { 42 | const replacement = fieldsOnly.find( 43 | (bItem: { name: string }) => 44 | bItem.name === '最后操作时间|lastOperationTime', 45 | ); 46 | return replacement; 47 | } 48 | 49 | if (item.name === '操作|action') { 50 | const replacement = fieldsOnly.find( 51 | (bItem: { name: string }) => bItem.name === '操作|action', 52 | ); 53 | return replacement; 54 | } 55 | 56 | return item; 57 | }); 58 | 59 | // 补充缺失的属性 60 | for (const field of fieldsOnly) { 61 | if ( 62 | [ 63 | '同步状态|syncStatus', 64 | '操作|action', 65 | '最后操作时间|lastOperationTime', 66 | ].includes(field.name) 67 | ) { 68 | const replacement = newfields.find( 69 | (bItem: any) => bItem.name === field.name, 70 | ); 71 | if (!replacement) { 72 | newfields.push(field); 73 | } 74 | } 75 | } 76 | return newfields; 77 | } 78 | -------------------------------------------------------------------------------- /src/db/vikaModel/Notice/fields.json: -------------------------------------------------------------------------------- 1 | {"code":200,"success":true,"data":{"fields":[{"id":"fldHwSik2Eolp","name":"内容|desc","type":"Text","editable":true,"isPrimary":true},{"id":"fldxCZRZfTDn6","name":"通知目标类型|type","type":"SingleSelect","property":{"options":[{"id":"optGyHAfqtB7r","name":"好友","color":{"name":"deepPurple_0","value":"#E5E1FC"}},{"id":"optKYW1f87glJ","name":"群","color":{"name":"indigo_0","value":"#DDE7FF"}}]},"editable":true},{"id":"flds6abj33bLu","name":"昵称/群名称|name","type":"SingleText","property":{"defaultValue":""},"editable":true},{"id":"fld3YJmN9LacI","name":"好友ID/群ID(选填)|id","type":"SingleText","property":{"defaultValue":""},"editable":true},{"id":"fldIf0aM9UZQD","name":"好友备注(选填)|alias","type":"SingleText","property":{},"editable":true},{"id":"fldSJwhk9I0cN","name":"时间|time","type":"DateTime","property":{"format":"YYYY-MM-DD HH:mm","includeTime":true,"autoFill":true},"editable":true},{"id":"fldT5ozWV1cYy","name":"周期|cycle","type":"SingleSelect","property":{"options":[{"id":"optU9qx2FaT9M","name":"无重复","color":{"name":"deepPurple_0","value":"#E5E1FC"}},{"id":"opt75gotqemx6","name":"每天","color":{"name":"indigo_0","value":"#DDE7FF"}},{"id":"optQZx96GV6G7","name":"每周","color":{"name":"blue_0","value":"#DDF5FF"}},{"id":"opt6D3HqKELUp","name":"每月","color":{"name":"teal_0","value":"#D6F3E8"}},{"id":"optY4Rgw9pckJ","name":"每小时","color":{"name":"green_0","value":"#DCF3D1"}},{"id":"optNK4nRKpArH","name":"每分钟","color":{"name":"yellow_0","value":"#FFF6D8"}},{"id":"optQxlAbChMus","name":"每5分钟","color":{"name":"orange_0","value":"#FFEECC"}},{"id":"optLG0qdcQdV4","name":"每10分钟","color":{"name":"tangerine_0","value":"#FFE4CC"}},{"id":"optg7XF0JZOwl","name":"每15分钟","color":{"name":"pink_0","value":"#FFE2E8"}},{"id":"optHsQpqBKrjz","name":"每30分钟","color":{"name":"red_0","value":"#F9D8D7"}},{"id":"optirXg6VoJuy","name":"每季度","color":{"name":"deepPurple_0","value":"#E5E1FC"}}]},"editable":true},{"id":"fldlANZT8mFsn","name":"启用状态|state","type":"SingleSelect","property":{"options":[{"id":"opt3h2FSNwAhs","name":"开启","color":{"name":"deepPurple_0","value":"#E5E1FC"}},{"id":"optZ7OiRN3xdQ","name":"关闭","color":{"name":"indigo_0","value":"#DDE7FF"}}]},"editable":true}]},"message":"SUCCESS"} -------------------------------------------------------------------------------- /src/db/vikaModel/Media/fields.ts: -------------------------------------------------------------------------------- 1 | export const vikaFields = { 2 | code: 200, 3 | success: true, 4 | data: { 5 | fields: [ 6 | { 7 | id: 'fldkTrMcb63oY', 8 | name: '名字', 9 | type: 'SingleText', 10 | property: {}, 11 | editable: true, 12 | isPrimary: true, 13 | }, 14 | { 15 | id: 'fldXrv3ioaJgK', 16 | name: '类型', 17 | type: 'SingleSelect', 18 | property: { 19 | options: [ 20 | { 21 | id: 'optzOM0Xof9Ln', 22 | name: '动漫番剧', 23 | color: { name: 'deepPurple_0', value: '#E5E1FC' }, 24 | }, 25 | { 26 | id: 'opt8qyV17YhtJ', 27 | name: '电视剧', 28 | color: { name: 'indigo_0', value: '#DDE7FF' }, 29 | }, 30 | { 31 | id: 'opt8qyV17YhtJ', 32 | name: '短剧', 33 | color: { name: 'indigo_0', value: '#DDE7FF' }, 34 | }, 35 | { 36 | id: 'opt8qyV17YhtJ', 37 | name: '小说', 38 | color: { name: 'indigo_0', value: '#DDE7FF' }, 39 | }, 40 | ], 41 | }, 42 | editable: true, 43 | }, 44 | { 45 | id: 'fldAj0kCgDIDT', 46 | name: '链接', 47 | type: 'Text', 48 | editable: true, 49 | }, 50 | { 51 | id: 'fldAj0kCgDIDT', 52 | name: '链接1', 53 | type: 'Text', 54 | editable: true, 55 | }, 56 | { 57 | id: 'fldXrv3ioaJgK', 58 | name: '启用状态|state', 59 | type: 'SingleSelect', 60 | property: { 61 | options: [ 62 | { 63 | id: 'optzOM0Xof9Ln', 64 | name: '开启', 65 | color: { name: 'deepPurple_0', value: '#E5E1FC' }, 66 | }, 67 | { 68 | id: 'opt8qyV17YhtJ', 69 | name: '关闭', 70 | color: { name: 'indigo_0', value: '#DDE7FF' }, 71 | }, 72 | ], 73 | }, 74 | editable: true, 75 | }, 76 | ], 77 | }, 78 | message: 'SUCCESS', 79 | }; 80 | -------------------------------------------------------------------------------- /src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Request, 5 | Post, 6 | Get, 7 | // UseGuards, 8 | Query, 9 | HttpCode, 10 | HttpStatus, 11 | } from '@nestjs/common'; 12 | import { AppService } from './app.service'; 13 | // import { AuthGuard } from '@nestjs/passport'; 14 | import { AuthService } from './modules/auth/auth.service'; 15 | // import { LocalAuthGuard } from './modules/auth/local-auth.guard'; 16 | import { Public } from './modules/auth/decorators/public.decorator'; 17 | 18 | @Controller() 19 | export class AppController { 20 | constructor( 21 | private readonly appService: AppService, 22 | private readonly authService: AuthService, 23 | ) {} 24 | 25 | @Get() 26 | getHello(): string { 27 | return this.appService.getHello(); 28 | } 29 | 30 | @Public() 31 | @HttpCode(HttpStatus.OK) 32 | @Post('auth/login') 33 | async login(@Body() signInDto: Record) { 34 | const access_token_res = this.authService.signIn( 35 | signInDto.username, 36 | signInDto.password, 37 | ); 38 | return { 39 | code: 200, 40 | message: 'success', 41 | data: { 42 | access_token: (await access_token_res).access_token, 43 | expires_in: 36000000, 44 | type: 'Bearer', 45 | }, 46 | }; 47 | } 48 | 49 | @Get('profile') 50 | getProfile(@Request() req: any) { 51 | const user = { 52 | code: 200, 53 | message: 'success', 54 | data: { 55 | avatar: 56 | 'https://im.gzydong.com/public/media/image/avatar/20230530/f76a14ce98ca684752df742974f5473a_200x200.png', 57 | birthday: '2023-06-11', 58 | email: '837215079@qq.com', 59 | gender: 2, 60 | id: 2055, 61 | mobile: '13800138000', 62 | motto: '...', 63 | nickname: '老牛逼了', 64 | }, 65 | }; 66 | user.data.nickname = req.user.username; 67 | user.data.id = req.user.userId; 68 | return user; 69 | } 70 | 71 | @Public() 72 | @Get('auth/loginGet') 73 | async loginGet(@Query() query: any, @Request() req: any) { 74 | console.info(query); 75 | return this.authService.login(req.user); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/modules/auth/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Post, Body } from '@nestjs/common'; 2 | import { AuthService } from '../auth/auth.service'; 3 | import { Public } from './decorators/public.decorator'; 4 | 5 | @Controller('api/v1/auth') 6 | export class AuthController { 7 | constructor(private readonly authService: AuthService) {} 8 | @Public() 9 | @Post('login') 10 | async login( 11 | @Body() signInDto: { password: string; mobile: string; platform?: string }, 12 | ) { 13 | console.log(signInDto); 14 | 15 | const access_token_res = await this.authService.signIn( 16 | signInDto.mobile, 17 | signInDto.password, 18 | ); 19 | console.info(access_token_res); 20 | if (access_token_res.access_token) { 21 | return { 22 | code: 200, 23 | message: 'success', 24 | data: { 25 | access_token: access_token_res.access_token, 26 | expires_in: 36000000, 27 | type: 'Bearer', 28 | }, 29 | }; 30 | } else { 31 | return { 32 | code: 400, 33 | message: '登录用户名或密码填写错误! ', 34 | meta: { 35 | error_line: 41, 36 | }, 37 | }; 38 | } 39 | } 40 | 41 | @Public() 42 | @Post('init') 43 | async init( 44 | @Body() signInDto: { password: string; mobile: string; platform?: string }, 45 | ) { 46 | console.log(signInDto); 47 | const token = signInDto.password; 48 | const spaceId = signInDto.mobile; 49 | try { 50 | const res = await this.authService.init(spaceId, token); 51 | console.info('init res:', res); 52 | if (res?.message === 'success') { 53 | return { 54 | code: 200, 55 | message: 'success', 56 | data: res.data, 57 | }; 58 | } else { 59 | return { 60 | code: 400, 61 | message: '初始化失败! ', 62 | data: res, 63 | }; 64 | } 65 | } catch (e) { 66 | console.error(e); 67 | return { 68 | code: 400, 69 | message: '初始化失败! ', 70 | data: e, 71 | }; 72 | } 73 | } 74 | 75 | @Post('logout') 76 | async logout(@Body() body: any) { 77 | console.info(body); 78 | return { 79 | code: 200, 80 | message: 'success', 81 | data: {}, 82 | }; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/modules/carpoolings/carpoolings.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Get, 5 | Post, 6 | Query, 7 | Request, 8 | UnauthorizedException, 9 | } from '@nestjs/common'; 10 | import { Store } from '../../db/store.js'; 11 | 12 | @Controller('api/v1/carpooling') 13 | export class CarpoolingsController { 14 | @Get('list') 15 | async findAll(@Request() req: any, @Query() query: any): Promise { 16 | const user = req.user; 17 | // console.debug(user); 18 | // console.debug(Store.users); 19 | const db = Store.findUser(user.userId); 20 | if (!db) { 21 | throw new UnauthorizedException(); 22 | } 23 | // console.debug(db); 24 | let data; 25 | if (query.keyword) { 26 | data = await db.db.carpooling.findByQuery(query.keyword); 27 | } else { 28 | data = await db.db.carpooling.findAll(); 29 | } 30 | const res: any = { 31 | code: 400, 32 | message: 'error', 33 | data, 34 | }; 35 | if (data.data.length) { 36 | const items = data.data.map((value: any) => { 37 | const fields = value.fields; 38 | fields.recordId = value.recordId; 39 | return fields; 40 | }); 41 | res.data = { 42 | page: 1, 43 | pageSize: 1000, 44 | pageCount: 1, 45 | itemCount: data.data.length, 46 | items: items, 47 | }; 48 | } 49 | return res; 50 | } 51 | @Post('create') 52 | async create(@Body() body: any, @Request() req: any): Promise { 53 | console.debug('carpooling create', body); 54 | const user = req.user; 55 | // console.debug(user); 56 | // console.debug(Store.users); 57 | const db = Store.findUser(user.userId); 58 | if (!db) { 59 | throw new UnauthorizedException(); 60 | } 61 | // console.debug(db); 62 | const res: any = { code: 400, message: 'fail', data: {} }; 63 | try { 64 | const resCreate = await db.db.carpooling.create(body); 65 | res.data = resCreate; 66 | console.debug('resCreate', resCreate); 67 | if (resCreate.data.recordId) { 68 | res.code = 200; 69 | res.message = 'success'; 70 | res.data = resCreate; 71 | } 72 | } catch (e) { 73 | console.error(e); 74 | res.message = 'error'; 75 | res.data = e; 76 | } 77 | return res; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | ChatFlow Admin Launcher 9 | 39 | 40 | 41 | 42 |

一键启动API服务

43 | ChatFlow Admin API服务管理器 44 |

45 | 46 | 47 | 48 |
49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/db/vikaModel/Carpooling/db.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, MappingOptions } from '../../mod.js'; 2 | 3 | export class Carpoolings extends BaseEntity { 4 | /** 5 | * Type (车找人, 人找车) 6 | */ 7 | type: '车找人' | '人找车'; 8 | /** 9 | * Departure location 10 | */ 11 | departureLocation: string; 12 | /** 13 | * Destination 14 | */ 15 | destination: string; 16 | /** 17 | * Departure date 18 | */ 19 | departureDate: string; 20 | /** 21 | * Departure time 22 | */ 23 | departureTime: string; 24 | /** 25 | * Contact phone 26 | */ 27 | contactPhone: string; 28 | /** 29 | * Publisher 30 | */ 31 | publisher: string; 32 | /** 33 | * Car fee 34 | */ 35 | carFee: string; 36 | /** 37 | * Route 38 | */ 39 | route: string; 40 | 41 | text: string; 42 | topic: string; 43 | roomId: string; 44 | wxid: string; 45 | createdAt: string; 46 | 47 | state: '开启' | '关闭'; 48 | 49 | // protected static override recordId: string = '' // 定义记录ID,初始为空字符串 50 | 51 | protected override mappingOptions: MappingOptions = { 52 | // 定义字段映射选项 53 | fieldMapping: { 54 | // 字段映射 55 | /** 56 | * Type (车找人, 人找车) 57 | */ 58 | type: '类型', 59 | /** 60 | * Departure location 61 | */ 62 | departureLocation: '出发地', 63 | /** 64 | * Destination 65 | */ 66 | destination: '目的地', 67 | /** 68 | * Departure date 69 | */ 70 | departureDate: '出发日期', 71 | /** 72 | * Departure time 73 | */ 74 | departureTime: '出发时间', 75 | /** 76 | * Contact phone 77 | */ 78 | contactPhone: '联系电话', 79 | /** 80 | * Publisher 81 | */ 82 | publisher: '发布人', 83 | /** 84 | * Car fee 85 | */ 86 | carFee: '车费', 87 | /** 88 | * Route 89 | */ 90 | route: '途经路线', 91 | text: '原始消息', 92 | topic: '群名称', 93 | roomId: '群ID', 94 | wxid: '发布者ID', 95 | createdAt: '创建时间', 96 | state: '状态', 97 | }, 98 | tableName: '顺风车|Carpooling', // 表名 99 | }; 100 | 101 | protected override getMappingOptions(): MappingOptions { 102 | // 获取映射选项的方法 103 | return this.mappingOptions; // 返回当前类的映射选项 104 | } 105 | 106 | override setMappingOptions(options: MappingOptions) { 107 | // 设置映射选项的方法 108 | this.mappingOptions = options; // 更新当前类的映射选项 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/modules/chats/chats.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { v4 } from 'uuid'; 3 | 4 | @Injectable() 5 | export class ChatsService { 6 | static formatMsgToWechaty(data: any) { 7 | // {"type":"text","content":"ok","quote_id":"","mention":{"all":0,"uids":[]},"receiver":{"receiver_id":"wxid_pnza7m7kf9tq12","talk_type":1}} 8 | // {"type":"image","width":1024,"height":1024,"url":"https://im.gzydong.com/public/media/image/common/20231030/2143db60700049fd68ab44263cd8b2cc_1024x1024.png","size":10000,"receiver":{"receiver_id":"20889085065@chatroom","talk_type":2}} 9 | const msg_type = data.type; 10 | let messageType: any = 'Text'; 11 | let messagePayload = ''; 12 | 13 | switch (msg_type) { 14 | case 'text': 15 | messageType = 'Text'; 16 | messagePayload = data.content; 17 | break; 18 | case 'code': 19 | messageType = 'Text'; 20 | messagePayload = '```' + data.lang + '\n' + data.code + '\n' + '```'; 21 | break; 22 | case 'image': 23 | messagePayload = data.url; 24 | messageType = 'Image'; 25 | break; 26 | case 'Emoticon': 27 | messageType = 'Text'; 28 | break; 29 | case 'ChatHistory': 30 | messageType = 'Text'; 31 | break; 32 | case 'Audio': 33 | messageType = 4; 34 | break; 35 | case 'Attachment': 36 | messageType = 6; 37 | break; 38 | case 'Video': 39 | messageType = 5; 40 | break; 41 | case 'MiniProgram': 42 | messageType = 1; 43 | break; 44 | case 'Url': 45 | messageType = 1; 46 | break; 47 | case 'Recalled': 48 | messageType = 1; 49 | break; 50 | case 'RedEnvelope': 51 | messageType = 1; 52 | break; 53 | case 'Contact': 54 | messageType = 1; 55 | break; 56 | case 'Location': 57 | messageType = 1; 58 | break; 59 | default: 60 | messageType = 'Text'; 61 | break; 62 | } 63 | const msg = { 64 | reqId: v4(), 65 | method: 'thing.command.invoke', 66 | version: '1.0', 67 | timestamp: new Date().getTime(), 68 | name: 'send', 69 | params: { 70 | toContacts: [ 71 | data.receiver.receiver_id, 72 | // "5550027590@chatroom", 73 | ], 74 | messageType: messageType, 75 | messagePayload: messagePayload, 76 | }, 77 | }; 78 | return JSON.stringify(msg); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /test/vika-orm-test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import 'dotenv/config.js'; 3 | import { BiTable } from '../src/db/vika-db.js'; 4 | import { BaseEntity, MappingOptions } from '../src/db/vika-orm.js'; 5 | 6 | const main = async () => { 7 | const db = new BiTable(); 8 | const dbInit = await db.init({ 9 | token: process.env.VIKA_TOKEN || '', 10 | spaceId: process.env.VIKA_SPACE_ID || '', 11 | }); 12 | 13 | console.log('dbInit:', dbInit); 14 | 15 | class Env extends BaseEntity { 16 | name?: string; 17 | 18 | key?: string; 19 | 20 | value?: string; 21 | 22 | desc?: string; 23 | 24 | syncStatus?: string; 25 | 26 | lastOperationTime?: string; 27 | 28 | action?: string; 29 | 30 | // protected static override recordId: string = '' // 定义记录ID,初始为空字符串 31 | 32 | protected override mappingOptions: MappingOptions = { 33 | // 定义字段映射选项 34 | fieldMapping: { 35 | // 字段映射 36 | name: '配置项|name', 37 | key: '标识|key', 38 | value: '值|value', 39 | desc: '说明|desc', 40 | syncStatus: '同步状态|syncStatus', 41 | lastOperationTime: '最后操作时间|lastOperationTime', 42 | action: '操作|action', 43 | }, 44 | tableName: '环境变量|Env', // 表名 45 | }; // 设置映射选项为上面定义的 mappingOptions 46 | 47 | protected override getMappingOptions(): MappingOptions { 48 | // 获取映射选项的方法 49 | return this.mappingOptions; // 返回当前类的映射选项 50 | } 51 | 52 | override setMappingOptions(options: MappingOptions) { 53 | // 设置映射选项的方法 54 | this.mappingOptions = options; // 更新当前类的映射选项 55 | } 56 | } 57 | 58 | // 测试 59 | const env = new Env(); 60 | await env.setVikaOptions({ 61 | apiKey: db.token, 62 | baseId: db.dataBaseIds.envSheet, // 设置 base ID 63 | }); 64 | 65 | const envData = await env.findAll(); 66 | console.log('envData:', JSON.stringify(envData, null, 2)); 67 | 68 | const demo = [ 69 | { 70 | recordId: 'rec0ofQXMfryc', 71 | createdAt: 1694860633000, 72 | updatedAt: 1697695907000, 73 | fields: { 74 | name: 'Wechaty-Puppet', 75 | key: 'WECHATY_PUPPET', 76 | value: 'wechaty-puppet-padlocal', 77 | desc: '可选值:\nwechaty-puppet-wechat4u\nwechaty-puppet-wechat\nwechaty-puppet-xp\nwechaty-puppet-engine\u0000\nwechaty-puppet-padlocal\nwechaty-puppet-service', 78 | syncStatus: '未同步', 79 | lastOperationTime: 1694860632945, 80 | action: '选择操作', 81 | }, 82 | }, 83 | ]; 84 | 85 | console.log('demo:', JSON.stringify(demo)); 86 | }; 87 | 88 | main(); 89 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | events { 2 | worker_connections 1024; 3 | } 4 | 5 | http { 6 | include mime.types; 7 | default_type application/octet-stream; 8 | client_max_body_size 10M; 9 | types { 10 | application/javascript js; 11 | } 12 | error_page 404 =301 https://chat.vlist.cc; 13 | server { 14 | listen 80; 15 | server_name chat.vlist.cc; 16 | 17 | # 重定向所有 HTTP 请求到 HTTPS 18 | return 301 https://$host$request_uri; 19 | } 20 | 21 | server { 22 | listen 443 ssl; 23 | server_name chat.vlist.cc; 24 | 25 | # SSL 证书和私钥的位置(根据您的实际路径替换) 26 | ssl_certificate chat.vlist.cc_bundle.crt; 27 | ssl_certificate_key chat.vlist.cc.key; 28 | 29 | # SSL 配置(根据需要调整) 30 | ssl_session_timeout 5m; 31 | ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3; 32 | ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; 33 | ssl_prefer_server_ciphers on; 34 | location ~* \.js$ { 35 | add_header Content-Type application/javescript; 36 | } 37 | location /api/v1 { 38 | proxy_pass http://chatflow-admin.com:9503; # 使用域名同时在Nginx配置中添加hosts 39 | } 40 | 41 | location / { 42 | root html; 43 | index index.html index.htm; 44 | 45 | # CORS 配置 46 | if ($request_method = 'OPTIONS') { 47 | add_header 'Access-Control-Allow-Origin' '*'; 48 | add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; 49 | add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; 50 | add_header 'Access-Control-Max-Age' 1728000; 51 | add_header 'Content-Type' 'text/plain; charset=utf-8'; 52 | add_header 'Content-Length' 0; 53 | return 204; 54 | } 55 | 56 | if ($request_method = 'POST') { 57 | add_header 'Access-Control-Allow-Origin' '*'; 58 | add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; 59 | add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; 60 | } 61 | 62 | if ($request_method = 'GET') { 63 | add_header 'Access-Control-Allow-Origin' '*'; 64 | add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; 65 | add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; 66 | } 67 | } 68 | } 69 | } 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/db/vikaModel/Keyword/records.json: -------------------------------------------------------------------------------- 1 | {"code":200,"success":true,"data":{"total":15,"records":[{"recordId":"recLbBbCd91mb","createdAt":1694140017000,"updatedAt":1694170942000,"fields":{"类型|type":"系统指令","说明|desc":"获得操作指令集","指令名称|name":"帮助","详细说明|details":"获得操作指令集"}},{"recordId":"recvHeSWmXZvk","createdAt":1694140017000,"updatedAt":1694170942000,"fields":{"类型|type":"系统指令","说明|desc":"更新系统环境变量配置","指令名称|name":"更新配置","详细说明|details":"更新系统配置,更改配置后需主动更新一次配置配置才会生效"}},{"recordId":"rec3hlDeA8LAw","createdAt":1694140017000,"updatedAt":1694170942000,"fields":{"类型|type":"系统指令","说明|desc":"更新定时提醒任务","指令名称|name":"更新定时提醒","详细说明|details":"更新机器人的群列表和好友列表"}},{"recordId":"reclI661i824K","createdAt":1694140017000,"updatedAt":1694170942000,"fields":{"类型|type":"系统指令","说明|desc":"更新机器人的群列表和好友列表","指令名称|name":"更新通讯录","详细说明|details":"下载通讯录xlsx表"}},{"recordId":"recnkrZdO3KGS","createdAt":1694140017000,"updatedAt":1694170942000,"fields":{"类型|type":"系统指令","说明|desc":"更新群白名单","指令名称|name":"更新白名单","详细说明|details":"下载通知模板"}},{"recordId":"recZF6aKkfKtI","createdAt":1694171389000,"updatedAt":1694171594000,"fields":{"类型|type":"系统指令","说明|desc":"更新活动列表","指令名称|name":"更新活动","详细说明|details":"更新活跃的活动到数据库"}},{"recordId":"recVX8alc15cS","createdAt":1694171412000,"updatedAt":1694171548000,"fields":{"类型|type":"系统指令","说明|desc":"群发通知给群或好友","指令名称|name":"群发通知","详细说明|details":"将群发通知表中状态为待发送状态的全部消息进行群发"}},{"recordId":"rec4eHtkSLeVN","createdAt":1694160064000,"updatedAt":1694170942000,"fields":{"类型|type":"系统指令","说明|desc":"问答列表更新到微信对话开放平台","指令名称|name":"更新问答","详细说明|details":"更新定时提醒任务"}},{"recordId":"reccHwKYG3fJv","createdAt":1694171458000,"updatedAt":1694171575000,"fields":{"类型|type":"系统指令","说明|desc":"下载通讯录csv表","指令名称|name":"下载csv通讯录","详细说明|details":"下载csv格式的通讯录,包括群和好友"}},{"recordId":"recsjHdtuyRth","createdAt":1694140017000,"updatedAt":1694170942000,"fields":{"类型|type":"系统指令","说明|desc":"下载通讯录xlsx表","指令名称|name":"下载通讯录","详细说明|details":"更新群白名单,白名单变动时需主动更新白名单"}},{"recordId":"rec2gI3L9wAOe","createdAt":1694140017000,"updatedAt":1694170942000,"fields":{"类型|type":"系统指令","说明|desc":"下载通知模板","指令名称|name":"下载通知模板","详细说明|details":"TBD当前群启用智能问答"}},{"recordId":"recZxLJbwXCiH","createdAt":1694140017000,"updatedAt":1694170942000,"fields":{"类型|type":"群指令","说明|desc":"TBD当前群启用智能问答","指令名称|name":"启用问答","详细说明|details":"TBD当前群关闭智能问答"}},{"recordId":"recgsLQC7pZV4","createdAt":1694140017000,"updatedAt":1694170942000,"fields":{"类型|type":"群指令","说明|desc":"TBD当前群关闭智能问答","指令名称|name":"关闭问答","详细说明|details":"筛选车找人信息"}},{"recordId":"recRNDWA0X5km","createdAt":1694140017000,"updatedAt":1694170942000,"fields":{"类型|type":"等于关键字","说明|desc":"筛选车找人信息","指令名称|name":"车找人","详细说明|details":"匹配人找车信息"}},{"recordId":"recgmfoHSUNO7","createdAt":1694140018000,"updatedAt":1694140018000,"fields":{"类型|type":"包含关键字","说明|desc":"匹配人找车信息","指令名称|name":"人找车"}}],"pageNum":1,"pageSize":15},"message":"SUCCESS"} -------------------------------------------------------------------------------- /src/db/vikaModel/Contact/mod.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sort-keys */ 2 | 3 | import type { 4 | Sheet, 5 | // Field, 6 | } from '../Model'; 7 | 8 | import { replaceSyncStatus, actionState } from '../actionBar.js'; 9 | 10 | const name = '好友列表|Contact'; 11 | const code = 'contactSheet'; 12 | 13 | const vikaFields = { 14 | code: 200, 15 | success: true, 16 | data: { 17 | fields: [ 18 | { 19 | id: 'fldQkY67dBZ4e', 20 | name: '好友ID|id', 21 | type: 'SingleText', 22 | property: {}, 23 | editable: true, 24 | isPrimary: true, 25 | }, 26 | { 27 | id: 'fldsQQJOJHIkX', 28 | name: '好友昵称|name', 29 | type: 'SingleText', 30 | property: { defaultValue: '' }, 31 | editable: true, 32 | }, 33 | { 34 | id: 'fldmnMANJ1IqN', 35 | name: '备注名称|alias', 36 | type: 'SingleText', 37 | property: { defaultValue: '' }, 38 | editable: true, 39 | }, 40 | { 41 | id: 'fld0CxvEhhO5A', 42 | name: '性别|gender', 43 | type: 'SingleText', 44 | property: { defaultValue: '' }, 45 | editable: true, 46 | }, 47 | { 48 | id: 'fldrAMLW8Ysg9', 49 | name: '更新时间|updated', 50 | type: 'SingleText', 51 | property: { defaultValue: '' }, 52 | editable: true, 53 | }, 54 | { 55 | id: 'fldZh6vCCEUb0', 56 | name: '是否好友|friend', 57 | type: 'Checkbox', 58 | property: { icon: '✅' }, 59 | editable: true, 60 | }, 61 | { 62 | id: 'fldj9vvNOciYB', 63 | name: '类型|type', 64 | type: 'SingleText', 65 | property: { defaultValue: '' }, 66 | editable: true, 67 | }, 68 | { 69 | id: 'fldkTLOkFxGdP', 70 | name: '头像|avatar', 71 | type: 'Text', 72 | editable: true, 73 | }, 74 | { 75 | id: 'fldysArm2EbRI', 76 | name: '手机号|phone', 77 | type: 'SingleText', 78 | property: { defaultValue: '' }, 79 | editable: true, 80 | }, 81 | { 82 | id: 'fldYxqREKJ3Or', 83 | name: '头像图片|file', 84 | type: 'Attachment', 85 | editable: true, 86 | }, 87 | ], 88 | }, 89 | message: 'SUCCESS', 90 | }; 91 | let fields: any = vikaFields.data.fields; 92 | 93 | if (actionState[code]) { 94 | fields = replaceSyncStatus(fields); 95 | } 96 | 97 | const defaultRecords = { 98 | code: 200, 99 | success: true, 100 | data: { total: 0, records: [], pageNum: 1, pageSize: 0 }, 101 | message: 'SUCCESS', 102 | }; 103 | 104 | export const sheet: Sheet = { 105 | fields, 106 | name, 107 | defaultRecords: defaultRecords.data.records, 108 | }; 109 | -------------------------------------------------------------------------------- /src/db/vikaModel/ChatBot/fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "success": true, 4 | "data": { 5 | "fields": [ 6 | { 7 | "id": "fldA47yx9L3bk", 8 | "name": "机器人ID|id", 9 | "type": "SingleText", 10 | "property": { 11 | "defaultValue": "" 12 | }, 13 | "editable": true, 14 | "isPrimary": true 15 | }, 16 | { 17 | "id": "fldPrcQ5QfhxV", 18 | "name": "昵称|botname", 19 | "type": "SingleText", 20 | "property": {}, 21 | "editable": true 22 | }, 23 | { 24 | "id": "fld6rYKTZ0zQV", 25 | "name": "用户ID|wxid", 26 | "type": "SingleText", 27 | "property": {}, 28 | "editable": true 29 | }, 30 | { 31 | "id": "fldxD8wwu1hGy", 32 | "name": "用户名称|name", 33 | "type": "SingleText", 34 | "property": {}, 35 | "editable": true 36 | }, 37 | { 38 | "id": "fld4P2sX0CHco", 39 | "name": "用户提示词|prompt", 40 | "type": "Text", 41 | "editable": true 42 | }, 43 | { 44 | "id": "fldguMiluobGu", 45 | "name": "配额|quota", 46 | "type": "Number", 47 | "property": { 48 | "precision": 0 49 | }, 50 | "editable": true 51 | }, 52 | { 53 | "id": "fldUcpFFLyMo7", 54 | "name": "启用状态|state", 55 | "type": "SingleSelect", 56 | "property": { 57 | "options": [ 58 | { 59 | "id": "optMTJ3j2fMr4", 60 | "name": "启用", 61 | "color": { 62 | "name": "deepPurple_0", 63 | "value": "#E5E1FC" 64 | } 65 | }, 66 | { 67 | "id": "opt4bTBzh2Mx6", 68 | "name": "禁用", 69 | "color": { 70 | "name": "indigo_0", 71 | "value": "#DDE7FF" 72 | } 73 | } 74 | ] 75 | }, 76 | "editable": true 77 | }, 78 | { 79 | "id": "fldsG69W4KzJa", 80 | "name": "备注|info", 81 | "type": "Text", 82 | "editable": true 83 | } 84 | ] 85 | }, 86 | "message": "SUCCESS" 87 | } -------------------------------------------------------------------------------- /src/modules/keywords/keywords.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Get, 4 | Post, 5 | Body, 6 | Request, 7 | UnauthorizedException, 8 | } from '@nestjs/common'; 9 | import { Store } from '../../db/store.js'; 10 | 11 | @Controller('api/v1/keyword') 12 | export class KeywordsController { 13 | @Get('list') 14 | async findAll(@Request() req: any): Promise { 15 | const user = req.user; 16 | // console.debug(user); 17 | // console.debug(Store.users); 18 | const db = Store.findUser(user.userId); 19 | if (!db) { 20 | throw new UnauthorizedException(); 21 | } 22 | // console.debug(db); 23 | const data = await db.db.keyword.findAll(); 24 | const items = data.data.map((value: any) => { 25 | const fields = value.fields; 26 | fields.recordId = value.recordId; 27 | return fields; 28 | }); 29 | // console.debug(data); 30 | const res: any = { 31 | code: 200, 32 | message: 'success', 33 | data: { 34 | page: 1, 35 | pageSize: 1000, 36 | pageCount: 1, 37 | itemCount: data.data.length, 38 | items: items, 39 | }, 40 | }; 41 | return res; 42 | } 43 | @Post('create') 44 | async create(@Body() body: any, @Request() req: any): Promise { 45 | const user = req.user; 46 | // console.debug(user); 47 | // console.debug(Store.users); 48 | const db = Store.findUser(user.userId); 49 | if (!db) { 50 | throw new UnauthorizedException(); 51 | } 52 | // console.debug(db); 53 | 54 | const resCreate = await db.db.keyword.create(body); 55 | console.debug('resCreate', resCreate); 56 | const res: any = { code: 400, message: 'fail', data: {} }; 57 | if (resCreate.data.recordId) { 58 | res.code = 200; 59 | res.message = 'success'; 60 | res.data = resCreate; 61 | } 62 | return res; 63 | } 64 | 65 | @Post('delete') 66 | async delete(@Body() body: any, @Request() req: any): Promise { 67 | // { 68 | // "recordId":21705 69 | // } 70 | console.debug('keyword delete', body); 71 | const user = req.user; 72 | // console.debug(user); 73 | // console.debug(Store.users); 74 | const db = Store.findUser(user.userId); 75 | if (!db) { 76 | throw new UnauthorizedException(); 77 | } 78 | // console.debug(db); 79 | 80 | const resDel = await db.db.keyword.delete(body.recordId); 81 | console.debug('keyword resDel', resDel); 82 | 83 | let res: any = ''; 84 | if (resDel.message === 'success') { 85 | res = { 86 | code: 200, 87 | message: 'success', 88 | data: {}, 89 | }; 90 | } else { 91 | res = { 92 | code: 400, 93 | message: 'error', 94 | data: {}, 95 | }; 96 | } 97 | return res; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/db/vikaModel/Contact/fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "success": true, 4 | "data": { 5 | "fields": [ 6 | { 7 | "id": "fldQkY67dBZ4e", 8 | "name": "好友ID|id", 9 | "type": "SingleText", 10 | "property": {}, 11 | "editable": true, 12 | "isPrimary": true 13 | }, 14 | { 15 | "id": "fldsQQJOJHIkX", 16 | "name": "好友昵称|name", 17 | "type": "SingleText", 18 | "property": { 19 | "defaultValue": "" 20 | }, 21 | "editable": true 22 | }, 23 | { 24 | "id": "fldmnMANJ1IqN", 25 | "name": "备注名称|alias", 26 | "type": "SingleText", 27 | "property": { 28 | "defaultValue": "" 29 | }, 30 | "editable": true 31 | }, 32 | { 33 | "id": "fld0CxvEhhO5A", 34 | "name": "性别|gender", 35 | "type": "SingleText", 36 | "property": { 37 | "defaultValue": "" 38 | }, 39 | "editable": true 40 | }, 41 | { 42 | "id": "fldrAMLW8Ysg9", 43 | "name": "更新时间|updated", 44 | "type": "SingleText", 45 | "property": { 46 | "defaultValue": "" 47 | }, 48 | "editable": true 49 | }, 50 | { 51 | "id": "fldZh6vCCEUb0", 52 | "name": "是否好友|friend", 53 | "type": "Checkbox", 54 | "property": { 55 | "icon": "✅" 56 | }, 57 | "editable": true 58 | }, 59 | { 60 | "id": "fldj9vvNOciYB", 61 | "name": "类型|type", 62 | "type": "SingleText", 63 | "property": { 64 | "defaultValue": "" 65 | }, 66 | "editable": true 67 | }, 68 | { 69 | "id": "fldkTLOkFxGdP", 70 | "name": "头像|avatar", 71 | "type": "Text", 72 | "editable": true 73 | }, 74 | { 75 | "id": "fldysArm2EbRI", 76 | "name": "手机号|phone", 77 | "type": "SingleText", 78 | "property": { 79 | "defaultValue": "" 80 | }, 81 | "editable": true 82 | }, 83 | { 84 | "id": "fldYxqREKJ3Or", 85 | "name": "头像图片|file", 86 | "type": "Attachment", 87 | "editable": true 88 | } 89 | ] 90 | }, 91 | "message": "SUCCESS" 92 | } -------------------------------------------------------------------------------- /src/db/vikaModel/Order/mod.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sort-keys */ 2 | 3 | /* eslint-disable sort-keys */ 4 | 5 | import type { 6 | Sheet, 7 | // Field, 8 | } from '../Model'; 9 | 10 | const vikaFields = { 11 | code: 200, 12 | success: true, 13 | data: { 14 | fields: [ 15 | { 16 | id: 'fldfVJETOZGFh', 17 | name: '编号|serialNumber', 18 | type: 'SingleText', 19 | property: {}, 20 | editable: true, 21 | isPrimary: true, 22 | }, 23 | { 24 | id: 'fldg36krPnZRM', 25 | name: '活动编号|code', 26 | type: 'SingleText', 27 | property: {}, 28 | editable: true, 29 | }, 30 | { 31 | id: 'fldjWxmF6I6z8', 32 | name: '活动描述|desc', 33 | type: 'Text', 34 | editable: true, 35 | }, 36 | { 37 | id: 'fld0RUDgKs2lo', 38 | name: '昵称|name', 39 | type: 'SingleText', 40 | property: {}, 41 | editable: true, 42 | }, 43 | { 44 | id: 'fldBL0jp8pFuS', 45 | name: '备注名称(选填)|alias', 46 | type: 'SingleText', 47 | property: {}, 48 | editable: true, 49 | }, 50 | { 51 | id: 'fldXO7uQw6c1L', 52 | name: '好友ID(选填)|wxid', 53 | type: 'SingleText', 54 | property: {}, 55 | editable: true, 56 | }, 57 | { 58 | id: 'fldWsOpoiX9r4', 59 | name: '群名称|topic', 60 | type: 'SingleText', 61 | property: {}, 62 | editable: true, 63 | }, 64 | { 65 | id: 'fldKsSCeStEsI', 66 | name: '创建时间|createdAt', 67 | type: 'DateTime', 68 | property: { 69 | format: 'YYYY-MM-DD HH:mm', 70 | includeTime: true, 71 | autoFill: true, 72 | }, 73 | editable: true, 74 | }, 75 | { id: 'fldfP5qQKvF24', name: '备注|info', type: 'Text', editable: true }, 76 | ], 77 | }, 78 | message: 'SUCCESS', 79 | }; 80 | 81 | const defaultRecords = { 82 | code: 200, 83 | success: true, 84 | message: 'Request successful', 85 | data: { 86 | total: 1, 87 | pageNum: 1, 88 | pageSize: 1, 89 | records: [ 90 | // { 91 | // recordId: 'rechd25gDKrw7', 92 | // fields: { 93 | // '编号|serialNumber': '123', 94 | // '活动编号|code': '4', 95 | // '活动描述|desc': '示例活动', 96 | // '昵称|name': '大师', 97 | // '备注名称(选填)|alias': 'chatflow作者', 98 | // '好友ID(选填)|wxid': 'ledongmao', 99 | // '群名称|topic': '瓦力是群主', 100 | // '创建时间|createdAt': '1702522321914', 101 | // '备注|info': '示例订单信息', 102 | // }, 103 | // }, 104 | ], 105 | }, 106 | }; 107 | 108 | export const orderSheet: Sheet = { 109 | fields: vikaFields.data.fields, 110 | name: '记录单|Order', 111 | defaultRecords: defaultRecords.data.records, 112 | }; 113 | -------------------------------------------------------------------------------- /src/modules/orders/orders.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Get, 4 | Post, 5 | Body, 6 | Request, 7 | UnauthorizedException, 8 | } from '@nestjs/common'; 9 | import { Store } from '../../db/store.js'; 10 | 11 | @Controller('api/v1/order') 12 | export class OrdersController { 13 | @Get('list') 14 | async findAll(@Request() req: any): Promise { 15 | const user = req.user; 16 | // console.debug(user); 17 | // console.debug(Store.users); 18 | const db = Store.findUser(user.userId); 19 | if (!db) { 20 | throw new UnauthorizedException(); 21 | } 22 | // console.debug(db); 23 | const data = await db.db.order.findAll(); 24 | const items = data.data.map((value: any) => { 25 | const fields = value.fields; 26 | fields.recordId = value.recordId; 27 | return fields; 28 | }); 29 | // console.debug(data); 30 | const res: any = { 31 | code: 200, 32 | message: 'success', 33 | data: { 34 | page: 1, 35 | pageSize: 1000, 36 | pageCount: 1, 37 | itemCount: data.data.length, 38 | items: items, 39 | }, 40 | }; 41 | return res; 42 | } 43 | @Post('create') 44 | async create(@Body() body: any, @Request() req: any): Promise { 45 | const user = req.user; 46 | // console.debug(user); 47 | // console.debug(Store.users); 48 | const db = Store.findUser(user.userId); 49 | if (!db) { 50 | throw new UnauthorizedException(); 51 | } 52 | // console.debug(db); 53 | 54 | const resCreate = await db.db.order.create(body); 55 | console.debug('resCreate', resCreate); 56 | const res: any = { code: 400, message: 'fail', data: {} }; 57 | if (resCreate.data.recordId) { 58 | res.code = 200; 59 | res.message = 'success'; 60 | res.data = resCreate; 61 | } 62 | return res; 63 | } 64 | @Post('delete') 65 | async delete(@Body() body: any, @Request() req: any): Promise { 66 | // { 67 | // "recordId":21705 68 | // } 69 | console.debug('order delete', body); 70 | const user = req.user; 71 | // console.debug(user); 72 | // console.debug(Store.users); 73 | const db = Store.findUser(user.userId); 74 | if (!db) { 75 | throw new UnauthorizedException(); 76 | } 77 | // console.debug(db); 78 | 79 | const resDel = await db.db.order.delete(body.recordId); 80 | console.debug('order resDel', resDel); 81 | 82 | let res: any = ''; 83 | if (resDel.message === 'success') { 84 | res = { 85 | code: 200, 86 | message: 'success', 87 | data: { 88 | recordId: body.recordId, 89 | }, 90 | }; 91 | } else { 92 | res = { 93 | code: 400, 94 | message: 'error', 95 | data: {}, 96 | }; 97 | } 98 | return res; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/modules/groups/groups.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Get, 4 | Request, 5 | Query, 6 | Post, 7 | Body, 8 | UnauthorizedException, 9 | } from '@nestjs/common'; 10 | import { Store } from '../../db/store.js'; 11 | 12 | @Controller('api/v1/contact/group') 13 | export class GroupsController { 14 | @Get('list') 15 | async findAllWhite( 16 | @Request() req: any, 17 | @Query() query: any, 18 | ): Promise { 19 | console.debug('list query:', query); 20 | const user = req.user; 21 | // console.debug(user); 22 | // console.debug(Store.users); 23 | const db = Store.findUser(user.userId); 24 | if (!db) { 25 | throw new UnauthorizedException(); 26 | } 27 | // console.debug(db); 28 | let data: any = []; 29 | if (query.fieldName && query.value) { 30 | data = await db.db.group.findByField(query.fieldName, query.value); 31 | } else { 32 | data = await db.db.group.findAll(); 33 | } 34 | const res: any = { 35 | code: 400, 36 | message: 'error', 37 | data, 38 | }; 39 | if (data.length) { 40 | const hash: any = {}; 41 | data.map((value: any) => { 42 | const fields = value.fields; 43 | fields.recordId = value.recordId; 44 | if (fields.groupName && hash[fields.groupName]) { 45 | hash[fields.groupName].count = hash[fields.groupName].count + 1; 46 | return null; 47 | } else { 48 | const sort = Object.keys(hash).length + 1; 49 | const item = { 50 | count: 1, 51 | id: sort, 52 | name: fields.groupName, 53 | sort, 54 | }; 55 | hash[fields.groupName] = item; 56 | return item; 57 | } 58 | }); 59 | 60 | // 将hash转换为items数组 61 | const items = Object.keys(hash).map((key: any) => hash[key]); 62 | 63 | items.unshift({ 64 | count: data.length, 65 | id: 0, 66 | name: '全部', 67 | sort: 0, 68 | }); 69 | 70 | res.code = 200; 71 | res.message = 'success'; 72 | res.data = { 73 | items, 74 | }; 75 | } 76 | return res; 77 | } 78 | @Post('/save') 79 | async create(@Body() body: any, @Request() req: any): Promise { 80 | console.debug('white create', body); 81 | const user = req.user; 82 | // console.debug(user); 83 | // console.debug(Store.users); 84 | const db = Store.findUser(user.userId); 85 | if (!db) { 86 | throw new UnauthorizedException(); 87 | } 88 | // console.debug(db); 89 | const res: any = { code: 400, message: 'fail', data: {} }; 90 | try { 91 | const resCreate = await db.db.group.create(body); 92 | res.data = resCreate; 93 | console.debug('resCreate', resCreate); 94 | if (resCreate.data.recordId) { 95 | res.code = 200; 96 | res.message = 'success'; 97 | } 98 | } catch (e) { 99 | console.error('white create error', e); 100 | res.data = e; 101 | } 102 | return res; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/modules/statistics/statistics.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Get, 4 | Post, 5 | Body, 6 | Request, 7 | UnauthorizedException, 8 | } from '@nestjs/common'; 9 | import { Store } from '../../db/store.js'; 10 | 11 | @Controller('api/v1/statistic') 12 | export class StatisticsController { 13 | @Get('list') 14 | async findAll(@Request() req: any): Promise { 15 | const user = req.user; 16 | // console.debug(user); 17 | // console.debug(Store.users); 18 | const db = Store.findUser(user.userId); 19 | if (!db) { 20 | throw new UnauthorizedException(); 21 | } 22 | // console.debug(db); 23 | try { 24 | const data = await db.db.statistic.findAll(); 25 | // console.debug('Statistics data', data); 26 | const res: any = { 27 | code: 200, 28 | message: 'success', 29 | data, 30 | }; 31 | const items = data.data.map((value: any) => { 32 | const fields = value.fields; 33 | fields.recordId = value.recordId; 34 | return fields; 35 | }); 36 | res.data = { 37 | page: 1, 38 | pageSize: 1000, 39 | pageCount: 1, 40 | itemCount: data.data.length, 41 | items: items, 42 | }; 43 | return res; 44 | } catch (e) { 45 | console.error(e); 46 | const res: any = { 47 | code: 400, 48 | message: 'error', 49 | data: e, 50 | }; 51 | return res; 52 | } 53 | } 54 | @Post('create') 55 | async create(@Body() body: any, @Request() req: any): Promise { 56 | const user = req.user; 57 | // console.debug(user); 58 | // console.debug(Store.users); 59 | const db = Store.findUser(user.userId); 60 | if (!db) { 61 | throw new UnauthorizedException(); 62 | } 63 | // console.debug(db); 64 | 65 | const resCreate = await db.db.statistic.create(body); 66 | console.debug('resCreate', resCreate); 67 | const res: any = { code: 400, message: 'fail', data: { data: resCreate } }; 68 | if (resCreate.data.recordId) { 69 | res.code = 200; 70 | res.message = 'success'; 71 | res.data = resCreate; 72 | } 73 | return res; 74 | } 75 | @Post('delete') 76 | async delete(@Body() body: any, @Request() req: any): Promise { 77 | // { 78 | // "recordId":21705 79 | // } 80 | console.debug('statistic delete', body); 81 | const user = req.user; 82 | // console.debug(user); 83 | // console.debug(Store.users); 84 | const db = Store.findUser(user.userId); 85 | if (!db) { 86 | throw new UnauthorizedException(); 87 | } 88 | // console.debug(db); 89 | 90 | const resDel = await db.db.statistic.delete(body.recordId); 91 | console.debug('statistic resDel', resDel); 92 | 93 | let res: any = { 94 | code: 400, 95 | message: 'error', 96 | data: resDel, 97 | }; 98 | if (resDel.message === 'success') { 99 | res = { 100 | code: 200, 101 | message: 'success', 102 | data: { 103 | recordId: body.recordId, 104 | }, 105 | }; 106 | } 107 | return res; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/modules/magic/magic.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Post, Body } from '@nestjs/common'; 2 | import { SetMetadata } from '@nestjs/common'; 3 | // import * as path from 'path'; 4 | import { drawPosterWithText, downloadImage } from './poster.js'; 5 | import { S3 } from 'aws-sdk'; 6 | import 'dotenv/config.js'; 7 | import { FileBox } from 'file-box'; 8 | import * as fs from 'fs'; 9 | 10 | const IS_PUBLIC_KEY = 'isPublic'; 11 | const Public = () => SetMetadata(IS_PUBLIC_KEY, true); 12 | @Controller('api/v1/magic') 13 | export class MagicController { 14 | @Public() 15 | @Post('poem-poster') 16 | async posterCreate( 17 | @Body() 18 | body: { 19 | title: string; 20 | text: string; 21 | image_url: string; 22 | }, 23 | ) { 24 | console.log(body); 25 | // 诗词生成海报 26 | /* 27 | { 28 | "action": "poem-poster", 29 | "parms": { 30 | "title": "春联", 31 | "text": "春联题材广无边,福寿康宁喜气添。家国情怀展宏图,吉祥如意庆丰年。", 32 | "image_url": "https://lf-plugin-resouce.oceancloudapi.com/obj/bot-studio-platform-plugin-tos/artist/image/44f743db3d514657bd0648de9ca5869e.png" 33 | } 34 | } 35 | */ 36 | const { title, text, image_url } = body; 37 | try { 38 | // const uuid = Math.random().toString(36).substr(2, 8); 39 | const outputPath = './'; 40 | const imagePath = await downloadImage(image_url, outputPath); 41 | console.info('downloadImage imagePath', imagePath); 42 | 43 | // 生成海报 44 | const fileRes = await drawPosterWithText( 45 | imagePath, 46 | title, 47 | text, 48 | outputPath, 49 | ); 50 | console.info('drawPosterWithText fileRes', JSON.stringify(fileRes)); 51 | 52 | async function uploadToS3( 53 | s3: S3, 54 | bucketName: string, 55 | file: any, 56 | file_name: string, 57 | ): Promise { 58 | return s3 59 | .upload({ 60 | Bucket: bucketName, 61 | Key: file_name, 62 | Body: file, 63 | }) 64 | .promise(); 65 | } 66 | // 比如将文件保存到服务器或云存储,并获取文件的URL 67 | const s3 = new S3({ 68 | accessKeyId: process.env['accessKeyId'], 69 | secretAccessKey: process.env.secretAccessKey, 70 | region: process.env.region, 71 | endpoint: process.env.endpoint, 72 | }); 73 | 74 | const bucketName = process.env.bucketName || 'poem-poster'; 75 | const uploadResult = await uploadToS3( 76 | s3, 77 | bucketName, 78 | await FileBox.fromFile(fileRes.outputFileName).toBuffer(), 79 | 'poster' + '/' + fileRes.newFileName, 80 | ); 81 | console.info('uploadResult', JSON.stringify(uploadResult)); 82 | // 删除文件fileRes.outputFileName 83 | fs.unlinkSync(fileRes.outputFileName); 84 | fs.unlinkSync(imagePath); 85 | 86 | return { 87 | code: 200, 88 | message: 'success', 89 | data: { 90 | url: uploadResult.Location, 91 | title, 92 | text, 93 | }, 94 | }; 95 | } catch (e) { 96 | console.error(e); 97 | return { 98 | code: 500, 99 | message: 'fail', 100 | data: e, 101 | }; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/db/vikaModel/Carpooling/fields.ts: -------------------------------------------------------------------------------- 1 | export const vikaFields = { 2 | code: 200, 3 | success: true, 4 | data: { 5 | fields: [ 6 | { 7 | id: 'fldkTrMcb63oY', 8 | name: '发布人', 9 | type: 'SingleText', 10 | property: {}, 11 | editable: true, 12 | isPrimary: true, 13 | }, 14 | { 15 | id: 'fldkTrMcb63oY', 16 | name: '联系电话', 17 | type: 'SingleText', 18 | property: {}, 19 | editable: true, 20 | }, 21 | { 22 | id: 'fldXrv3ioaJgK', 23 | name: '类型', 24 | type: 'SingleSelect', 25 | property: { 26 | options: [ 27 | { 28 | id: 'optzOM0Xof9Ln', 29 | name: '人找车', 30 | color: { name: 'deepPurple_0', value: '#E5E1FC' }, 31 | }, 32 | { 33 | id: 'opt8qyV17YhtJ', 34 | name: '车找人', 35 | color: { name: 'indigo_0', value: '#DDE7FF' }, 36 | }, 37 | ], 38 | }, 39 | editable: true, 40 | }, 41 | { 42 | id: 'fldAj0kCgDIDT', 43 | name: '出发地', 44 | type: 'Text', 45 | editable: true, 46 | }, 47 | { 48 | id: 'fldAj0kCgDIDT', 49 | name: '目的地', 50 | type: 'Text', 51 | editable: true, 52 | }, 53 | { 54 | id: 'fldAj0kCgDIDT', 55 | name: '出发日期', 56 | type: 'Text', 57 | editable: true, 58 | }, 59 | { 60 | id: 'fldAj0kCgDIDT', 61 | name: '出发时间', 62 | type: 'Text', 63 | editable: true, 64 | }, 65 | { 66 | id: 'fldAj0kCgDIDT', 67 | name: '车费', 68 | type: 'Text', 69 | editable: true, 70 | }, 71 | { 72 | id: 'fldAj0kCgDIDT', 73 | name: '途经路线', 74 | type: 'Text', 75 | editable: true, 76 | }, 77 | { 78 | id: 'fldAj0kCgDIDT', 79 | name: '原始消息', 80 | type: 'Text', 81 | editable: true, 82 | }, 83 | { 84 | id: 'fldAj0kCgDIDT', 85 | name: '群名称', 86 | type: 'Text', 87 | editable: true, 88 | }, 89 | { 90 | id: 'fldAj0kCgDIDT', 91 | name: '群ID', 92 | type: 'Text', 93 | editable: true, 94 | }, 95 | { 96 | id: 'fldAj0kCgDIDT', 97 | name: '发布者ID', 98 | type: 'Text', 99 | editable: true, 100 | }, 101 | { 102 | id: 'fldAj0kCgDIDT', 103 | name: '创建时间', 104 | type: 'Text', 105 | editable: true, 106 | }, 107 | { 108 | id: 'fldXrv3ioaJgK', 109 | name: '状态', 110 | type: 'SingleSelect', 111 | property: { 112 | options: [ 113 | { 114 | id: 'optzOM0Xof9Ln', 115 | name: '开启', 116 | color: { name: 'deepPurple_0', value: '#E5E1FC' }, 117 | }, 118 | { 119 | id: 'opt8qyV17YhtJ', 120 | name: '关闭', 121 | color: { name: 'indigo_0', value: '#DDE7FF' }, 122 | }, 123 | ], 124 | }, 125 | editable: true, 126 | }, 127 | ], 128 | }, 129 | message: 'SUCCESS', 130 | }; 131 | -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sort-keys */ 2 | // 时间格式化 3 | function formatTimestamp(timestamp: string | number | Date) { 4 | const currentDate = new Date(timestamp); 5 | const year = currentDate.getFullYear(); 6 | const month = currentDate.getMonth() + 1; 7 | const day = currentDate.getDate(); 8 | const hour = currentDate.getHours(); 9 | const minute = currentDate.getMinutes(); 10 | const second = currentDate.getSeconds(); 11 | const dayOfWeek = currentDate.getDay(); 12 | 13 | const startOfDayTimestamp: number = currentDate.setHours(0, 0, 0, 0); 14 | const endOfDayTimestamp: number = startOfDayTimestamp + 60 * 60 * 24 * 1000; 15 | const daysOfWeek: string[] = ['日', '一', '二', '三', '四', '五', '六']; 16 | const dayText: string = '周' + daysOfWeek[dayOfWeek]; 17 | const timeText: string = `${hour < 10 ? '0' + hour : hour}:${ 18 | minute < 10 ? '0' + minute : minute 19 | }:${second < 10 ? '0' + second : second}`; 20 | const dateText: string = `${month}月${day}日`; 21 | const dateTimeText: string = `${year}-${month}-${day} ${dayText} ${timeText}`; 22 | 23 | const res: [string, number, number, string, string, string] = [ 24 | dateText, 25 | startOfDayTimestamp, 26 | endOfDayTimestamp, 27 | dayText, 28 | timeText, 29 | dateTimeText, 30 | ]; 31 | return res; 32 | } 33 | 34 | /** 35 | * 延时函数 36 | * @param {*} ms 毫秒 37 | */ 38 | async function delay(ms: number) { 39 | return new Promise((resolve) => setTimeout(resolve, ms)); 40 | } 41 | 42 | function getNow() { 43 | return new Date().toLocaleString(); 44 | } 45 | 46 | function getCurTime() { 47 | // timestamp是整数,否则要parseInt转换 48 | const timestamp = new Date().getTime(); 49 | const timezone = 8; // 目标时区时间,东八区 50 | const offsetGMT = new Date().getTimezoneOffset(); // 本地时间和格林威治的时间差,单位为分钟 51 | const time = timestamp + offsetGMT * 60 * 1000 + timezone * 60 * 60 * 1000; 52 | return time; 53 | } 54 | 55 | export function getCurrentTime(timestamp?: number) { 56 | const now = timestamp ? new Date(timestamp) : new Date(); 57 | const year = now.getFullYear(); 58 | const month = now.getMonth() + 1; 59 | const day = now.getDate(); 60 | const hour = now.getHours(); 61 | const minute = now.getMinutes(); 62 | const second = now.getSeconds(); 63 | return `${year}-${month.toString().padStart(2, '0')}-${day 64 | .toString() 65 | .padStart(2, '0')} ${hour.toString().padStart(2, '0')}:${minute 66 | .toString() 67 | .padStart(2, '0')}:${second.toString().padStart(2, '0')}`; 68 | } 69 | 70 | // 半角转全角 71 | function toDBC(txtstring: any) { 72 | let tmp = ''; 73 | let h = 0; 74 | let c = 0; 75 | for (let i = 0; i < txtstring.length; i++) { 76 | if (txtstring.charCodeAt(i) === 32) { 77 | tmp = tmp + String.fromCharCode(12288); 78 | h += 1; 79 | } else if (txtstring.charCodeAt(i) < 127) { 80 | tmp = tmp + String.fromCharCode(txtstring.charCodeAt(i) + 65248); 81 | h += 1; 82 | } else { 83 | tmp = tmp + txtstring[i]; 84 | c += 1; 85 | } 86 | } 87 | return [tmp, h, c]; 88 | } 89 | 90 | function generateRandomNumber(base: number): number { 91 | return Math.floor(Math.random() * 100) + base; 92 | } 93 | 94 | export { 95 | generateRandomNumber, 96 | toDBC, 97 | getNow, 98 | delay, 99 | formatTimestamp, 100 | getCurTime, 101 | }; 102 | -------------------------------------------------------------------------------- /src/db/vikaModel/Message/mod.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sort-keys */ 2 | 3 | import type { Sheet } from '../Model'; 4 | 5 | const vikaFields = { 6 | code: 200, 7 | success: true, 8 | data: { 9 | fields: [ 10 | { 11 | id: 'fld7J6pZsrNat', 12 | name: '时间|timeHms', 13 | type: 'SingleText', 14 | property: { defaultValue: '' }, 15 | editable: true, 16 | isPrimary: true, 17 | }, 18 | { 19 | id: 'fldYaWgbe2iEG', 20 | name: '发送者|name', 21 | type: 'SingleText', 22 | property: { defaultValue: '' }, 23 | editable: true, 24 | }, 25 | { 26 | id: 'fldaLPuahrn7S', 27 | name: '好友备注|alias', 28 | type: 'SingleText', 29 | property: { defaultValue: '' }, 30 | editable: true, 31 | }, 32 | { 33 | id: 'fld5E5KJmzUow', 34 | name: '群名称|topic', 35 | type: 'SingleText', 36 | property: { defaultValue: '' }, 37 | editable: true, 38 | }, 39 | { 40 | id: 'fld07r2ePsUiV', 41 | name: '接收人|listener', 42 | type: 'SingleText', 43 | property: {}, 44 | editable: true, 45 | }, 46 | { 47 | id: 'fldQJl6upgfYd', 48 | name: '消息内容|messagePayload', 49 | type: 'Text', 50 | editable: true, 51 | }, 52 | { 53 | id: 'fldnxD3QoudNs', 54 | name: '文件图片|file', 55 | type: 'Attachment', 56 | editable: true, 57 | }, 58 | { 59 | id: 'fldVBdInXiBr3', 60 | name: '消息类型|messageType', 61 | type: 'SingleText', 62 | property: { defaultValue: '' }, 63 | editable: true, 64 | }, 65 | { 66 | id: 'fldAivHA8anFP', 67 | name: '好友ID|wxid', 68 | type: 'SingleText', 69 | property: { defaultValue: '' }, 70 | editable: true, 71 | }, 72 | { 73 | id: 'fldOCt60zYh8f', 74 | name: '接收人ID|listenerid', 75 | type: 'SingleText', 76 | property: {}, 77 | editable: true, 78 | }, 79 | { 80 | id: 'fldXswnj8SFxo', 81 | name: '群ID|roomid', 82 | type: 'SingleText', 83 | property: { defaultValue: '' }, 84 | editable: true, 85 | }, 86 | { 87 | id: 'fldkTLOkFxGdP', 88 | name: '发送者头像|wxAvatar', 89 | type: 'Text', 90 | editable: true, 91 | }, 92 | { 93 | id: 'fldkTLOkFxGdP2', 94 | name: '群头像|roomAvatar', 95 | type: 'Text', 96 | editable: true, 97 | }, 98 | { 99 | id: 'fldkTLOkFxGdP3', 100 | name: '接收人头像|listenerAvatar', 101 | type: 'Text', 102 | editable: true, 103 | }, 104 | { 105 | id: 'fldEavgqazETa', 106 | name: '消息ID|messageId', 107 | type: 'SingleText', 108 | property: { defaultValue: '' }, 109 | editable: true, 110 | }, 111 | ], 112 | }, 113 | message: 'SUCCESS', 114 | }; 115 | const defaultRecords = { 116 | code: 200, 117 | success: true, 118 | data: { total: 0, records: [], pageNum: 1, pageSize: 0 }, 119 | message: 'SUCCESS', 120 | }; 121 | 122 | export const messageSheet: Sheet = { 123 | fields: vikaFields.data.fields, 124 | name: '消息记录|Message', 125 | defaultRecords: defaultRecords.data.records, 126 | }; 127 | -------------------------------------------------------------------------------- /src/db/vikaModel/ChatBotUser/mod.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sort-keys */ 2 | 3 | import type { 4 | Sheet, 5 | // Field, 6 | } from '../Model'; 7 | 8 | import { replaceSyncStatus, actionState } from '../actionBar.js'; 9 | 10 | const name = '智聊用户|ChatbotUser'; 11 | const code = 'chatBotUserSheet'; 12 | 13 | const vikaFields = { 14 | code: 200, 15 | success: true, 16 | data: { 17 | fields: [ 18 | { 19 | id: 'fldA47yx9L3bk', 20 | name: '机器人ID|id', 21 | type: 'SingleText', 22 | property: { 23 | defaultValue: '', 24 | }, 25 | editable: true, 26 | isPrimary: true, 27 | }, 28 | { 29 | id: 'fldPrcQ5QfhxV', 30 | name: '昵称|botname', 31 | type: 'SingleText', 32 | property: {}, 33 | editable: true, 34 | }, 35 | { 36 | id: 'fld6rYKTZ0zQV', 37 | name: '用户ID|wxid', 38 | type: 'SingleText', 39 | property: {}, 40 | editable: true, 41 | }, 42 | { 43 | id: 'fldxD8wwu1hGy', 44 | name: '用户名称|name', 45 | type: 'SingleText', 46 | property: {}, 47 | editable: true, 48 | }, 49 | { 50 | id: 'fldmYtMgA15iI', 51 | name: '好友备注(选填)|alias', 52 | type: 'SingleText', 53 | property: {}, 54 | editable: true, 55 | }, 56 | { 57 | id: 'fld4P2sX0CHco', 58 | name: '用户提示词|prompt', 59 | type: 'Text', 60 | editable: true, 61 | }, 62 | { 63 | id: 'fldguMiluobGu', 64 | name: '配额|quota', 65 | type: 'Number', 66 | property: { 67 | precision: 0, 68 | }, 69 | editable: true, 70 | }, 71 | { 72 | id: 'fldsG69W4KzJa', 73 | name: '备注|info', 74 | type: 'Text', 75 | editable: true, 76 | }, 77 | { 78 | id: 'fldUcpFFLyMo7', 79 | name: '启用状态|state', 80 | type: 'SingleSelect', 81 | property: { 82 | options: [ 83 | { 84 | id: 'optMTJ3j2fMr4', 85 | name: '启用', 86 | color: { 87 | name: 'deepPurple_0', 88 | value: '#E5E1FC', 89 | }, 90 | }, 91 | { 92 | id: 'opt4bTBzh2Mx6', 93 | name: '禁用', 94 | color: { 95 | name: 'indigo_0', 96 | value: '#DDE7FF', 97 | }, 98 | }, 99 | ], 100 | }, 101 | editable: true, 102 | }, 103 | ], 104 | }, 105 | message: 'SUCCESS', 106 | }; 107 | 108 | let fields: any = vikaFields.data.fields; 109 | 110 | if (actionState[code]) { 111 | fields = replaceSyncStatus(fields); 112 | } 113 | 114 | const defaultRecords: any = { 115 | code: 200, 116 | success: true, 117 | message: 'Request successful', 118 | data: { 119 | total: 1, 120 | pageNum: 1, 121 | pageSize: 1, 122 | records: [ 123 | { 124 | recordId: 'rec55WfnpHQpM', 125 | fields: { 126 | '机器人ID|id': '4', 127 | '昵称|botname': '小G', 128 | '用户ID|wxid': 'zhangsan', 129 | '用户名称|name': '张三', 130 | '好友备注(选填)|alias': '张三', 131 | '配额|quota': 100, 132 | '备注|info': '示例配置数据', 133 | '启用状态|state': '启用', 134 | }, 135 | }, 136 | ], 137 | }, 138 | }; 139 | 140 | export const sheet: Sheet = { 141 | fields, 142 | name, 143 | defaultRecords: defaultRecords.data.records, 144 | }; 145 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chatflow-admin", 3 | "version": "1.1.3.2", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "main": "electron/main/index.mjs", 9 | "scripts": { 10 | "build": "nest build", 11 | "pkg": "pkg .", 12 | "electron": "electron .", 13 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 14 | "start": "nest start", 15 | "dev": "nest start --watch", 16 | "start:dev": "nest start --watch", 17 | "start:debug": "nest start --debug --watch", 18 | "start:prod": "node dist/main", 19 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 20 | "test": "jest", 21 | "test:watch": "jest --watch", 22 | "test:cov": "jest --coverage", 23 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 24 | "test:e2e": "jest --config ./test/jest-e2e.json", 25 | "test:lark-orm": "cross-env NODE_OPTIONS=\"--no-warnings --loader=ts-node/esm\" node ./test/lark-orm-test.ts", 26 | "test:vika-orm": "cross-env NODE_OPTIONS=\"--no-warnings --loader=ts-node/esm\" node ./test/vika-orm-test.ts", 27 | "cm": "ts-node ./scripts/createModule.ts" 28 | }, 29 | "dependencies": { 30 | "@larksuiteoapi/node-sdk": "^1.23.0", 31 | "@nestjs/common": "^10.0.0", 32 | "@nestjs/core": "^10.0.0", 33 | "@nestjs/jwt": "^10.2.0", 34 | "@nestjs/passport": "^10.0.2", 35 | "@nestjs/platform-express": "^10.2.10", 36 | "@vikadata/vika": "^1.3.0", 37 | "aedes": "^0.51.0", 38 | "aedes-server-factory": "^0.2.1", 39 | "aws-sdk": "^2.1509.0", 40 | "axios": "^1.6.2", 41 | "boxen": "^7.1.1", 42 | "canvas": "^2.11.2", 43 | "cross-env": "^7.0.3", 44 | "crypto-js": "^4.2.0", 45 | "dotenv": "^16.3.1", 46 | "file-box": "^1.4.15", 47 | "mqtt": "^5.3.0", 48 | "multer": "^1.4.5-lts.1", 49 | "passport": "^0.6.0", 50 | "passport-jwt": "^4.0.1", 51 | "passport-local": "^1.0.0", 52 | "reflect-metadata": "^0.1.13", 53 | "rxjs": "^7.8.1", 54 | "uuid": "^9.0.1", 55 | "websocket-stream": "^5.5.2" 56 | }, 57 | "devDependencies": { 58 | "@nestjs/cli": "^10.0.0", 59 | "@nestjs/schematics": "^10.0.0", 60 | "@nestjs/testing": "^10.0.0", 61 | "@types/crypto-js": "^4.1.3", 62 | "@types/express": "^4.17.21", 63 | "@types/jest": "^29.5.2", 64 | "@types/mosca": "^2.8.8", 65 | "@types/multer": "^1.4.11", 66 | "@types/node": "^20.3.1", 67 | "@types/passport-jwt": "^3.0.13", 68 | "@types/passport-local": "^1.0.38", 69 | "@types/supertest": "^2.0.12", 70 | "@types/uuid": "^9.0.7", 71 | "@typescript-eslint/eslint-plugin": "^6.0.0", 72 | "@typescript-eslint/parser": "^6.0.0", 73 | "eslint": "^8.42.0", 74 | "eslint-config-prettier": "^9.0.0", 75 | "eslint-plugin-prettier": "^5.0.0", 76 | "jest": "^29.5.0", 77 | "prettier": "^3.0.0", 78 | "source-map-support": "^0.5.21", 79 | "supertest": "^6.3.3", 80 | "ts-jest": "^29.1.0", 81 | "ts-loader": "^9.4.3", 82 | "ts-node": "^10.9.1", 83 | "tsconfig-paths": "^4.2.0" 84 | }, 85 | "bin": "dist/src/main.js", 86 | "jest": { 87 | "moduleFileExtensions": [ 88 | "js", 89 | "json", 90 | "ts" 91 | ], 92 | "rootDir": "src", 93 | "testRegex": ".*\\.spec\\.ts$", 94 | "transform": { 95 | "^.+\\.(t|j)s$": "ts-jest" 96 | }, 97 | "collectCoverageFrom": [ 98 | "**/*.(t|j)s" 99 | ], 100 | "coverageDirectory": "../coverage", 101 | "testEnvironment": "node" 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/modules/qas/qas.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Get, 5 | Post, 6 | Query, 7 | Request, 8 | UnauthorizedException, 9 | } from '@nestjs/common'; 10 | import { Store } from '../../db/store.js'; 11 | 12 | @Controller('api/v1/qa') 13 | export class QasController { 14 | @Get('list') 15 | async findAll(@Request() req: any, @Query() query: any): Promise { 16 | const user = req.user; 17 | // console.debug(user); 18 | // console.debug(Store.users); 19 | const db = Store.findUser(user.userId); 20 | if (!db) { 21 | throw new UnauthorizedException(); 22 | } 23 | // console.debug(db); 24 | let data; 25 | if (query.keyword) { 26 | data = await db.db.qa.findByQuery(query.keyword); 27 | } else { 28 | data = await db.db.qa.findAll(); 29 | } 30 | const res: any = { 31 | code: 400, 32 | message: 'error', 33 | data, 34 | }; 35 | if (data.data.length) { 36 | const items = data.data.map((value: any) => { 37 | const fields = value.fields; 38 | fields.recordId = value.recordId; 39 | return fields; 40 | }); 41 | res.code = 200; 42 | res.message = 'success'; 43 | res.data = { 44 | page: 1, 45 | pageSize: 1000, 46 | pageCount: 1, 47 | itemCount: data.data.length, 48 | items: items, 49 | }; 50 | } 51 | return res; 52 | } 53 | @Post('create') 54 | async create(@Body() body: any, @Request() req: any): Promise { 55 | console.debug('qa create', body); 56 | const user = req.user; 57 | // console.debug(user); 58 | // console.debug(Store.users); 59 | const db = Store.findUser(user.userId); 60 | if (!db) { 61 | throw new UnauthorizedException(); 62 | } 63 | // console.debug(db); 64 | const res: any = { code: 400, message: 'fail', data: {} }; 65 | try { 66 | const resCreate = await db.db.qa.create(body); 67 | res.data = resCreate; 68 | console.debug('resCreate', resCreate); 69 | if (resCreate.data.recordId) { 70 | res.code = 200; 71 | res.message = 'success'; 72 | res.data = resCreate; 73 | } 74 | } catch (e) { 75 | console.error(e); 76 | res.message = 'error'; 77 | res.data = e; 78 | } 79 | return res; 80 | } 81 | @Post('update') 82 | async update(@Request() req: any): Promise { 83 | const user = req.user; 84 | // console.debug(user); 85 | // console.debug(Store.users); 86 | const db = Store.findUser(user.userId); 87 | if (!db) { 88 | throw new UnauthorizedException(); 89 | } 90 | // console.debug(db); 91 | return ''; 92 | } 93 | @Post('delete') 94 | async delete(@Body() body: any, @Request() req: any): Promise { 95 | // { 96 | // "recordId":21705 97 | // } 98 | console.debug('welcome delete', body); 99 | const user = req.user; 100 | // console.debug(user); 101 | // console.debug(Store.users); 102 | const db = Store.findUser(user.userId); 103 | if (!db) { 104 | throw new UnauthorizedException(); 105 | } 106 | // console.debug(db); 107 | 108 | const resDel = await db.db.qa.delete(body.recordId); 109 | console.debug('welcome resDel', resDel); 110 | 111 | const res: any = { 112 | code: 400, 113 | message: 'error', 114 | data: resDel, 115 | }; 116 | if (resDel.message === 'success') { 117 | res.code = 200; 118 | res.message = 'success'; 119 | res.data = { 120 | recordId: body.recordId, 121 | }; 122 | } 123 | return res; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/modules/groupnotices/groupnotices.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Get, 4 | Post, 5 | Body, 6 | Request, 7 | UnauthorizedException, 8 | } from '@nestjs/common'; 9 | import { Store } from '../../db/store.js'; 10 | 11 | @Controller('api/v1/groupnotice') 12 | export class GroupnoticesController { 13 | @Get('list') 14 | async findAll(@Request() req: any): Promise { 15 | const user = req.user; 16 | // console.debug(user); 17 | // console.debug(Store.users); 18 | const db = Store.findUser(user.userId); 19 | if (!db) { 20 | throw new UnauthorizedException(); 21 | } 22 | // console.debug(db); 23 | 24 | const data = await db.db.groupNotice.findAll(); 25 | const items = data.data.map((value: any) => { 26 | const fields = value.fields; 27 | fields.recordId = value.recordId; 28 | return fields; 29 | }); 30 | // console.debug(data); 31 | const res: any = { 32 | code: 200, 33 | message: 'success', 34 | data: { 35 | page: 1, 36 | pageSize: 1000, 37 | pageCount: 1, 38 | itemCount: data.data.length, 39 | items: items, 40 | }, 41 | }; 42 | return res; 43 | } 44 | @Post('create') 45 | async create(@Body() body: any, @Request() req: any): Promise { 46 | const user = req.user; 47 | // console.debug(user); 48 | // console.debug(Store.users); 49 | const db = Store.findUser(user.userId); 50 | if (!db) { 51 | throw new UnauthorizedException(); 52 | } 53 | // console.debug(db); 54 | const resCreate = await db.db.groupNotice.create(body); 55 | console.debug('resCreate', resCreate); 56 | const res: any = { code: 400, message: 'fail', data: {} }; 57 | if (resCreate.data.recordId) { 58 | res.code = 200; 59 | res.message = 'success'; 60 | res.data = resCreate; 61 | } 62 | return res; 63 | } 64 | @Post('delete') 65 | async delete(@Body() body: any, @Request() req: any): Promise { 66 | // { 67 | // "recordId":21705 68 | // } 69 | console.debug('groupnotice delete', body); 70 | const user = req.user; 71 | // console.debug(user); 72 | // console.debug(Store.users); 73 | const db = Store.findUser(user.userId); 74 | if (!db) { 75 | throw new UnauthorizedException(); 76 | } 77 | // console.debug(db); 78 | const resDel = await db.db.groupNotice.delete(body.recordId); 79 | console.debug('groupnotice resDel', resDel); 80 | 81 | let res: any = { 82 | code: 400, 83 | message: 'error', 84 | data: {}, 85 | }; 86 | if (resDel.message === 'success') { 87 | res = { 88 | code: 200, 89 | message: 'success', 90 | data: { 91 | recordId: body.recordId, 92 | }, 93 | }; 94 | } 95 | return res; 96 | } 97 | // 批量更新配置信息 98 | @Post('update') 99 | async update(@Request() req: any, @Body() body: any) { 100 | console.debug('group/update body:', body); 101 | const user = req.user; 102 | // console.debug(user); 103 | // console.debug(Store.users); 104 | const db = Store.findUser(user.userId); 105 | if (!db) { 106 | throw new UnauthorizedException(); 107 | } 108 | // console.debug(db); 109 | const res = await db.db.groupNotice.updatEmultiple(body); 110 | console.debug('update config:', res); 111 | const data: any = { 112 | code: 400, 113 | message: 'fail', 114 | data: {}, 115 | }; 116 | if (res.message === 'success') { 117 | data.code = 200; 118 | data.message = 'success'; 119 | data.data = res.data; 120 | } 121 | 122 | return data; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/db/vikaModel/Qa/fields.ts: -------------------------------------------------------------------------------- 1 | export const vikaFields = { 2 | code: 200, 3 | success: true, 4 | data: { 5 | fields: [ 6 | { 7 | id: 'fldljzplNFLxH', 8 | name: '分类|skillname', 9 | type: 'Text', 10 | editable: true, 11 | isPrimary: true, 12 | }, 13 | { 14 | id: 'fldPVePxESZ1E', 15 | name: '标准问题|title', 16 | type: 'Text', 17 | editable: true, 18 | }, 19 | { 20 | id: 'fldUEXdAhTzlt', 21 | name: '相似问题1(选填)|question1', 22 | type: 'Text', 23 | editable: true, 24 | }, 25 | { 26 | id: 'fld0FMab66JzE', 27 | name: '相似问题2(选填)|question2', 28 | type: 'Text', 29 | editable: true, 30 | }, 31 | { 32 | id: 'fldKXmqxlXuCz', 33 | name: '相似问题3(选填)|question3', 34 | type: 'Text', 35 | editable: true, 36 | }, 37 | { 38 | id: 'fldnu4M9NyIIx', 39 | name: '机器人回答|answer', 40 | type: 'Text', 41 | editable: true, 42 | }, 43 | { 44 | id: 'fldOEh1RPNtx3', 45 | name: '启用状态|state', 46 | type: 'SingleSelect', 47 | property: { 48 | options: [ 49 | { 50 | id: 'opteWxGg5mvPP', 51 | name: '启用', 52 | color: { name: 'deepPurple_0', value: '#E5E1FC' }, 53 | }, 54 | { 55 | id: 'optX67msptucG', 56 | name: '停用', 57 | color: { name: 'indigo_0', value: '#DDE7FF' }, 58 | }, 59 | ], 60 | }, 61 | editable: true, 62 | }, 63 | { 64 | id: 'fld23GGnV4tNP', 65 | name: '同步状态|syncStatus', 66 | type: 'SingleSelect', 67 | property: { 68 | options: [ 69 | { 70 | id: 'optgBphdOFkA5', 71 | name: '未同步', 72 | color: { name: 'indigo_0', value: '#DDE7FF' }, 73 | }, 74 | { 75 | id: 'opthCgGQILUWj', 76 | name: '已同步', 77 | color: { name: 'deepPurple_0', value: '#E5E1FC' }, 78 | }, 79 | { 80 | id: 'optG3MKM3MMkk', 81 | name: '同步失败', 82 | color: { name: 'blue_0', value: '#DDF5FF' }, 83 | }, 84 | ], 85 | }, 86 | editable: true, 87 | }, 88 | { 89 | id: 'fld1LaNksJLDa', 90 | name: '最后操作时间|lastOperationTime', 91 | type: 'DateTime', 92 | property: { 93 | format: 'YYYY-MM-DD HH:mm', 94 | includeTime: true, 95 | autoFill: true, 96 | }, 97 | editable: true, 98 | }, 99 | { 100 | id: 'fld2XBme6uAoF', 101 | name: '操作|action', 102 | type: 'SingleSelect', 103 | property: { 104 | options: [ 105 | { 106 | id: 'optt4uJVT8R9A', 107 | name: '选择操作', 108 | color: { name: 'green_0', value: '#DCF3D1' }, 109 | }, 110 | { 111 | id: 'optLwTD20mANS', 112 | name: '提交此条', 113 | color: { name: 'deepPurple_0', value: '#E5E1FC' }, 114 | }, 115 | { 116 | id: 'optwEgi4xDTId', 117 | name: '提交全部', 118 | color: { name: 'indigo_0', value: '#DDE7FF' }, 119 | }, 120 | { 121 | id: 'optF9RPpvQ6BC', 122 | name: '删除此条', 123 | color: { name: 'blue_0', value: '#DDF5FF' }, 124 | }, 125 | ], 126 | }, 127 | editable: true, 128 | }, 129 | ], 130 | }, 131 | message: 'SUCCESS', 132 | }; 133 | -------------------------------------------------------------------------------- /src/db/vikaModel/acitonFields.ts: -------------------------------------------------------------------------------- 1 | export const acitonFields = { 2 | code: 200, 3 | success: true, 4 | data: { 5 | fields: [ 6 | { 7 | id: 'fldljzplNFLxH', 8 | name: '分类(必填)|skillname', 9 | type: 'Text', 10 | editable: true, 11 | isPrimary: true, 12 | }, 13 | { 14 | id: 'fldPVePxESZ1E', 15 | name: '标准问题(必填)|title', 16 | type: 'Text', 17 | editable: true, 18 | }, 19 | { 20 | id: 'fldUEXdAhTzlt', 21 | name: '相似问题1(选填)|question1', 22 | type: 'Text', 23 | editable: true, 24 | }, 25 | { 26 | id: 'fld0FMab66JzE', 27 | name: '相似问题2(选填)|question2', 28 | type: 'Text', 29 | editable: true, 30 | }, 31 | { 32 | id: 'fldKXmqxlXuCz', 33 | name: '相似问题3(选填)|question3', 34 | type: 'Text', 35 | editable: true, 36 | }, 37 | { 38 | id: 'fldnu4M9NyIIx', 39 | name: '机器人回答(必填)|answer', 40 | type: 'Text', 41 | editable: true, 42 | }, 43 | { 44 | id: 'fldOEh1RPNtx3', 45 | name: '启用状态|state', 46 | type: 'SingleSelect', 47 | property: { 48 | options: [ 49 | { 50 | id: 'opteWxGg5mvPP', 51 | name: '启用', 52 | color: { name: 'deepPurple_0', value: '#E5E1FC' }, 53 | }, 54 | { 55 | id: 'optX67msptucG', 56 | name: '停用', 57 | color: { name: 'indigo_0', value: '#DDE7FF' }, 58 | }, 59 | ], 60 | }, 61 | editable: true, 62 | }, 63 | { 64 | id: 'fld23GGnV4tNP', 65 | name: '同步状态|syncStatus', 66 | type: 'SingleSelect', 67 | property: { 68 | options: [ 69 | { 70 | id: 'optgBphdOFkA5', 71 | name: '未同步', 72 | color: { name: 'indigo_0', value: '#DDE7FF' }, 73 | }, 74 | { 75 | id: 'opthCgGQILUWj', 76 | name: '已同步', 77 | color: { name: 'deepPurple_0', value: '#E5E1FC' }, 78 | }, 79 | { 80 | id: 'optG3MKM3MMkk', 81 | name: '同步失败', 82 | color: { name: 'blue_0', value: '#DDF5FF' }, 83 | }, 84 | ], 85 | }, 86 | editable: true, 87 | }, 88 | { 89 | id: 'fld1LaNksJLDa', 90 | name: '最后操作时间|lastOperationTime', 91 | type: 'DateTime', 92 | property: { 93 | format: 'YYYY-MM-DD HH:mm', 94 | includeTime: true, 95 | autoFill: true, 96 | }, 97 | editable: true, 98 | }, 99 | { 100 | id: 'fld2XBme6uAoF', 101 | name: '操作|action', 102 | type: 'SingleSelect', 103 | property: { 104 | options: [ 105 | { 106 | id: 'optt4uJVT8R9A', 107 | name: '选择操作', 108 | color: { name: 'green_0', value: '#DCF3D1' }, 109 | }, 110 | { 111 | id: 'optLwTD20mANS', 112 | name: '提交此条', 113 | color: { name: 'deepPurple_0', value: '#E5E1FC' }, 114 | }, 115 | { 116 | id: 'optwEgi4xDTId', 117 | name: '提交全部', 118 | color: { name: 'indigo_0', value: '#DDE7FF' }, 119 | }, 120 | { 121 | id: 'optF9RPpvQ6BC', 122 | name: '删除此条', 123 | color: { name: 'blue_0', value: '#DDF5FF' }, 124 | }, 125 | ], 126 | }, 127 | editable: true, 128 | }, 129 | ], 130 | }, 131 | message: 'SUCCESS', 132 | }; 133 | -------------------------------------------------------------------------------- /src/db/vikaModel/ChatBot/mod.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sort-keys */ 2 | 3 | import type { 4 | Sheet, 5 | // Field, 6 | } from '../Model'; 7 | 8 | import { replaceSyncStatus, actionState } from '../actionBar.js'; 9 | 10 | const name = '智聊|Chatbot'; 11 | const code = 'chatBotSheet'; 12 | 13 | const vikaFields = { 14 | code: 200, 15 | success: true, 16 | data: { 17 | fields: [ 18 | { 19 | id: 'fldA47yx9L3bk', 20 | name: '机器人ID|id', 21 | type: 'AutoNumber', 22 | editable: false, 23 | isPrimary: true, 24 | }, 25 | { 26 | id: 'fldPrcQ5QfhxV', 27 | name: '昵称|name', 28 | type: 'SingleText', 29 | property: {}, 30 | editable: true, 31 | }, 32 | { 33 | id: 'fld6rYKTZ0zQV', 34 | name: '描述|desc', 35 | type: 'Text', 36 | editable: true, 37 | }, 38 | { 39 | id: 'fldxD8wwu1hGy', 40 | name: '类型|type', 41 | type: 'SingleSelect', 42 | property: { 43 | options: [ 44 | { 45 | id: 'opt1LUWhuInQB', 46 | name: 'ChatGPT', 47 | color: { 48 | name: 'deepPurple_0', 49 | value: '#E5E1FC', 50 | }, 51 | }, 52 | { 53 | id: 'optMmnD7Lx6dp', 54 | name: '文心一言', 55 | color: { 56 | name: 'indigo_0', 57 | value: '#DDE7FF', 58 | }, 59 | }, 60 | { 61 | id: 'optOyZnLw4AZJ', 62 | name: '扣子', 63 | color: { 64 | name: 'blue_0', 65 | value: '#DDF5FF', 66 | }, 67 | }, 68 | ], 69 | }, 70 | editable: true, 71 | }, 72 | { 73 | id: 'fldRVqJtsrH0H', 74 | name: '模型|model', 75 | type: 'SingleText', 76 | property: {}, 77 | editable: true, 78 | }, 79 | { 80 | id: 'fld4P2sX0CHco', 81 | name: '系统提示词|prompt', 82 | type: 'Text', 83 | editable: true, 84 | }, 85 | { 86 | id: 'fldguMiluobGu', 87 | name: '配额|quota', 88 | type: 'Number', 89 | property: { 90 | precision: 0, 91 | }, 92 | editable: true, 93 | }, 94 | { 95 | id: 'fld7aHIRIo4Sj', 96 | name: '接入点|endpoint', 97 | type: 'SingleText', 98 | property: {}, 99 | editable: true, 100 | }, 101 | { 102 | id: 'fldcZcmi2z6ZE', 103 | name: '密钥|key', 104 | type: 'SingleText', 105 | property: {}, 106 | editable: true, 107 | }, 108 | ], 109 | }, 110 | message: 'SUCCESS', 111 | }; 112 | 113 | let fields: any = vikaFields.data.fields; 114 | 115 | if (actionState[code]) { 116 | fields = replaceSyncStatus(fields); 117 | } 118 | 119 | const defaultRecords: any = { 120 | code: 200, 121 | success: true, 122 | message: 'Request successful', 123 | data: { 124 | total: 1, 125 | pageNum: 1, 126 | pageSize: 1, 127 | records: [ 128 | { 129 | recordId: 'rec4KyET4ALNJ', 130 | fields: { 131 | '昵称|name': '小G', 132 | '描述|desc': '示例配置数据', 133 | '类型|type': 'ChatGPT', 134 | '模型|model': 'gpt-3.5-turbo', 135 | '系统提示词|prompt': '你是智能助手小G', 136 | '配额|quota': 99999999, 137 | '接入点|endpoint': 'https://api.gptgod.online', 138 | '密钥|key': 'sk-Glz4aVRaiXT7AyPH7E7xxxxxx7zQRXxxxxxkuycUi', 139 | }, 140 | }, 141 | ], 142 | }, 143 | }; 144 | 145 | export const sheet: Sheet = { 146 | fields, 147 | name, 148 | defaultRecords: defaultRecords.data.records, 149 | }; 150 | -------------------------------------------------------------------------------- /src/modules/notices/notices.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | @Injectable() 3 | export class NoticesService { 4 | getTimedTask(taskRecords: any[]): TaskConfig[] { 5 | const timedTasks: TaskConfig[] = taskRecords 6 | .map((fields: any) => { 7 | const { desc, time, cycle, type, name, id, alias, state, recordId } = 8 | fields as TaskFields; 9 | 10 | const isActive = state === '开启'; 11 | const isContact = type === '好友'; 12 | const target = isContact 13 | ? { name: name || '', id: id || '', alias: alias || '' } 14 | : { topic: name || '', id: id || '' }; 15 | 16 | const taskConfig: TaskConfig = { 17 | id: recordId, 18 | msg: desc || '', 19 | time: Number(time) || 0, 20 | cycle: cycle || '无重复', 21 | targetType: isContact ? 'contact' : 'room', 22 | target, 23 | active: isActive, 24 | rule: '', 25 | }; 26 | 27 | taskConfig.rule = getRule(taskConfig); 28 | 29 | return isActive && desc && time && cycle && (name || id || alias) 30 | ? taskConfig 31 | : null; 32 | }) 33 | .filter(Boolean) as TaskConfig[]; 34 | 35 | return timedTasks; 36 | } 37 | } 38 | 39 | const getRule = (task: TaskConfig) => { 40 | const curTimeF = new Date(task.time); 41 | // const curTimeF = new Date(task.time+8*60*60*1000) 42 | let curRule = '* * * * * *'; 43 | let dayOfWeek: any = '*'; 44 | let month: any = '*'; 45 | let dayOfMonth: any = '*'; 46 | let hour: any = curTimeF.getHours(); 47 | let minute: any = curTimeF.getMinutes(); 48 | const second = 0; 49 | const addMonth = []; 50 | switch (task.cycle) { 51 | case '每季度': 52 | month = curTimeF.getMonth(); 53 | for (let i = 0; i < 4; i++) { 54 | if (month + 3 <= 11) { 55 | addMonth.push(month); 56 | } else { 57 | addMonth.push(month - 9); 58 | } 59 | month = month + 3; 60 | } 61 | month = addMonth; 62 | break; 63 | case '每天': 64 | break; 65 | case '每周': 66 | dayOfWeek = curTimeF.getDay(); 67 | break; 68 | case '每月': 69 | month = curTimeF.getMonth(); 70 | break; 71 | case '每小时': 72 | hour = '*'; 73 | break; 74 | case '每30分钟': 75 | hour = '*'; 76 | minute = [0, 30]; 77 | break; 78 | case '每15分钟': 79 | hour = '*'; 80 | minute = [0, 15, 30, 45]; 81 | break; 82 | case '每10分钟': 83 | hour = '*'; 84 | minute = [0, 10, 20, 30, 40, 50]; 85 | break; 86 | case '每5分钟': 87 | hour = '*'; 88 | minute = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55]; 89 | break; 90 | case '每分钟': 91 | hour = '*'; 92 | minute = '*'; 93 | break; 94 | default: 95 | month = curTimeF.getMonth(); 96 | dayOfMonth = curTimeF.getDate(); 97 | break; 98 | } 99 | curRule = `${second} ${minute} ${hour} ${dayOfMonth} ${month} ${dayOfWeek}`; 100 | return curRule; 101 | }; 102 | 103 | export type TaskFields = { 104 | desc?: string; 105 | time?: string; 106 | cycle?: string; 107 | type?: string; 108 | name?: string; 109 | id?: string; 110 | alias?: string; 111 | state?: string; 112 | recordId: string; 113 | }; 114 | 115 | export interface TaskConfig { 116 | id: string; 117 | msg: string; 118 | time: number; 119 | cycle: string; 120 | targetType: 'contact' | 'room'; 121 | target: BusinessRoom | BusinessUser; 122 | active: boolean; 123 | rule: string; 124 | } 125 | 126 | export type BusinessRoom = { 127 | id?: string; 128 | luckyDog?: string; 129 | memberAlias?: string; 130 | topic: string; 131 | }; 132 | 133 | export type BusinessUser = { 134 | alias?: string; 135 | id?: string; 136 | name: string; 137 | }; 138 | -------------------------------------------------------------------------------- /src/db/vikaModel/WhiteList/fields.ts: -------------------------------------------------------------------------------- 1 | export const vikaFields = { 2 | code: 200, 3 | success: true, 4 | data: { 5 | fields: [ 6 | { 7 | id: 'fldcsbKTfZhqh', 8 | name: '编号|serialNumber', 9 | type: 'AutoNumber', 10 | editable: false, 11 | isPrimary: true, 12 | }, 13 | { 14 | id: 'fldUWBUG0jtxW', 15 | name: '所属应用|app', 16 | type: 'SingleSelect', 17 | property: { 18 | options: [ 19 | { 20 | id: 'optcUHnoLIZgh', 21 | name: '智能问答|qa', 22 | color: { name: 'deepPurple_0', value: '#E5E1FC' }, 23 | }, 24 | { 25 | id: 'optARq3pMmw2i', 26 | name: '活动管理|act', 27 | color: { name: 'indigo_0', value: '#DDE7FF' }, 28 | }, 29 | { 30 | id: 'opts3hdWGDZyN', 31 | name: '消息存储|msg', 32 | color: { name: 'blue_0', value: '#DDF5FF' }, 33 | }, 34 | { 35 | id: 'optEGmWTUW9IZ', 36 | name: 'ChatGPT|gpt', 37 | color: { name: 'teal_0', value: '#D6F3E8' }, 38 | }, 39 | ], 40 | }, 41 | editable: true, 42 | }, 43 | { 44 | id: 'fldxFNHqTxc5h', 45 | name: '类型|type', 46 | type: 'SingleSelect', 47 | property: { 48 | options: [ 49 | { 50 | id: 'optqTEbP3IKGO', 51 | name: '好友', 52 | color: { name: 'deepPurple_0', value: '#E5E1FC' }, 53 | }, 54 | { 55 | id: 'opt5IiZCFPldl', 56 | name: '群', 57 | color: { name: 'indigo_0', value: '#DDE7FF' }, 58 | }, 59 | ], 60 | }, 61 | editable: true, 62 | }, 63 | { 64 | id: 'fldzD9S6sq9QR', 65 | name: '昵称/群名称|name', 66 | type: 'SingleText', 67 | property: {}, 68 | editable: true, 69 | }, 70 | { 71 | id: 'fldkTrMcb63oY', 72 | name: '好友ID/群ID(选填)|id', 73 | type: 'SingleText', 74 | property: {}, 75 | editable: true, 76 | }, 77 | { 78 | id: 'fldWelcS8ISNn', 79 | name: '好友备注(选填)|alias', 80 | type: 'SingleText', 81 | property: {}, 82 | editable: true, 83 | }, 84 | { 85 | id: 'fldAj0kCgDIDT', 86 | name: '备注说明(选填)|info', 87 | type: 'Text', 88 | editable: true, 89 | }, 90 | { 91 | id: 'fldXrv3ioaJgK', 92 | name: '启用状态|state', 93 | type: 'SingleSelect', 94 | property: { 95 | options: [ 96 | { 97 | id: 'optzOM0Xof9Ln', 98 | name: '开启', 99 | color: { name: 'deepPurple_0', value: '#E5E1FC' }, 100 | }, 101 | { 102 | id: 'opt8qyV17YhtJ', 103 | name: '关闭', 104 | color: { name: 'indigo_0', value: '#DDE7FF' }, 105 | }, 106 | ], 107 | }, 108 | editable: true, 109 | }, 110 | { 111 | id: 'fldjMUlBxFjDK', 112 | name: '配额(选填)|quota', 113 | type: 'Number', 114 | property: { defaultValue: '20', precision: 0 }, 115 | editable: true, 116 | }, 117 | { 118 | id: 'fldTg4jPISBHT', 119 | name: '管理员昵称|adminName', 120 | type: 'Text', 121 | editable: true, 122 | }, 123 | { 124 | id: 'fldVXbsL4Qrqp', 125 | name: '管理员好友备注(选填)|adminAlias', 126 | type: 'Text', 127 | editable: true, 128 | }, 129 | { 130 | id: 'fldY7IfHw3QxC', 131 | name: '管理员ID(选填)|adminId', 132 | type: 'Text', 133 | editable: true, 134 | }, 135 | ], 136 | }, 137 | message: 'SUCCESS', 138 | }; 139 | -------------------------------------------------------------------------------- /src/modules/notices/notices.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Get, 4 | Post, 5 | Body, 6 | Request, 7 | UnauthorizedException, 8 | } from '@nestjs/common'; 9 | import { Store } from '../../db/store.js'; 10 | import { NoticesService } from './notices.service.js'; 11 | 12 | @Controller('api/v1/notice') 13 | export class NoticesController { 14 | constructor(private readonly noticesService: NoticesService) {} 15 | @Get('list') 16 | async findAll(@Request() req: any): Promise { 17 | const user = req.user; 18 | // console.debug(user); 19 | // console.debug(Store.users); 20 | const db = Store.findUser(user.userId); 21 | if (!db) { 22 | throw new UnauthorizedException(); 23 | } 24 | // console.debug(db); 25 | const data = await db.db.notice.findAll(); 26 | const items = data.data.map((value: any) => { 27 | const fields = value.fields; 28 | fields.recordId = value.recordId; 29 | return fields; 30 | }); 31 | // console.debug(data); 32 | const res: any = { 33 | code: 200, 34 | message: 'success', 35 | data: { 36 | page: 1, 37 | pageSize: 1000, 38 | pageCount: 1, 39 | itemCount: data.data.length, 40 | items: items, 41 | }, 42 | }; 43 | return res; 44 | } 45 | 46 | @Get('task') 47 | async getTimedTask(@Request() req: any): Promise { 48 | const user = req.user; 49 | // console.debug(user); 50 | // console.debug(Store.users); 51 | const db = Store.findUser(user.userId); 52 | if (!db) { 53 | throw new UnauthorizedException(); 54 | } 55 | // console.debug(db); 56 | const data = await db.db.notice.findAll(); 57 | const items = data.data.map((value: any) => { 58 | const fields = value.fields; 59 | fields.recordId = value.recordId; 60 | return fields; 61 | }); 62 | 63 | const jobs = this.noticesService.getTimedTask(items); 64 | 65 | // console.debug(data); 66 | const res: any = { 67 | code: 200, 68 | message: 'success', 69 | data: { 70 | page: 1, 71 | pageSize: 1000, 72 | pageCount: 1, 73 | itemCount: jobs.length, 74 | items: jobs, 75 | }, 76 | }; 77 | return res; 78 | } 79 | @Post('create') 80 | async create(@Body() body: any, @Request() req: any): Promise { 81 | const user = req.user; 82 | // console.debug(user); 83 | // console.debug(Store.users); 84 | const db = Store.findUser(user.userId); 85 | if (!db) { 86 | throw new UnauthorizedException(); 87 | } 88 | // console.debug(db); 89 | 90 | const resCreate = await db.db.notice.create(body); 91 | console.debug('resCreate', resCreate); 92 | const res: any = { code: 400, message: 'fail', data: {} }; 93 | if (resCreate.data.recordId) { 94 | res.code = 200; 95 | res.message = 'success'; 96 | res.data = resCreate; 97 | } 98 | return res; 99 | } 100 | @Post('delete') 101 | async delete(@Body() body: any, @Request() req: any): Promise { 102 | // { 103 | // "recordId":21705 104 | // } 105 | console.debug('notice delete', body); 106 | const user = req.user; 107 | // console.debug(user); 108 | // console.debug(Store.users); 109 | const db = Store.findUser(user.userId); 110 | if (!db) { 111 | throw new UnauthorizedException(); 112 | } 113 | // console.debug(db); 114 | 115 | const resDel = await db.db.notice.delete(body.recordId); 116 | console.debug('notice resDel', resDel); 117 | 118 | let res: any = { 119 | code: 400, 120 | message: 'error', 121 | data: {}, 122 | }; 123 | if (resDel.message === 'success') { 124 | res = { 125 | code: 200, 126 | message: 'success', 127 | data: { 128 | recordId: body.recordId, 129 | }, 130 | }; 131 | } 132 | return res; 133 | } 134 | } 135 | --------------------------------------------------------------------------------