This commit is contained in:
longdayi 2024-12-30 09:22:38 +08:00
parent c8dfb4db25
commit 124e263f78
41 changed files with 12611 additions and 462 deletions

View File

@ -6,3 +6,4 @@ dist
test test
.md .md
volumes volumes
*.tar

282
.gitignore vendored Normal file → Executable file
View File

@ -1,235 +1,69 @@
# Node.js # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
node_modules/ backup
# dependencies
**/node_modules/
volumes
/.pnp
.pnp.js
*.tar
# testing
**/coverage/
.env
docker-compose.yml
packages/common/prisma/migrations
packages/common/src/generated
# production
**/build/
**/dist/
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Logs # 快速刷新错误记录
logs/ .expo/web/cache/development/
*.log
npm-debug.log*
yarn-debug.log*
pnpm-debug.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html) # Expo
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json **/.expo/
**/.expo-shared/
# Runtime data # Android
pids/ *.apk
*.pid *.aar
*.seed *.jks
*.pid.lock !apps/mobile/android/app/release.jks
**/android/.gradle/
**/android/app/build/
**/android/app/release/
**/android/react-native-jsc/build/
**/android/react/build/
**/android/*/google-services.json
**/android/app/src/debug/res/xml/react_native_debug.xml
**/android/app/src/dev19/res/xml/react_native_debug.xml
**/android/app/src/dev20/res/xml/react_native_debug.xml
**/android/app/src/main/assets/shell-app.bundle
**/android/app/src/main/res/raw/shell_app_bundle
**/android/app/src/release/res/xml/react_native_debug.xml
# Directory for instrumented libs generated by jscoverage/JSCover # iOS
lib-cov/ **/ios/Pods/
/ios/*.xcworkspace
**/ios/DerivedData/
**/ios/build/
**/ios/Podfile.lock
# Coverage directory used by tools like istanbul # Yarn Plug'n'Play
coverage/ .pnp.*
*.lcov .yarn/cache/
.yarn/unplugged/
# nyc test coverage
.nyc_output/
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt/
# Bower dependency directory (https://bower.io/)
bower_components/
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release/
# Dependency directories
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm/
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.*
!.env.example
# Parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next/
out/
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
.build
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# Yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml .yarn/build-state.yml
.yarn/install-state.gz .yarn/install-state.gz
.pnp.*
# Vite local server cache # Ignore .idea files in the Expo monorepo
.vite **/.idea/
# Vitest cache
.vitest
# Storybook build outputs
.out/
.storybook-out/
# Monorepo-specific
apps/*/node_modules/
apps/*/dist/
apps/*/build/
apps/*/.turbo/
apps/*/.next/
apps/*/.nuxt/
apps/*/.cache/
apps/*/.parcel-cache/
apps/*/.vuepress/dist
apps/*/.docusaurus
apps/*/.build
apps/*/.serverless
apps/*/.fusebox
apps/*/.dynamodb
apps/*/.tern-port
apps/*/.vscode-test
apps/*/.yarn
apps/*/.vite
apps/*/.vitest
apps/*/.out
apps/*/.storybook-out
packages/*/node_modules/
packages/*/dist/
packages/*/build/
packages/*/.turbo/
packages/*/.next/
packages/*/.nuxt/
packages/*/.cache/
packages/*/.parcel-cache/
packages/*/.vuepress/dist
packages/*/.docusaurus
packages/*/.build
packages/*/.serverless
packages/*/.fusebox
packages/*/.dynamodb
packages/*/.tern-port
packages/*/.vscode-test
packages/*/.yarn
packages/*/.vite
packages/*/.vitest
packages/*/.out
packages/*/.storybook-out
libs/*/node_modules/
libs/*/dist/
libs/*/build/
libs/*/.turbo/
libs/*/.next/
libs/*/.nuxt/
libs/*/.cache/
libs/*/.parcel-cache/
libs/*/.vuepress/dist
libs/*/.docusaurus
libs/*/.build
libs/*/.serverless
libs/*/.fusebox
libs/*/.dynamodb
libs/*/.tern-port
libs/*/.vscode-test
libs/*/.yarn
libs/*/.vite
libs/*/.vitest
libs/*/.out
libs/*/.storybook-out
# Ignore lockfiles
**/package-lock.json
**/yarn.lock
**/pnpm-lock.yaml
# IDEs and editors
.vscode/
.idea/
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# MacOS
.DS_Store
# Windows
Thumbs.db
ehthumbs.db
Desktop.ini
$RECYCLE.BIN/
# Linux
*~
docker-compose.yml
.env
packages/common/prisma/migrations
volumes

1
.npmrc Normal file
View File

@ -0,0 +1 @@
node-linker=hoisted

View File

@ -1,12 +1,10 @@
# 基础镜像 # 基础镜像
FROM node:20-alpine as base FROM node:20-alpine as base
# 更改 apk 镜像源为阿里云
# 设置 npm 镜像源 RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
RUN yarn config set registry https://registry.npmmirror.com yarn config set registry https://registry.npmmirror.com && \
yarn global add pnpm && \
# 全局安装 pnpm 并设置其镜像源 pnpm config set registry https://registry.npmmirror.com
RUN yarn global add pnpm && pnpm config set registry https://registry.npmmirror.com
# 设置工作目录 # 设置工作目录
WORKDIR /app WORKDIR /app
@ -16,64 +14,71 @@ COPY pnpm-workspace.yaml ./
# 首先复制 package.json, package-lock.json 和 pnpm-lock.yaml 文件 # 首先复制 package.json, package-lock.json 和 pnpm-lock.yaml 文件
COPY package*.json pnpm-lock.yaml* ./ COPY package*.json pnpm-lock.yaml* ./
COPY tsconfig.json . COPY tsconfig.base.json .
# 利用 Docker 缓存机制,如果依赖没有改变则不会重新执行 pnpm install
FROM base As server-build FROM base As server-build
WORKDIR /app WORKDIR /app
COPY packages/common /app/packages/common COPY packages/common /app/packages/common
COPY apps/back-worker /app/apps/back-worker COPY apps/server /app/apps/server
RUN pnpm install --filter back-worker RUN pnpm install --filter common && \
RUN pnpm install --filter common pnpm install --filter server && \
RUN pnpm --filter common build pnpm --filter common generate && \
RUN pnpm run build:server pnpm --filter common build:cjs && \
pnpm --filter server build
FROM base As server-prod-dep FROM base As server-prod-dep
WORKDIR /app WORKDIR /app
COPY packages/common /app/packages/common COPY packages/common /app/packages/common
COPY apps/back-worker /app/apps/back-worker COPY apps/server /app/apps/server
RUN pnpm install --filter back-worker --prod RUN pnpm install --filter common --prod && \
RUN pnpm install --filter common --prod pnpm install --filter server --prod && \
# 清理包管理器缓存
pnpm store prune && rm -rf /root/.npm && rm -rf /root/.cache
FROM server-prod-dep as server FROM server-prod-dep as server
WORKDIR /app WORKDIR /app
ENV NODE_ENV production ENV NODE_ENV production
COPY --from=server-build /app/packages/common/dist ./packages/common/dist COPY --from=server-build /app/packages/common/dist ./packages/common/dist
COPY --from=server-build /app/apps/back-worker/dist ./apps/back-worker/dist COPY --from=server-build /app/apps/server/dist ./apps/server/dist
COPY apps/back-worker/entrypoint.sh ./apps/back-worker/entrypoint.sh COPY apps/server/entrypoint.sh ./apps/server/entrypoint.sh
RUN chmod +x ./apps/server/entrypoint.sh
# RUN apk add --no-cache postgresql-client
RUN chmod +x ./apps/back-worker/entrypoint.sh
RUN apk add postgresql-client
EXPOSE 3010 EXPOSE 3000
ENTRYPOINT [ "/app/apps/back-worker/entrypoint.sh" ] ENTRYPOINT [ "/app/apps/server/entrypoint.sh" ]
FROM base AS front-app-build FROM base AS web-build
# 复制其余文件到工作目录 # 复制其余文件到工作目录
COPY . . COPY . .
RUN pnpm install RUN pnpm install && pnpm --filter web build
RUN pnpm run build:web
# 第二阶段,使用 nginx 提供服务 # 第二阶段,使用 nginx 提供服务
FROM nginx:stable-alpine as front-app FROM nginx:stable-alpine as web
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
# 设置工作目录 # 设置工作目录
WORKDIR /usr/share/nginx/html WORKDIR /usr/share/nginx/html
# 设置环境变量 # 设置环境变量
ENV NODE_ENV production ENV NODE_ENV production
# 将构建的文件从上一阶段复制到当前镜像中 # 将构建的文件从上一阶段复制到当前镜像中
COPY --from=front-app-build /app/apps/front-app/build . COPY --from=web-build /app/apps/web/dist .
# 删除默认的nginx配置文件并添加自定义配置 # 删除默认的nginx配置文件并添加自定义配置
RUN rm /etc/nginx/conf.d/default.conf RUN rm /etc/nginx/conf.d/default.conf
COPY apps/front-app/nginx.conf /etc/nginx/conf.d COPY apps/web/nginx.conf /etc/nginx/conf.d
# 添加 entrypoint 脚本,并确保其可执行 # 添加 entrypoint 脚本,并确保其可执行
COPY apps/front-app/entrypoint.sh /usr/bin/ COPY apps/web/entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh RUN chmod +x /usr/bin/entrypoint.sh
# 安装 envsubst 以支持环境变量替换 # 安装 envsubst 以支持环境变量替换
RUN apk add envsubst # RUN apk add --no-cache envsubst
# RUN echo "http://mirrors.aliyun.com/alpine/v3.12/main/" > /etc/apk/repositories && \
# echo "http://mirrors.aliyun.com/alpine/v3.12/community/" >> /etc/apk/repositories && \
# apk update && \
RUN apk add --no-cache gettext
# 暴露 80 端口 # 暴露 80 端口
EXPOSE 80 EXPOSE 80

9
apps/server/.env.example Normal file → Executable file
View File

@ -1,6 +1,13 @@
DATABASE_URL="postgresql://root:Letusdoit000@localhost:5432/defender_app?schema=public" DATABASE_URL="postgresql://root:Letusdoit000@localhost:5432/defender_app?schema=public"
REDIS_HOST=localhost REDIS_HOST=localhost
REDIS_PORT=6379 REDIS_PORT=6379
REDIS_PASSWORD=Letusdoit000
TUS_URL=http://localhost:8080 TUS_URL=http://localhost:8080
JWT_SECRET=/yT9MnLm/r6NY7ee2Fby6ihCHZl+nFx4OQFKupivrhA= JWT_SECRET=/yT9MnLm/r6NY7ee2Fby6ihCHZl+nFx4OQFKupivrhA=
APP_URL=http://localhost:5173 PUSH_URL=http://dns:9092
PUSH_APPID=123
PUSH_APPSECRET=123
DEADLINE_CRON="0 0 8 * * *"
SERVER_PORT=3000
ADMIN_PHONE_NUMBER=13258117304
NODE_ENV=development

0
apps/server/.eslintrc.js Normal file → Executable file
View File

0
apps/server/.prettierrc Normal file → Executable file
View File

14
apps/server/README.md Normal file → Executable file
View File

@ -29,33 +29,33 @@
## Installation ## Installation
```bash ```bash
$ pnpm install $ yarn install
``` ```
## Running the app ## Running the app
```bash ```bash
# development # development
$ pnpm run start $ yarn run start
# watch mode # watch mode
$ pnpm run start:dev $ yarn run start:dev
# production mode # production mode
$ pnpm run start:prod $ yarn run start:prod
``` ```
## Test ## Test
```bash ```bash
# unit tests # unit tests
$ pnpm run test $ yarn run test
# e2e tests # e2e tests
$ pnpm run test:e2e $ yarn run test:e2e
# test coverage # test coverage
$ pnpm run test:cov $ yarn run test:cov
``` ```
## Support ## Support

View File

@ -1,24 +1,41 @@
#!/bin/sh #!/bin/sh
# # 从 DATABASE_URL 环境变量中提取主机名、端口和用户名
# DB_HOST=$(echo $DATABASE_URL | cut -d '@' -f 2 | cut -d ':' -f 1)
# DB_PORT=$(echo $DATABASE_URL | cut -d ':' -f 4 | cut -d '/' -f 1)
# DB_USER=$(echo $DATABASE_URL | cut -d '/' -f 3 | cut -d ':' -f 1)
# # 检查数据库是否就绪
# until pg_isready -h $DB_HOST -p $DB_PORT -U $DB_USER; do
# echo "Database is unavailable - sleeping"
# sleep 1
# done
# echo "Database is up"
# # 检查标记文件是否存在,如果不存在,则执行 prisma deploy 并创建标记文件
# # if [ ! -f "/app/prisma-deployed" ]; then
# # pnpm prisma generate
# # pnpm prisma migrate deploy
# # touch /app/prisma-deployed
# # fi
# # 启动主应用
# exec node apps/server/dist/main
# 从 DATABASE_URL 环境变量中提取主机名、端口和用户名 # 从 DATABASE_URL 环境变量中提取主机名、端口和用户名
DB_HOST=$(echo $DATABASE_URL | cut -d '@' -f 2 | cut -d ':' -f 1) DB_HOST=$(echo $DATABASE_URL | cut -d '@' -f 2 | cut -d ':' -f 1)
DB_PORT=$(echo $DATABASE_URL | cut -d ':' -f 4 | cut -d '/' -f 1) DB_PORT=$(echo $DATABASE_URL | cut -d ':' -f 4 | cut -d '/' -f 1)
DB_USER=$(echo $DATABASE_URL | cut -d '/' -f 3 | cut -d ':' -f 1) DB_USER=$(echo $DATABASE_URL | cut -d '/' -f 3 | cut -d ':' -f 1)
# 检查数据库是否就绪 # 检查数据库是否就绪
until pg_isready -h $DB_HOST -p $DB_PORT -U $DB_USER; do until nc -z $DB_HOST $DB_PORT; do
echo "Database is unavailable - sleeping" echo "Database is unavailable - sleeping"
sleep 1 sleep 1
done done
echo "Database is up" echo "Database is up"
# 检查标记文件是否存在,如果不存在,则执行 prisma deploy 并创建标记文件
# if [ ! -f "/app/prisma-deployed" ]; then
# pnpm prisma generate
# pnpm prisma migrate deploy
# touch /app/prisma-deployed
# fi
# 启动主应用 # 启动主应用
exec node apps/back-worker/dist/main exec node apps/server/dist/main

0
apps/server/nest-cli.json Normal file → Executable file
View File

0
apps/server/package.json Normal file → Executable file
View File

View File

@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { TrpcService } from '@server/trpc/trpc.service'; import { TrpcService } from '@server/trpc/trpc.service';
import { ChangedRows, ObjectType, Prisma } from '@nicestack/common'; import { Prisma } from '@nicestack/common';
import { PostService } from './post.service'; import { PostService } from './post.service';
import { z, ZodType } from 'zod'; import { z, ZodType } from 'zod';
const PostCreateArgsSchema: ZodType<Prisma.PostCreateArgs> = z.any(); const PostCreateArgsSchema: ZodType<Prisma.PostCreateArgs> = z.any();
@ -15,7 +15,7 @@ export class PostRouter {
constructor( constructor(
private readonly trpc: TrpcService, private readonly trpc: TrpcService,
private readonly postService: PostService, private readonly postService: PostService,
) {} ) { }
router = this.trpc.router({ router = this.trpc.router({
create: this.trpc.protectProcedure create: this.trpc.protectProcedure
.input(PostCreateArgsSchema) .input(PostCreateArgsSchema)

View File

@ -1,5 +1,5 @@
import { db, Post, PostType, UserProfile, VisitType } from "@nicestack/common"; import { db, Post, PostType, UserProfile, VisitType } from "@nicestack/common";
import { getTroubleWithRelation } from "../trouble/utils";
export async function setPostRelation(params: { data: Post, staff?: UserProfile }) { export async function setPostRelation(params: { data: Post, staff?: UserProfile }) {
const { data, staff } = params const { data, staff } = params
const limitedComments = await db.post.findMany({ const limitedComments = await db.post.findMany({
@ -32,13 +32,13 @@ export async function setPostRelation(params: { data: Post, staff?: UserProfile
visitType: VisitType.READED, visitType: VisitType.READED,
}, },
}); });
const trouble = await getTroubleWithRelation(data.referenceId, staff) // const trouble = await getTroubleWithRelation(data.referenceId, staff)
Object.assign(data, { Object.assign(data, {
readed, readed,
readedCount, readedCount,
limitedComments, limitedComments,
commentsCount, commentsCount,
trouble // trouble
}) })
} }

View File

@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common';
import { TransformService } from './transform.service'; import { TransformService } from './transform.service';
import { TransformMethodSchema} from '@nicestack/common'; import { TransformMethodSchema} from '@nicestack/common';
import { TrpcService } from '@server/trpc/trpc.service'; import { TrpcService } from '@server/trpc/trpc.service';
@Injectable() @Injectable()
export class TransformRouter { export class TransformRouter {
constructor( constructor(

View File

@ -7,10 +7,13 @@ import {
db, db,
Prisma, Prisma,
Staff, Staff,
GetTroubleLevel,
UserProfile,
TroubleDto,
ObjectType,
RiskState,
} from '@nicestack/common'; } from '@nicestack/common';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import * as argon2 from 'argon2'; import * as argon2 from 'argon2';
import { TaxonomyService } from '@server/models/taxonomy/taxonomy.service'; import { TaxonomyService } from '@server/models/taxonomy/taxonomy.service';
import { uploadFile } from '@server/utils/tool'; import { uploadFile } from '@server/utils/tool';

View File

@ -14,5 +14,6 @@ export default async function (job: Job<any, any, CustomJobType>) {
logger.log(`push message ${job.data.id}`) logger.log(`push message ${job.data.id}`)
pushService.messagePush(job.data.registerToken, job.data.messageContent) pushService.messagePush(job.data.registerToken, job.data.messageContent)
break break
} }
} }

View File

@ -2,7 +2,7 @@ import { Injectable, OnModuleInit } from "@nestjs/common";
import { WebSocketType } from "../types"; import { WebSocketType } from "../types";
import { BaseWebSocketServer } from "../base/base-websocket-server"; import { BaseWebSocketServer } from "../base/base-websocket-server";
import EventBus, { CrudOperation } from "@server/utils/event-bus"; import EventBus, { CrudOperation } from "@server/utils/event-bus";
import { ObjectType, SocketMsgType, TroubleDto, MessageDto, PostDto, PostType } from "@nicestack/common"; import { ObjectType, SocketMsgType, MessageDto, PostDto, PostType } from "@nicestack/common";
@Injectable() @Injectable()
export class RealtimeServer extends BaseWebSocketServer implements OnModuleInit { export class RealtimeServer extends BaseWebSocketServer implements OnModuleInit {
onModuleInit() { onModuleInit() {
@ -11,11 +11,7 @@ export class RealtimeServer extends BaseWebSocketServer implements OnModuleInit
const receiverIds = (data as Partial<MessageDto>).receivers.map(receiver => receiver.id) const receiverIds = (data as Partial<MessageDto>).receivers.map(receiver => receiver.id)
this.sendToUsers(receiverIds, { type: SocketMsgType.NOTIFY, payload: { objectType: ObjectType.MESSAGE } }) this.sendToUsers(receiverIds, { type: SocketMsgType.NOTIFY, payload: { objectType: ObjectType.MESSAGE } })
} }
if (type === ObjectType.TROUBLE) {
const trouble = data as Partial<TroubleDto>
this.sendToRoom('troubles', { type: SocketMsgType.NOTIFY, payload: { objectType: ObjectType.TROUBLE } })
this.sendToRoom(trouble.id, { type: SocketMsgType.NOTIFY, payload: { objectType: ObjectType.TROUBLE } })
}
if (type === ObjectType.POST) { if (type === ObjectType.POST) {
const post = data as Partial<PostDto> const post = data as Partial<PostDto>
if (post.type === PostType.TROUBLE_INSTRUCTION || post.type === PostType.TROUBLE_PROGRESS) { if (post.type === PostType.TROUBLE_INSTRUCTION || post.type === PostType.TROUBLE_PROGRESS) {

View File

@ -22,6 +22,7 @@ import {
getCounts, getCounts,
getRandomImageLinks getRandomImageLinks
} from './utils'; } from './utils';
import { StaffService } from '@server/models/staff/staff.service'; import { StaffService } from '@server/models/staff/staff.service';
@Injectable() @Injectable()
export class GenDevService { export class GenDevService {
@ -40,6 +41,7 @@ export class GenDevService {
deptGeneratedCount = 0; deptGeneratedCount = 0;
constructor( constructor(
private readonly appConfigService: AppConfigService, private readonly appConfigService: AppConfigService,
private readonly departmentService: DepartmentService, private readonly departmentService: DepartmentService,
private readonly staffService: StaffService, private readonly staffService: StaffService,
private readonly termService: TermService, private readonly termService: TermService,
@ -183,7 +185,6 @@ export class GenDevService {
} }
} }
private async createDepartment( private async createDepartment(
name: string, name: string,
parentId?: string, parentId?: string,

View File

@ -2,12 +2,14 @@ import { db, getRandomElement, getRandomIntInRange, getRandomTimeInterval, Objec
import dayjs from 'dayjs'; import dayjs from 'dayjs';
export interface DevDataCounts { export interface DevDataCounts {
deptCount: number; deptCount: number;
staffCount: number staffCount: number
termCount: number termCount: number
} }
export async function getCounts(): Promise<DevDataCounts> { export async function getCounts(): Promise<DevDataCounts> {
const counts = { const counts = {
deptCount: await db.department.count(), deptCount: await db.department.count(),
staffCount: await db.staff.count(), staffCount: await db.staff.count(),
termCount: await db.term.count(), termCount: await db.term.count(),
}; };

View File

@ -3,7 +3,7 @@ import { ReminderService } from './reminder.service';
import { MessageModule } from '@server/models/message/message.module'; import { MessageModule } from '@server/models/message/message.module';
@Module({ @Module({
imports: [MessageModule], imports: [ MessageModule],
providers: [ReminderService], providers: [ReminderService],
exports: [ReminderService] exports: [ReminderService]
}) })

View File

@ -7,9 +7,8 @@
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { db, getUniqueItems, MessageMethodSchema, TroubleType, truncateString } from '@nicestack/common';
import { MessageService } from '@server/models/message/message.service'; import { MessageService } from '@server/models/message/message.service';
import { extractUniqueStaffIds } from '@server/models/department/utils';
/** /**
* *
@ -75,6 +74,8 @@ export class ReminderService {
* *
*/ */
async remindDeadline() { async remindDeadline() {
this.logger.log('开始检查截止日期以发送提醒。');
} }
} }

View File

@ -26,7 +26,6 @@ export class TrpcRouter {
private readonly taxonomy: TaxonomyRouter, private readonly taxonomy: TaxonomyRouter,
private readonly role: RoleRouter, private readonly role: RoleRouter,
private readonly rolemap: RoleMapRouter, private readonly rolemap: RoleMapRouter,
private readonly transform: TransformRouter, private readonly transform: TransformRouter,
private readonly auth: AuthRouter, private readonly auth: AuthRouter,
private readonly app_config: AppConfigRouter, private readonly app_config: AppConfigRouter,

0
apps/server/test/app.e2e-spec.ts Normal file → Executable file
View File

0
apps/server/test/jest-e2e.json Normal file → Executable file
View File

0
apps/server/tsconfig.build.json Normal file → Executable file
View File

44
auto.sh Normal file
View File

@ -0,0 +1,44 @@
#!/bin/bash
# 进入指定目录
cd /opt/projects/two-defender-app || exit
# 停止 server 容器
echo "停止 server 容器..."
sudo docker-compose stop server
# 移除 server 容器,自动确认
echo "移除 server 容器..."
yes | sudo docker-compose rm server
# 停止 web 容器
echo "停止 web 容器..."
sudo docker-compose stop web
# 移除 web 容器,自动确认
echo "移除 web 容器..."
yes | sudo docker-compose rm web
# 删除镜像
echo "删除 Docker 镜像..."
sudo docker image rm td-server:latest
sudo docker image rm td-web:latest
# 加载镜像
echo "加载 Docker 镜像..."
sudo docker load -i td-server.tar
sudo docker load -i td-web.tar
# 删除已加载的 tar 文件
sudo rm td-server.tar
sudo rm td-web.tar
# 启动服务
echo "启动服务..."
sudo docker-compose up -d
# 查看 server 容器的日志
echo "查看 server 容器的日志..."
sudo docker-compose logs server
echo "脚本执行完成。"

109
docker-compose.example.yml Normal file
View File

@ -0,0 +1,109 @@
version: "3.8"
services:
db:
image: postgres:latest
ports:
- "5432:5432"
environment:
- POSTGRES_DB=defender_app
- POSTGRES_USER=root
- POSTGRES_PASSWORD=Letusdoit000
volumes:
- ./volumes/postgres:/var/lib/postgresql/data
minio:
image: minio/minio
ports:
- "9000:9000"
- "9001:9001"
volumes:
- ./volumes/minio:/minio_data
environment:
- MINIO_ACCESS_KEY=minioadmin
- MINIO_SECRET_KEY=minioadmin
command: minio server /minio_data --console-address ":9001" -address ":9000"
healthcheck:
test:
[
"CMD",
"curl",
"-f",
"http://192.168.2.1:9001/minio/health/live"
]
interval: 30s
timeout: 20s
retries: 3
pgadmin:
image: dpage/pgadmin4
ports:
- "8082:80"
environment:
- PGADMIN_DEFAULT_EMAIL=insiinc@outlook.com
- PGADMIN_DEFAULT_PASSWORD=Letusdoit000
tusd:
image: tusproject/tusd
ports:
- "8080:8080"
environment:
- AWS_REGION=cn-north-1
- AWS_ACCESS_KEY_ID=minioadmin
- AWS_SECRET_ACCESS_KEY=minioadmin
command: -verbose -s3-bucket app -s3-endpoint http://minio:9000
volumes:
- ./volumes/tusd:/data
depends_on:
- minio
redis:
image: redis:latest
ports:
- "6379:6379"
volumes:
- ./config/redis.conf:/usr/local/etc/redis/redis.conf
- ./volumes/redis:/data
command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
# restic:
# image: restic/restic:latest
# environment:
# - RESTIC_REPOSITORY=/backup
# - RESTIC_PASSWORD=Letusdoit000
# volumes:
# - ./volumes/postgres:/data
# - ./volumes/restic-cache:/root/.cache/restic
# - ./backup:/backup # 本地目录挂载到容器内的 /backup
# - ./config/backup.sh:/usr/local/bin/backup.sh # Mount your script inside the container
# entrypoint: /usr/local/bin/backup.sh
# depends_on:
# - db
# web:
# image: td-web:latest
# ports:
# - "80:80"
# environment:
# - VITE_APP_SERVER_IP=192.168.79.77
# - VITE_APP_VERSION=0.3.0
# - VITE_APP_APP_NAME=两道防线管理后台
# server:
# image: td-server:latest
# ports:
# - "3000:3000"
# - "3001:3001"
# environment:
# - DATABASE_URL=postgresql://root:Letusdoit000@db:5432/defender_app?schema=public
# - REDIS_HOST=redis
# - REDIS_PORT=6379
# - REDIS_PASSWORD=Letusdoit000
# - TUS_URL=http://192.168.2.1:8080
# - JWT_SECRET=/yT9MnLm/r6NY7ee2Fby6ihCHZl+nFx4OQFKupivrhA=
# - PUSH_URL=http://dns:9092
# - PUSH_APPID=123
# - PUSH_APPSECRET=123
# - MINIO_HOST=minio
# - ADMIN_PHONE_NUMBER=13258117304
# - DEADLINE_CRON=0 0 8 * * *
# depends_on:
# - db
# - redis
networks:
default:
name: defender-app

View File

@ -1,60 +0,0 @@
version: "3.8"
services:
db:
image: postgres:latest
ports:
- "5432:5432"
environment:
- POSTGRES_DB=app
- POSTGRES_USER=root
- POSTGRES_PASSWORD=Letusdoit000
volumes:
- ./volumes/postgres:/var/lib/postgresql/data
minio:
image: minio/minio
ports:
- "9000:9000"
- "9001:9001"
volumes:
- ./volumes/minio:/data
environment:
- MINIO_ACCESS_KEY=minioadmin
- MINIO_SECRET_KEY=minioadmin
command: server /data --console-address ":9001" -address ":9000"
healthcheck:
test:
[
"CMD",
"curl",
"-f",
"http://localhost:9001/minio/health/live"
]
interval: 30s
timeout: 20s
retries: 3
pgadmin:
image: dpage/pgadmin4
ports:
- "8081:80"
environment:
- PGADMIN_DEFAULT_EMAIL=insiinc@outlook.com
- PGADMIN_DEFAULT_PASSWORD=Letusdoit000
tusd:
image: tusproject/tusd
ports:
- "8080:8080"
environment:
- AWS_REGION=cn-north-1
- AWS_ACCESS_KEY_ID=minioadmin
- AWS_SECRET_ACCESS_KEY=minioadmin
command: -verbose -s3-bucket app -s3-endpoint http://minio:9000
volumes:
- ./volumes/tusd:/data
redis:
image: redis:latest
ports:
- "6379:6379"
volumes:
- ./volumes/redis:/data

View File

@ -19,6 +19,7 @@
"tinycolor2": "^1.6.0" "tinycolor2": "^1.6.0"
}, },
"peerDependencies": { "peerDependencies": {
"dayjs": "^1.11.12",
"react": "18.2.0", "react": "18.2.0",
"@nicestack/common": "workspace:^", "@nicestack/common": "workspace:^",
"@tanstack/query-async-storage-persister": "^5.51.9", "@tanstack/query-async-storage-persister": "^5.51.9",

View File

@ -5,7 +5,6 @@ export * from "./useTerm"
export * from "./useRole" export * from "./useRole"
export * from "./useRoleMap" export * from "./useRoleMap"
export * from "./useTransform" export * from "./useTransform"
export * from "./useTrouble"
export * from "./useTaxonomy" export * from "./useTaxonomy"
export * from "./useVisitor" export * from "./useVisitor"
export * from "./useMessage" export * from "./useMessage"

View File

@ -0,0 +1,34 @@
import { getQueryKey } from "@trpc/react-query";
import { api } from "../trpc"; // Adjust path as necessary
import { useQueryClient } from "@tanstack/react-query";
export function useTransform() {
const queryClient = useQueryClient();
const queryKey = getQueryKey(api.transform);
const termQueryKey = getQueryKey(api.term);
const deptQueryKey = getQueryKey(api.department);
const importTerms = api.transform.importTerms.useMutation({
onSuccess: () => {
queryClient.invalidateQueries({ queryKey });
queryClient.invalidateQueries({ queryKey: termQueryKey });
},
});
const importDepts = api.transform.importDepts.useMutation({
onSuccess: () => {
queryClient.invalidateQueries({ queryKey });
queryClient.invalidateQueries({ queryKey: deptQueryKey });
},
});
const importStaffs = api.transform.importStaffs.useMutation({
onSuccess: () => {
queryClient.invalidateQueries({ queryKey });
},
});
return {
importTerms,
importDepts,
importStaffs,
};
}

View File

@ -8,7 +8,7 @@ export function useVisitor() {
const create = api.visitor.create.useMutation({ const create = api.visitor.create.useMutation({
onSuccess() { onSuccess() {
utils.visitor.invalidate(); utils.visitor.invalidate();
utils.trouble.invalidate(); // utils.trouble.invalidate();
}, },
}); });
/** /**
@ -20,67 +20,67 @@ export function useVisitor() {
updateFn: (item: any, variables: any) => any updateFn: (item: any, variables: any) => any
) => ({ ) => ({
// 在请求发送前执行本地数据预更新 // 在请求发送前执行本地数据预更新
onMutate: async (variables: any) => { // onMutate: async (variables: any) => {
const previousDataList: any[] = []; // const previousDataList: any[] = [];
// 动态生成参数列表,包括星标和其他参数 // // 动态生成参数列表,包括星标和其他参数
const paramsList = troubleParams.getItems(); // const paramsList = troubleParams.getItems();
console.log(paramsList.length); // console.log(paramsList.length);
// 遍历所有参数列表,执行乐观更新 // // 遍历所有参数列表,执行乐观更新
for (const params of paramsList) { // for (const params of paramsList) {
// 取消可能的并发请求 // // 取消可能的并发请求
await utils.trouble.findManyWithCursor.cancel(); // await utils.trouble.findManyWithCursor.cancel();
// 获取并保存当前数据 // // 获取并保存当前数据
const previousData = // const previousData =
utils.trouble.findManyWithCursor.getInfiniteData({ // utils.trouble.findManyWithCursor.getInfiniteData({
...params, // ...params,
}); // });
previousDataList.push(previousData); // previousDataList.push(previousData);
// 执行乐观更新 // // 执行乐观更新
utils.trouble.findManyWithCursor.setInfiniteData( // utils.trouble.findManyWithCursor.setInfiniteData(
{ // {
...params, // ...params,
}, // },
(oldData) => { // (oldData) => {
if (!oldData) return oldData; // if (!oldData) return oldData;
return { // return {
...oldData, // ...oldData,
pages: oldData.pages.map((page) => ({ // pages: oldData.pages.map((page) => ({
...page, // ...page,
items: page.items.map((item) => // items: page.items.map((item) =>
item.id === variables?.troubleId // item.id === variables?.troubleId
? updateFn(item, variables) // ? updateFn(item, variables)
: item // : item
), // ),
})), // })),
}; // };
} // }
); // );
} // }
return { previousDataList }; // return { previousDataList };
}, // },
// 错误处理:数据回滚 // // 错误处理:数据回滚
onError: (_err: any, _variables: any, context: any) => { // onError: (_err: any, _variables: any, context: any) => {
const paramsList = troubleParams.getItems(); // const paramsList = troubleParams.getItems();
paramsList.forEach((params, index) => { // paramsList.forEach((params, index) => {
if (context?.previousDataList?.[index]) { // if (context?.previousDataList?.[index]) {
utils.trouble.findManyWithCursor.setInfiniteData( // utils.trouble.findManyWithCursor.setInfiniteData(
{ ...params }, // { ...params },
context.previousDataList[index] // context.previousDataList[index]
); // );
} // }
}); // });
}, // },
// 成功后的缓存失效 // // 成功后的缓存失效
onSuccess: (_: any, variables: any) => { // onSuccess: (_: any, variables: any) => {
utils.visitor.invalidate(); // utils.visitor.invalidate();
utils.trouble.findFirst.invalidate({ // utils.trouble.findFirst.invalidate({
where: { // where: {
id: (variables as any)?.troubleId, // id: (variables as any)?.troubleId,
}, // },
}); // });
}, // },
}); });
// 定义具体的mutation // 定义具体的mutation
const read = api.visitor.create.useMutation( const read = api.visitor.create.useMutation(

View File

@ -4,7 +4,7 @@
*/ */
import mitt from 'mitt'; import mitt from 'mitt';
import { DepartmentDto, ObjectType, RoleMapDto, StaffDto, TermDto, TroubleDto } from '@nicestack/common'; import { DepartmentDto, ObjectType, RoleMapDto, StaffDto, TermDto, } from '@nicestack/common';
/** /**
* CRUD操作 * CRUD操作
@ -38,7 +38,6 @@ type EmitChangeFunction<T> = (data: Partial<T>, operation: CrudOperation) => voi
*/ */
interface EmitChangeHandlers { interface EmitChangeHandlers {
[ObjectType.STAFF]: EmitChangeFunction<StaffDto>; // 员工数据变更处理器 [ObjectType.STAFF]: EmitChangeFunction<StaffDto>; // 员工数据变更处理器
[ObjectType.TROUBLE]: EmitChangeFunction<TroubleDto>; // 问题数据变更处理器
[ObjectType.ROLE_MAP]: EmitChangeFunction<RoleMapDto>; // 角色映射数据变更处理器 [ObjectType.ROLE_MAP]: EmitChangeFunction<RoleMapDto>; // 角色映射数据变更处理器
[ObjectType.DEPARTMENT]: EmitChangeFunction<DepartmentDto>; // 部门数据变更处理器 [ObjectType.DEPARTMENT]: EmitChangeFunction<DepartmentDto>; // 部门数据变更处理器
[ObjectType.TERM]: EmitChangeFunction<TermDto> // 术语数据变更处理器 [ObjectType.TERM]: EmitChangeFunction<TermDto> // 术语数据变更处理器
@ -66,21 +65,6 @@ const emitChangeHandlers: EmitChangeHandlers = {
}); });
}, },
[ObjectType.TROUBLE]: (data, operation) => {
// 转换问题数据,包含额外字段
const rowData = {
...data,
dept_name: data.department?.name,
possible_result: data.possibleResult,
};
// 发出问题数据变更事件
EventBus.emit("dataChanged", {
type: ObjectType.TROUBLE,
operation,
data: [rowData]
});
},
[ObjectType.ROLE_MAP]: (data, operation) => { [ObjectType.ROLE_MAP]: (data, operation) => {
// 转换角色映射数据,包含额外字段 // 转换角色映射数据,包含额外字段
const rowData = { const rowData = {

View File

@ -9,7 +9,6 @@ import {
TroubleType, TroubleType,
VisitType, VisitType,
} from "./enum"; } from "./enum";
import { troubleUnDetailSelect } from "./select";
export const InitRoles: { export const InitRoles: {
name: string; name: string;

View File

@ -1,5 +1,3 @@
import { troubleUnDetailSelect } from "./select";
export enum SocketMsgType { export enum SocketMsgType {
NOTIFY, NOTIFY,
} }

View File

@ -4,7 +4,6 @@ import type {
Term, Term,
Message, Message,
Post, Post,
Trouble,
RoleMap, RoleMap,
} from "@prisma/client"; } from "@prisma/client";
import { SocketMsgType, RolePerms } from "./enum"; import { SocketMsgType, RolePerms } from "./enum";

View File

@ -3,6 +3,7 @@
"target": "es2022", "target": "es2022",
"module": "esnext", "module": "esnext",
"lib": [ "lib": [
"DOM",
"es2022" "es2022"
], ],
"declaration": true, "declaration": true,

12176
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,5 @@ packages:
# all packages in direct subdirs of packages/ # all packages in direct subdirs of packages/
- 'packages/*' - 'packages/*'
- 'apps/*' - 'apps/*'
- 'libs/*'
# exclude packages that are inside test directories # exclude packages that are inside test directories
# - '!**/test/**' # - '!**/test/**'

7
tsconfig.base.json Normal file → Executable file
View File

@ -3,9 +3,8 @@
"baseUrl": ".", "baseUrl": ".",
"experimentalDecorators": true, "experimentalDecorators": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"incremental": true,
"skipLibCheck": true, "skipLibCheck": true,
"strictNullChecks": true, "strictNullChecks": false,
"strictBindCallApply": true, "strictBindCallApply": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
@ -17,10 +16,10 @@
"strictPropertyInitialization": false, "strictPropertyInitialization": false,
"paths": { "paths": {
"@server/*": [ "@server/*": [
"./apps/server/src/*" "./apps/server/src/*",
], ],
"@web/*": [ "@web/*": [
"apps/web/*" "./apps/web/*",
], ],
} }
} }