Merge branch 'main' of git.backyard.segmentfault.com:opensource/answer

This commit is contained in:
mingcheng 2022-11-01 15:50:50 +08:00
commit e4ca2bb925
99 changed files with 2484 additions and 833 deletions

View File

@ -1,4 +1,8 @@
# Contributing to answer
## Coding and documentation Style
To be developed.
## Submitting Modifications
To be developed.

View File

@ -6,9 +6,9 @@ Before installing Answer, you need to install the base environment first.
You can then install Answer in several ways:
- Deploy with Docker
- binary installation
- Source installation
- [Deploy with Docker](#Docker-compose-for-Answer)
- [Binary installation](#Install-Answer-using-binary)
- [Source installation](#Compile-the-image)
## Docker-compose for Answer
```bash
@ -17,15 +17,15 @@ $ wget https://raw.githubusercontent.com/answerdev/answer/main/docker-compose.ya
$ docker-compose up
```
browser open URL [http://127.0.0.1:9080/](http://127.0.0.1:9080/).
In browser, open URL [http://127.0.0.1:9080/](http://127.0.0.1:9080/).
You can log in with the default administrator username( **`admin@admin.com`** ) and password( **`admin`** ).
You can log in with the default administrator username (**`admin@admin.com`**) and password (**`admin`**).
## Docker for Answer
Visit Docker Hub or GitHub Container registry to see all available images and tags.
Visit [Docker Hub](https://hub.docker.com/r/answerdev/answer) or GitHub Container registry to see all available images and tags.
### Usage
To keep your data out of Docker container, we do a volume (/var/data -> /data) here, and you can change it based on your situation.
To persist data beyond the life of a Docker container, use a volume (/var/data -> /data). You can modify this based on your situation.
```
# Pull image from Docker Hub.
@ -35,9 +35,9 @@ $ docker pull answerdev/answer:latest
$ mkdir -p /var/data
# Run the image first
$ docker run --name=answer -p 9080:80 -v /var/data:/data answer/answer
$ docker run --name=answer -p 9080:80 -v /var/data:/data answerdev/answer
# After the first startup, a configuration file will be generated in the /var/data directory
# After successful first startup, a configuration file will be generated in the /var/data directory
# /var/data/conf/config.yaml
# Need to modify the Mysql database address in the configuration file
vim /var/data/conf/config.yaml
@ -46,34 +46,32 @@ vim /var/data/conf/config.yaml
# connection: [username]:[password]@tcp([host]:[port])/[DbName]
...
# After configuring the configuration file, you can start the mirror again to start the service
# After configuring the configuration file, you can start the container again to start the service
$ docker start answer
```
## Binary for Answer
## Install Answer using binary
1. Unzip the compressed package
2. Use the command cd to enter the directory you just created
3. Execute the command ./answer init
4. Answer will generate a ./data directory in the current directory
5. Enter the data directory and modify the config.yaml file
6. Modify the database connection address to your database connection address
connection: [username]:[password]@tcp([host]:[port])/[DbName]
7. Exit the data directory and execute ./answer run -c ./data/conf/config.yaml
2. Use the command `cd` to enter the directory you just created
3. Execute the command `./answer init`
4. Answer will generate a `./data` directory in the current directory
5. Enter the `data` directory and modify the `config.yaml` file
6. Modify the database connection identify your database connection information
`connection: [username]:[password]@tcp([host]:[port])/[DbName]`
7. Use `cd ..` to return the directory from step 2, and execute `./answer run -c ./data/conf/config.yaml`
## Available Commands
Usage: answer [command]
Usage: `answer [command]`
- help: Help about any command
- init: Init answer application
- run: Run answer application
- check: Check answer required environment
- dump: Backup answer data
- `help`: Help about any command
- `init`: Init answer application
- `run`: Run answer application
- `check`: Check answer required environment
- `dump`: Backup answer data
## config.yaml Description
Here is a sample/default config.yaml file, as would be created from `answer init`.
```
server:
http:
@ -97,9 +95,9 @@ service_config:
```
## Compile the image
If you have modified the source files and want to repackage the image, you can use the following statement to repackage the image
If you have modified the source files and want to repackage the image, you can use the following to repackage the image
```
docker build -t answer:v1.0.0 .
```
## common problem
1. The project cannot be started, answer the main program startup depends on the configuration file config.yaml, the internationalization translation directory/i18n, the upload file storage directory/upfiles, you need to ensure that the configuration file is loaded when the project starts, answer run -c config.yaml and the correct config.yaml The configuration items that specify the i18n and upfiles directories
1. The project cannot be started: the main program startup depends on proper configuraiton of the configuration file, `config.yaml`, as well as the internationalization translation directory (`i18n`), and the upload file storage directory (`upfiles`). Ensure that the configuration file is loaded when the project starts, such as when using `answer run -c config.yaml` and that the `config.yaml` correctly specifies the i18n and upfiles directories.

View File

@ -35,7 +35,7 @@ $ docker pull answerdev/answer:latest
$ mkdir -p /var/data
# 先运行一遍镜像
$ docker run --name=answer -p 9080:80 -v /var/data:/data answer/answer
$ docker run --name=answer -p 9080:80 -v /var/data:/data answerdev/answer
# 第一次启动后会在/var/data 目录下生成配置文件
# /var/data/conf/config.yaml

View File

@ -4,13 +4,14 @@
# Answer - Build Q&A community
An open-source knowledge based community software. You can use it to quickly build your Q&A community for product technical support, customer support, user communication, and more.
An open-source knowledge-based community software. You can use it to quickly build your Q&A community for product technical support, customer support, user communication, and more.
To learn more about the project, visit [answer.dev](https://answer.dev).
[![LICENSE](https://img.shields.io/badge/License-Apache-green)](https://github.com/answerdev/answer/blob/main/LICENSE)
[![Language](https://img.shields.io/badge/Language-Go-blue.svg)](https://golang.org/)
[![Language](https://img.shields.io/badge/Language-React-blue.svg)](https://reactjs.org/)
[![Go Report Card](https://goreportcard.com/badge/github.com/answerdev/answer)](https://goreportcard.com/report/github.com/answerdev/answer)
## Screenshots
@ -18,15 +19,13 @@ To learn more about the project, visit [answer.dev](https://answer.dev).
## Quick start
### Running with docker-compose
### Running with docker
```bash
mkdir answer && cd answer
wget https://raw.githubusercontent.com/answerdev/answer/main/docker-compose.yaml
docker-compose up
docker run -d -p 9080:80 -v $PWD/answer-data/data:/data --name answer answerdev/answer:latest
```
For more information you can see [INSTALL.md](./INSTALL.md)
For more information, see [INSTALL.md](./INSTALL.md)
## Contributing
@ -36,4 +35,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for ways to get started.
## License
[Apache](https://github.com/answerdev/answer/blob/main/LICENSE)
[Apache License 2.0](https://github.com/answerdev/answer/blob/main/LICENSE)

View File

@ -11,6 +11,7 @@
[![LICENSE](https://img.shields.io/badge/License-Apache-green)](https://github.com/answerdev/answer/blob/main/LICENSE)
[![Language](https://img.shields.io/badge/Language-Go-blue.svg)](https://golang.org/)
[![Language](https://img.shields.io/badge/Language-React-blue.svg)](https://reactjs.org/)
[![Go Report Card](https://goreportcard.com/badge/github.com/answerdev/answer)](https://goreportcard.com/report/github.com/answerdev/answer)
## 截图
@ -18,12 +19,10 @@
## 快速开始
### 使用 docker-compose 快速搭建
### 使用 docker 快速搭建
```bash
mkdir answer && cd answer
wget https://raw.githubusercontent.com/answerdev/answer/main/docker-compose.yaml
docker-compose up
docker run -d -p 9080:80 -v $PWD/answer-data/data:/data --name answer answerdev/answer:latest
```
其他安装配置细节请参考 [INSTALL.md](./INSTALL.md)
@ -36,4 +35,4 @@ docker-compose up
## License
[Apache](https://github.com/answerdev/answer/blob/main/LICENSE)
[Apache License 2.0](https://github.com/answerdev/answer/blob/main/LICENSE)

View File

@ -48,7 +48,7 @@ To run answer, use:
Use: "run",
Short: "Run the application",
Long: `Run the application`,
Run: func(cmd *cobra.Command, args []string) {
Run: func(_ *cobra.Command, _ []string) {
runApp()
},
}
@ -58,7 +58,7 @@ To run answer, use:
Use: "init",
Short: "init answer application",
Long: `init answer application`,
Run: func(cmd *cobra.Command, args []string) {
Run: func(_ *cobra.Command, _ []string) {
cli.InstallAllInitialEnvironment(dataDirPath)
c, err := readConfig()
if err != nil {
@ -79,7 +79,7 @@ To run answer, use:
Use: "upgrade",
Short: "upgrade Answer version",
Long: `upgrade Answer version`,
Run: func(cmd *cobra.Command, args []string) {
Run: func(_ *cobra.Command, _ []string) {
c, err := readConfig()
if err != nil {
fmt.Println("read config failed: ", err.Error())
@ -98,7 +98,7 @@ To run answer, use:
Use: "dump",
Short: "back up data",
Long: `back up data`,
Run: func(cmd *cobra.Command, args []string) {
Run: func(_ *cobra.Command, _ []string) {
fmt.Println("Answer is backing up data")
c, err := readConfig()
if err != nil {
@ -119,7 +119,7 @@ To run answer, use:
Use: "check",
Short: "checking the required environment",
Long: `Check if the current environment meets the startup requirements`,
Run: func(cmd *cobra.Command, args []string) {
Run: func(_ *cobra.Command, _ []string) {
fmt.Println("Start checking the required environment...")
if cli.CheckConfigFile(configFilePath) {
fmt.Println("config file exists [✔]")

View File

@ -67,7 +67,7 @@ import (
// Injectors from wire.go:
// initApplication init application.
func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database, cacheConf *data.CacheConf, i18nConf *translator.I18n, swaggerConf *router.SwaggerConfig, serviceConf *service_config.ServiceConfig, logConf log.Logger) (*pacman.Application, func(), error) {
func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database, cacheConf *data.CacheConf, i18nConf *translator.I18n, swaggerConf *router.SwaggerConfig, serviceConf *service_config.ServiceConfig, _ log.Logger) (*pacman.Application, func(), error) {
staticRouter := router.NewStaticRouter(serviceConf)
i18nTranslator, err := translator.NewTranslator(i18nConf)
if err != nil {

View File

@ -3,8 +3,8 @@ server:
addr: 0.0.0.0:80
data:
database:
driver: "mysql"
connection: root:root@tcp(db:3306)/answer
driver: "sqlite3"
connection: "/data/sqlite3/answer.db"
cache:
file_path: "/data/cache/cache.db"
i18n:

View File

@ -56,6 +56,12 @@ const docTemplate = `{
"description": "user status",
"name": "status",
"in": "query"
},
{
"type": "string",
"description": "answer id or question title",
"name": "query",
"in": "query"
}
],
"responses": {
@ -173,6 +179,12 @@ const docTemplate = `{
"description": "user status",
"name": "status",
"in": "query"
},
{
"type": "string",
"description": "question id or title",
"name": "query",
"in": "query"
}
],
"responses": {
@ -709,19 +721,12 @@ const docTemplate = `{
},
{
"type": "string",
"description": "username",
"name": "username",
"in": "query"
},
{
"type": "string",
"description": "email",
"name": "e_mail",
"description": "search query: email, username or id:[id]",
"name": "query",
"in": "query"
},
{
"enum": [
"normal",
"suspended",
"deleted",
"inactive"

View File

@ -44,6 +44,12 @@
"description": "user status",
"name": "status",
"in": "query"
},
{
"type": "string",
"description": "answer id or question title",
"name": "query",
"in": "query"
}
],
"responses": {
@ -161,6 +167,12 @@
"description": "user status",
"name": "status",
"in": "query"
},
{
"type": "string",
"description": "question id or title",
"name": "query",
"in": "query"
}
],
"responses": {
@ -697,19 +709,12 @@
},
{
"type": "string",
"description": "username",
"name": "username",
"in": "query"
},
{
"type": "string",
"description": "email",
"name": "e_mail",
"description": "search query: email, username or id:[id]",
"name": "query",
"in": "query"
},
{
"enum": [
"normal",
"suspended",
"deleted",
"inactive"

View File

@ -1390,6 +1390,10 @@ paths:
in: query
name: status
type: string
- description: answer id or question title
in: query
name: query
type: string
produces:
- application/json
responses:
@ -1463,6 +1467,10 @@ paths:
in: query
name: status
type: string
- description: question id or title
in: query
name: query
type: string
produces:
- application/json
responses:
@ -1788,17 +1796,12 @@ paths:
in: query
name: page_size
type: integer
- description: username
- description: 'search query: email, username or id:[id]'
in: query
name: username
type: string
- description: email
in: query
name: e_mail
name: query
type: string
- description: user status
enum:
- normal
- suspended
- deleted
- inactive

38
go.mod
View File

@ -17,22 +17,22 @@ require (
github.com/google/wire v0.5.0
github.com/jinzhu/copier v0.3.5
github.com/jinzhu/now v1.1.5
github.com/lib/pq v1.10.2
github.com/mattn/go-sqlite3 v1.14.15
github.com/lib/pq v1.10.7
github.com/mattn/go-sqlite3 v1.14.16
github.com/mojocn/base64Captcha v1.3.5
github.com/segmentfault/pacman v1.0.1
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20220929065758-260b3093a347
github.com/segmentfault/pacman/contrib/conf/viper v0.0.0-20220929065758-260b3093a347
github.com/segmentfault/pacman/contrib/i18n v0.0.0-20220929065758-260b3093a347
github.com/segmentfault/pacman/contrib/log/zap v0.0.0-20220929065758-260b3093a347
github.com/segmentfault/pacman/contrib/server/http v0.0.0-20220929065758-260b3093a347
github.com/spf13/cobra v1.5.0
github.com/stretchr/testify v1.8.0
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20221018072427-a15dd1434e05
github.com/segmentfault/pacman/contrib/conf/viper v0.0.0-20221018072427-a15dd1434e05
github.com/segmentfault/pacman/contrib/i18n v0.0.0-20221018072427-a15dd1434e05
github.com/segmentfault/pacman/contrib/log/zap v0.0.0-20221018072427-a15dd1434e05
github.com/segmentfault/pacman/contrib/server/http v0.0.0-20221018072427-a15dd1434e05
github.com/spf13/cobra v1.6.1
github.com/stretchr/testify v1.8.1
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a
github.com/swaggo/gin-swagger v1.5.3
github.com/swaggo/swag v1.8.6
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be
golang.org/x/net v0.0.0-20220927171203-f486391704dc
github.com/swaggo/swag v1.8.7
golang.org/x/crypto v0.1.0
golang.org/x/net v0.1.0
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
xorm.io/builder v0.3.12
xorm.io/core v0.7.3
@ -41,9 +41,9 @@ require (
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/andybalholm/brotli v1.0.1 // indirect
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
@ -52,7 +52,7 @@ require (
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
@ -81,10 +81,10 @@ require (
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.23.0 // indirect
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 // indirect
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.12 // indirect
golang.org/x/image v0.1.0 // indirect
golang.org/x/sys v0.1.0 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/tools v0.2.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/ini.v1 v1.67.0 // indirect

48
go.sum
View File

@ -64,6 +64,8 @@ github.com/anargu/gin-brotli v0.0.0-20220116052358-12bf532d5267 h1:vDHsaEcs/Q0dw
github.com/anargu/gin-brotli v0.0.0-20220116052358-12bf532d5267/go.mod h1:Yj3yPP/vi87JjwylUTCMyd6FrOfGqP1AHk0305hDm2o=
github.com/andybalholm/brotli v1.0.1 h1:KqhlKozYbRtJvsPrrEeXcO+N2l6NYT5A2QAFmSULpEc=
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@ -131,6 +133,8 @@ github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
@ -298,6 +302,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
@ -397,6 +403,8 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
@ -426,6 +434,8 @@ github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsO
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
@ -539,14 +549,24 @@ github.com/segmentfault/pacman v1.0.1 h1:GFdvPtNxvVVjnDM4ty02D/+4unHwG9PmjcOZSc2
github.com/segmentfault/pacman v1.0.1/go.mod h1:5lNp5REd8QMThmBUvR3Fi9Y3AsOB4GRq7soCB4QLqOs=
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20220929065758-260b3093a347 h1:0xWBBXHHuemzMY61KYJXh7F5FW/4K8g98RYKNXodTCc=
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20220929065758-260b3093a347/go.mod h1:rmf1TCwz67dyM+AmTwSd1BxTo2AOYHj262lP93bOZbs=
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20221018072427-a15dd1434e05 h1:rXsXgC/HR7m4V425l9pDBW/qxJv6zCh6pEvvO1ZCNsI=
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20221018072427-a15dd1434e05/go.mod h1:rmf1TCwz67dyM+AmTwSd1BxTo2AOYHj262lP93bOZbs=
github.com/segmentfault/pacman/contrib/conf/viper v0.0.0-20220929065758-260b3093a347 h1:WpnEbmZFE8FYIgvseX+NJtDgGJlM1KSaKJhoxJywUgo=
github.com/segmentfault/pacman/contrib/conf/viper v0.0.0-20220929065758-260b3093a347/go.mod h1:prPjFam7MyZ5b3S9dcDOt2tMPz6kf7C9c243s9zSwPY=
github.com/segmentfault/pacman/contrib/conf/viper v0.0.0-20221018072427-a15dd1434e05 h1:BlqTgc3/MYKG6vMI2MI+6o+7P4Gy5PXlawu185wPXAk=
github.com/segmentfault/pacman/contrib/conf/viper v0.0.0-20221018072427-a15dd1434e05/go.mod h1:prPjFam7MyZ5b3S9dcDOt2tMPz6kf7C9c243s9zSwPY=
github.com/segmentfault/pacman/contrib/i18n v0.0.0-20220929065758-260b3093a347 h1:Q29Ky9ZUGhdLIygfX6jwPYeEa7Wqn8o3f1NJWb8LvvE=
github.com/segmentfault/pacman/contrib/i18n v0.0.0-20220929065758-260b3093a347/go.mod h1:5Afm+OQdau/HQqSOp/ALlSUp0vZsMMMbv//kJhxuoi8=
github.com/segmentfault/pacman/contrib/i18n v0.0.0-20221018072427-a15dd1434e05 h1:gFCY9KUxhYg+/MXNcDYl4ILK+R1SG78FtaSR3JqZNYY=
github.com/segmentfault/pacman/contrib/i18n v0.0.0-20221018072427-a15dd1434e05/go.mod h1:5Afm+OQdau/HQqSOp/ALlSUp0vZsMMMbv//kJhxuoi8=
github.com/segmentfault/pacman/contrib/log/zap v0.0.0-20220929065758-260b3093a347 h1:7Adjc296AKv32dg88S0T8t9K3+N+PFYLSCctpPnCUr0=
github.com/segmentfault/pacman/contrib/log/zap v0.0.0-20220929065758-260b3093a347/go.mod h1:L4GqtXLoR73obTYqUQIzfkm8NG8pvZafxFb6KZFSSHk=
github.com/segmentfault/pacman/contrib/log/zap v0.0.0-20221018072427-a15dd1434e05 h1:jcGZU2juv0L3eFEkuZYV14ESLUlWfGMWnP0mjOfrSZc=
github.com/segmentfault/pacman/contrib/log/zap v0.0.0-20221018072427-a15dd1434e05/go.mod h1:L4GqtXLoR73obTYqUQIzfkm8NG8pvZafxFb6KZFSSHk=
github.com/segmentfault/pacman/contrib/server/http v0.0.0-20220929065758-260b3093a347 h1:CfuRhTPK2CBQIZruq5ceuTVthspe8U1FDjWXXI2RWdo=
github.com/segmentfault/pacman/contrib/server/http v0.0.0-20220929065758-260b3093a347/go.mod h1:UjNiOFYv1uGCq1ZCcONaKq4eE7MW3nbgpLqgl8f9N40=
github.com/segmentfault/pacman/contrib/server/http v0.0.0-20221018072427-a15dd1434e05 h1:91is1nKNbfTOl8CvMYiFgg4c5Vmol+5mVmMV/jDXD+A=
github.com/segmentfault/pacman/contrib/server/http v0.0.0-20221018072427-a15dd1434e05/go.mod h1:UjNiOFYv1uGCq1ZCcONaKq4eE7MW3nbgpLqgl8f9N40=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
@ -565,6 +585,8 @@ github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
@ -579,6 +601,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -588,6 +611,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a h1:kAe4YSu0O0UFn1DowNo2MY5p6xzqtJ/wQ7LZynSvGaY=
@ -597,10 +622,13 @@ github.com/swaggo/gin-swagger v1.5.3/go.mod h1:3XJKSfHjDMB5dBo/0rrTXidPmgLeqsX89
github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=
github.com/swaggo/swag v1.8.6 h1:2rgOaLbonWu1PLP6G+/rYjSvPg0jQE0HtrEKuE380eg=
github.com/swaggo/swag v1.8.6/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBnseg=
github.com/swaggo/swag v1.8.7 h1:2K9ivTD3teEO+2fXV6zrZKDqk5IuU2aJtBDo8U7omWU=
github.com/swaggo/swag v1.8.7/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
@ -614,6 +642,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
@ -663,10 +692,13 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A=
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -683,6 +715,8 @@ golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+o
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 h1:Lj6HJGCSn5AjxRAH2+r35Mir4icalbqku+CLUtjnvXY=
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk=
golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -706,6 +740,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -749,8 +784,11 @@ golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220927171203-f486391704dc h1:FxpXZdoBqT8RjqTy6i1E8nXHhW21wK7ptQ/EPIGxzPQ=
golang.org/x/net v0.0.0-20220927171203-f486391704dc/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -771,6 +809,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -830,9 +869,14 @@ golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -845,6 +889,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -909,6 +955,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

170
i18n/it_IT.yaml Normal file
View File

@ -0,0 +1,170 @@
base:
success:
other: "Successo"
unknown:
other: "Errore sconosciuto"
request_format_error:
other: "Il formato della richiesta non è valido"
unauthorized_error:
other: "Non autorizzato"
database_error:
other: "Errore server dati"
email:
other: "email"
password:
other: "password"
email_or_password_wrong_error: &email_or_password_wrong
other: "Email o password errati"
error:
admin:
email_or_password_wrong: *email_or_password_wrong
answer:
not_found:
other: "Risposta non trovata"
comment:
edit_without_permission:
other: "Non si hanno di privilegi sufficienti per modificare il commento"
not_found:
other: "Commento non trovato"
email:
duplicate:
other: "email già esistente"
need_to_be_verified:
other: "email deve essere verificata"
verify_url_expired:
other: "l'url di verifica email è scaduto, si prega di reinviare la email"
lang:
not_found:
other: "lingua non trovata"
object:
captcha_verification_failed:
other: "captcha errato"
disallow_follow:
other: "Non sei autorizzato a seguire"
disallow_vote:
other: "non sei autorizzato a votare"
disallow_vote_your_self:
other: "Non puoi votare un tuo post!"
not_found:
other: "oggetto non trovato"
verification_failed:
other: "verifica fallita"
email_or_password_incorrect:
other: "email o password incorretti"
old_password_verification_failed:
other: "la verifica della vecchia password è fallita"
new_password_same_as_previous_setting:
other: "La nuova password è identica alla precedente"
question:
not_found:
other: "domanda non trovata"
rank:
fail_to_meet_the_condition:
other: "Condizioni non valide per il grado"
report:
handle_failed:
other: "Gestione del report fallita"
not_found:
other: "Report non trovato"
tag:
not_found:
other: "Etichetta non trovata"
theme:
not_found:
other: "tema non trovato"
user:
email_or_password_wrong:
other: *email_or_password_wrong
not_found:
other: "utente non trovato"
suspended:
other: "utente sospeso"
username_invalid:
other: "utente non valido"
username_duplicate:
other: "utente già in uso"
report:
spam:
name:
other: "spam"
description:
other: "Questo articolo è una pubblicità o vandalismo. Non è utile o rilevante all'argomento corrente"
rude:
name:
other: "scortese o violento"
description:
other: "Una persona ragionevole trova questo contenuto inappropriato a un discorso rispettoso"
duplicate:
name:
other: "duplicato"
description:
other: "Questa domanda è già stata posta e ha già una risposta."
not_answer:
name:
other: "non è una risposta"
description:
other: "Questo è stato postato come una risposta, ma non sta cercando di rispondere alla domanda. Dovrebbe essere una modifica, un commento, un'altra domanda o cancellato del tutto."
not_need:
name:
other: "non più necessario"
description:
other: "Questo commento è datato, conversazionale o non rilevante a questo articolo."
other:
name:
other: "altro"
description:
other: "Questo articolo richiede una supervisione dello staff per altre ragioni non listate sopra."
question:
close:
duplicate:
name:
other: "spam"
description:
other: "Questa domanda è già stata chiesta o ha già una risposta."
guideline:
name:
other: "motivo legato alla community"
description:
other: "Questa domanda non soddisfa le linee guida della comunità."
multiple:
name:
other: "richiede maggiori dettagli o chiarezza"
description:
other: "Questa domanda attualmente contiene più domande. Deve concentrarsi solamente su un unico problema."
other:
name:
other: "altro"
description:
other: "Questo articolo richiede un'altro motivo non listato sopra."
notification:
action:
update_question:
other: "domanda aggiornata"
answer_the_question:
other: "domanda risposta"
update_answer:
other: "risposta aggiornata"
adopt_answer:
other: "risposta accettata"
comment_question:
other: "domanda commentata"
comment_answer:
other: "risposta commentata"
reply_to_you:
other: "hai ricevuto risposta"
mention_you:
other: "sei stato menzionato"
your_question_is_closed:
other: "la tua domanda è stata chiusa"
your_question_was_deleted:
other: "la tua domanda è stata rimossa"
your_answer_was_deleted:
other: "la tua risposta è stata rimossa"
your_comment_was_deleted:
other: "il tuo commento è stato rimosso"

View File

@ -3,7 +3,7 @@ package constant
import "time"
const (
Default_PageSize = 20 //Default number of pages
DefaultPageSize = 20 // Default number of pages
UserStatusChangedCacheKey = "answer:user:status:"
UserStatusChangedCacheTime = 7 * 24 * time.Hour
UserTokenCacheKey = "answer:user:token:"

View File

@ -13,7 +13,7 @@ const (
ReportNotAnswerDescription = "report.not_answer.description"
ReportNotNeedName = "report.not_need.name"
ReportNotNeedDescription = "report.not_need.description"
//question close
// question close
QuestionCloseDuplicateName = "question.close.duplicate.name"
QuestionCloseDuplicateDescription = "question.close.duplicate.description"
QuestionCloseGuidelineName = "question.close.guideline.name"
@ -27,8 +27,8 @@ const (
const (
// TODO put this in database
// TODO need reason controller to resolve
QuestionCloseJson = `[{"name":"question.close.duplicate.name","description":"question.close.duplicate.description","source":"question","type":1,"have_content":false,"content_type":""},{"name":"question.close.guideline.name","description":"question.close.guideline.description","source":"question","type":2,"have_content":false,"content_type":""},{"name":"question.close.multiple.name","description":"question.close.multiple.description","source":"question","type":3,"have_content":true,"content_type":"text"},{"name":"question.close.other.name","description":"question.close.other.description","source":"question","type":4,"have_content":true,"content_type":"textarea"}]`
QuestionReportJson = `[{"name":"report.spam.name","description":"report.spam.description","source":"question","type":1,"have_content":false,"content_type":""},{"name":"report.rude.name","description":"report.rude.description","source":"question","type":2,"have_content":false,"content_type":""},{"name":"report.duplicate.name","description":"report.duplicate.description","source":"question","type":3,"have_content":true,"content_type":"text"},{"name":"report.other.name","description":"report.other.description","source":"question","type":4,"have_content":true,"content_type":"textarea"}]`
AnswerReportJson = `[{"name":"report.spam.name","description":"report.spam.description","source":"answer","type":1,"have_content":false,"content_type":""},{"name":"report.rude.name","description":"report.rude.description","source":"answer","type":2,"have_content":false,"content_type":""},{"name":"report.not_answer.name","description":"report.not_answer.description","source":"answer","type":3,"have_content":false,"content_type":""},{"name":"report.other.name","description":"report.other.description","source":"answer","type":4,"have_content":true,"content_type":"textarea"}]`
CommentReportJson = `[{"name":"report.spam.name","description":"report.spam.description","source":"comment","type":1,"have_content":false,"content_type":""},{"name":"report.rude.name","description":"report.rude.description","source":"comment","type":2,"have_content":false,"content_type":""},{"name":"report.not_need.name","description":"report.not_need.description","source":"comment","type":3,"have_content":true,"content_type":"text"},{"name":"report.other.name","description":"report.other.description","source":"comment","type":4,"have_content":true,"content_type":"textarea"}]`
QuestionCloseJSON = `[{"name":"question.close.duplicate.name","description":"question.close.duplicate.description","source":"question","type":1,"have_content":false,"content_type":""},{"name":"question.close.guideline.name","description":"question.close.guideline.description","source":"question","type":2,"have_content":false,"content_type":""},{"name":"question.close.multiple.name","description":"question.close.multiple.description","source":"question","type":3,"have_content":true,"content_type":"text"},{"name":"question.close.other.name","description":"question.close.other.description","source":"question","type":4,"have_content":true,"content_type":"textarea"}]`
QuestionReportJSON = `[{"name":"report.spam.name","description":"report.spam.description","source":"question","type":1,"have_content":false,"content_type":""},{"name":"report.rude.name","description":"report.rude.description","source":"question","type":2,"have_content":false,"content_type":""},{"name":"report.duplicate.name","description":"report.duplicate.description","source":"question","type":3,"have_content":true,"content_type":"text"},{"name":"report.other.name","description":"report.other.description","source":"question","type":4,"have_content":true,"content_type":"textarea"}]`
AnswerReportJSON = `[{"name":"report.spam.name","description":"report.spam.description","source":"answer","type":1,"have_content":false,"content_type":""},{"name":"report.rude.name","description":"report.rude.description","source":"answer","type":2,"have_content":false,"content_type":""},{"name":"report.not_answer.name","description":"report.not_answer.description","source":"answer","type":3,"have_content":false,"content_type":""},{"name":"report.other.name","description":"report.other.description","source":"answer","type":4,"have_content":true,"content_type":"textarea"}]`
CommentReportJSON = `[{"name":"report.spam.name","description":"report.spam.description","source":"comment","type":1,"have_content":false,"content_type":""},{"name":"report.rude.name","description":"report.rude.description","source":"comment","type":2,"have_content":false,"content_type":""},{"name":"report.not_need.name","description":"report.not_need.description","source":"comment","type":3,"have_content":true,"content_type":"text"},{"name":"report.other.name","description":"report.other.description","source":"comment","type":4,"have_content":true,"content_type":"textarea"}]`
)

View File

@ -37,6 +37,13 @@ func NewDB(debug bool, dataConf *Database) (*xorm.Engine, error) {
if dataConf.Driver == "" {
dataConf.Driver = string(schemas.MYSQL)
}
if dataConf.Driver == string(schemas.SQLITE) {
dbFileDir := filepath.Dir(dataConf.Connection)
log.Debugf("try to create database directory %s", dbFileDir)
if err := dir.CreateDirIfNotExist(dbFileDir); err != nil {
log.Errorf("create database dir failed: %s", err)
}
}
engine, err := xorm.NewEngine(dataConf.Driver, dataConf.Connection)
if err != nil {
return nil, err

View File

@ -40,7 +40,6 @@ func HandleResponse(ctx *gin.Context, err error, data interface{}) {
respBody.Data = data
}
ctx.JSON(myErr.Code, respBody)
return
}
// BindAndCheck bind request and check

View File

@ -14,9 +14,7 @@ import (
"github.com/segmentfault/pacman/errors"
)
var (
ctxUuidKey = "ctxUuidKey"
)
var ctxUUIDKey = "ctxUuidKey"
// AuthUserMiddleware auth user middleware
type AuthUserMiddleware struct {
@ -44,7 +42,7 @@ func (am *AuthUserMiddleware) Auth() gin.HandlerFunc {
return
}
if userInfo != nil {
ctx.Set(ctxUuidKey, userInfo)
ctx.Set(ctxUUIDKey, userInfo)
}
ctx.Next()
}
@ -82,7 +80,7 @@ func (am *AuthUserMiddleware) MustAuth() gin.HandlerFunc {
ctx.Abort()
return
}
ctx.Set(ctxUuidKey, userInfo)
ctx.Set(ctxUUIDKey, userInfo)
ctx.Next()
}
}
@ -107,7 +105,7 @@ func (am *AuthUserMiddleware) CmsAuth() gin.HandlerFunc {
ctx.Abort()
return
}
ctx.Set(ctxUuidKey, userInfo)
ctx.Set(ctxUUIDKey, userInfo)
}
ctx.Next()
}
@ -115,7 +113,7 @@ func (am *AuthUserMiddleware) CmsAuth() gin.HandlerFunc {
// GetLoginUserIDFromContext get user id from context
func GetLoginUserIDFromContext(ctx *gin.Context) (userID string) {
userInfo, exist := ctx.Get(ctxUuidKey)
userInfo, exist := ctx.Get(ctxUUIDKey)
if !exist {
return ""
}
@ -128,7 +126,7 @@ func GetLoginUserIDFromContext(ctx *gin.Context) (userID string) {
// GetUserInfoFromContext get user info from context
func GetUserInfoFromContext(ctx *gin.Context) (u *entity.UserCacheInfo) {
userInfo, exist := ctx.Get(ctxUuidKey)
userInfo, exist := ctx.Get(ctxUUIDKey)
if !exist {
return nil
}

View File

@ -28,7 +28,7 @@ const (
UsernameDuplicate = "error.user.username_duplicate"
UserSetAvatar = "error.user.set_avatar"
EmailDuplicate = "error.email.duplicate"
EmailVerifyUrlExpired = "error.email.verify_url_expired"
EmailVerifyURLExpired = "error.email.verify_url_expired"
EmailNeedToBeVerified = "error.email.need_to_be_verified"
UserSuspended = "error.user.suspended"
ObjectNotFound = "error.object.not_found"

View File

@ -9,7 +9,7 @@ import (
"github.com/go-playground/locales"
english "github.com/go-playground/locales/en"
zhongwen "github.com/go-playground/locales/zh"
"github.com/go-playground/universal-translator"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
"github.com/go-playground/validator/v10/translations/en"
"github.com/go-playground/validator/v10/translations/zh"
@ -31,10 +31,8 @@ type ErrorField struct {
Value string `json:"value"`
}
var (
// GlobalValidatorMapping is a mapping from validator to translator used
GlobalValidatorMapping = make(map[string]*MyValidator, 0)
)
// GlobalValidatorMapping is a mapping from validator to translator used
var GlobalValidatorMapping = make(map[string]*MyValidator, 0)
func init() {
zhTran, zhVal := getTran(zhongwen.New(), i18n.LanguageChinese.Abbr()), createDefaultValidator(i18n.LanguageChinese)

View File

@ -31,7 +31,6 @@ func InstallAllInitialEnvironment(dataDirPath string) {
installUploadDir()
installI18nBundle()
fmt.Println("install all initial environment done")
return
}
func installConfigFile() {
@ -96,7 +95,7 @@ func installI18nBundle() {
}
func writerFile(filePath, content string) error {
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666)
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0o666)
if err != nil {
return err
}

View File

@ -62,9 +62,9 @@ func (ac *AnswerController) RemoveAnswer(ctx *gin.Context) {
// @Success 200 {string} string ""
func (ac *AnswerController) Get(ctx *gin.Context) {
id := ctx.Query("id")
userId := middleware.GetLoginUserIDFromContext(ctx)
userID := middleware.GetLoginUserIDFromContext(ctx)
info, questionInfo, has, err := ac.answerService.Get(ctx, id, userId)
info, questionInfo, has, err := ac.answerService.Get(ctx, id, userID)
if err != nil {
handler.HandleResponse(ctx, err, gin.H{})
return
@ -101,18 +101,18 @@ func (ac *AnswerController) Add(ctx *gin.Context) {
return
}
answerId, err := ac.answerService.Insert(ctx, req)
answerID, err := ac.answerService.Insert(ctx, req)
if err != nil {
handler.HandleResponse(ctx, err, nil)
return
}
info, questionInfo, has, err := ac.answerService.Get(ctx, answerId, req.UserID)
info, questionInfo, has, err := ac.answerService.Get(ctx, answerID, req.UserID)
if err != nil {
handler.HandleResponse(ctx, err, nil)
return
}
if !has {
//todo !has
// todo !has
handler.HandleResponse(ctx, nil, nil)
return
}
@ -120,7 +120,6 @@ func (ac *AnswerController) Add(ctx *gin.Context) {
"info": info,
"question": questionInfo,
})
}
// Update godoc
@ -156,7 +155,7 @@ func (ac *AnswerController) Update(ctx *gin.Context) {
return
}
if !has {
//todo !has
// todo !has
handler.HandleResponse(ctx, nil, nil)
return
}

View File

@ -66,7 +66,7 @@ func (qc *QuestionController) CloseQuestion(ctx *gin.Context) {
if handler.BindAndCheck(ctx, req) {
return
}
req.UserId = middleware.GetLoginUserIDFromContext(ctx)
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
err := qc.questionService.CloseQuestion(ctx, req)
handler.HandleResponse(ctx, err, nil)
}
@ -114,7 +114,6 @@ func (qc *QuestionController) SimilarQuestion(ctx *gin.Context) {
"list": list,
"count": count,
})
}
// Index godoc
@ -365,6 +364,7 @@ func (qc *QuestionController) UserCollectionList(ctx *gin.Context) {
// @Param page query int false "page size"
// @Param page_size query int false "page size"
// @Param status query string false "user status" Enums(available, closed, deleted)
// @Param query query string false "question id or title"
// @Success 200 {object} handler.RespBody
// @Router /answer/admin/api/question/page [get]
func (qc *QuestionController) CmsSearchList(ctx *gin.Context) {
@ -390,6 +390,7 @@ func (qc *QuestionController) CmsSearchList(ctx *gin.Context) {
// @Param page query int false "page size"
// @Param page_size query int false "page size"
// @Param status query string false "user status" Enums(available,deleted)
// @Param query query string false "answer id or question title"
// @Success 200 {object} handler.RespBody
// @Router /answer/admin/api/answer/page [get]
func (qc *QuestionController) CmsSearchAnswerList(ctx *gin.Context) {

View File

@ -35,7 +35,8 @@ func NewUserController(
userService *service.UserService,
actionService *action.CaptchaService,
emailService *export.EmailService,
uploaderService *uploader.UploaderService) *UserController {
uploaderService *uploader.UploaderService,
) *UserController {
return &UserController{
authService: authService,
userService: userService,
@ -119,7 +120,7 @@ func (uc *UserController) UserEmailLogin(ctx *gin.Context) {
return
}
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecord_Type_Login, ctx.ClientIP(), req.CaptchaID, req.CaptchaCode)
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecordTypeLogin, ctx.ClientIP(), req.CaptchaID, req.CaptchaCode)
if !captchaPass {
resp := schema.UserVerifyEmailErrorResponse{
Key: "captcha_code",
@ -132,7 +133,7 @@ func (uc *UserController) UserEmailLogin(ctx *gin.Context) {
resp, err := uc.userService.EmailLogin(ctx, req)
if err != nil {
_, _ = uc.actionService.ActionRecordAdd(ctx, schema.ActionRecord_Type_Login, ctx.ClientIP())
_, _ = uc.actionService.ActionRecordAdd(ctx, schema.ActionRecordTypeLogin, ctx.ClientIP())
resp := schema.UserVerifyEmailErrorResponse{
Key: "e_mail",
Value: "error.object.email_or_password_incorrect",
@ -141,7 +142,7 @@ func (uc *UserController) UserEmailLogin(ctx *gin.Context) {
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), resp)
return
}
uc.actionService.ActionRecordDel(ctx, schema.ActionRecord_Type_Login, ctx.ClientIP())
uc.actionService.ActionRecordDel(ctx, schema.ActionRecordTypeLogin, ctx.ClientIP())
handler.HandleResponse(ctx, nil, resp)
}
@ -159,7 +160,7 @@ func (uc *UserController) RetrievePassWord(ctx *gin.Context) {
if handler.BindAndCheck(ctx, req) {
return
}
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecord_Type_Find_Pass, ctx.ClientIP(), req.CaptchaID, req.CaptchaCode)
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecordTypeFindPass, ctx.ClientIP(), req.CaptchaID, req.CaptchaCode)
if !captchaPass {
resp := schema.UserVerifyEmailErrorResponse{
Key: "captcha_code",
@ -169,7 +170,7 @@ func (uc *UserController) RetrievePassWord(ctx *gin.Context) {
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), resp)
return
}
_, _ = uc.actionService.ActionRecordAdd(ctx, schema.ActionRecord_Type_Find_Pass, ctx.ClientIP())
_, _ = uc.actionService.ActionRecordAdd(ctx, schema.ActionRecordTypeFindPass, ctx.ClientIP())
code, err := uc.userService.RetrievePassWord(ctx, req)
handler.HandleResponse(ctx, err, code)
}
@ -191,13 +192,13 @@ func (uc *UserController) UseRePassWord(ctx *gin.Context) {
req.Content = uc.emailService.VerifyUrlExpired(ctx, req.Code)
if len(req.Content) == 0 {
handler.HandleResponse(ctx, errors.Forbidden(reason.EmailVerifyUrlExpired),
&schema.ForbiddenResp{Type: schema.ForbiddenReasonTypeUrlExpired})
handler.HandleResponse(ctx, errors.Forbidden(reason.EmailVerifyURLExpired),
&schema.ForbiddenResp{Type: schema.ForbiddenReasonTypeURLExpired})
return
}
resp, err := uc.userService.UseRePassWord(ctx, req)
uc.actionService.ActionRecordDel(ctx, schema.ActionRecord_Type_Find_Pass, ctx.ClientIP())
uc.actionService.ActionRecordDel(ctx, schema.ActionRecordTypeFindPass, ctx.ClientIP())
handler.HandleResponse(ctx, err, resp)
}
@ -252,8 +253,8 @@ func (uc *UserController) UserVerifyEmail(ctx *gin.Context) {
req.Content = uc.emailService.VerifyUrlExpired(ctx, req.Code)
if len(req.Content) == 0 {
handler.HandleResponse(ctx, errors.Forbidden(reason.EmailVerifyUrlExpired),
&schema.ForbiddenResp{Type: schema.ForbiddenReasonTypeUrlExpired})
handler.HandleResponse(ctx, errors.Forbidden(reason.EmailVerifyURLExpired),
&schema.ForbiddenResp{Type: schema.ForbiddenReasonTypeURLExpired})
return
}
@ -263,7 +264,7 @@ func (uc *UserController) UserVerifyEmail(ctx *gin.Context) {
return
}
uc.actionService.ActionRecordDel(ctx, schema.ActionRecord_Type_Email, ctx.ClientIP())
uc.actionService.ActionRecordDel(ctx, schema.ActionRecordTypeEmail, ctx.ClientIP())
handler.HandleResponse(ctx, err, resp)
}
@ -289,7 +290,7 @@ func (uc *UserController) UserVerifyEmailSend(ctx *gin.Context) {
return
}
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecord_Type_Email, ctx.ClientIP(),
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecordTypeEmail, ctx.ClientIP(),
req.CaptchaID, req.CaptchaCode)
if !captchaPass {
resp := schema.UserVerifyEmailErrorResponse{
@ -301,7 +302,7 @@ func (uc *UserController) UserVerifyEmailSend(ctx *gin.Context) {
return
}
uc.actionService.ActionRecordAdd(ctx, schema.ActionRecord_Type_Email, ctx.ClientIP())
uc.actionService.ActionRecordAdd(ctx, schema.ActionRecordTypeEmail, ctx.ClientIP())
err := uc.userService.UserVerifyEmailSend(ctx, userInfo.UserID)
handler.HandleResponse(ctx, err, nil)
}
@ -321,7 +322,7 @@ func (uc *UserController) UserModifyPassWord(ctx *gin.Context) {
if handler.BindAndCheck(ctx, req) {
return
}
req.UserId = middleware.GetLoginUserIDFromContext(ctx)
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
oldPassVerification, err := uc.userService.UserModifyPassWordVerification(ctx, req)
if err != nil {
@ -367,7 +368,7 @@ func (uc *UserController) UserUpdateInfo(ctx *gin.Context) {
if handler.BindAndCheck(ctx, req) {
return
}
req.UserId = middleware.GetLoginUserIDFromContext(ctx)
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
err := uc.userService.UpdateInfo(ctx, req)
handler.HandleResponse(ctx, err, nil)
}
@ -445,7 +446,7 @@ func (uc *UserController) ActionRecord(ctx *gin.Context) {
if handler.BindAndCheck(ctx, req) {
return
}
req.Ip = ctx.ClientIP()
req.IP = ctx.ClientIP()
resp, err := uc.actionService.ActionRecord(ctx, req)
handler.HandleResponse(ctx, err, resp)
@ -467,8 +468,8 @@ func (uc *UserController) UserNoticeSet(ctx *gin.Context) {
return
}
req.UserId = middleware.GetLoginUserIDFromContext(ctx)
resp, err := uc.userService.UserNoticeSet(ctx, req.UserId, req.NoticeSwitch)
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
resp, err := uc.userService.UserNoticeSet(ctx, req.UserID, req.NoticeSwitch)
handler.HandleResponse(ctx, err, resp)
}
@ -490,7 +491,7 @@ func (uc *UserController) UserChangeEmailSendCode(ctx *gin.Context) {
// If the user is not logged in, the api cannot be used.
// If the user email is not verified, that also can use this api to modify the email.
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecord_Type_Email, ctx.ClientIP(), req.CaptchaID, req.CaptchaCode)
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecordTypeEmail, ctx.ClientIP(), req.CaptchaID, req.CaptchaCode)
if !captchaPass {
resp := schema.UserVerifyEmailErrorResponse{
Key: "captcha_code",
@ -505,7 +506,7 @@ func (uc *UserController) UserChangeEmailSendCode(ctx *gin.Context) {
handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil)
return
}
_, _ = uc.actionService.ActionRecordAdd(ctx, schema.ActionRecord_Type_Email, ctx.ClientIP())
_, _ = uc.actionService.ActionRecordAdd(ctx, schema.ActionRecordTypeEmail, ctx.ClientIP())
err := uc.userService.UserChangeEmailSendCode(ctx, req)
handler.HandleResponse(ctx, err, nil)
}
@ -527,12 +528,12 @@ func (uc *UserController) UserChangeEmailVerify(ctx *gin.Context) {
}
req.Content = uc.emailService.VerifyUrlExpired(ctx, req.Code)
if len(req.Content) == 0 {
handler.HandleResponse(ctx, errors.Forbidden(reason.EmailVerifyUrlExpired),
&schema.ForbiddenResp{Type: schema.ForbiddenReasonTypeUrlExpired})
handler.HandleResponse(ctx, errors.Forbidden(reason.EmailVerifyURLExpired),
&schema.ForbiddenResp{Type: schema.ForbiddenReasonTypeURLExpired})
return
}
err := uc.userService.UserChangeEmailVerify(ctx, req.Content)
uc.actionService.ActionRecordDel(ctx, schema.ActionRecord_Type_Email, ctx.ClientIP())
uc.actionService.ActionRecordDel(ctx, schema.ActionRecordTypeEmail, ctx.ClientIP())
handler.HandleResponse(ctx, err, nil)
}

View File

@ -45,9 +45,8 @@ func (uc *UserBackyardController) UpdateUserStatus(ctx *gin.Context) {
// @Produce json
// @Param page query int false "page size"
// @Param page_size query int false "page size"
// @Param username query string false "username"
// @Param e_mail query string false "email"
// @Param status query string false "user status" Enums(normal, suspended, deleted, inactive)
// @Param query query string false "search query: email, username or id:[id]"
// @Param status query string false "user status" Enums(suspended, deleted, inactive)
// @Success 200 {object} handler.RespBody{data=pager.PageModel{records=[]schema.GetUserPageResp}}
// @Router /answer/admin/api/users/page [get]
func (uc *UserBackyardController) GetUserPage(ctx *gin.Context) {

View File

@ -3,9 +3,9 @@ package entity
import "time"
const (
Answer_Search_OrderBy_Default = "default"
Answer_Search_OrderBy_Time = "updated"
Answer_Search_OrderBy_Vote = "vote"
AnswerSearchOrderByDefault = "default"
AnswerSearchOrderByTime = "updated"
AnswerSearchOrderByVote = "vote"
AnswerStatusAvailable = 1
AnswerStatusDeleted = 10
@ -35,15 +35,16 @@ type Answer struct {
type AnswerSearch struct {
Answer
Order string `json:"order_by" ` // default or updated
Page int `json:"page" form:"page"` //Query number of pages
PageSize int `json:"page_size" form:"page_size"` //Search page size
Page int `json:"page" form:"page"` // Query number of pages
PageSize int `json:"page_size" form:"page_size"` // Search page size
}
type CmsAnswerSearch struct {
Page int `json:"page" form:"page"` //Query number of pages
PageSize int `json:"page_size" form:"page_size"` //Search page size
Page int `json:"page" form:"page"` // Query number of pages
PageSize int `json:"page_size" form:"page_size"` // Search page size
Status int `json:"-" form:"-"`
StatusStr string `json:"status" form:"status"` //Status 1 Available 2 closed 10 Deleted
StatusStr string `json:"status" form:"status"` // Status 1 Available 2 closed 10 Deleted
Query string `validate:"omitempty,gt=0,lte=100" json:"query" form:"query" ` //Query string
}
type AdminSetAnswerStatusRequest struct {

View File

@ -40,7 +40,7 @@ type User struct {
Avatar string `xorm:"not null default '' VARCHAR(255) avatar"`
Mobile string `xorm:"not null VARCHAR(20) mobile"`
Bio string `xorm:"not null TEXT bio"`
BioHtml string `xorm:"not null TEXT bio_html"`
BioHTML string `xorm:"not null TEXT bio_html"`
Website string `xorm:"not null default '' VARCHAR(255) website"`
Location string `xorm:"not null default '' VARCHAR(100) location"`
IPInfo string `xorm:"not null default '' VARCHAR(255) ip_info"`
@ -54,6 +54,6 @@ func (User) TableName() string {
type UserSearch struct {
User
Page int `json:"page" form:"page"` //Query number of pages
PageSize int `json:"page_size" form:"page_size"` //Search page size
Page int `json:"page" form:"page"` // Query number of pages
PageSize int `json:"page_size" form:"page_size"` // Search page size
}

View File

@ -8,27 +8,25 @@ import (
"xorm.io/xorm"
)
var (
tables = []interface{}{
&entity.Activity{},
&entity.Answer{},
&entity.Collection{},
&entity.CollectionGroup{},
&entity.Comment{},
&entity.Config{},
&entity.Meta{},
&entity.Notification{},
&entity.Question{},
&entity.Report{},
&entity.Revision{},
&entity.SiteInfo{},
&entity.Tag{},
&entity.TagRel{},
&entity.Uniqid{},
&entity.User{},
&entity.Version{},
}
)
var tables = []interface{}{
&entity.Activity{},
&entity.Answer{},
&entity.Collection{},
&entity.CollectionGroup{},
&entity.Comment{},
&entity.Config{},
&entity.Meta{},
&entity.Notification{},
&entity.Question{},
&entity.Report{},
&entity.Revision{},
&entity.SiteInfo{},
&entity.Tag{},
&entity.TagRel{},
&entity.Uniqid{},
&entity.User{},
&entity.Version{},
}
// InitDB init db
func InitDB(dataConf *data.Database) (err error) {
@ -95,91 +93,91 @@ func initSiteInfo(engine *xorm.Engine) error {
func initConfigTable(engine *xorm.Engine) error {
defaultConfigTable := []*entity.Config{
{1, "answer.accepted", `15`},
{2, "answer.voted_up", `10`},
{3, "question.voted_up", `10`},
{4, "tag.edit_accepted", `2`},
{5, "answer.accept", `2`},
{6, "answer.voted_down_cancel", `2`},
{7, "question.voted_down_cancel", `2`},
{8, "answer.vote_down_cancel", `1`},
{9, "question.vote_down_cancel", `1`},
{10, "user.activated", `1`},
{11, "edit.accepted", `2`},
{12, "answer.vote_down", `-1`},
{13, "question.voted_down", `-2`},
{14, "answer.voted_down", `-2`},
{15, "answer.accept_cancel", `-2`},
{16, "answer.deleted", `-5`},
{17, "question.voted_up_cancel", `-10`},
{18, "answer.voted_up_cancel", `-10`},
{19, "answer.accepted_cancel", `-15`},
{20, "object.reported", `-100`},
{21, "edit.rejected", `-2`},
{22, "daily_rank_limit", `200`},
{23, "daily_rank_limit.exclude", `["answer.accepted"]`},
{24, "user.follow", `0`},
{25, "comment.vote_up", `0`},
{26, "comment.vote_up_cancel", `0`},
{27, "question.vote_down", `0`},
{28, "question.vote_up", `0`},
{29, "question.vote_up_cancel", `0`},
{30, "answer.vote_up", `0`},
{31, "answer.vote_up_cancel", `0`},
{32, "question.follow", `0`},
{33, "email.config", `{"from_name":"answer","from_email":"answer@answer.com","smtp_host":"smtp.answer.org","smtp_port":465,"smtp_password":"answer","smtp_username":"answer@answer.com","smtp_authentication":true,"encryption":"","register_title":"[{{.SiteName}}] Confirm your new account","register_body":"Welcome to {{.SiteName}}<br><br>\n\nClick the following link to confirm and activate your new account:<br>\n<a href='{{.RegisterUrl}}' target='_blank'>{{.RegisterUrl}}</a><br><br>\n\nIf the above link is not clickable, try copying and pasting it into the address bar of your web browser.\n","pass_reset_title":"[{{.SiteName }}] Password reset","pass_reset_body":"Somebody asked to reset your password on [{{.SiteName}}].<br><br>\n\nIf it was not you, you can safely ignore this email.<br><br>\n\nClick the following link to choose a new password:<br>\n<a href='{{.PassResetUrl}}' target='_blank'>{{.PassResetUrl}}</a>\n","change_title":"[{{.SiteName}}] Confirm your new email address","change_body":"Confirm your new email address for {{.SiteName}} by clicking on the following link:<br><br>\n\n<a href='{{.ChangeEmailUrl}}' target='_blank'>{{.ChangeEmailUrl}}</a><br><br>\n\nIf you did not request this change, please ignore this email.\n","test_title":"[{{.SiteName}}] Test Email","test_body":"This is a test email."}`},
{35, "tag.follow", `0`},
{36, "rank.question.add", `0`},
{37, "rank.question.edit", `0`},
{38, "rank.question.delete", `0`},
{39, "rank.question.vote_up", `0`},
{40, "rank.question.vote_down", `0`},
{41, "rank.answer.add", `0`},
{42, "rank.answer.edit", `0`},
{43, "rank.answer.delete", `0`},
{44, "rank.answer.accept", `0`},
{45, "rank.answer.vote_up", `0`},
{46, "rank.answer.vote_down", `0`},
{47, "rank.comment.add", `0`},
{48, "rank.comment.edit", `0`},
{49, "rank.comment.delete", `0`},
{50, "rank.report.add", `0`},
{51, "rank.tag.add", `0`},
{52, "rank.tag.edit", `0`},
{53, "rank.tag.delete", `0`},
{54, "rank.tag.synonym", `0`},
{55, "rank.link.url_limit", `0`},
{56, "rank.vote.detail", `0`},
{57, "reason.spam", `{"name":"spam","description":"This post is an advertisement, or vandalism. It is not useful or relevant to the current topic."}`},
{58, "reason.rude_or_abusive", `{"name":"rude or abusive","description":"A reasonable person would find this content inappropriate for respectful discourse."}`},
{59, "reason.something", `{"name":"something else","description":"This post requires staff attention for another reason not listed above.","content_type":"textarea"}`},
{60, "reason.a_duplicate", `{"name":"a duplicate","description":"This question has been asked before and already has an answer.","content_type":"text"}`},
{61, "reason.not_a_answer", `{"name":"not a answer","description":"This was posted as an answer, but it does not attempt to answer the question. It should possibly be an edit, a comment, another question, or deleted altogether.","content_type":""}`},
{62, "reason.no_longer_needed", `{"name":"no longer needed","description":"This comment is outdated, conversational or not relevant to this post."}`},
{63, "reason.community_specific", `{"name":"a community-specific reason","description":"This question doesnt meet a community guideline."}`},
{64, "reason.not_clarity", `{"name":"needs details or clarity","description":"This question currently includes multiple questions in one. It should focus on one problem only.","content_type":"text"}`},
{65, "reason.normal", `{"name":"normal","description":"A normal post available to everyone."}`},
{66, "reason.normal.user", `{"name":"normal","description":"A normal user can ask and answer questions."}`},
{67, "reason.closed", `{"name":"closed","description":"A closed question cant answer, but still can edit, vote and comment."}`},
{68, "reason.deleted", `{"name":"deleted","description":"All reputation gained and lost will be restored."}`},
{69, "reason.deleted.user", `{"name":"deleted","description":"Delete profile, authentication associations."}`},
{70, "reason.suspended", `{"name":"suspended","description":"A suspended user cant log in."}`},
{71, "reason.inactive", `{"name":"inactive","description":"An inactive user must re-validate their email."}`},
{72, "reason.looks_ok", `{"name":"looks ok","description":"This post is good as-is and not low quality."}`},
{73, "reason.needs_edit", `{"name":"needs edit, and I did it","description":"Improve and correct problems with this post yourself."}`},
{74, "reason.needs_close", `{"name":"needs close","description":"A closed question cant answer, but still can edit, vote and comment."}`},
{75, "reason.needs_delete", `{"name":"needs delete","description":"All reputation gained and lost will be restored."}`},
{76, "question.flag.reasons", `["reason.spam","reason.rude_or_abusive","reason.something","reason.a_duplicate"]`},
{77, "answer.flag.reasons", `["reason.spam","reason.rude_or_abusive","reason.something","reason.not_a_answer"]`},
{78, "comment.flag.reasons", `["reason.spam","reason.rude_or_abusive","reason.something","reason.no_longer_needed"]`},
{79, "question.close.reasons", `["reason.a_duplicate","reason.community_specific","reason.not_clarity","reason.something"]`},
{80, "question.status.reasons", `["reason.normal","reason.closed","reason.deleted"]`},
{81, "answer.status.reasons", `["reason.normal","reason.deleted"]`},
{82, "comment.status.reasons", `["reason.normal","reason.deleted"]`},
{83, "user.status.reasons", `["reason.normal.user","reason.suspended","reason.deleted.user","reason.inactive"]`},
{84, "question.review.reasons", `["reason.looks_ok","reason.needs_edit","reason.needs_close","reason.needs_delete"]`},
{85, "answer.review.reasons", `["reason.looks_ok","reason.needs_edit","reason.needs_delete"]`},
{86, "comment.review.reasons", `["reason.looks_ok","reason.needs_edit","reason.needs_delete"]`},
{ID: 1, Key: "answer.accepted", Value: `15`},
{ID: 2, Key: "answer.voted_up", Value: `10`},
{ID: 3, Key: "question.voted_up", Value: `10`},
{ID: 4, Key: "tag.edit_accepted", Value: `2`},
{ID: 5, Key: "answer.accept", Value: `2`},
{ID: 6, Key: "answer.voted_down_cancel", Value: `2`},
{ID: 7, Key: "question.voted_down_cancel", Value: `2`},
{ID: 8, Key: "answer.vote_down_cancel", Value: `1`},
{ID: 9, Key: "question.vote_down_cancel", Value: `1`},
{ID: 10, Key: "user.activated", Value: `1`},
{ID: 11, Key: "edit.accepted", Value: `2`},
{ID: 12, Key: "answer.vote_down", Value: `-1`},
{ID: 13, Key: "question.voted_down", Value: `-2`},
{ID: 14, Key: "answer.voted_down", Value: `-2`},
{ID: 15, Key: "answer.accept_cancel", Value: `-2`},
{ID: 16, Key: "answer.deleted", Value: `-5`},
{ID: 17, Key: "question.voted_up_cancel", Value: `-10`},
{ID: 18, Key: "answer.voted_up_cancel", Value: `-10`},
{ID: 19, Key: "answer.accepted_cancel", Value: `-15`},
{ID: 20, Key: "object.reported", Value: `-100`},
{ID: 21, Key: "edit.rejected", Value: `-2`},
{ID: 22, Key: "daily_rank_limit", Value: `200`},
{ID: 23, Key: "daily_rank_limit.exclude", Value: `["answer.accepted"]`},
{ID: 24, Key: "user.follow", Value: `0`},
{ID: 25, Key: "comment.vote_up", Value: `0`},
{ID: 26, Key: "comment.vote_up_cancel", Value: `0`},
{ID: 27, Key: "question.vote_down", Value: `0`},
{ID: 28, Key: "question.vote_up", Value: `0`},
{ID: 29, Key: "question.vote_up_cancel", Value: `0`},
{ID: 30, Key: "answer.vote_up", Value: `0`},
{ID: 31, Key: "answer.vote_up_cancel", Value: `0`},
{ID: 32, Key: "question.follow", Value: `0`},
{ID: 33, Key: "email.config", Value: `{"from_name":"answer","from_email":"answer@answer.com","smtp_host":"smtp.answer.org","smtp_port":465,"smtp_password":"answer","smtp_username":"answer@answer.com","smtp_authentication":true,"encryption":"","register_title":"[{{.SiteName}}] Confirm your new account","register_body":"Welcome to {{.SiteName}}<br><br>\n\nClick the following link to confirm and activate your new account:<br>\n<a href='{{.RegisterUrl}}' target='_blank'>{{.RegisterUrl}}</a><br><br>\n\nIf the above link is not clickable, try copying and pasting it into the address bar of your web browser.\n","pass_reset_title":"[{{.SiteName }}] Password reset","pass_reset_body":"Somebody asked to reset your password on [{{.SiteName}}].<br><br>\n\nIf it was not you, you can safely ignore this email.<br><br>\n\nClick the following link to choose a new password:<br>\n<a href='{{.PassResetUrl}}' target='_blank'>{{.PassResetUrl}}</a>\n","change_title":"[{{.SiteName}}] Confirm your new email address","change_body":"Confirm your new email address for {{.SiteName}} by clicking on the following link:<br><br>\n\n<a href='{{.ChangeEmailUrl}}' target='_blank'>{{.ChangeEmailUrl}}</a><br><br>\n\nIf you did not request this change, please ignore this email.\n","test_title":"[{{.SiteName}}] Test Email","test_body":"This is a test email."}`},
{ID: 35, Key: "tag.follow", Value: `0`},
{ID: 36, Key: "rank.question.add", Value: `0`},
{ID: 37, Key: "rank.question.edit", Value: `0`},
{ID: 38, Key: "rank.question.delete", Value: `0`},
{ID: 39, Key: "rank.question.vote_up", Value: `0`},
{ID: 40, Key: "rank.question.vote_down", Value: `0`},
{ID: 41, Key: "rank.answer.add", Value: `0`},
{ID: 42, Key: "rank.answer.edit", Value: `0`},
{ID: 43, Key: "rank.answer.delete", Value: `0`},
{ID: 44, Key: "rank.answer.accept", Value: `0`},
{ID: 45, Key: "rank.answer.vote_up", Value: `0`},
{ID: 46, Key: "rank.answer.vote_down", Value: `0`},
{ID: 47, Key: "rank.comment.add", Value: `0`},
{ID: 48, Key: "rank.comment.edit", Value: `0`},
{ID: 49, Key: "rank.comment.delete", Value: `0`},
{ID: 50, Key: "rank.report.add", Value: `0`},
{ID: 51, Key: "rank.tag.add", Value: `0`},
{ID: 52, Key: "rank.tag.edit", Value: `0`},
{ID: 53, Key: "rank.tag.delete", Value: `0`},
{ID: 54, Key: "rank.tag.synonym", Value: `0`},
{ID: 55, Key: "rank.link.url_limit", Value: `0`},
{ID: 56, Key: "rank.vote.detail", Value: `0`},
{ID: 57, Key: "reason.spam", Value: `{"name":"spam","description":"This post is an advertisement, or vandalism. It is not useful or relevant to the current topic."}`},
{ID: 58, Key: "reason.rude_or_abusive", Value: `{"name":"rude or abusive","description":"A reasonable person would find this content inappropriate for respectful discourse."}`},
{ID: 59, Key: "reason.something", Value: `{"name":"something else","description":"This post requires staff attention for another reason not listed above.","content_type":"textarea"}`},
{ID: 60, Key: "reason.a_duplicate", Value: `{"name":"a duplicate","description":"This question has been asked before and already has an answer.","content_type":"text"}`},
{ID: 61, Key: "reason.not_a_answer", Value: `{"name":"not a answer","description":"This was posted as an answer, but it does not attempt to answer the question. It should possibly be an edit, a comment, another question, or deleted altogether.","content_type":""}`},
{ID: 62, Key: "reason.no_longer_needed", Value: `{"name":"no longer needed","description":"This comment is outdated, conversational or not relevant to this post."}`},
{ID: 63, Key: "reason.community_specific", Value: `{"name":"a community-specific reason","description":"This question doesnt meet a community guideline."}`},
{ID: 64, Key: "reason.not_clarity", Value: `{"name":"needs details or clarity","description":"This question currently includes multiple questions in one. It should focus on one problem only.","content_type":"text"}`},
{ID: 65, Key: "reason.normal", Value: `{"name":"normal","description":"A normal post available to everyone."}`},
{ID: 66, Key: "reason.normal.user", Value: `{"name":"normal","description":"A normal user can ask and answer questions."}`},
{ID: 67, Key: "reason.closed", Value: `{"name":"closed","description":"A closed question cant answer, but still can edit, vote and comment."}`},
{ID: 68, Key: "reason.deleted", Value: `{"name":"deleted","description":"All reputation gained and lost will be restored."}`},
{ID: 69, Key: "reason.deleted.user", Value: `{"name":"deleted","description":"Delete profile, authentication associations."}`},
{ID: 70, Key: "reason.suspended", Value: `{"name":"suspended","description":"A suspended user cant log in."}`},
{ID: 71, Key: "reason.inactive", Value: `{"name":"inactive","description":"An inactive user must re-validate their email."}`},
{ID: 72, Key: "reason.looks_ok", Value: `{"name":"looks ok","description":"This post is good as-is and not low quality."}`},
{ID: 73, Key: "reason.needs_edit", Value: `{"name":"needs edit, and I did it","description":"Improve and correct problems with this post yourself."}`},
{ID: 74, Key: "reason.needs_close", Value: `{"name":"needs close","description":"A closed question cant answer, but still can edit, vote and comment."}`},
{ID: 75, Key: "reason.needs_delete", Value: `{"name":"needs delete","description":"All reputation gained and lost will be restored."}`},
{ID: 76, Key: "question.flag.reasons", Value: `["reason.spam","reason.rude_or_abusive","reason.something","reason.a_duplicate"]`},
{ID: 77, Key: "answer.flag.reasons", Value: `["reason.spam","reason.rude_or_abusive","reason.something","reason.not_a_answer"]`},
{ID: 78, Key: "comment.flag.reasons", Value: `["reason.spam","reason.rude_or_abusive","reason.something","reason.no_longer_needed"]`},
{ID: 79, Key: "question.close.reasons", Value: `["reason.a_duplicate","reason.community_specific","reason.not_clarity","reason.something"]`},
{ID: 80, Key: "question.status.reasons", Value: `["reason.normal","reason.closed","reason.deleted"]`},
{ID: 81, Key: "answer.status.reasons", Value: `["reason.normal","reason.deleted"]`},
{ID: 82, Key: "comment.status.reasons", Value: `["reason.normal","reason.deleted"]`},
{ID: 83, Key: "user.status.reasons", Value: `["reason.normal.user","reason.suspended","reason.deleted.user","reason.inactive"]`},
{ID: 84, Key: "question.review.reasons", Value: `["reason.looks_ok","reason.needs_edit","reason.needs_close","reason.needs_delete"]`},
{ID: 85, Key: "answer.review.reasons", Value: `["reason.looks_ok","reason.needs_edit","reason.needs_delete"]`},
{ID: 86, Key: "comment.review.reasons", Value: `["reason.looks_ok","reason.needs_edit","reason.needs_delete"]`},
}
_, err := engine.Insert(defaultConfigTable)
return err

View File

@ -44,7 +44,6 @@ func NewAnswerActivityRepo(
userRankRepo rank.UserRankRepo,
) activity.AnswerActivityRepo {
return &AnswerActivityRepo{
data: data,
activityRepo: activityRepo,
userRankRepo: userRankRepo,
@ -125,7 +124,8 @@ func (ar *AnswerActivityRepo) DeleteQuestion(ctx context.Context, questionID str
// AcceptAnswer accept other answer
func (ar *AnswerActivityRepo) AcceptAnswer(ctx context.Context,
answerObjID, questionUserID, answerUserID string, isSelf bool) (err error) {
answerObjID, questionUserID, answerUserID string, isSelf bool,
) (err error) {
addActivityList := make([]*entity.Activity, 0)
for _, action := range acceptActionList {
// get accept answer need add rank amount
@ -173,7 +173,7 @@ func (ar *AnswerActivityRepo) AcceptAnswer(ctx context.Context,
}
if exists {
if _, e := session.Where("id = ?", existsActivity.ID).Cols("`cancelled`").
if _, e = session.Where("id = ?", existsActivity.ID).Cols("`cancelled`").
Update(&entity.Activity{Cancelled: entity.ActivityAvailable}); e != nil {
return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
}
@ -222,7 +222,8 @@ func (ar *AnswerActivityRepo) AcceptAnswer(ctx context.Context,
// CancelAcceptAnswer accept other answer
func (ar *AnswerActivityRepo) CancelAcceptAnswer(ctx context.Context,
answerObjID, questionUserID, answerUserID string) (err error) {
answerObjID, questionUserID, answerUserID string,
) (err error) {
addActivityList := make([]*entity.Activity, 0)
for _, action := range acceptActionList {
// get accept answer need add rank amount

View File

@ -37,8 +37,8 @@ func NewFollowRepo(
}
}
func (ar *FollowRepo) Follow(ctx context.Context, objectId, userId string) error {
activityType, _, _, err := ar.activityRepo.GetActivityTypeByObjID(ctx, objectId, "follow")
func (ar *FollowRepo) Follow(ctx context.Context, objectID, userID string) error {
activityType, _, _, err := ar.activityRepo.GetActivityTypeByObjID(ctx, objectID, "follow")
if err != nil {
return err
}
@ -51,8 +51,8 @@ func (ar *FollowRepo) Follow(ctx context.Context, objectId, userId string) error
result = nil
has, err = session.Where(builder.Eq{"activity_type": activityType}).
And(builder.Eq{"user_id": userId}).
And(builder.Eq{"object_id": objectId}).
And(builder.Eq{"user_id": userID}).
And(builder.Eq{"object_id": objectID}).
Get(&existsActivity)
if err != nil {
@ -72,8 +72,8 @@ func (ar *FollowRepo) Follow(ctx context.Context, objectId, userId string) error
} else {
// update existing activity with new user id and u object id
_, err = session.Insert(&entity.Activity{
UserID: userId,
ObjectID: objectId,
UserID: userID,
ObjectID: objectID,
ActivityType: activityType,
Cancelled: 0,
Rank: 0,
@ -87,7 +87,7 @@ func (ar *FollowRepo) Follow(ctx context.Context, objectId, userId string) error
}
// start update followers when everything is fine
err = ar.updateFollows(ctx, session, objectId, 1)
err = ar.updateFollows(ctx, session, objectID, 1)
if err != nil {
log.Error(err)
}
@ -98,8 +98,8 @@ func (ar *FollowRepo) Follow(ctx context.Context, objectId, userId string) error
return err
}
func (ar *FollowRepo) FollowCancel(ctx context.Context, objectId, userId string) error {
activityType, _, _, err := ar.activityRepo.GetActivityTypeByObjID(ctx, objectId, "follow")
func (ar *FollowRepo) FollowCancel(ctx context.Context, objectID, userID string) error {
activityType, _, _, err := ar.activityRepo.GetActivityTypeByObjID(ctx, objectID, "follow")
if err != nil {
return err
}
@ -112,8 +112,8 @@ func (ar *FollowRepo) FollowCancel(ctx context.Context, objectId, userId string)
result = nil
has, err = session.Where(builder.Eq{"activity_type": activityType}).
And(builder.Eq{"user_id": userId}).
And(builder.Eq{"object_id": objectId}).
And(builder.Eq{"user_id": userID}).
And(builder.Eq{"object_id": objectID}).
Get(&existsActivity)
if err != nil || !has {
@ -130,24 +130,24 @@ func (ar *FollowRepo) FollowCancel(ctx context.Context, objectId, userId string)
}); err != nil {
return
}
err = ar.updateFollows(ctx, session, objectId, -1)
err = ar.updateFollows(ctx, session, objectID, -1)
return
})
return err
}
func (ar *FollowRepo) updateFollows(ctx context.Context, session *xorm.Session, objectId string, follows int) error {
objectType, err := obj.GetObjectTypeStrByObjectID(objectId)
func (ar *FollowRepo) updateFollows(ctx context.Context, session *xorm.Session, objectID string, follows int) error {
objectType, err := obj.GetObjectTypeStrByObjectID(objectID)
if err != nil {
return err
}
switch objectType {
case "question":
_, err = session.Where("id = ?", objectId).Incr("follow_count", follows).Update(&entity.Question{})
_, err = session.Where("id = ?", objectID).Incr("follow_count", follows).Update(&entity.Question{})
case "user":
_, err = session.Where("id = ?", objectId).Incr("follow_count", follows).Update(&entity.User{})
_, err = session.Where("id = ?", objectID).Incr("follow_count", follows).Update(&entity.User{})
case "tag":
_, err = session.Where("id = ?", objectId).Incr("follow_count", follows).Update(&entity.Tag{})
_, err = session.Where("id = ?", objectID).Incr("follow_count", follows).Update(&entity.Tag{})
default:
err = errors.InternalServer(reason.DisallowFollow).WithMsg("this object can't be followed")
}

View File

@ -2,9 +2,10 @@ package activity
import (
"context"
"github.com/answerdev/answer/pkg/converter"
"strings"
"github.com/answerdev/answer/pkg/converter"
"github.com/answerdev/answer/internal/base/pager"
"github.com/answerdev/answer/internal/service/config"
"github.com/answerdev/answer/internal/service/notice_queue"
@ -42,7 +43,8 @@ func NewVoteRepo(
configRepo config.ConfigRepo,
activityRepo activity_common.ActivityRepo,
userRankRepo rank.UserRankRepo,
voteCommon activity_common.VoteRepo) service.VoteRepo {
voteCommon activity_common.VoteRepo,
) service.VoteRepo {
return &VoteRepo{
data: data,
uniqueIDRepo: uniqueIDRepo,
@ -65,7 +67,7 @@ var LimitDownActions = map[string][]string{
"comment": {"vote_down"},
}
func (vr *VoteRepo) vote(ctx context.Context, objectID string, userID, objectUserId string, actions []string) (resp *schema.VoteResp, err error) {
func (vr *VoteRepo) vote(ctx context.Context, objectID string, userID, objectUserID string, actions []string) (resp *schema.VoteResp, err error) {
resp = &schema.VoteResp{}
_, err = vr.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
result = nil
@ -75,24 +77,24 @@ func (vr *VoteRepo) vote(ctx context.Context, objectID string, userID, objectUse
insertActivity entity.Activity
has bool
triggerUserID,
activityUserId string
activityUserID string
activityType, deltaRank, hasRank int
)
activityUserId, activityType, deltaRank, hasRank, err = vr.CheckRank(ctx, objectID, objectUserId, userID, action)
activityUserID, activityType, deltaRank, hasRank, err = vr.CheckRank(ctx, objectID, objectUserID, userID, action)
if err != nil {
return
}
triggerUserID = userID
if userID == activityUserId {
if userID == activityUserID {
triggerUserID = "0"
}
// check is voted up
has, _ = session.
Where(builder.Eq{"object_id": objectID}).
And(builder.Eq{"user_id": activityUserId}).
And(builder.Eq{"user_id": activityUserID}).
And(builder.Eq{"trigger_user_id": triggerUserID}).
And(builder.Eq{"activity_type": activityType}).
Get(&existsActivity)
@ -104,7 +106,7 @@ func (vr *VoteRepo) vote(ctx context.Context, objectID string, userID, objectUse
insertActivity = entity.Activity{
ObjectID: objectID,
UserID: activityUserId,
UserID: activityUserID,
TriggerUserID: converter.StringToInt64(triggerUserID),
ActivityType: activityType,
Rank: deltaRank,
@ -114,7 +116,8 @@ func (vr *VoteRepo) vote(ctx context.Context, objectID string, userID, objectUse
// trigger user rank and send notification
if hasRank != 0 {
isReachStandard, err := vr.userRankRepo.TriggerUserRank(ctx, session, activityUserId, deltaRank, activityType)
var isReachStandard bool
isReachStandard, err = vr.userRankRepo.TriggerUserRank(ctx, session, activityUserID, deltaRank, activityType)
if err != nil {
return nil, err
}
@ -122,7 +125,7 @@ func (vr *VoteRepo) vote(ctx context.Context, objectID string, userID, objectUse
insertActivity.Rank = 0
}
vr.sendNotification(ctx, activityUserId, objectUserId, objectID)
vr.sendNotification(ctx, activityUserID, objectUserID, objectID)
}
if has {
@ -163,7 +166,7 @@ func (vr *VoteRepo) vote(ctx context.Context, objectID string, userID, objectUse
return
}
func (vr *VoteRepo) voteCancel(ctx context.Context, objectID string, userID, objectUserId string, actions []string) (resp *schema.VoteResp, err error) {
func (vr *VoteRepo) voteCancel(ctx context.Context, objectID string, userID, objectUserID string, actions []string) (resp *schema.VoteResp, err error) {
resp = &schema.VoteResp{}
_, err = vr.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
for _, action := range actions {
@ -171,24 +174,24 @@ func (vr *VoteRepo) voteCancel(ctx context.Context, objectID string, userID, obj
existsActivity entity.Activity
has bool
triggerUserID,
activityUserId string
activityUserID string
activityType,
deltaRank, hasRank int
)
result = nil
activityUserId, activityType, deltaRank, hasRank, err = vr.CheckRank(ctx, objectID, objectUserId, userID, action)
activityUserID, activityType, deltaRank, hasRank, err = vr.CheckRank(ctx, objectID, objectUserID, userID, action)
if err != nil {
return
}
triggerUserID = userID
if userID == activityUserId {
if userID == activityUserID {
triggerUserID = "0"
}
has, err = session.
Where(builder.Eq{"user_id": activityUserId}).
Where(builder.Eq{"user_id": activityUserID}).
And(builder.Eq{"trigger_user_id": triggerUserID}).
And(builder.Eq{"activity_type": activityType}).
And(builder.Eq{"object_id": objectID}).
@ -211,12 +214,12 @@ func (vr *VoteRepo) voteCancel(ctx context.Context, objectID string, userID, obj
// trigger user rank and send notification
if hasRank != 0 {
_, err = vr.userRankRepo.TriggerUserRank(ctx, session, activityUserId, -deltaRank, activityType)
_, err = vr.userRankRepo.TriggerUserRank(ctx, session, activityUserID, -deltaRank, activityType)
if err != nil {
return
}
vr.sendNotification(ctx, activityUserId, objectUserId, objectID)
vr.sendNotification(ctx, activityUserID, objectUserID, objectID)
}
// update votes
@ -242,7 +245,7 @@ func (vr *VoteRepo) voteCancel(ctx context.Context, objectID string, userID, obj
return
}
func (vr *VoteRepo) VoteUp(ctx context.Context, objectID string, userID, objectUserId string) (resp *schema.VoteResp, err error) {
func (vr *VoteRepo) VoteUp(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error) {
resp = &schema.VoteResp{}
objectType, err := obj.GetObjectTypeStrByObjectID(objectID)
if err != nil {
@ -256,11 +259,11 @@ func (vr *VoteRepo) VoteUp(ctx context.Context, objectID string, userID, objectU
return
}
_, _ = vr.VoteDownCancel(ctx, objectID, userID, objectUserId)
return vr.vote(ctx, objectID, userID, objectUserId, actions)
_, _ = vr.VoteDownCancel(ctx, objectID, userID, objectUserID)
return vr.vote(ctx, objectID, userID, objectUserID, actions)
}
func (vr *VoteRepo) VoteDown(ctx context.Context, objectID string, userID, objectUserId string) (resp *schema.VoteResp, err error) {
func (vr *VoteRepo) VoteDown(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error) {
resp = &schema.VoteResp{}
objectType, err := obj.GetObjectTypeStrByObjectID(objectID)
if err != nil {
@ -273,14 +276,12 @@ func (vr *VoteRepo) VoteDown(ctx context.Context, objectID string, userID, objec
return
}
_, _ = vr.VoteUpCancel(ctx, objectID, userID, objectUserId)
return vr.vote(ctx, objectID, userID, objectUserId, actions)
_, _ = vr.VoteUpCancel(ctx, objectID, userID, objectUserID)
return vr.vote(ctx, objectID, userID, objectUserID, actions)
}
func (vr *VoteRepo) VoteUpCancel(ctx context.Context, objectID string, userID, objectUserId string) (resp *schema.VoteResp, err error) {
var (
objectType string
)
func (vr *VoteRepo) VoteUpCancel(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error) {
var objectType string
resp = &schema.VoteResp{}
objectType, err = obj.GetObjectTypeStrByObjectID(objectID)
@ -294,13 +295,11 @@ func (vr *VoteRepo) VoteUpCancel(ctx context.Context, objectID string, userID, o
return
}
return vr.voteCancel(ctx, objectID, userID, objectUserId, actions)
return vr.voteCancel(ctx, objectID, userID, objectUserID, actions)
}
func (vr *VoteRepo) VoteDownCancel(ctx context.Context, objectID string, userID, objectUserId string) (resp *schema.VoteResp, err error) {
var (
objectType string
)
func (vr *VoteRepo) VoteDownCancel(ctx context.Context, objectID string, userID, objectUserID string) (resp *schema.VoteResp, err error) {
var objectType string
resp = &schema.VoteResp{}
objectType, err = obj.GetObjectTypeStrByObjectID(objectID)
@ -314,22 +313,22 @@ func (vr *VoteRepo) VoteDownCancel(ctx context.Context, objectID string, userID,
return
}
return vr.voteCancel(ctx, objectID, userID, objectUserId, actions)
return vr.voteCancel(ctx, objectID, userID, objectUserID, actions)
}
func (vr *VoteRepo) CheckRank(ctx context.Context, objectID, objectUserId, userID string, action string) (activityUserId string, activityType, rank, hasRank int, err error) {
func (vr *VoteRepo) CheckRank(ctx context.Context, objectID, objectUserID, userID string, action string) (activityUserID string, activityType, rank, hasRank int, err error) {
activityType, rank, hasRank, err = vr.activityRepo.GetActivityTypeByObjID(ctx, objectID, action)
if err != nil {
return
}
activityUserId = userID
activityUserID = userID
if strings.Contains(action, "voted") {
activityUserId = objectUserId
activityUserID = objectUserID
}
return activityUserId, activityType, rank, hasRank, nil
return activityUserID, activityType, rank, hasRank, nil
}
func (vr *VoteRepo) GetVoteResultByObjectId(ctx context.Context, objectID string) (resp *schema.VoteResp, err error) {
@ -341,7 +340,7 @@ func (vr *VoteRepo) GetVoteResultByObjectId(ctx context.Context, objectID string
activityType int
)
activityType, _, _, err = vr.activityRepo.GetActivityTypeByObjID(ctx, objectID, action)
activityType, _, _, _ = vr.activityRepo.GetActivityTypeByObjID(ctx, objectID, action)
votes, err = vr.data.DB.Where(builder.Eq{"object_id": objectID}).
And(builder.Eq{"activity_type": activityType}).
@ -417,15 +416,15 @@ func (vr *VoteRepo) updateVotes(ctx context.Context, session *xorm.Session, obje
}
// sendNotification send rank triggered notification
func (vr *VoteRepo) sendNotification(ctx context.Context, activityUserId, objectUserId, objectID string) {
func (vr *VoteRepo) sendNotification(ctx context.Context, activityUserID, objectUserID, objectID string) {
objectType, err := obj.GetObjectTypeStrByObjectID(objectID)
if err != nil {
return
}
msg := &schema.NotificationMsg{
ReceiverUserID: activityUserId,
TriggerUserID: objectUserId,
ReceiverUserID: activityUserID,
TriggerUserID: objectUserID,
Type: schema.NotificationTypeAchievement,
ObjectID: objectID,
ObjectType: objectType,

View File

@ -33,27 +33,27 @@ func NewFollowRepo(
}
// GetFollowAmount get object id's follows
func (ar *FollowRepo) GetFollowAmount(ctx context.Context, objectId string) (follows int, err error) {
objectType, err := obj.GetObjectTypeStrByObjectID(objectId)
func (ar *FollowRepo) GetFollowAmount(ctx context.Context, objectID string) (follows int, err error) {
objectType, err := obj.GetObjectTypeStrByObjectID(objectID)
if err != nil {
return 0, err
}
switch objectType {
case "question":
model := &entity.Question{}
_, err = ar.data.DB.Where("id = ?", objectId).Cols("`follow_count`").Get(model)
_, err = ar.data.DB.Where("id = ?", objectID).Cols("`follow_count`").Get(model)
if err == nil {
follows = int(model.FollowCount)
}
case "user":
model := &entity.User{}
_, err = ar.data.DB.Where("id = ?", objectId).Cols("`follow_count`").Get(model)
_, err = ar.data.DB.Where("id = ?", objectID).Cols("`follow_count`").Get(model)
if err == nil {
follows = int(model.FollowCount)
}
case "tag":
model := &entity.Tag{}
_, err = ar.data.DB.Where("id = ?", objectId).Cols("`follow_count`").Get(model)
_, err = ar.data.DB.Where("id = ?", objectID).Cols("`follow_count`").Get(model)
if err == nil {
follows = int(model.FollowCount)
}
@ -95,6 +95,9 @@ func (ar *FollowRepo) GetFollowUserIDs(ctx context.Context, objectID string) (us
func (ar *FollowRepo) GetFollowIDs(ctx context.Context, userID, objectKey string) (followIDs []string, err error) {
followIDs = make([]string, 0)
activityType, err := ar.activityRepo.GetActivityTypeByObjKey(ctx, objectKey, "follow")
if err != nil {
return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
session := ar.data.DB.Select("object_id")
session.Table(entity.Activity{}.TableName())
session.Where("user_id = ? AND activity_type = ?", userID, activityType)
@ -107,14 +110,14 @@ func (ar *FollowRepo) GetFollowIDs(ctx context.Context, userID, objectKey string
}
// IsFollowed check user if follow object or not
func (ar *FollowRepo) IsFollowed(userId, objectId string) (bool, error) {
activityType, _, _, err := ar.activityRepo.GetActivityTypeByObjID(nil, objectId, "follow")
func (ar *FollowRepo) IsFollowed(userID, objectID string) (bool, error) {
activityType, _, _, err := ar.activityRepo.GetActivityTypeByObjID(context.TODO(), objectID, "follow")
if err != nil {
return false, err
}
at := &entity.Activity{}
has, err := ar.data.DB.Where("user_id = ? AND object_id = ? AND activity_type = ?", userId, objectId, activityType).Get(at)
has, err := ar.data.DB.Where("user_id = ? AND object_id = ? AND activity_type = ?", userID, objectID, activityType).Get(at)
if err != nil {
return false, err
}

View File

@ -6,13 +6,11 @@ import (
"github.com/answerdev/answer/internal/base/data"
"github.com/answerdev/answer/internal/entity"
"github.com/answerdev/answer/internal/service/activity_common"
"github.com/answerdev/answer/internal/service/unique"
)
// VoteRepo activity repository
type VoteRepo struct {
data *data.Data
uniqueIDRepo unique.UniqueIDRepo
activityRepo activity_common.ActivityRepo
}
@ -24,11 +22,14 @@ func NewVoteRepo(data *data.Data, activityRepo activity_common.ActivityRepo) act
}
}
func (vr *VoteRepo) GetVoteStatus(ctx context.Context, objectId, userId string) (status string) {
func (vr *VoteRepo) GetVoteStatus(ctx context.Context, objectID, userID string) (status string) {
for _, action := range []string{"vote_up", "vote_down"} {
at := &entity.Activity{}
activityType, _, _, err := vr.activityRepo.GetActivityTypeByObjID(ctx, objectId, action)
has, err := vr.data.DB.Where("object_id =? AND cancelled=0 AND activity_type=? AND user_id=?", objectId, activityType, userId).Get(at)
activityType, _, _, err := vr.activityRepo.GetActivityTypeByObjID(ctx, objectID, action)
if err != nil {
return ""
}
has, err := vr.data.DB.Where("object_id =? AND cancelled=0 AND activity_type=? AND user_id=?", objectID, activityType, userID).Get(at)
if err != nil {
return ""
}

View File

@ -37,14 +37,14 @@ func NewActivityRepo(
}
}
func (ar *ActivityRepo) GetActivityTypeByObjID(ctx context.Context, objectId string, action string) (activityType, rank, hasRank int, err error) {
objectKey, err := obj.GetObjectTypeStrByObjectID(objectId)
func (ar *ActivityRepo) GetActivityTypeByObjID(ctx context.Context, objectID string, action string) (activityType, rank, hasRank int, err error) {
objectKey, err := obj.GetObjectTypeStrByObjectID(objectID)
if err != nil {
return
}
confKey := fmt.Sprintf("%s.%s", objectKey, action)
activityType, err = ar.configRepo.GetConfigType(confKey)
activityType, _ = ar.configRepo.GetConfigType(confKey)
rank, err = ar.configRepo.GetInt(confKey)
hasRank = 0
@ -64,7 +64,8 @@ func (ar *ActivityRepo) GetActivityTypeByObjKey(ctx context.Context, objectKey,
}
func (ar *ActivityRepo) GetActivity(ctx context.Context, session *xorm.Session,
objectID, userID string, activityType int) (existsActivity *entity.Activity, exist bool, err error) {
objectID, userID string, activityType int,
) (existsActivity *entity.Activity, exist bool, err error) {
existsActivity = &entity.Activity{}
exist, err = session.
Where(builder.Eq{"object_id": objectID}).

View File

@ -2,7 +2,10 @@ package repo
import (
"context"
"strings"
"time"
"unicode"
"xorm.io/builder"
"github.com/answerdev/answer/internal/base/constant"
"github.com/answerdev/answer/internal/base/data"
@ -89,7 +92,8 @@ func (ar *answerRepo) UpdateAnswerStatus(ctx context.Context, answer *entity.Ans
// GetAnswer get answer one
func (ar *answerRepo) GetAnswer(ctx context.Context, id string) (
answer *entity.Answer, exist bool, err error) {
answer *entity.Answer, exist bool, err error,
) {
answer = &entity.Answer{}
exist, err = ar.data.DB.ID(id).Get(answer)
if err != nil {
@ -120,20 +124,20 @@ func (ar *answerRepo) GetAnswerPage(ctx context.Context, page, pageSize int, ans
// UpdateAdopted
// If no answer is selected, the answer id can be 0
func (ar *answerRepo) UpdateAdopted(ctx context.Context, id string, questionId string) error {
if questionId == "" {
func (ar *answerRepo) UpdateAdopted(ctx context.Context, id string, questionID string) error {
if questionID == "" {
return nil
}
var data entity.Answer
data.ID = id
data.Adopted = schema.Answer_Adopted_Failed
_, err := ar.data.DB.Where("question_id =?", questionId).Cols("adopted").Update(&data)
data.Adopted = schema.AnswerAdoptedFailed
_, err := ar.data.DB.Where("question_id =?", questionID).Cols("adopted").Update(&data)
if err != nil {
return err
}
if id != "0" {
data.Adopted = schema.Answer_Adopted_Enable
data.Adopted = schema.AnswerAdoptedEnable
_, err = ar.data.DB.Where("id = ?", id).Cols("adopted").Update(&data)
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
@ -152,9 +156,9 @@ func (ar *answerRepo) GetByID(ctx context.Context, id string) (*entity.Answer, b
return &resp, has, nil
}
func (ar *answerRepo) GetByUserIdQuestionId(ctx context.Context, userId string, questionId string) (*entity.Answer, bool, error) {
func (ar *answerRepo) GetByUserIDQuestionID(ctx context.Context, userID string, questionID string) (*entity.Answer, bool, error) {
var resp entity.Answer
has, err := ar.data.DB.Where("question_id =? and user_id = ?", questionId, userId).Get(&resp)
has, err := ar.data.DB.Where("question_id =? and user_id = ?", questionID, userID).Get(&resp)
if err != nil {
return &resp, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
@ -172,7 +176,7 @@ func (ar *answerRepo) SearchList(ctx context.Context, search *entity.AnswerSearc
search.Page = 0
}
if search.PageSize == 0 {
search.PageSize = constant.Default_PageSize
search.PageSize = constant.DefaultPageSize
}
offset := search.Page * search.PageSize
session := ar.data.DB.Where("")
@ -183,14 +187,14 @@ func (ar *answerRepo) SearchList(ctx context.Context, search *entity.AnswerSearc
if len(search.UserID) > 0 {
session = session.And("user_id = ?", search.UserID)
}
if search.Order == entity.Answer_Search_OrderBy_Time {
switch search.Order {
case entity.AnswerSearchOrderByTime:
session = session.OrderBy("created_at desc")
} else if search.Order == entity.Answer_Search_OrderBy_Vote {
case entity.AnswerSearchOrderByVote:
session = session.OrderBy("vote_count desc")
} else {
default:
session = session.OrderBy("adopted desc,vote_count desc")
}
session = session.And("status = ?", entity.AnswerStatusAvailable)
session = session.Limit(search.PageSize, offset)
@ -202,11 +206,16 @@ func (ar *answerRepo) SearchList(ctx context.Context, search *entity.AnswerSearc
}
func (ar *answerRepo) CmsSearchList(ctx context.Context, search *entity.CmsAnswerSearch) ([]*entity.Answer, int64, error) {
var count int64
var err error
if search.Status == 0 {
search.Status = 1
}
var (
count int64
err error
session = ar.data.DB.Table([]string{entity.Answer{}.TableName(), "a"}).Select("a.*")
)
session.Where(builder.Eq{
"a.status": search.Status,
})
rows := make([]*entity.Answer, 0)
if search.Page > 0 {
search.Page = search.Page - 1
@ -214,13 +223,44 @@ func (ar *answerRepo) CmsSearchList(ctx context.Context, search *entity.CmsAnswe
search.Page = 0
}
if search.PageSize == 0 {
search.PageSize = constant.Default_PageSize
search.PageSize = constant.DefaultPageSize
}
// search by question title like or answer id
if len(search.Query) > 0 {
// check id search
var (
idSearch = false
id = ""
)
if strings.Contains(search.Query, "id:") {
idSearch = true
id = strings.TrimSpace(strings.TrimPrefix(search.Query, "id:"))
for _, r := range id {
if !unicode.IsDigit(r) {
idSearch = false
break
}
}
}
if idSearch {
session.And(builder.Eq{
"id": id,
})
} else {
session.Join("LEFT", []string{entity.Question{}.TableName(), "q"}, "q.id = a.question_id")
session.And(builder.Like{
"q.title", search.Query,
})
}
}
offset := search.Page * search.PageSize
session := ar.data.DB.Where("")
session = session.And("status =?", search.Status)
session = session.OrderBy("updated_at desc")
session = session.Limit(search.PageSize, offset)
session.
OrderBy("a.updated_at desc").
Limit(search.PageSize, offset)
count, err = session.FindAndCount(&rows)
if err != nil {
return rows, count, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()

View File

@ -37,7 +37,7 @@ func (cr *collectionGroupRepo) AddCollectionGroup(ctx context.Context, collectio
func (cr *collectionGroupRepo) AddCollectionDefaultGroup(ctx context.Context, userID string) (collectionGroup *entity.CollectionGroup, err error) {
defaultGroup := &entity.CollectionGroup{
Name: "default",
DefaultGroup: schema.CG_DEFAULT,
DefaultGroup: schema.CGDefault,
UserID: userID,
}
_, err = cr.data.DB.Insert(defaultGroup)
@ -60,7 +60,8 @@ func (cr *collectionGroupRepo) UpdateCollectionGroup(ctx context.Context, collec
// GetCollectionGroup get collection group one
func (cr *collectionGroupRepo) GetCollectionGroup(ctx context.Context, id string) (
collectionGroup *entity.CollectionGroup, exist bool, err error) {
collectionGroup *entity.CollectionGroup, exist bool, err error,
) {
collectionGroup = &entity.CollectionGroup{}
exist, err = cr.data.DB.ID(id).Get(collectionGroup)
if err != nil {
@ -84,9 +85,9 @@ func (cr *collectionGroupRepo) GetCollectionGroupPage(ctx context.Context, page,
return
}
func (cr *collectionGroupRepo) GetDefaultID(ctx context.Context, userId string) (collectionGroup *entity.CollectionGroup, has bool, err error) {
func (cr *collectionGroupRepo) GetDefaultID(ctx context.Context, userID string) (collectionGroup *entity.CollectionGroup, has bool, err error) {
collectionGroup = &entity.CollectionGroup{}
has, err = cr.data.DB.Where("user_id =? and default_group = ?", userId, schema.CG_DEFAULT).Get(collectionGroup)
has, err = cr.data.DB.Where("user_id =? and default_group = ?", userID, schema.CGDefault).Get(collectionGroup)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
return

View File

@ -74,9 +74,9 @@ func (cr *collectionRepo) GetCollectionList(ctx context.Context, collection *ent
}
// GetOneByObjectIDAndUser get one by object TagID and user
func (cr *collectionRepo) GetOneByObjectIDAndUser(ctx context.Context, userId string, objectId string) (collection *entity.Collection, exist bool, err error) {
func (cr *collectionRepo) GetOneByObjectIDAndUser(ctx context.Context, userID string, objectID string) (collection *entity.Collection, exist bool, err error) {
collection = &entity.Collection{}
exist, err = cr.data.DB.Where("user_id = ? and object_id = ?", userId, objectId).Get(collection)
exist, err = cr.data.DB.Where("user_id = ? and object_id = ?", userID, objectID).Get(collection)
if err != nil {
return nil, false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
@ -84,9 +84,9 @@ func (cr *collectionRepo) GetOneByObjectIDAndUser(ctx context.Context, userId st
}
// SearchByObjectIDsAndUser search by object IDs and user
func (cr *collectionRepo) SearchByObjectIDsAndUser(ctx context.Context, userId string, objectIds []string) ([]*entity.Collection, error) {
func (cr *collectionRepo) SearchByObjectIDsAndUser(ctx context.Context, userID string, objectIDs []string) ([]*entity.Collection, error) {
collectionList := make([]*entity.Collection, 0)
err := cr.data.DB.Where("user_id = ?", userId).In("object_id", objectIds).Find(&collectionList)
err := cr.data.DB.Where("user_id = ?", userID).In("object_id", objectIDs).Find(&collectionList)
if err != nil {
return collectionList, err
}
@ -94,9 +94,9 @@ func (cr *collectionRepo) SearchByObjectIDsAndUser(ctx context.Context, userId s
}
// CountByObjectID count by object TagID
func (cr *collectionRepo) CountByObjectID(ctx context.Context, objectId string) (total int64, err error) {
func (cr *collectionRepo) CountByObjectID(ctx context.Context, objectID string) (total int64, err error) {
collection := &entity.Collection{}
total, err = cr.data.DB.Where("object_id = ?", objectId).Count(collection)
total, err = cr.data.DB.Where("object_id = ?", objectID).Count(collection)
if err != nil {
return 0, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
@ -105,7 +105,6 @@ func (cr *collectionRepo) CountByObjectID(ctx context.Context, objectId string)
// GetCollectionPage get collection page
func (cr *collectionRepo) GetCollectionPage(ctx context.Context, page, pageSize int, collection *entity.Collection) (collectionList []*entity.Collection, total int64, err error) {
collectionList = make([]*entity.Collection, 0)
session := cr.data.DB.NewSession()
@ -124,9 +123,9 @@ func (cr *collectionRepo) GetCollectionPage(ctx context.Context, page, pageSize
}
// SearchObjectCollected check object is collected or not
func (cr *collectionRepo) SearchObjectCollected(ctx context.Context, userId string, objectIds []string) (map[string]bool, error) {
func (cr *collectionRepo) SearchObjectCollected(ctx context.Context, userID string, objectIds []string) (map[string]bool, error) {
collectedMap := make(map[string]bool)
list, err := cr.SearchByObjectIDsAndUser(ctx, userId, objectIds)
list, err := cr.SearchByObjectIDsAndUser(ctx, userID, objectIds)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
return collectedMap, err
@ -148,7 +147,7 @@ func (cr *collectionRepo) SearchList(ctx context.Context, search *entity.Collect
search.Page = 0
}
if search.PageSize == 0 {
search.PageSize = constant.Default_PageSize
search.PageSize = constant.DefaultPageSize
}
offset := search.Page * search.PageSize
session := cr.data.DB.Where("")

View File

@ -69,7 +69,8 @@ func (cr *commentRepo) UpdateComment(ctx context.Context, comment *entity.Commen
// GetComment get comment one
func (cr *commentRepo) GetComment(ctx context.Context, commentID string) (
comment *entity.Comment, exist bool, err error) {
comment *entity.Comment, exist bool, err error,
) {
comment = &entity.Comment{}
exist, err = cr.data.DB.ID(commentID).Get(comment)
if err != nil {
@ -80,7 +81,8 @@ func (cr *commentRepo) GetComment(ctx context.Context, commentID string) (
// GetCommentPage get comment page
func (cr *commentRepo) GetCommentPage(ctx context.Context, commentQuery *comment.CommentQuery) (
commentList []*entity.Comment, total int64, err error) {
commentList []*entity.Comment, total int64, err error,
) {
commentList = make([]*entity.Comment, 0)
session := cr.data.DB.NewSession()

View File

@ -37,15 +37,13 @@ func (cr *CommonRepo) GetRootObjectID(objectID string) (rootObjectID string, err
exist, err = cr.data.DB.ID(objectID).Get(&answer)
if !exist {
err = errors.BadRequest(reason.ObjectNotFound)
} else {
objectID = answer.QuestionID
}
case "comment":
exist, err = cr.data.DB.ID(objectID).Get(&comment)
exist, _ = cr.data.DB.ID(objectID).Get(&comment)
if !exist {
err = errors.BadRequest(reason.ObjectNotFound)
} else {
objectID, err = cr.GetRootObjectID(comment.ObjectID)
_, err = cr.GetRootObjectID(comment.ObjectID)
}
default:
rootObjectID = objectID
@ -72,7 +70,7 @@ func (cr *CommonRepo) GetObjectIDMap(objectID string) (objectIDMap map[string]st
}
switch objectType {
case "answer":
exist, err = cr.data.DB.ID(objectID).Get(&answer)
exist, _ = cr.data.DB.ID(objectID).Get(&answer)
if !exist {
err = errors.BadRequest(reason.ObjectNotFound)
} else {
@ -80,7 +78,7 @@ func (cr *CommonRepo) GetObjectIDMap(objectID string) (objectIDMap map[string]st
ID = answer.ID
}
case "comment":
exist, err = cr.data.DB.ID(objectID).Get(&comment)
exist, _ = cr.data.DB.ID(objectID).Get(&comment)
if !exist {
err = errors.BadRequest(reason.ObjectNotFound)
} else {

View File

@ -102,7 +102,7 @@ func (nr *notificationRepo) SearchList(ctx context.Context, search *schema.Notif
search.Page = 0
}
if search.PageSize == 0 {
search.PageSize = constant.Default_PageSize
search.PageSize = constant.DefaultPageSize
}
offset := search.Page * search.PageSize
session := nr.data.DB.Where("")

View File

@ -2,7 +2,10 @@ package repo
import (
"context"
"strings"
"time"
"unicode"
"xorm.io/builder"
"github.com/answerdev/answer/internal/base/constant"
"github.com/answerdev/answer/internal/base/data"
@ -64,27 +67,27 @@ func (qr *questionRepo) UpdateQuestion(ctx context.Context, question *entity.Que
return
}
func (qr *questionRepo) UpdatePvCount(ctx context.Context, questionId string) (err error) {
func (qr *questionRepo) UpdatePvCount(ctx context.Context, questionID string) (err error) {
question := &entity.Question{}
_, err = qr.data.DB.Where("id =?", questionId).Incr("view_count", 1).Update(question)
_, err = qr.data.DB.Where("id =?", questionID).Incr("view_count", 1).Update(question)
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
return nil
}
func (qr *questionRepo) UpdateAnswerCount(ctx context.Context, questionId string, num int) (err error) {
func (qr *questionRepo) UpdateAnswerCount(ctx context.Context, questionID string, num int) (err error) {
question := &entity.Question{}
_, err = qr.data.DB.Where("id =?", questionId).Incr("answer_count", num).Update(question)
_, err = qr.data.DB.Where("id =?", questionID).Incr("answer_count", num).Update(question)
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
return nil
}
func (qr *questionRepo) UpdateCollectionCount(ctx context.Context, questionId string, num int) (err error) {
func (qr *questionRepo) UpdateCollectionCount(ctx context.Context, questionID string, num int) (err error) {
question := &entity.Question{}
_, err = qr.data.DB.Where("id =?", questionId).Incr("collection_count", num).Update(question)
_, err = qr.data.DB.Where("id =?", questionID).Incr("collection_count", num).Update(question)
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
@ -119,7 +122,8 @@ func (qr *questionRepo) UpdateLastAnswer(ctx context.Context, question *entity.Q
// GetQuestion get question one
func (qr *questionRepo) GetQuestion(ctx context.Context, id string) (
question *entity.Question, exist bool, err error) {
question *entity.Question, exist bool, err error,
) {
question = &entity.Question{}
question.ID = id
exist, err = qr.data.DB.Where("id = ?", id).Get(question)
@ -179,7 +183,7 @@ func (qr *questionRepo) SearchList(ctx context.Context, search *schema.QuestionS
search.Page = 0
}
if search.PageSize == 0 {
search.PageSize = constant.Default_PageSize
search.PageSize = constant.DefaultPageSize
}
offset := search.Page * search.PageSize
session := qr.data.DB.Table("question")
@ -187,7 +191,7 @@ func (qr *questionRepo) SearchList(ctx context.Context, search *schema.QuestionS
if len(search.TagIDs) > 0 {
session = session.Join("LEFT", "tag_rel", "question.id = tag_rel.object_id")
session = session.And("tag_rel.tag_id =?", search.TagIDs[0])
//session = session.In("tag_rel.tag_id ", search.TagIDs)
// session = session.In("tag_rel.tag_id ", search.TagIDs)
session = session.And("tag_rel.status =?", entity.TagRelStatusAvailable)
}
@ -199,8 +203,8 @@ func (qr *questionRepo) SearchList(ctx context.Context, search *schema.QuestionS
// if search.Status > 0 {
// session = session.And("question.status = ?", search.Status)
// }
//switch
//newest, active,frequent,score,unanswered
// switch
// newest, active,frequent,score,unanswered
switch search.Order {
case "newest":
session = session.OrderBy("question.created_at desc")
@ -225,8 +229,16 @@ func (qr *questionRepo) SearchList(ctx context.Context, search *schema.QuestionS
}
func (qr *questionRepo) CmsSearchList(ctx context.Context, search *schema.CmsQuestionSearch) ([]*entity.Question, int64, error) {
var count int64
var err error
var (
count int64
err error
session = qr.data.DB.Table("question")
)
session.Where(builder.Eq{
"status": search.Status,
})
rows := make([]*entity.Question, 0)
if search.Page > 0 {
search.Page = search.Page - 1
@ -234,13 +246,42 @@ func (qr *questionRepo) CmsSearchList(ctx context.Context, search *schema.CmsQue
search.Page = 0
}
if search.PageSize == 0 {
search.PageSize = constant.Default_PageSize
search.PageSize = constant.DefaultPageSize
}
// search by question title like or question id
if len(search.Query) > 0 {
// check id search
var (
idSearch = false
id = ""
)
if strings.Contains(search.Query, "id:") {
idSearch = true
id = strings.TrimSpace(strings.TrimPrefix(search.Query, "id:"))
for _, r := range id {
if !unicode.IsDigit(r) {
idSearch = false
break
}
}
}
if idSearch {
session.And(builder.Eq{
"id": id,
})
} else {
session.And(builder.Like{
"title", search.Query,
})
}
}
offset := search.Page * search.PageSize
session := qr.data.DB.Table("question")
session = session.And("status =?", search.Status)
session = session.OrderBy("updated_at desc")
session = session.Limit(search.PageSize, offset)
session.OrderBy("updated_at desc").
Limit(search.PageSize, offset)
count, err = session.FindAndCount(&rows)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()

View File

@ -34,26 +34,28 @@ func NewUserRankRepo(data *data.Data, configRepo config.ConfigRepo) rank.UserRan
// session is need provider, it means this action must be success or failure
// if outer action is failed then this action is need rollback
func (ur *UserRankRepo) TriggerUserRank(ctx context.Context,
session *xorm.Session, userId string, deltaRank int, activityType int) (isReachStandard bool, err error) {
session *xorm.Session, userID string, deltaRank int, activityType int,
) (isReachStandard bool, err error) {
if deltaRank == 0 {
return false, nil
}
if deltaRank < 0 {
// if user rank is lower than 1 after this action, then user rank will be set to 1 only.
isReachMin, err := ur.checkUserMinRank(ctx, session, userId, activityType)
var isReachMin bool
isReachMin, err = ur.checkUserMinRank(ctx, session, userID, activityType)
if err != nil {
return false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
if isReachMin {
_, err = session.Where(builder.Eq{"id": userId}).Update(&entity.User{Rank: 1})
_, err = session.Where(builder.Eq{"id": userID}).Update(&entity.User{Rank: 1})
if err != nil {
return false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
return false, nil
}
} else {
isReachStandard, err = ur.checkUserTodayRank(ctx, session, userId, activityType)
isReachStandard, err = ur.checkUserTodayRank(ctx, session, userID, activityType)
if err != nil {
return false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
@ -61,7 +63,7 @@ func (ur *UserRankRepo) TriggerUserRank(ctx context.Context,
return isReachStandard, nil
}
}
_, err = session.Where(builder.Eq{"id": userId}).Incr("`rank`", deltaRank).Update(&entity.User{})
_, err = session.Where(builder.Eq{"id": userID}).Incr("`rank`", deltaRank).Update(&entity.User{})
if err != nil {
return false, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
@ -69,7 +71,8 @@ func (ur *UserRankRepo) TriggerUserRank(ctx context.Context,
}
func (ur *UserRankRepo) checkUserMinRank(ctx context.Context, session *xorm.Session, userID string, deltaRank int) (
isReachStandard bool, err error) {
isReachStandard bool, err error,
) {
bean := &entity.User{ID: userID}
_, err = session.Select("rank").Get(bean)
if err != nil {
@ -83,11 +86,13 @@ func (ur *UserRankRepo) checkUserMinRank(ctx context.Context, session *xorm.Sess
}
func (ur *UserRankRepo) checkUserTodayRank(ctx context.Context,
session *xorm.Session, userID string, activityType int) (isReachStandard bool, err error) {
session *xorm.Session, userID string, activityType int,
) (isReachStandard bool, err error) {
// exclude daily rank
exclude, err := ur.configRepo.GetArrayString("daily_rank_limit.exclude")
exclude, _ := ur.configRepo.GetArrayString("daily_rank_limit.exclude")
for _, item := range exclude {
excludeActivityType, err := ur.configRepo.GetInt(item)
var excludeActivityType int
excludeActivityType, err = ur.configRepo.GetInt(item)
if err != nil {
return false, err
}
@ -123,14 +128,15 @@ func (ur *UserRankRepo) checkUserTodayRank(ctx context.Context,
return true, nil
}
func (ur *UserRankRepo) UserRankPage(ctx context.Context, userId string, page, pageSize int) (
rankPage []*entity.Activity, total int64, err error) {
func (ur *UserRankRepo) UserRankPage(ctx context.Context, userID string, page, pageSize int) (
rankPage []*entity.Activity, total int64, err error,
) {
rankPage = make([]*entity.Activity, 0)
session := ur.data.DB.Where(builder.Eq{"has_rank": 1}.And(builder.Eq{"cancelled": 0}))
session.Desc("created_at")
cond := &entity.Activity{UserID: userId}
cond := &entity.Activity{UserID: userID}
total, err = pager.Help(page, pageSize, &rankPage, cond, session)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()

View File

@ -86,7 +86,8 @@ func (ar *reportRepo) GetByID(ctx context.Context, id string) (report entity.Rep
func (ar *reportRepo) UpdateByID(
ctx context.Context,
id string,
handleData entity.Report) (err error) {
handleData entity.Report,
) (err error) {
_, err = ar.data.DB.ID(id).Update(&handleData)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()

View File

@ -81,7 +81,8 @@ func (rr *revisionRepo) UpdateObjectRevisionId(ctx context.Context, revision *en
// GetRevision get revision one
func (rr *revisionRepo) GetRevision(ctx context.Context, id string) (
revision *entity.Revision, exist bool, err error) {
revision *entity.Revision, exist bool, err error,
) {
revision = &entity.Revision{}
exist, err = rr.data.DB.ID(id).Get(revision)
if err != nil {
@ -92,7 +93,8 @@ func (rr *revisionRepo) GetRevision(ctx context.Context, id string) (
// GetLastRevisionByObjectID get object's last revision by object TagID
func (rr *revisionRepo) GetLastRevisionByObjectID(ctx context.Context, objectID string) (
revision *entity.Revision, exist bool, err error) {
revision *entity.Revision, exist bool, err error,
) {
revision = &entity.Revision{}
exist, err = rr.data.DB.Where("object_id = ?", objectID).OrderBy("create_time DESC").Get(revision)
if err != nil {

View File

@ -21,7 +21,7 @@ import (
)
var (
q_fields = []string{
qFields = []string{
"`question`.`id`",
"`question`.`id` as `question_id`",
"`title`",
@ -34,7 +34,7 @@ var (
"`question`.`status` as `status`",
"`post_update_time`",
}
a_fields = []string{
aFields = []string{
"`answer`.`id` as `id`",
"`question_id`",
"`question`.`title` as `title`",
@ -70,8 +70,8 @@ func (sr *searchRepo) SearchContents(ctx context.Context, words []string, tagID,
var (
b *builder.Builder
ub *builder.Builder
qfs = q_fields
afs = a_fields
qfs = qFields
afs = aFields
argsQ = []interface{}{}
argsA = []interface{}{}
)
@ -141,11 +141,11 @@ func (sr *searchRepo) SearchContents(ctx context.Context, words []string, tagID,
b = b.Union("all", ub)
querySql, _, err := builder.MySQL().Select("*").From(b, "t").OrderBy(sr.parseOrder(ctx, order)).Limit(size, page-1).ToSQL()
querySQL, _, err := builder.MySQL().Select("*").From(b, "t").OrderBy(sr.parseOrder(ctx, order)).Limit(size, page-1).ToSQL()
if err != nil {
return
}
countSql, _, err := builder.MySQL().Select("count(*) total").From(b, "c").ToSQL()
countSQL, _, err := builder.MySQL().Select("count(*) total").From(b, "c").ToSQL()
if err != nil {
return
}
@ -153,11 +153,11 @@ func (sr *searchRepo) SearchContents(ctx context.Context, words []string, tagID,
queryArgs := []interface{}{}
countArgs := []interface{}{}
queryArgs = append(queryArgs, querySql)
queryArgs = append(queryArgs, querySQL)
queryArgs = append(queryArgs, argsQ...)
queryArgs = append(queryArgs, argsA...)
countArgs = append(countArgs, countSql)
countArgs = append(countArgs, countSQL)
countArgs = append(countArgs, argsQ...)
countArgs = append(countArgs, argsA...)
@ -182,7 +182,7 @@ func (sr *searchRepo) SearchContents(ctx context.Context, words []string, tagID,
// SearchQuestions search question data
func (sr *searchRepo) SearchQuestions(ctx context.Context, words []string, limitNoAccepted bool, answers, page, size int, order string) (resp []schema.SearchResp, total int64, err error) {
var (
qfs = q_fields
qfs = qFields
args = []interface{}{}
)
if order == "relevance" {
@ -223,18 +223,18 @@ func (sr *searchRepo) SearchQuestions(ctx context.Context, words []string, limit
queryArgs := []interface{}{}
countArgs := []interface{}{}
querySql, _, err := b.OrderBy(sr.parseOrder(ctx, order)).Limit(size, page-1).ToSQL()
querySQL, _, err := b.OrderBy(sr.parseOrder(ctx, order)).Limit(size, page-1).ToSQL()
if err != nil {
return
}
countSql, _, err := builder.MySQL().Select("count(*) total").From(b, "c").ToSQL()
countSQL, _, err := builder.MySQL().Select("count(*) total").From(b, "c").ToSQL()
if err != nil {
return
}
queryArgs = append(queryArgs, querySql)
queryArgs = append(queryArgs, querySQL)
queryArgs = append(queryArgs, args...)
countArgs = append(countArgs, countSql)
countArgs = append(countArgs, countSQL)
countArgs = append(countArgs, args...)
res, err := sr.data.DB.Query(queryArgs...)
@ -250,10 +250,6 @@ func (sr *searchRepo) SearchQuestions(ctx context.Context, words []string, limit
if len(tr) != 0 {
total = converter.StringToInt64(string(tr[0]["total"]))
}
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
return
}
resp, err = sr.parseResult(ctx, res)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
@ -264,7 +260,7 @@ func (sr *searchRepo) SearchQuestions(ctx context.Context, words []string, limit
// SearchAnswers search answer data
func (sr *searchRepo) SearchAnswers(ctx context.Context, words []string, limitAccepted bool, questionID string, page, size int, order string) (resp []schema.SearchResp, total int64, err error) {
var (
afs = a_fields
afs = aFields
args = []interface{}{}
)
if order == "relevance" {
@ -289,8 +285,8 @@ func (sr *searchRepo) SearchAnswers(ctx context.Context, words []string, limitAc
}
if limitAccepted {
b.Where(builder.Eq{"adopted": schema.Answer_Adopted_Enable})
args = append(args, schema.Answer_Adopted_Enable)
b.Where(builder.Eq{"adopted": schema.AnswerAdoptedEnable})
args = append(args, schema.AnswerAdoptedEnable)
}
if questionID != "" {
@ -301,18 +297,18 @@ func (sr *searchRepo) SearchAnswers(ctx context.Context, words []string, limitAc
queryArgs := []interface{}{}
countArgs := []interface{}{}
querySql, _, err := b.OrderBy(sr.parseOrder(ctx, order)).Limit(size, page-1).ToSQL()
querySQL, _, err := b.OrderBy(sr.parseOrder(ctx, order)).Limit(size, page-1).ToSQL()
if err != nil {
return
}
countSql, _, err := builder.MySQL().Select("count(*) total").From(b, "c").ToSQL()
countSQL, _, err := builder.MySQL().Select("count(*) total").From(b, "c").ToSQL()
if err != nil {
return
}
queryArgs = append(queryArgs, querySql)
queryArgs = append(queryArgs, querySQL)
queryArgs = append(queryArgs, args...)
countArgs = append(countArgs, countSql)
countArgs = append(countArgs, countSQL)
countArgs = append(countArgs, args...)
res, err := sr.data.DB.Query(queryArgs...)
@ -326,10 +322,6 @@ func (sr *searchRepo) SearchAnswers(ctx context.Context, words []string, limitAc
}
total = converter.StringToInt64(string(tr[0]["total"]))
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
return
}
resp, err = sr.parseResult(ctx, res)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
@ -438,7 +430,7 @@ func (sr *searchRepo) userBasicInfoFormat(ctx context.Context, dbinfo *entity.Us
Avatar: dbinfo.Avatar,
Website: dbinfo.Website,
Location: dbinfo.Location,
IpInfo: dbinfo.IPInfo,
IPInfo: dbinfo.IPInfo,
}
}
@ -451,24 +443,24 @@ func cutOutParsedText(parsedText string) string {
return parsedText
}
func addRelevanceField(search_fields, words, fields []string) (res []string, args []interface{}) {
var relevanceRes = []string{}
func addRelevanceField(searchFields, words, fields []string) (res []string, args []interface{}) {
relevanceRes := []string{}
args = []interface{}{}
for _, search_field := range search_fields {
for _, searchField := range searchFields {
var (
relevance = "(LENGTH(" + search_field + ") - LENGTH(%s))"
replacement = "REPLACE(%s, ?, '')"
replace_field = search_field
replaced string
argsField = []interface{}{}
relevance = "(LENGTH(" + searchField + ") - LENGTH(%s))"
replacement = "REPLACE(%s, ?, '')"
replaceField = searchField
replaced string
argsField = []interface{}{}
)
res = fields
for i, word := range words {
if i == 0 {
argsField = append(argsField, word)
replaced = fmt.Sprintf(replacement, replace_field)
replaced = fmt.Sprintf(replacement, replaceField)
} else {
argsField = append(argsField, word)
replaced = fmt.Sprintf(replacement, replaced)

View File

@ -27,7 +27,7 @@ func (sr *siteInfoRepo) SaveByType(ctx context.Context, siteType string, data *e
old = &entity.SiteInfo{}
exist bool
)
exist, err = sr.data.DB.Where(builder.Eq{"type": siteType}).Get(old)
exist, _ = sr.data.DB.Where(builder.Eq{"type": siteType}).Get(old)
if exist {
_, err = sr.data.DB.ID(old.ID).Update(data)
if err != nil {

View File

@ -32,8 +32,8 @@ func (tr *tagListRepo) AddTagRelList(ctx context.Context, tagList []*entity.TagR
}
// RemoveTagRelListByObjectID delete tag list
func (tr *tagListRepo) RemoveTagRelListByObjectID(ctx context.Context, objectId string) (err error) {
_, err = tr.data.DB.Where("object_id = ?", objectId).Update(&entity.TagRel{Status: entity.TagRelStatusDeleted})
func (tr *tagListRepo) RemoveTagRelListByObjectID(ctx context.Context, objectID string) (err error) {
_, err = tr.data.DB.Where("object_id = ?", objectID).Update(&entity.TagRel{Status: entity.TagRelStatusDeleted})
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
@ -50,10 +50,11 @@ func (tr *tagListRepo) RemoveTagRelListByIDs(ctx context.Context, ids []int64) (
}
// GetObjectTagRelWithoutStatus get object tag relation no matter status
func (tr *tagListRepo) GetObjectTagRelWithoutStatus(ctx context.Context, objectId, tagID string) (
tagRel *entity.TagRel, exist bool, err error) {
func (tr *tagListRepo) GetObjectTagRelWithoutStatus(ctx context.Context, objectID, tagID string) (
tagRel *entity.TagRel, exist bool, err error,
) {
tagRel = &entity.TagRel{}
session := tr.data.DB.Where("object_id = ?", objectId).And("tag_id = ?", tagID)
session := tr.data.DB.Where("object_id = ?", objectID).And("tag_id = ?", tagID)
exist, err = session.Get(tagRel)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
@ -71,9 +72,9 @@ func (tr *tagListRepo) EnableTagRelByIDs(ctx context.Context, ids []int64) (err
}
// GetObjectTagRelList get object tag relation list all
func (tr *tagListRepo) GetObjectTagRelList(ctx context.Context, objectId string) (tagListList []*entity.TagRel, err error) {
func (tr *tagListRepo) GetObjectTagRelList(ctx context.Context, objectID string) (tagListList []*entity.TagRel, err error) {
tagListList = make([]*entity.TagRel, 0)
session := tr.data.DB.Where("object_id = ?", objectId)
session := tr.data.DB.Where("object_id = ?", objectID)
session.Where("status = ?", entity.TagRelStatusAvailable)
err = session.Find(&tagListList)
if err != nil {

View File

@ -34,7 +34,8 @@ func NewTagRepo(
// AddTagList add tag
func (tr *tagRepo) AddTagList(ctx context.Context, tagList []*entity.Tag) (err error) {
for _, item := range tagList {
ID, err := tr.uniqueIDRepo.GenUniqueID(ctx, item.TableName())
var ID int64
ID, err = tr.uniqueIDRepo.GenUniqueID(ctx, item.TableName())
if err != nil {
return err
}
@ -128,7 +129,8 @@ func (tr *tagRepo) UpdateTagQuestionCount(ctx context.Context, tagID string, que
// UpdateTagSynonym update synonym tag
func (tr *tagRepo) UpdateTagSynonym(ctx context.Context, tagSlugNameList []string, mainTagID int64,
mainTagSlugName string) (err error) {
mainTagSlugName string,
) (err error) {
bean := &entity.Tag{MainTagID: mainTagID, MainTagSlugName: mainTagSlugName}
session := tr.data.DB.In("slug_name", tagSlugNameList).MustCols("main_tag_id", "main_tag_slug_name")
_, err = session.Update(bean)
@ -140,7 +142,8 @@ func (tr *tagRepo) UpdateTagSynonym(ctx context.Context, tagSlugNameList []strin
// GetTagByID get tag one
func (tr *tagRepo) GetTagByID(ctx context.Context, tagID string) (
tag *entity.Tag, exist bool, err error) {
tag *entity.Tag, exist bool, err error,
) {
tag = &entity.Tag{}
session := tr.data.DB.Where(builder.Eq{"id": tagID})
session.Where(builder.Eq{"status": entity.TagStatusAvailable})
@ -164,7 +167,8 @@ func (tr *tagRepo) GetTagList(ctx context.Context, tag *entity.Tag) (tagList []*
// GetTagPage get tag page
func (tr *tagRepo) GetTagPage(ctx context.Context, page, pageSize int, tag *entity.Tag, queryCond string) (
tagList []*entity.Tag, total int64, err error) {
tagList []*entity.Tag, total int64, err error,
) {
tagList = make([]*entity.Tag, 0)
session := tr.data.DB.NewSession()

View File

@ -3,7 +3,11 @@ package user
import (
"context"
"encoding/json"
"net/mail"
"strings"
"time"
"unicode"
"xorm.io/builder"
"github.com/answerdev/answer/internal/base/constant"
"github.com/answerdev/answer/internal/base/data"
@ -29,7 +33,8 @@ func NewUserBackyardRepo(data *data.Data) user_backyard.UserBackyardRepo {
// UpdateUserStatus update user status
func (ur *userBackyardRepo) UpdateUserStatus(ctx context.Context, userID string, userStatus, mailStatus int,
email string) (err error) {
email string,
) (err error) {
cond := &entity.User{Status: userStatus, MailStatus: mailStatus, EMail: email}
switch userStatus {
case entity.UserStatusSuspended:
@ -68,16 +73,51 @@ func (ur *userBackyardRepo) GetUserInfo(ctx context.Context, userID string) (use
}
// GetUserPage get user page
func (ur *userBackyardRepo) GetUserPage(ctx context.Context, page, pageSize int, user *entity.User) (users []*entity.User, total int64, err error) {
func (ur *userBackyardRepo) GetUserPage(ctx context.Context, page, pageSize int, user *entity.User, query string) (users []*entity.User, total int64, err error) {
users = make([]*entity.User, 0)
session := ur.data.DB.NewSession()
if user.Status == entity.UserStatusDeleted {
switch user.Status {
case entity.UserStatusDeleted:
session.Desc("deleted_at")
} else if user.Status == entity.UserStatusSuspended {
case entity.UserStatusSuspended:
session.Desc("suspended_at")
} else {
default:
session.Desc("created_at")
}
if len(query) > 0 {
if email, e := mail.ParseAddress(query); e == nil {
session.And(builder.Eq{"e_mail": email.Address})
} else {
var (
idSearch = false
id = ""
)
if strings.Contains(query, "id:") {
idSearch = true
id = strings.TrimSpace(strings.TrimPrefix(query, "id:"))
for _, r := range id {
if !unicode.IsDigit(r) {
idSearch = false
break
}
}
}
if idSearch {
session.And(builder.Eq{
"id": id,
})
} else {
session.And(builder.Or(
builder.Like{"username", query},
builder.Like{"display_name", query},
))
}
}
}
total, err = pager.Help(page, pageSize, &users, user, session)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()

View File

@ -9,44 +9,44 @@ type RemoveAnswerReq struct {
}
const (
Answer_Adopted_Failed = 1
Answer_Adopted_Enable = 2
AnswerAdoptedFailed = 1
AnswerAdoptedEnable = 2
)
type AnswerAddReq struct {
QuestionId string `json:"question_id" ` // question_id
QuestionID string `json:"question_id" ` // question_id
Content string `json:"content" ` // content
Html string `json:"html" ` // html
HTML string `json:"html" ` // html
UserID string `json:"-" ` // user_id
}
type AnswerUpdateReq struct {
ID string `json:"id"` // id
QuestionId string `json:"question_id" ` // question_id
QuestionID string `json:"question_id" ` // question_id
UserID string `json:"-" ` // user_id
Title string `json:"title" ` // title
Content string `json:"content"` // content
Html string `json:"html" ` // html
EditSummary string `validate:"omitempty" json:"edit_summary"` //edit_summary
HTML string `json:"html" ` // html
EditSummary string `validate:"omitempty" json:"edit_summary"` // edit_summary
}
type AnswerList struct {
QuestionId string `json:"question_id" form:"question_id"` // question_id
QuestionID string `json:"question_id" form:"question_id"` // question_id
Order string `json:"order" form:"order"` // 1 Default 2 time
Page int `json:"page" form:"page"` //Query number of pages
PageSize int `json:"page_size" form:"page_size"` //Search page size
Page int `json:"page" form:"page"` // Query number of pages
PageSize int `json:"page_size" form:"page_size"` // Search page size
LoginUserID string `json:"-" `
}
type AnswerInfo struct {
ID string `json:"id" xorm:"id"` // id
QuestionId string `json:"question_id" xorm:"question_id"` // question_id
QuestionID string `json:"question_id" xorm:"question_id"` // question_id
Content string `json:"content" xorm:"content"` // content
Html string `json:"html" xorm:"html"` // html
HTML string `json:"html" xorm:"html"` // html
CreateTime int64 `json:"create_time" xorm:"created"` // create_time
UpdateTime int64 `json:"update_time" xorm:"updated"` // update_time
Adopted int `json:"adopted"` // 1 Failed 2 Adopted
UserId string `json:"-" `
UserID string `json:"-" `
UserInfo *UserBasicInfo `json:"user_info,omitempty"`
UpdateUserInfo *UserBasicInfo `json:"update_user_info,omitempty"`
Collected bool `json:"collected"`
@ -60,12 +60,12 @@ type AnswerInfo struct {
type AdminAnswerInfo struct {
ID string `json:"id"`
QuestionId string `json:"question_id"`
QuestionID string `json:"question_id"`
Description string `json:"description"`
CreateTime int64 `json:"create_time"`
UpdateTime int64 `json:"update_time"`
Adopted int `json:"adopted"`
UserId string `json:"-" `
UserID string `json:"-" `
UserInfo *UserBasicInfo `json:"user_info"`
VoteCount int `json:"vote_count"`
QuestionInfo struct {

View File

@ -26,10 +26,8 @@ type GetUserPageReq struct {
Page int `validate:"omitempty,min=1" form:"page"`
// page size
PageSize int `validate:"omitempty,min=1" form:"page_size"`
// username
Username string `validate:"omitempty,gt=0,lte=50" form:"username"`
// email
EMail string `validate:"omitempty,gt=0,lte=100" form:"e_mail"`
Query string `validate:"omitempty,gt=0,lte=100" form:"query"`
// user status
Status string `validate:"omitempty,oneof=suspended deleted inactive" form:"status"`
}

View File

@ -3,8 +3,8 @@ package schema
import "time"
const (
CG_DEFAULT = 1
CG_DIY = 2
CGDefault = 1
CGDIY = 2
)
// CollectionSwitchReq switch collection request

View File

@ -2,7 +2,7 @@ package schema
const (
ForbiddenReasonTypeInactive = "inactive"
ForbiddenReasonTypeUrlExpired = "url_expired"
ForbiddenReasonTypeURLExpired = "url_expired"
ForbiddenReasonTypeUserSuspended = "suspended"
)

View File

@ -5,12 +5,11 @@ type RemoveQuestionReq struct {
// question id
ID string `validate:"required" comment:"question id" json:"id"`
UserID string `json:"-" ` // user_id
}
type CloseQuestionReq struct {
ID string `validate:"required" comment:"question id" json:"id"`
UserId string `json:"-" ` // user_id
UserID string `json:"-" ` // user_id
CloseType int `json:"close_type" ` // close_type
CloseMsg string `json:"close_msg" ` // close_type
}
@ -26,7 +25,7 @@ type QuestionAdd struct {
// content
Content string `validate:"required,gte=6,lte=65535" json:"content"`
// html
Html string `validate:"required,gte=6,lte=65535" json:"html"`
HTML string `validate:"required,gte=6,lte=65535" json:"html"`
// tags
Tags []*TagItem `validate:"required,dive" json:"tags"`
// user id
@ -41,7 +40,7 @@ type QuestionUpdate struct {
// content
Content string `validate:"required,gte=6,lte=65535" json:"content"`
// html
Html string `validate:"required,gte=6,lte=65535" json:"html"`
HTML string `validate:"required,gte=6,lte=65535" json:"html"`
// tags
Tags []*TagItem `validate:"required,dive" json:"tags"`
// edit summary
@ -65,7 +64,7 @@ type QuestionInfo struct {
ID string `json:"id" `
Title string `json:"title" xorm:"title"` // title
Content string `json:"content" xorm:"content"` // content
Html string `json:"html" xorm:"html"` // html
HTML string `json:"html" xorm:"html"` // html
Tags []*TagResp `json:"tags" ` // tags
ViewCount int `json:"view_count" xorm:"view_count"` // view_count
UniqueViewCount int `json:"unique_view_count" xorm:"unique_view_count"` // unique_view_count
@ -73,15 +72,15 @@ type QuestionInfo struct {
AnswerCount int `json:"answer_count" xorm:"answer_count"` // answer count
CollectionCount int `json:"collection_count" xorm:"collection_count"` // collection count
FollowCount int `json:"follow_count" xorm:"follow_count"` // follow count
AcceptedAnswerId string `json:"accepted_answer_id" ` // accepted_answer_id
LastAnswerId string `json:"last_answer_id" ` // last_answer_id
AcceptedAnswerID string `json:"accepted_answer_id" ` // accepted_answer_id
LastAnswerID string `json:"last_answer_id" ` // last_answer_id
CreateTime int64 `json:"create_time" ` // create_time
UpdateTime int64 `json:"-"` // update_time
PostUpdateTime int64 `json:"update_time"`
QuestionUpdateTime int64 `json:"edit_time"`
Status int `json:"status"`
Operation *Operation `json:"operation,omitempty"`
UserId string `json:"-" `
UserID string `json:"-" `
UserInfo *UserBasicInfo `json:"user_info"`
UpdateUserInfo *UserBasicInfo `json:"update_user_info,omitempty"`
LastAnsweredUserInfo *UserBasicInfo `json:"last_answered_user_info,omitempty"`
@ -108,10 +107,10 @@ type AdminQuestionInfo struct {
}
type Operation struct {
Operation_Type string `json:"operation_type"`
Operation_Description string `json:"operation_description"`
Operation_Msg string `json:"operation_msg"`
Operation_Time int64 `json:"operation_time"`
OperationType string `json:"operation_type"`
OperationDescription string `json:"operation_description"`
OperationMsg string `json:"operation_msg"`
OperationTime int64 `json:"operation_time"`
}
type GetCloseTypeResp struct {
@ -151,26 +150,27 @@ type UserQuestionInfo struct {
AnswerCount int `json:"answer_count"`
CollectionCount int `json:"collection_count"`
CreateTime int `json:"create_time"`
AcceptedAnswerId string `json:"accepted_answer_id"`
AcceptedAnswerID string `json:"accepted_answer_id"`
Status string `json:"status"`
}
type QuestionSearch struct {
Page int `json:"page" form:"page"` //Query number of pages
PageSize int `json:"page_size" form:"page_size"` //Search page size
Order string `json:"order" form:"order"` //Search order by
//Tags []string `json:"tags" form:"tags"` //Search tag
Page int `json:"page" form:"page"` // Query number of pages
PageSize int `json:"page_size" form:"page_size"` // Search page size
Order string `json:"order" form:"order"` // Search order by
// Tags []string `json:"tags" form:"tags"` // Search tag
Tag string `json:"tag" form:"tag"` //Search tag
TagIDs []string `json:"-" form:"-"` //Search tag
UserName string `json:"username" form:"username"` //Search username
TagIDs []string `json:"-" form:"-"` // Search tag
UserName string `json:"username" form:"username"` // Search username
UserID string `json:"-" form:"-"`
}
type CmsQuestionSearch struct {
Page int `json:"page" form:"page"` //Query number of pages
PageSize int `json:"page_size" form:"page_size"` //Search page size
Page int `json:"page" form:"page"` // Query number of pages
PageSize int `json:"page_size" form:"page_size"` // Search page size
Status int `json:"-" form:"-"`
StatusStr string `json:"status" form:"status"` //Status 1 Available 2 closed 10 UserDeleted
StatusStr string `json:"status" form:"status"` // Status 1 Available 2 closed 10 UserDeleted
Query string `validate:"omitempty,gt=0,lte=100" json:"query" form:"query" ` //Query string
}
type AdminSetQuestionStatusRequest struct {

View File

@ -107,7 +107,7 @@ func (tr *GetTagPageResp) GetExcerpt() {
}
type TagChange struct {
ObjectId string `json:"object_id"` // object_id
ObjectID string `json:"object_id"` // object_id
Tags []*TagItem `json:"tags"` // tags name
// user id
UserID string `json:"-"`

View File

@ -56,7 +56,7 @@ type GetUserResp struct {
// bio markdown
Bio string `json:"bio"`
// bio html
BioHtml string `json:"bio_html"`
BioHTML string `json:"bio_html"`
// website
Website string `json:"website"`
// location
@ -73,7 +73,7 @@ type GetUserResp struct {
func (r *GetUserResp) GetFromUserEntity(userInfo *entity.User) {
_ = copier.Copy(r, userInfo)
r.Avatar = r.AvatarInfo(userInfo.Avatar)
r.Avatar = FormatAvatarInfo(userInfo.Avatar)
r.CreatedAt = userInfo.CreatedAt.Unix()
r.LastLoginDate = userInfo.LastLoginDate.Unix()
statusShow, ok := UserStatusShow[userInfo.Status]
@ -103,7 +103,7 @@ func (r *GetUserToSetShowResp) GetFromUserEntity(userInfo *entity.User) {
r.Avatar = avatarInfo
}
func (us *GetUserResp) AvatarInfo(avatarJson string) string {
func FormatAvatarInfo(avatarJson string) string {
if avatarJson == "" {
return ""
}
@ -157,7 +157,7 @@ type GetOtherUserInfoByUsernameResp struct {
// bio markdown
Bio string `json:"bio"`
// bio html
BioHtml string `json:"bio_html"`
BioHTML string `json:"bio_html"`
// website
Website string `json:"website"`
// location
@ -172,6 +172,9 @@ type GetOtherUserInfoByUsernameResp struct {
func (r *GetOtherUserInfoByUsernameResp) GetFromUserEntity(userInfo *entity.User) {
_ = copier.Copy(r, userInfo)
Avatar := FormatAvatarInfo(userInfo.Avatar)
r.Avatar = Avatar
r.CreatedAt = userInfo.CreatedAt.Unix()
r.LastLoginDate = userInfo.LastLoginDate.Unix()
statusShow, ok := UserStatusShow[userInfo.Status]
@ -189,20 +192,19 @@ func (r *GetOtherUserInfoByUsernameResp) GetFromUserEntity(userInfo *entity.User
r.StatusMsg = statusMsgShow
}
}
}
const (
Mail_State_Pass = 1
Mail_State_Verifi = 2
MailStatePass = 1
MailStateVerifi = 2
Notice_Status_On = 1
Notice_Status_Off = 2
NoticeStatusOn = 1
NoticeStatusOff = 2
//ActionRecord ReportType
ActionRecord_Type_Login = "login"
ActionRecord_Type_Email = "e_mail"
ActionRecord_Type_Find_Pass = "find_pass"
// ActionRecord ReportType
ActionRecordTypeLogin = "login"
ActionRecordTypeEmail = "e_mail"
ActionRecordTypeFindPass = "find_pass"
)
var UserStatusShow = map[int]string{
@ -210,6 +212,7 @@ var UserStatusShow = map[int]string{
9: "forbidden",
10: "deleted",
}
var UserStatusShowMsg = map[int]string{
1: "",
9: "<strong>This user was suspended forever.</strong> This user doesnt meet a community guideline.",
@ -250,7 +253,7 @@ func (u *UserRegisterReq) Check() (errField *validator.ErrorField, err error) {
// UserModifyPassWordRequest
type UserModifyPassWordRequest struct {
UserId string `json:"-" ` // user_id
UserID string `json:"-" ` // user_id
OldPass string `json:"old_pass" ` // old password
Pass string `json:"pass" ` // password
}
@ -277,13 +280,13 @@ type UpdateInfoRequest struct {
// bio
Bio string `validate:"omitempty,gt=0,lte=4096" json:"bio"`
// bio
BioHtml string `validate:"omitempty,gt=0,lte=4096" json:"bio_html"`
BioHTML string `validate:"omitempty,gt=0,lte=4096" json:"bio_html"`
// website
Website string `validate:"omitempty,gt=0,lte=500" json:"website"`
// location
Location string `validate:"omitempty,gt=0,lte=100" json:"location"`
// user id
UserId string `json:"-" `
UserID string `json:"-" `
}
type AvatarInfo struct {
@ -332,7 +335,7 @@ func (u *UserRePassWordRequest) Check() (errField *validator.ErrorField, err err
}
type UserNoticeSetRequest struct {
UserId string `json:"-" ` // user_id
UserID string `json:"-" ` // user_id
NoticeSwitch bool `json:"notice_switch" `
}
@ -343,7 +346,7 @@ type UserNoticeSetResp struct {
type ActionRecordReq struct {
// action
Action string `validate:"required,oneof=login e_mail find_pass" form:"action"`
Ip string `json:"-"`
IP string `json:"-"`
}
type ActionRecordResp struct {
@ -360,7 +363,7 @@ type UserBasicInfo struct {
Avatar string `json:"avatar" ` // avatar
Website string `json:"website" ` // website
Location string `json:"location" ` // location
IpInfo string `json:"ip_info"` // ip info
IPInfo string `json:"ip_info"` // ip info
Status string `json:"status"` // status
}

View File

@ -36,7 +36,7 @@ func NewCaptchaService(captchaRepo CaptchaRepo) *CaptchaService {
// ActionRecord action record
func (cs *CaptchaService) ActionRecord(ctx context.Context, req *schema.ActionRecordReq) (resp *schema.ActionRecordResp, err error) {
resp = &schema.ActionRecordResp{}
num, err := cs.captchaRepo.GetActionType(ctx, req.Ip, req.Action)
num, err := cs.captchaRepo.GetActionType(ctx, req.IP, req.Action)
if err != nil {
num = 0
}
@ -51,7 +51,8 @@ func (cs *CaptchaService) ActionRecord(ctx context.Context, req *schema.ActionRe
// ActionRecordVerifyCaptcha
// Verify that you need to enter a CAPTCHA, and that the CAPTCHA is correct
func (cs *CaptchaService) ActionRecordVerifyCaptcha(
ctx context.Context, actionType string, ip string, id string, VerifyValue string) bool {
ctx context.Context, actionType string, ip string, id string, VerifyValue string,
) bool {
num, cahceErr := cs.captchaRepo.GetActionType(ctx, ip, actionType)
if cahceErr != nil {
return true

View File

@ -14,9 +14,9 @@ type AnswerRepo interface {
GetAnswer(ctx context.Context, id string) (answer *entity.Answer, exist bool, err error)
GetAnswerList(ctx context.Context, answer *entity.Answer) (answerList []*entity.Answer, err error)
GetAnswerPage(ctx context.Context, page, pageSize int, answer *entity.Answer) (answerList []*entity.Answer, total int64, err error)
UpdateAdopted(ctx context.Context, id string, questionId string) error
UpdateAdopted(ctx context.Context, id string, questionID string) error
GetByID(ctx context.Context, id string) (*entity.Answer, bool, error)
GetByUserIdQuestionId(ctx context.Context, userId string, questionId string) (*entity.Answer, bool, error)
GetByUserIDQuestionID(ctx context.Context, userID string, questionID string) (*entity.Answer, bool, error)
SearchList(ctx context.Context, search *entity.AnswerSearch) ([]*entity.Answer, int64, error)
CmsSearchList(ctx context.Context, search *entity.CmsAnswerSearch) ([]*entity.Answer, int64, error)
UpdateAnswerStatus(ctx context.Context, answer *entity.Answer) (err error)
@ -33,8 +33,8 @@ func NewAnswerCommon(answerRepo AnswerRepo) *AnswerCommon {
}
}
func (as *AnswerCommon) SearchAnswered(ctx context.Context, userId, questionId string) (bool, error) {
_, has, err := as.answerRepo.GetByUserIdQuestionId(ctx, userId, questionId)
func (as *AnswerCommon) SearchAnswered(ctx context.Context, userID, questionID string) (bool, error) {
_, has, err := as.answerRepo.GetByUserIDQuestionID(ctx, userID, questionID)
if err != nil {
return has, err
}
@ -42,6 +42,9 @@ func (as *AnswerCommon) SearchAnswered(ctx context.Context, userId, questionId s
}
func (as *AnswerCommon) CmsSearchList(ctx context.Context, search *entity.CmsAnswerSearch) ([]*entity.Answer, int64, error) {
if search.Status == 0 {
search.Status = 1
}
return as.answerRepo.CmsSearchList(ctx, search)
}
@ -56,26 +59,26 @@ func (as *AnswerCommon) Search(ctx context.Context, search *entity.AnswerSearch)
func (as *AnswerCommon) ShowFormat(ctx context.Context, data *entity.Answer) *schema.AnswerInfo {
info := schema.AnswerInfo{}
info.ID = data.ID
info.QuestionId = data.QuestionID
info.QuestionID = data.QuestionID
info.Content = data.OriginalText
info.Html = data.ParsedText
info.HTML = data.ParsedText
info.Adopted = data.Adopted
info.VoteCount = data.VoteCount
info.CreateTime = data.CreatedAt.Unix()
info.UpdateTime = data.UpdatedAt.Unix()
info.UserId = data.UserID
info.UserID = data.UserID
return &info
}
func (as *AnswerCommon) AdminShowFormat(ctx context.Context, data *entity.Answer) *schema.AdminAnswerInfo {
info := schema.AdminAnswerInfo{}
info.ID = data.ID
info.QuestionId = data.QuestionID
info.QuestionID = data.QuestionID
info.Description = data.ParsedText
info.Adopted = data.Adopted
info.VoteCount = data.VoteCount
info.CreateTime = data.CreatedAt.Unix()
info.UpdateTime = data.UpdatedAt.Unix()
info.UserId = data.UserID
info.UserID = data.UserID
return &info
}

View File

@ -74,7 +74,7 @@ func (as *AnswerService) RemoveAnswer(ctx context.Context, id string) (err error
return nil
}
//user add question count
// user add question count
err = as.questionCommon.UpdateAnswerCount(ctx, answerInfo.QuestionID, -1)
if err != nil {
log.Error("IncreaseAnswerCount error", err.Error())
@ -97,7 +97,7 @@ func (as *AnswerService) RemoveAnswer(ctx context.Context, id string) (err error
}
func (as *AnswerService) Insert(ctx context.Context, req *schema.AnswerAddReq) (string, error) {
questionInfo, exist, err := as.questionRepo.GetQuestion(ctx, req.QuestionId)
questionInfo, exist, err := as.questionRepo.GetQuestion(ctx, req.QuestionID)
if err != nil {
return "", err
}
@ -108,24 +108,24 @@ func (as *AnswerService) Insert(ctx context.Context, req *schema.AnswerAddReq) (
insertData := new(entity.Answer)
insertData.UserID = req.UserID
insertData.OriginalText = req.Content
insertData.ParsedText = req.Html
insertData.Adopted = schema.Answer_Adopted_Failed
insertData.QuestionID = req.QuestionId
insertData.ParsedText = req.HTML
insertData.Adopted = schema.AnswerAdoptedFailed
insertData.QuestionID = req.QuestionID
insertData.RevisionID = "0"
insertData.Status = entity.AnswerStatusAvailable
insertData.UpdatedAt = now
if err := as.answerRepo.AddAnswer(ctx, insertData); err != nil {
if err = as.answerRepo.AddAnswer(ctx, insertData); err != nil {
return "", err
}
err = as.questionCommon.UpdateAnswerCount(ctx, req.QuestionId, 1)
err = as.questionCommon.UpdateAnswerCount(ctx, req.QuestionID, 1)
if err != nil {
log.Error("IncreaseAnswerCount error", err.Error())
}
err = as.questionCommon.UpdateLastAnswer(ctx, req.QuestionId, insertData.ID)
err = as.questionCommon.UpdateLastAnswer(ctx, req.QuestionID, insertData.ID)
if err != nil {
log.Error("UpdateLastAnswer error", err.Error())
}
err = as.questionCommon.UpdataPostTime(ctx, req.QuestionId)
err = as.questionCommon.UpdataPostTime(ctx, req.QuestionID)
if err != nil {
return insertData.ID, err
}
@ -140,8 +140,8 @@ func (as *AnswerService) Insert(ctx context.Context, req *schema.AnswerAddReq) (
ObjectID: insertData.ID,
Title: "",
}
InfoJson, _ := json.Marshal(insertData)
revisionDTO.Content = string(InfoJson)
infoJSON, _ := json.Marshal(insertData)
revisionDTO.Content = string(infoJSON)
err = as.revisionService.AddRevision(ctx, revisionDTO, true)
if err != nil {
return insertData.ID, err
@ -151,7 +151,7 @@ func (as *AnswerService) Insert(ctx context.Context, req *schema.AnswerAddReq) (
}
func (as *AnswerService) Update(ctx context.Context, req *schema.AnswerUpdateReq) (string, error) {
questionInfo, exist, err := as.questionRepo.GetQuestion(ctx, req.QuestionId)
questionInfo, exist, err := as.questionRepo.GetQuestion(ctx, req.QuestionID)
if err != nil {
return "", err
}
@ -161,15 +161,15 @@ func (as *AnswerService) Update(ctx context.Context, req *schema.AnswerUpdateReq
now := time.Now()
insertData := new(entity.Answer)
insertData.ID = req.ID
insertData.QuestionID = req.QuestionId
insertData.QuestionID = req.QuestionID
insertData.UserID = req.UserID
insertData.OriginalText = req.Content
insertData.ParsedText = req.Html
insertData.ParsedText = req.HTML
insertData.UpdatedAt = now
if err := as.answerRepo.UpdateAnswer(ctx, insertData, []string{"original_text", "parsed_text", "update_time"}); err != nil {
if err = as.answerRepo.UpdateAnswer(ctx, insertData, []string{"original_text", "parsed_text", "update_time"}); err != nil {
return "", err
}
err = as.questionCommon.UpdataPostTime(ctx, req.QuestionId)
err = as.questionCommon.UpdataPostTime(ctx, req.QuestionID)
if err != nil {
return insertData.ID, err
}
@ -179,8 +179,8 @@ func (as *AnswerService) Update(ctx context.Context, req *schema.AnswerUpdateReq
Title: "",
Log: req.EditSummary,
}
InfoJson, _ := json.Marshal(insertData)
revisionDTO.Content = string(InfoJson)
infoJSON, _ := json.Marshal(insertData)
revisionDTO.Content = string(infoJSON)
err = as.revisionService.AddRevision(ctx, revisionDTO, true)
if err != nil {
return insertData.ID, err
@ -228,7 +228,7 @@ func (as *AnswerService) UpdateAdopted(ctx context.Context, req *schema.AnswerAd
var oldAnswerInfo *entity.Answer
if len(questionInfo.AcceptedAnswerID) > 0 && questionInfo.AcceptedAnswerID != "0" {
oldAnswerInfo, exist, err = as.answerRepo.GetByID(ctx, questionInfo.AcceptedAnswerID)
oldAnswerInfo, _, err = as.answerRepo.GetByID(ctx, questionInfo.AcceptedAnswerID)
if err != nil {
return err
}
@ -249,8 +249,8 @@ func (as *AnswerService) UpdateAdopted(ctx context.Context, req *schema.AnswerAd
}
func (as *AnswerService) updateAnswerRank(ctx context.Context, userID string,
questionInfo *entity.Question, newAnswerInfo *entity.Answer, oldAnswerInfo *entity.Answer) {
questionInfo *entity.Question, newAnswerInfo *entity.Answer, oldAnswerInfo *entity.Answer,
) {
// if this question is already been answered, should cancel old answer rank
if oldAnswerInfo != nil {
err := as.answerActivityService.CancelAcceptAnswer(
@ -266,21 +266,20 @@ func (as *AnswerService) updateAnswerRank(ctx context.Context, userID string,
log.Error(err)
}
}
}
func (as *AnswerService) Get(ctx context.Context, answerID, loginUserId string) (*schema.AnswerInfo, *schema.QuestionInfo, bool, error) {
func (as *AnswerService) Get(ctx context.Context, answerID, loginUserID string) (*schema.AnswerInfo, *schema.QuestionInfo, bool, error) {
answerInfo, has, err := as.answerRepo.GetByID(ctx, answerID)
if err != nil {
return nil, nil, has, err
}
info := as.ShowFormat(ctx, answerInfo)
//todo questionFunc
questionInfo, err := as.questionCommon.Info(ctx, answerInfo.QuestionID, loginUserId)
// todo questionFunc
questionInfo, err := as.questionCommon.Info(ctx, answerInfo.QuestionID, loginUserID)
if err != nil {
return nil, nil, has, err
}
//todo UserFunc
// todo UserFunc
userinfo, has, err := as.userCommon.GetUserBasicInfoByID(ctx, answerInfo.UserID)
if err != nil {
return nil, nil, has, err
@ -290,13 +289,13 @@ func (as *AnswerService) Get(ctx context.Context, answerID, loginUserId string)
info.UpdateUserInfo = userinfo
}
if loginUserId == "" {
if loginUserID == "" {
return info, questionInfo, has, nil
}
info.VoteStatus = as.voteRepo.GetVoteStatus(ctx, answerID, loginUserId)
info.VoteStatus = as.voteRepo.GetVoteStatus(ctx, answerID, loginUserID)
CollectedMap, err := as.collectionCommon.SearchObjectCollected(ctx, loginUserId, []string{answerInfo.ID})
CollectedMap, err := as.collectionCommon.SearchObjectCollected(ctx, loginUserID, []string{answerInfo.ID})
if err != nil {
log.Error("CollectionFunc.SearchObjectCollected error", err)
}
@ -348,7 +347,7 @@ func (as *AnswerService) AdminSetAnswerStatus(ctx context.Context, answerID stri
func (as *AnswerService) SearchList(ctx context.Context, search *schema.AnswerList) ([]*schema.AnswerInfo, int64, error) {
list := make([]*schema.AnswerInfo, 0)
dbSearch := entity.AnswerSearch{}
dbSearch.QuestionID = search.QuestionId
dbSearch.QuestionID = search.QuestionID
dbSearch.Page = search.Page
dbSearch.PageSize = search.PageSize
dbSearch.Order = search.Order
@ -363,7 +362,7 @@ func (as *AnswerService) SearchList(ctx context.Context, search *schema.AnswerLi
return AnswerList, count, nil
}
func (as *AnswerService) SearchFormatInfo(ctx context.Context, dblist []*entity.Answer, loginUserId string) ([]*schema.AnswerInfo, error) {
func (as *AnswerService) SearchFormatInfo(ctx context.Context, dblist []*entity.Answer, loginUserID string) ([]*schema.AnswerInfo, error) {
list := make([]*schema.AnswerInfo, 0)
objectIds := make([]string, 0)
userIds := make([]string, 0)
@ -372,9 +371,9 @@ func (as *AnswerService) SearchFormatInfo(ctx context.Context, dblist []*entity.
list = append(list, item)
objectIds = append(objectIds, dbitem.ID)
userIds = append(userIds, dbitem.UserID)
if loginUserId != "" {
//item.VoteStatus = as.activityFunc.GetVoteStatus(ctx, item.TagID, loginUserId)
item.VoteStatus = as.voteRepo.GetVoteStatus(ctx, item.ID, loginUserId)
if loginUserID != "" {
// item.VoteStatus = as.activityFunc.GetVoteStatus(ctx, item.TagID, loginUserId)
item.VoteStatus = as.voteRepo.GetVoteStatus(ctx, item.ID, loginUserID)
}
}
userInfoMap, err := as.userCommon.BatchUserBasicInfoByID(ctx, userIds)
@ -382,18 +381,18 @@ func (as *AnswerService) SearchFormatInfo(ctx context.Context, dblist []*entity.
return list, err
}
for _, item := range list {
_, ok := userInfoMap[item.UserId]
_, ok := userInfoMap[item.UserID]
if ok {
item.UserInfo = userInfoMap[item.UserId]
item.UpdateUserInfo = userInfoMap[item.UserId]
item.UserInfo = userInfoMap[item.UserID]
item.UpdateUserInfo = userInfoMap[item.UserID]
}
}
if loginUserId == "" {
if loginUserID == "" {
return list, nil
}
CollectedMap, err := as.collectionCommon.SearchObjectCollected(ctx, loginUserId, objectIds)
CollectedMap, err := as.collectionCommon.SearchObjectCollected(ctx, loginUserID, objectIds)
if err != nil {
log.Error("CollectionFunc.SearchObjectCollected error", err)
}
@ -406,7 +405,7 @@ func (as *AnswerService) SearchFormatInfo(ctx context.Context, dblist []*entity.
}
for _, item := range list {
item.MemberActions = permission.GetAnswerPermission(loginUserId, item.UserId)
item.MemberActions = permission.GetAnswerPermission(loginUserID, item.UserID)
}
return list, nil

View File

@ -17,7 +17,7 @@ type CollectionGroupRepo interface {
UpdateCollectionGroup(ctx context.Context, collectionGroup *entity.CollectionGroup, cols []string) (err error)
GetCollectionGroup(ctx context.Context, id string) (collectionGroup *entity.CollectionGroup, exist bool, err error)
GetCollectionGroupPage(ctx context.Context, page, pageSize int, collectionGroup *entity.CollectionGroup) (collectionGroupList []*entity.CollectionGroup, total int64, err error)
GetDefaultID(ctx context.Context, userId string) (collectionGroup *entity.CollectionGroup, has bool, err error)
GetDefaultID(ctx context.Context, userID string) (collectionGroup *entity.CollectionGroup, has bool, err error)
}
// CollectionGroupService user service

View File

@ -23,7 +23,6 @@ func NewCollectionService(
collectionRepo collectioncommon.CollectionRepo,
collectionGroupRepo CollectionGroupRepo,
questionCommon *questioncommon.QuestionCommon,
) *CollectionService {
return &CollectionService{
collectionRepo: collectionRepo,
@ -31,6 +30,7 @@ func NewCollectionService(
questionCommon: questionCommon,
}
}
func (cs *CollectionService) CollectionSwitch(ctx context.Context, dto *schema.CollectionSwitchDTO) (resp *schema.CollectionSwitchResp, err error) {
resp = &schema.CollectionSwitchResp{}
dbData, has, err := cs.collectionRepo.GetOneByObjectIDAndUser(ctx, dto.UserID, dto.ObjectID)
@ -46,7 +46,8 @@ func (cs *CollectionService) CollectionSwitch(ctx context.Context, dto *schema.C
if err != nil {
log.Error("UpdateCollectionCount", err.Error())
}
count, err := cs.objectCollectionCount(ctx, dto.ObjectID)
var count int64
count, err = cs.objectCollectionCount(ctx, dto.ObjectID)
if err != nil {
return resp, err
}
@ -56,12 +57,17 @@ func (cs *CollectionService) CollectionSwitch(ctx context.Context, dto *schema.C
}
if dto.GroupID == "" || dto.GroupID == "0" {
defaultGroup, has, err := cs.collectionGroupRepo.GetDefaultID(ctx, dto.UserID)
var (
defaultGroup *entity.CollectionGroup
has bool
)
defaultGroup, has, err = cs.collectionGroupRepo.GetDefaultID(ctx, dto.UserID)
if err != nil {
return nil, err
}
if !has {
dbdefaultGroup, err := cs.collectionGroupRepo.AddCollectionDefaultGroup(ctx, dto.UserID)
var dbdefaultGroup *entity.CollectionGroup
dbdefaultGroup, err = cs.collectionGroupRepo.AddCollectionDefaultGroup(ctx, dto.UserID)
if err != nil {
return nil, err
}
@ -93,8 +99,8 @@ func (cs *CollectionService) CollectionSwitch(ctx context.Context, dto *schema.C
return
}
func (cs *CollectionService) objectCollectionCount(ctx context.Context, objectId string) (int64, error) {
count, err := cs.collectionRepo.CountByObjectID(ctx, objectId)
func (cs *CollectionService) objectCollectionCount(ctx context.Context, objectID string) (int64, error) {
count, err := cs.collectionRepo.CountByObjectID(ctx, objectID)
return count, err
}
@ -108,12 +114,16 @@ func (cs *CollectionService) add(ctx context.Context, collection *entity.Collect
}
if collection.UserCollectionGroupID == "" || collection.UserCollectionGroupID == "0" {
defaultGroup, has, err := cs.collectionGroupRepo.GetDefaultID(ctx, collection.UserID)
var (
defaultGroup *entity.CollectionGroup
has bool
)
defaultGroup, has, err = cs.collectionGroupRepo.GetDefaultID(ctx, collection.UserID)
if err != nil {
return err
}
if !has {
defaultGroup, err := cs.collectionGroupRepo.AddCollectionDefaultGroup(ctx, collection.UserID)
defaultGroup, err = cs.collectionGroupRepo.AddCollectionDefaultGroup(ctx, collection.UserID)
if err != nil {
return err
}

View File

@ -31,9 +31,9 @@ type QuestionRepo interface {
SearchList(ctx context.Context, search *schema.QuestionSearch) ([]*entity.QuestionTag, int64, error)
UpdateQuestionStatus(ctx context.Context, question *entity.Question) (err error)
SearchByTitleLike(ctx context.Context, title string) (questionList []*entity.Question, err error)
UpdatePvCount(ctx context.Context, questionId string) (err error)
UpdateAnswerCount(ctx context.Context, questionId string, num int) (err error)
UpdateCollectionCount(ctx context.Context, questionId string, num int) (err error)
UpdatePvCount(ctx context.Context, questionID string) (err error)
UpdateAnswerCount(ctx context.Context, questionID string, num int) (err error)
UpdateCollectionCount(ctx context.Context, questionID string, num int) (err error)
UpdateAccepted(ctx context.Context, question *entity.Question) (err error)
UpdateLastAnswer(ctx context.Context, question *entity.Question) (err error)
FindByID(ctx context.Context, id []string) (questionList []*entity.Question, err error)
@ -64,7 +64,6 @@ func NewQuestionCommon(questionRepo QuestionRepo,
answerCommon *answercommon.AnswerCommon,
metaService *meta.MetaService,
configRepo config.ConfigRepo,
) *QuestionCommon {
return &QuestionCommon{
questionRepo: questionRepo,
@ -80,42 +79,44 @@ func NewQuestionCommon(questionRepo QuestionRepo,
}
}
func (qs *QuestionCommon) UpdataPv(ctx context.Context, questionId string) error {
return qs.questionRepo.UpdatePvCount(ctx, questionId)
}
func (qs *QuestionCommon) UpdateAnswerCount(ctx context.Context, questionId string, num int) error {
return qs.questionRepo.UpdateAnswerCount(ctx, questionId, num)
}
func (qs *QuestionCommon) UpdateCollectionCount(ctx context.Context, questionId string, num int) error {
return qs.questionRepo.UpdateCollectionCount(ctx, questionId, num)
func (qs *QuestionCommon) UpdataPv(ctx context.Context, questionID string) error {
return qs.questionRepo.UpdatePvCount(ctx, questionID)
}
func (qs *QuestionCommon) UpdateAccepted(ctx context.Context, questionId, AnswerId string) error {
func (qs *QuestionCommon) UpdateAnswerCount(ctx context.Context, questionID string, num int) error {
return qs.questionRepo.UpdateAnswerCount(ctx, questionID, num)
}
func (qs *QuestionCommon) UpdateCollectionCount(ctx context.Context, questionID string, num int) error {
return qs.questionRepo.UpdateCollectionCount(ctx, questionID, num)
}
func (qs *QuestionCommon) UpdateAccepted(ctx context.Context, questionID, AnswerID string) error {
question := &entity.Question{}
question.ID = questionId
question.AcceptedAnswerID = AnswerId
question.ID = questionID
question.AcceptedAnswerID = AnswerID
return qs.questionRepo.UpdateAccepted(ctx, question)
}
func (qs *QuestionCommon) UpdateLastAnswer(ctx context.Context, questionId, AnswerId string) error {
func (qs *QuestionCommon) UpdateLastAnswer(ctx context.Context, questionID, AnswerID string) error {
question := &entity.Question{}
question.ID = questionId
question.LastAnswerID = AnswerId
question.ID = questionID
question.LastAnswerID = AnswerID
return qs.questionRepo.UpdateLastAnswer(ctx, question)
}
func (qs *QuestionCommon) UpdataPostTime(ctx context.Context, questionId string) error {
func (qs *QuestionCommon) UpdataPostTime(ctx context.Context, questionID string) error {
questioninfo := &entity.Question{}
now := time.Now()
questioninfo.ID = questionId
questioninfo.ID = questionID
questioninfo.PostUpdateTime = now
return qs.questionRepo.UpdateQuestion(ctx, questioninfo, []string{"post_update_time"})
}
func (qs *QuestionCommon) FindInfoByID(ctx context.Context, questionIds []string, loginUserID string) (map[string]*schema.QuestionInfo, error) {
func (qs *QuestionCommon) FindInfoByID(ctx context.Context, questionIDs []string, loginUserID string) (map[string]*schema.QuestionInfo, error) {
list := make(map[string]*schema.QuestionInfo)
listAddTag := make([]*entity.QuestionTag, 0)
questionList, err := qs.questionRepo.FindByID(ctx, questionIds)
questionList, err := qs.questionRepo.FindByID(ctx, questionIDs)
if err != nil {
return list, err
}
@ -134,8 +135,8 @@ func (qs *QuestionCommon) FindInfoByID(ctx context.Context, questionIds []string
return list, nil
}
func (qs *QuestionCommon) Info(ctx context.Context, questionId string, loginUserID string) (showinfo *schema.QuestionInfo, err error) {
dbinfo, has, err := qs.questionRepo.GetQuestion(ctx, questionId)
func (qs *QuestionCommon) Info(ctx context.Context, questionID string, loginUserID string) (showinfo *schema.QuestionInfo, err error) {
dbinfo, has, err := qs.questionRepo.GetQuestion(ctx, questionID)
if err != nil {
return showinfo, err
}
@ -145,13 +146,14 @@ func (qs *QuestionCommon) Info(ctx context.Context, questionId string, loginUser
showinfo = qs.ShowFormat(ctx, dbinfo)
if showinfo.Status == 2 {
metainfo, err := qs.metaService.GetMetaByObjectIdAndKey(ctx, dbinfo.ID, entity.QuestionCloseReasonKey)
var metainfo *entity.Meta
metainfo, err = qs.metaService.GetMetaByObjectIdAndKey(ctx, dbinfo.ID, entity.QuestionCloseReasonKey)
if err != nil {
log.Error(err)
} else {
//metainfo.Value
// metainfo.Value
closemsg := &schema.CloseQuestionMeta{}
err := json.Unmarshal([]byte(metainfo.Value), closemsg)
err = json.Unmarshal([]byte(metainfo.Value), closemsg)
if err != nil {
log.Error("json.Unmarshal CloseQuestionMeta error", err.Error())
} else {
@ -161,10 +163,10 @@ func (qs *QuestionCommon) Info(ctx context.Context, questionId string, loginUser
log.Error("json.Unmarshal QuestionCloseJson error", err.Error())
} else {
operation := &schema.Operation{}
operation.Operation_Type = closeinfo.Name
operation.Operation_Description = closeinfo.Description
operation.Operation_Msg = closemsg.CloseMsg
operation.Operation_Time = metainfo.CreatedAt.Unix()
operation.OperationType = closeinfo.Name
operation.OperationDescription = closeinfo.Description
operation.OperationMsg = closemsg.CloseMsg
operation.OperationTime = metainfo.CreatedAt.Unix()
showinfo.Operation = operation
}
@ -173,7 +175,7 @@ func (qs *QuestionCommon) Info(ctx context.Context, questionId string, loginUser
}
}
tagmap, err := qs.tagCommon.GetObjectTag(ctx, questionId)
tagmap, err := qs.tagCommon.GetObjectTag(ctx, questionID)
if err != nil {
return showinfo, err
}
@ -193,10 +195,10 @@ func (qs *QuestionCommon) Info(ctx context.Context, questionId string, loginUser
return showinfo, nil
}
showinfo.VoteStatus = qs.voteRepo.GetVoteStatus(ctx, questionId, loginUserID)
showinfo.VoteStatus = qs.voteRepo.GetVoteStatus(ctx, questionID, loginUserID)
// // check is followed
isFollowed, _ := qs.followCommon.IsFollowed(loginUserID, questionId)
isFollowed, _ := qs.followCommon.IsFollowed(loginUserID, questionID)
showinfo.IsFollowed = isFollowed
has, err = qs.AnswerCommon.SearchAnswered(ctx, loginUserID, dbinfo.ID)
@ -205,7 +207,7 @@ func (qs *QuestionCommon) Info(ctx context.Context, questionId string, loginUser
}
showinfo.Answered = has
//login user Collected information
// login user Collected information
CollectedMap, err := qs.collectionCommon.SearchObjectCollected(ctx, loginUserID, []string{dbinfo.ID})
if err != nil {
@ -245,11 +247,11 @@ func (qs *QuestionCommon) ListFormat(ctx context.Context, questionList []*entity
if ok {
item.Tags = tagsMap[item.ID]
}
_, ok = userInfoMap[item.UserId]
_, ok = userInfoMap[item.UserID]
if ok {
item.UserInfo = userInfoMap[item.UserId]
item.UpdateUserInfo = userInfoMap[item.UserId]
item.LastAnsweredUserInfo = userInfoMap[item.UserId]
item.UserInfo = userInfoMap[item.UserID]
item.UpdateUserInfo = userInfoMap[item.UserID]
item.LastAnsweredUserInfo = userInfoMap[item.UserID]
}
}
@ -286,7 +288,7 @@ func (qs *QuestionCommon) RemoveQuestion(ctx context.Context, req *schema.Remove
return err
}
//user add question count
// user add question count
err = qs.userCommon.UpdateQuestionCount(ctx, questionInfo.UserID, -1)
if err != nil {
log.Error("user UpdateQuestionCount error", err.Error())
@ -332,7 +334,7 @@ func (as *QuestionCommon) RemoveAnswer(ctx context.Context, id string) (err erro
return nil
}
//user add question count
// user add question count
err = as.UpdateAnswerCount(ctx, answerinfo.QuestionID, -1)
if err != nil {
@ -356,21 +358,21 @@ func (qs *QuestionCommon) ShowFormat(ctx context.Context, data *entity.Question)
info.ID = data.ID
info.Title = data.Title
info.Content = data.OriginalText
info.Html = data.ParsedText
info.HTML = data.ParsedText
info.ViewCount = data.ViewCount
info.UniqueViewCount = data.UniqueViewCount
info.VoteCount = data.VoteCount
info.AnswerCount = data.AnswerCount
info.CollectionCount = data.CollectionCount
info.FollowCount = data.FollowCount
info.AcceptedAnswerId = data.AcceptedAnswerID
info.LastAnswerId = data.LastAnswerID
info.AcceptedAnswerID = data.AcceptedAnswerID
info.LastAnswerID = data.LastAnswerID
info.CreateTime = data.CreatedAt.Unix()
info.UpdateTime = data.UpdatedAt.Unix()
info.PostUpdateTime = data.PostUpdateTime.Unix()
info.QuestionUpdateTime = data.UpdatedAt.Unix()
info.Status = data.Status
info.UserId = data.UserID
info.UserID = data.UserID
info.Tags = make([]*schema.TagResp, 0)
return &info
}

View File

@ -89,9 +89,10 @@ func (qs *QuestionService) CloseQuestion(ctx context.Context, req *schema.CloseQ
// CloseMsgList list close question condition
func (qs *QuestionService) CloseMsgList(ctx context.Context, lang i18n.Language) (
resp []*schema.GetCloseTypeResp, err error) {
resp []*schema.GetCloseTypeResp, err error,
) {
resp = make([]*schema.GetCloseTypeResp, 0)
err = json.Unmarshal([]byte(constant.QuestionCloseJson), &resp)
err = json.Unmarshal([]byte(constant.QuestionCloseJSON), &resp)
if err != nil {
return nil, errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
}
@ -110,7 +111,7 @@ func (qs *QuestionService) AddQuestion(ctx context.Context, req *schema.Question
question.UserID = req.UserID
question.Title = req.Title
question.OriginalText = req.Content
question.ParsedText = req.Html
question.ParsedText = req.HTML
question.AcceptedAnswerID = "0"
question.LastAnswerID = "0"
question.PostUpdateTime = now
@ -123,7 +124,7 @@ func (qs *QuestionService) AddQuestion(ctx context.Context, req *schema.Question
return
}
objectTagData := schema.TagChange{}
objectTagData.ObjectId = question.ID
objectTagData.ObjectID = question.ID
objectTagData.Tags = req.Tags
objectTagData.UserID = req.UserID
err = qs.ChangeTag(ctx, &objectTagData)
@ -136,14 +137,14 @@ func (qs *QuestionService) AddQuestion(ctx context.Context, req *schema.Question
ObjectID: question.ID,
Title: "",
}
InfoJson, _ := json.Marshal(question)
revisionDTO.Content = string(InfoJson)
infoJSON, _ := json.Marshal(question)
revisionDTO.Content = string(infoJSON)
err = qs.revisionService.AddRevision(ctx, revisionDTO, true)
if err != nil {
return
}
//user add question count
// user add question count
err = qs.userCommon.UpdateQuestionCount(ctx, question.UserID, 1)
if err != nil {
log.Error("user IncreaseQuestionCount error", err.Error())
@ -168,7 +169,7 @@ func (qs *QuestionService) RemoveQuestion(ctx context.Context, req *schema.Remov
return err
}
//user add question count
// user add question count
err = qs.userCommon.UpdateQuestionCount(ctx, questionInfo.UserID, -1)
if err != nil {
log.Error("user IncreaseQuestionCount error", err.Error())
@ -190,7 +191,7 @@ func (qs *QuestionService) UpdateQuestion(ctx context.Context, req *schema.Quest
question.UserID = req.UserID
question.Title = req.Title
question.OriginalText = req.Content
question.ParsedText = req.Html
question.ParsedText = req.HTML
question.ID = req.ID
question.UpdatedAt = now
dbinfo, has, err := qs.questionRepo.GetQuestion(ctx, question.ID)
@ -208,7 +209,7 @@ func (qs *QuestionService) UpdateQuestion(ctx context.Context, req *schema.Quest
return
}
objectTagData := schema.TagChange{}
objectTagData.ObjectId = question.ID
objectTagData.ObjectID = question.ID
objectTagData.Tags = req.Tags
objectTagData.UserID = req.UserID
err = qs.ChangeTag(ctx, &objectTagData)
@ -222,8 +223,8 @@ func (qs *QuestionService) UpdateQuestion(ctx context.Context, req *schema.Quest
Title: "",
Log: req.EditSummary,
}
InfoJson, _ := json.Marshal(question)
revisionDTO.Content = string(InfoJson)
infoJSON, _ := json.Marshal(question)
revisionDTO.Content = string(infoJSON)
err = qs.revisionService.AddRevision(ctx, revisionDTO, true)
if err != nil {
return
@ -246,7 +247,7 @@ func (qs *QuestionService) GetQuestion(ctx context.Context, id, loginUserID stri
}
}
question.MemberActions = permission.GetQuestionPermission(loginUserID, question.UserId)
question.MemberActions = permission.GetQuestionPermission(loginUserID, question.UserID)
return question, nil
}
@ -300,9 +301,9 @@ func (qs *QuestionService) SearchUserAnswerList(ctx context.Context, userName, o
answersearch.PageSize = pageSize
answersearch.Page = page
if order == "newest" {
answersearch.Order = entity.Answer_Search_OrderBy_Time
answersearch.Order = entity.AnswerSearchOrderByTime
} else {
answersearch.Order = entity.Answer_Search_OrderBy_Default
answersearch.Order = entity.AnswerSearchOrderByDefault
}
questionIDs := make([]string, 0)
answerList, count, err := qs.questioncommon.AnswerCommon.Search(ctx, answersearch)
@ -319,16 +320,16 @@ func (qs *QuestionService) SearchUserAnswerList(ctx context.Context, userName, o
return userAnswerlist, count, err
}
for _, item := range answerlist {
_, ok := questionMaps[item.QuestionId]
_, ok := questionMaps[item.QuestionID]
if ok {
item.QuestionInfo = questionMaps[item.QuestionId]
item.QuestionInfo = questionMaps[item.QuestionID]
}
}
for _, item := range answerlist {
info := &schema.UserAnswerInfo{}
_ = copier.Copy(info, item)
info.AnswerID = item.ID
info.QuestionID = item.QuestionId
info.QuestionID = item.QuestionID
userAnswerlist = append(userAnswerlist, info)
}
return userAnswerlist, count, nil
@ -366,7 +367,7 @@ func (qs *QuestionService) SearchUserCollectionList(ctx context.Context, page, p
questionMaps[id].LastAnsweredUserInfo = nil
questionMaps[id].UpdateUserInfo = nil
questionMaps[id].Content = ""
questionMaps[id].Html = ""
questionMaps[id].HTML = ""
list = append(list, questionMaps[id])
}
}
@ -399,7 +400,7 @@ func (qs *QuestionService) SearchUserTopList(ctx context.Context, userName strin
answersearch := &entity.AnswerSearch{}
answersearch.UserID = userinfo.ID
answersearch.PageSize = 5
answersearch.Order = entity.Answer_Search_OrderBy_Vote
answersearch.Order = entity.AnswerSearchOrderByVote
questionIDs := make([]string, 0)
answerList, _, err := qs.questioncommon.AnswerCommon.Search(ctx, answersearch)
if err != nil {
@ -415,9 +416,9 @@ func (qs *QuestionService) SearchUserTopList(ctx context.Context, userName strin
return userQuestionlist, userAnswerlist, err
}
for _, item := range answerlist {
_, ok := questionMaps[item.QuestionId]
_, ok := questionMaps[item.QuestionID]
if ok {
item.QuestionInfo = questionMaps[item.QuestionId]
item.QuestionInfo = questionMaps[item.QuestionID]
}
}
@ -431,7 +432,7 @@ func (qs *QuestionService) SearchUserTopList(ctx context.Context, userName strin
info := &schema.UserAnswerInfo{}
_ = copier.Copy(info, item)
info.AnswerID = item.ID
info.QuestionID = item.QuestionId
info.QuestionID = item.QuestionID
userAnswerlist = append(userAnswerlist, info)
}
@ -629,13 +630,13 @@ func (qs *QuestionService) CmsSearchAnswerList(ctx context.Context, search *enti
return answerlist, count, err
}
for _, item := range answerlist {
_, ok := questionMaps[item.QuestionId]
_, ok := questionMaps[item.QuestionID]
if ok {
item.QuestionInfo.Title = questionMaps[item.QuestionId].Title
item.QuestionInfo.Title = questionMaps[item.QuestionID].Title
}
_, ok = userInfoMap[item.UserId]
_, ok = userInfoMap[item.UserID]
if ok {
item.UserInfo = userInfoMap[item.UserId]
item.UserInfo = userInfoMap[item.UserID]
}
}
return answerlist, count, nil

View File

@ -24,7 +24,8 @@ type ReportService struct {
// NewReportService new report service
func NewReportService(reportRepo report_common.ReportRepo,
objectInfoService *object_info.ObjService) *ReportService {
objectInfoService *object_info.ObjService,
) *ReportService {
return &ReportService{
reportRepo: reportRepo,
objectInfoService: objectInfoService,
@ -58,15 +59,16 @@ func (rs *ReportService) AddReport(ctx context.Context, req *schema.AddReportReq
// GetReportTypeList get report list all
func (rs *ReportService) GetReportTypeList(ctx context.Context, lang i18n.Language, req *schema.GetReportListReq) (
resp []*schema.GetReportTypeResp, err error) {
resp []*schema.GetReportTypeResp, err error,
) {
resp = make([]*schema.GetReportTypeResp, 0)
switch req.Source {
case constant.QuestionObjectType:
err = json.Unmarshal([]byte(constant.QuestionReportJson), &resp)
err = json.Unmarshal([]byte(constant.QuestionReportJSON), &resp)
case constant.AnswerObjectType:
err = json.Unmarshal([]byte(constant.AnswerReportJson), &resp)
err = json.Unmarshal([]byte(constant.AnswerReportJSON), &resp)
case constant.CommentObjectType:
err = json.Unmarshal([]byte(constant.CommentReportJson), &resp)
err = json.Unmarshal([]byte(constant.CommentReportJSON), &resp)
}
if err != nil {
err = errors.BadRequest(reason.UnknownError)

View File

@ -64,7 +64,7 @@ func (s *SiteInfoService) SaveSiteGeneral(ctx context.Context, req schema.SiteGe
siteType = "general"
content []byte
)
content, err = json.Marshal(req)
content, _ = json.Marshal(req)
data := entity.SiteInfo{
Type: siteType,
@ -107,7 +107,7 @@ func (s *SiteInfoService) SaveSiteInterface(ctx context.Context, req schema.Site
return
}
content, err = json.Marshal(req)
content, _ = json.Marshal(req)
data := entity.SiteInfo{
Type: siteType,
@ -120,7 +120,8 @@ func (s *SiteInfoService) SaveSiteInterface(ctx context.Context, req schema.Site
// GetSMTPConfig get smtp config
func (s *SiteInfoService) GetSMTPConfig(ctx context.Context) (
resp *schema.GetSMTPConfigResp, err error) {
resp *schema.GetSMTPConfigResp, err error,
) {
emailConfig, err := s.emailService.GetEmailConfig()
if err != nil {
return nil, err

View File

@ -47,7 +47,8 @@ type TagCommonService struct {
// NewTagCommonService new tag service
func NewTagCommonService(tagRepo TagRepo, tagRelRepo TagRelRepo,
revisionService *revision_common.RevisionService) *TagCommonService {
revisionService *revision_common.RevisionService,
) *TagCommonService {
return &TagCommonService{
tagRepo: tagRepo,
tagRelRepo: tagRelRepo,
@ -97,20 +98,20 @@ func (ts *TagCommonService) GetObjectTag(ctx context.Context, objectId string) (
// BatchGetObjectTag batch get object tag
func (ts *TagCommonService) BatchGetObjectTag(ctx context.Context, objectIds []string) (map[string][]*schema.TagResp, error) {
objectIdTagMap := make(map[string][]*schema.TagResp)
objectIDTagMap := make(map[string][]*schema.TagResp)
tagIDList := make([]string, 0)
tagsInfoMap := make(map[string]*entity.Tag)
tagList, err := ts.tagRelRepo.BatchGetObjectTagRelList(ctx, objectIds)
if err != nil {
return objectIdTagMap, err
return objectIDTagMap, err
}
for _, tag := range tagList {
tagIDList = append(tagIDList, tag.TagID)
}
tagsInfoList, err := ts.tagRepo.GetTagListByIDs(ctx, tagIDList)
if err != nil {
return objectIdTagMap, err
return objectIDTagMap, err
}
for _, item := range tagsInfoList {
tagsInfoMap[item.ID] = item
@ -124,10 +125,10 @@ func (ts *TagCommonService) BatchGetObjectTag(ctx context.Context, objectIds []s
DisplayName: tagInfo.DisplayName,
MainTagSlugName: tagInfo.MainTagSlugName,
}
objectIdTagMap[item.ObjectID] = append(objectIdTagMap[item.ObjectID], t)
objectIDTagMap[item.ObjectID] = append(objectIDTagMap[item.ObjectID], t)
}
}
return objectIdTagMap, nil
return objectIDTagMap, nil
}
// ObjectChangeTag change object tag list
@ -191,7 +192,7 @@ func (ts *TagCommonService) ObjectChangeTag(ctx context.Context, objectTagData *
}
}
err = ts.CreateOrUpdateTagRelList(ctx, objectTagData.ObjectId, thisObjTagIDList)
err = ts.CreateOrUpdateTagRelList(ctx, objectTagData.ObjectID, thisObjTagIDList)
if err != nil {
return err
}

View File

@ -17,7 +17,7 @@ import (
type UserBackyardRepo interface {
UpdateUserStatus(ctx context.Context, userID string, userStatus, mailStatus int, email string) (err error)
GetUserInfo(ctx context.Context, userID string) (user *entity.User, exist bool, err error)
GetUserPage(ctx context.Context, page, pageSize int, user *entity.User) (users []*entity.User, total int64, err error)
GetUserPage(ctx context.Context, page, pageSize int, user *entity.User, query string) (users []*entity.User, total int64, err error)
}
// UserBackyardService user service
@ -91,13 +91,14 @@ func (us *UserBackyardService) GetUserPage(ctx context.Context, req *schema.GetU
user.Status = entity.UserStatusDeleted
}
users, total, err := us.userRepo.GetUserPage(ctx, req.Page, req.PageSize, user)
users, total, err := us.userRepo.GetUserPage(ctx, req.Page, req.PageSize, user, req.Query)
if err != nil {
return
}
resp := make([]*schema.GetUserPageResp, 0)
for _, u := range users {
avatar := schema.FormatAvatarInfo(u.Avatar)
t := &schema.GetUserPageResp{
UserID: u.ID,
CreatedAt: u.CreatedAt.Unix(),
@ -105,7 +106,7 @@ func (us *UserBackyardService) GetUserPage(ctx context.Context, req *schema.GetU
EMail: u.EMail,
Rank: u.Rank,
DisplayName: u.DisplayName,
Avatar: u.Avatar,
Avatar: avatar,
}
if u.Status == entity.UserStatusDeleted {
t.Status = schema.UserDeleted

View File

@ -76,16 +76,15 @@ func (us *UserCommon) BatchUserBasicInfoByID(ctx context.Context, IDs []string)
// UserBasicInfoFormat
func (us *UserCommon) UserBasicInfoFormat(ctx context.Context, userInfo *entity.User) *schema.UserBasicInfo {
userBasicInfo := &schema.UserBasicInfo{}
uinfo := &schema.GetUserResp{}
uinfo.AvatarInfo(userInfo.Avatar)
Avatar := schema.FormatAvatarInfo(userInfo.Avatar)
userBasicInfo.ID = userInfo.ID
userBasicInfo.Username = userInfo.Username
userBasicInfo.Rank = userInfo.Rank
userBasicInfo.DisplayName = userInfo.DisplayName
userBasicInfo.Avatar = uinfo.Avatar
userBasicInfo.Avatar = Avatar
userBasicInfo.Website = userInfo.Website
userBasicInfo.Location = userInfo.Location
userBasicInfo.IpInfo = userInfo.IPInfo
userBasicInfo.IPInfo = userInfo.IPInfo
userBasicInfo.Status = schema.UserStatusShow[userInfo.Status]
if userBasicInfo.Status == schema.UserDeleted {
userBasicInfo.Avatar = ""

View File

@ -40,7 +40,8 @@ func NewUserService(userRepo usercommon.UserRepo,
userActivity activity.UserActiveActivityRepo,
emailService *export.EmailService,
authService *auth.AuthService,
serviceConfig *service_config.ServiceConfig) *UserService {
serviceConfig *service_config.ServiceConfig,
) *UserService {
return &UserService{
userRepo: userRepo,
userActivity: userActivity,
@ -95,7 +96,8 @@ func (us *UserService) GetUserStatus(ctx context.Context, userID, token string)
}
func (us *UserService) GetOtherUserInfoByUsername(ctx context.Context, username string) (
resp *schema.GetOtherUserInfoResp, err error) {
resp *schema.GetOtherUserInfoResp, err error,
) {
userInfo, exist, err := us.userRepo.GetByUsername(ctx, username)
if err != nil {
return nil, err
@ -166,8 +168,8 @@ func (us *UserService) RetrievePassWord(ctx context.Context, req *schema.UserRet
UserID: userInfo.ID,
}
code := uuid.NewString()
verifyEmailUrl := fmt.Sprintf("%s/users/password-reset?code=%s", us.serviceConfig.WebHost, code)
title, body, err := us.emailService.PassResetTemplate(ctx, verifyEmailUrl)
verifyEmailURL := fmt.Sprintf("%s/users/password-reset?code=%s", us.serviceConfig.WebHost, code)
title, body, err := us.emailService.PassResetTemplate(ctx, verifyEmailURL)
if err != nil {
return "", err
}
@ -180,7 +182,7 @@ func (us *UserService) UseRePassWord(ctx context.Context, req *schema.UserRePass
data := &schema.EmailCodeContent{}
err = data.FromJSONString(req.Content)
if err != nil {
return nil, errors.BadRequest(reason.EmailVerifyUrlExpired)
return nil, errors.BadRequest(reason.EmailVerifyURLExpired)
}
userInfo, exist, err := us.userRepo.GetByEmail(ctx, data.Email)
@ -204,8 +206,7 @@ func (us *UserService) UseRePassWord(ctx context.Context, req *schema.UserRePass
}
func (us *UserService) UserModifyPassWordVerification(ctx context.Context, request *schema.UserModifyPassWordRequest) (bool, error) {
userInfo, has, err := us.userRepo.GetByUserID(ctx, request.UserId)
userInfo, has, err := us.userRepo.GetByUserID(ctx, request.UserID)
if err != nil {
return false, err
}
@ -226,7 +227,7 @@ func (us *UserService) UserModifyPassWord(ctx context.Context, request *schema.U
if err != nil {
return err
}
userInfo, has, err := us.userRepo.GetByUserID(ctx, request.UserId)
userInfo, has, err := us.userRepo.GetByUserID(ctx, request.UserID)
if err != nil {
return err
}
@ -252,21 +253,21 @@ func (us *UserService) UpdateInfo(ctx context.Context, req *schema.UpdateInfoReq
if err != nil {
return err
}
if exist && userInfo.ID != req.UserId {
if exist && userInfo.ID != req.UserID {
return errors.BadRequest(reason.UsernameDuplicate)
}
}
Avatar, err := json.Marshal(req.Avatar)
avatar, err := json.Marshal(req.Avatar)
if err != nil {
err = errors.BadRequest(reason.UserSetAvatar).WithError(err).WithStack()
return err
}
userInfo := entity.User{}
userInfo.ID = req.UserId
userInfo.Avatar = string(Avatar)
userInfo.ID = req.UserID
userInfo.Avatar = string(avatar)
userInfo.DisplayName = req.DisplayName
userInfo.Bio = req.Bio
userInfo.BioHtml = req.BioHtml
userInfo.BioHTML = req.BioHTML
userInfo.Location = req.Location
userInfo.Website = req.Website
userInfo.Username = req.Username
@ -286,7 +287,8 @@ func (us *UserService) UserEmailHas(ctx context.Context, email string) (bool, er
// UserRegisterByEmail user register
func (us *UserService) UserRegisterByEmail(ctx context.Context, registerUserInfo *schema.UserRegisterReq) (
resp *schema.GetUserResp, err error) {
resp *schema.GetUserResp, err error,
) {
_, has, err := us.userRepo.GetByEmail(ctx, registerUserInfo.Email)
if err != nil {
return nil, err
@ -320,8 +322,8 @@ func (us *UserService) UserRegisterByEmail(ctx context.Context, registerUserInfo
UserID: userInfo.ID,
}
code := uuid.NewString()
verifyEmailUrl := fmt.Sprintf("%s/users/account-activation?code=%s", us.serviceConfig.WebHost, code)
title, body, err := us.emailService.RegisterTemplate(ctx, verifyEmailUrl)
verifyEmailURL := fmt.Sprintf("%s/users/account-activation?code=%s", us.serviceConfig.WebHost, code)
title, body, err := us.emailService.RegisterTemplate(ctx, verifyEmailURL)
if err != nil {
return nil, err
}
@ -363,8 +365,8 @@ func (us *UserService) UserVerifyEmailSend(ctx context.Context, userID string) e
UserID: userInfo.ID,
}
code := uuid.NewString()
verifyEmailUrl := fmt.Sprintf("%s/users/account-activation?code=%s", us.serviceConfig.WebHost, code)
title, body, err := us.emailService.RegisterTemplate(ctx, verifyEmailUrl)
verifyEmailURL := fmt.Sprintf("%s/users/account-activation?code=%s", us.serviceConfig.WebHost, code)
title, body, err := us.emailService.RegisterTemplate(ctx, verifyEmailURL)
if err != nil {
return err
}
@ -372,9 +374,10 @@ func (us *UserService) UserVerifyEmailSend(ctx context.Context, userID string) e
return nil
}
func (us *UserService) UserNoticeSet(ctx context.Context, userId string, noticeSwitch bool) (
resp *schema.UserNoticeSetResp, err error) {
userInfo, has, err := us.userRepo.GetByUserID(ctx, userId)
func (us *UserService) UserNoticeSet(ctx context.Context, userID string, noticeSwitch bool) (
resp *schema.UserNoticeSetResp, err error,
) {
userInfo, has, err := us.userRepo.GetByUserID(ctx, userID)
if err != nil {
return nil, err
}
@ -382,9 +385,9 @@ func (us *UserService) UserNoticeSet(ctx context.Context, userId string, noticeS
return nil, errors.BadRequest(reason.UserNotFound)
}
if noticeSwitch {
userInfo.NoticeStatus = schema.Notice_Status_On
userInfo.NoticeStatus = schema.NoticeStatusOn
} else {
userInfo.NoticeStatus = schema.Notice_Status_Off
userInfo.NoticeStatus = schema.NoticeStatusOff
}
err = us.userRepo.UpdateNoticeStatus(ctx, userInfo.ID, userInfo.NoticeStatus)
return &schema.UserNoticeSetResp{NoticeSwitch: noticeSwitch}, err
@ -394,7 +397,7 @@ func (us *UserService) UserVerifyEmail(ctx context.Context, req *schema.UserVeri
data := &schema.EmailCodeContent{}
err = data.FromJSONString(req.Content)
if err != nil {
return nil, errors.BadRequest(reason.EmailVerifyUrlExpired)
return nil, errors.BadRequest(reason.EmailVerifyURLExpired)
}
userInfo, has, err := us.userRepo.GetByEmail(ctx, data.Email)
@ -476,23 +479,20 @@ func (us *UserService) makeUsername(ctx context.Context, displayName string) (us
// Compare whether the password is correct
func (us *UserService) verifyPassword(ctx context.Context, LoginPass, UserPass string) bool {
err := bcrypt.CompareHashAndPassword([]byte(UserPass), []byte(LoginPass))
if err != nil {
return false
}
return true
return err == nil
}
// encryptPassword
// The password does irreversible encryption.
func (us *UserService) encryptPassword(ctx context.Context, Pass string) (string, error) {
hashPwd, err := bcrypt.GenerateFromPassword([]byte(Pass), bcrypt.DefaultCost)
//This encrypted string can be saved to the database and can be used as password matching verification
// This encrypted string can be saved to the database and can be used as password matching verification
return string(hashPwd), err
}
// UserChangeEmailSendCode user change email verification
func (us *UserService) UserChangeEmailSendCode(ctx context.Context, req *schema.UserChangeEmailSendCodeReq) error {
_, exist, err := us.userRepo.GetByUserID(ctx, req.UserID)
userInfo, exist, err := us.userRepo.GetByUserID(ctx, req.UserID)
if err != nil {
return err
}
@ -513,12 +513,17 @@ func (us *UserService) UserChangeEmailSendCode(ctx context.Context, req *schema.
UserID: req.UserID,
}
code := uuid.NewString()
verifyEmailUrl := fmt.Sprintf("%s/users/confirm-new-email?code=%s", us.serviceConfig.WebHost, code)
title, body, err := us.emailService.ChangeEmailTemplate(ctx, verifyEmailUrl)
var title, body string
verifyEmailURL := fmt.Sprintf("%s/users/confirm-new-email?code=%s", us.serviceConfig.WebHost, code)
if userInfo.MailStatus == entity.EmailStatusToBeVerified {
title, body, err = us.emailService.RegisterTemplate(ctx, verifyEmailURL)
} else {
title, body, err = us.emailService.ChangeEmailTemplate(ctx, verifyEmailURL)
}
if err != nil {
return err
}
log.Infof("send email confirmation %s", verifyEmailUrl)
log.Infof("send email confirmation %s", verifyEmailURL)
go us.emailService.Send(context.Background(), req.Email, title, body, code, data.ToJSONString())
return nil
@ -529,7 +534,7 @@ func (us *UserService) UserChangeEmailVerify(ctx context.Context, content string
data := &schema.EmailCodeContent{}
err = data.FromJSONString(content)
if err != nil {
return errors.BadRequest(reason.EmailVerifyUrlExpired)
return errors.BadRequest(reason.EmailVerifyURLExpired)
}
_, exist, err := us.userRepo.GetByEmail(ctx, data.Email)

View File

@ -67,7 +67,7 @@ func (as *VoteService) VoteUp(ctx context.Context, dto *schema.VoteDTO) (voteRes
var objectUserID string
objectUserID, err = as.GetObjectUserId(ctx, dto.ObjectID)
objectUserID, err = as.GetObjectUserID(ctx, dto.ObjectID)
if err != nil {
return
}
@ -91,7 +91,7 @@ func (as *VoteService) VoteDown(ctx context.Context, dto *schema.VoteDTO) (voteR
var objectUserID string
objectUserID, err = as.GetObjectUserId(ctx, dto.ObjectID)
objectUserID, err = as.GetObjectUserID(ctx, dto.ObjectID)
if err != nil {
return
}
@ -109,7 +109,7 @@ func (as *VoteService) VoteDown(ctx context.Context, dto *schema.VoteDTO) (voteR
}
}
func (vs *VoteService) GetObjectUserId(ctx context.Context, objectID string) (userID string, err error) {
func (vs *VoteService) GetObjectUserID(ctx context.Context, objectID string) (userID string, err error) {
var objectKey string
objectKey, err = obj.GetObjectTypeStrByObjectID(objectID)
@ -162,7 +162,8 @@ func (vs *VoteService) ListUserVotes(ctx context.Context, req schema.GetVoteWith
)
for _, typeKey := range typeKeys {
t, err := vs.configRepo.GetConfigType(typeKey)
var t int
t, err = vs.configRepo.GetConfigType(typeKey)
if err != nil {
continue
}
@ -175,7 +176,8 @@ func (vs *VoteService) ListUserVotes(ctx context.Context, req schema.GetVoteWith
}
for _, voteInfo := range voteList {
objInfo, err := vs.objectService.GetInfo(ctx, voteInfo.ObjectID)
var objInfo *schema.SimpleObjectInfo
objInfo, err = vs.objectService.GetInfo(ctx, voteInfo.ObjectID)
if err != nil {
log.Error(err)
}

View File

@ -3,12 +3,10 @@ package checker
import "unicode"
func IsChinese(str string) bool {
var count int
for _, v := range str {
if unicode.Is(unicode.Han, v) {
count++
break
return true
}
}
return count > 0
return false
}

View File

@ -30,7 +30,7 @@ func CheckPassword(minLength, maxLength, minLevel int, pwd string) error {
// The password strength level is initialized to D.
// The regular is used to verify the password strength.
// If the matching is successful, the password strength increases by 1
var level int = levelD
level := levelD
patternList := []string{`[0-9]+`, `[a-z]+`, `[A-Z]+`, `[~!@#$%^&*?_-]+`}
for _, pattern := range patternList {
match, _ := regexp.MatchString(pattern, pwd)
@ -41,7 +41,7 @@ func CheckPassword(minLength, maxLength, minLevel int, pwd string) error {
// If the final password strength falls below the required minimum strength, return with an error
if level < minLevel {
return fmt.Errorf("The password does not satisfy the current policy requirements. ")
return fmt.Errorf("the password does not satisfy the current policy requirements")
}
return nil
}

View File

@ -15,7 +15,7 @@ type SnowFlakeID struct {
var snowFlakeIDGenerator *SnowFlakeID
func init() {
//todo
// todo
rand.Seed(time.Now().UnixNano())
node, err := snowflake.NewNode(int64(rand.Intn(1000)) + 1)
if err != nil {

View File

@ -3,5 +3,6 @@
"tabWidth": 2,
"singleQuote": true,
"jsxBracketSameLine": true,
"printWidth": 80
}
"printWidth": 80,
"endOfLine": "auto"
}

View File

@ -25,8 +25,7 @@ module.exports = {
const config = configFunction(proxy, allowedHost);
config.proxy = {
'/answer': {
target: "http://10.0.20.84:8080",
// target: 'http://10.0.10.98:2060',
target: 'http://10.0.10.98:2060',
changeOrigin: true,
secure: false,
},

View File

@ -40,6 +40,7 @@
"katex": "^0.16.2",
"lodash": "^4.17.21",
"marked": "^4.0.19",
"md5": "^2.3.0",
"mermaid": "^9.1.7",
"next-share": "^0.18.1",
"qs": "^6.11.0",

View File

@ -57,6 +57,7 @@ specifiers:
lint-staged: ^13.0.3
lodash: ^4.17.21
marked: ^4.0.19
md5: ^2.3.0
mermaid: ^9.1.7
next-share: ^0.18.1
postcss: ^8.0.0
@ -96,6 +97,7 @@ dependencies:
katex: 0.16.2
lodash: 4.17.21
marked: 4.1.0
md5: 2.3.0
mermaid: 9.1.7
next-share: 0.18.1_lbqamd2wfmenkveygahn4wdfcq
qs: 6.11.0
@ -152,7 +154,7 @@ devDependencies:
prettier: 2.7.1
purgecss-webpack-plugin: 4.1.3
react-app-rewired: 2.2.1_react-scripts@5.0.1
react-scripts: 5.0.1_r727nmttzgvwuocpb6eyxi2m5i
react-scripts: 5.0.1_vcopaw66ubzwqe5wj7m4edgwnq
sass: 1.54.9
tsconfig-paths-webpack-plugin: 4.0.0
typescript: 4.8.3
@ -3660,6 +3662,10 @@ packages:
resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
dev: true
/charenc/0.0.2:
resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==}
dev: false
/check-types/11.1.2:
resolution: {integrity: sha512-tzWzvgePgLORb9/3a0YenggReLKAIb2owL03H2Xdoe5pKcUyWRSEQ8xfCar8t2SIAuEDwtmx2da1YB52YuHQMQ==}
@ -4060,8 +4066,8 @@ packages:
engines: {node: '>=10'}
hasBin: true
dependencies:
is-text-path: 1.0.1
JSONStream: 1.3.5
is-text-path: 1.0.1
lodash: 4.17.21
meow: 8.1.2
split2: 3.2.2
@ -4157,6 +4163,10 @@ packages:
shebang-command: 2.0.0
which: 2.0.2
/crypt/0.0.2:
resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==}
dev: false
/crypto-random-string/2.0.0:
resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==}
engines: {node: '>=8'}
@ -6807,6 +6817,10 @@ packages:
call-bind: 1.0.2
has-tostringtag: 1.0.0
/is-buffer/1.1.6:
resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==}
dev: false
/is-callable/1.2.6:
resolution: {integrity: sha512-krO72EO2NptOGAX2KYyqbP9vYMlNAXdB53rq6f8LXY6RY7JdSR/3BD6wLUlPHSAesmY9vstNrjvqGaCiRK/91Q==}
engines: {node: '>= 0.4'}
@ -7966,6 +7980,14 @@ packages:
hasBin: true
dev: false
/md5/2.3.0:
resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==}
dependencies:
charenc: 0.0.2
crypt: 0.0.2
is-buffer: 1.1.6
dev: false
/mdn-data/2.0.14:
resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==}
@ -8167,7 +8189,7 @@ packages:
jsonp: 0.2.1
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
react-scripts: 5.0.1_r727nmttzgvwuocpb6eyxi2m5i
react-scripts: 5.0.1_vcopaw66ubzwqe5wj7m4edgwnq
transitivePeerDependencies:
- supports-color
dev: false
@ -9497,7 +9519,7 @@ packages:
peerDependencies:
react-scripts: '>=2.1.3'
dependencies:
react-scripts: 5.0.1_r727nmttzgvwuocpb6eyxi2m5i
react-scripts: 5.0.1_vcopaw66ubzwqe5wj7m4edgwnq
semver: 5.7.1
dev: true
@ -9531,6 +9553,12 @@ packages:
/react-dev-utils/12.0.1_npfwkgbcmgrbevrxnqgustqabe:
resolution: {integrity: sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==}
engines: {node: '>=14'}
peerDependencies:
typescript: '>=2.7'
webpack: '>=4'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@babel/code-frame': 7.18.6
address: 1.2.1
@ -9561,9 +9589,7 @@ packages:
transitivePeerDependencies:
- eslint
- supports-color
- typescript
- vue-template-compiler
- webpack
/react-dom/18.2.0_react@18.2.0:
resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
@ -9655,11 +9681,12 @@ packages:
react: 18.2.0
dev: false
/react-scripts/5.0.1_r727nmttzgvwuocpb6eyxi2m5i:
/react-scripts/5.0.1_vcopaw66ubzwqe5wj7m4edgwnq:
resolution: {integrity: sha512-8VAmEm/ZAwQzJ+GOMLbBsTdDKOpuZh7RPs0UymvBR2vRk4iZWCskjbFnxqjrzoIvlNNRZ3QJFx6/qDSi6zSnaQ==}
engines: {node: '>=14.0.0'}
hasBin: true
peerDependencies:
eslint: '*'
react: '>= 16'
typescript: ^3.2.1 || ^4
peerDependenciesMeta:
@ -9708,7 +9735,7 @@ packages:
semver: 7.3.7
source-map-loader: 3.0.1_webpack@5.74.0
style-loader: 3.3.1_webpack@5.74.0
tailwindcss: 3.1.8
tailwindcss: 3.1.8_postcss@8.4.16
terser-webpack-plugin: 5.3.6_webpack@5.74.0
typescript: 4.8.3
webpack: 5.74.0
@ -10686,10 +10713,12 @@ packages:
/symbol-tree/3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
/tailwindcss/3.1.8:
/tailwindcss/3.1.8_postcss@8.4.16:
resolution: {integrity: sha512-YSneUCZSFDYMwk+TGq8qYFdCA3yfBRdBlS7txSq0LUmzyeqRe3a8fBQzbz9M3WS/iFT4BNf/nmw9mEzrnSaC0g==}
engines: {node: '>=12.13.0'}
hasBin: true
peerDependencies:
postcss: ^8.0.9
dependencies:
arg: 5.0.2
chokidar: 3.5.3

View File

@ -2,6 +2,7 @@ export interface FormValue<T = any> {
value: T;
isInvalid: boolean;
errorMsg: string;
[prop: string]: any;
}
export interface FormDataType {
@ -89,7 +90,7 @@ export interface ModifyPasswordReq {
export interface ModifyUserReq {
display_name: string;
username?: string;
avatar: string;
avatar: any;
bio: string;
bio_html?: string;
location: string;
@ -97,7 +98,7 @@ export interface ModifyUserReq {
}
export interface UserInfoBase {
avatar: string;
avatar: any;
username: string;
display_name: string;
rank: number;

View File

@ -6,15 +6,28 @@ import DefaultAvatar from '@/assets/images/default-avatar.svg';
interface IProps {
/** avatar url */
avatar: string;
avatar: string | { type: string; gravatar: string; custom: string };
/** size 48 96 128 256 */
size: string;
searchStr?: string;
className?: string;
}
const Index: FC<IProps> = ({ avatar, size, className }) => {
const Index: FC<IProps> = ({ avatar, size, className, searchStr = '' }) => {
let url = '';
if (typeof avatar === 'string') {
if (avatar.length > 1) {
url = `${avatar}?${searchStr}`;
}
} else if (avatar?.type === 'gravatar') {
url = `${avatar.gravatar}?${searchStr}&d=identicon`;
} else if (avatar?.type === 'custom') {
url = `${avatar.custom}?${searchStr}`;
}
return (
<img
src={avatar || DefaultAvatar}
src={url || DefaultAvatar}
width={size}
height={size}
className={classNames('rounded', className)}

View File

@ -9,6 +9,7 @@ interface Props {
data: any;
showAvatar?: boolean;
avatarSize?: string;
avatarSearchStr?: string;
className?: string;
}
@ -17,20 +18,31 @@ const Index: FC<Props> = ({
showAvatar = true,
avatarSize = '20px',
className = 'fs-14',
avatarSearchStr = 's=48',
}) => {
return (
<div className={`text-secondary ${className}`}>
{data?.status !== 'deleted' ? (
<Link to={`/users/${data?.username}`}>
{showAvatar && (
<Avatar avatar={data?.avatar} size={avatarSize} className="me-1" />
<Avatar
avatar={data?.avatar}
size={avatarSize}
className="me-1"
searchStr={avatarSearchStr}
/>
)}
<span className="me-1 text-break">{data?.display_name}</span>
</Link>
) : (
<>
{showAvatar && (
<Avatar avatar={data?.avatar} size={avatarSize} className="me-1" />
<Avatar
avatar={data?.avatar}
size={avatarSize}
className="me-1"
searchStr={avatarSearchStr}
/>
)}
<span className="me-1 text-break">{data?.display_name}</span>
</>

View File

@ -19,7 +19,9 @@ const Form = ({ userName, onSendReply, onCancel, mode }) => {
return (
<div className="mb-2">
<div className="fs-14 mb-2">Reply to {userName}</div>
<div className="fs-14 mb-2">
{t('reply_to')} {userName}
</div>
<div className="d-flex mb-1 align-items-start flex-column flex-md-row">
<div>
<Mentions

View File

@ -43,7 +43,7 @@ const Index: FC<Props> = ({ redDot, userInfo, logOut }) => {
id="dropdown-basic"
as="a"
className="no-toggle pointer">
<Avatar size="36px" avatar={userInfo?.avatar} />
<Avatar size="36px" avatar={userInfo?.avatar} searchStr="s=96" />
</Dropdown.Toggle>
<Dropdown.Menu>

View File

@ -41,7 +41,7 @@ const QuestionLastUpdate = ({ q }) => {
<FormatTime
time={q.update_time}
className="text-secondary mx-1"
className="text-secondary ms-1"
preFix={t('answered')}
/>
</div>
@ -60,7 +60,7 @@ const QuestionLastUpdate = ({ q }) => {
<FormatTime
time={q.edit_time}
className="text-secondary mx-1"
className="text-secondary ms-1"
preFix={t('modified')}
/>
</div>
@ -75,7 +75,7 @@ const QuestionLastUpdate = ({ q }) => {
<FormatTime
time={q.create_time}
preFix={t('asked')}
className="text-secondary mx-1"
className="text-secondary ms-1"
/>
</div>
);
@ -136,7 +136,7 @@ const QuestionList: FC<Props> = ({ source }) => {
<div className="ms-0 ms-md-3 mt-2 mt-md-0">
<span>
<Icon name="hand-thumbs-up-fill" />
<em className="fst-normal mx-1">{li.vote_count}</em>
<em className="fst-normal ms-1">{li.vote_count}</em>
</span>
<span
className={`ms-3 ${
@ -149,11 +149,11 @@ const QuestionList: FC<Props> = ({ source }) => {
: 'chat-square-text-fill'
}
/>
<em className="fst-normal mx-1">{li.answer_count}</em>
<em className="fst-normal ms-1">{li.answer_count}</em>
</span>
<span className="summary-stat ms-3">
<Icon name="eye-fill" />
<em className="fst-normal mx-1">{li.view_count}</em>
<em className="fst-normal ms-1">{li.view_count}</em>
</span>
</div>
</div>

View File

@ -1,6 +1,7 @@
import React, { useState, useEffect } from 'react';
import { Button, Col } from 'react-bootstrap';
import { Trans, useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { resendEmail, checkImgCode } from '@answer/api';
import { PicAuthCodeModal } from '@answer/components/Modal';
@ -120,6 +121,9 @@ const Index: React.FC<IProps> = ({ visible = false }) => {
<Button variant="link" onClick={onSentEmail}>
{t('btn_name')}
</Button>
<Link to="/users/change-email" replace className="btn btn-link ms-2">
{t('change_btn_name')}
</Link>
</>
)}

View File

@ -23,16 +23,23 @@ const Index: FC<Props> = ({ data, time, preFix, className = '' }) => {
avatar={data?.avatar}
size="40px"
className="me-2 d-none d-md-block"
searchStr="s=96"
/>
<Avatar
avatar={data?.avatar}
size="24px"
className="me-2 d-block d-md-none"
searchStr="s=48"
/>
</Link>
) : (
<Avatar avatar={data?.avatar} size="40px" className="me-2" />
<Avatar
avatar={data?.avatar}
size="40px"
className="me-2"
searchStr="s=96"
/>
)}
<div className="fs-14 text-secondary d-flex flex-row flex-md-column align-items-center align-items-md-start">
<div className="me-1 me-md-0">

View File

@ -27,7 +27,8 @@
"account_activation": "Account Activation",
"confirm_email": "Confirm Email",
"account_suspended": "Account Suspended",
"admin": "Admin"
"admin": "Admin",
"change_email": "Modify Email"
},
"notifications": {
"title": "Notifications",
@ -250,7 +251,7 @@
"synonyms_text": "The following tags will be remapped to",
"delete": {
"title": "Delete this tag",
"content": "<p>We do not allowed deleting tag with posts.</p><p>Please remove the python tag from the posts first.</p>",
"content": "<p>We do not allowed deleting tag with posts.</p><p>Please remove this tag from the posts first.</p>",
"content2": "Are you sure you wish to delete?",
"close": "Close"
}
@ -403,7 +404,7 @@
}
},
"footer": {
"build_on": "Build on <1> Answer </1>- the open-source software that power Q&A communities.<br />Made with love. © 2022 Answer."
"build_on": "Built on <1> Answer </1>- the open-source software that power Q&A communities<br />Made with love © 2022 Answer"
},
"upload_img": {
"name": "Change",
@ -421,6 +422,7 @@
"info": "If it doesn't arrive, check your spam folder.",
"another": "We sent another activation email to you at <bold>{{mail}}</bold>. It might take a few minutes for it to arrive; be sure to check your spam folder.",
"btn_name": "Resend activation email",
"change_btn_name": "Change email",
"msg": {
"empty": "Cannot be empty."
}
@ -462,6 +464,18 @@
}
}
},
"change_email": {
"page_title": "Welcome to Answer",
"btn_cancel": "Cancel",
"btn_update": "Update email address",
"send_success": "If an account matches <strong>{{mail}}</strong>, you should receive an email with instructions on how to reset your password shortly.",
"email": {
"label": "New Email",
"msg": {
"empty": "Email cannot be empty."
}
}
},
"password_reset": {
"page_title": "Password Reset",
"btn_name": "Reset my password",
@ -504,7 +518,12 @@
},
"avatar": {
"label": "Profile Image",
"text": "You can upload your image or <1>reset</1> it to"
"gravatar": "Gravatar",
"gravatar_text": "You can change image on <1>gravatar.com</1>",
"custom": "Custom",
"btn_refresh": "Refresh",
"custom_text": "You can upload your image.",
"default": "Default"
},
"bio": {
"label": "About Me (optional)"
@ -572,6 +591,8 @@
"update": "Modified",
"edit": "edited",
"Views": "Viewed",
"Follow": "Follow",
"Following": "Following",
"answered": "answered",
"closed_in": "Closed in",
"show_exist": "Show existing question.",

View File

@ -1,45 +1,440 @@
{
"how_to_format": {
"title": "如何设定文本格式",
"description": "<ul class=\"mb-0\"><li><p class=\"mb-2\">添加链接:</p><pre class=\"mb-2\"><code>&lt;https://url.com&gt;<br/><br/>[标题](https://url.com)</code></pre></li><li><p class=\"mb-2\">段落之间使用空行分隔</p></li><li><p class=\"mb-2\"><em>_斜体_</em> 或者 **<strong>粗体</strong>**</p></li><li><p class=\"mb-2\">使用 4 个空格缩进代码</p></li><li><p class=\"mb-2\">在行首添加<code>&gt;</code>表示引用</p></li><li><p class=\"mb-2\">反引号进行转义 <code>`像 _这样_`</code></p></li><li><p class=\"mb-2\">使用<code>```</code>创建代码块</p><pre class=\"mb-0\"><code>```<br/>// 这是代码<br/>```</code></pre></li></ul>"
},
"pagination": {
"prev": "上一页",
"next": "下一页"
},
"page_title": {
"question": "问题",
"questions": "问题",
"tag": "标签",
"tags": "标签",
"tag_wiki": "标签 wiki",
"edit_tag": "编辑标签",
"ask_a_question": "提问题",
"edit_question": "编辑问题",
"edit_answer": "编辑回答",
"search": "搜索",
"posts_containing": "包含",
"settings": "设定",
"notifications": "通知",
"login": "登录",
"sign_up": "注册",
"account_recovery": "账号恢复",
"account_activation": "账号激活",
"confirm_email": "确认电子邮件",
"account_suspended": "账号已封禁",
"admin": "后台管理"
},
"notifications": {
"title": "通知",
"inbox": "收件箱",
"achievement": "成就",
"all_read": "全部标记为已读",
"show_more": "显示更多"
},
"suspended": {
"title": "账号已封禁",
"until_time": "你的账号被封禁至{{ time }}。",
"forever": "你的账号已被永久封禁。",
"end": "违反了我们的社区准则。"
},
"editor": {
"blockquote": {
"text": "引用"
},
"bold": {
"text": "粗体"
},
"chart": {
"text": "图表",
"flow_chart": "流程图",
"sequence_diagram": "时序图",
"class_diagram": "类图",
"state_diagram": "状态图",
"entity_relationship_diagram": "ER 图",
"user_defined_diagram": "User defined diagram",
"gantt_chart": "甘特图",
"pie_chart": "饼图"
},
"code": {
"text": "代码块",
"add_code": "添加代码块",
"form": {
"fields": {
"code": {
"label": "代码块",
"msg": {
"empty": "代码块不能为空"
}
},
"language": {
"label": "语言 (可选)",
"placeholder": "自动识别"
}
}
},
"btn_cancel": "取消",
"btn_confirm": "添加"
},
"formula": {
"text": "公式",
"options": {
"inline": "行内公式",
"block": "公式块"
}
},
"heading": {
"text": "标题",
"options": {
"h1": "标题 1",
"h2": "标题 2",
"h3": "标题 3",
"h4": "标题 4",
"h5": "标题 5",
"h6": "标题 6"
}
},
"help": {
"text": "帮助"
},
"hr": {
"text": "水平分割线"
},
"image": {
"text": "图片",
"add_image": "添加图片",
"tab_image": "上传图片",
"form_image": {
"fields": {
"file": {
"label": "图片文件",
"btn": "选择图片",
"msg": {
"empty": "请选择图片文件。",
"only_image": "只能上传图片文件。",
"max_size": "图片文件大小不能超过 4 MB。"
}
},
"description": {
"label": "图片描述(可选)"
}
}
},
"tab_url": "网络图片",
"form_url": {
"fields": {
"url": {
"label": "图片地址",
"msg": {
"empty": "图片地址不能为空"
}
},
"name": {
"label": "图片描述(可选)"
}
}
},
"btn_cancel": "取消",
"btn_confirm": "添加",
"uploading": "上传中..."
},
"indent": {
"text": "添加缩进"
},
"outdent": {
"text": "减少缩进"
},
"italic": {
"text": "斜体"
},
"link": {
"text": "超链接",
"add_link": "添加超链接",
"form": {
"fields": {
"url": {
"label": "链接",
"msg": {
"empty": "链接不能为空。"
}
},
"name": {
"label": "链接描述(可选)"
}
}
},
"btn_cancel": "取消",
"btn_confirm": "添加"
},
"ordered_list": {
"text": "有编号列表"
},
"unordered_list": {
"text": "无编号列表"
},
"table": {
"text": "表格",
"heading": "表头",
"cell": "单元格"
}
},
"close_modal": {
"title": "关闭原因是...",
"btn_cancel": "取消",
"btn_submit": "提交",
"remark": {
"empty": "不能为空。"
},
"msg": {
"empty": "请选择一个原因。"
}
},
"report_modal": {
"flag_title": "举报原因是...",
"close_title": "关闭原因是...",
"review_question_title": "审查问题",
"review_answer_title": "审查回答",
"review_comment_title": "审查评论",
"btn_cancel": "取消",
"btn_submit": "提交",
"remark": {
"empty": "不能为空"
},
"msg": {
"empty": "请选择一个原因。"
}
},
"tag_modal": {
"title": "创建新标签",
"form": {
"fields": {
"display_name": {
"label": "显示名称(别名)",
"msg": {
"empty": "不能为空",
"range": "不能超过 35 个字符"
}
},
"slug_name": {
"label": "URL 固定链接",
"description": "必须由 \"a-z\", \"0-9\", \"+ # - .\" 组成",
"msg": {
"empty": "不能为空",
"range": "不能超过 35 个字符",
"character": "包含非法字符"
}
},
"description": {
"label": "标签描述(可选)"
}
}
},
"btn_cancel": "取消",
"btn_submit": "提交"
},
"tag_info": {
"created_at": "创建于",
"edited_at": "编辑于",
"synonyms": {
"title": "同义词",
"text": "以下标签等同于",
"empty": "此标签目前没有同义词。",
"btn_add": "添加同义词",
"btn_edit": "编辑",
"btn_save": "保存"
},
"synonyms_text": "以下标签等同于",
"delete": {
"title": "删除标签",
"content": "<p>不允许删除有关联问题的标签。</p><p>请先从关联的问题中删除此标签的引用。</p>",
"content2": "确定要删除吗?",
"close": "关闭"
}
},
"edit_tag": {
"title": "编辑标签",
"default_reason": "编辑标签",
"form": {
"fields": {
"revision": {
"label": "编辑历史"
},
"display_name": {
"label": "名称"
},
"slug_name": {
"label": "URL 固定链接",
"info": "必须由 \"a-z\", \"0-9\", \"+ # - .\" 组成"
},
"description": {
"label": "描述"
},
"edit_summary": {
"label": "编辑概要",
"placeholder": "简单描述更改原因 (错别字、文字表达、格式等等)"
}
}
},
"btn_save_edits": "保存更改",
"btn_cancel": "取消"
},
"dates": {
"long_date": "YYYY年MM月",
"long_date_with_year": "YYYY年MM月DD日",
"long_date_with_time": "YYYY年MM月DD日 HH:mm",
"now": "刚刚",
"x_seconds_ago": "{{count}} 秒前",
"x_minutes_ago": "{{count}} 分钟前",
"x_hours_ago": "{{count}} 小时前"
},
"comment": {
"btn_add_comment": "添加评论",
"reply_to": "回复",
"btn_reply": "回复",
"btn_edit": "编辑",
"btn_delete": "删除",
"btn_flag": "举报",
"btn_save_edits": "保存",
"btn_cancel": "取消",
"show_more": "显示更多评论",
"tip_question": "使用评论提问更多信息或者提出改进意见。尽量避免使用评论功能回答问题。",
"tip_answer": "使用评论对回答者进行回复,或者通知回答者你已更新了问题的内容。如果要补充或者完善问题的内容,请在原问题中更改。"
},
"edit_answer": {
"title": "编辑回答",
"default_reason": "编辑回答",
"form": {
"fields": {
"revision": {
"label": "编辑历史"
},
"answer": {
"label": "回答内容"
},
"edit_summary": {
"label": "编辑概要",
"placeholder": "简单描述更改原因 (错别字、文字表达、格式等等)"
}
}
},
"btn_save_edits": "保存更改",
"btn_cancel": "取消"
},
"tags": {
"title": "标签",
"sort_buttons": {
"popular": "热门",
"name": "名称",
"newest": "最新"
},
"button_follow": "关注",
"button_following": "已关注",
"tag_label": "个问题",
"search_placeholder": "通过标签名过滤",
"no_description": "此标签无描述。",
"more": "更多"
},
"ask": {
"title": "提交新的问题",
"edit_title": "编辑问题",
"default_reason": "编辑问题",
"similar_questions": "相似的问题",
"form": {
"fields": {
"revision": {
"label": "编辑历史"
},
"title": {
"label": "标题",
"placeholder": "请详细描述你的问题",
"msg": {
"empty": "标题不能为空",
"range": "标题最多 150 个字符"
}
},
"body": {
"label": "内容",
"msg": {
"empty": "内容不能为空"
}
},
"tags": {
"label": "标签",
"msg": {
"empty": "必须选择一个标签"
}
},
"answer": {
"label": "回答内容",
"msg": {
"empty": "回答内容不能为空"
}
}
}
},
"btn_post_question": "提交问题",
"btn_save_edits": "保存更改",
"answer_question": "直接发表回答",
"post_question&answer": "提交问题和回答"
},
"tag_selector": {
"add_btn": "添加标签",
"create_btn": "创建新标签",
"search_tag": "搜索标签",
"hint": "选择至少一个与问题相关的标签。",
"no_result": "没有匹配的标签"
},
"header": {
"nav": {
"question": "问题",
"tag": "标签",
"user": "用户",
"profile": "个人主页",
"setting": "个人设置",
"logout": "退出登录"
"profile": "用户主页",
"setting": "账号设置",
"logout": "退出登录",
"admin": "后台管理"
},
"search": {
"placeholder": "搜索"
},
"add_question": "提问题"
}
},
"footer": {
"build_on": "Built on <1> Answer </1>- the open-source software that power Q&A communities<br />Made with love © 2022 Answer"
},
"upload_img": {
"name": "更改图片",
"loading": "加载中..."
},
"pic_auth_code": {
"btn_name": "验证",
"title": "验证码",
"placeholder": "输入上面的文字",
"placeholder": "输入图片中的文字",
"msg": {
"empty": "不能为空"
}
},
"inactive": {
"first": "你几乎已经完成!我们发送了一封激活邮件到 <strong>{{mail}}</strong>. 请按照邮件中的说明激活您的帐户。",
"first": "马上就好了!我们发送了一封激活邮件到 <bold>{{mail}}</bold>。请按照邮件中的说明激活您的帐户。",
"info": "如果没有收到,请检查您的垃圾邮件文件夹。",
"another": "我们向您发送了另一封激活电子邮件,地址为 <strong>{{mail}}</strong>. 它可能需要几分钟才能到达;请务必检查您的垃圾邮件文件夹。",
"btn_name": "重新发送激活电邮",
"another": "我们向您发送了另一封激活电子邮件,地址为 <bold>{{mail}}</bold>。它可能需要几分钟才能到达;请务必检查您的垃圾邮件文件夹。",
"btn_name": "重新发送激活",
"msg": {
"empty": "不能为空"
}
},
"login": {
"page_title": "欢迎来到 Answer",
"btn_name": "登录",
"info_sign": "没有帐户?<1>注册</1>",
"info_login": "已经有一个帐户?<1>登录</1>",
"forgot_pass": "忘记密码?",
"name": {
"label": "昵称",
"msg": {
"empty": "昵称不能为空"
"empty": "昵称不能为空",
"range": "昵称最多 30 个字符"
}
},
"email": {
@ -56,7 +451,464 @@
}
}
},
"footer": {
"build_on": "由 <1> Answer </1>构建- 为知识社区提供动力的开源软件.<br />Made with love. © 2022 Answer ."
"account_forgot": {
"page_title": "忘记密码",
"btn_name": "发送恢复邮件",
"send_success": "如无意外,你的邮箱 <strong>{{mail}}</strong> 将会收到一封重置密码的邮件,请根据指引重置你的密码。",
"email": {
"label": "邮箱",
"msg": {
"empty": "邮箱不能为空"
}
}
},
"password_reset": {
"page_title": "密码重置",
"btn_name": "重置我的密码",
"reset_success": "你已经成功更改密码,将返回登录页面",
"link_invalid": "抱歉,此密码重置链接已失效。也许是你已经重置过密码了?",
"to_login": "前往登录页面",
"password": {
"label": "密码",
"msg": {
"empty": "密码不能为空",
"length": "密码长度在8-32个字符之间",
"different": "两次输入密码不一致"
}
},
"password_confirm": {
"label": "确认新密码"
}
},
"settings": {
"page_title": "设置",
"nav": {
"profile": "我的资料",
"notification": "通知",
"account": "账号",
"interface": "界面"
},
"profile": {
"btn_name": "保存更改",
"display_name": {
"label": "昵称",
"msg": "昵称不能为空",
"msg_range": "昵称不能超过 30 个字符"
},
"username": {
"label": "用户名",
"caption": "用户之间可以通过 \"@用户名\" 进行交互。",
"msg": "用户名不能为空",
"msg_range": "用户名不能超过 30 个字符",
"character": "用户名只能由 \"a-z\", \"0-9\", \" - . _\" 组成"
},
"avatar": {
"label": "头像",
"text": "您可以上传图片作为头像,也可以 <1>重置</1> 为"
},
"bio": {
"label": "关于我 (可选)"
},
"website": {
"label": "网站 (可选)",
"placeholder": "https://example.com",
"msg": "格式不正确"
},
"location": {
"label": "位置 (可选)",
"placeholder": "城市, 国家"
}
},
"notification": {
"email": {
"label": "邮件通知",
"radio": "你的提问有新的回答,评论,和其他"
}
},
"account": {
"change_email_btn": "更改邮箱",
"change_pass_btn": "更改密码",
"change_email_info": "邮件已发送。请根据指引完成验证。",
"email": {
"label": "邮箱",
"msg": "邮箱不能为空"
},
"password_title": "密码",
"current_pass": {
"label": "当前密码",
"msg": {
"empty": "当前密码不能为空",
"length": "密码长度必须在 8 至 32 之间",
"different": "两次输入的密码不匹配"
}
},
"new_pass": {
"label": "新密码"
},
"pass_confirm": {
"label": "确认新密码"
}
},
"interface": {
"lang": {
"label": "界面语言",
"text": "设置用户界面语言,在刷新页面后生效。"
}
}
},
"toast": {
"update": "更新成功",
"update_password": "更改密码成功。",
"flag_success": "感谢您的标记,我们会尽快处理。"
},
"related_question": {
"title": "相关问题",
"btn": "我要提问",
"answers": "个回答"
},
"question_detail": {
"Asked": "提问于",
"asked": "提问于",
"update": "修改于",
"edit": "最后编辑于",
"Views": "阅读次数",
"Follow": "关注此问题",
"Following": "已关注",
"answered": "回答于",
"closed_in": "关闭于",
"show_exist": "查看相关问题。",
"answers": {
"title": "个回答",
"score": "评分",
"newest": "最新",
"btn_accept": "采纳",
"btn_accepted": "已被采纳"
},
"write_answer": {
"title": "你的回答",
"btn_name": "提交你的回答",
"confirm_title": "继续回答",
"continue": "继续",
"confirm_info": "<p>您确定要提交一个新的回答吗?</p><p>您可以直接编辑和改善您之前的回答的。</p>",
"empty": "回答内容不能为空。"
}
},
"delete": {
"title": "删除",
"question": "我们不建议<strong>删除有回答的帖子</strong>。因为这样做会使得后来的读者无法从该问题中获得帮助。</p><p>如果删除过多有回答的帖子,你的账号将会被禁止提问。你确定要删除吗?",
"answer_accepted": "<p>我们不建议<strong>删除被采纳的回答</strong>。因为这样做会使得后来的读者无法从该回答中获得帮助。</p>如果删除过多被采纳的回答,你的账号将会被禁止回答任何提问。你确定要删除吗?",
"other": "你确定要删除?",
"tip_question_deleted": "此问题已被删除",
"tip_answer_deleted": "此回答已被删除"
},
"btns": {
"confirm": "确认",
"cancel": "取消",
"save": "保存",
"delete": "删除",
"login": "登录",
"signup": "注册",
"logout": "退出登录",
"verify": "验证",
"add_question": "我要提问"
},
"search": {
"title": "搜索结果",
"keywords": "关键词",
"options": "选项",
"follow": "关注",
"following": "已关注",
"counts": "{{count}} 个结果",
"more": "更多",
"sort_btns": {
"relevance": "相关性",
"newest": "最新的",
"active": "活跃的",
"score": "评分"
},
"tips": {
"title": "高级搜索提示",
"tag": "<1>[tag]</1> 在指定标签中搜索",
"user": "<1>user:username</1> 根据作者搜索",
"answer": "<1>answers:0</1> 搜索未回答的问题",
"score": "<1>score:3</1> 评分 3 分或以上",
"question": "<1>is:question</1> 只搜索问题",
"is_answer": "<1>is:answer</1> 只搜索回答"
},
"empty": "找不到任何相关的内容。<br /> 请尝试其他关键字,或者减少查找内容的长度。"
},
"share": {
"name": "分享",
"copy": "复制链接",
"via": "分享在...",
"copied": "已复制",
"facebook": "分享到 Facebook",
"twitter": "分享到 Twitter"
},
"cannot_vote_for_self": "不能给自己投票",
"modal_confirm": {
"title": "发生错误..."
},
"account_result": {
"page_title": "欢迎来到 Answer",
"success": "你的账号已通过验证,即将返回首页。",
"link": "返回首页",
"invalid": "抱歉,此验证链接已失效。也许是你的账号已经通过验证了?",
"confirm_new_email": "你的电子邮箱已更新",
"confirm_new_email_invalid": "抱歉,此验证链接已失效。也许是你的邮箱已经成功更改了?"
},
"question": {
"following_tags": "已关注的标签",
"edit": "编辑",
"save": "保存",
"follow_tag_tip": "按照标签整理您的问题列表。",
"hot_questions": "热点问题",
"all_questions": "全部问题",
"x_questions": "{{ count }} 个问题",
"x_answers": "{{ count }} 个回答",
"questions": "个问题",
"answers": "回答",
"newest": "最新",
"active": "活跃",
"frequent": "浏览量",
"score": "评分",
"unanswered": "未回答",
"modified": "修改于",
"answered": "回答于",
"asked": "提问于",
"closed": "已关闭",
"follow_a_tag": "关注一个标签",
"more": "更多"
},
"personal": {
"overview": "概览",
"answers": "回答",
"answer": "回答",
"questions": "问题",
"question": "问题",
"bookmarks": "收藏",
"reputation": "声望",
"comments": "评论",
"votes": "得票",
"newest": "最新",
"score": "评分",
"edit_profile": "编辑我的资料",
"visited_x_days": "Visited {{ count }} days",
"viewed": "Viewed",
"joined": "加入于",
"last_login": "上次登录",
"about_me": "关于我",
"about_me_empty": "// Hello, World !",
"top_answers": "热门回答",
"top_questions": "热门问题",
"stats": "状态",
"list_empty": "没有找到相关的内容。<br />试试看其他标签?",
"accepted": "已采纳",
"answered": "回答于",
"asked": "提问于",
"upvote": "赞同",
"downvote": "反对",
"mod_short": "管理员",
"mod_long": "管理员",
"x_reputation": "声望",
"x_votes": "得票",
"x_answers": "个回答",
"x_questions": "个问题"
},
"page_404": {
"description": "页面不存在",
"back_home": "回到主页"
},
"page_50X": {
"description": "服务器遇到了一个错误,无法完成你的请求。",
"back_home": "回到主页"
},
"admin": {
"admin_header": {
"title": "后台管理"
},
"nav_menus": {
"dashboard": "后台管理",
"contents": "内容管理",
"questions": "问题",
"answers": "回答",
"users": "用户管理",
"flags": "举报管理",
"settings": "站点设置",
"general": "一般",
"interface": "界面",
"smtp": "SMTP"
},
"dashboard": {
"title": "后台管理",
"welcome": "欢迎来到 Answer 后台管理!",
"version": "版本"
},
"flags": {
"title": "举报",
"pending": "等待处理",
"completed": "已完成",
"flagged": "被举报内容",
"created": "创建于",
"action": "操作",
"review": "审查"
},
"change_modal": {
"title": "更改用户状态为...",
"btn_cancel": "取消",
"btn_submit": "提交",
"normal_name": "正常",
"normal_description": "正常状态的用户可以提问和回答。",
"suspended_name": "封禁",
"suspended_description": "被封禁的用户将无法登录。",
"deleted_name": "删除",
"deleted_description": "删除用户的个人信息,认证等等。",
"inactive_name": "不活跃",
"inactive_description": "不活跃的用户必须重新验证邮箱。",
"confirm_title": "删除此用户",
"confirm_content": "确定要删除此用户?此操作无法撤销!",
"confirm_btn": "删除",
"msg": {
"empty": "请选择一个原因"
}
},
"status_modal": {
"title": "更改 {{ type }} 状态为...",
"normal_name": "正常",
"normal_description": "所有用户都可以访问",
"closed_name": "关闭",
"closed_description": "不能回答,但仍然可以编辑、投票和评论。",
"deleted_name": "删除",
"deleted_description": "所有获得/损失的声望将会恢复。",
"btn_cancel": "取消",
"btn_submit": "提交",
"btn_next": "下一步"
},
"users": {
"title": "用户",
"name": "名称",
"email": "邮箱",
"reputation": "声望",
"created_at": "创建时间",
"delete_at": "删除时间",
"suspend_at": "封禁时间",
"status": "状态",
"action": "操作",
"change": "更改",
"all": "全部",
"inactive": "不活跃",
"suspended": "已封禁",
"deleted": "已删除",
"normal": "正常"
},
"questions": {
"page_title": "问题",
"normal": "正常",
"closed": "已关闭",
"deleted": "已删除",
"post": "标题",
"votes": "得票数",
"answers": "回答数",
"created": "创建于",
"status": "状态",
"action": "操作",
"change": "更改"
},
"answers": {
"page_title": "回答",
"normal": "正常",
"deleted": "已删除",
"post": "标题",
"votes": "得票数",
"created": "创建于",
"status": "状态",
"action": "操作",
"change": "更改"
},
"general": {
"page_title": "一般",
"name": {
"label": "站点名称",
"msg": "不能为空",
"text": "站点的名称作为站点的标题HTML 的 title 标签)。"
},
"short_description": {
"label": "简短的站点标语 (可选)",
"msg": "不能为空",
"text": "简短的标语作为网站主页的标题HTML 的 title 标签)。"
},
"description": {
"label": "网站描述 (可选)",
"msg": "不能为空",
"text": "使用一句话描述本站作为网站的描述HTML 的 meta 标签)。"
}
},
"interface": {
"page_title": "界面",
"logo": {
"label": "Logo (可选)",
"msg": "不能为空",
"text": "可以上传图片,或者<1>重置</1>为站点标题。"
},
"theme": {
"label": "主题",
"msg": "不能为空",
"text": "选择一个主题"
},
"language": {
"label": "界面语言",
"msg": "不能为空",
"text": "设置用户界面语言,在刷新页面后生效。"
}
},
"smtp": {
"page_title": "SMTP",
"from_email": {
"label": "发件人地址",
"msg": "不能为空",
"text": "用于发送邮件的地址。"
},
"from_name": {
"label": "发件人名称",
"msg": "不能为空",
"text": "发件人的名称"
},
"smtp_host": {
"label": "SMTP 主机",
"msg": "不能为空",
"text": "邮件服务器"
},
"encryption": {
"label": "加密",
"msg": "不能为空",
"text": "对于大多数服务器而言SSL 是推荐开启的。",
"ssl": "SSL",
"none": "无加密"
},
"smtp_port": {
"label": "SMTP 端口",
"msg": "SMTP 端口必须在 1 ~ 65535 之间。",
"text": "邮件服务器的端口号。"
},
"smtp_username": {
"label": "SMTP 用户名",
"msg": "不能为空"
},
"smtp_password": {
"label": "SMTP 密码",
"msg": "不能为空"
},
"test_email_recipient": {
"label": "测试邮件收件人",
"text": "提供用于接收测试邮件的邮箱地址。",
"msg": "地址无效"
},
"smtp_authentication": {
"label": "SMTP 认证",
"msg": "不能为空",
"yes": "是",
"no": "否"
}
}
}
}

View File

@ -53,7 +53,7 @@ a {
height: 18px;
border-radius: 50%;
position: absolute;
left: 20px;
left: 15px;
top: 0;
border: 1px solid #fff;
}

View File

@ -107,6 +107,7 @@ const Users: FC = () => {
data={user}
className="fs-6"
avatarSize="24px"
avatarSearchStr="s=48"
/>
</td>
<td>{user.rank}</td>

View File

@ -88,7 +88,7 @@ const Index: FC<Props> = ({ data, initPage, hasAnswer }) => {
size="sm"
className="p-0 btn-no-border"
onClick={(e) => handleFollow(e)}>
{followed ? 'Following' : 'Follow'}
{t(followed ? 'Following' : 'Follow')}
</Button>
</div>
<div className="m-n1">

View File

@ -0,0 +1,176 @@
import { FC, memo, useEffect, useState } from 'react';
import { Form, Button } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { changeEmail, checkImgCode } from '@answer/api';
import type {
ImgCodeRes,
PasswordResetReq,
FormDataType,
} from '@answer/common/interface';
import { userInfoStore } from '@answer/stores';
import { PicAuthCodeModal } from '@/components/Modal';
const Index: FC = () => {
const { t } = useTranslation('translation', { keyPrefix: 'change_email' });
const [formData, setFormData] = useState<FormDataType>({
e_mail: {
value: '',
isInvalid: false,
errorMsg: '',
},
captcha_code: {
value: '',
isInvalid: false,
errorMsg: '',
},
});
const [imgCode, setImgCode] = useState<ImgCodeRes>({
captcha_id: '',
captcha_img: '',
verify: false,
});
const [showModal, setModalState] = useState(false);
const navigate = useNavigate();
const { user: userInfo, update: updateUser } = userInfoStore();
const getImgCode = () => {
checkImgCode({
action: 'e_mail',
}).then((res) => {
setImgCode(res);
});
};
const handleChange = (params: FormDataType) => {
setFormData({ ...formData, ...params });
};
const checkValidated = (): boolean => {
let bol = true;
if (!formData.e_mail.value) {
bol = false;
formData.e_mail = {
value: '',
isInvalid: true,
errorMsg: t('email.msg.empty'),
};
}
setFormData({
...formData,
});
return bol;
};
const sendEmail = (e?: any) => {
if (e) {
e.preventDefault();
}
const params: PasswordResetReq = {
e_mail: formData.e_mail.value,
};
if (imgCode.verify) {
params.captcha_code = formData.captcha_code.value;
params.captcha_id = imgCode.captcha_id;
}
changeEmail(params)
.then(() => {
userInfo.e_mail = formData.e_mail.value;
updateUser(userInfo);
navigate('/users/login', { replace: true });
setModalState(false);
})
.catch((err) => {
if (err.isError && err.key) {
formData[err.key].isInvalid = true;
formData[err.key].errorMsg = err.value;
if (err.key.indexOf('captcha') < 0) {
setModalState(false);
}
}
setFormData({ ...formData });
})
.finally(() => {
getImgCode();
});
};
const handleSubmit = async (event: any) => {
event.preventDefault();
event.stopPropagation();
if (!checkValidated()) {
return;
}
if (imgCode.verify) {
setModalState(true);
return;
}
sendEmail();
};
const goBack = () => {
navigate('/users/login?status=inactive', { replace: true });
};
useEffect(() => {
getImgCode();
}, []);
return (
<>
<Form noValidate onSubmit={handleSubmit} autoComplete="off">
<Form.Group controlId="email" className="mb-3">
<Form.Label>{t('email.label')}</Form.Label>
<Form.Control
required
type="email"
value={formData.e_mail.value}
isInvalid={formData.e_mail.isInvalid}
onChange={(e) => {
handleChange({
e_mail: {
value: e.target.value,
isInvalid: false,
errorMsg: '',
},
});
}}
/>
<Form.Control.Feedback type="invalid">
{formData.e_mail.errorMsg}
</Form.Control.Feedback>
</Form.Group>
<div className="d-grid mb-3">
<Button variant="primary" type="submit">
{t('btn_update')}
</Button>
<Button variant="link" className="mt-2 d-block" onClick={goBack}>
{t('btn_cancel')}
</Button>
</div>
</Form>
<PicAuthCodeModal
visible={showModal}
data={{
captcha: formData.captcha_code,
imgCode,
}}
handleCaptcha={handleChange}
clickSubmit={sendEmail}
refreshImgCode={getImgCode}
onClose={() => setModalState(false)}
/>
</>
);
};
export default memo(Index);

View File

@ -0,0 +1,25 @@
import { FC, memo } from 'react';
import { Container, Col } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import SendEmail from './components/sendEmail';
import { PageTitle } from '@/components';
const Index: FC = () => {
const { t } = useTranslation('translation', { keyPrefix: 'change_email' });
return (
<>
<PageTitle title={t('change_email', { keyPrefix: 'page_title' })} />
<Container style={{ paddingTop: '4rem', paddingBottom: '6rem' }}>
<h3 className="text-center mb-5">{t('page_title')}</h3>
<Col className="mx-auto" md={3}>
<SendEmail />
</Col>
</Container>
</>
);
};
export default memo(Index);

View File

@ -3,7 +3,8 @@ import { Container, Row, Col } from 'react-bootstrap';
import { Link, useSearchParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { changeEmailVerify } from '@answer/api';
import { changeEmailVerify, getUserInfo } from '@answer/api';
import { userInfoStore } from '@answer/stores';
import { PageTitle } from '@/components';
@ -12,6 +13,8 @@ const Index: FC = () => {
const [searchParams] = useSearchParams();
const [step, setStep] = useState('loading');
const updateUser = userInfoStore((state) => state.update);
useEffect(() => {
const code = searchParams.get('code');
if (code) {
@ -19,6 +22,10 @@ const Index: FC = () => {
changeEmailVerify({ code })
.then(() => {
setStep('success');
getUserInfo().then((res) => {
// update user info
updateUser(res);
});
})
.catch(() => {
setStep('invalid');

View File

@ -19,10 +19,10 @@ const Index: FC<Props> = ({ data }) => {
<div className="d-flex mb-4">
{data?.status !== 'deleted' ? (
<Link to={`/users/${data.username}`} reloadDocument>
<Avatar avatar={data.avatar} size="160px" />
<Avatar avatar={data.avatar} size="160px" searchStr="s=256" />
</Link>
) : (
<Avatar avatar={data.avatar} size="160px" />
<Avatar avatar={data.avatar} size="160px" searchStr="s=256" />
)}
<div className="ms-4">

View File

@ -116,7 +116,7 @@ const Personal: FC = () => {
)}
</Col>
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0">
<h5 className="mb-3">Stats</h5>
<h5 className="mb-3">{t('stats')}</h5>
{userInfo?.info && (
<>
<div className="text-secondary">

View File

@ -3,6 +3,7 @@ import { Form, Button } from 'react-bootstrap';
import { Trans, useTranslation } from 'react-i18next';
import { marked } from 'marked';
import MD5 from 'md5';
import { modifyUserInfo, uploadAvatar, getUserInfo } from '@answer/api';
import type { FormDataType } from '@answer/common/interface';
@ -16,6 +17,9 @@ const Index: React.FC = () => {
});
const toast = useToast();
const { user, update } = userInfoStore();
const [mailHash, setMailHash] = useState('');
const [count, setCount] = useState(0);
const [formData, setFormData] = useState<FormDataType>({
display_name: {
value: '',
@ -28,6 +32,9 @@ const Index: React.FC = () => {
errorMsg: '',
},
avatar: {
type: 'default',
gravatar: '',
custom: '',
value: '',
isInvalid: false,
errorMsg: '',
@ -59,7 +66,9 @@ const Index: React.FC = () => {
setFormData({
...formData,
avatar: {
value: res,
...formData.avatar,
type: 'custom',
custom: res,
isInvalid: false,
errorMsg: '',
},
@ -136,7 +145,11 @@ const Index: React.FC = () => {
const params = {
display_name: formData.display_name.value,
username: formData.username.value,
avatar: formData.avatar.value,
avatar: {
type: formData.avatar.type,
gravatar: formData.avatar.gravatar,
custom: formData.avatar.custom,
},
bio: formData.bio.value,
website: formData.website.value,
location: formData.location.value,
@ -168,13 +181,25 @@ const Index: React.FC = () => {
formData.display_name.value = res.display_name;
formData.username.value = res.username;
formData.bio.value = res.bio;
formData.avatar.value = res.avatar;
formData.avatar.type = res.avatar.type || 'default';
formData.avatar.gravatar = res.avatar.gravatar;
formData.avatar.custom = res.avatar.custom;
formData.location.value = res.location;
formData.website.value = res.website;
setFormData({ ...formData });
if (res.e_mail) {
const str = res.e_mail.toLowerCase().trim();
const hash = MD5(str);
console.log(str, hash, mailHash);
setMailHash(hash);
}
});
};
const refreshGravatar = () => {
setCount((pre) => pre + 1);
};
useEffect(() => {
getProfile();
}, []);
@ -227,39 +252,121 @@ const Index: React.FC = () => {
<Form.Group className="mb-3">
<Form.Label>{t('avatar.label')}</Form.Label>
<div className="d-flex align-items-center">
<Avatar
size="128px"
avatar={formData.avatar.value}
className="me-3 rounded"
<div className="mb-2">
<Form.Check
inline
type="radio"
id="gravatar"
label={t('avatar.gravatar')}
className="mb-0"
checked={formData.avatar.type === 'gravatar'}
onChange={() =>
handleChange({
avatar: {
...formData.avatar,
type: 'gravatar',
gravatar: `https://www.gravatar.com/avatar/${mailHash}`,
isInvalid: false,
errorMsg: '',
},
})
}
/>
<Form.Check
inline
type="radio"
label={t('avatar.custom')}
id="custom"
className="mb-0"
checked={formData.avatar.type === 'custom'}
onChange={() =>
handleChange({
avatar: {
...formData.avatar,
type: 'custom',
isInvalid: false,
errorMsg: '',
},
})
}
/>
<Form.Check
inline
type="radio"
id="default"
label={t('avatar.default')}
className="mb-0"
checked={formData.avatar.type === 'default'}
onChange={() =>
handleChange({
avatar: {
...formData.avatar,
type: 'default',
isInvalid: false,
errorMsg: '',
},
})
}
/>
</div>
<div className="d-flex align-items-center">
{formData.avatar.type === 'gravatar' && (
<>
<Avatar
size="128px"
avatar={formData.avatar.gravatar}
searchStr={`s=256&d=identicon${
count > 0 ? `&t=${new Date().valueOf()}` : ''
}`}
className="me-3 rounded"
/>
<div>
<Button
variant="outline-secondary"
className="mb-2"
onClick={refreshGravatar}>
{t('avatar.btn_refresh')}
</Button>
<div>
<Form.Text className="text-muted mt-0">
<Trans i18nKey="settings.profile.gravatar_text">
You can change your image on{' '}
<a
href="https://gravatar.com"
target="_blank"
rel="noreferrer">
gravatar.com
</a>
</Trans>
</Form.Text>
</div>
</div>
</>
)}
<div>
<UploadImg type="avatar" upload={avatarUpload} />
<div>
<Form.Text className="text-muted mt-0">
<Trans i18nKey="settings.profile.avatar.text">
You can upload your image or
<a
href="@/pages/Users/Settings/Profile/index##"
onClick={(e) => {
e.preventDefault();
handleChange({
avatar: {
value: '',
isInvalid: false,
errorMsg: '',
},
});
}}>
reset
</a>
it to
</Trans>
<a href="https://gravatar.com"> gravatar.com</a>
</Form.Text>
</div>
</div>
{formData.avatar.type === 'custom' && (
<>
<Avatar
size="128px"
searchStr="s=256"
avatar={formData.avatar.custom}
className="me-3 rounded"
/>
<div>
<UploadImg type="avatar" upload={avatarUpload} />
<div>
<Form.Text className="text-muted mt-0">
<Trans i18nKey="settings.profile.avatar.text">
You can upload your image.
</Trans>
</Form.Text>
</div>
</div>
</>
)}
{formData.avatar.type === 'default' && (
<Avatar size="128px" avatar="" className="me-3 rounded" />
)}
</div>
</Form.Group>

View File

@ -114,6 +114,10 @@ const routeConfig: RouteNode[] = [
path: 'users/account-recovery',
page: 'pages/Users/AccountForgot',
},
{
path: 'users/change-email',
page: 'pages/Users/ChangeEmail',
},
{
path: 'users/password-reset',
page: 'pages/Users/PasswordReset',

View File

@ -120,7 +120,7 @@ export const getUserInfo = () => {
};
export const modifyPassword = (params: Type.ModifyPasswordReq) => {
return request.post('/answer/api/v1/user/password/modify', params);
return request.put('/answer/api/v1/user/password', params);
};
export const modifyUserInfo = (params: Type.ModifyUserReq) => {