Compare commits
71 Commits
main
...
feature/pe
Author | SHA1 | Date | |
---|---|---|---|
|
b749cfa6f6 | ||
|
43001a12c7 | ||
|
d3c2180ac8 | ||
|
63e16e1694 | ||
|
c6cc0d22e5 | ||
|
949a989dcf | ||
|
9427e0efe5 | ||
|
70c236c879 | ||
|
3d31245ae5 | ||
|
98fc21995a | ||
|
f6e3778a2d | ||
|
6775a9df5e | ||
|
3cb843e493 | ||
|
bc3ce31c9e | ||
|
3e7f0cac90 | ||
|
f3b8ab278a | ||
|
013f9ac811 | ||
|
9e07e8330a | ||
|
8950073141 | ||
|
4d580bb789 | ||
|
f73bfb0ab3 | ||
|
8109d350e4 | ||
|
b233023bb3 | ||
|
fd7cc2ea96 | ||
|
724535a735 | ||
|
b5c7ec3008 | ||
|
c5698ad631 | ||
|
6fb337633c | ||
|
42b8d74ead | ||
|
d8e76dc2e6 | ||
|
4f2cd4ca47 | ||
|
82c98ec1f5 | ||
|
fbc654f21b | ||
|
9d89a1ceca | ||
|
517906f77f | ||
|
681e8fa3ae | ||
|
4d5c3eb15d | ||
|
9a35de7e36 | ||
|
c4b730c8af | ||
|
8ea8869ca7 | ||
|
7e5a8ae5c1 | ||
|
cf495b60d1 | ||
|
fb57eaaba7 | ||
|
b494bd6174 | ||
|
310fe0d325 | ||
|
ba418c5cd7 | ||
|
9596cd07a1 | ||
|
2ed5b64b18 | ||
|
492e0055f0 | ||
|
93938702fe | ||
|
e8907ca4fb | ||
|
2b32850046 | ||
|
da1749fb53 | ||
|
5bc5eb8719 | ||
|
3227a799f9 | ||
|
8740685a4d | ||
|
5c3915a74d | ||
|
b1958ec8ff | ||
|
bc39e9933d | ||
|
36dd5a4f2d | ||
|
a101878313 | ||
|
32e43b8260 | ||
|
9afb23c08e | ||
|
eaa1abda82 | ||
|
6431cc3210 | ||
|
122f584cad | ||
|
f45cf7982f | ||
|
2f0736fd95 | ||
|
6c72344204 | ||
|
61fd6e09af | ||
|
349b4dad8c |
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,6 +3,7 @@ node_modules
|
|||||||
dist
|
dist
|
||||||
|
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
# local env files
|
# local env files
|
||||||
.env.local
|
.env.local
|
||||||
|
@ -1,133 +0,0 @@
|
|||||||
|
|
||||||
# Contributor Covenant Code of Conduct
|
|
||||||
|
|
||||||
## Our Pledge
|
|
||||||
|
|
||||||
We as members, contributors, and leaders pledge to make participation in our
|
|
||||||
community a harassment-free experience for everyone, regardless of age, body
|
|
||||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
||||||
identity and expression, level of experience, education, socio-economic status,
|
|
||||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
|
||||||
identity and orientation.
|
|
||||||
|
|
||||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
||||||
diverse, inclusive, and healthy community.
|
|
||||||
|
|
||||||
## Our Standards
|
|
||||||
|
|
||||||
Examples of behavior that contributes to a positive environment for our
|
|
||||||
community include:
|
|
||||||
|
|
||||||
* Demonstrating empathy and kindness toward other people
|
|
||||||
* Being respectful of differing opinions, viewpoints, and experiences
|
|
||||||
* Giving and gracefully accepting constructive feedback
|
|
||||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
|
||||||
and learning from the experience
|
|
||||||
* Focusing on what is best not just for us as individuals, but for the overall
|
|
||||||
community
|
|
||||||
|
|
||||||
Examples of unacceptable behavior include:
|
|
||||||
|
|
||||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
|
||||||
any kind
|
|
||||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
||||||
* Public or private harassment
|
|
||||||
* Publishing others' private information, such as a physical or email address,
|
|
||||||
without their explicit permission
|
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
|
||||||
professional setting
|
|
||||||
|
|
||||||
## Enforcement Responsibilities
|
|
||||||
|
|
||||||
Community leaders are responsible for clarifying and enforcing our standards of
|
|
||||||
acceptable behavior and will take appropriate and fair corrective action in
|
|
||||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
||||||
or harmful.
|
|
||||||
|
|
||||||
Community leaders have the right and responsibility to remove, edit, or reject
|
|
||||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
|
||||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
|
||||||
decisions when appropriate.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This Code of Conduct applies within all community spaces, and also applies when
|
|
||||||
an individual is officially representing the community in public spaces.
|
|
||||||
Examples of representing our community include using an official email address,
|
|
||||||
posting via an official social media account, or acting as an appointed
|
|
||||||
representative at an online or offline event.
|
|
||||||
|
|
||||||
## Enforcement
|
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
||||||
reported to the community leaders responsible for enforcement at
|
|
||||||
[xiaojusurvey@gmail.com](mailto:xiaojusurvey@gmail.com).
|
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
|
||||||
|
|
||||||
All community leaders are obligated to respect the privacy and security of the
|
|
||||||
reporter of any incident.
|
|
||||||
|
|
||||||
## Enforcement Guidelines
|
|
||||||
|
|
||||||
Community leaders will follow these Community Impact Guidelines in determining
|
|
||||||
the consequences for any action they deem in violation of this Code of Conduct:
|
|
||||||
|
|
||||||
### 1. Correction
|
|
||||||
|
|
||||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
|
||||||
unprofessional or unwelcome in the community.
|
|
||||||
|
|
||||||
**Consequence**: A private, written warning from community leaders, providing
|
|
||||||
clarity around the nature of the violation and an explanation of why the
|
|
||||||
behavior was inappropriate. A public apology may be requested.
|
|
||||||
|
|
||||||
### 2. Warning
|
|
||||||
|
|
||||||
**Community Impact**: A violation through a single incident or series of
|
|
||||||
actions.
|
|
||||||
|
|
||||||
**Consequence**: A warning with consequences for continued behavior. No
|
|
||||||
interaction with the people involved, including unsolicited interaction with
|
|
||||||
those enforcing the Code of Conduct, for a specified period of time. This
|
|
||||||
includes avoiding interactions in community spaces as well as external channels
|
|
||||||
like social media. Violating these terms may lead to a temporary or permanent
|
|
||||||
ban.
|
|
||||||
|
|
||||||
### 3. Temporary Ban
|
|
||||||
|
|
||||||
**Community Impact**: A serious violation of community standards, including
|
|
||||||
sustained inappropriate behavior.
|
|
||||||
|
|
||||||
**Consequence**: A temporary ban from any sort of interaction or public
|
|
||||||
communication with the community for a specified period of time. No public or
|
|
||||||
private interaction with the people involved, including unsolicited interaction
|
|
||||||
with those enforcing the Code of Conduct, is allowed during this period.
|
|
||||||
Violating these terms may lead to a permanent ban.
|
|
||||||
|
|
||||||
### 4. Permanent Ban
|
|
||||||
|
|
||||||
**Community Impact**: Demonstrating a pattern of violation of community
|
|
||||||
standards, including sustained inappropriate behavior, harassment of an
|
|
||||||
individual, or aggression toward or disparagement of classes of individuals.
|
|
||||||
|
|
||||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
|
||||||
community.
|
|
||||||
|
|
||||||
## Attribution
|
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
||||||
version 2.1, available at
|
|
||||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
|
||||||
|
|
||||||
Community Impact Guidelines were inspired by
|
|
||||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
|
||||||
|
|
||||||
For answers to common questions about this code of conduct, see the FAQ at
|
|
||||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
|
||||||
[https://www.contributor-covenant.org/translations][translations].
|
|
||||||
|
|
||||||
[homepage]: https://www.contributor-covenant.org
|
|
||||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
|
||||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
|
||||||
[FAQ]: https://www.contributor-covenant.org/faq
|
|
||||||
[translations]: https://www.contributor-covenant.org/translations
|
|
@ -1,22 +0,0 @@
|
|||||||
# Important Disclosure re:XIAOJUSURVEY Data Collection
|
|
||||||
|
|
||||||
XIAOJUSURVEY is open-source software developed and maintained by XIAOJUSURVEY Team and available at https://github.com/didi/xiaoju-survey.
|
|
||||||
We hereby state the purpose and reason for collecting data.
|
|
||||||
|
|
||||||
## Purpose of data collection
|
|
||||||
|
|
||||||
Data collected is used to help improve XIAOJUSURVEY for all users. It is important that our team understands the usage patterns as soon as possible, so we can best decide how to design future features and prioritize current work.
|
|
||||||
|
|
||||||
## Types of data collected
|
|
||||||
|
|
||||||
XIAOJUSURVEY just collects data about version's information. The data collected is subsequently reported to the XIAOJUSURVEY's backend services.
|
|
||||||
|
|
||||||
All data collected will be used exclusively by the XIAOJUSURVEY team for analytical purposes only. The data will be neither accessible nor sold to any third party.
|
|
||||||
|
|
||||||
## Sensitive data
|
|
||||||
|
|
||||||
XIAOJUSURVEY will never collect and/or report sensitive information, such as private keys, API keys, or passwords.
|
|
||||||
|
|
||||||
## How do I opt-in to or opt-out of data sharing?
|
|
||||||
|
|
||||||
See [docs](https://xiaojusurvey.didi.cn/docs/next/community/%E6%95%B0%E6%8D%AE%E4%B8%8A%E6%8A%A5%E5%A3%B0%E6%98%8E) for information on configuring this functionality.
|
|
@ -1,5 +1,5 @@
|
|||||||
# 镜像集成
|
# 镜像集成
|
||||||
FROM node:18-slim
|
FROM node:18
|
||||||
|
|
||||||
# 设置工作区间
|
# 设置工作区间
|
||||||
WORKDIR /xiaoju-survey
|
WORKDIR /xiaoju-survey
|
||||||
|
2
LICENSE
2
LICENSE
@ -186,7 +186,7 @@
|
|||||||
same "printed page" as the copyright notice for easier
|
same "printed page" as the copyright notice for easier
|
||||||
identification within third-party archives.
|
identification within third-party archives.
|
||||||
|
|
||||||
Copyright (C) 2023 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
156
README.md
156
README.md
@ -33,49 +33,29 @@
|
|||||||
|
|
||||||
  内部系统已沉淀 40+种题型,累积精选模板 100+,适用于市场调研、客户满意度调研、在线考试、投票、报道、测评等众多场景。数据能力上,经过上亿量级打磨,沉淀了分题统计、交叉分析、多渠道分析等在线报表能力,快速满足专业化分析。
|
  内部系统已沉淀 40+种题型,累积精选模板 100+,适用于市场调研、客户满意度调研、在线考试、投票、报道、测评等众多场景。数据能力上,经过上亿量级打磨,沉淀了分题统计、交叉分析、多渠道分析等在线报表能力,快速满足专业化分析。
|
||||||
|
|
||||||
# 功能特性
|
# 功能简介
|
||||||
|
|
||||||
**🌈 易用**
|
- 问卷管理:创、编、投、收、数据分析
|
||||||
|
|
||||||
- 多类型数据采集,轻松创建调研表单:文本输入、数据选择、评分、投票、文件上传等。
|
- 多样化题型:单行输入框、多行输入框、单项选择、多项选择、判断题、评分、投票、...
|
||||||
|
|
||||||
- 智能逻辑编排,设计多规则动态表单:显示逻辑、跳转逻辑、选项引用、题目引用等。
|
- 用户管理:登录、注册、权限管理
|
||||||
|
|
||||||
- 精细权限管理,支持高效团队协同:空间管理、多角色权限管理等。
|
- 数据安全:传输加密、脱敏等
|
||||||
|
|
||||||
- 数据在线分析和导出,洞察调研结果:数据导出、回收数据管理、分题统计、交叉分析等。
|
> 更全的建设请查阅 [功能介绍](https://xiaojusurvey.didi.cn/docs/next/document/%E4%BA%A7%E5%93%81%E6%89%8B%E5%86%8C/%E5%8A%9F%E8%83%BD%E4%BB%8B%E7%BB%8D/%E5%9F%BA%E7%A1%80%E6%B5%81%E7%A8%8B)
|
||||||
|
|
||||||
**🎨 好看**
|
|
||||||
|
|
||||||
- 主题自由定制,适配您的品牌:自定义颜色、背景、图片、Logo、结果页规则等。
|
|
||||||
|
|
||||||
- 无缝嵌入各终端,满足不同场景需求:多端嵌入式小问卷 SDK。
|
|
||||||
|
|
||||||
**🚀 安全、可扩展**
|
|
||||||
|
|
||||||
- 安全能力可扩展,提供安全相关建设的经验指导:传输加密、敏感词库、发布审查等。
|
|
||||||
|
|
||||||
- 自定义 Hook 配置,轻松集成多方系统与各类工具:数据推送集成、消息推送集成等。
|
|
||||||
|
|
||||||
<img src="https://github.com/didi/xiaoju-survey/assets/16012672/dd427471-368d-49d9-bc44-13c34d84e3be" width="700" />
|
<img src="https://github.com/didi/xiaoju-survey/assets/16012672/dd427471-368d-49d9-bc44-13c34d84e3be" width="700" />
|
||||||
|
|
||||||
1、 **全部功能**请查看 [功能介绍](https://xiaojusurvey.didi.cn/docs/next/document/%E4%BA%A7%E5%93%81%E6%89%8B%E5%86%8C/%E5%8A%9F%E8%83%BD%E4%BB%8B%E7%BB%8D/%E5%9F%BA%E7%A1%80%E6%B5%81%E7%A8%8B)。
|
_**(个人和企业用户均可快速构建特定领域的调研类解决方案。)**_
|
||||||
|
|
||||||
2、**企业**和**个人**均可快速构建特定领域的调研类解决方案。
|
|
||||||
|
|
||||||
# 技术
|
# 技术
|
||||||
|
|
||||||
**1、Web 端:Vue3 + ElementPlus**
|
Web 端:Vue3 + ElementPlus;C 端多端渲染(规划中)
|
||||||
|
|
||||||
  C 端多端渲染:ReactNative SDK 建设中
|
Server 端:Nestjs + MongoDB;Java(在建,[欢迎加入共建](https://github.com/didi/xiaoju-survey/issues/306))
|
||||||
|
|
||||||
**2、Server 端:NestJS + MongoDB**
|
智能化基座:(规划中)
|
||||||
|
|
||||||
  Java 版:建设中,[欢迎加入共建](https://github.com/didi/xiaoju-survey/issues/306)
|
|
||||||
|
|
||||||
**3、能力增强**
|
|
||||||
|
|
||||||
  在线平台:建设中、智能化问卷:规划中
|
|
||||||
|
|
||||||
# 项目优势
|
# 项目优势
|
||||||
|
|
||||||
@ -126,25 +106,101 @@
|
|||||||
|
|
||||||
前后端分离,提供 Docker 化方案,提供了完善的部署指导手册。
|
前后端分离,提供 Docker 化方案,提供了完善的部署指导手册。
|
||||||
|
|
||||||
# 快速使用
|
# 快速启动
|
||||||
|
|
||||||
_(在线平台建设中)_
|
Node 版本 >= 18.x,
|
||||||
|
[查看环境准备指导](https://xiaojusurvey.didi.cn/docs/next/document/%E6%A6%82%E8%BF%B0/%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B)
|
||||||
|
|
||||||
# 本地开发
|
复制工程
|
||||||
|
|
||||||
请查看 [本地安装手册](https://xiaojusurvey.didi.cn/docs/next/document/%E6%A6%82%E8%BF%B0/%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B) 来启动项目。
|
```shell
|
||||||
|
git clone git@github.com:didi/xiaoju-survey.git
|
||||||
|
```
|
||||||
|
|
||||||
# 快速部署
|
## 服务端启动
|
||||||
|
|
||||||
### 服务部署
|
### 方案一、快速启动,无需安装数据库
|
||||||
|
|
||||||
请查看 [快速部署指导](https://xiaojusurvey.didi.cn/docs/next/document/%E5%B7%A5%E7%A8%8B%E9%83%A8%E7%BD%B2/Docker%E9%83%A8%E7%BD%B2) 。
|
> _便于快速预览工程,对于正式项目需要使用方案二。_
|
||||||
|
|
||||||
### 一键部署
|
#### 1、安装依赖
|
||||||
|
|
||||||
_(手册编写中)_
|
```shell
|
||||||
|
cd server
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
<br />
|
#### 2、启动
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm run local
|
||||||
|
```
|
||||||
|
|
||||||
|
> 服务运行依赖 [mongodb-memory-server](https://github.com/nodkz/mongodb-memory-server):
|
||||||
|
>
|
||||||
|
> 1、数据保存在内存中,重启服务会更新数据。<br />2、启动内存服务器新实例时,如果找不到 MongoDB 二进制文件会自动下载,因此首次可能需要一些时间。
|
||||||
|
|
||||||
|
### 方案二、(生产推荐)
|
||||||
|
|
||||||
|
#### 1、配置数据库
|
||||||
|
|
||||||
|
> 项目使用 MongoDB,需要提前准备,请查看[如何拥有 MongoDB 指南](./数据库#安装)
|
||||||
|
|
||||||
|
配置数据库信息,查看[MongoDB 配置](./数据库)。
|
||||||
|
|
||||||
|
#### 2、安装依赖
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cd server
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3、启动
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## 前端启动
|
||||||
|
|
||||||
|
### 安装依赖
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cd web
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 启动
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm run serve
|
||||||
|
```
|
||||||
|
|
||||||
|
## 访问
|
||||||
|
|
||||||
|
### 问卷管理端
|
||||||
|
|
||||||
|
[http://localhost:8080/management](http://localhost:8080)
|
||||||
|
|
||||||
|
### 问卷投放端
|
||||||
|
|
||||||
|
创建并发布问卷。
|
||||||
|
|
||||||
|
[http://localhost:8080/render/:surveyPath](http://localhost:8080/render/:surveyPath)
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
## 微信交流群(推荐)
|
||||||
|
|
||||||
|
官方群会发布项目最新消息、建设计划和社区活动,欢迎你的加入。任何问题和合作可以联系小助手:
|
||||||
|
|
||||||
|
<img src="https://img-hxy021.didistatic.com/static/starimg/img/KXKvc7sjHz1700061188156.png" width="200" />
|
||||||
|
|
||||||
|
## QQ 交流群
|
||||||
|
|
||||||
|
官方群会发布项目最新消息、建设计划和社区活动,欢迎你的加入:
|
||||||
|
|
||||||
|
[<img src="https://img-hxy021.didistatic.com/static/starimg/img/iJUmLIHKV21700192846057.png" width="210" />](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=P61UJI_q8AzizyBLGOm-bUvzNrUnSQq-&authKey=yZFtL9biGB5yiIME3%2Bi%2Bf6XMOdTNiuf0pCIaviEEAIryySNzVy6LJ4xl7uHdEcrM&noverify=0&group_code=920623419)
|
||||||
|
|
||||||
## Star
|
## Star
|
||||||
|
|
||||||
@ -152,26 +208,18 @@ _(手册编写中)_
|
|||||||
|
|
||||||
[![Star History Chart](https://api.star-history.com/svg?repos=didi/xiaoju-survey&type=Date)](https://star-history.com/#didi/xiaoju-survey&Date)
|
[![Star History Chart](https://api.star-history.com/svg?repos=didi/xiaoju-survey&type=Date)](https://star-history.com/#didi/xiaoju-survey&Date)
|
||||||
|
|
||||||
## 交流群
|
## 记录
|
||||||
|
|
||||||
官方群会发布项目最新消息、建设计划和社区活动,欢迎你的加入。
|
|
||||||
|
|
||||||
<img src="https://img-hxy021.didistatic.com/static/starimg/img/KXKvc7sjHz1700061188156.png" width="200" />
|
|
||||||
|
|
||||||
_任何问题和合作可以联系小助手。_
|
|
||||||
|
|
||||||
## 案例
|
|
||||||
|
|
||||||
如果你使用了该项目,请记录反馈:[我在使用](https://github.com/didi/xiaoju-survey/issues/64),你的支持是我们最大的动力。
|
如果你使用了该项目,请记录反馈:[我在使用](https://github.com/didi/xiaoju-survey/issues/64),你的支持是我们最大的动力。
|
||||||
|
|
||||||
## Future Tasks
|
|
||||||
|
|
||||||
[欢迎了解项目发展和共建](https://github.com/didi/xiaoju-survey/issues/85),你的支持是我们最大的动力。
|
|
||||||
|
|
||||||
## 贡献
|
## 贡献
|
||||||
|
|
||||||
如果你想成为贡献者或者扩展技术栈,请查看:[贡献者指南](https://xiaojusurvey.didi.cn/docs/next/share/%E5%A6%82%E4%BD%95%E5%8F%82%E4%B8%8E%E8%B4%A1%E7%8C%AE),你的加入使我们最大的荣幸。
|
如果你想成为贡献者或者扩展技术栈,请查看:[贡献者指南](https://xiaojusurvey.didi.cn/docs/next/share/%E5%A6%82%E4%BD%95%E5%8F%82%E4%B8%8E%E8%B4%A1%E7%8C%AE),你的加入使我们最大的荣幸。
|
||||||
|
|
||||||
|
## Future Tasks
|
||||||
|
|
||||||
|
[欢迎共建](https://github.com/didi/xiaoju-survey/issues/85)
|
||||||
|
|
||||||
## CHANGELOG
|
## CHANGELOG
|
||||||
|
|
||||||
关注项目重大变更:[MAJOR CHANGELOG](https://github.com/didi/xiaoju-survey/issues/48)。
|
关注重大变更:[MAJOR CHANGELOG](https://github.com/didi/xiaoju-survey/issues/48)
|
||||||
|
48
README_EN.md
48
README_EN.md
@ -33,43 +33,27 @@
|
|||||||
|
|
||||||
  The internal system has accumulated over 40 question types and more than 100 selected templates, suitable for market research, customer satisfaction surveys, online exams, voting, reporting, evaluations, and many other scenarios. In terms of data capabilities, it has been honed through hundreds of millions of iterations, resulting in the ability to provide online reports with per-question statistics, cross-analysis, and multi-channel analysis, quickly meeting professional analysis needs.
|
  The internal system has accumulated over 40 question types and more than 100 selected templates, suitable for market research, customer satisfaction surveys, online exams, voting, reporting, evaluations, and many other scenarios. In terms of data capabilities, it has been honed through hundreds of millions of iterations, resulting in the ability to provide online reports with per-question statistics, cross-analysis, and multi-channel analysis, quickly meeting professional analysis needs.
|
||||||
|
|
||||||
# Features
|
# Function Overview
|
||||||
|
|
||||||
**🌈 Easy to use**
|
- Questionnaire Management: Create, edit, distribute, collect, data analysis.
|
||||||
|
|
||||||
- Multi-type data collection, easy to create forms: text input, data selection, scoring, voting, file upload, etc.
|
- Diverse Question Types: Single-line input, multi-line input, single choice, multiple choice, true/false, rating, voting, etc.
|
||||||
|
|
||||||
- Smart logic arrangement, design multi-rule dynamic forms: display logic, jump logic, option reference, title reference, etc.
|
- User Management: Login, registration, permissions management.
|
||||||
|
|
||||||
- Multiple permission management, support efficient team collaboration: space management, multi-role permission management, etc.
|
- Data Security: Encrypted transmission, data masking, etc.
|
||||||
|
|
||||||
- Online data analysis and export, insight into survey results: data export, recycled data management, sub-topic statistics, cross-analysis, etc.
|
> For more comprehensive features, please refer to the [documentation](https://xiaojusurvey.didi.cn/docs/next/document/%E4%BA%A7%E5%93%81%E6%89%8B%E5%86%8C/%E5%8A%9F%E8%83%BD%E4%BB%8B%E7%BB%8D/%E5%9F%BA%E7%A1%80%E6%B5%81%E7%A8%8B).
|
||||||
|
|
||||||
**🎨 Good-looking**
|
|
||||||
|
|
||||||
- Free customization of themes to adapt to your brand: custom colors, backgrounds, pictures, logos, result page rules, etc.
|
|
||||||
|
|
||||||
- Seamlessly embedded in various terminals to meet the needs of different scenarios: multi-terminal embedded small questionnaire SDK.
|
|
||||||
|
|
||||||
**🚀 Secure and scalable**
|
|
||||||
|
|
||||||
- Scalable security capabilities, providing experience guidance for security-related construction: encrypted transmission, data masking, etc.
|
|
||||||
|
|
||||||
- Customized Hook configuration, easy integration of multiple systems and various tools: data push, message push, etc.
|
|
||||||
|
|
||||||
<img src="https://github.com/didi/xiaoju-survey/assets/16012672/508ce30f-0ae8-4f5f-84a7-e96de8238a7f" width="700" />
|
<img src="https://github.com/didi/xiaoju-survey/assets/16012672/508ce30f-0ae8-4f5f-84a7-e96de8238a7f" width="700" />
|
||||||
|
|
||||||
1. For more comprehensive features, please refer to the [documentation](https://xiaojusurvey.didi.cn/docs/next/document/%E4%BA%A7%E5%93%81%E6%89%8B%E5%86%8C/%E5%8A%9F%E8%83%BD%E4%BB%8B%E7%BB%8D/%E5%9F%BA%E7%A1%80%E6%B5%81%E7%A8%8B).
|
_**(Both individual and enterprise users can quickly build survey solutions specific to their fields.)**_
|
||||||
|
|
||||||
2. Both individual and enterprise users can quickly build survey solutions specific to their fields.
|
|
||||||
|
|
||||||
# Technology
|
# Technology
|
||||||
|
|
||||||
Web: Vue3 + ElementPlus; Multi-end rendering for C-end (planning).
|
Web: Vue3 + ElementPlus; Multi-end rendering for C-end (planning).
|
||||||
|
|
||||||
Server: NestJS + MongoDB; Java ([under construction](https://github.com/didi/xiaoju-survey/issues/306)).
|
Server: Nestjs + MongoDB; Java ([under construction](https://github.com/didi/xiaoju-survey/issues/306)).
|
||||||
|
|
||||||
Online Platform: (under construction).
|
|
||||||
|
|
||||||
Intelligent Foundation: (planning).
|
Intelligent Foundation: (planning).
|
||||||
|
|
||||||
@ -207,18 +191,22 @@ Create and publish a questionnaire.
|
|||||||
|
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
|
||||||
## Star
|
|
||||||
|
|
||||||
Open source is not easy. If this project helps you, please star it ❤️❤️❤️. Your support is our greatest motivation.
|
|
||||||
|
|
||||||
[![Star History Chart](https://api.star-history.com/svg?repos=didi/xiaoju-survey&type=Date)](https://star-history.com/#didi/xiaoju-survey&Date)
|
|
||||||
|
|
||||||
## WeChat Group
|
## WeChat Group
|
||||||
|
|
||||||
The official group will release the latest project news, construction plans, and community activities. Any questions and cooperation can contact the assistant:
|
The official group will release the latest project news, construction plans, and community activities. Any questions and cooperation can contact the assistant:
|
||||||
|
|
||||||
<img src="https://img-hxy021.didistatic.com/static/starimg/img/KXKvc7sjHz1700061188156.png" width="200" />
|
<img src="https://img-hxy021.didistatic.com/static/starimg/img/KXKvc7sjHz1700061188156.png" width="200" />
|
||||||
|
|
||||||
|
## QQ Group
|
||||||
|
|
||||||
|
The official group will release the latest project news, construction plans, and community activities. Welcome to join:
|
||||||
|
|
||||||
|
[<img src="https://img-hxy021.didistatic.com/static/starimg/img/iJUmLIHKV21700192846057.png" width="210" />](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=P61UJI_q8AzizyBLGOm-bUvzNrUnSQq-&authKey=yZFtL9biGB5yiIME3%2Bi%2Bf6XMOdTNiuf0pCIaviEEAIryySNzVy6LJ4xl7uHdEcrM&noverify=0&group_code=920623419)
|
||||||
|
|
||||||
|
## Star
|
||||||
|
|
||||||
|
Open source is not easy. If this project helps you, please star it ❤️❤️❤️. Your support is our greatest motivation.
|
||||||
|
|
||||||
## Feedback
|
## Feedback
|
||||||
|
|
||||||
If you use this project, please leave feedback:[I'm using](https://github.com/didi/xiaoju-survey/issues/64), Your support is our greatest.
|
If you use this project, please leave feedback:[I'm using](https://github.com/didi/xiaoju-survey/issues/64), Your support is our greatest.
|
||||||
|
@ -15,7 +15,7 @@ services:
|
|||||||
- xiaoju-survey
|
- xiaoju-survey
|
||||||
|
|
||||||
xiaoju-survey:
|
xiaoju-survey:
|
||||||
image: "xiaojusurvey/xiaoju-survey:1.3.0-slim" # 最新版本:https://hub.docker.com/r/xiaojusurvey/xiaoju-survey/tags
|
image: "xiaojusurvey/xiaoju-survey:1.1.6-slim" # 最新版本:https://hub.docker.com/r/xiaojusurvey/xiaoju-survey/tags
|
||||||
container_name: xiaoju-survey
|
container_name: xiaoju-survey
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
XIAOJU_SURVEY_MONGO_DB_NAME=xiaojuSurvey
|
XIAOJU_SURVEY_MONGO_DB_NAME=xiaojuSurvey
|
||||||
XIAOJU_SURVEY_MONGO_URL=
|
XIAOJU_SURVEY_MONGO_URL= # mongodb://127.0.0.1:27017 # 建议设置强密码
|
||||||
XIAOJU_SURVEY_MONGO_AUTH_SOURCE=admin
|
XIAOJU_SURVEY_MONGO_AUTH_SOURCE= # admin
|
||||||
|
|
||||||
XIAOJU_SURVEY_REDIS_HOST=
|
XIAOJU_SURVEY_REDIS_HOST=
|
||||||
XIAOJU_SURVEY_REDIS_PORT=
|
XIAOJU_SURVEY_REDIS_PORT=
|
||||||
@ -9,7 +9,7 @@ XIAOJU_SURVEY_REDIS_PASSWORD=
|
|||||||
XIAOJU_SURVEY_REDIS_DB=
|
XIAOJU_SURVEY_REDIS_DB=
|
||||||
|
|
||||||
|
|
||||||
XIAOJU_SURVEY_RESPONSE_AES_ENCRYPT_SECRET_KEY=dataAesEncryptSecretKey
|
XIAOJU_SURVEY_RESPONSE_AES_ENCRYPT_SECRET_KEY= # dataAesEncryptSecretKey
|
||||||
XIAOJU_SURVEY_HTTP_DATA_ENCRYPT_TYPE=rsa
|
XIAOJU_SURVEY_HTTP_DATA_ENCRYPT_TYPE=rsa
|
||||||
|
|
||||||
XIAOJU_SURVEY_JWT_SECRET=xiaojuSurveyJwtSecret
|
XIAOJU_SURVEY_JWT_SECRET=xiaojuSurveyJwtSecret
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
XIAOJU_SURVEY_MONGO_DB_NAME=xiaojuSurvey
|
|
||||||
XIAOJU_SURVEY_MONGO_URL=mongodb://127.0.0.1:27017
|
|
||||||
XIAOJU_SURVEY_MONGO_AUTH_SOURCE=admin
|
|
||||||
|
|
||||||
XIAOJU_SURVEY_REDIS_HOST=
|
|
||||||
XIAOJU_SURVEY_REDIS_PORT=
|
|
||||||
XIAOJU_SURVEY_REDIS_USERNAME=
|
|
||||||
XIAOJU_SURVEY_REDIS_PASSWORD=
|
|
||||||
XIAOJU_SURVEY_REDIS_DB=
|
|
||||||
|
|
||||||
|
|
||||||
XIAOJU_SURVEY_RESPONSE_AES_ENCRYPT_SECRET_KEY=dataAesEncryptSecretKey
|
|
||||||
XIAOJU_SURVEY_HTTP_DATA_ENCRYPT_TYPE=rsa
|
|
||||||
|
|
||||||
XIAOJU_SURVEY_JWT_SECRET=xiaojuSurveyJwtSecret
|
|
||||||
XIAOJU_SURVEY_JWT_EXPIRES_IN=8h
|
|
||||||
|
|
||||||
XIAOJU_SURVEY_LOGGER_FILENAME=./logs/app.log
|
|
||||||
|
|
||||||
XIAOJU_SURVEY_REPORT=true
|
|
@ -1,17 +0,0 @@
|
|||||||
XIAOJU_SURVEY_MONGO_DB_NAME=xiaojuSurvey
|
|
||||||
XIAOJU_SURVEY_MONGO_URL=
|
|
||||||
XIAOJU_SURVEY_MONGO_AUTH_SOURCE=admin
|
|
||||||
|
|
||||||
XIAOJU_SURVEY_REDIS_HOST=
|
|
||||||
XIAOJU_SURVEY_REDIS_PORT=
|
|
||||||
XIAOJU_SURVEY_REDIS_USERNAME=
|
|
||||||
XIAOJU_SURVEY_REDIS_PASSWORD=
|
|
||||||
XIAOJU_SURVEY_REDIS_DB=
|
|
||||||
|
|
||||||
XIAOJU_SURVEY_RESPONSE_AES_ENCRYPT_SECRET_KEY=dataAesEncryptSecretKey
|
|
||||||
XIAOJU_SURVEY_HTTP_DATA_ENCRYPT_TYPE=rsa
|
|
||||||
|
|
||||||
XIAOJU_SURVEY_JWT_SECRET=xiaojuSurveyJwtSecret
|
|
||||||
XIAOJU_SURVEY_JWT_EXPIRES_IN=8h
|
|
||||||
|
|
||||||
XIAOJU_SURVEY_LOGGER_FILENAME=./logs/app.log
|
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "xiaoju-survey-server",
|
"name": "server",
|
||||||
"version": "1.3.0",
|
"version": "0.0.1",
|
||||||
"description": "XIAOJUSURVEY的server端",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nest build",
|
"build": "nest build",
|
||||||
@ -22,7 +22,6 @@
|
|||||||
"@nestjs/common": "^10.0.0",
|
"@nestjs/common": "^10.0.0",
|
||||||
"@nestjs/config": "^3.1.1",
|
"@nestjs/config": "^3.1.1",
|
||||||
"@nestjs/core": "^10.0.0",
|
"@nestjs/core": "^10.0.0",
|
||||||
"@nestjs/microservices": "^10.4.4",
|
|
||||||
"@nestjs/platform-express": "^10.0.0",
|
"@nestjs/platform-express": "^10.0.0",
|
||||||
"@nestjs/serve-static": "^4.0.0",
|
"@nestjs/serve-static": "^4.0.0",
|
||||||
"@nestjs/swagger": "^7.3.0",
|
"@nestjs/swagger": "^7.3.0",
|
||||||
@ -49,7 +48,8 @@
|
|||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"svg-captcha": "^1.4.0",
|
"svg-captcha": "^1.4.0",
|
||||||
"typeorm": "^0.3.19"
|
"typeorm": "^0.3.19",
|
||||||
|
"xss": "^1.0.15"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/cli": "^10.0.0",
|
"@nestjs/cli": "^10.0.0",
|
||||||
@ -76,7 +76,7 @@
|
|||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
"redis-memory-server": "^0.11.0",
|
"redis-memory-server": "^0.11.0",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"supertest": "^7.0.0",
|
"supertest": "^6.3.3",
|
||||||
"ts-jest": "^29.1.0",
|
"ts-jest": "^29.1.0",
|
||||||
"ts-loader": "^9.4.3",
|
"ts-loader": "^9.4.3",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
@ -95,9 +95,7 @@
|
|||||||
"^.+\\.(t|j)s$": "ts-jest"
|
"^.+\\.(t|j)s$": "ts-jest"
|
||||||
},
|
},
|
||||||
"collectCoverageFrom": [
|
"collectCoverageFrom": [
|
||||||
"**/*.(t|j)s",
|
"**/*.(t|j)s"
|
||||||
"!**/*.module.ts",
|
|
||||||
"!**/upgrade.*.ts"
|
|
||||||
],
|
],
|
||||||
"coverageDirectory": "../coverage",
|
"coverageDirectory": "../coverage",
|
||||||
"testEnvironment": "node",
|
"testEnvironment": "node",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { MongoMemoryServer } from 'mongodb-memory-server';
|
import { MongoMemoryServer } from 'mongodb-memory-server';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
// import { RedisMemoryServer } from 'redis-memory-server';
|
import { RedisMemoryServer } from 'redis-memory-server';
|
||||||
|
|
||||||
async function startServerAndRunScript() {
|
async function startServerAndRunScript() {
|
||||||
// 启动 MongoDB 内存服务器
|
// 启动 MongoDB 内存服务器
|
||||||
@ -9,17 +9,17 @@ async function startServerAndRunScript() {
|
|||||||
|
|
||||||
console.log('MongoDB Memory Server started:', mongoUri);
|
console.log('MongoDB Memory Server started:', mongoUri);
|
||||||
|
|
||||||
// const redisServer = new RedisMemoryServer();
|
const redisServer = new RedisMemoryServer();
|
||||||
// const redisHost = await redisServer.getHost();
|
const redisHost = await redisServer.getHost();
|
||||||
// const redisPort = await redisServer.getPort();
|
const redisPort = await redisServer.getPort();
|
||||||
|
|
||||||
// 通过 spawn 运行另一个脚本,并传递 MongoDB 连接 URL 作为环境变量
|
// 通过 spawn 运行另一个脚本,并传递 MongoDB 连接 URL 作为环境变量
|
||||||
const tsnode = spawn(
|
const tsnode = spawn(
|
||||||
'cross-env',
|
'cross-env',
|
||||||
[
|
[
|
||||||
`XIAOJU_SURVEY_MONGO_URL=${mongoUri}`,
|
`XIAOJU_SURVEY_MONGO_URL=${mongoUri}`,
|
||||||
// `XIAOJU_SURVEY_REDIS_HOST=${redisHost}`,
|
`XIAOJU_SURVEY_REDIS_HOST=${redisHost}`,
|
||||||
// `XIAOJU_SURVEY_REDIS_PORT=${redisPort}`,
|
`XIAOJU_SURVEY_REDIS_PORT=${redisPort}`,
|
||||||
'NODE_ENV=development',
|
'NODE_ENV=development',
|
||||||
'SERVER_ENV=local',
|
'SERVER_ENV=local',
|
||||||
'npm',
|
'npm',
|
||||||
@ -42,7 +42,7 @@ async function startServerAndRunScript() {
|
|||||||
tsnode.on('close', async (code) => {
|
tsnode.on('close', async (code) => {
|
||||||
console.log(`Nodemon process exited with code ${code}`);
|
console.log(`Nodemon process exited with code ${code}`);
|
||||||
await mongod.stop(); // 停止 MongoDB 内存服务器
|
await mongod.stop(); // 停止 MongoDB 内存服务器
|
||||||
// await redisServer.stop();
|
await redisServer.stop();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,6 @@ import { AuthModule } from './modules/auth/auth.module';
|
|||||||
import { MessageModule } from './modules/message/message.module';
|
import { MessageModule } from './modules/message/message.module';
|
||||||
import { FileModule } from './modules/file/file.module';
|
import { FileModule } from './modules/file/file.module';
|
||||||
import { WorkspaceModule } from './modules/workspace/workspace.module';
|
import { WorkspaceModule } from './modules/workspace/workspace.module';
|
||||||
import { UpgradeModule } from './modules/upgrade/upgrade.module';
|
|
||||||
|
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
|
||||||
@ -36,21 +35,18 @@ import { MessagePushingLog } from './models/messagePushingLog.entity';
|
|||||||
import { WorkspaceMember } from './models/workspaceMember.entity';
|
import { WorkspaceMember } from './models/workspaceMember.entity';
|
||||||
import { Workspace } from './models/workspace.entity';
|
import { Workspace } from './models/workspace.entity';
|
||||||
import { Collaborator } from './models/collaborator.entity';
|
import { Collaborator } from './models/collaborator.entity';
|
||||||
import { DownloadTask } from './models/downloadTask.entity';
|
|
||||||
import { Session } from './models/session.entity';
|
|
||||||
|
|
||||||
import { LoggerProvider } from './logger/logger.provider';
|
import { LoggerProvider } from './logger/logger.provider';
|
||||||
import { PluginManagerProvider } from './securityPlugin/pluginManager.provider';
|
import { PluginManagerProvider } from './securityPlugin/pluginManager.provider';
|
||||||
import { LogRequestMiddleware } from './middlewares/logRequest.middleware';
|
import { LogRequestMiddleware } from './middlewares/logRequest.middleware';
|
||||||
import { PluginManager } from './securityPlugin/pluginManager';
|
import { XiaojuSurveyPluginManager } from './securityPlugin/pluginManager';
|
||||||
import { Logger } from './logger';
|
import { XiaojuSurveyLogger } from './logger';
|
||||||
|
import { DownloadTask } from './models/downloadTask.entity';
|
||||||
|
import { Session } from './models/session.entity';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot({
|
ConfigModule.forRoot({}),
|
||||||
envFilePath: `.env.${process.env.NODE_ENV}`, // 根据 NODE_ENV 动态加载对应的 .env 文件
|
|
||||||
isGlobal: true, // 使配置模块在应用的任何地方可用
|
|
||||||
}),
|
|
||||||
TypeOrmModule.forRootAsync({
|
TypeOrmModule.forRootAsync({
|
||||||
imports: [ConfigModule],
|
imports: [ConfigModule],
|
||||||
inject: [ConfigService],
|
inject: [ConfigService],
|
||||||
@ -108,7 +104,6 @@ import { Logger } from './logger';
|
|||||||
MessageModule,
|
MessageModule,
|
||||||
FileModule,
|
FileModule,
|
||||||
WorkspaceModule,
|
WorkspaceModule,
|
||||||
UpgradeModule,
|
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [
|
providers: [
|
||||||
@ -123,7 +118,7 @@ import { Logger } from './logger';
|
|||||||
export class AppModule {
|
export class AppModule {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
private readonly pluginManager: PluginManager,
|
private readonly pluginManager: XiaojuSurveyPluginManager,
|
||||||
) {}
|
) {}
|
||||||
configure(consumer: MiddlewareConsumer) {
|
configure(consumer: MiddlewareConsumer) {
|
||||||
consumer.apply(LogRequestMiddleware).forRoutes('*');
|
consumer.apply(LogRequestMiddleware).forRoutes('*');
|
||||||
@ -137,7 +132,7 @@ export class AppModule {
|
|||||||
),
|
),
|
||||||
new SurveyUtilPlugin(),
|
new SurveyUtilPlugin(),
|
||||||
);
|
);
|
||||||
Logger.init({
|
XiaojuSurveyLogger.init({
|
||||||
filename: this.configService.get<string>('XIAOJU_SURVEY_LOGGER_FILENAME'),
|
filename: this.configService.get<string>('XIAOJU_SURVEY_LOGGER_FILENAME'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
21
server/src/config/index.ts
Normal file
21
server/src/config/index.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
const mongo = {
|
||||||
|
url: process.env.XIAOJU_SURVEY_MONGO_URL || 'mongodb://localhost:27017',
|
||||||
|
dbName: process.env.XIAOJU_SURVER_MONGO_DBNAME || 'xiaojuSurvey',
|
||||||
|
};
|
||||||
|
|
||||||
|
const session = {
|
||||||
|
expireTime:
|
||||||
|
parseInt(process.env.XIAOJU_SURVEY_JWT_EXPIRES_IN) || 8 * 3600 * 1000,
|
||||||
|
};
|
||||||
|
|
||||||
|
const encrypt = {
|
||||||
|
type: process.env.XIAOJU_SURVEY_ENCRYPT_TYPE || 'aes',
|
||||||
|
aesCodelength: parseInt(process.env.XIAOJU_SURVEY_ENCRYPT_TYPE_LEN) || 10, //aes密钥长度
|
||||||
|
};
|
||||||
|
|
||||||
|
const jwt = {
|
||||||
|
secret: process.env.XIAOJU_SURVEY_JWT_SECRET || 'xiaojuSurveyJwtSecret',
|
||||||
|
expiresIn: process.env.XIAOJU_SURVEY_JWT_EXPIRES_IN || '8h',
|
||||||
|
};
|
||||||
|
|
||||||
|
export { mongo, session, encrypt, jwt };
|
@ -1,6 +0,0 @@
|
|||||||
export enum DOWNLOAD_TASK_STATUS {
|
|
||||||
WAITING = 'waiting', // 排队中
|
|
||||||
COMPUTING = 'computing', // 计算中
|
|
||||||
SUCCEED = 'succeed', // 导出成功
|
|
||||||
FAILED = 'failed', // 导出失败
|
|
||||||
}
|
|
@ -21,7 +21,6 @@ export enum EXCEPTION_CODE {
|
|||||||
RESPONSE_OVER_LIMIT = 9003, // 超出限制
|
RESPONSE_OVER_LIMIT = 9003, // 超出限制
|
||||||
RESPONSE_SCHEMA_REMOVED = 9004, // 问卷已删除
|
RESPONSE_SCHEMA_REMOVED = 9004, // 问卷已删除
|
||||||
RESPONSE_DATA_DECRYPT_ERROR = 9005, // 问卷已删除
|
RESPONSE_DATA_DECRYPT_ERROR = 9005, // 问卷已删除
|
||||||
RESPONSE_PAUSING = 9006, // 问卷已暂停
|
|
||||||
|
|
||||||
UPLOAD_FILE_ERROR = 5001, // 上传文件错误
|
UPLOAD_FILE_ERROR = 5001, // 上传文件错误
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
// 状态类型
|
// 状态类型
|
||||||
export enum RECORD_STATUS {
|
export enum RECORD_STATUS {
|
||||||
NEW = 'new', // 新建 | 未发布
|
NEW = 'new', // 新建
|
||||||
PUBLISHED = 'published', // 发布
|
|
||||||
EDITING = 'editing', // 编辑
|
EDITING = 'editing', // 编辑
|
||||||
FINISHED = 'finished', // 已结束
|
|
||||||
REMOVED = 'removed',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const enum RECORD_SUB_STATUS {
|
|
||||||
DEFAULT = '', // 默认
|
|
||||||
PAUSING = 'pausing', // 暂停
|
PAUSING = 'pausing', // 暂停
|
||||||
|
PUBLISHED = 'published', // 发布
|
||||||
|
REMOVED = 'removed', // 删除
|
||||||
|
FORCE_REMOVED = 'forceRemoved', // 从回收站删除
|
||||||
|
COMOPUTETING = 'computing', // 计算中
|
||||||
|
FINISHED = 'finished', // 已完成
|
||||||
|
ERROR = 'error', // 错误
|
||||||
}
|
}
|
||||||
|
|
||||||
// 历史类型
|
// 历史类型
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
export enum SESSION_STATUS {
|
|
||||||
ACTIVATED = 'activated',
|
|
||||||
DEACTIVATED = 'deactivated',
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
import { ExecutionContext } from '@nestjs/common';
|
|
||||||
import { Reflector } from '@nestjs/core';
|
|
||||||
import { SessionService } from 'src/modules/survey/services/session.service';
|
|
||||||
import { SessionGuard } from '../session.guard';
|
|
||||||
import { NoPermissionException } from 'src/exceptions/noPermissionException';
|
|
||||||
|
|
||||||
describe('SessionGuard', () => {
|
|
||||||
let sessionGuard: SessionGuard;
|
|
||||||
let reflector: Reflector;
|
|
||||||
let sessionService: SessionService;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
reflector = new Reflector();
|
|
||||||
sessionService = {
|
|
||||||
findOne: jest.fn(),
|
|
||||||
} as unknown as SessionService;
|
|
||||||
sessionGuard = new SessionGuard(reflector, sessionService);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return true when sessionId exists and sessionService returns sessionInfo', async () => {
|
|
||||||
const mockSessionId = '12345';
|
|
||||||
const mockSessionInfo = { id: mockSessionId, name: 'test session' };
|
|
||||||
|
|
||||||
const context = {
|
|
||||||
switchToHttp: jest.fn().mockReturnThis(),
|
|
||||||
getRequest: jest.fn().mockReturnValue({
|
|
||||||
sessionId: mockSessionId,
|
|
||||||
}),
|
|
||||||
getHandler: jest.fn(),
|
|
||||||
} as unknown as ExecutionContext;
|
|
||||||
|
|
||||||
jest.spyOn(reflector, 'get').mockReturnValue('sessionId');
|
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(sessionService, 'findOne')
|
|
||||||
.mockResolvedValue(mockSessionInfo as any);
|
|
||||||
|
|
||||||
const result = await sessionGuard.canActivate(context);
|
|
||||||
|
|
||||||
const request = context.switchToHttp().getRequest();
|
|
||||||
|
|
||||||
expect(result).toBe(true);
|
|
||||||
expect(reflector.get).toHaveBeenCalledWith(
|
|
||||||
'sessionId',
|
|
||||||
context.getHandler(),
|
|
||||||
);
|
|
||||||
expect(sessionService.findOne).toHaveBeenCalledWith(mockSessionId);
|
|
||||||
expect(request.sessionInfo).toEqual(mockSessionInfo);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw NoPermissionException when sessionId is missing', async () => {
|
|
||||||
const context = {
|
|
||||||
switchToHttp: jest.fn().mockReturnThis(),
|
|
||||||
getRequest: jest.fn().mockReturnValue({}),
|
|
||||||
getHandler: jest.fn(),
|
|
||||||
} as unknown as ExecutionContext;
|
|
||||||
|
|
||||||
jest.spyOn(reflector, 'get').mockReturnValue('sessionId');
|
|
||||||
|
|
||||||
await expect(sessionGuard.canActivate(context)).rejects.toThrow(
|
|
||||||
NoPermissionException,
|
|
||||||
);
|
|
||||||
expect(reflector.get).toHaveBeenCalledWith(
|
|
||||||
'sessionId',
|
|
||||||
context.getHandler(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
@ -2,16 +2,25 @@ import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
|||||||
import { Reflector } from '@nestjs/core';
|
import { Reflector } from '@nestjs/core';
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
import { NoPermissionException } from 'src/exceptions/noPermissionException';
|
import { NoPermissionException } from 'src/exceptions/noPermissionException';
|
||||||
|
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
|
||||||
import { SessionService } from 'src/modules/survey/services/session.service';
|
import { SessionService } from 'src/modules/survey/services/session.service';
|
||||||
|
import { SurveyMetaService } from 'src/modules/survey/services/surveyMeta.service';
|
||||||
|
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
|
||||||
|
import { CollaboratorService } from 'src/modules/survey/services/collaborator.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SessionGuard implements CanActivate {
|
export class SessionGuard implements CanActivate {
|
||||||
constructor(
|
constructor(
|
||||||
private reflector: Reflector,
|
private reflector: Reflector,
|
||||||
private readonly sessionService: SessionService,
|
private readonly sessionService: SessionService,
|
||||||
|
private readonly surveyMetaService: SurveyMetaService,
|
||||||
|
private readonly workspaceMemberService: WorkspaceMemberService,
|
||||||
|
private readonly collaboratorService: CollaboratorService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
const request = context.switchToHttp().getRequest();
|
const request = context.switchToHttp().getRequest();
|
||||||
|
const user = request.user;
|
||||||
const sessionIdKey = this.reflector.get<string>(
|
const sessionIdKey = this.reflector.get<string>(
|
||||||
'sessionId',
|
'sessionId',
|
||||||
context.getHandler(),
|
context.getHandler(),
|
||||||
@ -22,9 +31,64 @@ export class SessionGuard implements CanActivate {
|
|||||||
if (!sessionId) {
|
if (!sessionId) {
|
||||||
throw new NoPermissionException('没有权限');
|
throw new NoPermissionException('没有权限');
|
||||||
}
|
}
|
||||||
const sessionInfo = await this.sessionService.findOne(sessionId);
|
|
||||||
request.sessionInfo = sessionInfo;
|
const saveSession = await this.sessionService.findOne(sessionId);
|
||||||
request.surveyId = sessionInfo.surveyId;
|
|
||||||
|
request.saveSession = saveSession;
|
||||||
|
|
||||||
|
const surveyId = saveSession.surveyId;
|
||||||
|
|
||||||
|
const surveyMeta = await this.surveyMetaService.getSurveyById({ surveyId });
|
||||||
|
|
||||||
|
if (!surveyMeta) {
|
||||||
|
throw new SurveyNotFoundException('问卷不存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
request.surveyMeta = surveyMeta;
|
||||||
|
|
||||||
|
// 兼容老的问卷没有ownerId
|
||||||
|
if (
|
||||||
|
surveyMeta.ownerId === user._id.toString() ||
|
||||||
|
surveyMeta.owner === user.username
|
||||||
|
) {
|
||||||
|
// 问卷的owner,可以访问和操作问卷
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (surveyMeta.workspaceId) {
|
||||||
|
const memberInfo = await this.workspaceMemberService.findOne({
|
||||||
|
workspaceId: surveyMeta.workspaceId,
|
||||||
|
userId: user._id.toString(),
|
||||||
|
});
|
||||||
|
if (!memberInfo) {
|
||||||
|
throw new NoPermissionException('没有权限');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const permissions = this.reflector.get<string[]>(
|
||||||
|
'surveyPermission',
|
||||||
|
context.getHandler(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!Array.isArray(permissions) || permissions.length === 0) {
|
||||||
|
throw new NoPermissionException('没有权限');
|
||||||
|
}
|
||||||
|
|
||||||
|
const info = await this.collaboratorService.getCollaborator({
|
||||||
|
surveyId,
|
||||||
|
userId: user._id.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!info) {
|
||||||
|
throw new NoPermissionException('没有权限');
|
||||||
|
}
|
||||||
|
request.collaborator = info;
|
||||||
|
if (
|
||||||
|
permissions.some((permission) => info.permissions.includes(permission))
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
throw new NoPermissionException('没有权限');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,7 @@ export interface DataItem {
|
|||||||
rangeConfig?: any;
|
rangeConfig?: any;
|
||||||
starStyle?: string;
|
starStyle?: string;
|
||||||
innerType?: string;
|
innerType?: string;
|
||||||
|
quotaNoDisplay?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Option {
|
export interface Option {
|
||||||
@ -69,6 +70,7 @@ export interface Option {
|
|||||||
othersKey?: string;
|
othersKey?: string;
|
||||||
placeholderDesc: string;
|
placeholderDesc: string;
|
||||||
hash: string;
|
hash: string;
|
||||||
|
quota?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DataConf {
|
export interface DataConf {
|
||||||
@ -112,7 +114,7 @@ export enum MemberType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseConf {
|
export interface BaseConf {
|
||||||
beginTime: string;
|
begTime: string;
|
||||||
endTime: string;
|
endTime: string;
|
||||||
answerBegTime: string;
|
answerBegTime: string;
|
||||||
answerEndTime: string;
|
answerEndTime: string;
|
||||||
@ -135,17 +137,6 @@ export interface BaseConf {
|
|||||||
export interface SkinConf {
|
export interface SkinConf {
|
||||||
skinColor: string;
|
skinColor: string;
|
||||||
inputBgColor: string;
|
inputBgColor: string;
|
||||||
backgroundConf: {
|
|
||||||
color: string;
|
|
||||||
type: string;
|
|
||||||
image: string;
|
|
||||||
};
|
|
||||||
contentConf: {
|
|
||||||
opacity: number;
|
|
||||||
};
|
|
||||||
themeConf: {
|
|
||||||
color: string;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BottomConf {
|
export interface BottomConf {
|
||||||
|
@ -1,18 +1,15 @@
|
|||||||
import * as log4js from 'log4js';
|
import * as log4js from 'log4js';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Injectable, Scope, Inject } from '@nestjs/common';
|
import { Injectable, Scope } from '@nestjs/common';
|
||||||
import { CONTEXT, RequestContext } from '@nestjs/microservices';
|
|
||||||
|
|
||||||
const log4jsLogger = log4js.getLogger();
|
const log4jsLogger = log4js.getLogger();
|
||||||
|
|
||||||
@Injectable({ scope: Scope.REQUEST })
|
@Injectable({ scope: Scope.REQUEST })
|
||||||
export class Logger {
|
export class XiaojuSurveyLogger {
|
||||||
private static inited = false;
|
private static inited = false;
|
||||||
|
private traceId: string;
|
||||||
constructor(@Inject(CONTEXT) private readonly ctx: RequestContext) {}
|
|
||||||
|
|
||||||
static init(config: { filename: string }) {
|
static init(config: { filename: string }) {
|
||||||
if (Logger.inited) {
|
if (XiaojuSurveyLogger.inited) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log4js.configure({
|
log4js.configure({
|
||||||
@ -33,21 +30,23 @@ export class Logger {
|
|||||||
default: { appenders: ['app'], level: 'trace' },
|
default: { appenders: ['app'], level: 'trace' },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
Logger.inited = true;
|
XiaojuSurveyLogger.inited = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_log(message, options: { dltag?: string; level: string }) {
|
_log(message, options: { dltag?: string; level: string }) {
|
||||||
const datetime = moment().format('YYYY-MM-DD HH:mm:ss.SSS');
|
const datetime = moment().format('YYYY-MM-DD HH:mm:ss.SSS');
|
||||||
const level = options?.level;
|
const level = options?.level;
|
||||||
const dltag = options?.dltag ? `${options.dltag}||` : '';
|
const dltag = options?.dltag ? `${options.dltag}||` : '';
|
||||||
const traceIdStr = this.ctx?.['traceId']
|
const traceIdStr = this.traceId ? `traceid=${this.traceId}||` : '';
|
||||||
? `traceid=${this.ctx?.['traceId']}||`
|
|
||||||
: '';
|
|
||||||
return log4jsLogger[level](
|
return log4jsLogger[level](
|
||||||
`[${datetime}][${level.toUpperCase()}]${dltag}${traceIdStr}${message}`,
|
`[${datetime}][${level.toUpperCase()}]${dltag}${traceIdStr}${message}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTraceId(traceId: string) {
|
||||||
|
this.traceId = traceId;
|
||||||
|
}
|
||||||
|
|
||||||
info(message, options?: { dltag?: string }) {
|
info(message, options?: { dltag?: string }) {
|
||||||
return this._log(message, { ...options, level: 'info' });
|
return this._log(message, { ...options, level: 'info' });
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Provider } from '@nestjs/common';
|
import { Provider } from '@nestjs/common';
|
||||||
|
|
||||||
import { Logger } from './index';
|
import { XiaojuSurveyLogger } from './index';
|
||||||
|
|
||||||
export const LoggerProvider: Provider = {
|
export const LoggerProvider: Provider = {
|
||||||
provide: Logger,
|
provide: XiaojuSurveyLogger,
|
||||||
useClass: Logger,
|
useClass: XiaojuSurveyLogger,
|
||||||
};
|
};
|
||||||
|
@ -20,9 +20,7 @@ export const genTraceId = ({ ip }) => {
|
|||||||
} else {
|
} else {
|
||||||
ipArr = ip
|
ipArr = ip
|
||||||
.split('.')
|
.split('.')
|
||||||
.map((item) =>
|
.map((item) => parseInt(item).toString(16).padStart(2, '0'));
|
||||||
item ? parseInt(item).toString(16).padStart(2, '0') : '',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${ipArr.join('')}${Date.now().toString()}${getCountStr()}${process.pid.toString().slice(-5)}`;
|
return `${ipArr.join('')}${Date.now().toString()}${getCountStr()}${process.pid.toString().slice(-5)}`;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
||||||
import './report';
|
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
|
// logger.middleware.ts
|
||||||
import { Injectable, NestMiddleware } from '@nestjs/common';
|
import { Injectable, NestMiddleware } from '@nestjs/common';
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { Logger } from '../logger/index'; // 替换为你实际的logger路径
|
import { XiaojuSurveyLogger } from '../logger/index'; // 替换为你实际的logger路径
|
||||||
import { genTraceId } from '../logger/util';
|
import { genTraceId } from '../logger/util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LogRequestMiddleware implements NestMiddleware {
|
export class LogRequestMiddleware implements NestMiddleware {
|
||||||
constructor(private readonly logger: Logger) {}
|
constructor(private readonly logger: XiaojuSurveyLogger) {}
|
||||||
|
|
||||||
use(req: Request, res: Response, next: NextFunction) {
|
use(req: Request, res: Response, next: NextFunction) {
|
||||||
const { method, originalUrl, ip } = req;
|
const { method, originalUrl, ip } = req;
|
||||||
const userAgent = req.get('user-agent') || '';
|
const userAgent = req.get('user-agent') || '';
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
const traceId = genTraceId({ ip });
|
const traceId = genTraceId({ ip });
|
||||||
req['traceId'] = traceId;
|
this.logger.setTraceId(traceId);
|
||||||
const query = JSON.stringify(req.query);
|
const query = JSON.stringify(req.query);
|
||||||
const body = JSON.stringify(req.body);
|
const body = JSON.stringify(req.body);
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
|
30
server/src/models/__test/base.entity.spec.ts
Normal file
30
server/src/models/__test/base.entity.spec.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { BaseEntity } from '../base.entity';
|
||||||
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
|
|
||||||
|
describe('BaseEntity', () => {
|
||||||
|
let baseEntity: BaseEntity;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
baseEntity = new BaseEntity();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should initialize default info before insert', () => {
|
||||||
|
const now = Date.now();
|
||||||
|
baseEntity.initDefaultInfo();
|
||||||
|
|
||||||
|
expect(baseEntity.curStatus.status).toBe(RECORD_STATUS.NEW);
|
||||||
|
expect(baseEntity.curStatus.date).toBeCloseTo(now, -3);
|
||||||
|
expect(baseEntity.statusList).toHaveLength(1);
|
||||||
|
expect(baseEntity.statusList[0].status).toBe(RECORD_STATUS.NEW);
|
||||||
|
expect(baseEntity.statusList[0].date).toBeCloseTo(now, -3);
|
||||||
|
expect(baseEntity.createDate).toBeCloseTo(now, -3);
|
||||||
|
expect(baseEntity.updateDate).toBeCloseTo(now, -3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update updateDate before update', () => {
|
||||||
|
const now = Date.now();
|
||||||
|
baseEntity.onUpdate();
|
||||||
|
|
||||||
|
expect(baseEntity.updateDate).toBeCloseTo(now, -3);
|
||||||
|
});
|
||||||
|
});
|
@ -1,57 +0,0 @@
|
|||||||
import { SurveyMeta } from '../surveyMeta.entity';
|
|
||||||
import { RECORD_STATUS, RECORD_SUB_STATUS } from 'src/enums';
|
|
||||||
|
|
||||||
// 模拟日期
|
|
||||||
const mockDateNow = Date.now();
|
|
||||||
|
|
||||||
describe('SurveyMeta Entity', () => {
|
|
||||||
let surveyMeta: SurveyMeta;
|
|
||||||
|
|
||||||
// 在每个测试之前,初始化 SurveyMeta 实例
|
|
||||||
beforeEach(() => {
|
|
||||||
surveyMeta = new SurveyMeta();
|
|
||||||
// 模拟 Date.now() 返回固定的时间
|
|
||||||
jest.spyOn(Date, 'now').mockReturnValue(mockDateNow);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.restoreAllMocks(); // 每次测试后还原所有 mock
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set default curStatus and subStatus on insert when they are not provided', () => {
|
|
||||||
surveyMeta.initDefaultInfo();
|
|
||||||
|
|
||||||
// 验证 curStatus 是否被初始化为默认值
|
|
||||||
expect(surveyMeta.curStatus).toEqual({
|
|
||||||
status: RECORD_STATUS.NEW,
|
|
||||||
date: mockDateNow,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 验证 statusList 是否包含 curStatus
|
|
||||||
expect(surveyMeta.statusList).toEqual([
|
|
||||||
{
|
|
||||||
status: RECORD_STATUS.NEW,
|
|
||||||
date: mockDateNow,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 验证 subStatus 是否被初始化为默认值
|
|
||||||
expect(surveyMeta.subStatus).toEqual({
|
|
||||||
status: RECORD_SUB_STATUS.DEFAULT,
|
|
||||||
date: mockDateNow,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should initialize statusList if curStatus is provided but statusList is empty', () => {
|
|
||||||
surveyMeta.curStatus = null;
|
|
||||||
|
|
||||||
surveyMeta.initDefaultInfo();
|
|
||||||
|
|
||||||
expect(surveyMeta.statusList).toEqual([
|
|
||||||
{
|
|
||||||
status: RECORD_STATUS.NEW,
|
|
||||||
date: expect.any(Number),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,13 +1,43 @@
|
|||||||
import { ObjectIdColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
import { Column, ObjectIdColumn, BeforeInsert, BeforeUpdate } from 'typeorm';
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
|
import { RECORD_STATUS } from '../enums';
|
||||||
|
|
||||||
export class BaseEntity {
|
export class BaseEntity {
|
||||||
@ObjectIdColumn()
|
@ObjectIdColumn()
|
||||||
_id: ObjectId;
|
_id: ObjectId;
|
||||||
|
|
||||||
@CreateDateColumn({ type: 'timestamp', precision: 3 })
|
@Column()
|
||||||
createdAt: Date;
|
curStatus: {
|
||||||
|
status: RECORD_STATUS;
|
||||||
|
date: number;
|
||||||
|
};
|
||||||
|
|
||||||
@UpdateDateColumn({ type: 'timestamp', precision: 3 })
|
@Column()
|
||||||
updatedAt: Date;
|
statusList: Array<{
|
||||||
|
status: RECORD_STATUS;
|
||||||
|
date: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
createDate: number;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
updateDate: number;
|
||||||
|
|
||||||
|
@BeforeInsert()
|
||||||
|
initDefaultInfo() {
|
||||||
|
const now = Date.now();
|
||||||
|
if (!this.curStatus) {
|
||||||
|
const curStatus = { status: RECORD_STATUS.NEW, date: now };
|
||||||
|
this.curStatus = curStatus;
|
||||||
|
this.statusList = [curStatus];
|
||||||
|
}
|
||||||
|
this.createDate = now;
|
||||||
|
this.updateDate = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeUpdate()
|
||||||
|
onUpdate() {
|
||||||
|
this.updateDate = Date.now();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,16 +11,4 @@ export class Collaborator extends BaseEntity {
|
|||||||
|
|
||||||
@Column('jsonb')
|
@Column('jsonb')
|
||||||
permissions: Array<string>;
|
permissions: Array<string>;
|
||||||
|
|
||||||
@Column()
|
|
||||||
creator: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
creatorId: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
operator: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
operatorId: string;
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Entity, Column } from 'typeorm';
|
import { Entity, Column } from 'typeorm';
|
||||||
import { BaseEntity } from './base.entity';
|
import { BaseEntity } from './base.entity';
|
||||||
import { DOWNLOAD_TASK_STATUS } from 'src/enums/downloadTaskStatus';
|
|
||||||
|
|
||||||
@Entity({ name: 'downloadTask' })
|
@Entity({ name: 'downloadTask' })
|
||||||
export class DownloadTask extends BaseEntity {
|
export class DownloadTask extends BaseEntity {
|
||||||
@ -20,11 +19,7 @@ export class DownloadTask extends BaseEntity {
|
|||||||
|
|
||||||
// 任务创建人
|
// 任务创建人
|
||||||
@Column()
|
@Column()
|
||||||
creatorId: string;
|
ownerId: string;
|
||||||
|
|
||||||
// 任务创建人
|
|
||||||
@Column()
|
|
||||||
creator: string;
|
|
||||||
|
|
||||||
// 文件名
|
// 文件名
|
||||||
@Column()
|
@Column()
|
||||||
@ -36,13 +31,4 @@ export class DownloadTask extends BaseEntity {
|
|||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
params: string;
|
params: string;
|
||||||
|
|
||||||
@Column()
|
|
||||||
isDeleted: boolean;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
deletedAt: Date;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
status: DOWNLOAD_TASK_STATUS;
|
|
||||||
}
|
}
|
||||||
|
@ -27,16 +27,4 @@ export class MessagePushingTask extends BaseEntity {
|
|||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
ownerId: string;
|
ownerId: string;
|
||||||
|
|
||||||
@Column()
|
|
||||||
isDeleted: boolean;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
deletedAt: Date;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
operator: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
operatorId: string;
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { Entity, Column } from 'typeorm';
|
import { Entity, Column } from 'typeorm';
|
||||||
import { SurveySchemaInterface } from '../interfaces/survey';
|
import { SurveySchemaInterface } from '../interfaces/survey';
|
||||||
import { BaseEntity } from './base.entity';
|
import { BaseEntity } from './base.entity';
|
||||||
import { RECORD_STATUS, RECORD_SUB_STATUS } from '../enums';
|
|
||||||
|
|
||||||
@Entity({ name: 'surveyPublish' })
|
@Entity({ name: 'surveyPublish' })
|
||||||
export class ResponseSchema extends BaseEntity {
|
export class ResponseSchema extends BaseEntity {
|
||||||
@ -16,19 +15,4 @@ export class ResponseSchema extends BaseEntity {
|
|||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
pageId: string;
|
pageId: string;
|
||||||
|
|
||||||
@Column()
|
|
||||||
curStatus: {
|
|
||||||
status: RECORD_STATUS;
|
|
||||||
date: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
subStatus: {
|
|
||||||
status: RECORD_SUB_STATUS;
|
|
||||||
date: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
isDeleted: boolean;
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { Entity, Column, Index, ObjectIdColumn } from 'typeorm';
|
import { Entity, Column, Index, ObjectIdColumn } from 'typeorm';
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
import { BaseEntity } from './base.entity';
|
import { BaseEntity } from './base.entity';
|
||||||
import { SESSION_STATUS } from 'src/enums/surveySessionStatus';
|
|
||||||
|
|
||||||
@Entity({ name: 'session' })
|
@Entity({ name: 'session' })
|
||||||
export class Session extends BaseEntity {
|
export class Session extends BaseEntity {
|
||||||
@ -16,7 +15,4 @@ export class Session extends BaseEntity {
|
|||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|
||||||
@Column()
|
|
||||||
status: SESSION_STATUS;
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Entity, Column, BeforeInsert } from 'typeorm';
|
import { Entity, Column } from 'typeorm';
|
||||||
import { BaseEntity } from './base.entity';
|
import { BaseEntity } from './base.entity';
|
||||||
import { RECORD_STATUS, RECORD_SUB_STATUS } from '../enums';
|
|
||||||
|
|
||||||
@Entity({ name: 'surveyMeta' })
|
@Entity({ name: 'surveyMeta' })
|
||||||
export class SurveyMeta extends BaseEntity {
|
export class SurveyMeta extends BaseEntity {
|
||||||
@ -19,9 +18,6 @@ export class SurveyMeta extends BaseEntity {
|
|||||||
@Column()
|
@Column()
|
||||||
creator: string;
|
creator: string;
|
||||||
|
|
||||||
@Column()
|
|
||||||
creatorId: string;
|
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
owner: string;
|
owner: string;
|
||||||
|
|
||||||
@ -36,48 +32,4 @@ export class SurveyMeta extends BaseEntity {
|
|||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
|
|
||||||
@Column()
|
|
||||||
curStatus: {
|
|
||||||
status: RECORD_STATUS;
|
|
||||||
date: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
subStatus: {
|
|
||||||
status: RECORD_SUB_STATUS;
|
|
||||||
date: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
statusList: Array<{
|
|
||||||
status: RECORD_STATUS | RECORD_SUB_STATUS;
|
|
||||||
date: number;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
operator: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
operatorId: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
isDeleted: boolean;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
deletedAt: Date;
|
|
||||||
|
|
||||||
@BeforeInsert()
|
|
||||||
initDefaultInfo() {
|
|
||||||
const now = Date.now();
|
|
||||||
if (!this.curStatus) {
|
|
||||||
const curStatus = { status: RECORD_STATUS.NEW, date: now };
|
|
||||||
this.curStatus = curStatus;
|
|
||||||
this.statusList = [curStatus];
|
|
||||||
}
|
|
||||||
if (!this.subStatus) {
|
|
||||||
const subStatus = { status: RECORD_SUB_STATUS.DEFAULT, date: now };
|
|
||||||
this.subStatus = subStatus;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -27,11 +27,11 @@ export class SurveyResponse extends BaseEntity {
|
|||||||
|
|
||||||
@BeforeInsert()
|
@BeforeInsert()
|
||||||
async onDataInsert() {
|
async onDataInsert() {
|
||||||
return await pluginManager.triggerHook('encryptResponseData', this);
|
return await pluginManager.triggerHook('beforeResponseDataCreate', this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterLoad()
|
@AfterLoad()
|
||||||
async onDataLoaded() {
|
async onDataLoaded() {
|
||||||
return await pluginManager.triggerHook('decryptResponseData', this);
|
return await pluginManager.triggerHook('afterResponseDataReaded', this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,33 +3,12 @@ import { BaseEntity } from './base.entity';
|
|||||||
|
|
||||||
@Entity({ name: 'workspace' })
|
@Entity({ name: 'workspace' })
|
||||||
export class Workspace extends BaseEntity {
|
export class Workspace extends BaseEntity {
|
||||||
@Column()
|
|
||||||
creatorId: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
creator: string;
|
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
ownerId: string;
|
ownerId: string;
|
||||||
|
|
||||||
@Column()
|
|
||||||
owner: string;
|
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
description: string;
|
description: string;
|
||||||
|
|
||||||
@Column()
|
|
||||||
operator: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
operatorId: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
isDeleted: boolean;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
deletedAt: Date;
|
|
||||||
}
|
}
|
||||||
|
@ -11,16 +11,4 @@ export class WorkspaceMember extends BaseEntity {
|
|||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
role: string;
|
role: string;
|
||||||
|
|
||||||
@Column()
|
|
||||||
creator: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
creatorId: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
operator: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
operatorId: string;
|
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import { UserService } from '../services/user.service';
|
|||||||
import { User } from 'src/models/user.entity';
|
import { User } from 'src/models/user.entity';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { hash256 } from 'src/utils/hash256';
|
import { hash256 } from 'src/utils/hash256';
|
||||||
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
|
|
||||||
describe('UserService', () => {
|
describe('UserService', () => {
|
||||||
@ -144,6 +145,7 @@ describe('UserService', () => {
|
|||||||
expect(userRepository.findOne).toHaveBeenCalledWith({
|
expect(userRepository.findOne).toHaveBeenCalledWith({
|
||||||
where: {
|
where: {
|
||||||
username: username,
|
username: username,
|
||||||
|
'curStatus.status': { $ne: RECORD_STATUS.REMOVED },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(user).toEqual(userInfo);
|
expect(user).toEqual(userInfo);
|
||||||
@ -161,6 +163,7 @@ describe('UserService', () => {
|
|||||||
expect(findOneSpy).toHaveBeenCalledWith({
|
expect(findOneSpy).toHaveBeenCalledWith({
|
||||||
where: {
|
where: {
|
||||||
username: username,
|
username: username,
|
||||||
|
'curStatus.status': { $ne: RECORD_STATUS.REMOVED },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(user).toBe(null);
|
expect(user).toBe(null);
|
||||||
@ -181,6 +184,7 @@ describe('UserService', () => {
|
|||||||
expect(userRepository.findOne).toHaveBeenCalledWith({
|
expect(userRepository.findOne).toHaveBeenCalledWith({
|
||||||
where: {
|
where: {
|
||||||
_id: new ObjectId(id),
|
_id: new ObjectId(id),
|
||||||
|
'curStatus.status': { $ne: RECORD_STATUS.REMOVED },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(user).toEqual(userInfo);
|
expect(user).toEqual(userInfo);
|
||||||
@ -198,6 +202,7 @@ describe('UserService', () => {
|
|||||||
expect(findOneSpy).toHaveBeenCalledWith({
|
expect(findOneSpy).toHaveBeenCalledWith({
|
||||||
where: {
|
where: {
|
||||||
_id: new ObjectId(id),
|
_id: new ObjectId(id),
|
||||||
|
'curStatus.status': { $ne: RECORD_STATUS.REMOVED },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(user).toBe(null);
|
expect(user).toBe(null);
|
||||||
@ -206,8 +211,8 @@ describe('UserService', () => {
|
|||||||
it('should return a list of users by username', async () => {
|
it('should return a list of users by username', async () => {
|
||||||
const username = 'test';
|
const username = 'test';
|
||||||
const userList = [
|
const userList = [
|
||||||
{ _id: new ObjectId(), username: 'testUser1', createdAt: new Date() },
|
{ _id: new ObjectId(), username: 'testUser1', createDate: new Date() },
|
||||||
{ _id: new ObjectId(), username: 'testUser2', createdAt: new Date() },
|
{ _id: new ObjectId(), username: 'testUser2', createDate: new Date() },
|
||||||
];
|
];
|
||||||
|
|
||||||
jest
|
jest
|
||||||
@ -223,10 +228,11 @@ describe('UserService', () => {
|
|||||||
expect(userRepository.find).toHaveBeenCalledWith({
|
expect(userRepository.find).toHaveBeenCalledWith({
|
||||||
where: {
|
where: {
|
||||||
username: new RegExp(username),
|
username: new RegExp(username),
|
||||||
|
'curStatus.status': { $ne: RECORD_STATUS.REMOVED },
|
||||||
},
|
},
|
||||||
skip: 0,
|
skip: 0,
|
||||||
take: 10,
|
take: 10,
|
||||||
select: ['_id', 'username', 'createdAt'],
|
select: ['_id', 'username', 'createDate'],
|
||||||
});
|
});
|
||||||
expect(result).toEqual(userList);
|
expect(result).toEqual(userList);
|
||||||
});
|
});
|
||||||
@ -237,12 +243,12 @@ describe('UserService', () => {
|
|||||||
{
|
{
|
||||||
_id: new ObjectId(idList[0]),
|
_id: new ObjectId(idList[0]),
|
||||||
username: 'testUser1',
|
username: 'testUser1',
|
||||||
createdAt: new Date(),
|
createDate: new Date(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: new ObjectId(idList[1]),
|
_id: new ObjectId(idList[1]),
|
||||||
username: 'testUser2',
|
username: 'testUser2',
|
||||||
createdAt: new Date(),
|
createDate: new Date(),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -257,8 +263,9 @@ describe('UserService', () => {
|
|||||||
_id: {
|
_id: {
|
||||||
$in: idList.map((id) => new ObjectId(id)),
|
$in: idList.map((id) => new ObjectId(id)),
|
||||||
},
|
},
|
||||||
|
'curStatus.status': { $ne: RECORD_STATUS.REMOVED },
|
||||||
},
|
},
|
||||||
select: ['_id', 'username', 'createdAt'],
|
select: ['_id', 'username', 'createDate'],
|
||||||
});
|
});
|
||||||
expect(result).toEqual(userList);
|
expect(result).toEqual(userList);
|
||||||
});
|
});
|
||||||
|
@ -1,12 +1,4 @@
|
|||||||
import {
|
import { Controller, Post, Body, HttpCode, Get, Query } from '@nestjs/common';
|
||||||
Controller,
|
|
||||||
Post,
|
|
||||||
Body,
|
|
||||||
HttpCode,
|
|
||||||
Get,
|
|
||||||
Query,
|
|
||||||
Request,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { UserService } from '../services/user.service';
|
import { UserService } from '../services/user.service';
|
||||||
import { CaptchaService } from '../services/captcha.service';
|
import { CaptchaService } from '../services/captcha.service';
|
||||||
@ -195,7 +187,7 @@ export class AuthController {
|
|||||||
/**
|
/**
|
||||||
* 密码强度
|
* 密码强度
|
||||||
*/
|
*/
|
||||||
@Get('/password/strength')
|
@Get('register/password/strength')
|
||||||
@HttpCode(200)
|
@HttpCode(200)
|
||||||
async getPasswordStrength(@Query('password') password: string) {
|
async getPasswordStrength(@Query('password') password: string) {
|
||||||
const numberReg = /[0-9]/.test(password);
|
const numberReg = /[0-9]/.test(password);
|
||||||
@ -222,28 +214,4 @@ export class AuthController {
|
|||||||
data: 'Weak',
|
data: 'Weak',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('/verifyToken')
|
|
||||||
@HttpCode(200)
|
|
||||||
async verifyToken(@Request() req) {
|
|
||||||
const token = req.headers.authorization?.split(' ')[1];
|
|
||||||
if (!token) {
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
data: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await this.authService.verifyToken(token);
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
data: true,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
data: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -35,4 +35,13 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async expiredCheck(token: string) {
|
||||||
|
try {
|
||||||
|
verify(token, this.configService.get<string>('XIAOJU_SURVEY_JWT_SECRET'));
|
||||||
|
} catch (err) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import { User } from 'src/models/user.entity';
|
|||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
import { hash256 } from 'src/utils/hash256';
|
import { hash256 } from 'src/utils/hash256';
|
||||||
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -52,6 +53,9 @@ export class UserService {
|
|||||||
const user = await this.userRepository.findOne({
|
const user = await this.userRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
username: username,
|
username: username,
|
||||||
|
'curStatus.status': {
|
||||||
|
$ne: RECORD_STATUS.REMOVED,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -62,6 +66,9 @@ export class UserService {
|
|||||||
const user = await this.userRepository.findOne({
|
const user = await this.userRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
_id: new ObjectId(id),
|
_id: new ObjectId(id),
|
||||||
|
'curStatus.status': {
|
||||||
|
$ne: RECORD_STATUS.REMOVED,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -72,10 +79,13 @@ export class UserService {
|
|||||||
const list = await this.userRepository.find({
|
const list = await this.userRepository.find({
|
||||||
where: {
|
where: {
|
||||||
username: new RegExp(username),
|
username: new RegExp(username),
|
||||||
|
'curStatus.status': {
|
||||||
|
$ne: RECORD_STATUS.REMOVED,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
skip,
|
skip,
|
||||||
take,
|
take,
|
||||||
select: ['_id', 'username', 'createdAt'],
|
select: ['_id', 'username', 'createDate'],
|
||||||
});
|
});
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
@ -86,8 +96,11 @@ export class UserService {
|
|||||||
_id: {
|
_id: {
|
||||||
$in: idList.map((item) => new ObjectId(item)),
|
$in: idList.map((item) => new ObjectId(item)),
|
||||||
},
|
},
|
||||||
|
'curStatus.status': {
|
||||||
|
$ne: RECORD_STATUS.REMOVED,
|
||||||
},
|
},
|
||||||
select: ['_id', 'username', 'createdAt'],
|
},
|
||||||
|
select: ['_id', 'username', 'createDate'],
|
||||||
});
|
});
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
@ -14,17 +14,17 @@ export class FileService {
|
|||||||
configKey,
|
configKey,
|
||||||
file,
|
file,
|
||||||
pathPrefix,
|
pathPrefix,
|
||||||
filename,
|
keepOriginFilename,
|
||||||
}: {
|
}: {
|
||||||
configKey: string;
|
configKey: string;
|
||||||
file: Express.Multer.File;
|
file: Express.Multer.File;
|
||||||
pathPrefix: string;
|
pathPrefix: string;
|
||||||
filename?: string;
|
keepOriginFilename?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const handler = this.getHandler(configKey);
|
const handler = this.getHandler(configKey);
|
||||||
const { key } = await handler.upload(file, {
|
const { key } = await handler.upload(file, {
|
||||||
pathPrefix,
|
pathPrefix,
|
||||||
filename,
|
keepOriginFilename,
|
||||||
});
|
});
|
||||||
const url = await handler.getUrl(key);
|
const url = await handler.getUrl(key);
|
||||||
return {
|
return {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { join, dirname, sep } from 'path';
|
import { join, dirname } from 'path';
|
||||||
import fse from 'fs-extra';
|
import fse from 'fs-extra';
|
||||||
import { createWriteStream } from 'fs';
|
import { createWriteStream } from 'fs';
|
||||||
import { FileUploadHandler } from './uploadHandler.interface';
|
import { FileUploadHandler } from './uploadHandler.interface';
|
||||||
@ -12,20 +12,18 @@ export class LocalHandler implements FileUploadHandler {
|
|||||||
|
|
||||||
async upload(
|
async upload(
|
||||||
file: Express.Multer.File,
|
file: Express.Multer.File,
|
||||||
options?: { pathPrefix?: string; filename?: string },
|
options?: { pathPrefix?: string; keepOriginFilename?: boolean },
|
||||||
): Promise<{ key: string }> {
|
): Promise<{ key: string }> {
|
||||||
let filename;
|
let filename;
|
||||||
if (options?.filename) {
|
if (options?.keepOriginFilename) {
|
||||||
filename = file.filename;
|
filename = file.originalname;
|
||||||
} else {
|
} else {
|
||||||
filename = await generateUniqueFilename(file.originalname);
|
filename = await generateUniqueFilename(file.originalname);
|
||||||
}
|
}
|
||||||
const filePath = join(
|
const filePath = join(
|
||||||
options?.pathPrefix ? options?.pathPrefix : '',
|
options?.pathPrefix ? options?.pathPrefix : '',
|
||||||
filename,
|
filename,
|
||||||
)
|
);
|
||||||
.split(sep)
|
|
||||||
.join('/');
|
|
||||||
const physicalPath = join(this.physicalRootPath, filePath);
|
const physicalPath = join(this.physicalRootPath, filePath);
|
||||||
await fse.mkdir(dirname(physicalPath), { recursive: true });
|
await fse.mkdir(dirname(physicalPath), { recursive: true });
|
||||||
const writeStream = createWriteStream(physicalPath);
|
const writeStream = createWriteStream(physicalPath);
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
MESSAGE_PUSHING_TYPE,
|
MESSAGE_PUSHING_TYPE,
|
||||||
MESSAGE_PUSHING_HOOK,
|
MESSAGE_PUSHING_HOOK,
|
||||||
} from 'src/enums/messagePushing';
|
} from 'src/enums/messagePushing';
|
||||||
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
|
|
||||||
describe('MessagePushingTaskDto', () => {
|
describe('MessagePushingTaskDto', () => {
|
||||||
let dto: MessagePushingTaskDto;
|
let dto: MessagePushingTaskDto;
|
||||||
@ -33,9 +34,9 @@ describe('MessagePushingTaskDto', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should have a type', () => {
|
it('should have a type', () => {
|
||||||
dto.type = MESSAGE_PUSHING_TYPE.HTTP;
|
dto.type = MESSAGE_PUSHING_TYPE.HTTP; // Set your desired type here
|
||||||
expect(dto.type).toBeDefined();
|
expect(dto.type).toBeDefined();
|
||||||
expect(dto.type).toEqual(MESSAGE_PUSHING_TYPE.HTTP);
|
expect(dto.type).toEqual(MESSAGE_PUSHING_TYPE.HTTP); // Adjust based on your enum
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have a push address', () => {
|
it('should have a push address', () => {
|
||||||
@ -45,13 +46,13 @@ describe('MessagePushingTaskDto', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should have a trigger hook', () => {
|
it('should have a trigger hook', () => {
|
||||||
dto.triggerHook = MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED;
|
dto.triggerHook = MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED; // Set your desired hook here
|
||||||
expect(dto.triggerHook).toBeDefined();
|
expect(dto.triggerHook).toBeDefined();
|
||||||
expect(dto.triggerHook).toEqual(MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED);
|
expect(dto.triggerHook).toEqual(MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED); // Adjust based on your enum
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have an array of surveys', () => {
|
it('should have an array of surveys', () => {
|
||||||
dto.surveys = ['survey1', 'survey2'];
|
dto.surveys = ['survey1', 'survey2']; // Set your desired surveys here
|
||||||
expect(dto.surveys).toBeDefined();
|
expect(dto.surveys).toBeDefined();
|
||||||
expect(dto.surveys).toEqual(['survey1', 'survey2']);
|
expect(dto.surveys).toEqual(['survey1', 'survey2']);
|
||||||
});
|
});
|
||||||
@ -61,6 +62,13 @@ describe('MessagePushingTaskDto', () => {
|
|||||||
expect(dto.owner).toBeDefined();
|
expect(dto.owner).toBeDefined();
|
||||||
expect(dto.owner).toBe('test_owner');
|
expect(dto.owner).toBe('test_owner');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should have current status', () => {
|
||||||
|
dto.curStatus = { status: RECORD_STATUS.NEW, date: Date.now() };
|
||||||
|
expect(dto.curStatus).toBeDefined();
|
||||||
|
expect(dto.curStatus.status).toEqual(RECORD_STATUS.NEW);
|
||||||
|
expect(dto.curStatus.date).toBeDefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('CodeDto', () => {
|
describe('CodeDto', () => {
|
||||||
|
@ -10,6 +10,7 @@ import { MessagePushingLogService } from '../services/messagePushingLog.service'
|
|||||||
import { CreateMessagePushingTaskDto } from '../dto/createMessagePushingTask.dto';
|
import { CreateMessagePushingTaskDto } from '../dto/createMessagePushingTask.dto';
|
||||||
import { UpdateMessagePushingTaskDto } from '../dto/updateMessagePushingTask.dto';
|
import { UpdateMessagePushingTaskDto } from '../dto/updateMessagePushingTask.dto';
|
||||||
|
|
||||||
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
import { MESSAGE_PUSHING_TYPE } from 'src/enums/messagePushing';
|
import { MESSAGE_PUSHING_TYPE } from 'src/enums/messagePushing';
|
||||||
import { MESSAGE_PUSHING_HOOK } from 'src/enums/messagePushing';
|
import { MESSAGE_PUSHING_HOOK } from 'src/enums/messagePushing';
|
||||||
import { MessagePushingTask } from 'src/models/messagePushingTask.entity';
|
import { MessagePushingTask } from 'src/models/messagePushingTask.entity';
|
||||||
@ -117,12 +118,10 @@ describe('MessagePushingTaskService', () => {
|
|||||||
expect(result).toEqual(tasks);
|
expect(result).toEqual(tasks);
|
||||||
expect(repository.find).toHaveBeenCalledWith({
|
expect(repository.find).toHaveBeenCalledWith({
|
||||||
where: {
|
where: {
|
||||||
isDeleted: {
|
|
||||||
$ne: true,
|
|
||||||
},
|
|
||||||
ownerId: mockOwnerId,
|
ownerId: mockOwnerId,
|
||||||
surveys: { $all: [surveyId] },
|
surveys: { $all: [surveyId] },
|
||||||
triggerHook: hook,
|
triggerHook: hook,
|
||||||
|
'curStatus.status': { $ne: RECORD_STATUS.REMOVED },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -147,19 +146,10 @@ describe('MessagePushingTaskService', () => {
|
|||||||
where: {
|
where: {
|
||||||
ownerId: mockOwnerId,
|
ownerId: mockOwnerId,
|
||||||
_id: new ObjectId(taskId),
|
_id: new ObjectId(taskId),
|
||||||
isDeleted: {
|
'curStatus.status': { $ne: RECORD_STATUS.REMOVED },
|
||||||
$ne: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should throw an error when message pushing task is not found', async () => {
|
|
||||||
const taskId = '65afc62904d5db18534c0f78';
|
|
||||||
jest.spyOn(repository, 'findOne').mockResolvedValue(null); // 模拟未找到任务
|
|
||||||
const mockOwnerId = '66028642292c50f8b71a9eee';
|
|
||||||
|
|
||||||
await expect(service.findOne({ id: taskId, ownerId: mockOwnerId }));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('update', () => {
|
describe('update', () => {
|
||||||
@ -171,6 +161,10 @@ describe('MessagePushingTaskService', () => {
|
|||||||
pushAddress: 'http://update.example.com',
|
pushAddress: 'http://update.example.com',
|
||||||
triggerHook: MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED,
|
triggerHook: MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED,
|
||||||
surveys: ['new survey id'],
|
surveys: ['new survey id'],
|
||||||
|
curStatus: {
|
||||||
|
status: RECORD_STATUS.EDITING,
|
||||||
|
date: Date.now(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const existingTask = new MessagePushingTask();
|
const existingTask = new MessagePushingTask();
|
||||||
existingTask._id = new ObjectId(taskId);
|
existingTask._id = new ObjectId(taskId);
|
||||||
@ -196,20 +190,6 @@ describe('MessagePushingTaskService', () => {
|
|||||||
});
|
});
|
||||||
expect(repository.save).toHaveBeenCalledWith(updatedTask);
|
expect(repository.save).toHaveBeenCalledWith(updatedTask);
|
||||||
});
|
});
|
||||||
it('should throw an error if the task to be updated is not found', async () => {
|
|
||||||
const taskId = '65afc62904d5db18534c0f78';
|
|
||||||
const updateDto: UpdateMessagePushingTaskDto = { name: 'Updated Task' };
|
|
||||||
jest.spyOn(repository, 'findOne').mockResolvedValue(null); // 模拟任务未找到
|
|
||||||
const mockOwnerId = '66028642292c50f8b71a9eee';
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
service.update({
|
|
||||||
ownerId: mockOwnerId,
|
|
||||||
id: taskId,
|
|
||||||
updateData: updateDto,
|
|
||||||
}),
|
|
||||||
).rejects.toThrow(`Message pushing task with id ${taskId} not found`);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('remove', () => {
|
describe('remove', () => {
|
||||||
@ -217,52 +197,36 @@ describe('MessagePushingTaskService', () => {
|
|||||||
const taskId = '65afc62904d5db18534c0f78';
|
const taskId = '65afc62904d5db18534c0f78';
|
||||||
|
|
||||||
const updateResult = { modifiedCount: 1 };
|
const updateResult = { modifiedCount: 1 };
|
||||||
const mockOperatorId = '66028642292c50f8b71a9eee';
|
const mockOwnerId = '66028642292c50f8b71a9eee';
|
||||||
const mockOperator = 'mockOperator';
|
|
||||||
|
|
||||||
jest.spyOn(repository, 'updateOne').mockResolvedValue(updateResult);
|
jest.spyOn(repository, 'updateOne').mockResolvedValue(updateResult);
|
||||||
|
|
||||||
const result = await service.remove({
|
const result = await service.remove({
|
||||||
id: taskId,
|
id: taskId,
|
||||||
operatorId: mockOperatorId,
|
ownerId: mockOwnerId,
|
||||||
operator: mockOperator,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toEqual(updateResult);
|
expect(result).toEqual(updateResult);
|
||||||
expect(repository.updateOne).toHaveBeenCalledWith(
|
expect(repository.updateOne).toHaveBeenCalledWith(
|
||||||
{
|
{
|
||||||
|
ownerId: mockOwnerId,
|
||||||
_id: new ObjectId(taskId),
|
_id: new ObjectId(taskId),
|
||||||
|
'curStatus.status': { $ne: RECORD_STATUS.REMOVED },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
isDeleted: true,
|
curStatus: {
|
||||||
operatorId: mockOperatorId,
|
status: RECORD_STATUS.REMOVED,
|
||||||
operator: mockOperator,
|
date: expect.any(Number),
|
||||||
deletedAt: expect.any(Date),
|
},
|
||||||
|
},
|
||||||
|
$push: {
|
||||||
|
statusList: {
|
||||||
|
status: RECORD_STATUS.REMOVED,
|
||||||
|
date: expect.any(Number),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
|
||||||
});
|
|
||||||
it('should throw an error if the task to be removed is not found', async () => {
|
|
||||||
const taskId = '65afc62904d5db18534c0f78';
|
|
||||||
jest
|
|
||||||
.spyOn(repository, 'updateOne')
|
|
||||||
.mockResolvedValue({ modifiedCount: 0 }); // 模拟删除失败
|
|
||||||
const mockOperatorId = '66028642292c50f8b71a9eee';
|
|
||||||
const mockOperator = 'mockOperator';
|
|
||||||
|
|
||||||
const result = await service.remove({
|
|
||||||
id: taskId,
|
|
||||||
operatorId: mockOperatorId,
|
|
||||||
operator: mockOperator,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.modifiedCount).toBe(0);
|
|
||||||
expect(repository.updateOne).toHaveBeenCalledWith(
|
|
||||||
{
|
|
||||||
_id: new ObjectId(taskId),
|
|
||||||
},
|
},
|
||||||
expect.any(Object),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -294,35 +258,8 @@ describe('MessagePushingTaskService', () => {
|
|||||||
$push: {
|
$push: {
|
||||||
surveys: surveyId,
|
surveys: surveyId,
|
||||||
},
|
},
|
||||||
$set: {
|
|
||||||
updatedAt: expect.any(Date),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('should not add the surveyId if it already exists in the task', async () => {
|
|
||||||
const taskId = '65afc62904d5db18534c0f78';
|
|
||||||
const surveyId = '65af380475b64545e5277dd9';
|
|
||||||
const mockOwnerId = '66028642292c50f8b71a9eee';
|
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(repository, 'updateOne')
|
|
||||||
.mockResolvedValue({ modifiedCount: 0 }); // 模拟重复添加
|
|
||||||
const result = await service.surveyAuthorizeTask({
|
|
||||||
taskId,
|
|
||||||
surveyId,
|
|
||||||
ownerId: mockOwnerId,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.modifiedCount).toBe(0);
|
|
||||||
expect(repository.updateOne).toHaveBeenCalledWith(
|
|
||||||
{
|
|
||||||
_id: new ObjectId(taskId),
|
|
||||||
surveys: { $nin: [surveyId] }, // 确保只有不包含时才插入
|
|
||||||
ownerId: mockOwnerId,
|
|
||||||
},
|
|
||||||
expect.any(Object),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -37,4 +37,9 @@ describe('UpdateMessagePushingTaskDto', () => {
|
|||||||
dto.surveys = null;
|
dto.surveys = null;
|
||||||
expect(dto.surveys).toBeNull();
|
expect(dto.surveys).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should have a nullable curStatus', () => {
|
||||||
|
dto.curStatus = null;
|
||||||
|
expect(dto.curStatus).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -150,9 +150,8 @@ export class MessagePushingTaskController {
|
|||||||
async remove(@Request() req, @Param('id') id: string) {
|
async remove(@Request() req, @Param('id') id: string) {
|
||||||
const userId = req.user._id;
|
const userId = req.user._id;
|
||||||
const res = await this.messagePushingTaskService.remove({
|
const res = await this.messagePushingTaskService.remove({
|
||||||
|
ownerId: userId,
|
||||||
id,
|
id,
|
||||||
operator: req.user.username,
|
|
||||||
operatorId: userId,
|
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
MESSAGE_PUSHING_TYPE,
|
MESSAGE_PUSHING_TYPE,
|
||||||
MESSAGE_PUSHING_HOOK,
|
MESSAGE_PUSHING_HOOK,
|
||||||
} from 'src/enums/messagePushing';
|
} from 'src/enums/messagePushing';
|
||||||
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
|
|
||||||
export class MessagePushingTaskDto {
|
export class MessagePushingTaskDto {
|
||||||
@ApiProperty({ description: '任务id' })
|
@ApiProperty({ description: '任务id' })
|
||||||
@ -26,6 +27,12 @@ export class MessagePushingTaskDto {
|
|||||||
|
|
||||||
@ApiProperty({ description: '所有者' })
|
@ApiProperty({ description: '所有者' })
|
||||||
owner: string;
|
owner: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '任务状态', required: false })
|
||||||
|
curStatus?: {
|
||||||
|
status: RECORD_STATUS;
|
||||||
|
date: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CodeDto {
|
export class CodeDto {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
import {
|
import {
|
||||||
MESSAGE_PUSHING_TYPE,
|
MESSAGE_PUSHING_TYPE,
|
||||||
MESSAGE_PUSHING_HOOK,
|
MESSAGE_PUSHING_HOOK,
|
||||||
@ -19,4 +20,10 @@ export class UpdateMessagePushingTaskDto {
|
|||||||
|
|
||||||
@ApiProperty({ description: '绑定的问卷id', required: false })
|
@ApiProperty({ description: '绑定的问卷id', required: false })
|
||||||
surveys?: string[];
|
surveys?: string[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '任务状态', required: false })
|
||||||
|
curStatus?: {
|
||||||
|
status: RECORD_STATUS;
|
||||||
|
date: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import { MESSAGE_PUSHING_HOOK } from 'src/enums/messagePushing';
|
|||||||
import { CreateMessagePushingTaskDto } from '../dto/createMessagePushingTask.dto';
|
import { CreateMessagePushingTaskDto } from '../dto/createMessagePushingTask.dto';
|
||||||
import { UpdateMessagePushingTaskDto } from '../dto/updateMessagePushingTask.dto';
|
import { UpdateMessagePushingTaskDto } from '../dto/updateMessagePushingTask.dto';
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
import { MESSAGE_PUSHING_TYPE } from 'src/enums/messagePushing';
|
import { MESSAGE_PUSHING_TYPE } from 'src/enums/messagePushing';
|
||||||
import { MessagePushingLogService } from './messagePushingLog.service';
|
import { MessagePushingLogService } from './messagePushingLog.service';
|
||||||
import { httpPost } from 'src/utils/request';
|
import { httpPost } from 'src/utils/request';
|
||||||
@ -43,8 +44,8 @@ export class MessagePushingTaskService {
|
|||||||
ownerId?: string;
|
ownerId?: string;
|
||||||
}): Promise<MessagePushingTask[]> {
|
}): Promise<MessagePushingTask[]> {
|
||||||
const where: Record<string, any> = {
|
const where: Record<string, any> = {
|
||||||
isDeleted: {
|
'curStatus.status': {
|
||||||
$ne: true,
|
$ne: RECORD_STATUS.REMOVED,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if (surveyId) {
|
if (surveyId) {
|
||||||
@ -63,19 +64,19 @@ export class MessagePushingTaskService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
findOne({
|
async findOne({
|
||||||
id,
|
id,
|
||||||
ownerId,
|
ownerId,
|
||||||
}: {
|
}: {
|
||||||
id: string;
|
id: string;
|
||||||
ownerId: string;
|
ownerId: string;
|
||||||
}): Promise<MessagePushingTask> {
|
}): Promise<MessagePushingTask> {
|
||||||
return this.messagePushingTaskRepository.findOne({
|
return await this.messagePushingTaskRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
ownerId,
|
ownerId,
|
||||||
_id: new ObjectId(id),
|
_id: new ObjectId(id),
|
||||||
isDeleted: {
|
'curStatus.status': {
|
||||||
$ne: true,
|
$ne: RECORD_STATUS.REMOVED,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -103,25 +104,25 @@ export class MessagePushingTaskService {
|
|||||||
return await this.messagePushingTaskRepository.save(updatedTask);
|
return await this.messagePushingTaskRepository.save(updatedTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove({
|
async remove({ id, ownerId }: { id: string; ownerId: string }) {
|
||||||
id,
|
const curStatus = {
|
||||||
operator,
|
status: RECORD_STATUS.REMOVED,
|
||||||
operatorId,
|
date: Date.now(),
|
||||||
}: {
|
};
|
||||||
id: string;
|
|
||||||
operator: string;
|
|
||||||
operatorId: string;
|
|
||||||
}) {
|
|
||||||
return this.messagePushingTaskRepository.updateOne(
|
return this.messagePushingTaskRepository.updateOne(
|
||||||
{
|
{
|
||||||
|
ownerId,
|
||||||
_id: new ObjectId(id),
|
_id: new ObjectId(id),
|
||||||
|
'curStatus.status': {
|
||||||
|
$ne: RECORD_STATUS.REMOVED,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
isDeleted: true,
|
curStatus,
|
||||||
operator,
|
},
|
||||||
operatorId,
|
$push: {
|
||||||
deletedAt: new Date(),
|
statusList: curStatus as never,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -146,9 +147,6 @@ export class MessagePushingTaskService {
|
|||||||
$push: {
|
$push: {
|
||||||
surveys: surveyId as never,
|
surveys: surveyId as never,
|
||||||
},
|
},
|
||||||
$set: {
|
|
||||||
updatedAt: new Date(),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { CollaboratorController } from '../controllers/collaborator.controller';
|
import { CollaboratorController } from '../controllers/collaborator.controller';
|
||||||
import { CollaboratorService } from '../services/collaborator.service';
|
import { CollaboratorService } from '../services/collaborator.service';
|
||||||
import { Logger } from 'src/logger';
|
import { XiaojuSurveyLogger } from 'src/logger';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { CreateCollaboratorDto } from '../dto/createCollaborator.dto';
|
import { CreateCollaboratorDto } from '../dto/createCollaborator.dto';
|
||||||
import { Collaborator } from 'src/models/collaborator.entity';
|
import { Collaborator } from 'src/models/collaborator.entity';
|
||||||
@ -25,7 +25,7 @@ jest.mock('src/guards/workspace.guard');
|
|||||||
describe('CollaboratorController', () => {
|
describe('CollaboratorController', () => {
|
||||||
let controller: CollaboratorController;
|
let controller: CollaboratorController;
|
||||||
let collaboratorService: CollaboratorService;
|
let collaboratorService: CollaboratorService;
|
||||||
let logger: Logger;
|
let logger: XiaojuSurveyLogger;
|
||||||
let userService: UserService;
|
let userService: UserService;
|
||||||
let surveyMetaService: SurveyMetaService;
|
let surveyMetaService: SurveyMetaService;
|
||||||
let workspaceMemberServie: WorkspaceMemberService;
|
let workspaceMemberServie: WorkspaceMemberService;
|
||||||
@ -50,7 +50,7 @@ describe('CollaboratorController', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: Logger,
|
provide: XiaojuSurveyLogger,
|
||||||
useValue: {
|
useValue: {
|
||||||
error: jest.fn(),
|
error: jest.fn(),
|
||||||
info: jest.fn(),
|
info: jest.fn(),
|
||||||
@ -84,7 +84,7 @@ describe('CollaboratorController', () => {
|
|||||||
|
|
||||||
controller = module.get<CollaboratorController>(CollaboratorController);
|
controller = module.get<CollaboratorController>(CollaboratorController);
|
||||||
collaboratorService = module.get<CollaboratorService>(CollaboratorService);
|
collaboratorService = module.get<CollaboratorService>(CollaboratorService);
|
||||||
logger = module.get<Logger>(Logger);
|
logger = module.get<XiaojuSurveyLogger>(XiaojuSurveyLogger);
|
||||||
userService = module.get<UserService>(UserService);
|
userService = module.get<UserService>(UserService);
|
||||||
surveyMetaService = module.get<SurveyMetaService>(SurveyMetaService);
|
surveyMetaService = module.get<SurveyMetaService>(SurveyMetaService);
|
||||||
workspaceMemberServie = module.get<WorkspaceMemberService>(
|
workspaceMemberServie = module.get<WorkspaceMemberService>(
|
||||||
|
@ -3,13 +3,13 @@ import { CollaboratorService } from '../services/collaborator.service';
|
|||||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
import { Collaborator } from 'src/models/collaborator.entity';
|
import { Collaborator } from 'src/models/collaborator.entity';
|
||||||
import { MongoRepository } from 'typeorm';
|
import { MongoRepository } from 'typeorm';
|
||||||
import { Logger } from 'src/logger';
|
import { XiaojuSurveyLogger } from 'src/logger';
|
||||||
import { InsertManyResult, ObjectId } from 'mongodb';
|
import { InsertManyResult, ObjectId } from 'mongodb';
|
||||||
|
|
||||||
describe('CollaboratorService', () => {
|
describe('CollaboratorService', () => {
|
||||||
let service: CollaboratorService;
|
let service: CollaboratorService;
|
||||||
let repository: MongoRepository<Collaborator>;
|
let repository: MongoRepository<Collaborator>;
|
||||||
let logger: Logger;
|
let logger: XiaojuSurveyLogger;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
@ -20,7 +20,7 @@ describe('CollaboratorService', () => {
|
|||||||
useClass: MongoRepository,
|
useClass: MongoRepository,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: Logger,
|
provide: XiaojuSurveyLogger,
|
||||||
useValue: {
|
useValue: {
|
||||||
info: jest.fn(),
|
info: jest.fn(),
|
||||||
},
|
},
|
||||||
@ -32,7 +32,7 @@ describe('CollaboratorService', () => {
|
|||||||
repository = module.get<MongoRepository<Collaborator>>(
|
repository = module.get<MongoRepository<Collaborator>>(
|
||||||
getRepositoryToken(Collaborator),
|
getRepositoryToken(Collaborator),
|
||||||
);
|
);
|
||||||
logger = module.get<Logger>(Logger);
|
logger = module.get<XiaojuSurveyLogger>(XiaojuSurveyLogger);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('create', () => {
|
describe('create', () => {
|
||||||
@ -75,25 +75,6 @@ describe('CollaboratorService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('deleteCollaborator', () => {
|
|
||||||
it('should delete a collaborator by userId and surveyId', async () => {
|
|
||||||
const deleteOneSpy = jest
|
|
||||||
.spyOn(repository, 'deleteOne')
|
|
||||||
.mockResolvedValue({ acknowledged: true, deletedCount: 1 });
|
|
||||||
|
|
||||||
const result = await service.deleteCollaborator({
|
|
||||||
userId: '1',
|
|
||||||
surveyId: '1',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(deleteOneSpy).toHaveBeenCalledWith({
|
|
||||||
userId: '1',
|
|
||||||
surveyId: '1',
|
|
||||||
});
|
|
||||||
expect(result).toEqual({ acknowledged: true, deletedCount: 1 });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('batchCreate', () => {
|
describe('batchCreate', () => {
|
||||||
it('should batch create collaborators', async () => {
|
it('should batch create collaborators', async () => {
|
||||||
const insertManySpy = jest
|
const insertManySpy = jest
|
||||||
@ -105,193 +86,15 @@ describe('CollaboratorService', () => {
|
|||||||
const result = await service.batchCreate({
|
const result = await service.batchCreate({
|
||||||
surveyId: '1',
|
surveyId: '1',
|
||||||
collaboratorList: [{ userId: '1', permissions: [] }],
|
collaboratorList: [{ userId: '1', permissions: [] }],
|
||||||
creator: 'testCreator',
|
|
||||||
creatorId: 'testCreatorId',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(insertManySpy).toHaveBeenCalledWith([
|
expect(insertManySpy).toHaveBeenCalledWith([
|
||||||
{
|
{ surveyId: '1', userId: '1', permissions: [] },
|
||||||
userId: '1',
|
|
||||||
permissions: [],
|
|
||||||
surveyId: '1',
|
|
||||||
createdAt: expect.any(Date),
|
|
||||||
updatedAt: expect.any(Date),
|
|
||||||
creator: 'testCreator',
|
|
||||||
creatorId: 'testCreatorId',
|
|
||||||
},
|
|
||||||
]);
|
]);
|
||||||
expect(result).toEqual({ insertedCount: 1 });
|
expect(result).toEqual({ insertedCount: 1 });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('changeUserPermission', () => {
|
|
||||||
it("should update a user's permissions", async () => {
|
|
||||||
const updateOneSpy = jest
|
|
||||||
.spyOn(repository, 'updateOne')
|
|
||||||
.mockResolvedValue({});
|
|
||||||
|
|
||||||
const result = await service.changeUserPermission({
|
|
||||||
userId: '1',
|
|
||||||
surveyId: '1',
|
|
||||||
permission: 'read',
|
|
||||||
operator: 'testOperator',
|
|
||||||
operatorId: 'testOperatorId',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(updateOneSpy).toHaveBeenCalledWith(
|
|
||||||
{
|
|
||||||
surveyId: '1',
|
|
||||||
userId: '1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$set: {
|
|
||||||
permission: 'read',
|
|
||||||
operator: 'testOperator',
|
|
||||||
operatorId: 'testOperatorId',
|
|
||||||
updatedAt: expect.any(Date),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
expect(result).toEqual({});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('batchDelete', () => {
|
|
||||||
it('should batch delete collaborators', async () => {
|
|
||||||
const mockResult = { acknowledged: true, deletedCount: 1 };
|
|
||||||
const deleteManySpy = jest
|
|
||||||
.spyOn(repository, 'deleteMany')
|
|
||||||
.mockResolvedValue(mockResult);
|
|
||||||
|
|
||||||
const collaboratorId = new ObjectId().toString();
|
|
||||||
|
|
||||||
const result = await service.batchDelete({
|
|
||||||
surveyId: '1',
|
|
||||||
idList: [collaboratorId],
|
|
||||||
});
|
|
||||||
|
|
||||||
const expectedQuery = {
|
|
||||||
surveyId: '1',
|
|
||||||
$or: [
|
|
||||||
{
|
|
||||||
_id: {
|
|
||||||
$in: [new ObjectId(collaboratorId)],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(logger.info).toHaveBeenCalledWith(JSON.stringify(expectedQuery));
|
|
||||||
expect(deleteManySpy).toHaveBeenCalledWith(expectedQuery);
|
|
||||||
expect(result).toEqual(mockResult);
|
|
||||||
});
|
|
||||||
it('should batch delete collaborators by idList and userIdList', async () => {
|
|
||||||
const collaboratorId = new ObjectId().toString();
|
|
||||||
const deleteManySpy = jest
|
|
||||||
.spyOn(repository, 'deleteMany')
|
|
||||||
.mockResolvedValue({ acknowledged: true, deletedCount: 2 });
|
|
||||||
|
|
||||||
const result = await service.batchDelete({
|
|
||||||
idList: [collaboratorId],
|
|
||||||
userIdList: ['user1', 'user2'],
|
|
||||||
surveyId: '1',
|
|
||||||
});
|
|
||||||
|
|
||||||
const expectedQuery = {
|
|
||||||
surveyId: '1',
|
|
||||||
$or: [
|
|
||||||
{
|
|
||||||
userId: {
|
|
||||||
$in: ['user1', 'user2'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
_id: {
|
|
||||||
$in: [new ObjectId(collaboratorId)],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(deleteManySpy).toHaveBeenCalledWith(expectedQuery);
|
|
||||||
expect(result).toEqual({ acknowledged: true, deletedCount: 2 });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle batch delete with neIdList only', async () => {
|
|
||||||
const neCollaboratorId = new ObjectId().toString();
|
|
||||||
const deleteManySpy = jest
|
|
||||||
.spyOn(repository, 'deleteMany')
|
|
||||||
.mockResolvedValue({ acknowledged: true, deletedCount: 1 });
|
|
||||||
|
|
||||||
const result = await service.batchDelete({
|
|
||||||
neIdList: [neCollaboratorId],
|
|
||||||
surveyId: '1',
|
|
||||||
});
|
|
||||||
|
|
||||||
const expectedQuery = {
|
|
||||||
surveyId: '1',
|
|
||||||
$or: [
|
|
||||||
{
|
|
||||||
_id: {
|
|
||||||
$nin: [new ObjectId(neCollaboratorId)],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(deleteManySpy).toHaveBeenCalledWith(expectedQuery);
|
|
||||||
expect(result).toEqual({ acknowledged: true, deletedCount: 1 });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('batchDeleteBySurveyId', () => {
|
|
||||||
it('should batch delete collaborators by survey id', async () => {
|
|
||||||
const mockResult = { acknowledged: true, deletedCount: 1 };
|
|
||||||
const deleteManySpy = jest
|
|
||||||
.spyOn(repository, 'deleteMany')
|
|
||||||
.mockResolvedValue(mockResult);
|
|
||||||
|
|
||||||
const surveyId = new ObjectId().toString();
|
|
||||||
|
|
||||||
const result = await service.batchDeleteBySurveyId(surveyId);
|
|
||||||
|
|
||||||
expect(deleteManySpy).toHaveBeenCalledWith({
|
|
||||||
surveyId,
|
|
||||||
});
|
|
||||||
expect(result).toEqual(mockResult);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('updateById', () => {
|
|
||||||
it('should update collaborator by id', async () => {
|
|
||||||
const updateOneSpy = jest
|
|
||||||
.spyOn(repository, 'updateOne')
|
|
||||||
.mockResolvedValue({});
|
|
||||||
const collaboratorId = new ObjectId().toString();
|
|
||||||
const result = await service.updateById({
|
|
||||||
collaboratorId,
|
|
||||||
permissions: [],
|
|
||||||
operator: 'testOperator',
|
|
||||||
operatorId: 'testOperatorId',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(updateOneSpy).toHaveBeenCalledWith(
|
|
||||||
{
|
|
||||||
_id: new ObjectId(collaboratorId),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$set: {
|
|
||||||
permissions: [],
|
|
||||||
operator: 'testOperator',
|
|
||||||
operatorId: 'testOperatorId',
|
|
||||||
updatedAt: expect.any(Date),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
expect(result).toEqual({});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getSurveyCollaboratorList', () => {
|
describe('getSurveyCollaboratorList', () => {
|
||||||
it('should return a list of collaborators for a survey', async () => {
|
it('should return a list of collaborators for a survey', async () => {
|
||||||
const collaboratorId = new ObjectId().toString();
|
const collaboratorId = new ObjectId().toString();
|
||||||
@ -318,6 +121,38 @@ describe('CollaboratorService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getCollaboratorListByIds', () => {
|
||||||
|
it('should return a list of collaborators by ids', async () => {
|
||||||
|
const collaboratorId = new ObjectId().toString();
|
||||||
|
const findSpy = jest.spyOn(repository, 'find').mockResolvedValue([
|
||||||
|
{
|
||||||
|
_id: new ObjectId(collaboratorId),
|
||||||
|
surveyId: '1',
|
||||||
|
userId: '1',
|
||||||
|
permissions: [],
|
||||||
|
},
|
||||||
|
] as Collaborator[]);
|
||||||
|
|
||||||
|
const result = await service.getCollaboratorListByIds({
|
||||||
|
idList: [collaboratorId],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(findSpy).toHaveBeenCalledWith({
|
||||||
|
_id: {
|
||||||
|
$in: [new ObjectId(collaboratorId)],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
_id: new ObjectId(collaboratorId),
|
||||||
|
surveyId: '1',
|
||||||
|
userId: '1',
|
||||||
|
permissions: [],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('getCollaborator', () => {
|
describe('getCollaborator', () => {
|
||||||
it('should return a collaborator', async () => {
|
it('should return a collaborator', async () => {
|
||||||
const collaboratorId = new ObjectId().toString();
|
const collaboratorId = new ObjectId().toString();
|
||||||
@ -348,6 +183,127 @@ describe('CollaboratorService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('changeUserPermission', () => {
|
||||||
|
it("should update a user's permissions", async () => {
|
||||||
|
const updateOneSpy = jest
|
||||||
|
.spyOn(repository, 'updateOne')
|
||||||
|
.mockResolvedValue({});
|
||||||
|
|
||||||
|
const result = await service.changeUserPermission({
|
||||||
|
userId: '1',
|
||||||
|
surveyId: '1',
|
||||||
|
permission: 'read',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(updateOneSpy).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
surveyId: '1',
|
||||||
|
userId: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
permission: 'read',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(result).toEqual({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deleteCollaborator', () => {
|
||||||
|
it('should delete a collaborator', async () => {
|
||||||
|
const mockResult = { acknowledged: true, deletedCount: 1 };
|
||||||
|
const deleteOneSpy = jest
|
||||||
|
.spyOn(repository, 'deleteOne')
|
||||||
|
.mockResolvedValue(mockResult);
|
||||||
|
|
||||||
|
const result = await service.deleteCollaborator({
|
||||||
|
userId: '1',
|
||||||
|
surveyId: '1',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(deleteOneSpy).toHaveBeenCalledWith({
|
||||||
|
userId: '1',
|
||||||
|
surveyId: '1',
|
||||||
|
});
|
||||||
|
expect(result).toEqual(mockResult);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('batchDelete', () => {
|
||||||
|
it('should batch delete collaborators', async () => {
|
||||||
|
const mockResult = { acknowledged: true, deletedCount: 1 };
|
||||||
|
const deleteManySpy = jest
|
||||||
|
.spyOn(repository, 'deleteMany')
|
||||||
|
.mockResolvedValue(mockResult);
|
||||||
|
|
||||||
|
const collaboratorId = new ObjectId().toString();
|
||||||
|
|
||||||
|
const result = await service.batchDelete({
|
||||||
|
surveyId: '1',
|
||||||
|
idList: [collaboratorId],
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedQuery = {
|
||||||
|
surveyId: '1',
|
||||||
|
$or: [
|
||||||
|
{
|
||||||
|
_id: {
|
||||||
|
$in: [new ObjectId(collaboratorId)],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(logger.info).toHaveBeenCalledWith(JSON.stringify(expectedQuery));
|
||||||
|
expect(deleteManySpy).toHaveBeenCalledWith(expectedQuery);
|
||||||
|
expect(result).toEqual(mockResult);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('batchDeleteBySurveyId', () => {
|
||||||
|
it('should batch delete collaborators by survey id', async () => {
|
||||||
|
const mockResult = { acknowledged: true, deletedCount: 1 };
|
||||||
|
const deleteManySpy = jest
|
||||||
|
.spyOn(repository, 'deleteMany')
|
||||||
|
.mockResolvedValue(mockResult);
|
||||||
|
|
||||||
|
const surveyId = new ObjectId().toString();
|
||||||
|
|
||||||
|
const result = await service.batchDeleteBySurveyId(surveyId);
|
||||||
|
|
||||||
|
expect(deleteManySpy).toHaveBeenCalledWith({
|
||||||
|
surveyId,
|
||||||
|
});
|
||||||
|
expect(result).toEqual(mockResult);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updateById', () => {
|
||||||
|
it('should update collaborator by id', async () => {
|
||||||
|
const updateOneSpy = jest
|
||||||
|
.spyOn(repository, 'updateOne')
|
||||||
|
.mockResolvedValue({});
|
||||||
|
const collaboratorId = new ObjectId().toString();
|
||||||
|
const result = await service.updateById({
|
||||||
|
collaboratorId,
|
||||||
|
permissions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(updateOneSpy).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
_id: new ObjectId(collaboratorId),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
permissions: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(result).toEqual({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('getCollaboratorListByUserId', () => {
|
describe('getCollaboratorListByUserId', () => {
|
||||||
it('should return a list of collaborators by user id', async () => {
|
it('should return a list of collaborators by user id', async () => {
|
||||||
const userId = new ObjectId().toString();
|
const userId = new ObjectId().toString();
|
||||||
@ -371,48 +327,5 @@ describe('CollaboratorService', () => {
|
|||||||
{ _id: '1', surveyId: '1', userId, permissions: [] },
|
{ _id: '1', surveyId: '1', userId, permissions: [] },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of collaborators by their IDs', async () => {
|
|
||||||
const collaboratorId1 = new ObjectId().toString();
|
|
||||||
const collaboratorId2 = new ObjectId().toString();
|
|
||||||
const findSpy = jest.spyOn(repository, 'find').mockResolvedValue([
|
|
||||||
{
|
|
||||||
_id: new ObjectId(collaboratorId1),
|
|
||||||
surveyId: '1',
|
|
||||||
userId: 'user1',
|
|
||||||
permissions: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
_id: new ObjectId(collaboratorId2),
|
|
||||||
surveyId: '2',
|
|
||||||
userId: 'user2',
|
|
||||||
permissions: [],
|
|
||||||
},
|
|
||||||
] as Collaborator[]);
|
|
||||||
|
|
||||||
const result = await service.getCollaboratorListByIds({
|
|
||||||
idList: [collaboratorId1, collaboratorId2],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(findSpy).toHaveBeenCalledWith({
|
|
||||||
_id: {
|
|
||||||
$in: [new ObjectId(collaboratorId1), new ObjectId(collaboratorId2)],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
expect(result).toEqual([
|
|
||||||
{
|
|
||||||
_id: new ObjectId(collaboratorId1),
|
|
||||||
surveyId: '1',
|
|
||||||
userId: 'user1',
|
|
||||||
permissions: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
_id: new ObjectId(collaboratorId2),
|
|
||||||
surveyId: '2',
|
|
||||||
userId: 'user2',
|
|
||||||
permissions: [],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -8,8 +8,8 @@ import { SurveyMetaService } from '../services/surveyMeta.service';
|
|||||||
import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service';
|
import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service';
|
||||||
|
|
||||||
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
||||||
import { PluginManager } from 'src/securityPlugin/pluginManager';
|
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager';
|
||||||
import { Logger } from 'src/logger';
|
import { XiaojuSurveyLogger } from 'src/logger';
|
||||||
|
|
||||||
import { UserService } from 'src/modules/auth/services/user.service';
|
import { UserService } from 'src/modules/auth/services/user.service';
|
||||||
import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugin';
|
import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugin';
|
||||||
@ -27,8 +27,8 @@ describe('DataStatisticController', () => {
|
|||||||
let controller: DataStatisticController;
|
let controller: DataStatisticController;
|
||||||
let dataStatisticService: DataStatisticService;
|
let dataStatisticService: DataStatisticService;
|
||||||
let responseSchemaService: ResponseSchemaService;
|
let responseSchemaService: ResponseSchemaService;
|
||||||
let pluginManager: PluginManager;
|
let pluginManager: XiaojuSurveyPluginManager;
|
||||||
let logger: Logger;
|
let logger: XiaojuSurveyLogger;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
@ -56,7 +56,7 @@ describe('DataStatisticController', () => {
|
|||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: Logger,
|
provide: XiaojuSurveyLogger,
|
||||||
useValue: {
|
useValue: {
|
||||||
error: jest.fn(),
|
error: jest.fn(),
|
||||||
},
|
},
|
||||||
@ -70,8 +70,10 @@ describe('DataStatisticController', () => {
|
|||||||
responseSchemaService = module.get<ResponseSchemaService>(
|
responseSchemaService = module.get<ResponseSchemaService>(
|
||||||
ResponseSchemaService,
|
ResponseSchemaService,
|
||||||
);
|
);
|
||||||
pluginManager = module.get<PluginManager>(PluginManager);
|
pluginManager = module.get<XiaojuSurveyPluginManager>(
|
||||||
logger = module.get<Logger>(Logger);
|
XiaojuSurveyPluginManager,
|
||||||
|
);
|
||||||
|
logger = module.get<XiaojuSurveyLogger>(XiaojuSurveyLogger);
|
||||||
|
|
||||||
pluginManager.registerPlugin(
|
pluginManager.registerPlugin(
|
||||||
new ResponseSecurityPlugin('dataAesEncryptSecretKey'),
|
new ResponseSecurityPlugin('dataAesEncryptSecretKey'),
|
||||||
@ -88,7 +90,7 @@ describe('DataStatisticController', () => {
|
|||||||
const mockRequest = {
|
const mockRequest = {
|
||||||
query: {
|
query: {
|
||||||
surveyId,
|
surveyId,
|
||||||
isMasked: false,
|
isDesensitive: false,
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
},
|
},
|
||||||
@ -109,8 +111,8 @@ describe('DataStatisticController', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
listBody: [
|
listBody: [
|
||||||
{ diffTime: '0.5', createdAt: '2024-02-11' },
|
{ diffTime: '0.5', createDate: '2024-02-11' },
|
||||||
{ diffTime: '0.5', createdAt: '2024-02-11' },
|
{ diffTime: '0.5', createDate: '2024-02-11' },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -129,12 +131,12 @@ describe('DataStatisticController', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return data table with isMasked', async () => {
|
it('should return data table with isDesensitive', async () => {
|
||||||
const surveyId = new ObjectId().toString();
|
const surveyId = new ObjectId().toString();
|
||||||
const mockRequest = {
|
const mockRequest = {
|
||||||
query: {
|
query: {
|
||||||
surveyId,
|
surveyId,
|
||||||
isMasked: true,
|
isDesensitive: true,
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
},
|
},
|
||||||
@ -155,8 +157,8 @@ describe('DataStatisticController', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
listBody: [
|
listBody: [
|
||||||
{ diffTime: '0.5', createdAt: '2024-02-11', data123: '15200000000' },
|
{ diffTime: '0.5', createDate: '2024-02-11', data123: '15200000000' },
|
||||||
{ diffTime: '0.5', createdAt: '2024-02-11', data123: '13800000000' },
|
{ diffTime: '0.5', createDate: '2024-02-11', data123: '13800000000' },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -212,8 +214,8 @@ describe('DataStatisticController', () => {
|
|||||||
date: 1717158851823,
|
date: 1717158851823,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
createdAt: 1717158851823,
|
createDate: 1717158851823,
|
||||||
updatedAt: 1717159136025,
|
updateDate: 1717159136025,
|
||||||
title: '问卷调研',
|
title: '问卷调研',
|
||||||
surveyPath: 'ZdGNzTTR',
|
surveyPath: 'ZdGNzTTR',
|
||||||
code: {
|
code: {
|
||||||
@ -233,7 +235,7 @@ describe('DataStatisticController', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
baseConf: {
|
baseConf: {
|
||||||
beginTime: '2024-05-31 20:31:36',
|
begTime: '2024-05-31 20:31:36',
|
||||||
endTime: '2034-05-31 20:31:36',
|
endTime: '2034-05-31 20:31:36',
|
||||||
language: 'chinese',
|
language: 'chinese',
|
||||||
showVoteProcess: 'allow',
|
showVoteProcess: 'allow',
|
||||||
@ -249,8 +251,6 @@ describe('DataStatisticController', () => {
|
|||||||
skinConf: {
|
skinConf: {
|
||||||
backgroundConf: {
|
backgroundConf: {
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
type: 'color',
|
|
||||||
image: '',
|
|
||||||
},
|
},
|
||||||
themeConf: {
|
themeConf: {
|
||||||
color: '#ffa600',
|
color: '#ffa600',
|
||||||
|
@ -11,7 +11,7 @@ import { cloneDeep } from 'lodash';
|
|||||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
import { RECORD_STATUS } from 'src/enums';
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
||||||
import { PluginManager } from 'src/securityPlugin/pluginManager';
|
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager';
|
||||||
import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugin';
|
import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugin';
|
||||||
|
|
||||||
describe('DataStatisticService', () => {
|
describe('DataStatisticService', () => {
|
||||||
@ -34,7 +34,9 @@ describe('DataStatisticService', () => {
|
|||||||
surveyResponseRepository = module.get<MongoRepository<SurveyResponse>>(
|
surveyResponseRepository = module.get<MongoRepository<SurveyResponse>>(
|
||||||
getRepositoryToken(SurveyResponse),
|
getRepositoryToken(SurveyResponse),
|
||||||
);
|
);
|
||||||
const manager = module.get<PluginManager>(PluginManager);
|
const manager = module.get<XiaojuSurveyPluginManager>(
|
||||||
|
XiaojuSurveyPluginManager,
|
||||||
|
);
|
||||||
manager.registerPlugin(
|
manager.registerPlugin(
|
||||||
new ResponseSecurityPlugin('dataAesEncryptSecretKey'),
|
new ResponseSecurityPlugin('dataAesEncryptSecretKey'),
|
||||||
);
|
);
|
||||||
@ -151,8 +153,8 @@ describe('DataStatisticService', () => {
|
|||||||
date: 1710340863123.0,
|
date: 1710340863123.0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
createdAt: 1710340863123.0,
|
createDate: 1710340863123.0,
|
||||||
updatedAt: 1710340863123.0,
|
updateDate: 1710340863123.0,
|
||||||
},
|
},
|
||||||
] as unknown as Array<SurveyResponse>;
|
] as unknown as Array<SurveyResponse>;
|
||||||
|
|
||||||
@ -196,13 +198,13 @@ describe('DataStatisticService', () => {
|
|||||||
data413: expect.any(Number),
|
data413: expect.any(Number),
|
||||||
data863: expect.any(String),
|
data863: expect.any(String),
|
||||||
diffTime: expect.any(String),
|
diffTime: expect.any(String),
|
||||||
createdAt: expect.any(String),
|
createDate: expect.any(String),
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return desensitized table data', async () => {
|
it('should return desensitive table data', async () => {
|
||||||
const mockSchema = cloneDeep(mockSensitiveResponseSchema);
|
const mockSchema = cloneDeep(mockSensitiveResponseSchema);
|
||||||
const surveyResponseList: Array<SurveyResponse> = [
|
const surveyResponseList: Array<SurveyResponse> = [
|
||||||
{
|
{
|
||||||
@ -273,8 +275,8 @@ describe('DataStatisticService', () => {
|
|||||||
date: 1710400232161.0,
|
date: 1710400232161.0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
createdAt: 1710400232161.0,
|
createDate: 1710400232161.0,
|
||||||
updatedAt: 1710400232161.0,
|
updateDate: 1710400232161.0,
|
||||||
},
|
},
|
||||||
] as unknown as Array<SurveyResponse>;
|
] as unknown as Array<SurveyResponse>;
|
||||||
|
|
||||||
@ -295,7 +297,7 @@ describe('DataStatisticService', () => {
|
|||||||
expect(result.listBody).toEqual(
|
expect(result.listBody).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
createdAt: expect.any(String),
|
createDate: expect.any(String),
|
||||||
data405: expect.any(String),
|
data405: expect.any(String),
|
||||||
data450: expect.any(String),
|
data450: expect.any(String),
|
||||||
data458: expect.any(String),
|
data458: expect.any(String),
|
||||||
|
@ -1,264 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { ObjectId } from 'mongodb';
|
|
||||||
import { DownloadTaskController } from '../controllers/downloadTask.controller';
|
|
||||||
import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service';
|
|
||||||
import { AuthService } from 'src/modules/auth/services/auth.service';
|
|
||||||
import { DownloadTaskService } from '../services/downloadTask.service';
|
|
||||||
import { CollaboratorService } from '../services/collaborator.service';
|
|
||||||
import { SurveyMetaService } from '../services/surveyMeta.service';
|
|
||||||
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
|
|
||||||
|
|
||||||
import { Logger } from 'src/logger';
|
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
|
||||||
import { NoPermissionException } from 'src/exceptions/noPermissionException';
|
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
|
||||||
|
|
||||||
import { Authentication } from 'src/guards/authentication.guard';
|
|
||||||
import { SurveyGuard } from 'src/guards/survey.guard';
|
|
||||||
|
|
||||||
describe('DownloadTaskController', () => {
|
|
||||||
let controller: DownloadTaskController;
|
|
||||||
let responseSchemaService: ResponseSchemaService;
|
|
||||||
let downloadTaskService: DownloadTaskService;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
controllers: [DownloadTaskController],
|
|
||||||
providers: [
|
|
||||||
{
|
|
||||||
provide: ResponseSchemaService,
|
|
||||||
useValue: {
|
|
||||||
getResponseSchemaByPageId: jest.fn(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: DownloadTaskService,
|
|
||||||
useValue: {
|
|
||||||
createDownloadTask: jest.fn(),
|
|
||||||
processDownloadTask: jest.fn(),
|
|
||||||
getDownloadTaskList: jest.fn(),
|
|
||||||
getDownloadTaskById: jest.fn(),
|
|
||||||
deleteDownloadTask: jest.fn(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: Logger,
|
|
||||||
useValue: {
|
|
||||||
error: jest.fn(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: AuthService,
|
|
||||||
useClass: jest.fn().mockImplementation(() => ({
|
|
||||||
varifytoken() {
|
|
||||||
return {};
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: CollaboratorService,
|
|
||||||
useValue: {},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: SurveyMetaService,
|
|
||||||
useValue: {},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: WorkspaceMemberService,
|
|
||||||
useValue: {},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: Authentication,
|
|
||||||
useClass: jest.fn().mockImplementation(() => ({
|
|
||||||
canActivate: () => true,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: SurveyGuard,
|
|
||||||
useClass: jest.fn().mockImplementation(() => ({
|
|
||||||
canActivate: () => true,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
controller = module.get<DownloadTaskController>(DownloadTaskController);
|
|
||||||
responseSchemaService = module.get<ResponseSchemaService>(
|
|
||||||
ResponseSchemaService,
|
|
||||||
);
|
|
||||||
downloadTaskService = module.get<DownloadTaskService>(DownloadTaskService);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('createTask', () => {
|
|
||||||
it('should create a download task successfully', async () => {
|
|
||||||
const mockReqBody = {
|
|
||||||
surveyId: new ObjectId().toString(),
|
|
||||||
isMasked: false,
|
|
||||||
};
|
|
||||||
const mockReq = { user: { _id: 'mockUserId', username: 'mockUsername' } };
|
|
||||||
const mockTaskId = 'mockTaskId';
|
|
||||||
jest
|
|
||||||
.spyOn(responseSchemaService, 'getResponseSchemaByPageId')
|
|
||||||
.mockResolvedValue({} as any);
|
|
||||||
jest
|
|
||||||
.spyOn(downloadTaskService, 'createDownloadTask')
|
|
||||||
.mockResolvedValue(mockTaskId);
|
|
||||||
|
|
||||||
const result = await controller.createTask(mockReqBody, mockReq);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
responseSchemaService.getResponseSchemaByPageId,
|
|
||||||
).toHaveBeenCalledWith(mockReqBody.surveyId);
|
|
||||||
expect(downloadTaskService.createDownloadTask).toHaveBeenCalledWith({
|
|
||||||
surveyId: mockReqBody.surveyId,
|
|
||||||
responseSchema: {},
|
|
||||||
creatorId: mockReq.user._id.toString(),
|
|
||||||
creator: mockReq.user.username,
|
|
||||||
params: { isMasked: mockReqBody.isMasked },
|
|
||||||
});
|
|
||||||
expect(downloadTaskService.processDownloadTask).toHaveBeenCalledWith({
|
|
||||||
taskId: mockTaskId,
|
|
||||||
});
|
|
||||||
expect(result).toEqual({ code: 200, data: { taskId: mockTaskId } });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw HttpException if validation fails', async () => {
|
|
||||||
const mockReqBody: any = { isMasked: false };
|
|
||||||
const mockReq = { user: { _id: 'mockUserId', username: 'mockUsername' } };
|
|
||||||
|
|
||||||
await expect(controller.createTask(mockReqBody, mockReq)).rejects.toThrow(
|
|
||||||
HttpException,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('downloadList', () => {
|
|
||||||
it('should return the download task list', async () => {
|
|
||||||
const mockQueryInfo = { pageIndex: 1, pageSize: 10 };
|
|
||||||
const mockReq = { user: { _id: 'mockUserId' } };
|
|
||||||
const mockTaskList: any = {
|
|
||||||
total: 1,
|
|
||||||
list: [
|
|
||||||
{
|
|
||||||
_id: 'mockTaskId',
|
|
||||||
curStatus: 'completed',
|
|
||||||
filename: 'mockFile.csv',
|
|
||||||
url: 'http://mock-url.com',
|
|
||||||
fileSize: 1024,
|
|
||||||
createdAt: Date.now(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
jest
|
|
||||||
.spyOn(downloadTaskService, 'getDownloadTaskList')
|
|
||||||
.mockResolvedValue(mockTaskList);
|
|
||||||
|
|
||||||
const result = await controller.downloadList(mockQueryInfo, mockReq);
|
|
||||||
|
|
||||||
expect(downloadTaskService.getDownloadTaskList).toHaveBeenCalledWith({
|
|
||||||
creatorId: mockReq.user._id.toString(),
|
|
||||||
pageIndex: mockQueryInfo.pageIndex,
|
|
||||||
pageSize: mockQueryInfo.pageSize,
|
|
||||||
});
|
|
||||||
expect(result.data.total).toEqual(mockTaskList.total);
|
|
||||||
expect(result.data.list[0].taskId).toEqual(
|
|
||||||
mockTaskList.list[0]._id.toString(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw HttpException if validation fails', async () => {
|
|
||||||
const mockQueryInfo: any = { pageIndex: 'invalid', pageSize: 10 };
|
|
||||||
const mockReq = { user: { _id: 'mockUserId' } };
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
controller.downloadList(mockQueryInfo, mockReq),
|
|
||||||
).rejects.toThrow(HttpException);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getDownloadTask', () => {
|
|
||||||
it('should return a download task', async () => {
|
|
||||||
const mockQuery = { taskId: 'mockTaskId' };
|
|
||||||
const mockReq = { user: { _id: 'mockUserId' } };
|
|
||||||
const mockTaskInfo: any = {
|
|
||||||
_id: 'mockTaskId',
|
|
||||||
creatorId: 'mockUserId',
|
|
||||||
curStatus: 'completed',
|
|
||||||
};
|
|
||||||
jest
|
|
||||||
.spyOn(downloadTaskService, 'getDownloadTaskById')
|
|
||||||
.mockResolvedValue(mockTaskInfo);
|
|
||||||
|
|
||||||
const result = await controller.getDownloadTask(mockQuery, mockReq);
|
|
||||||
|
|
||||||
expect(downloadTaskService.getDownloadTaskById).toHaveBeenCalledWith({
|
|
||||||
taskId: mockQuery.taskId,
|
|
||||||
});
|
|
||||||
expect(result.data.taskId).toEqual(mockTaskInfo._id.toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw NoPermissionException if user has no permission', async () => {
|
|
||||||
const mockQuery = { taskId: 'mockTaskId' };
|
|
||||||
const mockReq = { user: { _id: new ObjectId() } };
|
|
||||||
const mockTaskInfo: any = {
|
|
||||||
_id: 'mockTaskId',
|
|
||||||
creatorId: 'mockUserId',
|
|
||||||
curStatus: 'completed',
|
|
||||||
};
|
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(downloadTaskService, 'getDownloadTaskById')
|
|
||||||
.mockResolvedValue(mockTaskInfo);
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
controller.getDownloadTask(mockQuery, mockReq),
|
|
||||||
).rejects.toThrow(new NoPermissionException('没有权限'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('deleteFileByName', () => {
|
|
||||||
it('should delete a download task successfully', async () => {
|
|
||||||
const mockBody = { taskId: 'mockTaskId' };
|
|
||||||
const mockUserId = new ObjectId();
|
|
||||||
const mockReq = {
|
|
||||||
user: { _id: mockUserId, username: 'mockUsername' },
|
|
||||||
};
|
|
||||||
const mockTaskInfo: any = {
|
|
||||||
_id: new ObjectId(),
|
|
||||||
creatorId: mockUserId.toString(),
|
|
||||||
};
|
|
||||||
const mockDelRes = { modifiedCount: 1 };
|
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(downloadTaskService, 'getDownloadTaskById')
|
|
||||||
.mockResolvedValue(mockTaskInfo);
|
|
||||||
jest
|
|
||||||
.spyOn(downloadTaskService, 'deleteDownloadTask')
|
|
||||||
.mockResolvedValue(mockDelRes);
|
|
||||||
|
|
||||||
const result = await controller.deleteFileByName(mockBody, mockReq);
|
|
||||||
|
|
||||||
expect(downloadTaskService.deleteDownloadTask).toHaveBeenCalledWith({
|
|
||||||
taskId: mockBody.taskId,
|
|
||||||
operator: mockReq.user.username,
|
|
||||||
operatorId: mockReq.user._id.toString(),
|
|
||||||
});
|
|
||||||
expect(result).toEqual({ code: 200, data: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw HttpException if task does not exist', async () => {
|
|
||||||
const mockBody = { taskId: 'mockTaskId' };
|
|
||||||
const mockReq = { user: { _id: 'mockUserId' } };
|
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(downloadTaskService, 'getDownloadTaskById')
|
|
||||||
.mockResolvedValue(null);
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
controller.deleteFileByName(mockBody, mockReq),
|
|
||||||
).rejects.toThrow(
|
|
||||||
new HttpException('任务不存在', EXCEPTION_CODE.PARAMETER_ERROR),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,245 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { DownloadTaskService } from '../services/downloadTask.service';
|
|
||||||
import { MongoRepository } from 'typeorm';
|
|
||||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
|
||||||
import { DownloadTask } from 'src/models/downloadTask.entity';
|
|
||||||
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
|
||||||
import { ResponseSchemaService } from 'src/modules/surveyResponse/services/responseScheme.service';
|
|
||||||
import { DataStatisticService } from '../services/dataStatistic.service';
|
|
||||||
import { FileService } from 'src/modules/file/services/file.service';
|
|
||||||
import { Logger } from 'src/logger';
|
|
||||||
import { ObjectId } from 'mongodb';
|
|
||||||
import { DOWNLOAD_TASK_STATUS } from 'src/enums/downloadTaskStatus';
|
|
||||||
|
|
||||||
describe('DownloadTaskService', () => {
|
|
||||||
let service: DownloadTaskService;
|
|
||||||
let downloadTaskRepository: MongoRepository<DownloadTask>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
providers: [
|
|
||||||
DownloadTaskService,
|
|
||||||
{
|
|
||||||
provide: getRepositoryToken(DownloadTask),
|
|
||||||
useClass: MongoRepository,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: getRepositoryToken(SurveyResponse),
|
|
||||||
useClass: MongoRepository,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: ResponseSchemaService,
|
|
||||||
useValue: {
|
|
||||||
getResponseSchemaByPageId: jest.fn(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: DataStatisticService,
|
|
||||||
useValue: {
|
|
||||||
getDataTable: jest.fn(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: FileService,
|
|
||||||
useValue: {
|
|
||||||
upload: jest.fn(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: Logger,
|
|
||||||
useValue: {
|
|
||||||
info: jest.fn(),
|
|
||||||
error: jest.fn(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
service = module.get<DownloadTaskService>(DownloadTaskService);
|
|
||||||
downloadTaskRepository = module.get<MongoRepository<DownloadTask>>(
|
|
||||||
getRepositoryToken(DownloadTask),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('createDownloadTask', () => {
|
|
||||||
it('should create and save a download task', async () => {
|
|
||||||
const mockTaskId = new ObjectId().toString();
|
|
||||||
const mockDownloadTask = { _id: new ObjectId(mockTaskId) };
|
|
||||||
const mockParams: any = {
|
|
||||||
surveyId: 'survey1',
|
|
||||||
responseSchema: { title: 'test-title', surveyPath: '/path' },
|
|
||||||
creatorId: 'creator1',
|
|
||||||
creator: 'creatorName',
|
|
||||||
params: { isMasked: true },
|
|
||||||
};
|
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(downloadTaskRepository, 'create')
|
|
||||||
.mockReturnValue(mockDownloadTask as any);
|
|
||||||
jest
|
|
||||||
.spyOn(downloadTaskRepository, 'save')
|
|
||||||
.mockResolvedValue(mockDownloadTask as any);
|
|
||||||
|
|
||||||
const result = await service.createDownloadTask(mockParams);
|
|
||||||
|
|
||||||
expect(downloadTaskRepository.create).toHaveBeenCalledWith({
|
|
||||||
surveyId: mockParams.surveyId,
|
|
||||||
surveyPath: mockParams.responseSchema.surveyPath,
|
|
||||||
fileSize: '计算中',
|
|
||||||
creatorId: mockParams.creatorId,
|
|
||||||
creator: mockParams.creator,
|
|
||||||
params: {
|
|
||||||
...mockParams.params,
|
|
||||||
title: mockParams.responseSchema.title,
|
|
||||||
},
|
|
||||||
filename: expect.any(String),
|
|
||||||
status: DOWNLOAD_TASK_STATUS.WAITING,
|
|
||||||
});
|
|
||||||
expect(downloadTaskRepository.save).toHaveBeenCalled();
|
|
||||||
expect(result).toEqual(mockTaskId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getDownloadTaskList', () => {
|
|
||||||
it('should return task list and total count', async () => {
|
|
||||||
const mockCreatorId = 'creator1';
|
|
||||||
const mockTasks = [{ _id: '1' }, { _id: '2' }];
|
|
||||||
const mockTotal = 2;
|
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(downloadTaskRepository, 'findAndCount')
|
|
||||||
.mockResolvedValue([mockTasks as any, mockTotal]);
|
|
||||||
|
|
||||||
const result = await service.getDownloadTaskList({
|
|
||||||
creatorId: mockCreatorId,
|
|
||||||
pageIndex: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(downloadTaskRepository.findAndCount).toHaveBeenCalledWith({
|
|
||||||
where: {
|
|
||||||
creatorId: mockCreatorId,
|
|
||||||
isDeleted: { $ne: true },
|
|
||||||
},
|
|
||||||
take: 10,
|
|
||||||
skip: 0,
|
|
||||||
order: { createdAt: -1 },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result).toEqual({
|
|
||||||
total: mockTotal,
|
|
||||||
list: mockTasks,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getDownloadTaskById', () => {
|
|
||||||
it('should return task by id', async () => {
|
|
||||||
const mockTaskId = new ObjectId().toString();
|
|
||||||
const mockTask = { _id: new ObjectId(mockTaskId) };
|
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(downloadTaskRepository, 'find')
|
|
||||||
.mockResolvedValue([mockTask as any]);
|
|
||||||
|
|
||||||
const result = await service.getDownloadTaskById({ taskId: mockTaskId });
|
|
||||||
|
|
||||||
expect(downloadTaskRepository.find).toHaveBeenCalledWith({
|
|
||||||
where: { _id: new ObjectId(mockTaskId) },
|
|
||||||
});
|
|
||||||
expect(result).toEqual(mockTask);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return null if task is not found', async () => {
|
|
||||||
const mockTaskId = new ObjectId().toString();
|
|
||||||
|
|
||||||
jest.spyOn(downloadTaskRepository, 'find').mockResolvedValue([]);
|
|
||||||
|
|
||||||
const result = await service.getDownloadTaskById({ taskId: mockTaskId });
|
|
||||||
|
|
||||||
expect(result).toBeNull();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('deleteDownloadTask', () => {
|
|
||||||
it('should mark task as deleted and set deletedAt', async () => {
|
|
||||||
const mockTaskId = new ObjectId().toString();
|
|
||||||
const mockOperator = 'operatorName';
|
|
||||||
const mockOperatorId = 'operatorId1';
|
|
||||||
const mockUpdateResult = { matchedCount: 1 };
|
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(downloadTaskRepository, 'updateOne')
|
|
||||||
.mockResolvedValue(mockUpdateResult as any);
|
|
||||||
|
|
||||||
const result = await service.deleteDownloadTask({
|
|
||||||
taskId: mockTaskId,
|
|
||||||
operator: mockOperator,
|
|
||||||
operatorId: mockOperatorId,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(downloadTaskRepository.updateOne).toHaveBeenCalledWith(
|
|
||||||
{ _id: new ObjectId(mockTaskId) },
|
|
||||||
{
|
|
||||||
$set: {
|
|
||||||
isDeleted: true,
|
|
||||||
operator: mockOperator,
|
|
||||||
operatorId: mockOperatorId,
|
|
||||||
deletedAt: expect.any(Date),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
expect(result).toEqual(mockUpdateResult);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('processDownloadTask', () => {
|
|
||||||
it('should push task to queue and execute if not executing', async () => {
|
|
||||||
const mockTaskId = new ObjectId().toString();
|
|
||||||
jest.spyOn(service, 'executeTask').mockImplementation(jest.fn());
|
|
||||||
|
|
||||||
service.processDownloadTask({ taskId: mockTaskId });
|
|
||||||
|
|
||||||
expect(DownloadTaskService.taskList).toContain(mockTaskId);
|
|
||||||
expect(service.executeTask).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle already executing case', async () => {
|
|
||||||
const mockTaskId = new ObjectId().toString();
|
|
||||||
DownloadTaskService.isExecuting = true;
|
|
||||||
jest.spyOn(service, 'executeTask').mockImplementation(jest.fn());
|
|
||||||
|
|
||||||
service.processDownloadTask({ taskId: mockTaskId });
|
|
||||||
|
|
||||||
expect(DownloadTaskService.taskList).toContain(mockTaskId);
|
|
||||||
expect(service.executeTask).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('executeTask', () => {
|
|
||||||
it('should process and execute tasks in queue', async () => {
|
|
||||||
const mockTaskId = new ObjectId().toString();
|
|
||||||
DownloadTaskService.taskList.push(mockTaskId);
|
|
||||||
|
|
||||||
jest.spyOn(service, 'getDownloadTaskById').mockResolvedValue({
|
|
||||||
_id: new ObjectId(mockTaskId),
|
|
||||||
isDeleted: false,
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
jest.spyOn(service, 'handleDownloadTask').mockResolvedValue(undefined);
|
|
||||||
|
|
||||||
await service.executeTask();
|
|
||||||
|
|
||||||
expect(service.getDownloadTaskById).toHaveBeenCalledWith({
|
|
||||||
taskId: mockTaskId,
|
|
||||||
});
|
|
||||||
expect(service.handleDownloadTask).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should stop executing when queue is empty', async () => {
|
|
||||||
DownloadTaskService.taskList = [];
|
|
||||||
await service.executeTask();
|
|
||||||
expect(DownloadTaskService.isExecuting).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -14,8 +14,8 @@ export const mockSensitiveResponseSchema: ResponseSchema = {
|
|||||||
date: 1710399368439,
|
date: 1710399368439,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
createdAt: 1710399368440,
|
createDate: 1710399368440,
|
||||||
updatedAt: 1710399368440,
|
updateDate: 1710399368440,
|
||||||
title: '加密全流程',
|
title: '加密全流程',
|
||||||
surveyPath: 'EBzdmnSp',
|
surveyPath: 'EBzdmnSp',
|
||||||
code: {
|
code: {
|
||||||
@ -32,7 +32,7 @@ export const mockSensitiveResponseSchema: ResponseSchema = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
baseConf: {
|
baseConf: {
|
||||||
beginTime: '2024-03-14 14:54:41',
|
begTime: '2024-03-14 14:54:41',
|
||||||
endTime: '2034-03-14 14:54:41',
|
endTime: '2034-03-14 14:54:41',
|
||||||
language: 'chinese',
|
language: 'chinese',
|
||||||
tLimit: 0,
|
tLimit: 0,
|
||||||
@ -44,17 +44,6 @@ export const mockSensitiveResponseSchema: ResponseSchema = {
|
|||||||
logoImageWidth: '60%',
|
logoImageWidth: '60%',
|
||||||
},
|
},
|
||||||
skinConf: {
|
skinConf: {
|
||||||
backgroundConf: {
|
|
||||||
color: '#fff',
|
|
||||||
type: 'color',
|
|
||||||
image: '',
|
|
||||||
},
|
|
||||||
themeConf: {
|
|
||||||
color: '#ffa600',
|
|
||||||
},
|
|
||||||
contentConf: {
|
|
||||||
opacity: 100,
|
|
||||||
},
|
|
||||||
skinColor: '#4a4c5b',
|
skinColor: '#4a4c5b',
|
||||||
inputBgColor: '#ffffff',
|
inputBgColor: '#ffffff',
|
||||||
},
|
},
|
||||||
@ -295,7 +284,7 @@ export const mockSensitiveResponseSchema: ResponseSchema = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
pageId: '65f29f3192862d6a9067ad1c',
|
pageId: '65f29f3192862d6a9067ad1c',
|
||||||
} as unknown as ResponseSchema;
|
} as ResponseSchema;
|
||||||
|
|
||||||
export const mockResponseSchema: ResponseSchema = {
|
export const mockResponseSchema: ResponseSchema = {
|
||||||
_id: new ObjectId('65b0d46e04d5db18534c0f7c'),
|
_id: new ObjectId('65b0d46e04d5db18534c0f7c'),
|
||||||
@ -326,7 +315,7 @@ export const mockResponseSchema: ResponseSchema = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
baseConf: {
|
baseConf: {
|
||||||
beginTime: '2024-01-23 21:59:05',
|
begTime: '2024-01-23 21:59:05',
|
||||||
endTime: '2034-01-23 21:59:05',
|
endTime: '2034-01-23 21:59:05',
|
||||||
language: 'chinese',
|
language: 'chinese',
|
||||||
tLimit: 0,
|
tLimit: 0,
|
||||||
@ -338,17 +327,6 @@ export const mockResponseSchema: ResponseSchema = {
|
|||||||
logoImageWidth: '60%',
|
logoImageWidth: '60%',
|
||||||
},
|
},
|
||||||
skinConf: {
|
skinConf: {
|
||||||
backgroundConf: {
|
|
||||||
color: '#fff',
|
|
||||||
type: 'color',
|
|
||||||
image: '',
|
|
||||||
},
|
|
||||||
themeConf: {
|
|
||||||
color: '#ffa600',
|
|
||||||
},
|
|
||||||
contentConf: {
|
|
||||||
opacity: 100,
|
|
||||||
},
|
|
||||||
skinColor: '#4a4c5b',
|
skinColor: '#4a4c5b',
|
||||||
inputBgColor: '#ffffff',
|
inputBgColor: '#ffffff',
|
||||||
},
|
},
|
||||||
@ -654,6 +632,6 @@ export const mockResponseSchema: ResponseSchema = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
pageId: '65afc62904d5db18534c0f78',
|
pageId: '65afc62904d5db18534c0f78',
|
||||||
createdAt: 1710340841289,
|
createDate: 1710340841289,
|
||||||
updatedAt: 1710340841289.0,
|
updateDate: 1710340841289.0,
|
||||||
} as unknown as ResponseSchema;
|
} as ResponseSchema;
|
||||||
|
@ -1,87 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { SessionController } from '../controllers/session.controller';
|
|
||||||
import { SessionService } from '../services/session.service';
|
|
||||||
import { Logger } from 'src/logger';
|
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
|
||||||
import { Authentication } from 'src/guards/authentication.guard';
|
|
||||||
import { SurveyGuard } from 'src/guards/survey.guard';
|
|
||||||
import { SessionGuard } from 'src/guards/session.guard';
|
|
||||||
|
|
||||||
describe('SessionController', () => {
|
|
||||||
let controller: SessionController;
|
|
||||||
let sessionService: jest.Mocked<SessionService>;
|
|
||||||
let logger: jest.Mocked<Logger>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
controllers: [SessionController],
|
|
||||||
providers: [
|
|
||||||
{
|
|
||||||
provide: SessionService,
|
|
||||||
useValue: {
|
|
||||||
create: jest.fn(),
|
|
||||||
updateSessionToEditing: jest.fn(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: Logger,
|
|
||||||
useValue: {
|
|
||||||
error: jest.fn(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
.overrideGuard(Authentication)
|
|
||||||
.useValue({ canActivate: () => true })
|
|
||||||
.overrideGuard(SurveyGuard)
|
|
||||||
.useValue({ canActivate: () => true })
|
|
||||||
.overrideGuard(SessionGuard)
|
|
||||||
.useValue({ canActivate: () => true })
|
|
||||||
.compile();
|
|
||||||
|
|
||||||
controller = module.get<SessionController>(SessionController);
|
|
||||||
sessionService = module.get<jest.Mocked<SessionService>>(SessionService);
|
|
||||||
logger = module.get<jest.Mocked<Logger>>(Logger);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create a session', async () => {
|
|
||||||
const reqBody = { surveyId: '123' };
|
|
||||||
const req = { user: { _id: 'userId' } };
|
|
||||||
const session: any = { _id: 'sessionId' };
|
|
||||||
|
|
||||||
sessionService.create.mockResolvedValue(session);
|
|
||||||
|
|
||||||
const result = await controller.create(reqBody, req);
|
|
||||||
|
|
||||||
expect(sessionService.create).toHaveBeenCalledWith({
|
|
||||||
surveyId: '123',
|
|
||||||
userId: 'userId',
|
|
||||||
});
|
|
||||||
expect(result).toEqual({ code: 200, data: { sessionId: 'sessionId' } });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw an exception if validation fails', async () => {
|
|
||||||
const reqBody = { surveyId: null };
|
|
||||||
const req = { user: { _id: 'userId' } };
|
|
||||||
|
|
||||||
try {
|
|
||||||
await controller.create(reqBody, req);
|
|
||||||
} catch (error) {
|
|
||||||
expect(error).toBeInstanceOf(HttpException);
|
|
||||||
expect(logger.error).toHaveBeenCalled();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should seize a session', async () => {
|
|
||||||
const req = {
|
|
||||||
sessionInfo: { _id: 'sessionId', surveyId: 'surveyId' },
|
|
||||||
};
|
|
||||||
|
|
||||||
await controller.seize(req);
|
|
||||||
|
|
||||||
expect(sessionService.updateSessionToEditing).toHaveBeenCalledWith({
|
|
||||||
sessionId: 'sessionId',
|
|
||||||
surveyId: 'surveyId',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,144 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { MongoRepository } from 'typeorm';
|
|
||||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
|
||||||
import { SessionService } from '../services/session.service';
|
|
||||||
import { Session } from 'src/models/session.entity';
|
|
||||||
import { ObjectId } from 'mongodb';
|
|
||||||
import { SESSION_STATUS } from 'src/enums/surveySessionStatus';
|
|
||||||
|
|
||||||
describe('SessionService', () => {
|
|
||||||
let service: SessionService;
|
|
||||||
let repository: MongoRepository<Session>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
providers: [
|
|
||||||
SessionService,
|
|
||||||
{
|
|
||||||
provide: getRepositoryToken(Session),
|
|
||||||
useClass: MongoRepository,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
service = module.get<SessionService>(SessionService);
|
|
||||||
repository = module.get<MongoRepository<Session>>(
|
|
||||||
getRepositoryToken(Session),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('create', () => {
|
|
||||||
it('should create and save a new session', async () => {
|
|
||||||
const mockSession = {
|
|
||||||
surveyId: 'survey123',
|
|
||||||
userId: 'user123',
|
|
||||||
status: SESSION_STATUS.DEACTIVATED,
|
|
||||||
};
|
|
||||||
|
|
||||||
const createdSession: any = { ...mockSession, _id: new ObjectId() };
|
|
||||||
jest.spyOn(repository, 'create').mockReturnValue(createdSession);
|
|
||||||
jest.spyOn(repository, 'save').mockResolvedValue(createdSession);
|
|
||||||
|
|
||||||
const result = await service.create({
|
|
||||||
surveyId: mockSession.surveyId,
|
|
||||||
userId: mockSession.userId,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result).toEqual(createdSession);
|
|
||||||
expect(repository.create).toHaveBeenCalledWith(mockSession);
|
|
||||||
expect(repository.save).toHaveBeenCalledWith(createdSession);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('findOne', () => {
|
|
||||||
it('should find a session by id', async () => {
|
|
||||||
const sessionId = '65afc62904d5db18534c0f78';
|
|
||||||
const foundSession = {
|
|
||||||
_id: new ObjectId(sessionId),
|
|
||||||
surveyId: 'survey123',
|
|
||||||
userId: 'user123',
|
|
||||||
status: SESSION_STATUS.ACTIVATED,
|
|
||||||
};
|
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(repository, 'findOne')
|
|
||||||
.mockResolvedValue(foundSession as Session);
|
|
||||||
|
|
||||||
const result = await service.findOne(sessionId);
|
|
||||||
|
|
||||||
expect(result).toEqual(foundSession);
|
|
||||||
expect(repository.findOne).toHaveBeenCalledWith({
|
|
||||||
where: { _id: new ObjectId(sessionId) },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('findLatestEditingOne', () => {
|
|
||||||
it('should find the latest editing session for a survey', async () => {
|
|
||||||
const surveyId = 'survey123';
|
|
||||||
const latestSession = {
|
|
||||||
_id: new ObjectId(),
|
|
||||||
surveyId: surveyId,
|
|
||||||
userId: 'user123',
|
|
||||||
status: SESSION_STATUS.ACTIVATED,
|
|
||||||
};
|
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(repository, 'findOne')
|
|
||||||
.mockResolvedValue(latestSession as Session);
|
|
||||||
|
|
||||||
const result = await service.findLatestEditingOne({ surveyId });
|
|
||||||
|
|
||||||
expect(result).toEqual(latestSession);
|
|
||||||
expect(repository.findOne).toHaveBeenCalledWith({
|
|
||||||
where: {
|
|
||||||
surveyId,
|
|
||||||
status: SESSION_STATUS.ACTIVATED,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('updateSessionToEditing', () => {
|
|
||||||
it('should update a session to editing and deactivate other sessions', async () => {
|
|
||||||
const sessionId = '65afc62904d5db18534c0f78';
|
|
||||||
const surveyId = 'survey123';
|
|
||||||
|
|
||||||
const updateResult: any = { affected: 1 };
|
|
||||||
const updateManyResult = { modifiedCount: 1 };
|
|
||||||
|
|
||||||
jest.spyOn(repository, 'update').mockResolvedValue(updateResult);
|
|
||||||
jest.spyOn(repository, 'updateMany').mockResolvedValue(updateManyResult);
|
|
||||||
|
|
||||||
const result = await service.updateSessionToEditing({
|
|
||||||
sessionId,
|
|
||||||
surveyId,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result).toEqual([updateResult, updateManyResult]);
|
|
||||||
expect(repository.update).toHaveBeenCalledWith(
|
|
||||||
{ _id: new ObjectId(sessionId) },
|
|
||||||
{
|
|
||||||
status: SESSION_STATUS.ACTIVATED,
|
|
||||||
updatedAt: expect.any(Date),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
expect(repository.updateMany).toHaveBeenCalledWith(
|
|
||||||
{
|
|
||||||
surveyId,
|
|
||||||
_id: { $ne: new ObjectId(sessionId) },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$set: {
|
|
||||||
status: SESSION_STATUS.DEACTIVATED,
|
|
||||||
updatedAt: expect.any(Date),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -5,20 +5,19 @@ import { SurveyConfService } from '../services/surveyConf.service';
|
|||||||
import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service';
|
import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service';
|
||||||
import { ContentSecurityService } from '../services/contentSecurity.service';
|
import { ContentSecurityService } from '../services/contentSecurity.service';
|
||||||
import { SurveyHistoryService } from '../services/surveyHistory.service';
|
import { SurveyHistoryService } from '../services/surveyHistory.service';
|
||||||
import { SessionService } from '../services/session.service';
|
|
||||||
import { UserService } from '../../auth/services/user.service';
|
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
import { Logger } from 'src/logger';
|
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
||||||
|
import { SurveyConf } from 'src/models/surveyConf.entity';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { Authentication } from 'src/guards/authentication.guard';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
|
import { LoggerProvider } from 'src/logger/logger.provider';
|
||||||
|
|
||||||
jest.mock('../services/surveyMeta.service');
|
jest.mock('../services/surveyMeta.service');
|
||||||
jest.mock('../services/surveyConf.service');
|
jest.mock('../services/surveyConf.service');
|
||||||
jest.mock('../../surveyResponse/services/responseScheme.service');
|
jest.mock('../../surveyResponse/services/responseScheme.service');
|
||||||
jest.mock('../services/contentSecurity.service');
|
jest.mock('../services/contentSecurity.service');
|
||||||
jest.mock('../services/surveyHistory.service');
|
jest.mock('../services/surveyHistory.service');
|
||||||
jest.mock('../services/session.service');
|
|
||||||
jest.mock('../../auth/services/user.service');
|
|
||||||
jest.mock('src/guards/authentication.guard');
|
jest.mock('src/guards/authentication.guard');
|
||||||
jest.mock('src/guards/survey.guard');
|
jest.mock('src/guards/survey.guard');
|
||||||
jest.mock('src/guards/workspace.guard');
|
jest.mock('src/guards/workspace.guard');
|
||||||
@ -28,6 +27,8 @@ describe('SurveyController', () => {
|
|||||||
let surveyMetaService: SurveyMetaService;
|
let surveyMetaService: SurveyMetaService;
|
||||||
let surveyConfService: SurveyConfService;
|
let surveyConfService: SurveyConfService;
|
||||||
let responseSchemaService: ResponseSchemaService;
|
let responseSchemaService: ResponseSchemaService;
|
||||||
|
let contentSecurityService: ContentSecurityService;
|
||||||
|
let surveyHistoryService: SurveyHistoryService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
@ -38,21 +39,7 @@ describe('SurveyController', () => {
|
|||||||
ResponseSchemaService,
|
ResponseSchemaService,
|
||||||
ContentSecurityService,
|
ContentSecurityService,
|
||||||
SurveyHistoryService,
|
SurveyHistoryService,
|
||||||
SessionService,
|
LoggerProvider,
|
||||||
UserService,
|
|
||||||
{
|
|
||||||
provide: Logger,
|
|
||||||
useValue: {
|
|
||||||
error: jest.fn(),
|
|
||||||
info: jest.fn(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: Authentication,
|
|
||||||
useClass: jest.fn().mockImplementation(() => ({
|
|
||||||
canActivate: () => true,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
@ -62,11 +49,17 @@ describe('SurveyController', () => {
|
|||||||
responseSchemaService = module.get<ResponseSchemaService>(
|
responseSchemaService = module.get<ResponseSchemaService>(
|
||||||
ResponseSchemaService,
|
ResponseSchemaService,
|
||||||
);
|
);
|
||||||
|
contentSecurityService = module.get<ContentSecurityService>(
|
||||||
|
ContentSecurityService,
|
||||||
|
);
|
||||||
|
surveyHistoryService =
|
||||||
|
module.get<SurveyHistoryService>(SurveyHistoryService);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getBannerData', () => {
|
describe('getBannerData', () => {
|
||||||
it('should return banner data', async () => {
|
it('should return banner data', async () => {
|
||||||
const result = await controller.getBannerData();
|
const result = await controller.getBannerData();
|
||||||
|
|
||||||
expect(result.code).toBe(200);
|
expect(result.code).toBe(200);
|
||||||
expect(result.data).toBeDefined();
|
expect(result.data).toBeDefined();
|
||||||
});
|
});
|
||||||
@ -78,16 +71,33 @@ describe('SurveyController', () => {
|
|||||||
surveyType: 'normal',
|
surveyType: 'normal',
|
||||||
remark: '问卷调研',
|
remark: '问卷调研',
|
||||||
title: '问卷调研',
|
title: '问卷调研',
|
||||||
};
|
} as SurveyMeta;
|
||||||
|
|
||||||
const newId = new ObjectId();
|
const newId = new ObjectId();
|
||||||
jest.spyOn(surveyMetaService, 'createSurveyMeta').mockResolvedValue({
|
jest
|
||||||
|
.spyOn(surveyMetaService, 'createSurveyMeta')
|
||||||
|
.mockImplementation(() => {
|
||||||
|
const result = {
|
||||||
_id: newId,
|
_id: newId,
|
||||||
} as any);
|
} as SurveyMeta;
|
||||||
|
return Promise.resolve(result);
|
||||||
jest.spyOn(surveyConfService, 'createSurveyConf').mockResolvedValue({
|
});
|
||||||
|
jest
|
||||||
|
.spyOn(surveyConfService, 'createSurveyConf')
|
||||||
|
.mockImplementation(
|
||||||
|
(params: {
|
||||||
|
surveyId: string;
|
||||||
|
surveyType: string;
|
||||||
|
createMethod: string;
|
||||||
|
createFrom: string;
|
||||||
|
}) => {
|
||||||
|
const result = {
|
||||||
_id: new ObjectId(),
|
_id: new ObjectId(),
|
||||||
} as any);
|
pageId: params.surveyId,
|
||||||
|
code: {},
|
||||||
|
} as SurveyConf;
|
||||||
|
return Promise.resolve(result);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const result = await controller.createSurvey(surveyInfo, {
|
const result = await controller.createSurvey(surveyInfo, {
|
||||||
user: { username: 'testUser', _id: new ObjectId() },
|
user: { username: 'testUser', _id: new ObjectId() },
|
||||||
@ -101,15 +111,13 @@ describe('SurveyController', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if validation fails', async () => {
|
|
||||||
const surveyInfo = {}; // Invalid data
|
|
||||||
await expect(
|
|
||||||
controller.createSurvey(surveyInfo as any, { user: {} }),
|
|
||||||
).rejects.toThrow(HttpException);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create a new survey by copy', async () => {
|
it('should create a new survey by copy', async () => {
|
||||||
const existsSurveyId = new ObjectId();
|
const existsSurveyId = new ObjectId();
|
||||||
|
const existsSurveyMeta = {
|
||||||
|
_id: existsSurveyId,
|
||||||
|
surveyType: 'exam',
|
||||||
|
owner: 'testUser',
|
||||||
|
} as SurveyMeta;
|
||||||
const params = {
|
const params = {
|
||||||
surveyType: 'normal',
|
surveyType: 'normal',
|
||||||
remark: '问卷调研',
|
remark: '问卷调研',
|
||||||
@ -118,15 +126,19 @@ describe('SurveyController', () => {
|
|||||||
createFrom: existsSurveyId.toString(),
|
createFrom: existsSurveyId.toString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(surveyMetaService, 'createSurveyMeta')
|
||||||
|
.mockImplementation(() => {
|
||||||
|
const result = {
|
||||||
|
_id: new ObjectId(),
|
||||||
|
} as SurveyMeta;
|
||||||
|
return Promise.resolve(result);
|
||||||
|
});
|
||||||
|
|
||||||
const request = {
|
const request = {
|
||||||
user: { username: 'testUser', _id: new ObjectId() },
|
user: { username: 'testUser', _id: new ObjectId() },
|
||||||
surveyMeta: { _id: existsSurveyId, surveyType: 'exam' },
|
surveyMeta: existsSurveyMeta,
|
||||||
};
|
}; // 模拟请求对象,根据实际情况进行调整
|
||||||
|
|
||||||
jest.spyOn(surveyMetaService, 'createSurveyMeta').mockResolvedValue({
|
|
||||||
_id: new ObjectId(),
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
const result = await controller.createSurvey(params, request);
|
const result = await controller.createSurvey(params, request);
|
||||||
expect(result?.data?.id).toBeDefined();
|
expect(result?.data?.id).toBeDefined();
|
||||||
});
|
});
|
||||||
@ -135,30 +147,48 @@ describe('SurveyController', () => {
|
|||||||
describe('updateConf', () => {
|
describe('updateConf', () => {
|
||||||
it('should update survey configuration', async () => {
|
it('should update survey configuration', async () => {
|
||||||
const surveyId = new ObjectId();
|
const surveyId = new ObjectId();
|
||||||
|
const surveyMeta = {
|
||||||
|
_id: surveyId,
|
||||||
|
surveyType: 'exam',
|
||||||
|
owner: 'testUser',
|
||||||
|
} as SurveyMeta;
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(surveyConfService, 'saveSurveyConf')
|
||||||
|
.mockResolvedValue(undefined);
|
||||||
|
jest
|
||||||
|
.spyOn(surveyHistoryService, 'addHistory')
|
||||||
|
.mockResolvedValue(undefined);
|
||||||
|
|
||||||
const reqBody = {
|
const reqBody = {
|
||||||
surveyId: surveyId.toString(),
|
surveyId: surveyId.toString(),
|
||||||
configData: {
|
configData: {
|
||||||
/* ... your config data here ... */
|
bannerConf: {
|
||||||
|
titleConfig: {},
|
||||||
|
bannerConfig: {},
|
||||||
|
},
|
||||||
|
baseConf: {
|
||||||
|
begTime: '2024-01-23 21:59:05',
|
||||||
|
endTime: '2034-01-23 21:59:05',
|
||||||
|
},
|
||||||
|
bottomConf: { logoImage: '/imgs/Logo.webp', logoImageWidth: '60%' },
|
||||||
|
skinConf: { skinColor: '#4a4c5b', inputBgColor: '#ffffff' },
|
||||||
|
submitConf: {},
|
||||||
|
dataConf: {
|
||||||
|
dataList: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
sessionId: 'mock-session-id',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await controller.updateConf(reqBody, {
|
const result = await controller.updateConf(reqBody, {
|
||||||
user: { username: 'testUser', _id: 'testUserId' },
|
user: { username: 'testUser', _id: 'testUserId' },
|
||||||
surveyMeta: { _id: surveyId },
|
surveyMeta,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
code: 200,
|
code: 200,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if validation fails', async () => {
|
|
||||||
const reqBody = {}; // Invalid data
|
|
||||||
await expect(
|
|
||||||
controller.updateConf(reqBody, { user: {} }),
|
|
||||||
).rejects.toThrow(HttpException);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('deleteSurvey', () => {
|
describe('deleteSurvey', () => {
|
||||||
@ -168,7 +198,7 @@ describe('SurveyController', () => {
|
|||||||
_id: surveyId,
|
_id: surveyId,
|
||||||
surveyType: 'exam',
|
surveyType: 'exam',
|
||||||
owner: 'testUser',
|
owner: 'testUser',
|
||||||
};
|
} as SurveyMeta;
|
||||||
|
|
||||||
jest
|
jest
|
||||||
.spyOn(surveyMetaService, 'deleteSurveyMeta')
|
.spyOn(surveyMetaService, 'deleteSurveyMeta')
|
||||||
@ -178,10 +208,13 @@ describe('SurveyController', () => {
|
|||||||
.mockResolvedValue(undefined);
|
.mockResolvedValue(undefined);
|
||||||
|
|
||||||
const result = await controller.deleteSurvey({
|
const result = await controller.deleteSurvey({
|
||||||
|
user: { username: 'testUser' },
|
||||||
surveyMeta,
|
surveyMeta,
|
||||||
user: { username: 'testUser', _id: new ObjectId() },
|
|
||||||
});
|
});
|
||||||
expect(result).toEqual({ code: 200 });
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
code: 200,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -192,102 +225,115 @@ describe('SurveyController', () => {
|
|||||||
_id: surveyId,
|
_id: surveyId,
|
||||||
surveyType: 'exam',
|
surveyType: 'exam',
|
||||||
owner: 'testUser',
|
owner: 'testUser',
|
||||||
};
|
} as SurveyMeta;
|
||||||
|
|
||||||
jest
|
jest
|
||||||
.spyOn(surveyConfService, 'getSurveyConfBySurveyId')
|
.spyOn(surveyConfService, 'getSurveyConfBySurveyId')
|
||||||
.mockResolvedValue({} as any);
|
.mockResolvedValue(
|
||||||
const result = await controller.getSurvey(
|
Promise.resolve({
|
||||||
{ surveyId: surveyId.toString() },
|
_id: new ObjectId(),
|
||||||
{
|
pageId: surveyId.toString(),
|
||||||
surveyMeta,
|
} as SurveyConf),
|
||||||
user: { username: 'testUser', _id: new ObjectId() },
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const request = {
|
||||||
|
user: { username: 'testUser', _id: new ObjectId() },
|
||||||
|
surveyMeta,
|
||||||
|
};
|
||||||
|
const result = await controller.getSurvey(
|
||||||
|
{ surveyId: surveyId.toString() },
|
||||||
|
request,
|
||||||
|
);
|
||||||
expect(result?.data?.surveyMetaRes).toBeDefined();
|
expect(result?.data?.surveyMetaRes).toBeDefined();
|
||||||
expect(result?.data?.surveyConfRes).toBeDefined();
|
expect(result?.data?.surveyConfRes).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('publishSurvey', () => {
|
describe('publishSurvey', () => {
|
||||||
it('should publish a survey successfully', async () => {
|
it('should publish a survey success', async () => {
|
||||||
const surveyId = new ObjectId();
|
const surveyId = new ObjectId();
|
||||||
const surveyMeta = {
|
const surveyMeta = {
|
||||||
_id: surveyId,
|
_id: surveyId,
|
||||||
surveyType: 'exam',
|
surveyType: 'exam',
|
||||||
owner: 'testUser',
|
owner: 'testUser',
|
||||||
isDeleted: false,
|
} as SurveyMeta;
|
||||||
};
|
|
||||||
|
|
||||||
jest
|
jest
|
||||||
.spyOn(surveyConfService, 'getSurveyConfBySurveyId')
|
.spyOn(surveyConfService, 'getSurveyConfBySurveyId')
|
||||||
.mockResolvedValue({
|
.mockResolvedValue(
|
||||||
code: {},
|
Promise.resolve({
|
||||||
} as any);
|
_id: new ObjectId(),
|
||||||
|
pageId: surveyId.toString(),
|
||||||
|
} as SurveyConf),
|
||||||
|
);
|
||||||
|
|
||||||
jest
|
jest
|
||||||
.spyOn(surveyConfService, 'getSurveyContentByCode')
|
.spyOn(surveyConfService, 'getSurveyContentByCode')
|
||||||
.mockResolvedValue({ text: '' });
|
.mockResolvedValue({
|
||||||
|
text: '题目1',
|
||||||
|
});
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(contentSecurityService, 'isForbiddenContent')
|
||||||
|
.mockResolvedValue(false);
|
||||||
jest
|
jest
|
||||||
.spyOn(surveyMetaService, 'publishSurveyMeta')
|
.spyOn(surveyMetaService, 'publishSurveyMeta')
|
||||||
.mockResolvedValue(undefined);
|
.mockResolvedValue(undefined);
|
||||||
|
jest
|
||||||
|
.spyOn(responseSchemaService, 'publishResponseSchema')
|
||||||
|
.mockResolvedValue(undefined);
|
||||||
|
jest
|
||||||
|
.spyOn(surveyHistoryService, 'addHistory')
|
||||||
|
.mockResolvedValue(undefined);
|
||||||
|
|
||||||
const result = await controller.publishSurvey(
|
const result = await controller.publishSurvey(
|
||||||
{ surveyId: surveyId.toString() },
|
{ surveyId: surveyId.toString() },
|
||||||
{ surveyMeta, user: { username: 'testUser', _id: new ObjectId() } },
|
{ user: { username: 'testUser', _id: 'testUserId' }, surveyMeta },
|
||||||
);
|
);
|
||||||
expect(result.code).toBe(200);
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
code: 200,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if the survey is deleted', async () => {
|
it('should not publish a survey with forbidden content', async () => {
|
||||||
const surveyId = new ObjectId();
|
const surveyId = new ObjectId();
|
||||||
const surveyMeta = { _id: surveyId, isDeleted: true };
|
const surveyMeta = {
|
||||||
|
_id: surveyId,
|
||||||
|
surveyType: 'normal',
|
||||||
|
owner: 'testUser',
|
||||||
|
} as SurveyMeta;
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(surveyConfService, 'getSurveyConfBySurveyId')
|
||||||
|
.mockResolvedValue(
|
||||||
|
Promise.resolve({
|
||||||
|
_id: new ObjectId(),
|
||||||
|
pageId: surveyId.toString(),
|
||||||
|
} as SurveyConf),
|
||||||
|
);
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(surveyConfService, 'getSurveyContentByCode')
|
||||||
|
.mockResolvedValue({
|
||||||
|
text: '违禁词',
|
||||||
|
});
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(contentSecurityService, 'isForbiddenContent')
|
||||||
|
.mockResolvedValue(true);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
controller.publishSurvey(
|
controller.publishSurvey(
|
||||||
{ surveyId: surveyId.toString() },
|
{ surveyId: surveyId.toString() },
|
||||||
{ surveyMeta, user: { username: 'testUser' } },
|
{ user: { username: 'testUser', _id: 'testUserId' }, surveyMeta },
|
||||||
),
|
),
|
||||||
).rejects.toThrow(HttpException);
|
).rejects.toThrow(
|
||||||
});
|
new HttpException(
|
||||||
});
|
'问卷存在非法关键字,不允许发布',
|
||||||
|
EXCEPTION_CODE.SURVEY_CONTENT_NOT_ALLOW,
|
||||||
// New tests for additional methods
|
),
|
||||||
describe('pausingSurvey', () => {
|
);
|
||||||
it('should pause the survey successfully', async () => {
|
|
||||||
const surveyMeta = { surveyPath: 'some/path' };
|
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(surveyMetaService, 'pausingSurveyMeta')
|
|
||||||
.mockResolvedValue(undefined);
|
|
||||||
jest
|
|
||||||
.spyOn(responseSchemaService, 'pausingResponseSchema')
|
|
||||||
.mockResolvedValue(undefined);
|
|
||||||
|
|
||||||
const result = await controller.pausingSurvey({
|
|
||||||
surveyMeta,
|
|
||||||
user: { username: 'testUser' },
|
|
||||||
});
|
|
||||||
expect(result.code).toBe(200);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getPreviewSchema', () => {
|
|
||||||
it('should get the preview schema successfully', async () => {
|
|
||||||
const surveyId = new ObjectId();
|
|
||||||
jest
|
|
||||||
.spyOn(surveyConfService, 'getSurveyConfBySurveyId')
|
|
||||||
.mockResolvedValue({} as any);
|
|
||||||
jest.spyOn(surveyMetaService, 'getSurveyById').mockResolvedValue({
|
|
||||||
title: 'Test Survey',
|
|
||||||
surveyPath: 'some/path',
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
const result = await controller.getPreviewSchema({
|
|
||||||
surveyPath: surveyId.toString(),
|
|
||||||
});
|
|
||||||
expect(result.code).toBe(200);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -7,7 +7,7 @@ import { SurveyMetaService } from '../services/surveyMeta.service';
|
|||||||
|
|
||||||
import { UserService } from 'src/modules/auth/services/user.service';
|
import { UserService } from 'src/modules/auth/services/user.service';
|
||||||
import { AuthService } from 'src/modules/auth/services/auth.service';
|
import { AuthService } from 'src/modules/auth/services/auth.service';
|
||||||
import { Logger } from 'src/logger';
|
import { XiaojuSurveyLogger } from 'src/logger';
|
||||||
|
|
||||||
jest.mock('src/guards/authentication.guard');
|
jest.mock('src/guards/authentication.guard');
|
||||||
jest.mock('src/guards/survey.guard');
|
jest.mock('src/guards/survey.guard');
|
||||||
@ -49,7 +49,7 @@ describe('SurveyHistoryController', () => {
|
|||||||
useClass: jest.fn().mockImplementation(() => ({})),
|
useClass: jest.fn().mockImplementation(() => ({})),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: Logger,
|
provide: XiaojuSurveyLogger,
|
||||||
useValue: {
|
useValue: {
|
||||||
info: jest.fn(),
|
info: jest.fn(),
|
||||||
error: jest.fn(),
|
error: jest.fn(),
|
||||||
|
@ -42,7 +42,7 @@ describe('SurveyHistoryService', () => {
|
|||||||
msgContent: undefined,
|
msgContent: undefined,
|
||||||
},
|
},
|
||||||
baseConf: {
|
baseConf: {
|
||||||
beginTime: '',
|
begTime: '',
|
||||||
endTime: '',
|
endTime: '',
|
||||||
answerBegTime: '',
|
answerBegTime: '',
|
||||||
answerEndTime: '',
|
answerEndTime: '',
|
||||||
@ -83,6 +83,7 @@ describe('SurveyHistoryService', () => {
|
|||||||
schema,
|
schema,
|
||||||
type,
|
type,
|
||||||
user,
|
user,
|
||||||
|
sessionId: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(spyCreate).toHaveBeenCalledWith({
|
expect(spyCreate).toHaveBeenCalledWith({
|
||||||
@ -121,9 +122,9 @@ describe('SurveyHistoryService', () => {
|
|||||||
},
|
},
|
||||||
take: 100,
|
take: 100,
|
||||||
order: {
|
order: {
|
||||||
createdAt: -1,
|
createDate: -1,
|
||||||
},
|
},
|
||||||
select: ['createdAt', 'operator', 'type', '_id'],
|
select: ['createDate', 'operator', 'type', '_id'],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { SurveyMetaController } from '../controllers/surveyMeta.controller';
|
import { SurveyMetaController } from '../controllers/surveyMeta.controller';
|
||||||
import { SurveyMetaService } from '../services/surveyMeta.service';
|
import { SurveyMetaService } from '../services/surveyMeta.service';
|
||||||
import { Logger } from 'src/logger';
|
import { LoggerProvider } from 'src/logger/logger.provider';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
import { CollaboratorService } from '../services/collaborator.service';
|
import { CollaboratorService } from '../services/collaborator.service';
|
||||||
@ -28,12 +28,7 @@ describe('SurveyMetaController', () => {
|
|||||||
.mockResolvedValue({ count: 0, data: [] }),
|
.mockResolvedValue({ count: 0, data: [] }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
LoggerProvider,
|
||||||
provide: Logger,
|
|
||||||
useValue: {
|
|
||||||
error() {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: CollaboratorService,
|
provide: CollaboratorService,
|
||||||
useValue: {
|
useValue: {
|
||||||
@ -59,25 +54,18 @@ describe('SurveyMetaController', () => {
|
|||||||
remark: '',
|
remark: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockUser = {
|
|
||||||
username: 'test-user',
|
|
||||||
_id: new ObjectId(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const req = {
|
const req = {
|
||||||
user: mockUser,
|
user: {
|
||||||
|
username: 'test-user',
|
||||||
|
},
|
||||||
surveyMeta: survey,
|
surveyMeta: survey,
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await controller.updateMeta(reqBody, req);
|
const result = await controller.updateMeta(reqBody, req);
|
||||||
|
|
||||||
expect(surveyMetaService.editSurveyMeta).toHaveBeenCalledWith({
|
expect(surveyMetaService.editSurveyMeta).toHaveBeenCalledWith({
|
||||||
operator: mockUser.username,
|
|
||||||
operatorId: mockUser._id.toString(),
|
|
||||||
survey: {
|
|
||||||
title: reqBody.title,
|
title: reqBody.title,
|
||||||
remark: reqBody.remark,
|
remark: reqBody.remark,
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toEqual({ code: 200 });
|
expect(result).toEqual({ code: 200 });
|
||||||
@ -123,15 +111,11 @@ describe('SurveyMetaController', () => {
|
|||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
_id: new ObjectId(),
|
_id: new ObjectId(),
|
||||||
createdAt: date,
|
createDate: date,
|
||||||
updatedAt: date,
|
updateDate: date,
|
||||||
curStatus: {
|
curStatus: {
|
||||||
date: date,
|
date: date,
|
||||||
},
|
},
|
||||||
subStatus: {
|
|
||||||
date: date,
|
|
||||||
},
|
|
||||||
surveyType: 'normal',
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -145,7 +129,10 @@ describe('SurveyMetaController', () => {
|
|||||||
count: 10,
|
count: 10,
|
||||||
data: expect.arrayContaining([
|
data: expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
createdAt: expect.stringMatching(
|
createDate: expect.stringMatching(
|
||||||
|
/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/,
|
||||||
|
),
|
||||||
|
updateDate: expect.stringMatching(
|
||||||
/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/,
|
/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/,
|
||||||
),
|
),
|
||||||
curStatus: expect.objectContaining({
|
curStatus: expect.objectContaining({
|
||||||
@ -153,17 +140,10 @@ describe('SurveyMetaController', () => {
|
|||||||
/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/,
|
/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/,
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
subStatus: expect.objectContaining({
|
|
||||||
date: expect.stringMatching(
|
|
||||||
/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/,
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
surveyType: 'normal',
|
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(surveyMetaService.getSurveyMetaList).toHaveBeenCalledWith({
|
expect(surveyMetaService.getSurveyMetaList).toHaveBeenCalledWith({
|
||||||
pageNum: queryInfo.curPage,
|
pageNum: queryInfo.curPage,
|
||||||
pageSize: queryInfo.pageSize,
|
pageSize: queryInfo.pageSize,
|
||||||
@ -190,7 +170,7 @@ describe('SurveyMetaController', () => {
|
|||||||
condition: [{ field: 'surveyType', value: 'normal' }],
|
condition: [{ field: 'surveyType', value: 'normal' }],
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
order: JSON.stringify([{ field: 'createdAt', value: -1 }]),
|
order: JSON.stringify([{ field: 'createDate', value: -1 }]),
|
||||||
};
|
};
|
||||||
const userId = new ObjectId().toString();
|
const userId = new ObjectId().toString();
|
||||||
const req = {
|
const req = {
|
||||||
@ -210,28 +190,8 @@ describe('SurveyMetaController', () => {
|
|||||||
surveyIdList: [],
|
surveyIdList: [],
|
||||||
userId,
|
userId,
|
||||||
filter: { surveyType: 'normal', title: { $regex: 'hahah' } },
|
filter: { surveyType: 'normal', title: { $regex: 'hahah' } },
|
||||||
order: { createdAt: -1 },
|
order: { createDate: -1 },
|
||||||
workspaceId: undefined,
|
workspaceId: undefined,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle Joi validation in getList', async () => {
|
|
||||||
const invalidQueryInfo: any = {
|
|
||||||
curPage: 'invalid',
|
|
||||||
pageSize: 10,
|
|
||||||
};
|
|
||||||
const req = {
|
|
||||||
user: {
|
|
||||||
username: 'test-user',
|
|
||||||
_id: new ObjectId(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
await controller.getList(invalidQueryInfo, req);
|
|
||||||
} catch (error) {
|
|
||||||
expect(error).toBeInstanceOf(HttpException);
|
|
||||||
expect(error.code).toBe(EXCEPTION_CODE.PARAMETER_ERROR);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -2,16 +2,18 @@ import { Test, TestingModule } from '@nestjs/testing';
|
|||||||
import { SurveyMetaService } from '../services/surveyMeta.service';
|
import { SurveyMetaService } from '../services/surveyMeta.service';
|
||||||
import { MongoRepository } from 'typeorm';
|
import { MongoRepository } from 'typeorm';
|
||||||
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
||||||
import { PluginManager } from 'src/securityPlugin/pluginManager';
|
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
||||||
|
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager';
|
||||||
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { RECORD_STATUS, RECORD_SUB_STATUS } from 'src/enums';
|
import { SurveyUtilPlugin } from 'src/securityPlugin/surveyUtilPlugin';
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
|
|
||||||
describe('SurveyMetaService', () => {
|
describe('SurveyMetaService', () => {
|
||||||
let service: SurveyMetaService;
|
let service: SurveyMetaService;
|
||||||
let surveyRepository: MongoRepository<SurveyMeta>;
|
let surveyRepository: MongoRepository<SurveyMeta>;
|
||||||
let pluginManager: PluginManager;
|
let pluginManager: XiaojuSurveyPluginManager;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
@ -24,11 +26,10 @@ describe('SurveyMetaService', () => {
|
|||||||
count: jest.fn(),
|
count: jest.fn(),
|
||||||
create: jest.fn(),
|
create: jest.fn(),
|
||||||
save: jest.fn(),
|
save: jest.fn(),
|
||||||
updateOne: jest.fn(),
|
|
||||||
findAndCount: jest.fn(),
|
findAndCount: jest.fn(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PluginManager,
|
PluginManagerProvider,
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
@ -36,19 +37,21 @@ describe('SurveyMetaService', () => {
|
|||||||
surveyRepository = module.get<MongoRepository<SurveyMeta>>(
|
surveyRepository = module.get<MongoRepository<SurveyMeta>>(
|
||||||
getRepositoryToken(SurveyMeta),
|
getRepositoryToken(SurveyMeta),
|
||||||
);
|
);
|
||||||
pluginManager = module.get<PluginManager>(PluginManager);
|
pluginManager = module.get<XiaojuSurveyPluginManager>(
|
||||||
|
XiaojuSurveyPluginManager,
|
||||||
|
);
|
||||||
|
pluginManager.registerPlugin(new SurveyUtilPlugin());
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getNewSurveyPath', () => {
|
describe('getNewSurveyPath', () => {
|
||||||
it('should generate a new survey path', async () => {
|
it('should generate a new survey path', async () => {
|
||||||
jest.spyOn(pluginManager, 'triggerHook').mockResolvedValueOnce('path1');
|
jest.spyOn(surveyRepository, 'count').mockResolvedValueOnce(1);
|
||||||
jest.spyOn(surveyRepository, 'count').mockResolvedValueOnce(0);
|
jest.spyOn(surveyRepository, 'count').mockResolvedValueOnce(0);
|
||||||
|
|
||||||
const surveyPath = await service.getNewSurveyPath();
|
const surveyPath = await service.getNewSurveyPath();
|
||||||
|
|
||||||
expect(surveyPath).toBe('path1');
|
expect(typeof surveyPath).toBe('string');
|
||||||
expect(pluginManager.triggerHook).toHaveBeenCalledTimes(1);
|
expect(surveyRepository.count).toHaveBeenCalledTimes(2);
|
||||||
expect(surveyRepository.count).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -62,11 +65,14 @@ describe('SurveyMetaService', () => {
|
|||||||
userId: new ObjectId().toString(),
|
userId: new ObjectId().toString(),
|
||||||
createMethod: '',
|
createMethod: '',
|
||||||
createFrom: '',
|
createFrom: '',
|
||||||
workspaceId: 'workspace1',
|
|
||||||
};
|
};
|
||||||
const newSurvey = new SurveyMeta();
|
const newSurvey = new SurveyMeta();
|
||||||
|
|
||||||
jest.spyOn(service, 'getNewSurveyPath').mockResolvedValue('path1');
|
const mockedSurveyPath = 'mockedSurveyPath';
|
||||||
|
jest
|
||||||
|
.spyOn(service, 'getNewSurveyPath')
|
||||||
|
.mockResolvedValue(mockedSurveyPath);
|
||||||
|
|
||||||
jest
|
jest
|
||||||
.spyOn(surveyRepository, 'create')
|
.spyOn(surveyRepository, 'create')
|
||||||
.mockImplementation(() => newSurvey);
|
.mockImplementation(() => newSurvey);
|
||||||
@ -78,118 +84,86 @@ describe('SurveyMetaService', () => {
|
|||||||
title: params.title,
|
title: params.title,
|
||||||
remark: params.remark,
|
remark: params.remark,
|
||||||
surveyType: params.surveyType,
|
surveyType: params.surveyType,
|
||||||
surveyPath: 'path1',
|
surveyPath: mockedSurveyPath,
|
||||||
creator: params.username,
|
creator: params.username,
|
||||||
creatorId: params.userId,
|
|
||||||
owner: params.username,
|
|
||||||
ownerId: params.userId,
|
ownerId: params.userId,
|
||||||
|
owner: params.username,
|
||||||
createMethod: params.createMethod,
|
createMethod: params.createMethod,
|
||||||
createFrom: params.createFrom,
|
createFrom: params.createFrom,
|
||||||
workspaceId: params.workspaceId,
|
|
||||||
});
|
});
|
||||||
expect(surveyRepository.save).toHaveBeenCalledWith(newSurvey);
|
expect(surveyRepository.save).toHaveBeenCalledWith(newSurvey);
|
||||||
expect(result).toEqual(newSurvey);
|
expect(result).toEqual(newSurvey);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('pausingSurveyMeta', () => {
|
|
||||||
it('should throw an exception if survey is in NEW status', async () => {
|
|
||||||
const survey = new SurveyMeta();
|
|
||||||
survey.curStatus = { status: RECORD_STATUS.NEW, date: Date.now() };
|
|
||||||
|
|
||||||
await expect(service.pausingSurveyMeta(survey)).rejects.toThrow(
|
|
||||||
HttpException,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should pause a survey and update subStatus', async () => {
|
|
||||||
const survey = new SurveyMeta();
|
|
||||||
survey.curStatus = { status: RECORD_STATUS.PUBLISHED, date: Date.now() };
|
|
||||||
survey.statusList = [];
|
|
||||||
|
|
||||||
jest.spyOn(surveyRepository, 'save').mockResolvedValue(survey);
|
|
||||||
|
|
||||||
const result = await service.pausingSurveyMeta(survey);
|
|
||||||
|
|
||||||
expect(survey.subStatus.status).toBe(RECORD_SUB_STATUS.PAUSING);
|
|
||||||
expect(survey.statusList.length).toBe(1);
|
|
||||||
expect(survey.statusList[0].status).toBe(RECORD_SUB_STATUS.PAUSING);
|
|
||||||
expect(surveyRepository.save).toHaveBeenCalledWith(survey);
|
|
||||||
expect(result).toEqual(survey);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('editSurveyMeta', () => {
|
describe('editSurveyMeta', () => {
|
||||||
it('should edit a survey meta and return it', async () => {
|
it('should edit a survey meta and return it if in NEW or EDITING status', async () => {
|
||||||
const survey = new SurveyMeta();
|
const survey = new SurveyMeta();
|
||||||
survey.curStatus = { status: RECORD_STATUS.PUBLISHED, date: Date.now() };
|
survey.curStatus = { status: RECORD_STATUS.PUBLISHED, date: Date.now() };
|
||||||
survey.statusList = [];
|
survey.statusList = [];
|
||||||
|
|
||||||
const operator = 'editor';
|
|
||||||
const operatorId = 'editorId';
|
|
||||||
|
|
||||||
jest.spyOn(surveyRepository, 'save').mockResolvedValue(survey);
|
jest.spyOn(surveyRepository, 'save').mockResolvedValue(survey);
|
||||||
|
|
||||||
const result = await service.editSurveyMeta({
|
const result = await service.editSurveyMeta(survey);
|
||||||
survey,
|
|
||||||
operator,
|
|
||||||
operatorId,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(survey.curStatus.status).toBe(RECORD_STATUS.EDITING);
|
expect(survey.curStatus.status).toEqual(RECORD_STATUS.EDITING);
|
||||||
expect(survey.statusList.length).toBe(1);
|
expect(survey.statusList.length).toBe(1);
|
||||||
expect(survey.statusList[0].status).toBe(RECORD_STATUS.EDITING);
|
expect(survey.statusList[0].status).toEqual(RECORD_STATUS.EDITING);
|
||||||
expect(survey.operator).toBe(operator);
|
|
||||||
expect(survey.operatorId).toBe(operatorId);
|
|
||||||
expect(surveyRepository.save).toHaveBeenCalledWith(survey);
|
expect(surveyRepository.save).toHaveBeenCalledWith(survey);
|
||||||
expect(result).toEqual(survey);
|
expect(result).toEqual(survey);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('deleteSurveyMeta', () => {
|
describe('deleteSurveyMeta', () => {
|
||||||
it('should mark a survey as deleted', async () => {
|
it('should delete survey meta and update status', async () => {
|
||||||
const surveyId = new ObjectId().toString();
|
// 准备假的SurveyMeta对象
|
||||||
const operator = 'deleter';
|
const survey = new SurveyMeta();
|
||||||
const operatorId = 'deleterId';
|
survey.curStatus = { status: RECORD_STATUS.NEW, date: Date.now() };
|
||||||
|
survey.statusList = [];
|
||||||
|
|
||||||
jest.spyOn(surveyRepository, 'updateOne').mockResolvedValue({
|
// 模拟save方法
|
||||||
matchedCount: 1,
|
jest.spyOn(surveyRepository, 'save').mockResolvedValue(survey);
|
||||||
modifiedCount: 1,
|
|
||||||
acknowledged: true,
|
// 调用要测试的方法
|
||||||
|
const result = await service.deleteSurveyMeta(survey);
|
||||||
|
|
||||||
|
// 验证结果
|
||||||
|
expect(result).toBe(survey);
|
||||||
|
expect(survey.curStatus.status).toBe(RECORD_STATUS.REMOVED);
|
||||||
|
expect(survey.statusList.length).toBe(1);
|
||||||
|
expect(survey.statusList[0].status).toBe(RECORD_STATUS.REMOVED);
|
||||||
|
expect(surveyRepository.save).toHaveBeenCalledTimes(1);
|
||||||
|
expect(surveyRepository.save).toHaveBeenCalledWith(survey);
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await service.deleteSurveyMeta({
|
it('should throw exception when survey is already removed', async () => {
|
||||||
surveyId,
|
// 准备假的SurveyMeta对象,其状态已设置为REMOVED
|
||||||
operator,
|
const survey = new SurveyMeta();
|
||||||
operatorId,
|
survey.curStatus = { status: RECORD_STATUS.REMOVED, date: Date.now() };
|
||||||
});
|
|
||||||
|
|
||||||
expect(surveyRepository.updateOne).toHaveBeenCalledWith(
|
// 调用要测试的方法并期待异常
|
||||||
{ _id: new ObjectId(surveyId) },
|
await expect(service.deleteSurveyMeta(survey)).rejects.toThrow(
|
||||||
{
|
HttpException,
|
||||||
$set: {
|
|
||||||
isDeleted: true,
|
|
||||||
operator,
|
|
||||||
operatorId,
|
|
||||||
deletedAt: expect.any(Date),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
expect(result.matchedCount).toBe(1);
|
|
||||||
|
// 验证save方法没有被调用
|
||||||
|
expect(surveyRepository.save).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getSurveyMetaList', () => {
|
describe('getSurveyMetaList', () => {
|
||||||
it('should return a list of survey metadata', async () => {
|
it('should return a list of survey metadata', async () => {
|
||||||
|
// 准备模拟数据
|
||||||
const mockData = [
|
const mockData = [
|
||||||
{ _id: 1, title: 'Survey 1' },
|
{ _id: 1, title: 'Survey 1' },
|
||||||
|
{ _id: 2, title: 'Survey 2' },
|
||||||
] as unknown as Array<SurveyMeta>;
|
] as unknown as Array<SurveyMeta>;
|
||||||
const mockCount = 1;
|
const mockCount = 2;
|
||||||
|
|
||||||
jest
|
jest
|
||||||
.spyOn(surveyRepository, 'findAndCount')
|
.spyOn(surveyRepository, 'findAndCount')
|
||||||
.mockResolvedValue([mockData, mockCount]);
|
.mockResolvedValue([mockData, mockCount]);
|
||||||
|
|
||||||
|
// 调用方法并检查返回值
|
||||||
const condition = {
|
const condition = {
|
||||||
pageNum: 1,
|
pageNum: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
@ -198,47 +172,40 @@ describe('SurveyMetaService', () => {
|
|||||||
filter: {},
|
filter: {},
|
||||||
order: {},
|
order: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await service.getSurveyMetaList(condition);
|
const result = await service.getSurveyMetaList(condition);
|
||||||
|
|
||||||
|
// 验证返回值
|
||||||
expect(result).toEqual({ data: mockData, count: mockCount });
|
expect(result).toEqual({ data: mockData, count: mockCount });
|
||||||
|
// 验证repository方法被正确调用
|
||||||
expect(surveyRepository.findAndCount).toHaveBeenCalledTimes(1);
|
expect(surveyRepository.findAndCount).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('publishSurveyMeta', () => {
|
describe('publishSurveyMeta', () => {
|
||||||
it('should publish a survey and update curStatus', async () => {
|
it('should publish a survey meta and add status to statusList', async () => {
|
||||||
const surveyMeta = new SurveyMeta();
|
// 准备模拟数据
|
||||||
surveyMeta.statusList = [];
|
const surveyMeta = {
|
||||||
|
id: 1,
|
||||||
|
title: 'Test Survey',
|
||||||
|
statusList: [],
|
||||||
|
} as unknown as SurveyMeta;
|
||||||
|
const savedSurveyMeta = {
|
||||||
|
...surveyMeta,
|
||||||
|
curStatus: {
|
||||||
|
status: RECORD_STATUS.PUBLISHED,
|
||||||
|
date: expect.any(Number),
|
||||||
|
},
|
||||||
|
} as unknown as SurveyMeta;
|
||||||
|
|
||||||
jest.spyOn(surveyRepository, 'save').mockResolvedValue(surveyMeta);
|
jest.spyOn(surveyRepository, 'save').mockResolvedValue(savedSurveyMeta);
|
||||||
|
|
||||||
|
// 调用方法并检查返回值
|
||||||
const result = await service.publishSurveyMeta({ surveyMeta });
|
const result = await service.publishSurveyMeta({ surveyMeta });
|
||||||
|
|
||||||
expect(surveyMeta.curStatus.status).toBe(RECORD_STATUS.PUBLISHED);
|
// 验证返回值
|
||||||
expect(surveyMeta.statusList.length).toBe(1);
|
expect(result).toEqual(savedSurveyMeta);
|
||||||
expect(surveyMeta.statusList[0].status).toBe(RECORD_STATUS.PUBLISHED);
|
// 验证repository方法被正确调用
|
||||||
expect(surveyRepository.save).toHaveBeenCalledWith(surveyMeta);
|
expect(surveyRepository.save).toHaveBeenCalledWith(savedSurveyMeta);
|
||||||
expect(result).toEqual(surveyMeta);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('countSurveyMetaByWorkspaceId', () => {
|
|
||||||
it('should return the count of surveys in a workspace', async () => {
|
|
||||||
const workspaceId = 'workspace1';
|
|
||||||
const mockCount = 5;
|
|
||||||
|
|
||||||
jest.spyOn(surveyRepository, 'count').mockResolvedValue(mockCount);
|
|
||||||
|
|
||||||
const result = await service.countSurveyMetaByWorkspaceId({
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result).toBe(mockCount);
|
|
||||||
expect(surveyRepository.count).toHaveBeenCalledWith({
|
|
||||||
workspaceId,
|
|
||||||
isDeleted: { $ne: true },
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -20,7 +20,7 @@ import {
|
|||||||
SURVEY_PERMISSION,
|
SURVEY_PERMISSION,
|
||||||
SURVEY_PERMISSION_DESCRIPTION,
|
SURVEY_PERMISSION_DESCRIPTION,
|
||||||
} from 'src/enums/surveyPermission';
|
} from 'src/enums/surveyPermission';
|
||||||
import { Logger } from 'src/logger';
|
import { XiaojuSurveyLogger } from 'src/logger';
|
||||||
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
|
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
|
||||||
|
|
||||||
import { CollaboratorService } from '../services/collaborator.service';
|
import { CollaboratorService } from '../services/collaborator.service';
|
||||||
@ -40,7 +40,7 @@ import { SurveyMetaService } from '../services/surveyMeta.service';
|
|||||||
export class CollaboratorController {
|
export class CollaboratorController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly collaboratorService: CollaboratorService,
|
private readonly collaboratorService: CollaboratorService,
|
||||||
private readonly logger: Logger,
|
private readonly logger: XiaojuSurveyLogger,
|
||||||
private readonly userService: UserService,
|
private readonly userService: UserService,
|
||||||
private readonly surveyMetaService: SurveyMetaService,
|
private readonly surveyMetaService: SurveyMetaService,
|
||||||
private readonly workspaceMemberServie: WorkspaceMemberService,
|
private readonly workspaceMemberServie: WorkspaceMemberService,
|
||||||
@ -185,14 +185,10 @@ export class CollaboratorController {
|
|||||||
userIdList: newCollaboratorUserIdList,
|
userIdList: newCollaboratorUserIdList,
|
||||||
});
|
});
|
||||||
this.logger.info('batchDelete:' + JSON.stringify(delRes));
|
this.logger.info('batchDelete:' + JSON.stringify(delRes));
|
||||||
const username = req.user.username;
|
|
||||||
const userId = req.user._id.toString();
|
|
||||||
if (Array.isArray(newCollaborator) && newCollaborator.length > 0) {
|
if (Array.isArray(newCollaborator) && newCollaborator.length > 0) {
|
||||||
const insertRes = await this.collaboratorService.batchCreate({
|
const insertRes = await this.collaboratorService.batchCreate({
|
||||||
surveyId: value.surveyId,
|
surveyId: value.surveyId,
|
||||||
collaboratorList: newCollaborator,
|
collaboratorList: newCollaborator,
|
||||||
creator: username,
|
|
||||||
creatorId: userId,
|
|
||||||
});
|
});
|
||||||
this.logger.info(`${JSON.stringify(insertRes)}`);
|
this.logger.info(`${JSON.stringify(insertRes)}`);
|
||||||
}
|
}
|
||||||
@ -202,8 +198,6 @@ export class CollaboratorController {
|
|||||||
this.collaboratorService.updateById({
|
this.collaboratorService.updateById({
|
||||||
collaboratorId: item._id,
|
collaboratorId: item._id,
|
||||||
permissions: item.permissions,
|
permissions: item.permissions,
|
||||||
operator: username,
|
|
||||||
operatorId: userId,
|
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -13,10 +13,10 @@ import { DataStatisticService } from '../services/dataStatistic.service';
|
|||||||
import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service';
|
import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service';
|
||||||
|
|
||||||
import { Authentication } from 'src/guards/authentication.guard';
|
import { Authentication } from 'src/guards/authentication.guard';
|
||||||
import { PluginManager } from 'src/securityPlugin/pluginManager';
|
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager';
|
||||||
import { SurveyGuard } from 'src/guards/survey.guard';
|
import { SurveyGuard } from 'src/guards/survey.guard';
|
||||||
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
||||||
import { Logger } from 'src/logger';
|
import { XiaojuSurveyLogger } from 'src/logger';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
import { AggregationStatisDto } from '../dto/aggregationStatis.dto';
|
import { AggregationStatisDto } from '../dto/aggregationStatis.dto';
|
||||||
@ -30,8 +30,8 @@ export class DataStatisticController {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly responseSchemaService: ResponseSchemaService,
|
private readonly responseSchemaService: ResponseSchemaService,
|
||||||
private readonly dataStatisticService: DataStatisticService,
|
private readonly dataStatisticService: DataStatisticService,
|
||||||
private readonly pluginManager: PluginManager,
|
private readonly pluginManager: XiaojuSurveyPluginManager,
|
||||||
private readonly logger: Logger,
|
private readonly logger: XiaojuSurveyLogger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get('/dataTable')
|
@Get('/dataTable')
|
||||||
@ -46,7 +46,7 @@ export class DataStatisticController {
|
|||||||
) {
|
) {
|
||||||
const { value, error } = await Joi.object({
|
const { value, error } = await Joi.object({
|
||||||
surveyId: Joi.string().required(),
|
surveyId: Joi.string().required(),
|
||||||
isMasked: Joi.boolean().default(true), // 默认true就是需要脱敏
|
isDesensitive: Joi.boolean().default(true), // 默认true就是需要脱敏
|
||||||
page: Joi.number().default(1),
|
page: Joi.number().default(1),
|
||||||
pageSize: Joi.number().default(10),
|
pageSize: Joi.number().default(10),
|
||||||
}).validate(queryInfo);
|
}).validate(queryInfo);
|
||||||
@ -54,7 +54,7 @@ export class DataStatisticController {
|
|||||||
this.logger.error(error.message);
|
this.logger.error(error.message);
|
||||||
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
}
|
}
|
||||||
const { surveyId, isMasked, page, pageSize } = value;
|
const { surveyId, isDesensitive, page, pageSize } = value;
|
||||||
const responseSchema =
|
const responseSchema =
|
||||||
await this.responseSchemaService.getResponseSchemaByPageId(surveyId);
|
await this.responseSchemaService.getResponseSchemaByPageId(surveyId);
|
||||||
const { total, listHead, listBody } =
|
const { total, listHead, listBody } =
|
||||||
@ -65,10 +65,10 @@ export class DataStatisticController {
|
|||||||
pageSize,
|
pageSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isMasked) {
|
if (isDesensitive) {
|
||||||
// 脱敏
|
// 脱敏
|
||||||
listBody.forEach((item) => {
|
listBody.forEach((item) => {
|
||||||
this.pluginManager.triggerHook('maskData', item);
|
this.pluginManager.triggerHook('desensitiveData', item);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
Request,
|
Request,
|
||||||
Post,
|
Post,
|
||||||
Body,
|
Body,
|
||||||
|
// Response,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||||
|
|
||||||
@ -16,9 +17,10 @@ import { ResponseSchemaService } from '../../surveyResponse/services/responseSch
|
|||||||
import { Authentication } from 'src/guards/authentication.guard';
|
import { Authentication } from 'src/guards/authentication.guard';
|
||||||
import { SurveyGuard } from 'src/guards/survey.guard';
|
import { SurveyGuard } from 'src/guards/survey.guard';
|
||||||
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
||||||
import { Logger } from 'src/logger';
|
import { XiaojuSurveyLogger } from 'src/logger';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
|
//后添加
|
||||||
import { DownloadTaskService } from '../services/downloadTask.service';
|
import { DownloadTaskService } from '../services/downloadTask.service';
|
||||||
import {
|
import {
|
||||||
GetDownloadTaskDto,
|
GetDownloadTaskDto,
|
||||||
@ -36,7 +38,7 @@ export class DownloadTaskController {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly responseSchemaService: ResponseSchemaService,
|
private readonly responseSchemaService: ResponseSchemaService,
|
||||||
private readonly downloadTaskService: DownloadTaskService,
|
private readonly downloadTaskService: DownloadTaskService,
|
||||||
private readonly logger: Logger,
|
private readonly logger: XiaojuSurveyLogger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Post('/createTask')
|
@Post('/createTask')
|
||||||
@ -55,15 +57,14 @@ export class DownloadTaskController {
|
|||||||
this.logger.error(error.message);
|
this.logger.error(error.message);
|
||||||
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
}
|
}
|
||||||
const { surveyId, isMasked } = value;
|
const { surveyId, isDesensitive } = value;
|
||||||
const responseSchema =
|
const responseSchema =
|
||||||
await this.responseSchemaService.getResponseSchemaByPageId(surveyId);
|
await this.responseSchemaService.getResponseSchemaByPageId(surveyId);
|
||||||
const id = await this.downloadTaskService.createDownloadTask({
|
const id = await this.downloadTaskService.createDownloadTask({
|
||||||
surveyId,
|
surveyId,
|
||||||
responseSchema,
|
responseSchema,
|
||||||
creatorId: req.user._id.toString(),
|
operatorId: req.user._id.toString(),
|
||||||
creator: req.user.username,
|
params: { isDesensitive },
|
||||||
params: { isMasked },
|
|
||||||
});
|
});
|
||||||
this.downloadTaskService.processDownloadTask({ taskId: id });
|
this.downloadTaskService.processDownloadTask({ taskId: id });
|
||||||
return {
|
return {
|
||||||
@ -87,7 +88,7 @@ export class DownloadTaskController {
|
|||||||
}
|
}
|
||||||
const { pageIndex, pageSize } = value;
|
const { pageIndex, pageSize } = value;
|
||||||
const { total, list } = await this.downloadTaskService.getDownloadTaskList({
|
const { total, list } = await this.downloadTaskService.getDownloadTaskList({
|
||||||
creatorId: req.user._id.toString(),
|
ownerId: req.user._id.toString(),
|
||||||
pageIndex,
|
pageIndex,
|
||||||
pageSize,
|
pageSize,
|
||||||
});
|
});
|
||||||
@ -98,7 +99,7 @@ export class DownloadTaskController {
|
|||||||
list: list.map((data) => {
|
list: list.map((data) => {
|
||||||
const item: Record<string, any> = {};
|
const item: Record<string, any> = {};
|
||||||
item.taskId = data._id.toString();
|
item.taskId = data._id.toString();
|
||||||
item.status = data.status;
|
item.curStatus = data.curStatus;
|
||||||
item.filename = data.filename;
|
item.filename = data.filename;
|
||||||
item.url = data.url;
|
item.url = data.url;
|
||||||
const fmt = 'YYYY-MM-DD HH:mm:ss';
|
const fmt = 'YYYY-MM-DD HH:mm:ss';
|
||||||
@ -114,7 +115,7 @@ export class DownloadTaskController {
|
|||||||
}
|
}
|
||||||
item.fileSize = `${size.toFixed()} ${units[unitIndex]}`;
|
item.fileSize = `${size.toFixed()} ${units[unitIndex]}`;
|
||||||
}
|
}
|
||||||
item.createdAt = moment(data.createdAt).format(fmt);
|
item.createDate = moment(Number(data.createDate)).format(fmt);
|
||||||
return item;
|
return item;
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -138,7 +139,7 @@ export class DownloadTaskController {
|
|||||||
throw new HttpException('任务不存在', EXCEPTION_CODE.PARAMETER_ERROR);
|
throw new HttpException('任务不存在', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (taskInfo.creatorId !== req.user._id.toString()) {
|
if (taskInfo.ownerId !== req.user._id.toString()) {
|
||||||
throw new NoPermissionException('没有权限');
|
throw new NoPermissionException('没有权限');
|
||||||
}
|
}
|
||||||
const res: Record<string, any> = {
|
const res: Record<string, any> = {
|
||||||
@ -171,14 +172,12 @@ export class DownloadTaskController {
|
|||||||
throw new HttpException('任务不存在', EXCEPTION_CODE.PARAMETER_ERROR);
|
throw new HttpException('任务不存在', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (taskInfo.creatorId !== req.user._id.toString()) {
|
if (taskInfo.ownerId !== req.user._id.toString()) {
|
||||||
throw new NoPermissionException('没有权限');
|
throw new NoPermissionException('没有权限');
|
||||||
}
|
}
|
||||||
|
|
||||||
const delRes = await this.downloadTaskService.deleteDownloadTask({
|
const delRes = await this.downloadTaskService.deleteDownloadTask({
|
||||||
taskId,
|
taskId,
|
||||||
operator: req.user.username,
|
|
||||||
operatorId: req.user._id.toString(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -15,7 +15,7 @@ import { SessionService } from '../services/session.service';
|
|||||||
import { Authentication } from 'src/guards/authentication.guard';
|
import { Authentication } from 'src/guards/authentication.guard';
|
||||||
import { SurveyGuard } from 'src/guards/survey.guard';
|
import { SurveyGuard } from 'src/guards/survey.guard';
|
||||||
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
||||||
import { Logger } from 'src/logger';
|
import { XiaojuSurveyLogger } from 'src/logger';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
import { SessionGuard } from 'src/guards/session.guard';
|
import { SessionGuard } from 'src/guards/session.guard';
|
||||||
@ -25,7 +25,7 @@ import { SessionGuard } from 'src/guards/session.guard';
|
|||||||
export class SessionController {
|
export class SessionController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly sessionService: SessionService,
|
private readonly sessionService: SessionService,
|
||||||
private readonly logger: Logger,
|
private readonly logger: XiaojuSurveyLogger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Post('/create')
|
@Post('/create')
|
||||||
@ -67,20 +67,19 @@ export class SessionController {
|
|||||||
|
|
||||||
@Post('/seize')
|
@Post('/seize')
|
||||||
@HttpCode(200)
|
@HttpCode(200)
|
||||||
@UseGuards(SessionGuard, SurveyGuard)
|
@UseGuards(SessionGuard)
|
||||||
@SetMetadata('sessionId', 'body.sessionId')
|
@SetMetadata('sessionId', 'body.sessionId')
|
||||||
@SetMetadata('surveyId', 'surveyId')
|
|
||||||
@SetMetadata('surveyPermission', [SURVEY_PERMISSION.SURVEY_CONF_MANAGE])
|
@SetMetadata('surveyPermission', [SURVEY_PERMISSION.SURVEY_CONF_MANAGE])
|
||||||
@UseGuards(Authentication)
|
@UseGuards(Authentication)
|
||||||
async seize(
|
async seize(
|
||||||
@Request()
|
@Request()
|
||||||
req,
|
req,
|
||||||
) {
|
) {
|
||||||
const sessionInfo = req.sessionInfo;
|
const saveSession = req.saveSession;
|
||||||
|
|
||||||
await this.sessionService.updateSessionToEditing({
|
await this.sessionService.updateSessionToEditing({
|
||||||
sessionId: sessionInfo._id.toString(),
|
sessionId: saveSession._id.toString(),
|
||||||
surveyId: sessionInfo.surveyId,
|
surveyId: saveSession.surveyId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -17,6 +17,7 @@ import { SurveyConfService } from '../services/surveyConf.service';
|
|||||||
import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service';
|
import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service';
|
||||||
import { ContentSecurityService } from '../services/contentSecurity.service';
|
import { ContentSecurityService } from '../services/contentSecurity.service';
|
||||||
import { SurveyHistoryService } from '../services/surveyHistory.service';
|
import { SurveyHistoryService } from '../services/surveyHistory.service';
|
||||||
|
import { CounterService } from 'src/modules/surveyResponse/services/counter.service';
|
||||||
|
|
||||||
import BannerData from '../template/banner/index.json';
|
import BannerData from '../template/banner/index.json';
|
||||||
import { CreateSurveyDto } from '../dto/createSurvey.dto';
|
import { CreateSurveyDto } from '../dto/createSurvey.dto';
|
||||||
@ -25,13 +26,14 @@ import { Authentication } from 'src/guards/authentication.guard';
|
|||||||
import { HISTORY_TYPE } from 'src/enums';
|
import { HISTORY_TYPE } from 'src/enums';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
import { Logger } from 'src/logger';
|
import { XiaojuSurveyLogger } from 'src/logger';
|
||||||
import { SurveyGuard } from 'src/guards/survey.guard';
|
import { SurveyGuard } from 'src/guards/survey.guard';
|
||||||
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
||||||
|
|
||||||
import { WorkspaceGuard } from 'src/guards/workspace.guard';
|
import { WorkspaceGuard } from 'src/guards/workspace.guard';
|
||||||
import { PERMISSION as WORKSPACE_PERMISSION } from 'src/enums/workspace';
|
import { PERMISSION as WORKSPACE_PERMISSION } from 'src/enums/workspace';
|
||||||
import { SessionService } from '../services/session.service';
|
import { SessionService } from '../services/session.service';
|
||||||
|
import { MemberType, WhitelistType } from 'src/interfaces/survey';
|
||||||
import { UserService } from 'src/modules/auth/services/user.service';
|
import { UserService } from 'src/modules/auth/services/user.service';
|
||||||
|
|
||||||
@ApiTags('survey')
|
@ApiTags('survey')
|
||||||
@ -43,7 +45,8 @@ export class SurveyController {
|
|||||||
private readonly responseSchemaService: ResponseSchemaService,
|
private readonly responseSchemaService: ResponseSchemaService,
|
||||||
private readonly contentSecurityService: ContentSecurityService,
|
private readonly contentSecurityService: ContentSecurityService,
|
||||||
private readonly surveyHistoryService: SurveyHistoryService,
|
private readonly surveyHistoryService: SurveyHistoryService,
|
||||||
private readonly logger: Logger,
|
private readonly logger: XiaojuSurveyLogger,
|
||||||
|
private readonly counterService: CounterService,
|
||||||
private readonly sessionService: SessionService,
|
private readonly sessionService: SessionService,
|
||||||
private readonly userService: UserService,
|
private readonly userService: UserService,
|
||||||
) {}
|
) {}
|
||||||
@ -144,7 +147,7 @@ export class SurveyController {
|
|||||||
|
|
||||||
if (latestEditingOne && latestEditingOne._id.toString() !== sessionId) {
|
if (latestEditingOne && latestEditingOne._id.toString() !== sessionId) {
|
||||||
const curSession = await this.sessionService.findOne(sessionId);
|
const curSession = await this.sessionService.findOne(sessionId);
|
||||||
if (curSession.createdAt <= latestEditingOne.updatedAt) {
|
if (curSession.createDate <= latestEditingOne.updateDate) {
|
||||||
// 在当前用户打开之后,被其他页面保存过了
|
// 在当前用户打开之后,被其他页面保存过了
|
||||||
const isSameOperator =
|
const isSameOperator =
|
||||||
latestEditingOne.userId === req.user._id.toString();
|
latestEditingOne.userId === req.user._id.toString();
|
||||||
@ -194,38 +197,11 @@ export class SurveyController {
|
|||||||
async deleteSurvey(@Request() req) {
|
async deleteSurvey(@Request() req) {
|
||||||
const surveyMeta = req.surveyMeta;
|
const surveyMeta = req.surveyMeta;
|
||||||
|
|
||||||
const delMetaRes = await this.surveyMetaService.deleteSurveyMeta({
|
await this.surveyMetaService.deleteSurveyMeta(surveyMeta);
|
||||||
surveyId: surveyMeta._id.toString(),
|
|
||||||
operator: req.user.username,
|
|
||||||
operatorId: req.user._id.toString(),
|
|
||||||
});
|
|
||||||
const delResponseRes =
|
|
||||||
await this.responseSchemaService.deleteResponseSchema({
|
await this.responseSchemaService.deleteResponseSchema({
|
||||||
surveyPath: surveyMeta.surveyPath,
|
surveyPath: surveyMeta.surveyPath,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.logger.info(JSON.stringify(delMetaRes));
|
|
||||||
this.logger.info(JSON.stringify(delResponseRes));
|
|
||||||
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@HttpCode(200)
|
|
||||||
@Post('/pausingSurvey')
|
|
||||||
@UseGuards(SurveyGuard)
|
|
||||||
@SetMetadata('surveyId', 'body.surveyId')
|
|
||||||
@SetMetadata('surveyPermission', [SURVEY_PERMISSION.SURVEY_CONF_MANAGE])
|
|
||||||
@UseGuards(Authentication)
|
|
||||||
async pausingSurvey(@Request() req) {
|
|
||||||
const surveyMeta = req.surveyMeta;
|
|
||||||
|
|
||||||
await this.surveyMetaService.pausingSurveyMeta(surveyMeta);
|
|
||||||
await this.responseSchemaService.pausingResponseSchema({
|
|
||||||
surveyPath: surveyMeta.surveyPath,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
};
|
};
|
||||||
@ -271,6 +247,16 @@ export class SurveyController {
|
|||||||
surveyMeta.isCollaborated = false;
|
surveyMeta.isCollaborated = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 白名单相关字段的默认值
|
||||||
|
const baseConf = surveyConf.code?.baseConf;
|
||||||
|
if (baseConf) {
|
||||||
|
baseConf.passwordSwitch = baseConf.passwordSwitch ?? false;
|
||||||
|
baseConf.password = baseConf.password ?? '';
|
||||||
|
baseConf.whitelistType = baseConf.whitelistType ?? WhitelistType.ALL;
|
||||||
|
baseConf.whitelist = baseConf.whitelist ?? [];
|
||||||
|
baseConf.memberType = baseConf.memberType ?? MemberType.MOBILE;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: {
|
data: {
|
||||||
@ -332,12 +318,6 @@ export class SurveyController {
|
|||||||
const username = req.user.username;
|
const username = req.user.username;
|
||||||
const surveyId = value.surveyId;
|
const surveyId = value.surveyId;
|
||||||
const surveyMeta = req.surveyMeta;
|
const surveyMeta = req.surveyMeta;
|
||||||
if (surveyMeta.isDeleted) {
|
|
||||||
throw new HttpException(
|
|
||||||
'问卷已删除,无法发布',
|
|
||||||
EXCEPTION_CODE.SURVEY_NOT_FOUND,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const surveyConf =
|
const surveyConf =
|
||||||
await this.surveyConfService.getSurveyConfBySurveyId(surveyId);
|
await this.surveyConfService.getSurveyConfBySurveyId(surveyId);
|
||||||
|
|
||||||
@ -363,8 +343,7 @@ export class SurveyController {
|
|||||||
pageId: surveyId,
|
pageId: surveyId,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 添加发布历史可以异步添加
|
await this.surveyHistoryService.addHistory({
|
||||||
this.surveyHistoryService.addHistory({
|
|
||||||
surveyId,
|
surveyId,
|
||||||
schema: surveyConf.code,
|
schema: surveyConf.code,
|
||||||
type: HISTORY_TYPE.PUBLISH_HIS,
|
type: HISTORY_TYPE.PUBLISH_HIS,
|
||||||
|
@ -14,15 +14,15 @@ import { SurveyHistoryService } from '../services/surveyHistory.service';
|
|||||||
import { Authentication } from 'src/guards/authentication.guard';
|
import { Authentication } from 'src/guards/authentication.guard';
|
||||||
import { SurveyGuard } from 'src/guards/survey.guard';
|
import { SurveyGuard } from 'src/guards/survey.guard';
|
||||||
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
||||||
import { Logger } from 'src/logger';
|
import { XiaojuSurveyLogger } from 'src/logger';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
@ApiTags('survey')
|
@ApiTags('survey')
|
||||||
@Controller('/api/surveyHistory')
|
@Controller('/api/surveyHisotry')
|
||||||
export class SurveyHistoryController {
|
export class SurveyHistoryController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly surveyHistoryService: SurveyHistoryService,
|
private readonly surveyHistoryService: SurveyHistoryService,
|
||||||
private readonly logger: Logger,
|
private readonly logger: XiaojuSurveyLogger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get('/getList')
|
@Get('/getList')
|
||||||
|
@ -19,7 +19,7 @@ import { getFilter, getOrder } from 'src/utils/surveyUtil';
|
|||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
import { Authentication } from 'src/guards/authentication.guard';
|
import { Authentication } from 'src/guards/authentication.guard';
|
||||||
import { Logger } from 'src/logger';
|
import { XiaojuSurveyLogger } from 'src/logger';
|
||||||
import { SurveyGuard } from 'src/guards/survey.guard';
|
import { SurveyGuard } from 'src/guards/survey.guard';
|
||||||
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
||||||
import { WorkspaceGuard } from 'src/guards/workspace.guard';
|
import { WorkspaceGuard } from 'src/guards/workspace.guard';
|
||||||
@ -33,7 +33,7 @@ import { CollaboratorService } from '../services/collaborator.service';
|
|||||||
export class SurveyMetaController {
|
export class SurveyMetaController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly surveyMetaService: SurveyMetaService,
|
private readonly surveyMetaService: SurveyMetaService,
|
||||||
private readonly logger: Logger,
|
private readonly logger: XiaojuSurveyLogger,
|
||||||
private readonly collaboratorService: CollaboratorService,
|
private readonly collaboratorService: CollaboratorService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -58,11 +58,7 @@ export class SurveyMetaController {
|
|||||||
survey.title = value.title;
|
survey.title = value.title;
|
||||||
survey.remark = value.remark;
|
survey.remark = value.remark;
|
||||||
|
|
||||||
await this.surveyMetaService.editSurveyMeta({
|
await this.surveyMetaService.editSurveyMeta(survey);
|
||||||
survey,
|
|
||||||
operator: req.user.username,
|
|
||||||
operatorId: req.user._id.toString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
@ -131,10 +127,9 @@ export class SurveyMetaController {
|
|||||||
if (!item.surveyType) {
|
if (!item.surveyType) {
|
||||||
item.surveyType = item.questionType || 'normal';
|
item.surveyType = item.questionType || 'normal';
|
||||||
}
|
}
|
||||||
item.createdAt = moment(item.createdAt).format(fmt);
|
item.createDate = moment(item.createDate).format(fmt);
|
||||||
|
item.updateDate = moment(item.updateDate).format(fmt);
|
||||||
item.curStatus.date = moment(item.curStatus.date).format(fmt);
|
item.curStatus.date = moment(item.curStatus.date).format(fmt);
|
||||||
item.subStatus.date = moment(item.subStatus.date).format(fmt);
|
|
||||||
item.updatedAt = moment(item.updatedAt).format(fmt);
|
|
||||||
const surveyId = item._id.toString();
|
const surveyId = item._id.toString();
|
||||||
if (cooperSurveyIdMap[surveyId]) {
|
if (cooperSurveyIdMap[surveyId]) {
|
||||||
item.isCollaborated = true;
|
item.isCollaborated = true;
|
||||||
|
@ -12,10 +12,10 @@ export class CreateSurveyDto {
|
|||||||
surveyType: string;
|
surveyType: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '创建方法', required: false })
|
@ApiProperty({ description: '创建方法', required: false })
|
||||||
createMethod?: string;
|
createMethod: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '创建来源', required: false })
|
@ApiProperty({ description: '创建来源', required: false })
|
||||||
createFrom?: string;
|
createFrom: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '问卷创建在哪个空间下', required: false })
|
@ApiProperty({ description: '问卷创建在哪个空间下', required: false })
|
||||||
workspaceId?: string;
|
workspaceId?: string;
|
||||||
|
@ -5,12 +5,12 @@ export class CreateDownloadDto {
|
|||||||
@ApiProperty({ description: '问卷id', required: true })
|
@ApiProperty({ description: '问卷id', required: true })
|
||||||
surveyId: string;
|
surveyId: string;
|
||||||
@ApiProperty({ description: '是否脱敏', required: false })
|
@ApiProperty({ description: '是否脱敏', required: false })
|
||||||
isMasked: boolean;
|
isDesensitive: boolean;
|
||||||
|
|
||||||
static validate(data) {
|
static validate(data) {
|
||||||
return Joi.object({
|
return Joi.object({
|
||||||
surveyId: Joi.string().required(),
|
surveyId: Joi.string().required(),
|
||||||
isMasked: Joi.boolean().allow(null).default(false),
|
isDesensitive: Joi.boolean().allow(null).default(false),
|
||||||
}).validate(data);
|
}).validate(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,14 +3,14 @@ import { Collaborator } from 'src/models/collaborator.entity';
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { MongoRepository } from 'typeorm';
|
import { MongoRepository } from 'typeorm';
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
import { Logger } from 'src/logger';
|
import { XiaojuSurveyLogger } from 'src/logger';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CollaboratorService {
|
export class CollaboratorService {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(Collaborator)
|
@InjectRepository(Collaborator)
|
||||||
private readonly collaboratorRepository: MongoRepository<Collaborator>,
|
private readonly collaboratorRepository: MongoRepository<Collaborator>,
|
||||||
private readonly logger: Logger,
|
private readonly logger: XiaojuSurveyLogger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async create({ surveyId, userId, permissions }) {
|
async create({ surveyId, userId, permissions }) {
|
||||||
@ -22,17 +22,12 @@ export class CollaboratorService {
|
|||||||
return this.collaboratorRepository.save(collaborator);
|
return this.collaboratorRepository.save(collaborator);
|
||||||
}
|
}
|
||||||
|
|
||||||
async batchCreate({ surveyId, collaboratorList, creator, creatorId }) {
|
async batchCreate({ surveyId, collaboratorList }) {
|
||||||
const now = new Date();
|
|
||||||
const res = await this.collaboratorRepository.insertMany(
|
const res = await this.collaboratorRepository.insertMany(
|
||||||
collaboratorList.map((item) => {
|
collaboratorList.map((item) => {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
surveyId,
|
surveyId,
|
||||||
createdAt: now,
|
|
||||||
updatedAt: now,
|
|
||||||
creator,
|
|
||||||
creatorId,
|
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -65,13 +60,7 @@ export class CollaboratorService {
|
|||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
async changeUserPermission({
|
async changeUserPermission({ userId, surveyId, permission }) {
|
||||||
userId,
|
|
||||||
surveyId,
|
|
||||||
permission,
|
|
||||||
operator,
|
|
||||||
operatorId,
|
|
||||||
}) {
|
|
||||||
const updateRes = await this.collaboratorRepository.updateOne(
|
const updateRes = await this.collaboratorRepository.updateOne(
|
||||||
{
|
{
|
||||||
surveyId,
|
surveyId,
|
||||||
@ -80,9 +69,6 @@ export class CollaboratorService {
|
|||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
permission,
|
permission,
|
||||||
operator,
|
|
||||||
operatorId,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -148,7 +134,7 @@ export class CollaboratorService {
|
|||||||
return delRes;
|
return delRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateById({ collaboratorId, permissions, operator, operatorId }) {
|
updateById({ collaboratorId, permissions }) {
|
||||||
return this.collaboratorRepository.updateOne(
|
return this.collaboratorRepository.updateOne(
|
||||||
{
|
{
|
||||||
_id: new ObjectId(collaboratorId),
|
_id: new ObjectId(collaboratorId),
|
||||||
@ -156,9 +142,6 @@ export class CollaboratorService {
|
|||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
permissions,
|
permissions,
|
||||||
operator,
|
|
||||||
operatorId,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -34,8 +34,8 @@ export class DataStatisticService {
|
|||||||
const dataListMap = keyBy(dataList, 'field');
|
const dataListMap = keyBy(dataList, 'field');
|
||||||
const where = {
|
const where = {
|
||||||
pageId: surveyId,
|
pageId: surveyId,
|
||||||
isDeleted: {
|
'curStatus.status': {
|
||||||
$ne: true,
|
$ne: 'removed',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const [surveyResponseList, total] =
|
const [surveyResponseList, total] =
|
||||||
@ -44,7 +44,7 @@ export class DataStatisticService {
|
|||||||
take: pageSize,
|
take: pageSize,
|
||||||
skip: (pageNum - 1) * pageSize,
|
skip: (pageNum - 1) * pageSize,
|
||||||
order: {
|
order: {
|
||||||
createdAt: -1,
|
createDate: -1,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -90,10 +90,10 @@ export class DataStatisticService {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...data,
|
...data,
|
||||||
diffTime: submitedData.diffTime
|
diffTime: (submitedData.diffTime / 1000).toFixed(2),
|
||||||
? (submitedData.diffTime / 1000).toFixed(2)
|
createDate: moment(submitedData.createDate).format(
|
||||||
: '0',
|
'YYYY-MM-DD HH:mm:ss',
|
||||||
createdAt: moment(submitedData.createdAt).format('YYYY-MM-DD HH:mm:ss'),
|
),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
@ -124,8 +124,8 @@ export class DataStatisticService {
|
|||||||
{
|
{
|
||||||
$match: {
|
$match: {
|
||||||
pageId: surveyId,
|
pageId: surveyId,
|
||||||
isDeleted: {
|
'curStatus.status': {
|
||||||
$ne: true,
|
$ne: 'removed',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -3,6 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm';
|
|||||||
import { MongoRepository } from 'typeorm';
|
import { MongoRepository } from 'typeorm';
|
||||||
import { ResponseSchema } from 'src/models/responseSchema.entity';
|
import { ResponseSchema } from 'src/models/responseSchema.entity';
|
||||||
import { DownloadTask } from 'src/models/downloadTask.entity';
|
import { DownloadTask } from 'src/models/downloadTask.entity';
|
||||||
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
import { ResponseSchemaService } from 'src/modules/surveyResponse/services/responseScheme.service';
|
import { ResponseSchemaService } from 'src/modules/surveyResponse/services/responseScheme.service';
|
||||||
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
||||||
@ -11,14 +12,13 @@ import xlsx from 'node-xlsx';
|
|||||||
import { load } from 'cheerio';
|
import { load } from 'cheerio';
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
import { FileService } from 'src/modules/file/services/file.service';
|
import { FileService } from 'src/modules/file/services/file.service';
|
||||||
import { Logger } from 'src/logger';
|
import { XiaojuSurveyLogger } from 'src/logger';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { DOWNLOAD_TASK_STATUS } from 'src/enums/downloadTaskStatus';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DownloadTaskService {
|
export class DownloadTaskService {
|
||||||
static taskList: Array<any> = [];
|
private static taskList: Array<any> = [];
|
||||||
static isExecuting: boolean = false;
|
private static isExecuting: boolean = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(DownloadTask)
|
@InjectRepository(DownloadTask)
|
||||||
@ -28,53 +28,49 @@ export class DownloadTaskService {
|
|||||||
private readonly surveyResponseRepository: MongoRepository<SurveyResponse>,
|
private readonly surveyResponseRepository: MongoRepository<SurveyResponse>,
|
||||||
private readonly dataStatisticService: DataStatisticService,
|
private readonly dataStatisticService: DataStatisticService,
|
||||||
private readonly fileService: FileService,
|
private readonly fileService: FileService,
|
||||||
private readonly logger: Logger,
|
private readonly logger: XiaojuSurveyLogger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async createDownloadTask({
|
async createDownloadTask({
|
||||||
surveyId,
|
surveyId,
|
||||||
responseSchema,
|
responseSchema,
|
||||||
creatorId,
|
operatorId,
|
||||||
creator,
|
|
||||||
params,
|
params,
|
||||||
}: {
|
}: {
|
||||||
surveyId: string;
|
surveyId: string;
|
||||||
responseSchema: ResponseSchema;
|
responseSchema: ResponseSchema;
|
||||||
creatorId: string;
|
operatorId: string;
|
||||||
creator: string;
|
|
||||||
params: any;
|
params: any;
|
||||||
}) {
|
}) {
|
||||||
const filename = `${responseSchema.title}-${params.isMasked ? '脱敏' : '原'}回收数据-${moment().format('YYYYMMDDHHmmss')}.xlsx`;
|
const filename = `${responseSchema.title}-${params.isDesensitive ? '脱敏' : '原'}回收数据-${moment().format('YYYYMMDDHHmmss')}.xlsx`;
|
||||||
const downloadTask = this.downloadTaskRepository.create({
|
const downloadTask = this.downloadTaskRepository.create({
|
||||||
surveyId,
|
surveyId,
|
||||||
surveyPath: responseSchema.surveyPath,
|
surveyPath: responseSchema.surveyPath,
|
||||||
fileSize: '计算中',
|
fileSize: '计算中',
|
||||||
creatorId,
|
ownerId: operatorId,
|
||||||
creator,
|
|
||||||
params: {
|
params: {
|
||||||
...params,
|
...params,
|
||||||
title: responseSchema.title,
|
title: responseSchema.title,
|
||||||
},
|
},
|
||||||
filename,
|
filename,
|
||||||
status: DOWNLOAD_TASK_STATUS.WAITING,
|
|
||||||
});
|
});
|
||||||
await this.downloadTaskRepository.save(downloadTask);
|
await this.downloadTaskRepository.save(downloadTask);
|
||||||
return downloadTask._id.toString();
|
return downloadTask._id.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDownloadTaskList({
|
async getDownloadTaskList({
|
||||||
creatorId,
|
ownerId,
|
||||||
pageIndex,
|
pageIndex,
|
||||||
pageSize,
|
pageSize,
|
||||||
}: {
|
}: {
|
||||||
creatorId: string;
|
ownerId: string;
|
||||||
pageIndex: number;
|
pageIndex: number;
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
}) {
|
}) {
|
||||||
const where = {
|
const where = {
|
||||||
creatorId,
|
ownerId,
|
||||||
isDeleted: {
|
'curStatus.status': {
|
||||||
$ne: true,
|
$ne: RECORD_STATUS.REMOVED,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const [surveyDownloadList, total] =
|
const [surveyDownloadList, total] =
|
||||||
@ -83,7 +79,7 @@ export class DownloadTaskService {
|
|||||||
take: pageSize,
|
take: pageSize,
|
||||||
skip: (pageIndex - 1) * pageSize,
|
skip: (pageIndex - 1) * pageSize,
|
||||||
order: {
|
order: {
|
||||||
createdAt: -1,
|
createDate: -1,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
@ -104,25 +100,24 @@ export class DownloadTaskService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteDownloadTask({
|
async deleteDownloadTask({ taskId }: { taskId: string }) {
|
||||||
taskId,
|
const curStatus = {
|
||||||
operator,
|
status: RECORD_STATUS.REMOVED,
|
||||||
operatorId,
|
date: Date.now(),
|
||||||
}: {
|
};
|
||||||
taskId: string;
|
|
||||||
operator: string;
|
|
||||||
operatorId: string;
|
|
||||||
}) {
|
|
||||||
return this.downloadTaskRepository.updateOne(
|
return this.downloadTaskRepository.updateOne(
|
||||||
{
|
{
|
||||||
_id: new ObjectId(taskId),
|
_id: new ObjectId(taskId),
|
||||||
|
'curStatus.status': {
|
||||||
|
$ne: RECORD_STATUS.REMOVED,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
isDeleted: true,
|
curStatus,
|
||||||
operator,
|
},
|
||||||
operatorId,
|
$push: {
|
||||||
deletedAt: new Date(),
|
statusList: curStatus as never,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -136,13 +131,11 @@ export class DownloadTaskService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async executeTask() {
|
private async executeTask() {
|
||||||
try {
|
try {
|
||||||
while (DownloadTaskService.taskList.length > 0) {
|
for (const taskId of DownloadTaskService.taskList) {
|
||||||
const taskId = DownloadTaskService.taskList.shift();
|
|
||||||
this.logger.info(`handle taskId: ${taskId}`);
|
|
||||||
const taskInfo = await this.getDownloadTaskById({ taskId });
|
const taskInfo = await this.getDownloadTaskById({ taskId });
|
||||||
if (!taskInfo || taskInfo.isDeleted) {
|
if (!taskInfo || taskInfo.curStatus.status === RECORD_STATUS.REMOVED) {
|
||||||
// 不存在或者已删除的,不处理
|
// 不存在或者已删除的,不处理
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -153,7 +146,7 @@ export class DownloadTaskService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleDownloadTask({ taskInfo }) {
|
private async handleDownloadTask({ taskInfo }) {
|
||||||
try {
|
try {
|
||||||
// 更新任务状态为计算中
|
// 更新任务状态为计算中
|
||||||
const updateRes = await this.downloadTaskRepository.updateOne(
|
const updateRes = await this.downloadTaskRepository.updateOne(
|
||||||
@ -162,8 +155,10 @@ export class DownloadTaskService {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
status: DOWNLOAD_TASK_STATUS.COMPUTING,
|
curStatus: {
|
||||||
updatedAt: new Date(),
|
status: RECORD_STATUS.COMOPUTETING,
|
||||||
|
date: Date.now(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -176,6 +171,9 @@ export class DownloadTaskService {
|
|||||||
await this.responseSchemaService.getResponseSchemaByPageId(surveyId);
|
await this.responseSchemaService.getResponseSchemaByPageId(surveyId);
|
||||||
const where = {
|
const where = {
|
||||||
pageId: surveyId,
|
pageId: surveyId,
|
||||||
|
'curStatus.status': {
|
||||||
|
$ne: 'removed',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const total = await this.surveyResponseRepository.count(where);
|
const total = await this.surveyResponseRepository.count(where);
|
||||||
const pageSize = 200;
|
const pageSize = 200;
|
||||||
@ -202,13 +200,9 @@ export class DownloadTaskService {
|
|||||||
for (const headItem of listHead) {
|
for (const headItem of listHead) {
|
||||||
const field = headItem.field;
|
const field = headItem.field;
|
||||||
const val = get(bodyItem, field, '');
|
const val = get(bodyItem, field, '');
|
||||||
if (typeof val === 'string') {
|
|
||||||
const $ = load(val);
|
const $ = load(val);
|
||||||
const text = $.text();
|
const text = $.text();
|
||||||
bodyData.push(text);
|
bodyData.push(text);
|
||||||
} else {
|
|
||||||
bodyData.push(val);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
xlsxBody.push(bodyData);
|
xlsxBody.push(bodyData);
|
||||||
}
|
}
|
||||||
@ -234,9 +228,14 @@ export class DownloadTaskService {
|
|||||||
configKey: 'SERVER_LOCAL_CONFIG',
|
configKey: 'SERVER_LOCAL_CONFIG',
|
||||||
file,
|
file,
|
||||||
pathPrefix: 'exportfile',
|
pathPrefix: 'exportfile',
|
||||||
filename: taskInfo.filename,
|
keepOriginFilename: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const curStatus = {
|
||||||
|
status: RECORD_STATUS.FINISHED,
|
||||||
|
date: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
// 更新计算结果
|
// 更新计算结果
|
||||||
const updateFinishRes = await this.downloadTaskRepository.updateOne(
|
const updateFinishRes = await this.downloadTaskRepository.updateOne(
|
||||||
{
|
{
|
||||||
@ -244,24 +243,32 @@ export class DownloadTaskService {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
status: DOWNLOAD_TASK_STATUS.SUCCEED,
|
curStatus,
|
||||||
url,
|
url,
|
||||||
fileKey: key,
|
fileKey: key,
|
||||||
fileSize: buffer.length,
|
fileSize: buffer.length,
|
||||||
updatedAt: new Date(),
|
},
|
||||||
|
$push: {
|
||||||
|
statusList: curStatus as never,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
this.logger.info(JSON.stringify(updateFinishRes));
|
this.logger.info(JSON.stringify(updateFinishRes));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
const curStatus = {
|
||||||
|
status: RECORD_STATUS.ERROR,
|
||||||
|
date: Date.now(),
|
||||||
|
};
|
||||||
await this.downloadTaskRepository.updateOne(
|
await this.downloadTaskRepository.updateOne(
|
||||||
{
|
{
|
||||||
_id: taskInfo._id,
|
_id: taskInfo._id,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
status: DOWNLOAD_TASK_STATUS.FAILED,
|
curStatus,
|
||||||
updatedAt: new Date(),
|
},
|
||||||
|
$push: {
|
||||||
|
statusList: curStatus as never,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -3,7 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm';
|
|||||||
import { MongoRepository } from 'typeorm';
|
import { MongoRepository } from 'typeorm';
|
||||||
import { Session } from 'src/models/session.entity';
|
import { Session } from 'src/models/session.entity';
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
import { SESSION_STATUS } from 'src/enums/surveySessionStatus';
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SessionService {
|
export class SessionService {
|
||||||
@ -16,7 +16,6 @@ export class SessionService {
|
|||||||
const session = this.sessionRepository.create({
|
const session = this.sessionRepository.create({
|
||||||
surveyId,
|
surveyId,
|
||||||
userId,
|
userId,
|
||||||
status: SESSION_STATUS.DEACTIVATED,
|
|
||||||
});
|
});
|
||||||
return this.sessionRepository.save(session);
|
return this.sessionRepository.save(session);
|
||||||
}
|
}
|
||||||
@ -33,20 +32,33 @@ export class SessionService {
|
|||||||
return this.sessionRepository.findOne({
|
return this.sessionRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
surveyId,
|
surveyId,
|
||||||
status: SESSION_STATUS.ACTIVATED,
|
'curStatus.status': {
|
||||||
|
$ne: RECORD_STATUS.NEW,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSessionToEditing({ sessionId, surveyId }) {
|
updateSessionToEditing({ sessionId, surveyId }) {
|
||||||
|
const now = Date.now();
|
||||||
|
const editingStatus = {
|
||||||
|
status: RECORD_STATUS.EDITING,
|
||||||
|
date: now,
|
||||||
|
};
|
||||||
|
const newStatus = {
|
||||||
|
status: RECORD_STATUS.NEW,
|
||||||
|
date: now,
|
||||||
|
};
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
this.sessionRepository.update(
|
this.sessionRepository.updateOne(
|
||||||
{
|
{
|
||||||
_id: new ObjectId(sessionId),
|
_id: new ObjectId(sessionId),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
status: SESSION_STATUS.ACTIVATED,
|
$set: {
|
||||||
updatedAt: new Date(),
|
curStatus: editingStatus,
|
||||||
|
updateDate: now,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
this.sessionRepository.updateMany(
|
this.sessionRepository.updateMany(
|
||||||
@ -58,8 +70,8 @@ export class SessionService {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
status: SESSION_STATUS.DEACTIVATED,
|
curStatus: newStatus,
|
||||||
updatedAt: new Date(),
|
updateDate: now,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -45,9 +45,9 @@ export class SurveyHistoryService {
|
|||||||
},
|
},
|
||||||
take: 100,
|
take: 100,
|
||||||
order: {
|
order: {
|
||||||
createdAt: -1,
|
createDate: -1,
|
||||||
},
|
},
|
||||||
select: ['createdAt', 'operator', 'type', '_id'],
|
select: ['createDate', 'operator', 'type', '_id'],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,18 +2,18 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { MongoRepository, FindOptionsOrder } from 'typeorm';
|
import { MongoRepository, FindOptionsOrder } from 'typeorm';
|
||||||
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
||||||
import { RECORD_STATUS, RECORD_SUB_STATUS } from 'src/enums';
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
import { PluginManager } from 'src/securityPlugin/pluginManager';
|
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SurveyMetaService {
|
export class SurveyMetaService {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(SurveyMeta)
|
@InjectRepository(SurveyMeta)
|
||||||
private readonly surveyRepository: MongoRepository<SurveyMeta>,
|
private readonly surveyRepository: MongoRepository<SurveyMeta>,
|
||||||
private readonly pluginManager: PluginManager,
|
private readonly pluginManager: XiaojuSurveyPluginManager,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getNewSurveyPath(): Promise<string> {
|
async getNewSurveyPath(): Promise<string> {
|
||||||
@ -65,7 +65,6 @@ export class SurveyMetaService {
|
|||||||
surveyType: surveyType,
|
surveyType: surveyType,
|
||||||
surveyPath,
|
surveyPath,
|
||||||
creator: username,
|
creator: username,
|
||||||
creatorId: userId,
|
|
||||||
owner: username,
|
owner: username,
|
||||||
ownerId: userId,
|
ownerId: userId,
|
||||||
createMethod,
|
createMethod,
|
||||||
@ -76,36 +75,11 @@ export class SurveyMetaService {
|
|||||||
return await this.surveyRepository.save(newSurvey);
|
return await this.surveyRepository.save(newSurvey);
|
||||||
}
|
}
|
||||||
|
|
||||||
async pausingSurveyMeta(survey: SurveyMeta) {
|
async editSurveyMeta(survey: SurveyMeta) {
|
||||||
if (survey?.curStatus?.status === RECORD_STATUS.NEW) {
|
if (
|
||||||
throw new HttpException(
|
survey.curStatus.status !== RECORD_STATUS.NEW &&
|
||||||
'问卷不能暂停',
|
survey.curStatus.status !== RECORD_STATUS.EDITING
|
||||||
EXCEPTION_CODE.SURVEY_STATUS_TRANSFORM_ERROR,
|
) {
|
||||||
);
|
|
||||||
}
|
|
||||||
const subCurStatus = {
|
|
||||||
status: RECORD_SUB_STATUS.PAUSING,
|
|
||||||
date: Date.now(),
|
|
||||||
};
|
|
||||||
survey.subStatus = subCurStatus;
|
|
||||||
if (Array.isArray(survey.statusList)) {
|
|
||||||
survey.statusList.push(subCurStatus);
|
|
||||||
} else {
|
|
||||||
survey.statusList = [subCurStatus];
|
|
||||||
}
|
|
||||||
return this.surveyRepository.save(survey);
|
|
||||||
}
|
|
||||||
|
|
||||||
async editSurveyMeta({
|
|
||||||
survey,
|
|
||||||
operator,
|
|
||||||
operatorId,
|
|
||||||
}: {
|
|
||||||
survey: SurveyMeta;
|
|
||||||
operator: string;
|
|
||||||
operatorId: string;
|
|
||||||
}) {
|
|
||||||
if (survey?.curStatus?.status !== RECORD_STATUS.EDITING) {
|
|
||||||
const newStatus = {
|
const newStatus = {
|
||||||
status: RECORD_STATUS.EDITING,
|
status: RECORD_STATUS.EDITING,
|
||||||
date: Date.now(),
|
date: Date.now(),
|
||||||
@ -113,27 +87,28 @@ export class SurveyMetaService {
|
|||||||
survey.curStatus = newStatus;
|
survey.curStatus = newStatus;
|
||||||
survey.statusList.push(newStatus);
|
survey.statusList.push(newStatus);
|
||||||
}
|
}
|
||||||
survey.updatedAt = new Date();
|
|
||||||
survey.operator = operator;
|
|
||||||
survey.operatorId = operatorId;
|
|
||||||
return this.surveyRepository.save(survey);
|
return this.surveyRepository.save(survey);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteSurveyMeta({ surveyId, operator, operatorId }) {
|
async deleteSurveyMeta(survey: SurveyMeta) {
|
||||||
return this.surveyRepository.updateOne(
|
if (survey.curStatus.status === RECORD_STATUS.REMOVED) {
|
||||||
{
|
throw new HttpException(
|
||||||
_id: new ObjectId(surveyId),
|
'问卷已删除,不能重复删除',
|
||||||
},
|
EXCEPTION_CODE.SURVEY_STATUS_TRANSFORM_ERROR,
|
||||||
{
|
|
||||||
$set: {
|
|
||||||
isDeleted: true,
|
|
||||||
operator,
|
|
||||||
operatorId,
|
|
||||||
deletedAt: new Date(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const newStatusInfo = {
|
||||||
|
status: RECORD_STATUS.REMOVED,
|
||||||
|
date: Date.now(),
|
||||||
|
};
|
||||||
|
survey.curStatus = newStatusInfo;
|
||||||
|
if (Array.isArray(survey.statusList)) {
|
||||||
|
survey.statusList.push(newStatusInfo);
|
||||||
|
} else {
|
||||||
|
survey.statusList = [newStatusInfo];
|
||||||
|
}
|
||||||
|
return this.surveyRepository.save(survey);
|
||||||
|
}
|
||||||
|
|
||||||
async getSurveyMetaList(condition: {
|
async getSurveyMetaList(condition: {
|
||||||
pageNum: number;
|
pageNum: number;
|
||||||
@ -150,16 +125,14 @@ export class SurveyMetaService {
|
|||||||
const skip = (pageNum - 1) * pageSize;
|
const skip = (pageNum - 1) * pageSize;
|
||||||
try {
|
try {
|
||||||
const query: Record<string, any> = Object.assign(
|
const query: Record<string, any> = Object.assign(
|
||||||
|
{},
|
||||||
{
|
{
|
||||||
isDeleted: {
|
'curStatus.status': {
|
||||||
$ne: true,
|
$ne: 'removed',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
condition.filter,
|
condition.filter,
|
||||||
);
|
);
|
||||||
if (condition.filter['curStatus.status']) {
|
|
||||||
query['subStatus.status'] = RECORD_SUB_STATUS.DEFAULT;
|
|
||||||
}
|
|
||||||
if (workspaceId) {
|
if (workspaceId) {
|
||||||
query.workspaceId = workspaceId;
|
query.workspaceId = workspaceId;
|
||||||
} else {
|
} else {
|
||||||
@ -187,8 +160,9 @@ export class SurveyMetaService {
|
|||||||
condition.order && Object.keys(condition.order).length > 0
|
condition.order && Object.keys(condition.order).length > 0
|
||||||
? (condition.order as FindOptionsOrder<SurveyMeta>)
|
? (condition.order as FindOptionsOrder<SurveyMeta>)
|
||||||
: ({
|
: ({
|
||||||
createdAt: -1,
|
createDate: -1,
|
||||||
} as FindOptionsOrder<SurveyMeta>);
|
} as FindOptionsOrder<SurveyMeta>);
|
||||||
|
|
||||||
const [data, count] = await this.surveyRepository.findAndCount({
|
const [data, count] = await this.surveyRepository.findAndCount({
|
||||||
where: query,
|
where: query,
|
||||||
skip,
|
skip,
|
||||||
@ -207,10 +181,6 @@ export class SurveyMetaService {
|
|||||||
date: Date.now(),
|
date: Date.now(),
|
||||||
};
|
};
|
||||||
surveyMeta.curStatus = curStatus;
|
surveyMeta.curStatus = curStatus;
|
||||||
surveyMeta.subStatus = {
|
|
||||||
status: RECORD_SUB_STATUS.DEFAULT,
|
|
||||||
date: Date.now(),
|
|
||||||
};
|
|
||||||
if (Array.isArray(surveyMeta.statusList)) {
|
if (Array.isArray(surveyMeta.statusList)) {
|
||||||
surveyMeta.statusList.push(curStatus);
|
surveyMeta.statusList.push(curStatus);
|
||||||
} else {
|
} else {
|
||||||
@ -222,8 +192,8 @@ export class SurveyMetaService {
|
|||||||
async countSurveyMetaByWorkspaceId({ workspaceId }) {
|
async countSurveyMetaByWorkspaceId({ workspaceId }) {
|
||||||
const total = await this.surveyRepository.count({
|
const total = await this.surveyRepository.count({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
isDeleted: {
|
'curStatus.status': {
|
||||||
$ne: true,
|
$ne: RECORD_STATUS.REMOVED,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return total;
|
return total;
|
||||||
|
@ -43,21 +43,26 @@
|
|||||||
"options": [
|
"options": [
|
||||||
{
|
{
|
||||||
"text": "选项1",
|
"text": "选项1",
|
||||||
|
"imageUrl": "",
|
||||||
"others": false,
|
"others": false,
|
||||||
"mustOthers": false,
|
"mustOthers": false,
|
||||||
"othersKey": "",
|
"othersKey": "",
|
||||||
"placeholderDesc": "",
|
"placeholderDesc": "",
|
||||||
"hash": "115019"
|
"hash": "115019",
|
||||||
|
"quota": "0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "选项2",
|
"text": "选项2",
|
||||||
|
"imageUrl": "",
|
||||||
"others": false,
|
"others": false,
|
||||||
"mustOthers": false,
|
"mustOthers": false,
|
||||||
"othersKey": "",
|
"othersKey": "",
|
||||||
"placeholderDesc": "",
|
"placeholderDesc": "",
|
||||||
"hash": "115020"
|
"hash": "115020",
|
||||||
}
|
"quota": "0"
|
||||||
]
|
}
|
||||||
|
],
|
||||||
|
"quotaNoDisplay": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -39,8 +39,8 @@
|
|||||||
"showSpliter": true,
|
"showSpliter": true,
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"isRequired": true,
|
"isRequired": true,
|
||||||
"starMin": "",
|
"min": "",
|
||||||
"starMax": "",
|
"max": "",
|
||||||
"type": "radio-star",
|
"type": "radio-star",
|
||||||
"title": "标题2"
|
"title": "标题2"
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,7 @@
|
|||||||
{
|
{
|
||||||
"text": "课程1",
|
"text": "课程1",
|
||||||
"hash": "115019",
|
"hash": "115019",
|
||||||
|
"imageUrl": "",
|
||||||
"others": false,
|
"others": false,
|
||||||
"mustOthers": false,
|
"mustOthers": false,
|
||||||
"othersKey": "",
|
"othersKey": "",
|
||||||
@ -55,6 +56,7 @@
|
|||||||
{
|
{
|
||||||
"text": "课程2",
|
"text": "课程2",
|
||||||
"hash": "115020",
|
"hash": "115020",
|
||||||
|
"imageUrl": "",
|
||||||
"others": false,
|
"others": false,
|
||||||
"mustOthers": false,
|
"mustOthers": false,
|
||||||
"othersKey": "",
|
"othersKey": "",
|
||||||
@ -63,6 +65,7 @@
|
|||||||
{
|
{
|
||||||
"text": "课程3",
|
"text": "课程3",
|
||||||
"hash": "115021",
|
"hash": "115021",
|
||||||
|
"imageUrl": "",
|
||||||
"others": false,
|
"others": false,
|
||||||
"mustOthers": false,
|
"mustOthers": false,
|
||||||
"othersKey": "",
|
"othersKey": "",
|
||||||
@ -71,6 +74,7 @@
|
|||||||
{
|
{
|
||||||
"text": "课程4",
|
"text": "课程4",
|
||||||
"hash": "115022",
|
"hash": "115022",
|
||||||
|
"imageUrl": "",
|
||||||
"others": false,
|
"others": false,
|
||||||
"mustOthers": false,
|
"mustOthers": false,
|
||||||
"othersKey": "",
|
"othersKey": "",
|
||||||
|
@ -46,6 +46,7 @@
|
|||||||
"options": [
|
"options": [
|
||||||
{
|
{
|
||||||
"text": "选项1",
|
"text": "选项1",
|
||||||
|
"imageUrl": "",
|
||||||
"others": false,
|
"others": false,
|
||||||
"mustOthers": false,
|
"mustOthers": false,
|
||||||
"othersKey": "",
|
"othersKey": "",
|
||||||
@ -54,6 +55,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "选项2",
|
"text": "选项2",
|
||||||
|
"imageUrl": "",
|
||||||
"others": false,
|
"others": false,
|
||||||
"mustOthers": false,
|
"mustOthers": false,
|
||||||
"othersKey": "",
|
"othersKey": "",
|
||||||
|
@ -2,12 +2,10 @@
|
|||||||
"bannerConf": {
|
"bannerConf": {
|
||||||
"titleConfig": {
|
"titleConfig": {
|
||||||
"mainTitle": "<h3 style=\"text-align: center\">欢迎填写问卷</h3><p>为了给您提供更好的服务,希望您能抽出几分钟时间,将您的感受和建议告诉我们,<span style=\"color: rgb(204, 0, 0)\">期待您的参与!</span></p>",
|
"mainTitle": "<h3 style=\"text-align: center\">欢迎填写问卷</h3><p>为了给您提供更好的服务,希望您能抽出几分钟时间,将您的感受和建议告诉我们,<span style=\"color: rgb(204, 0, 0)\">期待您的参与!</span></p>",
|
||||||
"subTitle": "<p>为了给您提供更好的服务,希望您能抽出几分钟时间,将您的感受和建议告诉我们,<span style=\"color: rgb(204, 0, 0)\">期待您的参与!</span></p>"
|
"subTitle": ""
|
||||||
},
|
},
|
||||||
"bannerConfig": {
|
"bannerConfig": {
|
||||||
"bgImage": "/imgs/skin/17e06b7604a007e1d3e1453b9ddadc3c.webp",
|
"bgImage": "/imgs/skin/17e06b7604a007e1d3e1453b9ddadc3c.webp",
|
||||||
"bgImageAllowJump": false,
|
|
||||||
"bgImageJumpLink": "",
|
|
||||||
"videoLink": "",
|
"videoLink": "",
|
||||||
"postImg": ""
|
"postImg": ""
|
||||||
}
|
}
|
||||||
@ -24,35 +22,25 @@
|
|||||||
"msg_9002": "请勿多次提交!",
|
"msg_9002": "请勿多次提交!",
|
||||||
"msg_9003": "您来晚了,已经满额!",
|
"msg_9003": "您来晚了,已经满额!",
|
||||||
"msg_9004": "提交失败!"
|
"msg_9004": "提交失败!"
|
||||||
},
|
}
|
||||||
"link": ""
|
|
||||||
},
|
},
|
||||||
"bottomConf": {
|
"bottomConf": {
|
||||||
"logoImage": "/imgs/Logo.webp",
|
"logoImage": "/imgs/Logo.webp",
|
||||||
"logoImageWidth": "60%"
|
"logoImageWidth": "60%"
|
||||||
},
|
},
|
||||||
"baseConf": {
|
"baseConf": {
|
||||||
"beginTime": "2024-01-01 00:00:00",
|
"begTime": "2024-01-01 00:00:00",
|
||||||
"endTime": "2034-01-01 00:00:00",
|
"endTime": "2034-01-01 00:00:00",
|
||||||
"tLimit": 0,
|
"tLimit": 0,
|
||||||
"language": "chinese",
|
"language": "chinese",
|
||||||
"answerBegTime": "00:00:00",
|
"answerBegTime": "00:00:00",
|
||||||
"answerEndTime": "23:59:59",
|
"answerEndTime": "23:59:59"
|
||||||
"passwordSwitch": false,
|
|
||||||
"password": "",
|
|
||||||
"whitelistType": "ALL",
|
|
||||||
"whitelist": [],
|
|
||||||
"memberType": "MOBILE",
|
|
||||||
"fillAnswer": false,
|
|
||||||
"fillSubmitAnswer": false
|
|
||||||
},
|
},
|
||||||
"skinConf": {
|
"skinConf": {
|
||||||
"skinColor": "#4a4c5b",
|
"skinColor": "#4a4c5b",
|
||||||
"inputBgColor": "#ffffff",
|
"inputBgColor": "#ffffff",
|
||||||
"backgroundConf": {
|
"backgroundConf": {
|
||||||
"color": "#b8dbff",
|
"color": "#ffffff"
|
||||||
"type": "color",
|
|
||||||
"image": ""
|
|
||||||
},
|
},
|
||||||
"themeConf": {
|
"themeConf": {
|
||||||
"color": "#ffa600"
|
"color": "#ffa600"
|
||||||
@ -63,7 +51,6 @@
|
|||||||
},
|
},
|
||||||
"pageConf": [],
|
"pageConf": [],
|
||||||
"logicConf": {
|
"logicConf": {
|
||||||
"showLogicConf": [],
|
"showLogicConf": []
|
||||||
"jumpLogicConf": []
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ export async function getSchemaBySurveyType(surveyType: string) {
|
|||||||
}
|
}
|
||||||
const code = Object.assign({}, templateBase, codeData);
|
const code = Object.assign({}, templateBase, codeData);
|
||||||
const nowMoment = moment();
|
const nowMoment = moment();
|
||||||
code.baseConf.beginTime = nowMoment.format('YYYY-MM-DD HH:mm:ss');
|
code.baseConf.begTime = nowMoment.format('YYYY-MM-DD HH:mm:ss');
|
||||||
code.baseConf.endTime = nowMoment
|
code.baseConf.endTime = nowMoment
|
||||||
.add(10, 'years')
|
.add(10, 'years')
|
||||||
.format('YYYY-MM-DD HH:mm:ss');
|
.format('YYYY-MM-DD HH:mm:ss');
|
||||||
@ -63,7 +63,7 @@ export function getListHeadByDataList(dataList) {
|
|||||||
type: QUESTION_TYPE.TEXT,
|
type: QUESTION_TYPE.TEXT,
|
||||||
});
|
});
|
||||||
listHead.push({
|
listHead.push({
|
||||||
field: 'createdAt',
|
field: 'createDate',
|
||||||
title: '提交时间',
|
title: '提交时间',
|
||||||
type: QUESTION_TYPE.TEXT,
|
type: QUESTION_TYPE.TEXT,
|
||||||
});
|
});
|
||||||
|
@ -3,6 +3,7 @@ import { MongoRepository } from 'typeorm';
|
|||||||
import { ClientEncryptService } from '../services/clientEncrypt.service';
|
import { ClientEncryptService } from '../services/clientEncrypt.service';
|
||||||
import { ClientEncrypt } from 'src/models/clientEncrypt.entity';
|
import { ClientEncrypt } from 'src/models/clientEncrypt.entity';
|
||||||
import { ENCRYPT_TYPE } from 'src/enums/encrypt';
|
import { ENCRYPT_TYPE } from 'src/enums/encrypt';
|
||||||
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
|
|
||||||
@ -21,7 +22,6 @@ describe('ClientEncryptService', () => {
|
|||||||
save: jest.fn(),
|
save: jest.fn(),
|
||||||
findOne: jest.fn(),
|
findOne: jest.fn(),
|
||||||
updateOne: jest.fn(),
|
updateOne: jest.fn(),
|
||||||
deleteOne: jest.fn(),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -88,6 +88,9 @@ describe('ClientEncryptService', () => {
|
|||||||
expect(repository.findOne).toHaveBeenCalledWith({
|
expect(repository.findOne).toHaveBeenCalledWith({
|
||||||
where: {
|
where: {
|
||||||
_id: new ObjectId(id),
|
_id: new ObjectId(id),
|
||||||
|
'curStatus.status': {
|
||||||
|
$ne: RECORD_STATUS.REMOVED,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(result).toEqual(encryptInfo);
|
expect(result).toEqual(encryptInfo);
|
||||||
@ -106,13 +109,11 @@ describe('ClientEncryptService', () => {
|
|||||||
describe('deleteEncryptInfo', () => {
|
describe('deleteEncryptInfo', () => {
|
||||||
it('should delete encrypt info by id', async () => {
|
it('should delete encrypt info by id', async () => {
|
||||||
const id = new ObjectId().toHexString();
|
const id = new ObjectId().toHexString();
|
||||||
const deleteResult = { matchedCount: 1, modifiedCount: 1 };
|
const updateResult = { matchedCount: 1, modifiedCount: 1 };
|
||||||
jest
|
jest.spyOn(repository, 'updateOne').mockResolvedValue(updateResult);
|
||||||
.spyOn(repository, 'deleteOne')
|
|
||||||
.mockResolvedValue(deleteResult as any);
|
|
||||||
|
|
||||||
const result = await service.deleteEncryptInfo(id);
|
const result = await service.deleteEncryptInfo(id);
|
||||||
expect(result).toEqual(deleteResult);
|
expect(result).toEqual(updateResult);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -50,7 +50,6 @@ describe('CounterService', () => {
|
|||||||
surveyPath: 'testPath',
|
surveyPath: 'testPath',
|
||||||
type: 'testType',
|
type: 'testType',
|
||||||
data,
|
data,
|
||||||
updatedAt: expect.any(Date),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ upsert: true },
|
{ upsert: true },
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { ResponseSchema } from 'src/models/responseSchema.entity';
|
import { ResponseSchema } from 'src/models/responseSchema.entity';
|
||||||
import { RECORD_STATUS, RECORD_SUB_STATUS } from 'src/enums';
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
|
|
||||||
export const mockResponseSchema: ResponseSchema = {
|
export const mockResponseSchema: ResponseSchema = {
|
||||||
@ -8,18 +8,14 @@ export const mockResponseSchema: ResponseSchema = {
|
|||||||
status: RECORD_STATUS.PUBLISHED,
|
status: RECORD_STATUS.PUBLISHED,
|
||||||
date: 1710399368439,
|
date: 1710399368439,
|
||||||
},
|
},
|
||||||
subStatus: {
|
|
||||||
status: RECORD_SUB_STATUS.DEFAULT,
|
|
||||||
date: 1710399368439,
|
|
||||||
},
|
|
||||||
statusList: [
|
statusList: [
|
||||||
{
|
{
|
||||||
status: RECORD_STATUS.PUBLISHED,
|
status: RECORD_STATUS.PUBLISHED,
|
||||||
date: 1710399368439,
|
date: 1710399368439,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
createdAt: 1710399368440,
|
createDate: 1710399368440,
|
||||||
updatedAt: 1710399368440,
|
updateDate: 1710399368440,
|
||||||
title: '加密全流程',
|
title: '加密全流程',
|
||||||
surveyPath: 'EBzdmnSp',
|
surveyPath: 'EBzdmnSp',
|
||||||
code: {
|
code: {
|
||||||
@ -36,7 +32,7 @@ export const mockResponseSchema: ResponseSchema = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
baseConf: {
|
baseConf: {
|
||||||
beginTime: '2024-03-14 14:54:41',
|
begTime: '2024-03-14 14:54:41',
|
||||||
endTime: '2034-03-14 14:54:41',
|
endTime: '2034-03-14 14:54:41',
|
||||||
language: 'chinese',
|
language: 'chinese',
|
||||||
tLimit: 10,
|
tLimit: 10,
|
||||||
@ -48,17 +44,6 @@ export const mockResponseSchema: ResponseSchema = {
|
|||||||
logoImageWidth: '60%',
|
logoImageWidth: '60%',
|
||||||
},
|
},
|
||||||
skinConf: {
|
skinConf: {
|
||||||
backgroundConf: {
|
|
||||||
color: '#fff',
|
|
||||||
type: 'color',
|
|
||||||
image: '',
|
|
||||||
},
|
|
||||||
themeConf: {
|
|
||||||
color: '#ffa600',
|
|
||||||
},
|
|
||||||
contentConf: {
|
|
||||||
opacity: 100,
|
|
||||||
},
|
|
||||||
skinColor: '#4a4c5b',
|
skinColor: '#4a4c5b',
|
||||||
inputBgColor: '#ffffff',
|
inputBgColor: '#ffffff',
|
||||||
},
|
},
|
||||||
@ -251,4 +236,4 @@ export const mockResponseSchema: ResponseSchema = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
pageId: '65f29f3192862d6a9067ad1c',
|
pageId: '65f29f3192862d6a9067ad1c',
|
||||||
} as unknown as ResponseSchema;
|
} as ResponseSchema;
|
||||||
|
@ -3,28 +3,27 @@ import { ResponseSchemaController } from '../controllers/responseSchema.controll
|
|||||||
import { ResponseSchemaService } from '../services/responseScheme.service';
|
import { ResponseSchemaService } from '../services/responseScheme.service';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
import { RECORD_SUB_STATUS } from 'src/enums';
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
|
|
||||||
import { ResponseSchema } from 'src/models/responseSchema.entity';
|
import { ResponseSchema } from 'src/models/responseSchema.entity';
|
||||||
import { Logger } from 'src/logger';
|
import { Logger } from 'src/logger';
|
||||||
import { UserService } from 'src/modules/auth/services/user.service';
|
import { UserService } from 'src/modules/auth/services/user.service';
|
||||||
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
|
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
|
||||||
|
import { AuthService } from 'src/modules/auth/services/auth.service';
|
||||||
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
|
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
|
||||||
|
|
||||||
jest.mock('../services/responseScheme.service');
|
jest.mock('../services/responseScheme.service');
|
||||||
jest.mock('src/modules/auth/services/user.service');
|
|
||||||
jest.mock('src/modules/workspace/services/workspaceMember.service');
|
|
||||||
|
|
||||||
describe('ResponseSchemaController', () => {
|
describe('ResponseSchemaController', () => {
|
||||||
let controller: ResponseSchemaController;
|
let controller: ResponseSchemaController;
|
||||||
let responseSchemaService: ResponseSchemaService;
|
let responseSchemaService: ResponseSchemaService;
|
||||||
let userService: UserService;
|
|
||||||
let workspaceMemberService: WorkspaceMemberService;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [ResponseSchemaController],
|
controllers: [ResponseSchemaController],
|
||||||
providers: [
|
providers: [
|
||||||
ResponseSchemaService,
|
ResponseSchemaService,
|
||||||
|
AuthService,
|
||||||
{
|
{
|
||||||
provide: Logger,
|
provide: Logger,
|
||||||
useValue: {
|
useValue: {
|
||||||
@ -43,6 +42,12 @@ describe('ResponseSchemaController', () => {
|
|||||||
findAllByUserId: jest.fn(),
|
findAllByUserId: jest.fn(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: AuthService,
|
||||||
|
useValue: {
|
||||||
|
create: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
provide: Logger,
|
provide: Logger,
|
||||||
useValue: {
|
useValue: {
|
||||||
@ -56,10 +61,6 @@ describe('ResponseSchemaController', () => {
|
|||||||
responseSchemaService = module.get<ResponseSchemaService>(
|
responseSchemaService = module.get<ResponseSchemaService>(
|
||||||
ResponseSchemaService,
|
ResponseSchemaService,
|
||||||
);
|
);
|
||||||
userService = module.get<UserService>(UserService);
|
|
||||||
workspaceMemberService = module.get<WorkspaceMemberService>(
|
|
||||||
WorkspaceMemberService,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getSchema', () => {
|
describe('getSchema', () => {
|
||||||
@ -67,20 +68,12 @@ describe('ResponseSchemaController', () => {
|
|||||||
const mockQueryInfo = { surveyPath: 'validSurveyPath' };
|
const mockQueryInfo = { surveyPath: 'validSurveyPath' };
|
||||||
const mockResponseSchema = {
|
const mockResponseSchema = {
|
||||||
surveyPath: 'testSurveyPath',
|
surveyPath: 'testSurveyPath',
|
||||||
curStatus: { status: 'published', date: Date.now() },
|
curStatus: { status: RECORD_STATUS.PUBLISHED, date: Date.now() },
|
||||||
subStatus: { status: RECORD_SUB_STATUS.DEFAULT, date: Date.now() },
|
|
||||||
code: {
|
|
||||||
baseConf: {
|
|
||||||
passwordSwitch: false,
|
|
||||||
password: null,
|
|
||||||
whitelist: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as ResponseSchema;
|
} as ResponseSchema;
|
||||||
|
|
||||||
jest
|
jest
|
||||||
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||||
.mockResolvedValue(mockResponseSchema);
|
.mockResolvedValue(Promise.resolve(mockResponseSchema));
|
||||||
|
|
||||||
const result = await controller.getSchema(mockQueryInfo);
|
const result = await controller.getSchema(mockQueryInfo);
|
||||||
|
|
||||||
@ -104,180 +97,153 @@ describe('ResponseSchemaController', () => {
|
|||||||
jest
|
jest
|
||||||
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||||
.mockResolvedValue({
|
.mockResolvedValue({
|
||||||
isDeleted: true,
|
curStatus: { status: RECORD_STATUS.REMOVED },
|
||||||
} as ResponseSchema);
|
} as ResponseSchema);
|
||||||
|
|
||||||
await expect(controller.getSchema(mockQueryInfo)).rejects.toThrow(
|
await expect(controller.getSchema(mockQueryInfo)).rejects.toThrow(
|
||||||
new HttpException(
|
new HttpException('问卷已删除', EXCEPTION_CODE.RESPONSE_SCHEMA_REMOVED),
|
||||||
'问卷不存在或已删除',
|
|
||||||
EXCEPTION_CODE.RESPONSE_SCHEMA_REMOVED,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw HttpException with RESPONSE_PAUSING code when survey is paused', async () => {
|
it('whitelistValidate should throw SurveyNotFoundException when survey is removed', async () => {
|
||||||
const mockQueryInfo = { surveyPath: 'pausedSurveyPath' };
|
const surveyPath = '';
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
|
||||||
.mockResolvedValue({
|
|
||||||
curStatus: { status: 'published' },
|
|
||||||
subStatus: { status: RECORD_SUB_STATUS.PAUSING },
|
|
||||||
} as ResponseSchema);
|
|
||||||
|
|
||||||
await expect(controller.getSchema(mockQueryInfo)).rejects.toThrow(
|
|
||||||
new HttpException('该问卷已暂停回收', EXCEPTION_CODE.RESPONSE_PAUSING),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('whitelistValidate', () => {
|
|
||||||
it('should throw HttpException when parameters are invalid', async () => {
|
|
||||||
const surveyPath = 'testSurveyPath';
|
|
||||||
const body = { password: 1 };
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
controller.whitelistValidate(surveyPath, body),
|
|
||||||
).rejects.toThrow(HttpException);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw SurveyNotFoundException when survey is removed', async () => {
|
|
||||||
const surveyPath = 'removedSurveyPath';
|
|
||||||
jest
|
jest
|
||||||
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||||
.mockResolvedValue(null);
|
.mockResolvedValue(null);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
controller.whitelistValidate(surveyPath, { password: '123456' }),
|
controller.whitelistValidate(surveyPath, {
|
||||||
|
password: '123456',
|
||||||
|
}),
|
||||||
).rejects.toThrow(new SurveyNotFoundException('该问卷不存在,无法提交'));
|
).rejects.toThrow(new SurveyNotFoundException('该问卷不存在,无法提交'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw HttpException when password is incorrect', async () => {
|
it('whitelistValidate should throw WHITELIST_ERROR code when password is incorrect', async () => {
|
||||||
const surveyPath = 'testSurveyPath';
|
const surveyPath = '';
|
||||||
const mockSchema = {
|
jest
|
||||||
|
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||||
|
.mockResolvedValue({
|
||||||
|
curStatus: {
|
||||||
|
status: 'published',
|
||||||
|
},
|
||||||
code: {
|
code: {
|
||||||
baseConf: {
|
baseConf: {
|
||||||
passwordSwitch: true,
|
passwordSwitch: true,
|
||||||
password: '123456',
|
password: '123456',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
} as ResponseSchema);
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
|
||||||
.mockResolvedValue(mockSchema as any);
|
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
controller.whitelistValidate(surveyPath, { password: 'wrongPassword' }),
|
controller.whitelistValidate(surveyPath, {
|
||||||
|
password: '123457',
|
||||||
|
}),
|
||||||
).rejects.toThrow(
|
).rejects.toThrow(
|
||||||
new HttpException('密码验证失败', EXCEPTION_CODE.WHITELIST_ERROR),
|
new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should validate successfully when password is correct', async () => {
|
it('whitelistValidate should be successfully', async () => {
|
||||||
const surveyPath = 'testSurveyPath';
|
const surveyPath = 'test';
|
||||||
const mockSchema = {
|
jest
|
||||||
|
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||||
|
.mockResolvedValue({
|
||||||
|
curStatus: {
|
||||||
|
status: 'published',
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
baseConf: {
|
||||||
|
passwordSwitch: true,
|
||||||
|
password: '123456',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ResponseSchema);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
controller.whitelistValidate(surveyPath, {
|
||||||
|
password: '123456',
|
||||||
|
}),
|
||||||
|
).resolves.toEqual({ code: 200, data: null });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('whitelistValidate should throw WHITELIST_ERROR code when mobile or email is incorrect', async () => {
|
||||||
|
const surveyPath = '';
|
||||||
|
jest
|
||||||
|
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||||
|
.mockResolvedValue({
|
||||||
|
curStatus: {
|
||||||
|
status: 'published',
|
||||||
|
},
|
||||||
code: {
|
code: {
|
||||||
baseConf: {
|
baseConf: {
|
||||||
passwordSwitch: true,
|
passwordSwitch: true,
|
||||||
password: '123456',
|
password: '123456',
|
||||||
whitelistType: 'CUSTOM',
|
whitelistType: 'CUSTOM',
|
||||||
whitelist: ['allowed@example.com'],
|
memberType: 'MOBILE',
|
||||||
|
whitelist: ['13500000000'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
} as ResponseSchema);
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
|
||||||
.mockResolvedValue(mockSchema as any);
|
|
||||||
|
|
||||||
const result = await controller.whitelistValidate(surveyPath, {
|
|
||||||
password: '123456',
|
|
||||||
whitelist: 'allowed@example.com',
|
|
||||||
});
|
|
||||||
expect(result).toEqual({ code: 200, data: null });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw HttpException when whitelist value is not in CUSTOM whitelist', async () => {
|
|
||||||
const surveyPath = 'testSurveyPath';
|
|
||||||
const mockSchema = {
|
|
||||||
code: {
|
|
||||||
baseConf: {
|
|
||||||
whitelistType: 'CUSTOM',
|
|
||||||
whitelist: ['allowed@example.com'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
|
||||||
.mockResolvedValue(mockSchema as any);
|
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
controller.whitelistValidate(surveyPath, {
|
controller.whitelistValidate(surveyPath, {
|
||||||
password: '123456',
|
password: '123456',
|
||||||
whitelist: 'notAllowed@example.com',
|
whitelist: '13500000001',
|
||||||
}),
|
}),
|
||||||
).rejects.toThrow(
|
).rejects.toThrow(
|
||||||
new HttpException('白名单验证失败', EXCEPTION_CODE.WHITELIST_ERROR),
|
new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw HttpException when user is not found in MEMBER whitelist', async () => {
|
it('whitelistValidate should throw WHITELIST_ERROR code when member is incorrect', async () => {
|
||||||
const surveyPath = 'testSurveyPath';
|
const surveyPath = '';
|
||||||
const mockSchema = {
|
|
||||||
code: {
|
|
||||||
baseConf: {
|
|
||||||
whitelistType: 'MEMBER',
|
|
||||||
whitelist: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
jest
|
jest
|
||||||
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||||
.mockResolvedValue(mockSchema as any);
|
.mockResolvedValue({
|
||||||
jest.spyOn(userService, 'getUserByUsername').mockResolvedValue(null);
|
curStatus: {
|
||||||
|
status: 'published',
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
baseConf: {
|
||||||
|
passwordSwitch: true,
|
||||||
|
password: '123456',
|
||||||
|
whitelistType: 'MEMBER',
|
||||||
|
whitelist: ['Jack'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ResponseSchema);
|
||||||
await expect(
|
await expect(
|
||||||
controller.whitelistValidate(surveyPath, {
|
controller.whitelistValidate(surveyPath, {
|
||||||
password: '123456',
|
password: '123456',
|
||||||
whitelist: 'nonExistentUser',
|
whitelist: 'James',
|
||||||
}),
|
|
||||||
).rejects.toThrow(
|
|
||||||
new HttpException('名单验证失败', EXCEPTION_CODE.WHITELIST_ERROR),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw HttpException when user is not a workspace member', async () => {
|
|
||||||
const surveyPath = 'testSurveyPath';
|
|
||||||
const mockSchema = {
|
|
||||||
code: {
|
|
||||||
baseConf: {
|
|
||||||
whitelistType: 'MEMBER',
|
|
||||||
whitelist: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
|
||||||
.mockResolvedValue(mockSchema as any);
|
|
||||||
jest
|
|
||||||
.spyOn(userService, 'getUserByUsername')
|
|
||||||
.mockResolvedValue({ _id: new Object(), username: '' } as any);
|
|
||||||
jest
|
|
||||||
.spyOn(workspaceMemberService, 'findAllByUserId')
|
|
||||||
.mockResolvedValue([]);
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
controller.whitelistValidate(surveyPath, {
|
|
||||||
password: '123456',
|
|
||||||
whitelist: 'testUser',
|
|
||||||
}),
|
}),
|
||||||
).rejects.toThrow(
|
).rejects.toThrow(
|
||||||
new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR),
|
new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('whitelistValidate should return verifyId successfully', async () => {
|
||||||
|
const surveyPath = '';
|
||||||
|
jest
|
||||||
|
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||||
|
.mockResolvedValue({
|
||||||
|
curStatus: {
|
||||||
|
status: 'published',
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
baseConf: {
|
||||||
|
passwordSwitch: true,
|
||||||
|
password: '123456',
|
||||||
|
whitelistType: 'CUSTOM',
|
||||||
|
memberType: 'MOBILE',
|
||||||
|
whitelist: ['13500000000'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ResponseSchema);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
controller.whitelistValidate(surveyPath, {
|
||||||
|
password: '123456',
|
||||||
|
whitelist: '13500000000',
|
||||||
|
}),
|
||||||
|
).resolves.toEqual({ code: 200, data: null });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -20,7 +20,6 @@ describe('ResponseSchemaService', () => {
|
|||||||
findOne: jest.fn().mockResolvedValue(mockResponseSchema),
|
findOne: jest.fn().mockResolvedValue(mockResponseSchema),
|
||||||
create: jest.fn(),
|
create: jest.fn(),
|
||||||
save: jest.fn(),
|
save: jest.fn(),
|
||||||
updateOne: jest.fn(),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -121,24 +120,22 @@ describe('ResponseSchemaService', () => {
|
|||||||
describe('deleteResponseSchema', () => {
|
describe('deleteResponseSchema', () => {
|
||||||
it('should delete response schema by survey path', async () => {
|
it('should delete response schema by survey path', async () => {
|
||||||
jest
|
jest
|
||||||
.spyOn(responseSchemaRepository, 'updateOne')
|
.spyOn(responseSchemaRepository, 'findOne')
|
||||||
.mockResolvedValueOnce(cloneDeep(mockResponseSchema));
|
.mockResolvedValueOnce(cloneDeep(mockResponseSchema));
|
||||||
|
jest
|
||||||
|
.spyOn(responseSchemaRepository, 'save')
|
||||||
|
.mockResolvedValueOnce(undefined);
|
||||||
|
|
||||||
await service.deleteResponseSchema({
|
await service.deleteResponseSchema({
|
||||||
surveyPath: mockResponseSchema.surveyPath,
|
surveyPath: mockResponseSchema.surveyPath,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(responseSchemaRepository.updateOne).toHaveBeenCalledWith(
|
expect(responseSchemaRepository.findOne).toHaveBeenCalledWith({
|
||||||
{
|
where: {
|
||||||
surveyPath: mockResponseSchema.surveyPath,
|
surveyPath: mockResponseSchema.surveyPath,
|
||||||
},
|
},
|
||||||
{
|
});
|
||||||
$set: {
|
expect(responseSchemaRepository.save).toHaveBeenCalledTimes(1);
|
||||||
isDeleted: true,
|
|
||||||
updatedAt: expect.any(Date),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -13,14 +13,14 @@ import { ClientEncryptService } from '../services/clientEncrypt.service';
|
|||||||
import { MessagePushingTaskService } from 'src/modules/message/services/messagePushingTask.service';
|
import { MessagePushingTaskService } from 'src/modules/message/services/messagePushingTask.service';
|
||||||
|
|
||||||
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
||||||
import { PluginManager } from 'src/securityPlugin/pluginManager';
|
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
|
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
|
||||||
import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugin';
|
import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugin';
|
||||||
|
|
||||||
import { RECORD_STATUS, RECORD_SUB_STATUS } from 'src/enums';
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
||||||
import { Logger } from 'src/logger';
|
import { XiaojuSurveyLogger } from 'src/logger';
|
||||||
import { ResponseSchema } from 'src/models/responseSchema.entity';
|
import { ResponseSchema } from 'src/models/responseSchema.entity';
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
import { UserService } from 'src/modules/auth/services/user.service';
|
import { UserService } from 'src/modules/auth/services/user.service';
|
||||||
@ -71,8 +71,8 @@ const mockClientEncryptInfo = {
|
|||||||
date: 1710399425273.0,
|
date: 1710399425273.0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
createdAt: 1710399425273.0,
|
createDate: 1710399425273.0,
|
||||||
updatedAt: 1710399425273.0,
|
updateDate: 1710399425273.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('SurveyResponseController', () => {
|
describe('SurveyResponseController', () => {
|
||||||
@ -122,7 +122,7 @@ describe('SurveyResponseController', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: Logger,
|
provide: XiaojuSurveyLogger,
|
||||||
useValue: {
|
useValue: {
|
||||||
error: jest.fn(),
|
error: jest.fn(),
|
||||||
info: jest.fn(),
|
info: jest.fn(),
|
||||||
@ -153,7 +153,9 @@ describe('SurveyResponseController', () => {
|
|||||||
clientEncryptService =
|
clientEncryptService =
|
||||||
module.get<ClientEncryptService>(ClientEncryptService);
|
module.get<ClientEncryptService>(ClientEncryptService);
|
||||||
|
|
||||||
const pluginManager = module.get<PluginManager>(PluginManager);
|
const pluginManager = module.get<XiaojuSurveyPluginManager>(
|
||||||
|
XiaojuSurveyPluginManager,
|
||||||
|
);
|
||||||
pluginManager.registerPlugin(
|
pluginManager.registerPlugin(
|
||||||
new ResponseSecurityPlugin('dataAesEncryptSecretKey'),
|
new ResponseSecurityPlugin('dataAesEncryptSecretKey'),
|
||||||
);
|
);
|
||||||
@ -178,7 +180,7 @@ describe('SurveyResponseController', () => {
|
|||||||
.mockResolvedValueOnce({
|
.mockResolvedValueOnce({
|
||||||
_id: new ObjectId('65fc2dd77f4520858046e129'),
|
_id: new ObjectId('65fc2dd77f4520858046e129'),
|
||||||
clientTime: 1711025112552,
|
clientTime: 1711025112552,
|
||||||
createdAt: 1711025113146,
|
createDate: 1711025113146,
|
||||||
curStatus: {
|
curStatus: {
|
||||||
status: RECORD_STATUS.NEW,
|
status: RECORD_STATUS.NEW,
|
||||||
date: 1711025113146,
|
date: 1711025113146,
|
||||||
@ -212,7 +214,7 @@ describe('SurveyResponseController', () => {
|
|||||||
],
|
],
|
||||||
|
|
||||||
surveyPath: 'EBzdmnSp',
|
surveyPath: 'EBzdmnSp',
|
||||||
updatedAt: 1711025113146,
|
updateDate: 1711025113146,
|
||||||
secretKeys: [],
|
secretKeys: [],
|
||||||
} as unknown as SurveyResponse);
|
} as unknown as SurveyResponse);
|
||||||
jest
|
jest
|
||||||
@ -334,9 +336,6 @@ describe('SurveyResponseController', () => {
|
|||||||
curStatus: {
|
curStatus: {
|
||||||
status: RECORD_STATUS.PUBLISHED,
|
status: RECORD_STATUS.PUBLISHED,
|
||||||
},
|
},
|
||||||
subStatus: {
|
|
||||||
status: RECORD_SUB_STATUS.DEFAULT,
|
|
||||||
},
|
|
||||||
code: {
|
code: {
|
||||||
baseConf: {
|
baseConf: {
|
||||||
passwordSwitch: true,
|
passwordSwitch: true,
|
||||||
|
@ -18,7 +18,6 @@ describe('SurveyResponseService', () => {
|
|||||||
create: jest.fn(),
|
create: jest.fn(),
|
||||||
save: jest.fn(),
|
save: jest.fn(),
|
||||||
count: jest.fn(),
|
count: jest.fn(),
|
||||||
find: jest.fn(),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -70,12 +69,16 @@ describe('SurveyResponseService', () => {
|
|||||||
it('should get the total survey response count by path', async () => {
|
it('should get the total survey response count by path', async () => {
|
||||||
const surveyPath = 'testPath';
|
const surveyPath = 'testPath';
|
||||||
const count = 10;
|
const count = 10;
|
||||||
jest
|
jest.spyOn(surveyResponseRepository, 'count').mockResolvedValue(count);
|
||||||
.spyOn(surveyResponseRepository, 'find')
|
|
||||||
.mockResolvedValue(new Array(10));
|
|
||||||
|
|
||||||
const result = await service.getSurveyResponseTotalByPath(surveyPath);
|
const result = await service.getSurveyResponseTotalByPath(surveyPath);
|
||||||
|
|
||||||
expect(result).toEqual(count);
|
expect(result).toEqual(count);
|
||||||
|
expect(surveyResponseRepository.count).toHaveBeenCalledWith({
|
||||||
|
where: {
|
||||||
|
surveyPath,
|
||||||
|
'curStatus.status': { $ne: 'removed' },
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -10,10 +10,10 @@ import {
|
|||||||
import { ResponseSchemaService } from '../services/responseScheme.service';
|
import { ResponseSchemaService } from '../services/responseScheme.service';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
import { RECORD_SUB_STATUS } from 'src/enums';
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
import { Logger } from 'src/logger';
|
import { XiaojuSurveyLogger } from 'src/logger';
|
||||||
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
|
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
|
||||||
import { WhitelistType } from 'src/interfaces/survey';
|
import { WhitelistType } from 'src/interfaces/survey';
|
||||||
import { UserService } from 'src/modules/auth/services/user.service';
|
import { UserService } from 'src/modules/auth/services/user.service';
|
||||||
@ -24,7 +24,7 @@ import { WorkspaceMemberService } from 'src/modules/workspace/services/workspace
|
|||||||
export class ResponseSchemaController {
|
export class ResponseSchemaController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly responseSchemaService: ResponseSchemaService,
|
private readonly responseSchemaService: ResponseSchemaService,
|
||||||
private readonly logger: Logger,
|
private readonly logger: XiaojuSurveyLogger,
|
||||||
private readonly userService: UserService,
|
private readonly userService: UserService,
|
||||||
private readonly workspaceMemberService: WorkspaceMemberService,
|
private readonly workspaceMemberService: WorkspaceMemberService,
|
||||||
) {}
|
) {}
|
||||||
@ -44,20 +44,16 @@ export class ResponseSchemaController {
|
|||||||
await this.responseSchemaService.getResponseSchemaByPath(
|
await this.responseSchemaService.getResponseSchemaByPath(
|
||||||
queryInfo.surveyPath,
|
queryInfo.surveyPath,
|
||||||
);
|
);
|
||||||
if (!responseSchema || responseSchema.isDeleted) {
|
if (
|
||||||
|
!responseSchema ||
|
||||||
|
responseSchema.curStatus.status === RECORD_STATUS.REMOVED
|
||||||
|
) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
'问卷不存在或已删除',
|
'问卷已删除',
|
||||||
EXCEPTION_CODE.RESPONSE_SCHEMA_REMOVED,
|
EXCEPTION_CODE.RESPONSE_SCHEMA_REMOVED,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responseSchema.subStatus.status === RECORD_SUB_STATUS.PAUSING) {
|
|
||||||
throw new HttpException(
|
|
||||||
'该问卷已暂停回收',
|
|
||||||
EXCEPTION_CODE.RESPONSE_PAUSING,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 去掉C端的敏感字段
|
// 去掉C端的敏感字段
|
||||||
if (responseSchema.code?.baseConf) {
|
if (responseSchema.code?.baseConf) {
|
||||||
responseSchema.code.baseConf.password = null;
|
responseSchema.code.baseConf.password = null;
|
||||||
@ -86,7 +82,7 @@ export class ResponseSchemaController {
|
|||||||
// 问卷信息
|
// 问卷信息
|
||||||
const schema =
|
const schema =
|
||||||
await this.responseSchemaService.getResponseSchemaByPath(surveyPath);
|
await this.responseSchemaService.getResponseSchemaByPath(surveyPath);
|
||||||
if (!schema || schema.isDeleted) {
|
if (!schema || schema.curStatus.status === 'removed') {
|
||||||
throw new SurveyNotFoundException('该问卷不存在,无法提交');
|
throw new SurveyNotFoundException('该问卷不存在,无法提交');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,17 +97,14 @@ export class ResponseSchemaController {
|
|||||||
// 密码校验
|
// 密码校验
|
||||||
if (passwordSwitch) {
|
if (passwordSwitch) {
|
||||||
if (settingPassword !== password) {
|
if (settingPassword !== password) {
|
||||||
throw new HttpException('密码验证失败', EXCEPTION_CODE.WHITELIST_ERROR);
|
throw new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 名单校验(手机号/邮箱)
|
// 名单校验(手机号/邮箱)
|
||||||
if (whitelistType === WhitelistType.CUSTOM) {
|
if (whitelistType === WhitelistType.CUSTOM) {
|
||||||
if (!whitelist.includes(whitelistValue)) {
|
if (!whitelist.includes(whitelistValue)) {
|
||||||
throw new HttpException(
|
throw new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR);
|
||||||
'白名单验证失败',
|
|
||||||
EXCEPTION_CODE.WHITELIST_ERROR,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,7 +112,7 @@ export class ResponseSchemaController {
|
|||||||
if (whitelistType === WhitelistType.MEMBER) {
|
if (whitelistType === WhitelistType.MEMBER) {
|
||||||
const user = await this.userService.getUserByUsername(whitelistValue);
|
const user = await this.userService.getUserByUsername(whitelistValue);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new HttpException('名单验证失败', EXCEPTION_CODE.WHITELIST_ERROR);
|
throw new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
const workspaceMember = await this.workspaceMemberService.findAllByUserId(
|
const workspaceMember = await this.workspaceMemberService.findAllByUserId(
|
||||||
|
@ -5,13 +5,12 @@ import { checkSign } from 'src/utils/checkSign';
|
|||||||
import { ENCRYPT_TYPE } from 'src/enums/encrypt';
|
import { ENCRYPT_TYPE } from 'src/enums/encrypt';
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
import { getPushingData } from 'src/utils/messagePushing';
|
import { getPushingData } from 'src/utils/messagePushing';
|
||||||
import { RECORD_SUB_STATUS } from 'src/enums';
|
|
||||||
|
|
||||||
import { ResponseSchemaService } from '../services/responseScheme.service';
|
import { ResponseSchemaService } from '../services/responseScheme.service';
|
||||||
import { SurveyResponseService } from '../services/surveyResponse.service';
|
import { SurveyResponseService } from '../services/surveyResponse.service';
|
||||||
import { ClientEncryptService } from '../services/clientEncrypt.service';
|
import { ClientEncryptService } from '../services/clientEncrypt.service';
|
||||||
import { MessagePushingTaskService } from '../../message/services/messagePushingTask.service';
|
import { MessagePushingTaskService } from '../../message/services/messagePushingTask.service';
|
||||||
// import { RedisService } from 'src/modules/redis/redis.service';
|
import { RedisService } from 'src/modules/redis/redis.service';
|
||||||
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import * as Joi from 'joi';
|
import * as Joi from 'joi';
|
||||||
@ -19,7 +18,7 @@ import * as forge from 'node-forge';
|
|||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
import { CounterService } from '../services/counter.service';
|
import { CounterService } from '../services/counter.service';
|
||||||
import { Logger } from 'src/logger';
|
import { XiaojuSurveyLogger } from 'src/logger';
|
||||||
import { WhitelistType } from 'src/interfaces/survey';
|
import { WhitelistType } from 'src/interfaces/survey';
|
||||||
import { UserService } from 'src/modules/auth/services/user.service';
|
import { UserService } from 'src/modules/auth/services/user.service';
|
||||||
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
|
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
|
||||||
@ -41,8 +40,8 @@ export class SurveyResponseController {
|
|||||||
private readonly clientEncryptService: ClientEncryptService,
|
private readonly clientEncryptService: ClientEncryptService,
|
||||||
private readonly messagePushingTaskService: MessagePushingTaskService,
|
private readonly messagePushingTaskService: MessagePushingTaskService,
|
||||||
private readonly counterService: CounterService,
|
private readonly counterService: CounterService,
|
||||||
private readonly logger: Logger,
|
private readonly logger: XiaojuSurveyLogger,
|
||||||
// private readonly redisService: RedisService,
|
private readonly redisService: RedisService,
|
||||||
private readonly userService: UserService,
|
private readonly userService: UserService,
|
||||||
private readonly workspaceMemberService: WorkspaceMemberService,
|
private readonly workspaceMemberService: WorkspaceMemberService,
|
||||||
) {}
|
) {}
|
||||||
@ -83,15 +82,9 @@ export class SurveyResponseController {
|
|||||||
// 查询schema
|
// 查询schema
|
||||||
const responseSchema =
|
const responseSchema =
|
||||||
await this.responseSchemaService.getResponseSchemaByPath(surveyPath);
|
await this.responseSchemaService.getResponseSchemaByPath(surveyPath);
|
||||||
if (!responseSchema || responseSchema.isDeleted) {
|
if (!responseSchema || responseSchema.curStatus.status === 'removed') {
|
||||||
throw new SurveyNotFoundException('该问卷不存在,无法提交');
|
throw new SurveyNotFoundException('该问卷不存在,无法提交');
|
||||||
}
|
}
|
||||||
if (responseSchema?.subStatus?.status === RECORD_SUB_STATUS.PAUSING) {
|
|
||||||
throw new HttpException(
|
|
||||||
'该问卷已暂停,无法提交',
|
|
||||||
EXCEPTION_CODE.RESPONSE_PAUSING,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 白名单的verifyId校验
|
// 白名单的verifyId校验
|
||||||
const baseConf = responseSchema.code.baseConf;
|
const baseConf = responseSchema.code.baseConf;
|
||||||
@ -139,12 +132,12 @@ export class SurveyResponseController {
|
|||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
// 提交时间限制
|
// 提交时间限制
|
||||||
const beginTime = responseSchema.code?.baseConf?.beginTime || 0;
|
const begTime = responseSchema.code?.baseConf?.begTime || 0;
|
||||||
const endTime = responseSchema?.code?.baseConf?.endTime || 0;
|
const endTime = responseSchema?.code?.baseConf?.endTime || 0;
|
||||||
if (beginTime && endTime) {
|
if (begTime && endTime) {
|
||||||
const beginTimeStamp = new Date(beginTime).getTime();
|
const begTimeStamp = new Date(begTime).getTime();
|
||||||
const endTimeStamp = new Date(endTime).getTime();
|
const endTimeStamp = new Date(endTime).getTime();
|
||||||
if (now < beginTimeStamp || now > endTimeStamp) {
|
if (now < begTimeStamp || now > endTimeStamp) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
'不在答题有效期内',
|
'不在答题有效期内',
|
||||||
EXCEPTION_CODE.RESPONSE_CURRENT_TIME_NOT_ALLOW,
|
EXCEPTION_CODE.RESPONSE_CURRENT_TIME_NOT_ALLOW,
|
||||||
@ -231,15 +224,17 @@ export class SurveyResponseController {
|
|||||||
const arr = cur.options.map((optionItem) => ({
|
const arr = cur.options.map((optionItem) => ({
|
||||||
hash: optionItem.hash,
|
hash: optionItem.hash,
|
||||||
text: optionItem.text,
|
text: optionItem.text,
|
||||||
|
quota: optionItem.quota,
|
||||||
}));
|
}));
|
||||||
pre[cur.field] = arr;
|
pre[cur.field] = arr;
|
||||||
return pre;
|
return pre;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
|
// 使用redis作为锁,校验选项配额
|
||||||
const surveyId = responseSchema.pageId;
|
const surveyId = responseSchema.pageId;
|
||||||
// const lockKey = `locks:optionSelectedCount:${surveyId}`;
|
const lockKey = `locks:optionSelectedCount:${surveyId}`;
|
||||||
// const lock = await this.redisService.lockResource(lockKey, 1000);
|
const lock = await this.redisService.lockResource(lockKey, 1000);
|
||||||
// this.logger.info(`lockKey: ${lockKey}`);
|
this.logger.info(`lockKey: ${lockKey}`);
|
||||||
try {
|
try {
|
||||||
const successParams = [];
|
const successParams = [];
|
||||||
for (const field in decryptedData) {
|
for (const field in decryptedData) {
|
||||||
@ -255,6 +250,23 @@ export class SurveyResponseController {
|
|||||||
|
|
||||||
//遍历选项hash值
|
//遍历选项hash值
|
||||||
for (const val of values) {
|
for (const val of values) {
|
||||||
|
const option = optionTextAndId[field].find(
|
||||||
|
(opt) => opt['hash'] === val,
|
||||||
|
);
|
||||||
|
const quota = parseInt(option['quota']);
|
||||||
|
if (
|
||||||
|
quota &&
|
||||||
|
optionCountData?.[val] &&
|
||||||
|
quota <= optionCountData[val]
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
code: EXCEPTION_CODE.RESPONSE_OVER_LIMIT,
|
||||||
|
data: {
|
||||||
|
field,
|
||||||
|
optionHash: option.hash,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
if (!optionCountData[val]) {
|
if (!optionCountData[val]) {
|
||||||
optionCountData[val] = 0;
|
optionCountData[val] = 0;
|
||||||
}
|
}
|
||||||
@ -280,11 +292,10 @@ export class SurveyResponseController {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(error.message);
|
this.logger.error(error.message);
|
||||||
throw error;
|
throw error;
|
||||||
|
} finally {
|
||||||
|
await this.redisService.unlockResource(lock);
|
||||||
|
this.logger.info(`unlockResource: ${lockKey}`);
|
||||||
}
|
}
|
||||||
// finally {
|
|
||||||
// await this.redisService.unlockResource(lock);
|
|
||||||
// this.logger.info(`unlockResource: ${lockKey}`);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 入库
|
// 入库
|
||||||
const surveyResponse =
|
const surveyResponse =
|
||||||
|
@ -4,6 +4,7 @@ import { MongoRepository } from 'typeorm';
|
|||||||
import { ClientEncrypt } from 'src/models/clientEncrypt.entity';
|
import { ClientEncrypt } from 'src/models/clientEncrypt.entity';
|
||||||
import { ENCRYPT_TYPE } from 'src/enums/encrypt';
|
import { ENCRYPT_TYPE } from 'src/enums/encrypt';
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ClientEncryptService {
|
export class ClientEncryptService {
|
||||||
@ -37,13 +38,26 @@ export class ClientEncryptService {
|
|||||||
return this.clientEncryptRepository.findOne({
|
return this.clientEncryptRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
_id: new ObjectId(id),
|
_id: new ObjectId(id),
|
||||||
|
'curStatus.status': {
|
||||||
|
$ne: RECORD_STATUS.REMOVED,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteEncryptInfo(id: string) {
|
deleteEncryptInfo(id: string) {
|
||||||
return this.clientEncryptRepository.deleteOne({
|
return this.clientEncryptRepository.updateOne(
|
||||||
|
{
|
||||||
_id: new ObjectId(id),
|
_id: new ObjectId(id),
|
||||||
});
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
curStatus: {
|
||||||
|
status: RECORD_STATUS.REMOVED,
|
||||||
|
date: Date.now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,6 @@ export class CounterService {
|
|||||||
surveyPath,
|
surveyPath,
|
||||||
type,
|
type,
|
||||||
data,
|
data,
|
||||||
updatedAt: new Date(),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { MongoRepository } from 'typeorm';
|
import { MongoRepository } from 'typeorm';
|
||||||
import { ResponseSchema } from 'src/models/responseSchema.entity';
|
import { ResponseSchema } from 'src/models/responseSchema.entity';
|
||||||
import { RECORD_STATUS, RECORD_SUB_STATUS } from 'src/enums';
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ResponseSchemaService {
|
export class ResponseSchemaService {
|
||||||
@ -23,27 +23,19 @@ export class ResponseSchemaService {
|
|||||||
status: RECORD_STATUS.PUBLISHED,
|
status: RECORD_STATUS.PUBLISHED,
|
||||||
date: Date.now(),
|
date: Date.now(),
|
||||||
};
|
};
|
||||||
clientSurvey.subStatus = {
|
|
||||||
status: RECORD_SUB_STATUS.DEFAULT,
|
|
||||||
date: Date.now(),
|
|
||||||
};
|
|
||||||
return this.responseSchemaRepository.save(clientSurvey);
|
return this.responseSchemaRepository.save(clientSurvey);
|
||||||
} else {
|
} else {
|
||||||
const curStatus = {
|
const curStatus = {
|
||||||
status: RECORD_STATUS.PUBLISHED,
|
status: RECORD_STATUS.PUBLISHED,
|
||||||
date: Date.now(),
|
date: Date.now(),
|
||||||
};
|
};
|
||||||
const subStatus = {
|
|
||||||
status: RECORD_SUB_STATUS.DEFAULT,
|
|
||||||
date: Date.now(),
|
|
||||||
};
|
|
||||||
const newClientSurvey = this.responseSchemaRepository.create({
|
const newClientSurvey = this.responseSchemaRepository.create({
|
||||||
title,
|
title,
|
||||||
surveyPath,
|
surveyPath,
|
||||||
code,
|
code,
|
||||||
pageId,
|
pageId,
|
||||||
curStatus,
|
curStatus,
|
||||||
subStatus,
|
statusList: [curStatus],
|
||||||
});
|
});
|
||||||
return this.responseSchemaRepository.save(newClientSurvey);
|
return this.responseSchemaRepository.save(newClientSurvey);
|
||||||
}
|
}
|
||||||
@ -61,32 +53,22 @@ export class ResponseSchemaService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async pausingResponseSchema({ surveyPath }) {
|
async deleteResponseSchema({ surveyPath }) {
|
||||||
const responseSchema = await this.responseSchemaRepository.findOne({
|
const responseSchema = await this.responseSchemaRepository.findOne({
|
||||||
where: { surveyPath },
|
where: { surveyPath },
|
||||||
});
|
});
|
||||||
if (responseSchema) {
|
if (responseSchema) {
|
||||||
const subStatus = {
|
const newStatus = {
|
||||||
status: RECORD_SUB_STATUS.PAUSING,
|
status: RECORD_STATUS.PUBLISHED,
|
||||||
date: Date.now(),
|
date: Date.now(),
|
||||||
};
|
};
|
||||||
responseSchema.subStatus = subStatus;
|
responseSchema.curStatus = newStatus;
|
||||||
responseSchema.curStatus.status = RECORD_STATUS.PUBLISHED;
|
if (Array.isArray(responseSchema.statusList)) {
|
||||||
|
responseSchema.statusList.push(newStatus);
|
||||||
|
} else {
|
||||||
|
responseSchema.statusList = [newStatus];
|
||||||
|
}
|
||||||
return this.responseSchemaRepository.save(responseSchema);
|
return this.responseSchemaRepository.save(responseSchema);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteResponseSchema({ surveyPath }) {
|
|
||||||
return this.responseSchemaRepository.updateOne(
|
|
||||||
{
|
|
||||||
surveyPath,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
$set: {
|
|
||||||
isDeleted: true,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { MongoRepository } from 'typeorm';
|
import { MongoRepository } from 'typeorm';
|
||||||
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SurveyResponseService {
|
export class SurveyResponseService {
|
||||||
constructor(
|
constructor(
|
||||||
@ -36,11 +35,14 @@ export class SurveyResponseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getSurveyResponseTotalByPath(surveyPath: string) {
|
async getSurveyResponseTotalByPath(surveyPath: string) {
|
||||||
const data = await this.surveyResponseRepository.find({
|
const count = await this.surveyResponseRepository.count({
|
||||||
where: {
|
where: {
|
||||||
surveyPath,
|
surveyPath,
|
||||||
|
'curStatus.status': {
|
||||||
|
$ne: 'removed',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return (data || []).length;
|
return count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user