diff --git a/package.json b/package.json index 80a14188..201a1e8a 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "only-build:web": "pnpm run -C packages/web build", "watch:web": "pnpm run -C packages/web watch", "project:web": "pnpm run -C packages/web build && node tools/copy-files-web2server.js", - "dev:server": "pnpm run -C packages/server dev", + "dev:server": "pnpm run -C packages/server start:dev", "build:server": "pnpm run -C packages/server build", "dev:docs": "pnpm run -C packages/docs dev", "build:docs": "pnpm run -C packages/docs build", diff --git a/packages/desktop/electron/main/contract.ts b/packages/desktop/electron/main/contract.ts index fc5ab801..126cfba2 100644 --- a/packages/desktop/electron/main/contract.ts +++ b/packages/desktop/electron/main/contract.ts @@ -11,7 +11,7 @@ export const VITE_API_URL = import.meta.env.VITE_API_URL; // export const WEB_URL = 'http://127.0.0.1:9191/'; // export const VITE_API_URL = 'http://127.0.0.1:9190/'; export const preload = path.join(__dirname, '../preload/index.js'); - +export const serverPath = path.join(__dirname, '../server/index.js'); export const DIST_ELECTRON = path.join(__dirname, '../'); export const DIST = path.join(DIST_ELECTRON, '../dist'); diff --git a/packages/desktop/electron/main/index.ts b/packages/desktop/electron/main/index.ts index 9d3794fc..eb328cac 100644 --- a/packages/desktop/electron/main/index.ts +++ b/packages/desktop/electron/main/index.ts @@ -1,13 +1,17 @@ import { app, BrowserWindow, shell, ipcMain } from 'electron'; import { release } from 'node:os'; +import { spawn } from 'node:child_process'; import * as mainWin from '../win/mainWin'; import { initTray } from './tray'; import { update } from './update'; import { registerFileProtocol } from './protocol'; import { registerGlobalShortcut, unregisterAllGlobalShortcut } from './globalShortcut'; import { initConfig, getConfig } from '@pear-rec/server/src/config'; +import { serverPath } from './contract'; import './ipcMain'; -import '@pear-rec/server/src'; + +const appName = app.getPath('exe'); +const serverAppProcess = spawn(appName, [serverPath]); initConfig(); @@ -16,8 +20,10 @@ initConfig(); // ├─┬ dist-electron // │ ├─┬ main // │ │ └── index.js > Electron-Main -// │ └─┬ preload -// │ └── index.js > Preload-Scripts +// │ ├─┬ preload +// │ │ └── index.js > Preload-Scripts +// │ └─┬ server +// │ └── index.js > Server-Scripts // ├─┬ dist // │ └── index.html > Electron-Renderer // @@ -52,6 +58,7 @@ app.whenReady().then(() => { }); app.on('will-quit', () => { + serverAppProcess?.kill(); unregisterAllGlobalShortcut(); }); diff --git a/packages/desktop/vite.config.ts b/packages/desktop/vite.config.ts index f3b391e7..d7141380 100644 --- a/packages/desktop/vite.config.ts +++ b/packages/desktop/vite.config.ts @@ -5,7 +5,7 @@ import pkg from './package.json'; // https://vitejs.dev/config/ export default defineConfig(({ command }) => { - rmSync('dist-electron', { recursive: true, force: true }); + // rmSync('dist-electron', { recursive: true, force: true }); const isServe = command === 'serve'; const isBuild = command === 'build'; diff --git a/packages/server/CHANGELOG.md b/packages/server/CHANGELOG.md index 69fb4e3d..b42b68c1 100644 --- a/packages/server/CHANGELOG.md +++ b/packages/server/CHANGELOG.md @@ -1,5 +1,9 @@ # @pear-rec/server +## 1.3.0 + +refactor: 升级 nestjs 架构 + ## 1.2.5 feat: 重置设置 diff --git a/packages/server/README.md b/packages/server/README.md new file mode 100644 index 00000000..f5aa86c5 --- /dev/null +++ b/packages/server/README.md @@ -0,0 +1,73 @@ +

+ Nest Logo +

+ +[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 +[circleci-url]: https://circleci.com/gh/nestjs/nest + +

A progressive Node.js framework for building efficient and scalable server-side applications.

+

+NPM Version +Package License +NPM Downloads +CircleCI +Coverage +Discord +Backers on Open Collective +Sponsors on Open Collective + + Support us + +

+ + +## Description + +[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. + +## Installation + +```bash +$ pnpm install +``` + +## Running the app + +```bash +# development +$ pnpm run start + +# watch mode +$ pnpm run start:dev + +# production mode +$ pnpm run start:prod +``` + +## Test + +```bash +# unit tests +$ pnpm run test + +# e2e tests +$ pnpm run test:e2e + +# test coverage +$ pnpm run test:cov +``` + +## Support + +Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). + +## Stay in touch + +- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) +- Website - [https://nestjs.com](https://nestjs.com/) +- Twitter - [@nestframework](https://twitter.com/nestframework) + +## License + +Nest is [MIT licensed](LICENSE). diff --git a/packages/server/nest-cli.json b/packages/server/nest-cli.json new file mode 100644 index 00000000..f9aa683b --- /dev/null +++ b/packages/server/nest-cli.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true + } +} diff --git a/packages/server/package.json b/packages/server/package.json index 26ef4994..2cba8ec2 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,39 +1,44 @@ { "name": "@pear-rec/server", - "version": "1.2.4", - "description": "", - "main": "dist/index.js", - "typings": "index.d.ts", + "version": "1.3.0", "scripts": { - "dev": "nodemon index.ts", - "build": "rollup -c", - "watch": "rollup -c --watch", - "preview": "node dist/index.js" + "build": "nest build", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "start": "nest start", + "start:dev": "nest start --watch", + "start:debug": "nest start --debug --watch", + "start:prod": "node dist/main" + }, + "dependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/platform-express": "^10.0.0", + "@nestjs/serve-static": "^4.0.0", + "@nestjs/typeorm": "^10.0.1", + "jsonfile": "^6.1.0", + "multer": "^1.4.5-lts.1", + "reflect-metadata": "^0.1.13", + "rxjs": "^7.8.1" }, - "author": "027xiguapi", - "license": "Apache-2.0", "devDependencies": { - "@rollup/plugin-commonjs": "^25.0.4", - "@rollup/plugin-json": "^6.0.0", - "@rollup/plugin-node-resolve": "^15.2.1", - "@rollup/plugin-typescript": "^11.1.5", - "@types/cors": "^2.8.14", + "@nestjs/cli": "^10.0.0", + "@nestjs/schematics": "^10.0.0", + "@nestjs/testing": "^10.0.0", "@types/express": "^4.17.17", - "@types/node": "^20.6.0", - "@types/validator": "^13.11.1", - "nodemon": "^3.0.1", - "rollup": "^3.29.3", + "@types/multer": "^1.4.11", + "@types/node": "^20.3.1", + "@types/supertest": "^2.0.12", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "eslint": "^8.42.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.0", + "prettier": "^3.0.0", + "source-map-support": "^0.5.21", + "supertest": "^6.3.3", + "ts-loader": "^9.4.3", "ts-node": "^10.9.1", - "typescript": "^5.2.2" - }, - "dependencies": { - "body-parser": "^1.20.2", - "cors": "^2.8.5", - "express": "^4.18.2", - "http-proxy-middleware": "^2.0.6", - "multer": "1.4.5-lts.1", - "reflect-metadata": "^0.1.13", - "sql.js": "^1.8.0", - "typeorm": "^0.3.17" + "tsconfig-paths": "^4.2.0", + "typescript": "^5.1.3" } } \ No newline at end of file diff --git a/packages/server/rollup.config.mjs b/packages/server/rollup.config.mjs deleted file mode 100644 index 7c9c563d..00000000 --- a/packages/server/rollup.config.mjs +++ /dev/null @@ -1,21 +0,0 @@ -import typescript from '@rollup/plugin-typescript'; -import { nodeResolve } from '@rollup/plugin-node-resolve'; -import commonjs from '@rollup/plugin-commonjs'; -import json from '@rollup/plugin-json'; - -export default { - input: 'src/index.ts', - output: { - file: 'dist/index.js', - format: 'cjs', - sourcemap: true, - }, - context: 'window', - external: ['typeorm', 'sql.js'], - plugins: [ - nodeResolve({ preferBuiltins: true }), - commonjs({ ignoreDynamicRequires: true }), - json(), - typescript(), - ], -}; diff --git a/packages/server/src/api/index.ts b/packages/server/src/api/index.ts index ab83dcdc..82281654 100644 --- a/packages/server/src/api/index.ts +++ b/packages/server/src/api/index.ts @@ -1,13 +1,7 @@ -import { Application, Request, Response } from 'express'; +// import { initBaiduProxy } from '../proxy/baidu'; +// import { initGoogleProxy } from '../proxy/google'; -import { initLocalApi } from './local'; -import { initPageProxy } from './page'; -import { initBaiduProxy } from '../proxy/baidu'; -import { initGoogleProxy } from '../proxy/google'; - -export function initApi(app: Application) { - initLocalApi(app); - initBaiduProxy(app); - initGoogleProxy(app); - initPageProxy(app); -} +// export function initApi(app: Application) { +// initBaiduProxy(app); +// initGoogleProxy(app); +// } diff --git a/packages/server/src/api/local.ts b/packages/server/src/api/local.ts index 19ebbb26..906179e8 100644 --- a/packages/server/src/api/local.ts +++ b/packages/server/src/api/local.ts @@ -4,26 +4,24 @@ import { join, dirname } from 'node:path'; import { Application } from 'express'; import { exec } from 'child_process'; import { getImgsByImgUrl, getAudiosByAudioUrl, getVideosByVideoUrl } from '../util/index'; -import { RecordController } from '../controller/RecordController'; -import { UserController } from '../controller/UserController'; -import { SettingController } from '../controller/SettingController'; +import { RecordsService } from '../records/records.service'; +import { UsersService } from '../users/users.service'; +import { SettingsService } from '../settings/settings.service'; import { PEAR_FILES_PATH } from '../contract'; -const recordController = new RecordController(); -const userController = new UserController(); -const settingController = new SettingController(); +// const usersService = new UsersService(); const storage = multer.diskStorage({ destination: async function (req, file, cb) { const { type, userId } = req.body; - const user = await userController._getUserById(userId); - const setting = await settingController._getSettingByUserId(userId); + // const user = await usersService.findOneById(userId); + // const setting = await settingController._getSettingByUserId(userId); try { - const filePath = join(setting?.filePath || PEAR_FILES_PATH, `${user.uuid}/${type}`); - if (!fs.existsSync(filePath)) { - fs.mkdirSync(filePath, { recursive: true }); - } - cb(null, filePath); + // const filePath = join(setting?.filePath || PEAR_FILES_PATH, `${user.uuid}/${type}`); + // if (!fs.existsSync(filePath)) { + // fs.mkdirSync(filePath, { recursive: true }); + // } + // cb(null, filePath); } catch (err) { console.log('saveFile err', err); } @@ -45,7 +43,7 @@ const upload = multer({ storage: storage }); export function initLocalApi(app: Application) { app.post('/saveFile', upload.single('file'), async (req: any, res) => { - recordController.saveFile(req, res); + // recordController.saveFile(req, res); }); app.get('/getFile', async (req, res) => { diff --git a/packages/server/src/api/page.ts b/packages/server/src/api/page.ts deleted file mode 100644 index 17612554..00000000 --- a/packages/server/src/api/page.ts +++ /dev/null @@ -1,50 +0,0 @@ -import express, { Application, Request, Response } from "express"; -import path from "node:path"; - -export function initPageProxy(app: Application) { - app.use(express.static(path.resolve("public"))); - - app.get("/", (req, res) => { - res.sendFile(path.resolve("public/index.html")); - }); - - app.get("/shotScreen", (req, res) => { - res.sendFile(path.resolve("public/shotScreen.html")); - }); - - app.get("/recorderScreen", (req, res) => { - res.sendFile(path.resolve("public/recorderScreen.html")); - }); - - app.get("/recorderVideo", (req, res) => { - res.sendFile(path.resolve("public/recorderVideo.html")); - }); - - app.get("/recorderAudio", (req, res) => { - res.sendFile(path.resolve("public/recorderAudio.html")); - }); - - app.get("/viewImage", (req, res) => { - res.sendFile(path.resolve("public/viewImage.html")); - }); - - app.get("/viewVideo", (req, res) => { - res.sendFile(path.resolve("public/viewVideo.html")); - }); - - app.get("/setting", (req, res) => { - res.sendFile(path.resolve("public/setting.html")); - }); - - app.get("/clipScreen", (req, res) => { - res.sendFile(path.resolve("public/clipScreen.html")); - }); - - app.get("/editImage", (req, res) => { - res.sendFile(path.resolve("public/editImage.html")); - }); - - app.get("/viewAudio", (req, res) => { - res.sendFile(path.resolve("public/viewAudio.html")); - }); -} diff --git a/packages/server/src/app/app.controller.ts b/packages/server/src/app/app.controller.ts new file mode 100644 index 00000000..15f0c470 --- /dev/null +++ b/packages/server/src/app/app.controller.ts @@ -0,0 +1,199 @@ +import { + Controller, + Get, + Post, + StreamableFile, + Query, + UseInterceptors, + Res, + UploadedFile, +} from '@nestjs/common'; +import { join, dirname, basename, extname } from 'node:path'; +import { readdirSync, createReadStream, statSync } from 'node:fs'; +import { FileInterceptor } from '@nestjs/platform-express'; +import { CreateFileDto } from './dto/create-file.dto'; +import { AppService } from './app.service'; +import { Record } from '../records/entity/record.entity'; +import { exec } from 'child_process'; +import type { Response } from 'express'; + +@Controller() +export class AppController { + constructor(private readonly appService: AppService) {} + + @Post('/upload/file') + // @UseInterceptors(FileInterceptor('fileName')) + async uploadFile(@UploadedFile() fileDto: CreateFileDto): Promise { + return this.appService.uploadFile(fileDto); + } + + @Get('/getFile') + getFile(@Query() query): StreamableFile { + const file = createReadStream(query.url); + return new StreamableFile(file); + } + + @Get('/audio') + getAudio(@Query() query, @Res({ passthrough: true }) res: Response): StreamableFile { + const stream = createReadStream(query.url); + const stat = statSync(query.url); + const fileSize = stat.size; + res.set({ + 'Content-Type': 'audio/mp3', + 'Content-Length': fileSize, + }); + return new StreamableFile(stream); + } + + @Get('/video') + getVideo(@Query() query, @Res({ passthrough: true }) res: Response): StreamableFile { + const stream = createReadStream(query.url); + const stat = statSync(query.url); + const fileSize = stat.size; + res.set({ + 'Content-Length': fileSize, + 'Content-Type': 'video/mp4', + }); + return new StreamableFile(stream); + } + + @Get('/getImgs') + getImgs(@Query() query) { + const imgUrl = query.imgUrl; + let imgs: any[] = []; + let index = 0; + let currentIndex = 0; + try { + const directoryPath = dirname(imgUrl); + const files = readdirSync(directoryPath); // 读取目录内容 + files.forEach((file) => { + const filePath = join(directoryPath, file); + function isImgFile(filePath: string): boolean { + const ext = extname(filePath).toLowerCase(); + return [ + '.jpg', + '.jpeg', + '.jfif', + '.pjpeg', + '.pjp', + '.png', + 'apng', + '.gif', + '.bmp', + '.avif', + '.webp', + '.ico', + ].includes(ext); + } + const port = process.env.PORT || 9190; + const protocol = `http://localhost:${port}/getFile?url=`; + if (isImgFile(filePath)) { + filePath == imgUrl && (currentIndex = index); + imgs.push({ + url: `${protocol}${filePath}`, + filePath: filePath, + index, + }); + index++; + } + }); + } catch (err) { + console.log('getImgsByImgUrl', err); + } + return { imgs, currentIndex }; + } + + @Get('/getAudios') + async getAudios(@Query() query): Promise { + const audioUrl = query.audioUrl; + const directoryPath = dirname(audioUrl); + const files = readdirSync(directoryPath); // 读取目录内容 + let audios: any[] = []; + let index = 0; + files.forEach((file) => { + const filePath = join(directoryPath, file); + const port = process.env.PORT || 9190; + const protocol = `http://localhost:${port}/audio?url=`; + function isAudioFile(filePath: string): boolean { + const ext = extname(filePath).toLowerCase(); + return [ + '.mp3', + '.wav', + '.aac', + '.ogg', + '.flac', + '.aiff', + '.aif', + '.m4a', + '.alac', + '.ac3', + '.webm', + ].includes(ext); + } + if (isAudioFile(filePath)) { + const fileName = basename(filePath); + if (filePath == audioUrl) { + audios.unshift({ + url: `${protocol}${filePath}`, + name: fileName, + cover: './imgs/music.png', + }); + } else { + audios.push({ + url: `${protocol}${filePath}`, + name: fileName, + cover: './imgs/music.png', + }); + } + index++; + } + }); + + return audios; + } + + @Get('/getVideos') + getVideos(@Query() query) { + const videoUrl = query.videoUrl; + const directoryPath = dirname(videoUrl); + const files = readdirSync(directoryPath); + let videos: any[] = []; + let index = 0; + let currentIndex = 0; + files.forEach((file) => { + const filePath = join(directoryPath, file); + const port = process.env.PORT || 9190; + const protocol = `http://localhost:${port}/video?url=`; + function isVideoFile(filePath: string): boolean { + const ext = extname(filePath).toLowerCase(); + return ['.mp4', '.mkv', '.avi', '.mov', '.wmv', '.webm'].includes(ext); + } + if (isVideoFile(filePath)) { + const fileName = basename(filePath); + filePath == videoUrl && (currentIndex = index); + videos.push({ + url: `${protocol}${filePath}`, + index, + name: fileName, + }); + index++; + } + }); + return { videos, currentIndex }; + } + + @Get('/getFolder') + getFolder(@Query() query): string { + const folderPath = query.folderPath; + exec(`start "" "${folderPath}"`); + return 'ok'; + } + + @Get('/openFilePath') + openFilePath(@Query() query): string { + const filePath = query.filePath as string; + const folderPath = dirname(filePath); + exec(`start "" "${folderPath}"`); + return 'ok'; + } +} diff --git a/packages/server/src/app/app.module.ts b/packages/server/src/app/app.module.ts new file mode 100644 index 00000000..ef45ade6 --- /dev/null +++ b/packages/server/src/app/app.module.ts @@ -0,0 +1,49 @@ +import { join } from 'node:path'; +import { Module } from '@nestjs/common'; +import { APP_INTERCEPTOR, APP_FILTER } from '@nestjs/core'; +import { ServeStaticModule } from '@nestjs/serve-static'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { User } from '../users/entity/user.entity'; +import { Record } from '../records/entity/record.entity'; +import { Setting } from '../settings/entity/setting.entity'; +import { DB_PATH } from '../contract'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; +import { UsersModule } from '../users/users.module'; +import { RecordsModule } from '../records/records.module'; +import { SettingsModule } from '../settings/settings.module'; +import { ResponseInterceptor } from '../util/response.interceptor'; +import { AllExceptionsFilter } from '../util/exception.filter'; + +@Module({ + imports: [ + ServeStaticModule.forRoot({ + rootPath: join(__dirname, '../../', 'public'), + }), + TypeOrmModule.forRoot({ + type: 'sqljs', + location: DB_PATH, + autoSave: true, + entities: [User, Record, Setting], + synchronize: true, + logging: true, + logger: 'file', + }), + UsersModule, + RecordsModule, + SettingsModule, + ], + controllers: [AppController], + providers: [ + AppService, + { + provide: APP_INTERCEPTOR, + useClass: ResponseInterceptor, + }, + { + provide: APP_FILTER, + useClass: AllExceptionsFilter, + }, + ], +}) +export class AppModule {} diff --git a/packages/server/src/app/app.service.ts b/packages/server/src/app/app.service.ts new file mode 100644 index 00000000..d1c487d4 --- /dev/null +++ b/packages/server/src/app/app.service.ts @@ -0,0 +1,31 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { Injectable } from '@nestjs/common'; +import { CreateFileDto } from './dto/create-file.dto'; +import { Record } from '../records/entity/record.entity'; +import { RecordsService } from '../records/records.service'; +import { UsersService } from '../users/users.service'; + +@Injectable() +export class AppService { + constructor( + private readonly recordsService: RecordsService, + private readonly usersService: UsersService, + ) {} + + getHello(): string { + return 'Hello World!'; + } + + async uploadFile(fileDto: CreateFileDto): Promise { + const userId = fileDto.userId; + const user = await this.usersService.findOne(userId); + let record = { + filePath: fileDto.path, + fileType: fileDto.type, + user: user, + }; + + return this.recordsService.create(record); + } +} diff --git a/packages/server/src/app/dto/create-file.dto.ts b/packages/server/src/app/dto/create-file.dto.ts new file mode 100644 index 00000000..2fc04c09 --- /dev/null +++ b/packages/server/src/app/dto/create-file.dto.ts @@ -0,0 +1,11 @@ +export class CreateFileDto { + file: string; + + fileName: string; + + type: string; + + userId: number; + + path: string; +} diff --git a/packages/server/src/config/index.ts b/packages/server/src/config/index.ts index 93785899..1492a596 100644 --- a/packages/server/src/config/index.ts +++ b/packages/server/src/config/index.ts @@ -1,4 +1,4 @@ -import jsonfile from 'jsonfile'; +import * as jsonfile from 'jsonfile'; import * as fs from 'node:fs'; import { v5 as uuidv5 } from 'uuid'; import dayjs from 'dayjs'; diff --git a/packages/server/src/controller/RecordController.ts b/packages/server/src/controller/RecordController.ts deleted file mode 100644 index 0ac1489e..00000000 --- a/packages/server/src/controller/RecordController.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { AppDataSource } from '../dataSource'; -import { Record } from '../entity/Record'; -import { UserController } from '../controller/UserController'; - -const userController = new UserController(); - -export class RecordController { - async getRecords(req, res) { - const recordRepository = AppDataSource.getRepository(Record); - let { pageSize = 20, pageNumber = 1 } = req.query; - const offset = (pageNumber - 1) * pageSize; - const [records, total] = await recordRepository.findAndCount({ - skip: offset, - take: pageSize, - }); - res.json({ code: 0, data: records, total: total }); - } - - async createRecord(req, res) { - const recordRepository = AppDataSource.getRepository(Record); - const record = recordRepository.create(req.body); - recordRepository.save(record); - res.json({ code: 0, data: record }); - } - - async getRecord(req, res) { - const recordRepository = AppDataSource.getRepository(Record); - const record = await recordRepository.findOneBy({ id: req.params.id }); - - if (!record) { - return res.json({ code: -1, data: 'record not found' }); - } - - res.json({ code: 0, data: record }); - } - - async updateRecord(req, res) { - const recordRepository = AppDataSource.getRepository(Record); - const record = await recordRepository.findOneBy({ id: req.params.id }); - - if (!record) { - return res.json({ code: -1, data: 'record not found' }); - } - - recordRepository.merge(record, req.body); - await recordRepository.save(record); - res.json({ code: 0, data: record }); - } - - async deleteAllRecord(req, res) { - const recordRepository = AppDataSource.getRepository(Record); - const userId = req.params.userId; - const records = await recordRepository - .createQueryBuilder('record') - .leftJoinAndSelect('record.user', 'user') - .where('user.id = :id', { id: userId }) - .getMany(); - - if (!records) { - return res.json({ code: -1, data: 'record not found' }); - } - - await recordRepository.remove(records); - res.json({ code: 0, data: 'record deleted successfully' }); - } - - async deleteRecord(req, res) { - const recordRepository = AppDataSource.getRepository(Record); - const record = await recordRepository.findOneBy({ id: req.params.id }); - - if (!record) { - return res.json({ code: -1, data: 'record not found' }); - } - - await recordRepository.remove(record); - res.json({ code: 0, data: 'record deleted successfully' }); - } - - async saveFile(req, res) { - const recordRepository = AppDataSource.getRepository(Record); - const userId = req.body.userId; - const user = await userController._getUserById(userId); - let data = { - filePath: req.file.path, - fileType: req.body.type, - user: user, - }; - const record = recordRepository.create(data); - recordRepository.save(record); - res.json({ code: 0, data: record }); - } -} diff --git a/packages/server/src/controller/SettingController.ts b/packages/server/src/controller/SettingController.ts deleted file mode 100644 index 8f605b50..00000000 --- a/packages/server/src/controller/SettingController.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { AppDataSource } from '../dataSource'; -import { Setting } from '../entity/Setting'; -import { UserController } from '../controller/UserController'; -import { getDefaultConfig, resetConfig, editConfig } from '../config'; - -const userController = new UserController(); -export class SettingController { - async getSettings(req, res) { - const settingRepository = AppDataSource.getRepository(Setting); - const settings = await settingRepository.find(); - res.json({ code: 0, data: settings }); - } - - async createSetting(req, res) { - const settingRepository = AppDataSource.getRepository(Setting); - const setting = settingRepository.create(req.body); - await settingRepository.save(setting); - res.json({ code: 0, data: setting }); - } - - async getSetting(req, res) { - const settingRepository = AppDataSource.getRepository(Setting); - const setting = await settingRepository.findOneBy({ id: req.params.id }); - - if (!setting) { - return res.json({ code: -1, data: 'setting not found' }); - } - - res.json({ code: 0, data: setting }); - } - - async getSettingByUserId(req, res) { - const settingRepository = AppDataSource.getRepository(Setting); - const userId = req.params.userId; - const setting = await settingRepository - .createQueryBuilder('setting') - .leftJoinAndSelect('setting.user', 'user') - .where('user.id = :id', { id: userId }) - .getOne(); - - if (!setting) { - const user = await userController._getUserById(userId); - const defaultConfig = getDefaultConfig(); - let _setting = { - isProxy: defaultConfig.isProxy, - proxyPort: defaultConfig.proxyPort, - language: defaultConfig.language, - filePath: defaultConfig.filePath, - openAtLogin: defaultConfig.openAtLogin, - serverPath: defaultConfig.serverPath, - user: user, - }; - _setting = await settingRepository.save(_setting); - return res.json({ code: 0, data: _setting }); - } - - res.json({ code: 0, data: setting }); - } - - async resetSetting(req, res) { - const settingRepository = AppDataSource.getRepository(Setting); - const setting = await settingRepository.findOneBy({ id: req.params.id }); - - if (!setting) { - return res.json({ code: -1, data: 'setting not found' }); - } - let defaultConfig = getDefaultConfig(); - let _setting = { - isProxy: defaultConfig.isProxy, - proxyPort: defaultConfig.proxyPort, - language: defaultConfig.language, - filePath: defaultConfig.filePath, - openAtLogin: defaultConfig.openAtLogin, - serverPath: defaultConfig.serverPath, - }; - settingRepository.merge(setting, _setting); - await settingRepository.save(setting); - resetConfig(); - res.json({ code: 0, data: setting }); - } - - async updateSetting(req, res) { - const settingRepository = AppDataSource.getRepository(Setting); - const setting = await settingRepository.findOneBy({ id: req.params.id }); - - if (!setting) { - return res.json({ code: -1, data: 'setting not found' }); - } - const _setting = req.body; - settingRepository.merge(setting, _setting); - await settingRepository.save(setting); - for (let key in _setting) { - let value = _setting[key]; - editConfig(key, value); - } - res.json({ code: 0, data: setting }); - } - - async deleteSetting(req, res) { - const settingRepository = AppDataSource.getRepository(Setting); - const setting = await settingRepository.findOneBy({ id: req.params.id }); - - if (!setting) { - return res.json({ code: -1, data: 'setting not found' }); - } - - await settingRepository.remove(setting); - res.json({ code: 0, data: 'setting deleted successfully' }); - } - - async _getSettingByUserId(userId) { - const settingRepository = AppDataSource.getRepository(Setting); - const setting = await settingRepository - .createQueryBuilder('setting') - .leftJoinAndSelect('setting.user', 'user') - .where('user.id = :id', { id: userId }) - .getOne(); - - return setting; - } -} diff --git a/packages/server/src/controller/UserController.ts b/packages/server/src/controller/UserController.ts deleted file mode 100644 index d18aafac..00000000 --- a/packages/server/src/controller/UserController.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { AppDataSource } from '../dataSource'; -import { User } from '../entity/User'; -import { getConfig } from '../config'; - -export class UserController { - async getUsers(req, res) { - const userRepository = AppDataSource.getRepository(User); - const users = await userRepository.find(); - res.json({ code: 0, data: users }); - } - - async createUser(req, res) { - const userRepository = AppDataSource.getRepository(User); - const user = userRepository.create(req.body); - await userRepository.save(user); - res.json({ code: 0, data: user }); - } - - async getUser(req, res) { - const userRepository = AppDataSource.getRepository(User); - const user = await userRepository.findOneBy({ id: req.params.id }); - - if (!user) { - res.json({ code: -1, data: 'User not found' }); - } - - res.json({ code: 0, data: user }); - } - - async updateUser(req, res) { - const userRepository = AppDataSource.getRepository(User); - const user = await userRepository.findOneBy({ id: req.params.id }); - - if (!user) { - res.json({ code: -1, data: 'User not found' }); - } - - userRepository.merge(user, req.body); - await userRepository.save(user); - res.json({ code: 0, data: user }); - } - - async deleteUser(req, res) { - const userRepository = AppDataSource.getRepository(User); - const user = await userRepository.findOneBy({ id: req.params.id }); - - if (!user) { - res.json({ code: -1, data: 'User not found' }); - } - - await userRepository.remove(user); - res.json({ code: 0, data: 'User deleted successfully' }); - } - - async getCurrentUser(req, res) { - const userRepository = AppDataSource.getRepository(User); - const config = getConfig(); - const user = await userRepository.findOneBy({ uuid: config.user.uuid }); - - if (!user) { - let _user = { - ...config.user, - }; - _user = userRepository.create(_user); - await userRepository.save(_user); - res.json({ code: 0, data: _user }); - } - - res.json({ code: 0, data: user }); - } - - async _getUserById(id) { - const userRepository = AppDataSource.getRepository(User); - const user = await userRepository.findOneBy({ id: id }); - - return user; - } -} diff --git a/packages/server/src/dataSource.ts b/packages/server/src/dataSource.ts deleted file mode 100644 index daba135c..00000000 --- a/packages/server/src/dataSource.ts +++ /dev/null @@ -1,23 +0,0 @@ -import 'reflect-metadata'; -import fs from 'node:fs'; -import { dirname } from 'node:path'; -import { DataSource } from 'typeorm'; -import { User } from './entity/User'; -import { Record } from './entity/Record'; -import { Setting } from './entity/Setting'; -import { DB_PATH } from './contract'; - -const fileDir = dirname(DB_PATH); -if (!fs.existsSync(fileDir)) { - fs.mkdirSync(fileDir, { recursive: true }); -} - -export const AppDataSource = new DataSource({ - type: 'sqljs', - location: DB_PATH, - autoSave: true, - entities: [User, Record, Setting], - synchronize: true, - logging: true, - logger: 'file', -}); diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts deleted file mode 100644 index f2778dbf..00000000 --- a/packages/server/src/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -import 'reflect-metadata'; -import cors from 'cors'; -import bodyParser from 'body-parser'; -import express, { Request, Response } from 'express'; -import { AppDataSource } from './dataSource'; -import { AppRoutes } from './route/index'; -import { initApi } from './api'; -import { initConfig } from './config'; -import { PORT } from './contract'; - -export default function initApp() { - AppDataSource.initialize() - .then(async (connection) => { - const app = express(); - - app.use(cors()); - app.use(bodyParser.json()); - app.use(bodyParser.urlencoded({ extended: true })); - app.use(function (err, req, res, next) { - console.error('server:', err.stack); - res.status(500).send('服务器内部错误'); - }); - - initConfig(); - initApi(app); - - AppRoutes.forEach((route: any) => { - app[route.method](route.path, (request: Request, response: Response, next: Function) => { - route - .action(request, response) - .then(() => next) - .catch((err) => next(err)); - }); - }); - - app.listen(PORT); - - console.log(`Express application is up and running on port ${PORT}`); - }) - .catch((error) => console.log('TypeORM connection error: ', error)); -} - -initApp(); diff --git a/packages/server/src/main.ts b/packages/server/src/main.ts new file mode 100644 index 00000000..7629ff56 --- /dev/null +++ b/packages/server/src/main.ts @@ -0,0 +1,50 @@ +import * as fs from 'node:fs'; +import { join } from 'node:path'; +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app/app.module'; +import { initConfig, getConfig } from './config'; +import { PORT } from './contract'; +import * as multer from 'multer'; + +async function bootstrap() { + initConfig(); + + const app = await NestFactory.create(AppModule, { cors: true, bodyParser: false }); + + const storage = multer.diskStorage({ + destination: async function (req, file, cb) { + const { type, userId } = req.body; + const config = getConfig(); + const user = config.user; + try { + const filePath = join(config?.filePath, `${user.uuid}/${type}`); + if (!fs.existsSync(filePath)) { + fs.mkdirSync(filePath, { recursive: true }); + } + cb(null, filePath); + } catch (err) { + console.log('saveFile err', err); + } + }, + filename: function (req, file, cb) { + const fileTypeMap = { + ss: 'png', + rs: 'webm', + ra: 'webm', + ei: 'png', + }; + const type = req.body.type; + const fileType = fileTypeMap[type] || 'webm'; + const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9); + const fileName = `${type}-${uniqueSuffix}.${fileType}`; + cb(null, fileName); + }, + }); + + app.use(multer({ storage }).single('file')); + + await app.listen(PORT); + + console.log(`Server application is up and running on port ${PORT}`); +} +bootstrap(); diff --git a/packages/server/src/model/IUser.ts b/packages/server/src/model/IUser.ts deleted file mode 100644 index db003c08..00000000 --- a/packages/server/src/model/IUser.ts +++ /dev/null @@ -1,17 +0,0 @@ -export interface IUser { - id: number; - - uuid: string; - - userName: string; - - userType: string; - - createdAt: Date; - - createdBy: string; - - updatedAt: Date; - - updatedBy: string; -} diff --git a/packages/server/src/records/dto/create-record.dto.ts b/packages/server/src/records/dto/create-record.dto.ts new file mode 100644 index 00000000..dd789011 --- /dev/null +++ b/packages/server/src/records/dto/create-record.dto.ts @@ -0,0 +1,19 @@ +import { IUser } from '../../users/interfaces/user.interface'; + +export class CreateRecordDto { + filePath: string; + + fileType: string; + + mark?: string; + + user: IUser; + + createdAt?: Date; + + createdBy?: string; + + updatedAt?: Date; + + updatedBy?: string; +} diff --git a/packages/server/src/entity/Record.ts b/packages/server/src/records/entity/record.entity.ts similarity index 57% rename from packages/server/src/entity/Record.ts rename to packages/server/src/records/entity/record.entity.ts index 8812d68e..2da16248 100644 --- a/packages/server/src/entity/Record.ts +++ b/packages/server/src/records/entity/record.entity.ts @@ -7,11 +7,13 @@ import { CreateDateColumn, UpdateDateColumn, } from 'typeorm'; -import { User } from './User'; -import { IRecord } from '../model/IRecord'; +import { User } from '../../users/entity/user.entity'; +import { IUser } from '../../users/interfaces/user.interface'; +// import { IRecord } from '../interfaces/record.interface'; @Entity() -export class Record implements IRecord { +export class Record { + user: IUser; @PrimaryGeneratedColumn() id: number; @@ -22,20 +24,20 @@ export class Record implements IRecord { fileType: string; @Column('varchar', { nullable: true }) - mark: string; + mark?: string; @CreateDateColumn({ nullable: true }) - createdAt: Date; + createdAt?: Date; @Column('varchar', { nullable: true }) - createdBy: string; + createdBy?: string; @UpdateDateColumn({ nullable: true }) - updatedAt: Date; + updatedAt?: Date; @Column('varchar', { nullable: true }) - updatedBy: string; + updatedBy?: string; - @ManyToOne(() => User, (user) => user.records) - user: User; + // @ManyToOne(() => User, (user) => user.records) + // user: IUser; } diff --git a/packages/server/src/model/IRecord.ts b/packages/server/src/records/interfaces/record.interface.ts similarity index 69% rename from packages/server/src/model/IRecord.ts rename to packages/server/src/records/interfaces/record.interface.ts index b5aa0712..cb471026 100644 --- a/packages/server/src/model/IRecord.ts +++ b/packages/server/src/records/interfaces/record.interface.ts @@ -1,3 +1,5 @@ +import { IUser } from '../../users/interfaces/user.interface'; + export interface IRecord { id: number; @@ -7,6 +9,8 @@ export interface IRecord { mark: string; + user: IUser; + createdAt: Date; createdBy: string; diff --git a/packages/server/src/records/records.controller.ts b/packages/server/src/records/records.controller.ts new file mode 100644 index 00000000..1dabff2f --- /dev/null +++ b/packages/server/src/records/records.controller.ts @@ -0,0 +1,39 @@ +import { Controller, Get, Post, Body, Param, Query } from '@nestjs/common'; +import { CreateRecordDto } from './dto/create-record.dto'; +import { RecordsService } from './records.service'; +import { Record } from './entity/record.entity'; + +@Controller('records') +export class RecordsController { + constructor(private recordsService: RecordsService) {} + + @Post() + create(@Body() record: CreateRecordDto): Promise { + return this.recordsService.create(record); + } + + @Get() + findAll(@Query('pageNumber') page = 1, @Query('pageSize') limit = 20): Promise { + return this.recordsService.findAll(page, limit); + } + + @Get(':id') + findOne(@Param('id') id: number): Promise { + return this.recordsService.findOne(id); + } + + @Post('/edit/:id') + update(@Param('id') id: number, @Body() record: CreateRecordDto): Promise { + return this.recordsService.update(id, record); + } + + @Post('/delete/list') + removeList(@Body('ids') ids: number[]): Promise { + return this.recordsService.removeList(ids); + } + + @Post('/delete/:id') + remove(@Param('id') id: number): Promise { + return this.recordsService.remove(id); + } +} diff --git a/packages/server/src/records/records.module.ts b/packages/server/src/records/records.module.ts new file mode 100644 index 00000000..7ba3a251 --- /dev/null +++ b/packages/server/src/records/records.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { RecordsController } from './records.controller'; +import { RecordsService } from './records.service'; +import { Record } from './entity/record.entity'; + +@Module({ + imports: [TypeOrmModule.forFeature([Record])], + controllers: [RecordsController], + providers: [RecordsService], + exports: [RecordsService], +}) +export class RecordsModule {} diff --git a/packages/server/src/records/records.providers.ts b/packages/server/src/records/records.providers.ts new file mode 100644 index 00000000..56b638a2 --- /dev/null +++ b/packages/server/src/records/records.providers.ts @@ -0,0 +1,10 @@ +import { DataSource } from 'typeorm'; +import { Record } from './entity/record.entity'; + +export const recordProviders = [ + { + provide: 'RECORDS_REPOSITORY', + useFactory: (dataSource: DataSource) => dataSource.getRepository(Record), + inject: ['DATA_SOURCE'], + }, +]; diff --git a/packages/server/src/records/records.service.ts b/packages/server/src/records/records.service.ts new file mode 100644 index 00000000..f2857296 --- /dev/null +++ b/packages/server/src/records/records.service.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Record } from './entity/record.entity'; +import { CreateRecordDto } from './dto/create-record.dto'; + +@Injectable() +export class RecordsService { + constructor( + @InjectRepository(Record) + private readonly recordRepository: Repository, + ) {} + + async create(record: CreateRecordDto): Promise { + return await this.recordRepository.save(record); + } + + async findAll(pageNumber: number, pageSize: number): Promise { + const [records, totalCount] = await this.recordRepository.findAndCount({ + skip: (pageNumber - 1) * pageSize, + take: pageSize, + }); + + return records; + } + + async findOne(id: number): Promise { + return await this.recordRepository.findOneBy({ id }); + } + + async update(id: number, record: CreateRecordDto): Promise { + await this.recordRepository.update(id, record); + return await this.recordRepository.findOneBy({ id }); + } + + async remove(id: number): Promise { + await this.recordRepository.delete(id); + return `${id} is removed`; + } + + async removeList(ids: number[]): Promise { + await this.recordRepository.delete(ids); + return `${ids} is removed`; + } +} diff --git a/packages/server/src/route/Record.ts b/packages/server/src/route/Record.ts deleted file mode 100644 index 9f9f5d8c..00000000 --- a/packages/server/src/route/Record.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { RecordController } from '../controller/RecordController'; - -const recordController = new RecordController(); - -export const RecordRoutes = [ - { - path: '/records', - method: 'get', - action: recordController.getRecords, - }, - { - path: '/addRecord', - method: 'post', - action: recordController.createRecord, - }, - { - path: '/record/:id', - method: 'get', - action: recordController.getRecord, - }, - { - path: '/editRecord/:id', - method: 'post', - action: recordController.updateRecord, - }, - { - path: '/deleteRecord/:id', - method: 'post', - action: recordController.deleteRecord, - }, - { - path: '/deleteAllRecord', - method: 'post', - action: recordController.deleteAllRecord, - }, -]; diff --git a/packages/server/src/route/Setting.ts b/packages/server/src/route/Setting.ts deleted file mode 100644 index d9838952..00000000 --- a/packages/server/src/route/Setting.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { SettingController } from '../controller/SettingController'; - -const settingController = new SettingController(); - -export const SettingRoutes = [ - { - path: '/settings', - method: 'get', - action: settingController.getSettings, - }, - { - path: '/addSetting', - method: 'post', - action: settingController.createSetting, - }, - { - path: '/setting/:id', - method: 'get', - action: settingController.getSetting, - }, - { - path: '/getSettingByUserId/:userId', - method: 'get', - action: settingController.getSettingByUserId, - }, - { - path: '/editSetting/:id', - method: 'post', - action: settingController.updateSetting, - }, - { - path: '/deleteSettings/:id', - method: 'get', - action: settingController.deleteSetting, - }, - { - path: '/resetSetting/:id', - method: 'get', - action: settingController.resetSetting, - }, -]; diff --git a/packages/server/src/route/User.ts b/packages/server/src/route/User.ts deleted file mode 100644 index 8aa206e8..00000000 --- a/packages/server/src/route/User.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { UserController } from "../controller/UserController"; - -const userController = new UserController(); - -export const UserRoutes = [ - { - path: "/users", - method: "get", - action: userController.getUsers, - }, - { - path: "/addUser", - method: "post", - action: userController.createUser, - }, - { - path: "/getUser/:id", - method: "get", - action: userController.getUser, - }, - { - path: "/editUser/:id", - method: "get", - action: userController.updateUser, - }, - { - path: "/deleteUser/:id", - method: "get", - action: userController.deleteUser, - }, - { - path: "/getCurrentUser", - method: "get", - action: userController.getCurrentUser, - }, -]; diff --git a/packages/server/src/route/index.ts b/packages/server/src/route/index.ts deleted file mode 100644 index 467cf24e..00000000 --- a/packages/server/src/route/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { UserRoutes } from './User'; -import { RecordRoutes } from './Record'; -import { SettingRoutes } from './Setting'; - -/** - * All application routes. - */ -export const AppRoutes = [...UserRoutes, ...RecordRoutes, ...SettingRoutes]; diff --git a/packages/server/src/settings/dto/create-setting.dto.ts b/packages/server/src/settings/dto/create-setting.dto.ts new file mode 100644 index 00000000..e525aab2 --- /dev/null +++ b/packages/server/src/settings/dto/create-setting.dto.ts @@ -0,0 +1,27 @@ +import { IUser } from '../../users/interfaces/user.interface'; + +export class CreateSettingDto { + id: number; + + isProxy: boolean; + + proxyPort: number; + + language: string; + + filePath: string; + + openAtLogin: boolean; + + serverPath: string; + + user: IUser; + + createdAt: Date; + + createdBy: string; + + updatedAt: Date; + + updatedBy: string; +} diff --git a/packages/server/src/entity/Setting.ts b/packages/server/src/settings/entity/setting.entity.ts similarity index 77% rename from packages/server/src/entity/Setting.ts rename to packages/server/src/settings/entity/setting.entity.ts index 1d294257..5e9e815a 100644 --- a/packages/server/src/entity/Setting.ts +++ b/packages/server/src/settings/entity/setting.entity.ts @@ -7,11 +7,13 @@ import { UpdateDateColumn, OneToOne, } from 'typeorm'; -import { User } from './User'; -import { ISetting } from '../model/ISetting'; +import { User } from '../../users/entity/user.entity'; +import { IUser } from '../../users/interfaces/user.interface'; +import { ISetting } from '../interfaces/setting.interface'; @Entity() export class Setting implements ISetting { + user: IUser; @PrimaryGeneratedColumn() id: number; @@ -51,7 +53,7 @@ export class Setting implements ISetting { @Column('varchar', { nullable: true }) updatedBy: string; - @OneToOne(() => User, (user) => user.setting) - @JoinColumn() - user: User; + // @OneToOne(() => User, (user) => user.setting) + // @JoinColumn() + // user: IUser; } diff --git a/packages/server/src/model/ISetting.ts b/packages/server/src/settings/interfaces/setting.interface.ts similarity index 76% rename from packages/server/src/model/ISetting.ts rename to packages/server/src/settings/interfaces/setting.interface.ts index e1190c89..bf5d3ef9 100644 --- a/packages/server/src/model/ISetting.ts +++ b/packages/server/src/settings/interfaces/setting.interface.ts @@ -1,3 +1,5 @@ +import { IUser } from '../../users/interfaces/user.interface'; + export interface ISetting { id: number; @@ -13,6 +15,8 @@ export interface ISetting { serverPath: string; + user: IUser; + createdAt: Date; createdBy: string; diff --git a/packages/server/src/settings/settings.controller.ts b/packages/server/src/settings/settings.controller.ts new file mode 100644 index 00000000..fca6bc98 --- /dev/null +++ b/packages/server/src/settings/settings.controller.ts @@ -0,0 +1,39 @@ +import { Controller, Get, Post, Body, Param, UseInterceptors } from '@nestjs/common'; +import { CreateSettingDto } from './dto/create-setting.dto'; +import { SettingsService } from './settings.service'; +import { Setting } from './entity/setting.entity'; + +@Controller('settings') +export class SettingsController { + constructor(private settingsService: SettingsService) {} + + @Post() + create(@Body() record: CreateSettingDto): Promise { + return this.settingsService.create(record); + } + + @Get() + findAll(): Promise { + return this.settingsService.findAll(); + } + + @Get(':id') + findOne(@Param('id') id: number): Promise { + return this.settingsService.findOne(id); + } + + @Post('/edit/:id') + update(@Param('id') id: number, @Body() record: CreateSettingDto): Promise { + return this.settingsService.update(id, record); + } + + @Post('/delete/:id') + remove(@Param('id') id: number): Promise { + return this.settingsService.remove(id); + } + + @Post('/reset/:id') + reset(@Param('id') id: number): Promise { + return this.settingsService.reset(id); + } +} diff --git a/packages/server/src/settings/settings.module.ts b/packages/server/src/settings/settings.module.ts new file mode 100644 index 00000000..94f95b5d --- /dev/null +++ b/packages/server/src/settings/settings.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +// import { DatabaseModule } from '../database/database.module'; +import { SettingsController } from './settings.controller'; +import { SettingsService } from './settings.service'; +import { Setting } from './entity/setting.entity'; +// import { settingsProviders } from './settings.providers'; + +@Module({ + // imports: [DatabaseModule], + imports: [TypeOrmModule.forFeature([Setting])], + controllers: [SettingsController], + providers: [SettingsService], + exports: [SettingsService], +}) +export class SettingsModule {} diff --git a/packages/server/src/settings/settings.providers.ts b/packages/server/src/settings/settings.providers.ts new file mode 100644 index 00000000..3767d143 --- /dev/null +++ b/packages/server/src/settings/settings.providers.ts @@ -0,0 +1,10 @@ +import { DataSource } from 'typeorm'; +import { Setting } from './entity/setting.entity'; + +export const settingsProviders = [ + { + provide: 'SETTINGS_REPOSITORY', + useFactory: (dataSource: DataSource) => dataSource.getRepository(Setting), + inject: ['DATA_SOURCE'], + }, +]; diff --git a/packages/server/src/settings/settings.service.ts b/packages/server/src/settings/settings.service.ts new file mode 100644 index 00000000..8d8567cd --- /dev/null +++ b/packages/server/src/settings/settings.service.ts @@ -0,0 +1,48 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Setting } from './entity/setting.entity'; +import { getDefaultConfig } from '../config'; + +@Injectable() +export class SettingsService { + constructor( + @InjectRepository(Setting) + private readonly settingRepository: Repository, + ) {} + + async create(setting: Setting): Promise { + return await this.settingRepository.save(setting); + } + + async findAll(): Promise { + return await this.settingRepository.find(); + } + + async findOne(id: number): Promise { + return await this.settingRepository.findOneBy({ id }); + } + + async update(id: number, setting: Setting): Promise { + await this.settingRepository.update(id, setting); + return await this.settingRepository.findOneBy({ id }); + } + + async remove(id: number): Promise { + await this.settingRepository.delete(id); + } + + async reset(id: number): Promise { + const defaultConfig = getDefaultConfig(); + let setting = { + isProxy: defaultConfig.isProxy, + proxyPort: defaultConfig.proxyPort, + language: defaultConfig.language, + filePath: defaultConfig.filePath, + openAtLogin: defaultConfig.openAtLogin, + serverPath: defaultConfig.serverPath, + }; + await this.settingRepository.update(id, setting); + return await this.settingRepository.findOneBy({ id }); + } +} diff --git a/packages/server/src/users/dto/create-user.dto.ts b/packages/server/src/users/dto/create-user.dto.ts new file mode 100644 index 00000000..1fa7d5ba --- /dev/null +++ b/packages/server/src/users/dto/create-user.dto.ts @@ -0,0 +1,24 @@ +import { ISetting } from '../../settings/interfaces/setting.interface'; +import { IRecord } from '../../records/interfaces/record.interface'; + +export class CreateUserDto { + id: number; + + uuid: string; + + userName: string; + + userType: string; + + // records: IRecord[]; + + // setting: ISetting; + + createdAt: Date; + + createdBy: string; + + updatedAt: Date; + + updatedBy: string; +} diff --git a/packages/server/src/entity/User.ts b/packages/server/src/users/entity/user.entity.ts similarity index 64% rename from packages/server/src/entity/User.ts rename to packages/server/src/users/entity/user.entity.ts index d4560d3a..7a42181b 100644 --- a/packages/server/src/entity/User.ts +++ b/packages/server/src/users/entity/user.entity.ts @@ -7,11 +7,11 @@ import { CreateDateColumn, UpdateDateColumn, } from 'typeorm'; -import { IUser } from '../model/IUser'; -import { Record } from './Record'; -import { Setting } from './Setting'; +import { IUser } from '../interfaces/user.interface'; +import { Record } from '../../records/entity/record.entity'; +import { Setting } from '../../settings/entity/setting.entity'; // import { IRecord } from '../model/IRecord'; -import { ISetting } from '../model/ISetting'; +// import { ISetting } from '../model/ISetting'; @Entity() export class User implements IUser { @@ -42,9 +42,9 @@ export class User implements IUser { @Column('varchar', { nullable: true }) updatedBy: string; - @OneToMany(() => Record, (record) => record.user) - records: Record[]; + // @OneToMany(() => Record, (record) => record.user) + // records: Record[]; - @OneToOne(() => Setting, (setting) => setting.user) - setting: Setting; + // @OneToOne(() => Setting, (setting) => setting.user) + // setting: Setting; } diff --git a/packages/server/src/users/interfaces/user.interface.ts b/packages/server/src/users/interfaces/user.interface.ts new file mode 100644 index 00000000..2e5c230b --- /dev/null +++ b/packages/server/src/users/interfaces/user.interface.ts @@ -0,0 +1,24 @@ +import { ISetting } from '../../settings/interfaces/setting.interface'; +import { IRecord } from '../../records/interfaces/record.interface'; + +export interface IUser { + id: number; + + uuid: string; + + userName: string; + + userType: string; + + // records: IRecord[]; + + // setting: ISetting; + + createdAt: Date; + + createdBy: string; + + updatedAt: Date; + + updatedBy: string; +} diff --git a/packages/server/src/users/users.controller.ts b/packages/server/src/users/users.controller.ts new file mode 100644 index 00000000..50b5750b --- /dev/null +++ b/packages/server/src/users/users.controller.ts @@ -0,0 +1,39 @@ +import { Controller, Get, Post, Body, Param } from '@nestjs/common'; +import { CreateUserDto } from './dto/create-user.dto'; +import { UsersService } from './users.service'; +import { User } from './entity/user.entity'; + +@Controller('users') +export class UsersController { + constructor(private readonly usersService: UsersService) {} + + @Post() + create(@Body() user: User): Promise { + return this.usersService.create(user); + } + + @Get() + findAll(): Promise { + return this.usersService.findAll(); + } + + @Get('/current') + findCurrent(): Promise { + return this.usersService.findCurrent(); + } + + @Get(':id') + findOne(@Param('id') id: number): Promise { + return this.usersService.findOne(id); + } + + @Post('/edit/:id') + update(@Param('id') id: number, @Body() user: User): Promise { + return this.usersService.update(id, user); + } + + @Post('/delete/:id') + remove(@Param('id') id: number): Promise { + return this.usersService.remove(id); + } +} diff --git a/packages/server/src/users/users.module.ts b/packages/server/src/users/users.module.ts new file mode 100644 index 00000000..1bd5373b --- /dev/null +++ b/packages/server/src/users/users.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { UsersController } from './users.controller'; +import { UsersService } from './users.service'; +import { User } from './entity/user.entity'; + +@Module({ + imports: [TypeOrmModule.forFeature([User])], + controllers: [UsersController], + providers: [UsersService], + exports: [UsersService], +}) +export class UsersModule {} diff --git a/packages/server/src/users/users.providers.ts b/packages/server/src/users/users.providers.ts new file mode 100644 index 00000000..3cb0c779 --- /dev/null +++ b/packages/server/src/users/users.providers.ts @@ -0,0 +1,10 @@ +import { DataSource } from 'typeorm'; +import { User } from './entity/user.entity'; + +export const userProviders = [ + { + provide: 'USERS_REPOSITORY', + useFactory: (dataSource: DataSource) => dataSource.getRepository(User), + inject: ['DATA_SOURCE'], + }, +]; diff --git a/packages/server/src/users/users.service.ts b/packages/server/src/users/users.service.ts new file mode 100644 index 00000000..551610ff --- /dev/null +++ b/packages/server/src/users/users.service.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { User } from './entity/user.entity'; +import { getConfig } from '../config'; + +@Injectable() +export class UsersService { + constructor( + @InjectRepository(User) + private usersRepository: Repository, + ) {} + + findAll(): Promise { + return this.usersRepository.find(); + } + + findCurrent(): Promise { + const config = getConfig(); + return this.usersRepository.findOneBy({ uuid: config.user.uuid }); + } + + findOne(id: number): Promise { + return this.usersRepository.findOneBy({ id }); + } + + async create(user: User): Promise { + return await this.usersRepository.save(user); + } + + async update(id: number, user: User): Promise { + await this.usersRepository.update(id, user); + return await this.usersRepository.findOneBy({ id }); + } + + async remove(id: number): Promise { + await this.usersRepository.delete(id); + } +} diff --git a/packages/server/src/util/exception.filter.ts b/packages/server/src/util/exception.filter.ts new file mode 100644 index 00000000..7a489a7b --- /dev/null +++ b/packages/server/src/util/exception.filter.ts @@ -0,0 +1,22 @@ +import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common'; + +@Catch() +export class AllExceptionsFilter implements ExceptionFilter { + catch(exception: unknown, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + + let statusCode = HttpStatus.INTERNAL_SERVER_ERROR; + let message = 'Internal server error'; + + if (exception instanceof HttpException) { + statusCode = exception.getStatus(); + message = exception.message; + } + + response.status(statusCode).json({ + code: -1, + message, + }); + } +} diff --git a/packages/server/src/util/response.interceptor.ts b/packages/server/src/util/response.interceptor.ts new file mode 100644 index 00000000..99825a2d --- /dev/null +++ b/packages/server/src/util/response.interceptor.ts @@ -0,0 +1,30 @@ +import { + Injectable, + NestInterceptor, + ExecutionContext, + CallHandler, + BadGatewayException, + StreamableFile, +} from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Response } from 'express'; + +@Injectable() +export class ResponseInterceptor implements NestInterceptor { + intercept(context: ExecutionContext, next: CallHandler): Observable { + const response = context.switchToHttp().getResponse(); + + return next.handle().pipe( + map((data) => { + if (data.code === -1) { + throw new BadGatewayException(data.message); + } else if (data instanceof StreamableFile) { + return data; + } else { + return { code: 0, message: 'Internal server success', data }; + } + }), + ); + } +} diff --git a/packages/server/tsconfig.build.json b/packages/server/tsconfig.build.json new file mode 100644 index 00000000..37816248 --- /dev/null +++ b/packages/server/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "exclude": [ + "node_modules", + "test", + "dist", + "**/*spec.ts" + ] +} \ No newline at end of file diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index e8ac29be..000fbb17 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -1,18 +1,21 @@ { "compilerOptions": { - "target": "ES5", - "experimentalDecorators": true, + "module": "commonjs", + "declaration": true, + "removeComments": true, "emitDecoratorMetadata": true, - "esModuleInterop": true, - "moduleResolution": "Node", - "outDir": "dist", - "importHelpers": true, - "useDefineForClassFields": false, - }, - "include": [ - "src/**/*.ts" - ], - "exclude": [ - "node_modules" - ] + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2021", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": false, + "noImplicitAny": false, + "strictBindCallApply": false, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false + } } \ No newline at end of file diff --git a/packages/web/src/api/index.ts b/packages/web/src/api/index.ts index ed507b1b..a8bc4d78 100644 --- a/packages/web/src/api/index.ts +++ b/packages/web/src/api/index.ts @@ -4,8 +4,11 @@ export function useApi() { return { saveFile: (formData) => { return request({ - url: `/saveFile`, + url: `/upload/file`, method: 'post', + headers: { + 'Content-Type': 'multipart/form-data', + }, data: formData, }); }, diff --git a/packages/web/src/api/record.ts b/packages/web/src/api/record.ts index 7362408b..2e1252ff 100644 --- a/packages/web/src/api/record.ts +++ b/packages/web/src/api/record.ts @@ -10,14 +10,17 @@ export function useRecordApi() { }, deleteRecord: (recordId) => { return request({ - url: `/deleteRecord/${recordId}`, + url: `/records/delete/${recordId}`, method: 'post', }); }, - deleteAllRecord: () => { + deleteListRecord: (ids) => { return request({ - url: `/deleteAllRecord`, + url: `/records/delete/list`, method: 'post', + data: { + ids: JSON.stringify(ids), + }, }); }, }; diff --git a/packages/web/src/api/setting.ts b/packages/web/src/api/setting.ts index 188f20ff..95305619 100644 --- a/packages/web/src/api/setting.ts +++ b/packages/web/src/api/setting.ts @@ -4,21 +4,21 @@ export function useSettingApi() { return { getSetting: (userId: string) => { return request({ - url: `/getSettingByUserId/${userId}`, + url: `/settings/${userId}`, method: 'get', }); }, editSetting: (settingId, data) => { return request({ - url: `/editSetting/${settingId}`, + url: `/settings/edit/${settingId}`, method: 'post', data: data, }); }, - resetSetting: (userId) => { + resetSetting: (settingId) => { return request({ - url: `/resetSetting/${userId}`, - method: 'get', + url: `/settings/reset/${settingId}`, + method: 'post', }); }, }; diff --git a/packages/web/src/api/user.ts b/packages/web/src/api/user.ts index bfbf1014..c73331bf 100644 --- a/packages/web/src/api/user.ts +++ b/packages/web/src/api/user.ts @@ -4,29 +4,9 @@ export function useUserApi() { return { getCurrentUser: () => { return request({ - url: `/getCurrentUser`, + url: `/users/current`, method: 'get', }); }, - addUser: (data) => { - return request({ - url: `/addUser`, - method: 'post', - data: data, - }); - }, - editUser: (userId, data) => { - return request({ - url: `/editUser/${userId}`, - method: 'post', - data: data, - }); - }, - deleteUser: (userId) => { - return request({ - url: `/deleteUser/${userId}`, - method: 'post', - }); - }, }; } diff --git a/packages/web/src/components/recorderAudio/WaveSurferPlayer.tsx b/packages/web/src/components/recorderAudio/WaveSurferPlayer.tsx index 7d13e96d..c0614454 100644 --- a/packages/web/src/components/recorderAudio/WaveSurferPlayer.tsx +++ b/packages/web/src/components/recorderAudio/WaveSurferPlayer.tsx @@ -1,191 +1,179 @@ -import React, { useState, useRef, useEffect, useCallback } from "react"; -import { useTranslation } from "react-i18next"; -import { - Button, - Space, - Divider, - Slider, - Row, - Col, - Checkbox, - Modal, - message, -} from "antd"; -import WaveSurfer from "wavesurfer.js"; -import { saveAs } from "file-saver"; -import { useApi } from "../../api"; +import React, { useState, useRef, useEffect, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Button, Space, Divider, Slider, Row, Col, Checkbox, Modal, message } from 'antd'; +import WaveSurfer from 'wavesurfer.js'; +import { saveAs } from 'file-saver'; +import { useApi } from '../../api'; const useWavesurfer = (containerRef, options) => { - const [wavesurfer, setWavesurfer] = useState(null); + const [wavesurfer, setWavesurfer] = useState(null); - useEffect(() => { - if (!containerRef.current) return; + useEffect(() => { + if (!containerRef.current) return; - const ws = WaveSurfer.create({ - ...options, - container: containerRef.current, - }); + const ws = WaveSurfer.create({ + ...options, + container: containerRef.current, + }); - setWavesurfer(ws); + setWavesurfer(ws); - ws.on("interaction", () => { - ws.play(); - }); + ws.on('interaction', () => { + ws.play(); + }); - return () => { - ws.destroy(); - }; - }, [options, containerRef]); + return () => { + ws.destroy(); + }; + }, [options, containerRef]); - return wavesurfer; + return wavesurfer; }; const WaveSurferPlayer = (props) => { - const { t } = useTranslation(); - const api = useApi(); - const containerRef = useRef(); - const [isPlaying, setIsPlaying] = useState(false); - const [currentTime, setCurrentTime] = useState(0); - const [scrollbar, setScrollbar] = useState(true); - const [fillParent, setFillParent] = useState(true); - const [autoCenter, setAutoCenter] = useState(true); - const wavesurfer = useWavesurfer(containerRef, props); - - const onPlayClick = useCallback(() => { - wavesurfer.isPlaying() ? wavesurfer.pause() : wavesurfer.play(); - }, [wavesurfer]); - - const onDownloadClick = useCallback(async () => { - const url = wavesurfer.getMediaElement().src; - const data = await fetch(url); - const blob = await data.blob(); - - window.isOffline - ? saveAs(url, `pear-rec_${+new Date()}.webm`) - : saveFile(blob); - }, [wavesurfer]); - - async function saveFile(blob) { - try { - const formData = new FormData(); - formData.append("type", "ra"); - formData.append("userId", props.user.id); - formData.append("file", blob); - const res = (await api.saveFile(formData)) as any; - if (res.code == 0) { - if (window.isElectron) { - window.electronAPI.sendRaCloseWin(); - window.electronAPI.sendVaOpenWin({ audioUrl: res.data.filePath }); - } else { - Modal.confirm({ - title: "音频已保存,是否查看?", - content: `${res.data.filePath}`, - okText: t("modal.ok"), - cancelText: t("modal.cancel"), - onOk() { - window.open(`/viewAudio.html?audioUrl=${res.data.filePath}`); - }, - }); - } - } - } catch (err) { - message.error("保存失败"); - } - } - - const onRangeChange = useCallback( - (minPxPerSec) => { - wavesurfer.zoom(minPxPerSec); - }, - [wavesurfer], - ); - - const onScrollbarChange = useCallback( - (e) => { - wavesurfer.setOptions({ - scrollbar: e.target.checked, - }); - setScrollbar(e.target.checked); - }, - [wavesurfer], - ); - - const onFillParentChange = useCallback( - (e) => { - wavesurfer.setOptions({ - fillParent: e.target.checked, - }); - setFillParent(e.target.checked); - }, - [wavesurfer], - ); - - const onAutoCenterChange = useCallback( - (e) => { - wavesurfer.setOptions({ - autoCenter: e.target.checked, - }); - setAutoCenter(e.target.checked); - }, - [wavesurfer], - ); - - useEffect(() => { - if (!wavesurfer) return; - - setCurrentTime(0); - setIsPlaying(false); - - const subscriptions = [ - wavesurfer.on("play", () => setIsPlaying(true)), - wavesurfer.on("pause", () => setIsPlaying(false)), - wavesurfer.on("timeupdate", (currentTime) => setCurrentTime(currentTime)), - ]; - - return () => { - subscriptions.forEach((unsub) => unsub()); - }; - }, [wavesurfer]); - - return ( - <> - - Zoom: - - - - - - Scroll bar - - - Fill parent - - - Auto center - - - -
- - - - - -

Seconds played: {currentTime}

- - ); + const { t } = useTranslation(); + const api = useApi(); + const containerRef = useRef(); + const [isPlaying, setIsPlaying] = useState(false); + const [currentTime, setCurrentTime] = useState(0); + const [scrollbar, setScrollbar] = useState(true); + const [fillParent, setFillParent] = useState(true); + const [autoCenter, setAutoCenter] = useState(true); + const wavesurfer = useWavesurfer(containerRef, props); + + const onPlayClick = useCallback(() => { + wavesurfer.isPlaying() ? wavesurfer.pause() : wavesurfer.play(); + }, [wavesurfer]); + + const onDownloadClick = useCallback(async () => { + const url = wavesurfer.getMediaElement().src; + const data = await fetch(url); + const blob = await data.blob(); + + window.isOffline ? saveAs(url, `pear-rec_${+new Date()}.webm`) : saveFile(blob); + }, [wavesurfer]); + + async function saveFile(blob) { + try { + const formData = new FormData(); + formData.append('type', 'ra'); + formData.append('userId', props.user.id); + formData.append('file', blob); + const res = (await api.saveFile(formData)) as any; + if (res.code == 0) { + if (window.isElectron) { + window.electronAPI.sendRaCloseWin(); + window.electronAPI.sendVaOpenWin({ audioUrl: res.data.filePath }); + } else { + Modal.confirm({ + title: '音频已保存,是否查看?', + content: `${res.data.filePath}`, + okText: t('modal.ok'), + cancelText: t('modal.cancel'), + onOk() { + window.open(`/viewAudio.html?audioUrl=${res.data.filePath}`); + }, + }); + } + } + } catch (err) { + message.error('保存失败'); + } + } + + const onRangeChange = useCallback( + (minPxPerSec) => { + wavesurfer.zoom(minPxPerSec); + }, + [wavesurfer], + ); + + const onScrollbarChange = useCallback( + (e) => { + wavesurfer.setOptions({ + scrollbar: e.target.checked, + }); + setScrollbar(e.target.checked); + }, + [wavesurfer], + ); + + const onFillParentChange = useCallback( + (e) => { + wavesurfer.setOptions({ + fillParent: e.target.checked, + }); + setFillParent(e.target.checked); + }, + [wavesurfer], + ); + + const onAutoCenterChange = useCallback( + (e) => { + wavesurfer.setOptions({ + autoCenter: e.target.checked, + }); + setAutoCenter(e.target.checked); + }, + [wavesurfer], + ); + + useEffect(() => { + if (!wavesurfer) return; + + setCurrentTime(0); + setIsPlaying(false); + + const subscriptions = [ + wavesurfer.on('play', () => setIsPlaying(true)), + wavesurfer.on('pause', () => setIsPlaying(false)), + wavesurfer.on('timeupdate', (currentTime) => setCurrentTime(currentTime)), + ]; + + return () => { + subscriptions.forEach((unsub) => unsub()); + }; + }, [wavesurfer]); + + return ( + <> + + Zoom: + + + + + + Scroll bar + + + Fill parent + + + Auto center + + + +
+ + + + + +

Seconds played: {currentTime}

+ + ); }; export default WaveSurferPlayer; diff --git a/packages/web/src/components/records/RecordsContent.tsx b/packages/web/src/components/records/RecordsContent.tsx index 597cc8f1..708142da 100644 --- a/packages/web/src/components/records/RecordsContent.tsx +++ b/packages/web/src/components/records/RecordsContent.tsx @@ -9,6 +9,7 @@ import { import { useApi } from '../../api'; import { useRecordApi } from '../../api/record'; import { eventEmitter } from '../../util/bus'; +import Item from 'antd/es/list/Item'; const { Content } = Layout; const recordApi = useRecordApi(); @@ -23,6 +24,7 @@ const RecordAudioCard = forwardRef(() => { const [pageNumber, setPageNumber] = useState(1); const [isMore, setIsMore] = useState(true); const [filterVal, setFilterVal] = useState(''); + const [isDelete, setIsDelete] = useState(false); useEffect(() => { setPageNumber(1); @@ -41,14 +43,27 @@ const RecordAudioCard = forwardRef(() => { filterData(); }, [filterVal]); + useEffect(() => { + isDelete && deleteListRecord(); + }, [isDelete]); + async function handleDeleteAllRecord() { - const res = (await recordApi.deleteAllRecord()) as any; + setIsDelete(true); + } + + async function deleteListRecord() { + let ids = []; + data.map((item) => { + ids.push(item.id); + }); + const res = (await recordApi.deleteListRecord(ids)) as any; if (res.code == 0) { const newData = []; setData(newData); setList(newData); window.dispatchEvent(new Event('resize')); } + setIsDelete(false); } function handleSearchRecord(filterVal) {