mirror of https://gitee.com/answerdev/answer.git
fix: merge from main
This commit is contained in:
commit
d4f02c668d
|
@ -6,6 +6,7 @@
|
|||
.DS_Store
|
||||
._*
|
||||
/.idea
|
||||
/.fleet
|
||||
/.vscode/*.log
|
||||
/cmd/answer/*.sh
|
||||
/cmd/logs
|
||||
|
|
|
@ -27,11 +27,8 @@ stages:
|
|||
"compile the golang project":
|
||||
image: golang:1.18
|
||||
stage: compile-golang
|
||||
before_script:
|
||||
- export GOPROXY="https://goproxy.cn"
|
||||
- export GOPRIVATE=git.backyard.segmentfault.com
|
||||
- sh ./script/prebuild.sh
|
||||
script:
|
||||
- make generate
|
||||
- make build
|
||||
artifacts:
|
||||
paths:
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"eslint.workingDirectories": [
|
||||
"ui"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
# Contributing to answer
|
||||
## Coding and documentation Style
|
||||
|
||||
## Submitting Modifications
|
22
Dockerfile
22
Dockerfile
|
@ -4,12 +4,7 @@ LABEL maintainer="mingcheng<mc@sf.com>"
|
|||
|
||||
COPY . /answer
|
||||
WORKDIR /answer
|
||||
|
||||
RUN make install-ui-packages ui
|
||||
RUN mv ui/build /tmp
|
||||
CMD ls -al /tmp
|
||||
RUN du -sh /tmp/build
|
||||
|
||||
RUN make install-ui-packages ui && mv ui/build /tmp
|
||||
|
||||
FROM golang:1.18 AS golang-builder
|
||||
LABEL maintainer="aichy"
|
||||
|
@ -23,16 +18,15 @@ ENV GOPRIVATE git.backyard.segmentfault.com
|
|||
# Build
|
||||
COPY . ${BUILD_DIR}
|
||||
WORKDIR ${BUILD_DIR}
|
||||
COPY --from=node-builder /tmp/build ${BUILD_DIR}/web/html
|
||||
CMD ls -al ${BUILD_DIR}/web/html
|
||||
COPY --from=node-builder /tmp/build ${BUILD_DIR}/ui/build
|
||||
RUN make clean build && \
|
||||
cp answer /usr/bin/answer && \
|
||||
cp configs/config.yaml /etc/config.yaml && \
|
||||
mkdir -p /tmp/cache && chmod 777 /tmp/cache && \
|
||||
mkdir -p /data/upfiles && chmod 777 /data/upfiles && cp -r i18n /data
|
||||
mkdir /data && chmod 777 /data && cp configs/config.yaml /data/config.yaml && \
|
||||
mkdir -p /data/upfiles && chmod 777 /data/upfiles && \
|
||||
mkdir -p /data/i18n && chmod 777 /data/i18n && cp -r i18n/*.yaml /data/i18n
|
||||
|
||||
FROM debian:bullseye
|
||||
|
||||
ENV TZ "Asia/Shanghai"
|
||||
RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list \
|
||||
&& sed -i 's/security.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list \
|
||||
|
@ -45,9 +39,9 @@ RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.li
|
|||
COPY --from=golang-builder /data /data
|
||||
VOLUME /data
|
||||
|
||||
COPY --from=golang-builder /etc/config.yaml /etc/answer.yaml
|
||||
COPY --from=golang-builder /usr/bin/answer /usr/bin/answer
|
||||
COPY /script/entrypoint.sh /entrypoint.sh
|
||||
RUN chmod 755 /entrypoint.sh
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
ENTRYPOINT ["dumb-init", "/usr/bin/answer", "-c", "/etc/answer.yaml"]
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
|
|
85
INSTALL.md
85
INSTALL.md
|
@ -1 +1,86 @@
|
|||
# How to build and install
|
||||
|
||||
Before installing Answer, you need to install the base environment first.
|
||||
- database
|
||||
- [MySQL](http://dev.mysql.com) Version >= 5.7
|
||||
|
||||
You can then install Answer in several ways:
|
||||
|
||||
- Deploy with Docker
|
||||
- binary installation
|
||||
- Source installation
|
||||
|
||||
## Docker for Answer
|
||||
Visit Docker Hub 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.
|
||||
|
||||
```
|
||||
# Pull image from Docker Hub.
|
||||
$ docker pull answer/answer
|
||||
|
||||
# Create local directory for volume.
|
||||
$ mkdir -p /var/data
|
||||
|
||||
# Run the image first
|
||||
$ docker run --name=answer -p 9080:80 -v /var/data:/data answer/answer
|
||||
|
||||
# After the first startup, a configuration file will be generated in the /var/data directory
|
||||
# /var/data/config.yaml
|
||||
# Need to modify the Mysql database address in the configuration file
|
||||
vim /var/data/config.yaml
|
||||
|
||||
# Modify database connection
|
||||
# connection: [username]:[password]@tcp([host]:[port])/[DbName]
|
||||
...
|
||||
|
||||
# After configuring the configuration file, you can start the mirror 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/config.yaml
|
||||
|
||||
## config.yaml Description
|
||||
|
||||
```
|
||||
server:
|
||||
http:
|
||||
addr: 0.0.0.0:80 #Project access port number
|
||||
data:
|
||||
database:
|
||||
connection: root:root@tcp(127.0.0.1:3306)/answer #MySQL database connection address
|
||||
cache:
|
||||
file_path: "/tmp/cache/cache.db" #Cache file storage path
|
||||
i18n:
|
||||
bundle_dir: "/data/i18n" #Internationalized file storage directory
|
||||
swaggerui:
|
||||
show: true #Whether to display the swaggerapi documentation, address /swagger/index.html
|
||||
protocol: http #swagger protocol header
|
||||
host: 127.0.0.1 #An accessible IP address or domain name
|
||||
address: ':80' #accessible port number
|
||||
service_config:
|
||||
secret_key: "answer" #encryption key
|
||||
web_host: "http://127.0.0.1" #Page access using domain name address
|
||||
upload_path: "./upfiles" #upload directory
|
||||
|
||||
```
|
||||
## 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
|
||||
```
|
||||
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,9 +1,85 @@
|
|||
# Answer 安装指引
|
||||
|
||||
## 前端安装
|
||||
安装 Answer 之前,您需要先安装基本环境。
|
||||
- 数据库
|
||||
- [MySQL](http://dev.mysql.com):版本 >= 5.7
|
||||
|
||||
## 后端安装
|
||||
然后,您可以通过以下几种种方式来安装 Answer:
|
||||
|
||||
- 采用 Docker 部署
|
||||
- 二进制安装
|
||||
- 源码安装
|
||||
|
||||
## 使用Docker 安装 Answer
|
||||
可以从 Docker Hub 或者 GitHub Container registry 下载最新的tags 镜像
|
||||
|
||||
### 用法
|
||||
将配置和存储目录挂在到镜像之外 volume (/var/data -> /data),你可以修改外部挂载地址
|
||||
|
||||
```
|
||||
# 将镜像从 docker hub 拉到本地
|
||||
$ docker pull answer/answer
|
||||
|
||||
# 创建一个挂载目录
|
||||
$ mkdir -p /var/data
|
||||
|
||||
# 先运行一遍镜像
|
||||
$ docker run --name=answer -p 9080:80 -v /var/data:/data answer/answer
|
||||
|
||||
# 第一次启动后会在/var/data 目录下生成配置文件
|
||||
# /var/data/config.yaml
|
||||
# 需要修改配置文件中的Mysql 数据库地址
|
||||
vim /var/data/config.yaml
|
||||
|
||||
# 修改数据库连接 connection: [username]:[password]@tcp([host]:[port])/[DbName]
|
||||
...
|
||||
|
||||
# 配置好配置文件后可以再次启动镜像即可启动服务
|
||||
$ docker start answer
|
||||
```
|
||||
## 使用二进制 安装 Answer
|
||||
可以使用编译完成的各个平台的二进制文件运行 Answer 项目
|
||||
### 用法
|
||||
从GitHub 最新版本的tag中下载对应平台的二进制文件压缩包
|
||||
|
||||
1. 解压压缩包。
|
||||
2. 使用命令 cd 进入到刚刚创建的目录。
|
||||
3. 执行命令 ./answer init。
|
||||
4. Answer 会在当前目录生成./data 目录
|
||||
5. 进入data目录修改config.yaml文件
|
||||
6. 将数据库连接地址修改为你的数据库连接地址
|
||||
|
||||
connection: [username]:[password]@tcp([host]:[port])/[DbName]
|
||||
7. 退出data 目录 执行 ./answer run -c ./data/config.yaml
|
||||
|
||||
## 配置文件 config.yaml 参数说明
|
||||
|
||||
```
|
||||
server:
|
||||
http:
|
||||
addr: 0.0.0.0:80 #项目访问端口号
|
||||
data:
|
||||
database:
|
||||
connection: root:root@tcp(127.0.0.1:3306)/answer #mysql数据库连接地址
|
||||
cache:
|
||||
file_path: "/tmp/cache/cache.db" #缓存文件存放路径
|
||||
i18n:
|
||||
bundle_dir: "/data/i18n" #国际化文件存放目录
|
||||
swaggerui:
|
||||
show: true #是否显示swaggerapi文档,地址 /swagger/index.html
|
||||
protocol: http #swagger 协议头
|
||||
host: 127.0.0.1 #可被访问的ip地址或域名
|
||||
address: ':80' #可被访问的端口号
|
||||
service_config:
|
||||
secret_key: "answer" #加密key
|
||||
web_host: "http://127.0.0.1" #页面访问使用域名地址
|
||||
upload_path: "./upfiles" #上传目录
|
||||
|
||||
```
|
||||
## 编译镜像
|
||||
|
||||
如果修改了源文件并且要重新打包镜像可以使用以下语句重新打包镜像
|
||||
```
|
||||
docker build -t answer:v1.0.0 .
|
||||
```
|
||||
## 常见问题
|
||||
1. 项目无法启动,answer主程序启动依赖配置文件config.yaml 、国际化翻译目录 /i18n 、上传文件存放目录/upfiles 需要确保项目启动时加载了配置文件 answer run -c config.yaml 以及在config.yaml 正确的指定i18n 和 upfiles 目录的配置项
|
||||
|
|
5
Makefile
5
Makefile
|
@ -12,6 +12,11 @@ GO=$(GO_ENV) $(shell which go)
|
|||
build:
|
||||
@$(GO_ENV) $(GO) build $(GO_FLAGS) -o $(BIN) $(DIR_SRC)
|
||||
|
||||
generate:
|
||||
go get github.com/google/wire/cmd/wire@latest
|
||||
go generate ./...
|
||||
go mod tidy
|
||||
|
||||
test:
|
||||
@$(GO) test ./...
|
||||
|
||||
|
|
59
README.md
59
README.md
|
@ -1,35 +1,38 @@
|
|||
# answer
|
||||

|
||||
|
||||
问答社区主项目代码
|
||||
# Answer - Simple Q&A Community
|
||||
|
||||
# Dependence
|
||||
github.com/segmentfault/pacman
|
||||
* config-file `viper` https://github.com/spf13/viper
|
||||
* web `gin` https://gin-gonic.com/zh-cn/
|
||||
* log `zap` https://github.com/uber-go/zap
|
||||
* orm `xorm` https://xorm.io/zh/
|
||||
* redis `go-redis` https://github.com/go-redis/redis
|
||||
[](https://github.com/segmentfault/answer/blob/master/LICENSE)
|
||||
[](https://golang.org/)
|
||||
[](https://reactjs.org/)
|
||||
|
||||
# module
|
||||
- email github.com/jordan-wright/email
|
||||
- session github.com/gin-contrib/sessions
|
||||
- Captcha github.com/mojocn/base64Captcha
|
||||
## What is Answer?
|
||||
This is a minimalist open source Q&A community. Users can post questions and others can answer them.
|
||||

|
||||
|
||||
# Run
|
||||
```
|
||||
cd cmd
|
||||
export GOPRIVATE=git.backyard.segmentfault.com
|
||||
go mod tidy
|
||||
./dev.sh
|
||||
## Why?
|
||||
- Help companies build knowledge and Q&A communities better and faster.
|
||||
|
||||
## Features
|
||||
- Produce knowledge by asking and answering questions.
|
||||
- Maintain knowledge by voting and working together.
|
||||
|
||||
## Quick start
|
||||
### Running with docker-compose
|
||||
```bash
|
||||
mkdir answer && cd answer
|
||||
wget https://github.com/segmentfault/answer/releases/latest/download/docker-compose.yaml
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
# pprof
|
||||
For more information you can see [INSTALL.md](./INSTALL.md)
|
||||
|
||||
```
|
||||
# Installation dependency
|
||||
go get -u github.com/google/pprof
|
||||
brew install graphviz
|
||||
```
|
||||
```
|
||||
pprof -http :8082 http://XXX/debug/pprof/profile\?seconds\=10
|
||||
```
|
||||
## Contributing
|
||||
|
||||
Contributions are always welcome!
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for ways to get started.
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/segmentfault/answer/blob/master/LICENSE)
|
||||
|
|
39
README_CN.md
39
README_CN.md
|
@ -1,9 +1,38 @@
|
|||
# Answer 问答社区
|
||||

|
||||
|
||||
## 功能说明
|
||||
# Answer - 极简问答社区
|
||||
|
||||
## 安装
|
||||
[](https://github.com/segmentfault/answer/blob/master/LICENSE)
|
||||
[](https://golang.org/)
|
||||
[](https://reactjs.org/)
|
||||
|
||||
## 配置
|
||||
## 什么是 Answer?
|
||||
这是一个极简的开源问答社区。用户可以发布问题,其他人可以回答。
|
||||

|
||||
|
||||
## 常见问题
|
||||
## 目标
|
||||
- 帮助企业更好更快构建知识问答社区
|
||||
|
||||
## 产品功能
|
||||
- 通过提问、回答方式生产知识
|
||||
- 通过投票、共同协作方式维护知识
|
||||
|
||||
## 快速开始
|
||||
### 使用 docker-compose 快速搭建
|
||||
```bash
|
||||
mkdir answer && cd answer
|
||||
wget https://github.com/segmentfault/answer/releases/latest/download/docker-compose.yaml
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
其他安装配置细节请参考 [INSTALL.md](./INSTALL.md)
|
||||
|
||||
## 贡献
|
||||
|
||||
我们随时欢迎你的贡献!
|
||||
|
||||
参考 [CONTRIBUTING.md](CONTRIBUTING.md) 其中的贡献指南
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/segmentfault/answer/blob/master/LICENSE)
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,6 @@
|
|||
package assets
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed answer.sql
|
||||
var AnswerSql []byte
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/segmentfault/answer/internal/base/conf"
|
||||
"github.com/segmentfault/answer/internal/cli"
|
||||
"github.com/segmentfault/pacman"
|
||||
"github.com/segmentfault/pacman/contrib/conf/viper"
|
||||
"github.com/segmentfault/pacman/contrib/log/zap"
|
||||
|
@ -37,6 +38,24 @@ func init() {
|
|||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
args := flag.Args()
|
||||
|
||||
if len(args) < 1 {
|
||||
cli.Usage()
|
||||
os.Exit(0)
|
||||
return
|
||||
}
|
||||
|
||||
if args[0] == "init" {
|
||||
cli.InitConfig()
|
||||
return
|
||||
}
|
||||
if len(args) >= 3 {
|
||||
if args[0] == "run" && args[1] == "-c" {
|
||||
confFlag = args[2]
|
||||
}
|
||||
}
|
||||
|
||||
log.SetLogger(zap.NewLogger(
|
||||
log.ParseLevel(logLevel), zap.WithName(Name), zap.WithPath(logPath), zap.WithCallerFullPath()))
|
||||
|
||||
|
@ -50,11 +69,16 @@ func main() {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
err = cli.InitDB(c.Data.Database)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
app, cleanup, err := initApplication(
|
||||
c.Debug, c.Server, c.Data.Database, c.Data.Cache, c.I18n, c.Swaggerui, c.ServiceConfig, log.GetLogger())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
defer cleanup()
|
||||
if err := app.Run(); err != nil {
|
||||
panic(err)
|
||||
|
|
|
@ -101,13 +101,14 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
|||
userController := controller.NewUserController(authService, userService, captchaService, emailService, uploaderService)
|
||||
commentRepo := comment.NewCommentRepo(dataData, uniqueIDRepo)
|
||||
commentCommonRepo := comment.NewCommentCommonRepo(dataData, uniqueIDRepo)
|
||||
userCommon := usercommon.NewUserCommon(userRepo)
|
||||
answerRepo := repo.NewAnswerRepo(dataData, uniqueIDRepo, userRankRepo, activityRepo)
|
||||
questionRepo := repo.NewQuestionRepo(dataData, uniqueIDRepo)
|
||||
tagRepo := tag.NewTagRepo(dataData, uniqueIDRepo)
|
||||
objService := object_info.NewObjService(answerRepo, questionRepo, commentCommonRepo, tagRepo)
|
||||
voteRepo := activity_common.NewVoteRepo(dataData, activityRepo)
|
||||
commentService := comment2.NewCommentService(commentRepo, commentCommonRepo, userRepo, objService, voteRepo)
|
||||
rankService := rank2.NewRankService(userRepo, userRankRepo, objService, configRepo)
|
||||
commentService := comment2.NewCommentService(commentRepo, commentCommonRepo, userCommon, objService, voteRepo)
|
||||
rankService := rank2.NewRankService(userCommon, userRankRepo, objService, configRepo)
|
||||
commentController := controller.NewCommentController(commentService, rankService)
|
||||
reportRepo := report.NewReportRepo(dataData, uniqueIDRepo)
|
||||
reportService := report2.NewReportService(reportRepo, objService)
|
||||
|
@ -127,7 +128,6 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
|||
collectionGroupRepo := collection.NewCollectionGroupRepo(dataData)
|
||||
tagRelRepo := tag.NewTagListRepo(dataData)
|
||||
tagCommonService := tagcommon.NewTagCommonService(tagRepo, tagRelRepo, revisionService)
|
||||
userCommon := usercommon.NewUserCommon(userRepo)
|
||||
collectionCommon := collectioncommon.NewCollectionCommon(collectionRepo)
|
||||
answerCommon := answercommon.NewAnswerCommon(answerRepo)
|
||||
metaRepo := meta.NewMetaRepo(dataData)
|
||||
|
@ -142,10 +142,10 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
|||
questionController := controller.NewQuestionController(questionService, rankService)
|
||||
answerService := service.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo)
|
||||
answerController := controller.NewAnswerController(answerService, rankService)
|
||||
searchRepo := repo.NewSearchRepo(dataData, uniqueIDRepo, userRepo)
|
||||
searchService := service.NewSearchService(searchRepo, tagRepo, userRepo, followRepo)
|
||||
searchRepo := repo.NewSearchRepo(dataData, uniqueIDRepo, userCommon)
|
||||
searchService := service.NewSearchService(searchRepo, tagRepo, userCommon, followRepo)
|
||||
searchController := controller.NewSearchController(searchService)
|
||||
serviceRevisionService := service.NewRevisionService(revisionRepo, userRepo, questionCommon, answerService)
|
||||
serviceRevisionService := service.NewRevisionService(revisionRepo, userCommon, questionCommon, answerService)
|
||||
revisionController := controller.NewRevisionController(serviceRevisionService)
|
||||
rankController := controller.NewRankController(rankService)
|
||||
commonRepo := common.NewCommonRepo(dataData, uniqueIDRepo)
|
||||
|
@ -169,9 +169,9 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
|||
notificationController := controller.NewNotificationController(notificationService)
|
||||
answerAPIRouter := router.NewAnswerAPIRouter(langController, userController, commentController, reportController, voteController, tagController, followController, collectionController, questionController, answerController, searchController, revisionController, rankController, controller_backyardReportController, userBackyardController, reasonController, themeController, siteInfoController, siteinfoController, notificationController)
|
||||
swaggerRouter := router.NewSwaggerRouter(swaggerConf)
|
||||
viewRouter := router.NewViewRouter()
|
||||
uiRouter := router.NewUIRouter()
|
||||
authUserMiddleware := middleware.NewAuthUserMiddleware(authService)
|
||||
ginEngine := server.NewHTTPServer(debug, staticRouter, answerAPIRouter, swaggerRouter, viewRouter, authUserMiddleware)
|
||||
ginEngine := server.NewHTTPServer(debug, staticRouter, answerAPIRouter, swaggerRouter, uiRouter, authUserMiddleware)
|
||||
application := newApplication(serverConf, ginEngine)
|
||||
return application, func() {
|
||||
cleanup2()
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package configs
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed config.yaml
|
||||
var Config []byte
|
|
@ -1,19 +1,19 @@
|
|||
server:
|
||||
http:
|
||||
addr: ""
|
||||
addr: 0.0.0.0:80
|
||||
data:
|
||||
database:
|
||||
connection: ""
|
||||
connection: root:root@tcp(db:3306)/answer
|
||||
cache:
|
||||
file_path: ""
|
||||
file_path: "/tmp/cache/cache.db"
|
||||
i18n:
|
||||
bundle_dir: ""
|
||||
bundle_dir: "/data/i18n"
|
||||
swaggerui:
|
||||
show: true
|
||||
protocol: http
|
||||
host: 0
|
||||
address: ':'
|
||||
host: 127.0.0.1
|
||||
address: ':80'
|
||||
service_config:
|
||||
secret_key: ""
|
||||
web_host: ""
|
||||
upload_path: ""
|
||||
secret_key: "answer"
|
||||
web_host: "http://127.0.0.1"
|
||||
upload_path: "./upfiles"
|
||||
|
|
|
@ -1,7 +1,24 @@
|
|||
version: "3"
|
||||
version: "3.9"
|
||||
services:
|
||||
answer:
|
||||
build:
|
||||
context: .
|
||||
image: github.com/segmentfault/answer
|
||||
ports:
|
||||
- '9080:80'
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
links:
|
||||
- db
|
||||
db:
|
||||
image: mariadb:10.4.7
|
||||
ports:
|
||||
- '13306:3306'
|
||||
restart: on-failure
|
||||
environment:
|
||||
MYSQL_DATABASE: answer
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
healthcheck:
|
||||
test: [ "CMD", "mysqladmin" ,"ping", "-uroot", "-proot"]
|
||||
timeout: 20s
|
||||
retries: 10
|
||||
|
|
10
docs/docs.go
10
docs/docs.go
|
@ -4246,6 +4246,10 @@ const docTemplate = `{
|
|||
"description": "reply user id",
|
||||
"type": "string"
|
||||
},
|
||||
"reply_user_status": {
|
||||
"description": "reply user status",
|
||||
"type": "string"
|
||||
},
|
||||
"reply_username": {
|
||||
"description": "reply user username",
|
||||
"type": "string"
|
||||
|
@ -4262,6 +4266,10 @@ const docTemplate = `{
|
|||
"description": "user id",
|
||||
"type": "string"
|
||||
},
|
||||
"user_status": {
|
||||
"description": "user status",
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"description": "username",
|
||||
"type": "string"
|
||||
|
@ -5504,7 +5512,7 @@ const docTemplate = `{
|
|||
},
|
||||
"status": {
|
||||
"description": "status",
|
||||
"type": "integer"
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"description": "name",
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 118 KiB |
Binary file not shown.
After Width: | Height: | Size: 9.6 KiB |
|
@ -4234,6 +4234,10 @@
|
|||
"description": "reply user id",
|
||||
"type": "string"
|
||||
},
|
||||
"reply_user_status": {
|
||||
"description": "reply user status",
|
||||
"type": "string"
|
||||
},
|
||||
"reply_username": {
|
||||
"description": "reply user username",
|
||||
"type": "string"
|
||||
|
@ -4250,6 +4254,10 @@
|
|||
"description": "user id",
|
||||
"type": "string"
|
||||
},
|
||||
"user_status": {
|
||||
"description": "user status",
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"description": "username",
|
||||
"type": "string"
|
||||
|
@ -5492,7 +5500,7 @@
|
|||
},
|
||||
"status": {
|
||||
"description": "status",
|
||||
"type": "integer"
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"description": "name",
|
||||
|
|
|
@ -299,6 +299,9 @@ definitions:
|
|||
reply_user_id:
|
||||
description: reply user id
|
||||
type: string
|
||||
reply_user_status:
|
||||
description: reply user status
|
||||
type: string
|
||||
reply_username:
|
||||
description: reply user username
|
||||
type: string
|
||||
|
@ -311,6 +314,9 @@ definitions:
|
|||
user_id:
|
||||
description: user id
|
||||
type: string
|
||||
user_status:
|
||||
description: user status
|
||||
type: string
|
||||
username:
|
||||
description: username
|
||||
type: string
|
||||
|
@ -1209,7 +1215,7 @@ definitions:
|
|||
type: integer
|
||||
status:
|
||||
description: status
|
||||
type: integer
|
||||
type: string
|
||||
username:
|
||||
description: name
|
||||
type: string
|
||||
|
|
14
go.mod
14
go.mod
|
@ -19,17 +19,17 @@ require (
|
|||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
|
||||
github.com/mojocn/base64Captcha v1.3.5
|
||||
github.com/segmentfault/pacman v1.0.1
|
||||
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20220926035018-18f894415e5b
|
||||
github.com/segmentfault/pacman/contrib/conf/viper v0.0.0-20220926035018-18f894415e5b
|
||||
github.com/segmentfault/pacman/contrib/i18n v0.0.0-20220926035018-18f894415e5b
|
||||
github.com/segmentfault/pacman/contrib/log/zap v0.0.0-20220926035018-18f894415e5b
|
||||
github.com/segmentfault/pacman/contrib/server/http v0.0.0-20220926035018-18f894415e5b
|
||||
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/stretchr/testify v1.8.0
|
||||
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-20220926192436-02166a98028e
|
||||
golang.org/x/net v0.0.0-20220927171203-f486391704dc
|
||||
xorm.io/builder v0.3.12
|
||||
xorm.io/core v0.7.3
|
||||
xorm.io/xorm v1.3.2
|
||||
|
@ -76,7 +76,7 @@ require (
|
|||
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-20220926163933-8cfa568d3c25 // 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
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
|
|
21
go.sum
21
go.sum
|
@ -525,14 +525,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-20220926035018-18f894415e5b h1:jSnRy3z3KVtVuGM2YTZihXwc4zEhW+TvyyJbBm8rjh4=
|
||||
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20220926035018-18f894415e5b/go.mod h1:rmf1TCwz67dyM+AmTwSd1BxTo2AOYHj262lP93bOZbs=
|
||||
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/conf/viper v0.0.0-20220926035018-18f894415e5b h1:Gx3Brm+VMAyBJn4aBsxgKl+EIhFHc/YH5cLGeFHAW4g=
|
||||
github.com/segmentfault/pacman/contrib/conf/viper v0.0.0-20220926035018-18f894415e5b/go.mod h1:prPjFam7MyZ5b3S9dcDOt2tMPz6kf7C9c243s9zSwPY=
|
||||
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/i18n v0.0.0-20220926035018-18f894415e5b h1:uQmSgcV2w4OVXU6l3bQb9O+cSAVuzDQ9adJArQyFBa4=
|
||||
github.com/segmentfault/pacman/contrib/i18n v0.0.0-20220926035018-18f894415e5b/go.mod h1:5Afm+OQdau/HQqSOp/ALlSUp0vZsMMMbv//kJhxuoi8=
|
||||
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/log/zap v0.0.0-20220926035018-18f894415e5b h1:TaOBmAglooq+qKdnNTK2sy11t26ud7psHFB7/AV7l5U=
|
||||
github.com/segmentfault/pacman/contrib/log/zap v0.0.0-20220926035018-18f894415e5b/go.mod h1:L4GqtXLoR73obTYqUQIzfkm8NG8pvZafxFb6KZFSSHk=
|
||||
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/server/http v0.0.0-20220926035018-18f894415e5b h1:n5n5VPeYGuZCmVppKPgWR/CaINHnL+ipEp9iE1XkcQc=
|
||||
github.com/segmentfault/pacman/contrib/server/http v0.0.0-20220926035018-18f894415e5b/go.mod h1:UjNiOFYv1uGCq1ZCcONaKq4eE7MW3nbgpLqgl8f9N40=
|
||||
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/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=
|
||||
|
@ -584,7 +594,6 @@ github.com/swaggo/swag v1.8.6/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBn
|
|||
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.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.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
|
@ -648,8 +657,6 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y
|
|||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/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-20220924013350-4ba4fb4dd9e7 h1:WJywXQVIb56P2kAvXeMGTIgQ1ZHQxR60+F9dLsodECc=
|
||||
golang.org/x/crypto v0.0.0-20220924013350-4ba4fb4dd9e7/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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
@ -733,10 +740,10 @@ 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-20220923203811-8be639271d50 h1:vKyz8L3zkd+xrMeIaBsQ/MNVPVFSffdaU3ZyYlBGFnI=
|
||||
golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.0.0-20220926192436-02166a98028e h1:I51lVG9ykW5AQeTE50sJ0+gJCAF0J78Hf1+1VUCGxDI=
|
||||
golang.org/x/net v0.0.0-20220926192436-02166a98028e/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
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/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=
|
||||
|
@ -817,10 +824,10 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc=
|
||||
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220926163933-8cfa568d3c25 h1:nwzwVf0l2Y/lkov/+IYgMMbFyI+QypZDds9RxlSmsFQ=
|
||||
golang.org/x/sys v0.0.0-20220926163933-8cfa568d3c25/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/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=
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package i18n
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed *.yaml
|
||||
var I18n embed.FS
|
|
@ -28,44 +28,3 @@ type Data struct {
|
|||
Database *data.Database `json:"database" mapstructure:"database"`
|
||||
Cache *data.CacheConf `json:"cache" mapstructure:"cache"`
|
||||
}
|
||||
|
||||
// ------------------ remove
|
||||
|
||||
// log .
|
||||
type log struct {
|
||||
Dir string `json:"dir"`
|
||||
Name string `json:"name"`
|
||||
Access bool `json:"access"`
|
||||
Level string `json:"level"`
|
||||
MaxSize int `json:"max_size"`
|
||||
MaxBackups int `json:"max_backups"`
|
||||
MaxAge int `json:"max_age"`
|
||||
}
|
||||
|
||||
// Local .
|
||||
type Local struct {
|
||||
Address string `json:"address"`
|
||||
Debug bool `json:"debug"`
|
||||
log log `json:"log"`
|
||||
}
|
||||
|
||||
// // SwaggerConfig .
|
||||
// type SwaggerConfig struct {
|
||||
// Show bool `json:"show"`
|
||||
// Protocol string `json:"protocol"`
|
||||
// Host string `json:"host"`
|
||||
// Address string `json:"address"`
|
||||
// }
|
||||
|
||||
// Answer .
|
||||
type Answer struct {
|
||||
MaxIdle int `json:"max_idle"`
|
||||
MaxOpen int `json:"max_open"`
|
||||
IsDebug bool `json:"is_debug"`
|
||||
Datasource string `json:"datasource"`
|
||||
}
|
||||
|
||||
// Mysql .
|
||||
type Mysql struct {
|
||||
Answer Answer `json:"answer"`
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ const (
|
|||
UserTokenCacheTime = 7 * 24 * time.Hour
|
||||
AdminTokenCacheKey = "answer:admin:token:"
|
||||
AdminTokenCacheTime = 7 * 24 * time.Hour
|
||||
AcceptLanguageFlag = "Accept-Language"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/segmentfault/answer/internal/base/constant"
|
||||
"github.com/segmentfault/answer/internal/base/reason"
|
||||
"github.com/segmentfault/answer/internal/base/validator"
|
||||
myErrors "github.com/segmentfault/pacman/errors"
|
||||
|
@ -44,13 +45,15 @@ func HandleResponse(ctx *gin.Context, err error, data interface{}) {
|
|||
|
||||
// BindAndCheck bind request and check
|
||||
func BindAndCheck(ctx *gin.Context, data interface{}) bool {
|
||||
lang := GetLang(ctx)
|
||||
ctx.Set(constant.AcceptLanguageFlag, lang)
|
||||
if err := ctx.ShouldBind(data); err != nil {
|
||||
log.Errorf("http_handle BindAndCheck fail, %s", err.Error())
|
||||
HandleResponse(ctx, myErrors.New(http.StatusBadRequest, reason.RequestFormatError), nil)
|
||||
return true
|
||||
}
|
||||
|
||||
errField, err := validator.GetValidatorByLang(GetLang(ctx).Abbr()).Check(data)
|
||||
errField, err := validator.GetValidatorByLang(lang.Abbr()).Check(data)
|
||||
if err != nil {
|
||||
HandleResponse(ctx, myErrors.New(http.StatusBadRequest, reason.RequestFormatError).WithMsg(err.Error()), errField)
|
||||
return true
|
||||
|
|
|
@ -2,12 +2,13 @@ package handler
|
|||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/segmentfault/answer/internal/base/constant"
|
||||
"github.com/segmentfault/pacman/i18n"
|
||||
)
|
||||
|
||||
// GetLang get language from header
|
||||
func GetLang(ctx *gin.Context) i18n.Language {
|
||||
acceptLanguage := ctx.GetHeader("Accept-Language")
|
||||
acceptLanguage := ctx.GetHeader(constant.AcceptLanguageFlag)
|
||||
switch i18n.Language(acceptLanguage) {
|
||||
case i18n.LanguageChinese:
|
||||
return i18n.LanguageChinese
|
||||
|
|
|
@ -17,7 +17,7 @@ type PageCond struct {
|
|||
}
|
||||
|
||||
// NewPageModel new page model
|
||||
func NewPageModel(page, pageSize int, totalRecords int64, records interface{}) *PageModel {
|
||||
func NewPageModel(totalRecords int64, records interface{}) *PageModel {
|
||||
sliceValue := reflect.Indirect(reflect.ValueOf(records))
|
||||
if sliceValue.Kind() != reflect.Slice {
|
||||
panic("not a slice")
|
||||
|
|
|
@ -11,7 +11,7 @@ func NewHTTPServer(debug bool,
|
|||
staticRouter *router.StaticRouter,
|
||||
answerRouter *router.AnswerAPIRouter,
|
||||
swaggerRouter *router.SwaggerRouter,
|
||||
viewRouter *router.ViewRouter,
|
||||
viewRouter *router.UIRouter,
|
||||
authUserMiddleware *middleware.AuthUserMiddleware) *gin.Engine {
|
||||
|
||||
if debug {
|
||||
|
@ -22,7 +22,7 @@ func NewHTTPServer(debug bool,
|
|||
r := gin.New()
|
||||
r.GET("/healthz", func(ctx *gin.Context) { ctx.String(200, "OK") })
|
||||
|
||||
viewRouter.RegisterViewRouter(r)
|
||||
viewRouter.Register(r)
|
||||
|
||||
rootGroup := r.Group("")
|
||||
swaggerRouter.Register(rootGroup)
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/segmentfault/answer/configs"
|
||||
"github.com/segmentfault/answer/i18n"
|
||||
"github.com/segmentfault/answer/pkg/dir"
|
||||
)
|
||||
|
||||
var SuccessMsg = `
|
||||
answer initialized successfully.
|
||||
`
|
||||
|
||||
var HasBeenInitializedMsg = `
|
||||
Has been initialized.
|
||||
`
|
||||
|
||||
func InitConfig() {
|
||||
exist, err := PathExists("data/config.yaml")
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(2)
|
||||
}
|
||||
if exist {
|
||||
fmt.Println(HasBeenInitializedMsg)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
_, err = dir.CreatePathIsNotExist("data")
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(2)
|
||||
}
|
||||
WriterFile("data/config.yaml", string(configs.Config))
|
||||
_, err = dir.CreatePathIsNotExist("data/i18n")
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(2)
|
||||
}
|
||||
_, err = dir.CreatePathIsNotExist("data/upfiles")
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(2)
|
||||
}
|
||||
i18nList, err := i18n.I18n.ReadDir(".")
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(2)
|
||||
}
|
||||
for _, item := range i18nList {
|
||||
path := fmt.Sprintf("data/i18n/%s", item.Name())
|
||||
content, err := i18n.I18n.ReadFile(item.Name())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
WriterFile(path, string(content))
|
||||
}
|
||||
fmt.Println(SuccessMsg)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func WriterFile(filePath, content string) error {
|
||||
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
write := bufio.NewWriter(file)
|
||||
write.WriteString(content)
|
||||
write.Flush()
|
||||
return nil
|
||||
}
|
||||
|
||||
func PathExists(path string) (bool, error) {
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/segmentfault/answer/assets"
|
||||
"github.com/segmentfault/answer/internal/base/data"
|
||||
"github.com/segmentfault/answer/internal/entity"
|
||||
)
|
||||
|
||||
// InitDB init db
|
||||
func InitDB(dataConf *data.Database) (err error) {
|
||||
db := data.NewDB(false, dataConf)
|
||||
// check db connection
|
||||
err = db.Ping()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
exist, err := db.IsTableExist(&entity.User{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
return nil
|
||||
}
|
||||
|
||||
// create table if not exist
|
||||
s := &bytes.Buffer{}
|
||||
s.Write(assets.AnswerSql)
|
||||
_, err = db.Import(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package cli
|
||||
|
||||
import "fmt"
|
||||
|
||||
var usageDoc = `
|
||||
Welcome to answer
|
||||
|
||||
VERSION:
|
||||
1.0.0
|
||||
|
||||
USAGE:
|
||||
answer [global options] command [command options] [arguments...]
|
||||
|
||||
COMMANDS:
|
||||
init Init config, eg:./answer init
|
||||
run Start web server, eg:./answer run -c data/config.yaml
|
||||
`
|
||||
|
||||
func Usage() {
|
||||
fmt.Println(usageDoc)
|
||||
}
|
|
@ -93,16 +93,16 @@ func (nc *NotificationController) ClearIDUnRead(ctx *gin.Context) {
|
|||
handler.HandleResponse(ctx, err, gin.H{})
|
||||
}
|
||||
|
||||
// GetList
|
||||
// @Summary GetRedDot
|
||||
// @Description GetRedDot
|
||||
// GetList get notification list
|
||||
// @Summary get notification list
|
||||
// @Description get notification list
|
||||
// @Tags Notification
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param page query int false "page size"
|
||||
// @Param page_size query int false "page size"
|
||||
// @Param type query string false "type" Enums(inbox,achievement)
|
||||
// @Param type query string true "type" Enums(inbox,achievement)
|
||||
// @Success 200 {object} handler.RespBody
|
||||
// @Router /answer/api/v1/notification/page [get]
|
||||
func (nc *NotificationController) GetList(ctx *gin.Context) {
|
||||
|
@ -111,9 +111,6 @@ func (nc *NotificationController) GetList(ctx *gin.Context) {
|
|||
return
|
||||
}
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
list, count, err := nc.notificationService.GetList(ctx, req)
|
||||
handler.HandleResponse(ctx, err, gin.H{
|
||||
"list": list,
|
||||
"count": count,
|
||||
})
|
||||
resp, err := nc.notificationService.GetList(ctx, req)
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
|
|
@ -328,15 +328,15 @@ func (uc *UserController) UserModifyPassWord(ctx *gin.Context) {
|
|||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
|
||||
// UserUpdateInfo godoc
|
||||
// @Summary UserUpdateInfo
|
||||
// @Description UserUpdateInfo
|
||||
// UserUpdateInfo update user info
|
||||
// @Summary UserUpdateInfo update user info
|
||||
// @Description UserUpdateInfo update user info
|
||||
// @Tags User
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param Authorization header string true "access-token"
|
||||
// @Param data body schema.UpdateInfoRequest true "UpdateInfoRequest"
|
||||
// @Param data body schema.UpdateInfoRequest true "UpdateInfoRequest"
|
||||
// @Success 200 {object} handler.RespBody
|
||||
// @Router /answer/api/v1/user/info [put]
|
||||
func (uc *UserController) UserUpdateInfo(ctx *gin.Context) {
|
||||
|
|
|
@ -20,7 +20,7 @@ var CmsAnswerSearchStatus = map[string]int{
|
|||
type Answer struct {
|
||||
ID string `xorm:"not null pk autoincr comment('answer id') BIGINT(20) id"`
|
||||
CreatedAt time.Time `xorm:"created not null default CURRENT_TIMESTAMP TIMESTAMP created_at"`
|
||||
UpdatedAt time.Time `xorm:"updated not null default CURRENT_TIMESTAMP TIMESTAMP updated_at"`
|
||||
UpdatedAt time.Time `xorm:"not null default CURRENT_TIMESTAMP TIMESTAMP updated_at"`
|
||||
QuestionID string `xorm:"not null default 0 comment('question id') BIGINT(20) question_id"`
|
||||
UserID string `xorm:"not null default 0 comment('answer user id') BIGINT(20) user_id"`
|
||||
OriginalText string `xorm:"not null comment('original content') MEDIUMTEXT original_text"`
|
||||
|
|
|
@ -24,6 +24,8 @@ type QuestionTag struct {
|
|||
// Question question
|
||||
type Question struct {
|
||||
ID string `xorm:"not null pk comment('question id') BIGINT(20) id"`
|
||||
CreatedAt time.Time `xorm:"not null default CURRENT_TIMESTAMP comment('create time') TIMESTAMP created_at"`
|
||||
UpdatedAt time.Time `xorm:"not null default CURRENT_TIMESTAMP comment('update time') TIMESTAMP updated_at"`
|
||||
UserID string `xorm:"not null default 0 comment('user id') BIGINT(20) user_id"`
|
||||
Title string `xorm:"not null default '' comment('question title') VARCHAR(255) title"`
|
||||
OriginalText string `xorm:"not null comment('original content') MEDIUMTEXT original_text"`
|
||||
|
@ -37,8 +39,6 @@ type Question struct {
|
|||
FollowCount int `xorm:"not null default 0 comment('follow count') INT(11) follow_count"`
|
||||
AcceptedAnswerID string `xorm:"not null default 0 comment('accepted answer id') BIGINT(20) accepted_answer_id"`
|
||||
LastAnswerID string `xorm:"not null default 0 comment('last answer id') BIGINT(20) last_answer_id"`
|
||||
CreatedAt time.Time `xorm:"not null default CURRENT_TIMESTAMP comment('create time') TIMESTAMP created_at"`
|
||||
UpdatedAt time.Time `xorm:"not null default CURRENT_TIMESTAMP comment('update time') TIMESTAMP updated_at"`
|
||||
PostUpdateTime time.Time `xorm:"default CURRENT_TIMESTAMP comment('answer the last update time') TIMESTAMP post_update_time"`
|
||||
RevisionID string `xorm:"not null default 0 BIGINT(20) revision_id"`
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ type User struct {
|
|||
Rank int `xorm:"not null default 0 comment('rank') INT(11) rank"`
|
||||
Status int `xorm:"not null default 1 comment('user status(available: 1; deleted: 10)') INT(11) status"`
|
||||
AuthorityGroup int `xorm:"not null default 1 comment('authority group') INT(11) authority_group"`
|
||||
DisplayName string `xorm:"not null default '' comment('display name') VARCHAR(50) display_name"`
|
||||
DisplayName string `xorm:"not null default '' comment('display name') VARCHAR(30) display_name"`
|
||||
Avatar string `xorm:"not null default '' comment('avatar') VARCHAR(255) avatar"`
|
||||
Mobile string `xorm:"not null comment('mobile') VARCHAR(20) mobile"`
|
||||
Bio string `xorm:"not null comment('bio markdown') TEXT bio"`
|
||||
|
|
|
@ -184,7 +184,7 @@ func (ar *answerRepo) SearchList(ctx context.Context, search *entity.AnswerSearc
|
|||
session = session.And("user_id = ?", search.UserID)
|
||||
}
|
||||
if search.Order == entity.Answer_Search_OrderBy_Time {
|
||||
session = session.OrderBy("updated_at desc")
|
||||
session = session.OrderBy("created_at desc")
|
||||
} else if search.Order == entity.Answer_Search_OrderBy_Vote {
|
||||
session = session.OrderBy("vote_count desc")
|
||||
} else {
|
||||
|
@ -194,7 +194,7 @@ func (ar *answerRepo) SearchList(ctx context.Context, search *entity.AnswerSearc
|
|||
session = session.And("status = ?", entity.AnswerStatusAvailable)
|
||||
|
||||
session = session.Limit(search.PageSize, offset)
|
||||
count, err = session.OrderBy("updated_at desc").FindAndCount(&rows)
|
||||
count, err = session.FindAndCount(&rows)
|
||||
if err != nil {
|
||||
return rows, count, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
|
|
|
@ -22,16 +22,16 @@ import (
|
|||
// searchRepo tag repository
|
||||
type searchRepo struct {
|
||||
data *data.Data
|
||||
userRepo usercommon.UserRepo
|
||||
userCommon *usercommon.UserCommon
|
||||
uniqueIDRepo unique.UniqueIDRepo
|
||||
}
|
||||
|
||||
// NewSearchRepo new repository
|
||||
func NewSearchRepo(data *data.Data, uniqueIDRepo unique.UniqueIDRepo, userRepo usercommon.UserRepo) search_common.SearchRepo {
|
||||
func NewSearchRepo(data *data.Data, uniqueIDRepo unique.UniqueIDRepo, userCommon *usercommon.UserCommon) search_common.SearchRepo {
|
||||
return &searchRepo{
|
||||
data: data,
|
||||
uniqueIDRepo: uniqueIDRepo,
|
||||
userRepo: userRepo,
|
||||
userCommon: userCommon,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -219,7 +219,6 @@ func (sr *searchRepo) parseResult(ctx context.Context, res []map[string][]byte)
|
|||
var (
|
||||
objectKey,
|
||||
status string
|
||||
uInfo *schema.UserBasicInfo
|
||||
|
||||
tags []schema.TagResp
|
||||
tagsEntity []entity.Tag
|
||||
|
@ -233,16 +232,12 @@ func (sr *searchRepo) parseResult(ctx context.Context, res []map[string][]byte)
|
|||
tp, _ := time.ParseInLocation("2006-01-02 15:04:05", string(r["created_at"]), time.Local)
|
||||
|
||||
// get user info
|
||||
userInfo, exist, e := sr.userRepo.GetByUserID(ctx, string(r["user_id"]))
|
||||
userInfo, _, e := sr.userCommon.GetUserBasicInfoByID(ctx, string(r["user_id"]))
|
||||
if e != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||
return
|
||||
}
|
||||
|
||||
if exist {
|
||||
uInfo = sr.userBasicInfoFormat(ctx, userInfo)
|
||||
}
|
||||
|
||||
// get tags
|
||||
err = sr.data.DB.
|
||||
Select("`display_name`,`slug_name`,`main_tag_slug_name`").
|
||||
|
@ -279,7 +274,7 @@ func (sr *searchRepo) parseResult(ctx context.Context, res []map[string][]byte)
|
|||
Title: string(r["title"]),
|
||||
Excerpt: cutOutParsedText(string(r["original_text"])),
|
||||
CreatedAtParsed: tp.Unix(),
|
||||
UserInfo: uInfo,
|
||||
UserInfo: userInfo,
|
||||
Tags: tags,
|
||||
VoteCount: converter.StringToInt(string(r["vote_count"])),
|
||||
Accepted: string(r["accepted"]) == "2",
|
||||
|
@ -297,8 +292,8 @@ func (sr *searchRepo) parseResult(ctx context.Context, res []map[string][]byte)
|
|||
// userBasicInfoFormat
|
||||
func (sr *searchRepo) userBasicInfoFormat(ctx context.Context, dbinfo *entity.User) *schema.UserBasicInfo {
|
||||
return &schema.UserBasicInfo{
|
||||
UserId: dbinfo.ID,
|
||||
UserName: dbinfo.Username,
|
||||
ID: dbinfo.ID,
|
||||
Username: dbinfo.Username,
|
||||
Rank: dbinfo.Rank,
|
||||
DisplayName: dbinfo.DisplayName,
|
||||
Avatar: dbinfo.Avatar,
|
||||
|
|
|
@ -73,10 +73,11 @@ func (tr *tagRepo) GetTagBySlugName(ctx context.Context, slugName string) (tagIn
|
|||
}
|
||||
|
||||
// GetTagListByName get tag list all like name
|
||||
func (tr *tagRepo) GetTagListByName(ctx context.Context, name string) (tagList []*entity.Tag, err error) {
|
||||
func (tr *tagRepo) GetTagListByName(ctx context.Context, name string, limit int) (tagList []*entity.Tag, err error) {
|
||||
tagList = make([]*entity.Tag, 0)
|
||||
session := tr.data.DB.Where(builder.Like{"slug_name", name})
|
||||
session := tr.data.DB.Where("LIKE ?", name+"%")
|
||||
session.Where(builder.Eq{"status": entity.TagStatusAvailable})
|
||||
session.Limit(limit).Asc("slug_name")
|
||||
err = session.Find(&tagList)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
|
|
|
@ -3,4 +3,4 @@ package router
|
|||
import "github.com/google/wire"
|
||||
|
||||
// ProviderSetRouter is providers.
|
||||
var ProviderSetRouter = wire.NewSet(NewAnswerAPIRouter, NewSwaggerRouter, NewStaticRouter, NewViewRouter)
|
||||
var ProviderSetRouter = wire.NewSet(NewAnswerAPIRouter, NewSwaggerRouter, NewStaticRouter, NewUIRouter)
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/segmentfault/answer/ui"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
const UIIndexFilePath = "build/index.html"
|
||||
const UIStaticPath = "build/static"
|
||||
|
||||
// UIRouter is an interface that provides ui static file routers
|
||||
type UIRouter struct {
|
||||
}
|
||||
|
||||
// NewUIRouter creates a new UIRouter instance with the embed resources
|
||||
func NewUIRouter() *UIRouter {
|
||||
return &UIRouter{}
|
||||
}
|
||||
|
||||
// _resource is an interface that provides static file, it's a private interface
|
||||
type _resource struct {
|
||||
fs embed.FS
|
||||
}
|
||||
|
||||
// Open to implement the interface by http.FS required
|
||||
func (r *_resource) Open(name string) (fs.File, error) {
|
||||
name = fmt.Sprintf(UIStaticPath+"/%s", name)
|
||||
log.Debugf("open static path %s", name)
|
||||
return r.fs.Open(name)
|
||||
}
|
||||
|
||||
// Register a new static resource which generated by ui directory
|
||||
func (a *UIRouter) Register(r *gin.Engine) {
|
||||
staticPath := os.Getenv("ANSWER_STATIC_PATH")
|
||||
|
||||
// if ANSWER_STATIC_PATH is set and not empty, ignore embed resource
|
||||
if staticPath != "" {
|
||||
info, err := os.Stat(staticPath)
|
||||
|
||||
if err != nil || !info.IsDir() {
|
||||
log.Error(err)
|
||||
} else {
|
||||
log.Debugf("registering static path %s", staticPath)
|
||||
|
||||
r.LoadHTMLGlob(staticPath + "/*.html")
|
||||
r.Static("/static", staticPath+"/static")
|
||||
r.NoRoute(func(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "index.html", gin.H{})
|
||||
})
|
||||
|
||||
// return immediately if the static path is set
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// handle the static file by default ui static files
|
||||
r.StaticFS("/static", http.FS(&_resource{
|
||||
fs: ui.Build,
|
||||
}))
|
||||
|
||||
// specify the not router for default routes and redirect
|
||||
r.NoRoute(func(c *gin.Context) {
|
||||
index, err := ui.Build.ReadFile(UIIndexFilePath)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
c.Status(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("content-type", "text/html;charset=utf-8")
|
||||
c.String(http.StatusOK, string(index))
|
||||
})
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUIRouter_Register(t *testing.T) {
|
||||
r := gin.Default()
|
||||
|
||||
NewUIRouter().Register(r)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/", nil)
|
||||
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
func TestUIRouter_Static(t *testing.T) {
|
||||
r := gin.Default()
|
||||
|
||||
NewUIRouter().Register(r)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/static/version.txt", nil)
|
||||
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, "OK", w.Body.String())
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"errors"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/segmentfault/answer/web"
|
||||
)
|
||||
|
||||
// RegisterViewRouter
|
||||
type ViewRouter struct {
|
||||
}
|
||||
|
||||
// NewRegisterViewRouter
|
||||
func NewViewRouter() *ViewRouter {
|
||||
return &ViewRouter{}
|
||||
}
|
||||
|
||||
type Resource struct {
|
||||
fs embed.FS
|
||||
path string
|
||||
}
|
||||
|
||||
func NewResource() *Resource {
|
||||
return &Resource{
|
||||
fs: web.Static,
|
||||
path: "html",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Resource) Open(name string) (fs.File, error) {
|
||||
if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) {
|
||||
return nil, errors.New("http: invalid character in file path")
|
||||
}
|
||||
fullName := filepath.Join(r.path, filepath.FromSlash(path.Clean("/static/"+name)))
|
||||
file, err := r.fs.Open(fullName)
|
||||
return file, err
|
||||
}
|
||||
|
||||
func (a *ViewRouter) RegisterViewRouter(r *gin.Engine) {
|
||||
//export answer_html_static_path="../../web/static"
|
||||
//export answer_html_page_path="../../web"
|
||||
static := os.Getenv("answer_html_static_path")
|
||||
index := os.Getenv("answer_html_page_path")
|
||||
if len(static) > 0 && len(index) > 0 {
|
||||
r.LoadHTMLGlob(index + "/*.html")
|
||||
r.Static("/static", static)
|
||||
r.NoRoute(func(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "index.html", gin.H{})
|
||||
})
|
||||
return
|
||||
} else {
|
||||
r.StaticFS("/static", http.FS(NewResource()))
|
||||
r.NoRoute(func(c *gin.Context) {
|
||||
c.Header("content-type", "text/html;charset=utf-8")
|
||||
c.String(200, string(web.Html))
|
||||
})
|
||||
}
|
||||
|
||||
}
|
|
@ -1,95 +1,13 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// RemoveAnswerReq delete answer request
|
||||
type RemoveAnswerReq struct {
|
||||
// answer id
|
||||
ID string `validate:"required" comment:"answer id" json:"id"`
|
||||
ID string `validate:"required" json:"id"`
|
||||
// user id
|
||||
UserID string `json:"-"`
|
||||
}
|
||||
|
||||
// GetAnswerListReq get answer list all request
|
||||
type GetAnswerListReq struct {
|
||||
// question id
|
||||
QuestionID int64 `validate:"omitempty" comment:"question id" form:"question_id"`
|
||||
// answer user id
|
||||
UserID int64 `validate:"omitempty" comment:"answer user id" form:"user_id"`
|
||||
// content markdown
|
||||
Content string `validate:"omitempty" comment:"content markdown" form:"content"`
|
||||
// content html
|
||||
Html string `validate:"omitempty" comment:"content html" form:"html"`
|
||||
// answer status(available: 1; deleted: 10)
|
||||
Status int `validate:"omitempty" comment:" answer status(available: 1; deleted: 10)" form:"status"`
|
||||
// adopted (1 failed 2 adopted)
|
||||
Adopted int `validate:"omitempty" comment:"adopted (1 failed 2 adopted)" form:"adopted"`
|
||||
// comment count
|
||||
CommentCount int `validate:"omitempty" comment:"comment count" form:"comment_count"`
|
||||
// vote count
|
||||
VoteCount int `validate:"omitempty" comment:"vote count" form:"vote_count"`
|
||||
//
|
||||
CreateTime time.Time `validate:"omitempty" comment:"" form:"create_time"`
|
||||
//
|
||||
UpdateTime time.Time `validate:"omitempty" comment:"" form:"update_time"`
|
||||
}
|
||||
|
||||
// GetAnswerWithPageReq get answer list page request
|
||||
type GetAnswerWithPageReq struct {
|
||||
// page
|
||||
Page int `validate:"omitempty,min=1" form:"page"`
|
||||
// page size
|
||||
PageSize int `validate:"omitempty,min=1" form:"page_size"`
|
||||
// question id
|
||||
QuestionID int64 `validate:"omitempty" comment:"question id" form:"question_id"`
|
||||
// answer user id
|
||||
UserID int64 `validate:"omitempty" comment:"answer user id" form:"user_id"`
|
||||
// content markdown
|
||||
Content string `validate:"omitempty" comment:"content markdown" form:"content"`
|
||||
// content html
|
||||
Html string `validate:"omitempty" comment:"content html" form:"html"`
|
||||
// answer status(available: 1; deleted: 10)
|
||||
Status int `validate:"omitempty" comment:" answer status(available: 1; deleted: 10)" form:"status"`
|
||||
// adopted (1 failed 2 adopted)
|
||||
Adopted int `validate:"omitempty" comment:"adopted (1 failed 2 adopted)" form:"adopted"`
|
||||
// comment count
|
||||
CommentCount int `validate:"omitempty" comment:"comment count" form:"comment_count"`
|
||||
// vote count
|
||||
VoteCount int `validate:"omitempty" comment:"vote count" form:"vote_count"`
|
||||
//
|
||||
CreateTime time.Time `validate:"omitempty" comment:"" form:"create_time"`
|
||||
//
|
||||
UpdateTime time.Time `validate:"omitempty" comment:"" form:"update_time"`
|
||||
}
|
||||
|
||||
// GetAnswerResp get answer response
|
||||
type GetAnswerResp struct {
|
||||
// answer id
|
||||
ID int64 `json:"id"`
|
||||
// question id
|
||||
QuestionID int64 `json:"question_id"`
|
||||
// answer user id
|
||||
UserID int64 `json:"user_id"`
|
||||
// content markdown
|
||||
Content string `json:"content"`
|
||||
// content html
|
||||
Html string `json:"html"`
|
||||
// answer status(available: 1; deleted: 10)
|
||||
Status int `json:"status"`
|
||||
// adopted (1 failed 2 adopted)
|
||||
Adopted int `json:"adopted"`
|
||||
// comment count
|
||||
CommentCount int `json:"comment_count"`
|
||||
// vote count
|
||||
VoteCount int `json:"vote_count"`
|
||||
//
|
||||
CreateTime time.Time `json:"create_time"`
|
||||
//
|
||||
UpdateTime time.Time `json:"update_time"`
|
||||
}
|
||||
|
||||
const (
|
||||
Answer_Adopted_Failed = 1
|
||||
Answer_Adopted_Enable = 2
|
||||
|
|
|
@ -9,16 +9,16 @@ type UpdateUserStatusReq struct {
|
|||
}
|
||||
|
||||
const (
|
||||
Normal = "normal"
|
||||
Suspended = "suspended"
|
||||
Deleted = "deleted"
|
||||
Inactive = "inactive"
|
||||
UserNormal = "normal"
|
||||
UserSuspended = "suspended"
|
||||
UserDeleted = "deleted"
|
||||
UserInactive = "inactive"
|
||||
)
|
||||
|
||||
func (r *UpdateUserStatusReq) IsNormal() bool { return r.Status == Normal }
|
||||
func (r *UpdateUserStatusReq) IsSuspended() bool { return r.Status == Suspended }
|
||||
func (r *UpdateUserStatusReq) IsDeleted() bool { return r.Status == Deleted }
|
||||
func (r *UpdateUserStatusReq) IsInactive() bool { return r.Status == Inactive }
|
||||
func (r *UpdateUserStatusReq) IsNormal() bool { return r.Status == UserNormal }
|
||||
func (r *UpdateUserStatusReq) IsSuspended() bool { return r.Status == UserSuspended }
|
||||
func (r *UpdateUserStatusReq) IsDeleted() bool { return r.Status == UserDeleted }
|
||||
func (r *UpdateUserStatusReq) IsInactive() bool { return r.Status == UserInactive }
|
||||
|
||||
// GetUserPageReq get user list page request
|
||||
type GetUserPageReq struct {
|
||||
|
@ -34,9 +34,9 @@ type GetUserPageReq struct {
|
|||
Status string `validate:"omitempty,oneof=suspended deleted inactive" form:"status"`
|
||||
}
|
||||
|
||||
func (r *GetUserPageReq) IsSuspended() bool { return r.Status == Suspended }
|
||||
func (r *GetUserPageReq) IsDeleted() bool { return r.Status == Deleted }
|
||||
func (r *GetUserPageReq) IsInactive() bool { return r.Status == Inactive }
|
||||
func (r *GetUserPageReq) IsSuspended() bool { return r.Status == UserSuspended }
|
||||
func (r *GetUserPageReq) IsDeleted() bool { return r.Status == UserDeleted }
|
||||
func (r *GetUserPageReq) IsInactive() bool { return r.Status == UserInactive }
|
||||
|
||||
// GetUserPageResp get user response
|
||||
type GetUserPageResp struct {
|
||||
|
|
|
@ -43,12 +43,6 @@ type AddCollectionGroupReq struct {
|
|||
UpdateTime time.Time `validate:"required" comment:"" json:"update_time"`
|
||||
}
|
||||
|
||||
// RemoveCollectionGroupReq delete collection group request
|
||||
type RemoveCollectionGroupReq struct {
|
||||
//
|
||||
ID int64 `validate:"required" comment:"" json:"id"`
|
||||
}
|
||||
|
||||
// UpdateCollectionGroupReq update collection group request
|
||||
type UpdateCollectionGroupReq struct {
|
||||
//
|
||||
|
@ -65,38 +59,6 @@ type UpdateCollectionGroupReq struct {
|
|||
UpdateTime time.Time `validate:"omitempty" comment:"" json:"update_time"`
|
||||
}
|
||||
|
||||
// GetCollectionGroupListReq get collection group list all request
|
||||
type GetCollectionGroupListReq struct {
|
||||
//
|
||||
UserID int64 `validate:"omitempty" comment:"" form:"user_id"`
|
||||
// the collection group name
|
||||
Name string `validate:"omitempty,gt=0,lte=50" comment:"the collection group name" form:"name"`
|
||||
// mark this group is default, default 1
|
||||
DefaultGroup int `validate:"omitempty" comment:"mark this group is default, default 1" form:"default_group"`
|
||||
//
|
||||
CreateTime time.Time `validate:"omitempty" comment:"" form:"create_time"`
|
||||
//
|
||||
UpdateTime time.Time `validate:"omitempty" comment:"" form:"update_time"`
|
||||
}
|
||||
|
||||
// GetCollectionGroupWithPageReq get collection group list page request
|
||||
type GetCollectionGroupWithPageReq struct {
|
||||
// page
|
||||
Page int `validate:"omitempty,min=1" form:"page"`
|
||||
// page size
|
||||
PageSize int `validate:"omitempty,min=1" form:"page_size"`
|
||||
//
|
||||
UserID int64 `validate:"omitempty" comment:"" form:"user_id"`
|
||||
// the collection group name
|
||||
Name string `validate:"omitempty,gt=0,lte=50" comment:"the collection group name" form:"name"`
|
||||
// mark this group is default, default 1
|
||||
DefaultGroup int `validate:"omitempty" comment:"mark this group is default, default 1" form:"default_group"`
|
||||
//
|
||||
CreateTime time.Time `validate:"omitempty" comment:"" form:"create_time"`
|
||||
//
|
||||
UpdateTime time.Time `validate:"omitempty" comment:"" form:"update_time"`
|
||||
}
|
||||
|
||||
// GetCollectionGroupResp get collection group response
|
||||
type GetCollectionGroupResp struct {
|
||||
//
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
package schema
|
||||
|
||||
import "time"
|
||||
|
||||
// AddCollectionReq add collection request
|
||||
type AddCollectionReq struct {
|
||||
// user id
|
||||
UserID int64 `validate:"required" comment:"user id" json:"user_id"`
|
||||
// object id
|
||||
ObjectID int64 `validate:"required" comment:"object id" json:"object_id"`
|
||||
// user collection group id
|
||||
UserCollectionGroupID int64 `validate:"required" comment:"user collection group id" json:"user_collection_group_id"`
|
||||
//
|
||||
CreateTime time.Time `validate:"required" comment:"" json:"create_time"`
|
||||
//
|
||||
UpdateTime time.Time `validate:"required" comment:"" json:"update_time"`
|
||||
}
|
||||
|
||||
// RemoveCollectionReq delete collection request
|
||||
type RemoveCollectionReq struct {
|
||||
// collection id
|
||||
ID int64 `validate:"required" comment:"collection id" json:"id"`
|
||||
}
|
||||
|
||||
// UpdateCollectionReq update collection request
|
||||
type UpdateCollectionReq struct {
|
||||
// collection id
|
||||
ID int64 `validate:"required" comment:"collection id" json:"id"`
|
||||
// user id
|
||||
UserID int64 `validate:"omitempty" comment:"user id" json:"user_id"`
|
||||
// object id
|
||||
ObjectID int64 `validate:"omitempty" comment:"object id" json:"object_id"`
|
||||
// user collection group id
|
||||
UserCollectionGroupID int64 `validate:"omitempty" comment:"user collection group id" json:"user_collection_group_id"`
|
||||
//
|
||||
CreateTime time.Time `validate:"omitempty" comment:"" json:"create_time"`
|
||||
//
|
||||
UpdateTime time.Time `validate:"omitempty" comment:"" json:"update_time"`
|
||||
}
|
||||
|
||||
// GetCollectionListReq get collection list all request
|
||||
type GetCollectionListReq struct {
|
||||
// user id
|
||||
UserID int64 `validate:"omitempty" comment:"user id" form:"user_id"`
|
||||
// object id
|
||||
ObjectID int64 `validate:"omitempty" comment:"object id" form:"object_id"`
|
||||
// user collection group id
|
||||
UserCollectionGroupID int64 `validate:"omitempty" comment:"user collection group id" form:"user_collection_group_id"`
|
||||
//
|
||||
CreateTime time.Time `validate:"omitempty" comment:"" form:"create_time"`
|
||||
//
|
||||
UpdateTime time.Time `validate:"omitempty" comment:"" form:"update_time"`
|
||||
}
|
||||
|
||||
// GetCollectionWithPageReq get collection list page request
|
||||
type GetCollectionWithPageReq struct {
|
||||
// page
|
||||
Page int `validate:"omitempty,min=1" form:"page"`
|
||||
// page size
|
||||
PageSize int `validate:"omitempty,min=1" form:"page_size"`
|
||||
// user id
|
||||
UserID int64 `validate:"omitempty" comment:"user id" form:"user_id"`
|
||||
// object id
|
||||
ObjectID int64 `validate:"omitempty" comment:"object id" form:"object_id"`
|
||||
// user collection group id
|
||||
UserCollectionGroupID int64 `validate:"omitempty" comment:"user collection group id" form:"user_collection_group_id"`
|
||||
//
|
||||
CreateTime time.Time `validate:"omitempty" comment:"" form:"create_time"`
|
||||
//
|
||||
UpdateTime time.Time `validate:"omitempty" comment:"" form:"update_time"`
|
||||
}
|
||||
|
||||
// GetCollectionResp get collection response
|
||||
type GetCollectionResp struct {
|
||||
// collection id
|
||||
ID int64 `json:"id"`
|
||||
// user id
|
||||
UserID int64 `json:"user_id"`
|
||||
// object id
|
||||
ObjectID int64 `json:"object_id"`
|
||||
// user collection group id
|
||||
UserCollectionGroupID int64 `json:"user_collection_group_id"`
|
||||
//
|
||||
CreateTime time.Time `json:"create_time"`
|
||||
//
|
||||
UpdateTime time.Time `json:"update_time"`
|
||||
}
|
|
@ -109,6 +109,8 @@ type GetCommentResp struct {
|
|||
UserDisplayName string `json:"user_display_name"`
|
||||
// user avatar
|
||||
UserAvatar string `json:"user_avatar"`
|
||||
// user status
|
||||
UserStatus string `json:"user_status"`
|
||||
|
||||
// reply user id
|
||||
ReplyUserID string `json:"reply_user_id"`
|
||||
|
@ -118,6 +120,8 @@ type GetCommentResp struct {
|
|||
ReplyUserDisplayName string `json:"reply_user_display_name"`
|
||||
// reply comment id
|
||||
ReplyCommentID string `json:"reply_comment_id"`
|
||||
// reply user status
|
||||
ReplyUserStatus string `json:"reply_user_status"`
|
||||
|
||||
// MemberActions
|
||||
MemberActions []*PermissionMemberAction `json:"member_actions"`
|
||||
|
|
|
@ -22,7 +22,7 @@ type CloseQuestionMeta struct {
|
|||
|
||||
type QuestionAdd struct {
|
||||
// question title
|
||||
Title string `validate:"required,gte=6,lte=64" json:"title"`
|
||||
Title string `validate:"required,gte=6,lte=150" json:"title"`
|
||||
// content
|
||||
Content string `validate:"required,gte=6,lte=65535" json:"content"`
|
||||
// html
|
||||
|
@ -37,7 +37,7 @@ type QuestionUpdate struct {
|
|||
// question id
|
||||
ID string `validate:"required" json:"id"`
|
||||
// question title
|
||||
Title string `validate:"required,gte=6,lte=64" json:"title"`
|
||||
Title string `validate:"required,gte=6,lte=150" json:"title"`
|
||||
// content
|
||||
Content string `validate:"required,gte=6,lte=65535" json:"content"`
|
||||
// html
|
||||
|
@ -166,7 +166,7 @@ type CmsQuestionSearch struct {
|
|||
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 UserDeleted
|
||||
}
|
||||
|
||||
type AdminSetQuestionStatusRequest struct {
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
// SearchTagLikeReq get tag list all request
|
||||
type SearchTagLikeReq struct {
|
||||
// tag
|
||||
Tag string `validate:"required,gt=0,lte=50" form:"tag"`
|
||||
Tag string `validate:"required,gt=0,lte=35" form:"tag"`
|
||||
}
|
||||
|
||||
// GetTagInfoReq get tag info request
|
||||
|
@ -19,7 +19,7 @@ type GetTagInfoReq struct {
|
|||
// tag id
|
||||
ID string `validate:"omitempty" form:"id"`
|
||||
// tag slug name
|
||||
Name string `validate:"omitempty,gt=0,lte=50" form:"name"`
|
||||
Name string `validate:"omitempty,gt=0,lte=35" form:"name"`
|
||||
// user id
|
||||
UserID string `json:"-"`
|
||||
}
|
||||
|
@ -115,9 +115,9 @@ type TagChange struct {
|
|||
|
||||
type TagItem struct {
|
||||
// slug_name
|
||||
SlugName string `validate:"omitempty,gt=0,lte=50" json:"slug_name"`
|
||||
SlugName string `validate:"omitempty,gt=0,lte=35" json:"slug_name"`
|
||||
// display_name
|
||||
DisplayName string `validate:"omitempty,gt=0,lte=50" json:"display_name"`
|
||||
DisplayName string `validate:"omitempty,gt=0,lte=35" json:"display_name"`
|
||||
// original text
|
||||
OriginalText string `validate:"omitempty" json:"original_text"`
|
||||
// parsed text
|
||||
|
@ -137,9 +137,9 @@ type UpdateTagReq struct {
|
|||
// tag_id
|
||||
TagID string `validate:"required" json:"tag_id"`
|
||||
// slug_name
|
||||
SlugName string `validate:"omitempty,gt=0,lte=50" json:"slug_name"`
|
||||
SlugName string `validate:"omitempty,gt=0,lte=35" json:"slug_name"`
|
||||
// display_name
|
||||
DisplayName string `validate:"omitempty,gt=0,lte=50" json:"display_name"`
|
||||
DisplayName string `validate:"omitempty,gt=0,lte=35" json:"display_name"`
|
||||
// original text
|
||||
OriginalText string `validate:"omitempty" json:"original_text"`
|
||||
// parsed text
|
||||
|
@ -164,9 +164,9 @@ type GetTagWithPageReq struct {
|
|||
// page size
|
||||
PageSize int `validate:"omitempty,min=1" form:"page_size"`
|
||||
// slug_name
|
||||
SlugName string `validate:"omitempty,gt=0,lte=50" form:"slug_name"`
|
||||
SlugName string `validate:"omitempty,gt=0,lte=35" form:"slug_name"`
|
||||
// display_name
|
||||
DisplayName string `validate:"omitempty,gt=0,lte=50" form:"display_name"`
|
||||
DisplayName string `validate:"omitempty,gt=0,lte=35" form:"display_name"`
|
||||
// query condition
|
||||
QueryCond string `validate:"omitempty,oneof=popular name newest" form:"query_cond"`
|
||||
// user id
|
||||
|
|
|
@ -166,7 +166,7 @@ const (
|
|||
var UserStatusShow = map[int]string{
|
||||
1: "normal",
|
||||
9: "forbidden",
|
||||
10: "delete",
|
||||
10: "deleted",
|
||||
}
|
||||
var UserStatusShowMsg = map[int]string{
|
||||
1: "",
|
||||
|
@ -226,14 +226,20 @@ func (u *UserModifyPassWordRequest) Check() (errField *validator.ErrorField, err
|
|||
}
|
||||
|
||||
type UpdateInfoRequest struct {
|
||||
UserId string `json:"-" ` // user_id
|
||||
UserName string `json:"username"` // name
|
||||
DisplayName string `json:"display_name" ` // display_name
|
||||
Avatar string `json:"avatar" ` // avatar
|
||||
Bio string `json:"bio"`
|
||||
BioHtml string `json:"bio_html"`
|
||||
Website string `json:"website" ` // website
|
||||
Location string `json:"location"` // location
|
||||
// display_name
|
||||
DisplayName string `validate:"required,gt=0,lte=30" json:"display_name"`
|
||||
// avatar
|
||||
Avatar string `validate:"omitempty,gt=0,lte=500" json:"avatar"`
|
||||
// bio
|
||||
Bio string `validate:"omitempty,gt=0,lte=4096" json:"bio"`
|
||||
// bio
|
||||
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:"-" `
|
||||
}
|
||||
|
||||
type UserRetrievePassWordRequest struct {
|
||||
|
@ -282,15 +288,15 @@ type ActionRecordResp struct {
|
|||
}
|
||||
|
||||
type UserBasicInfo struct {
|
||||
UserId string `json:"-" ` // user_id
|
||||
UserName string `json:"username" ` // name
|
||||
ID string `json:"-" ` // user_id
|
||||
Username string `json:"username" ` // name
|
||||
Rank int `json:"rank" ` // rank
|
||||
DisplayName string `json:"display_name"` // display_name
|
||||
Avatar string `json:"avatar" ` // avatar
|
||||
Website string `json:"website" ` // website
|
||||
Location string `json:"location" ` // location
|
||||
IpInfo string `json:"ip_info"` // ip info
|
||||
Status int `json:"status"` // status
|
||||
Status string `json:"status"` // status
|
||||
}
|
||||
|
||||
type GetOtherUserInfoByUsernameReq struct {
|
||||
|
|
|
@ -32,7 +32,7 @@ type CommentRepo interface {
|
|||
type CommentService struct {
|
||||
commentRepo CommentRepo
|
||||
commentCommonRepo comment_common.CommentCommonRepo
|
||||
userRepo usercommon.UserRepo
|
||||
userCommon *usercommon.UserCommon
|
||||
voteCommon activity_common.VoteRepo
|
||||
objectInfoService *object_info.ObjService
|
||||
}
|
||||
|
@ -61,13 +61,13 @@ func (c *CommentQuery) GetOrderBy() string {
|
|||
func NewCommentService(
|
||||
commentRepo CommentRepo,
|
||||
commentCommonRepo comment_common.CommentCommonRepo,
|
||||
userRepo usercommon.UserRepo,
|
||||
userCommon *usercommon.UserCommon,
|
||||
objectInfoService *object_info.ObjService,
|
||||
voteCommon activity_common.VoteRepo) *CommentService {
|
||||
return &CommentService{
|
||||
commentRepo: commentRepo,
|
||||
commentCommonRepo: commentCommonRepo,
|
||||
userRepo: userRepo,
|
||||
userCommon: userCommon,
|
||||
voteCommon: voteCommon,
|
||||
objectInfoService: objectInfoService,
|
||||
}
|
||||
|
@ -124,19 +124,20 @@ func (cs *CommentService) AddComment(ctx context.Context, req *schema.AddComment
|
|||
|
||||
// get reply user info
|
||||
if len(resp.ReplyUserID) > 0 {
|
||||
replyUser, exist, err := cs.userRepo.GetByUserID(ctx, resp.ReplyUserID)
|
||||
replyUser, exist, err := cs.userCommon.GetUserBasicInfoByID(ctx, resp.ReplyUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exist {
|
||||
resp.ReplyUsername = replyUser.Username
|
||||
resp.ReplyUserDisplayName = replyUser.DisplayName
|
||||
resp.ReplyUserStatus = replyUser.Status
|
||||
}
|
||||
cs.notificationCommentReply(ctx, replyUser.ID, objInfo.QuestionID, req.UserID)
|
||||
}
|
||||
|
||||
// get user info
|
||||
userInfo, exist, err := cs.userRepo.GetByUserID(ctx, resp.UserID)
|
||||
userInfo, exist, err := cs.userCommon.GetUserBasicInfoByID(ctx, resp.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -144,6 +145,7 @@ func (cs *CommentService) AddComment(ctx context.Context, req *schema.AddComment
|
|||
resp.Username = userInfo.Username
|
||||
resp.UserDisplayName = userInfo.DisplayName
|
||||
resp.UserAvatar = userInfo.Avatar
|
||||
resp.UserStatus = userInfo.Status
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
@ -191,7 +193,7 @@ func (cs *CommentService) GetComment(ctx context.Context, req *schema.GetComment
|
|||
|
||||
// get comment user info
|
||||
if len(resp.UserID) > 0 {
|
||||
commentUser, exist, err := cs.userRepo.GetByUserID(ctx, resp.UserID)
|
||||
commentUser, exist, err := cs.userCommon.GetUserBasicInfoByID(ctx, resp.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -199,18 +201,20 @@ func (cs *CommentService) GetComment(ctx context.Context, req *schema.GetComment
|
|||
resp.Username = commentUser.Username
|
||||
resp.UserDisplayName = commentUser.DisplayName
|
||||
resp.UserAvatar = commentUser.Avatar
|
||||
resp.UserStatus = commentUser.Status
|
||||
}
|
||||
}
|
||||
|
||||
// get reply user info
|
||||
if len(resp.ReplyUserID) > 0 {
|
||||
replyUser, exist, err := cs.userRepo.GetByUserID(ctx, resp.ReplyUserID)
|
||||
replyUser, exist, err := cs.userCommon.GetUserBasicInfoByID(ctx, resp.ReplyUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exist {
|
||||
resp.ReplyUsername = replyUser.Username
|
||||
resp.ReplyUserDisplayName = replyUser.DisplayName
|
||||
resp.ReplyUserStatus = replyUser.Status
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -249,7 +253,7 @@ func (cs *CommentService) GetCommentWithPage(ctx context.Context, req *schema.Ge
|
|||
|
||||
// get comment user info
|
||||
if len(commentResp.UserID) > 0 {
|
||||
commentUser, exist, err := cs.userRepo.GetByUserID(ctx, commentResp.UserID)
|
||||
commentUser, exist, err := cs.userCommon.GetUserBasicInfoByID(ctx, commentResp.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -257,18 +261,20 @@ func (cs *CommentService) GetCommentWithPage(ctx context.Context, req *schema.Ge
|
|||
commentResp.Username = commentUser.Username
|
||||
commentResp.UserDisplayName = commentUser.DisplayName
|
||||
commentResp.UserAvatar = commentUser.Avatar
|
||||
commentResp.UserStatus = commentUser.Status
|
||||
}
|
||||
}
|
||||
|
||||
// get reply user info
|
||||
if len(commentResp.ReplyUserID) > 0 {
|
||||
replyUser, exist, err := cs.userRepo.GetByUserID(ctx, commentResp.ReplyUserID)
|
||||
replyUser, exist, err := cs.userCommon.GetUserBasicInfoByID(ctx, commentResp.ReplyUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exist {
|
||||
commentResp.ReplyUsername = replyUser.Username
|
||||
commentResp.ReplyUserDisplayName = replyUser.DisplayName
|
||||
commentResp.ReplyUserStatus = replyUser.Status
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -278,7 +284,7 @@ func (cs *CommentService) GetCommentWithPage(ctx context.Context, req *schema.Ge
|
|||
commentResp.MemberActions = permission.GetCommentPermission(req.UserID, commentResp.UserID)
|
||||
resp = append(resp, commentResp)
|
||||
}
|
||||
return pager.NewPageModel(req.Page, req.PageSize, total, resp), nil
|
||||
return pager.NewPageModel(total, resp), nil
|
||||
}
|
||||
|
||||
func (cs *CommentService) checkCommentWhetherOwner(ctx context.Context, userID, commentID string) error {
|
||||
|
@ -305,7 +311,7 @@ func (cs *CommentService) checkIsVote(ctx context.Context, userID, commentID str
|
|||
func (cs *CommentService) GetCommentPersonalWithPage(ctx context.Context, req *schema.GetCommentPersonalWithPageReq) (
|
||||
pageModel *pager.PageModel, err error) {
|
||||
if len(req.Username) > 0 {
|
||||
userInfo, exist, err := cs.userRepo.GetByUsername(ctx, req.Username)
|
||||
userInfo, exist, err := cs.userCommon.GetUserBasicInfoByUserName(ctx, req.Username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -348,7 +354,7 @@ func (cs *CommentService) GetCommentPersonalWithPage(ctx context.Context, req *s
|
|||
}
|
||||
resp = append(resp, commentResp)
|
||||
}
|
||||
return pager.NewPageModel(req.Page, req.PageSize, total, resp), nil
|
||||
return pager.NewPageModel(total, resp), nil
|
||||
}
|
||||
|
||||
func (cs *CommentService) notificationQuestionComment(ctx context.Context, questionUserID, commentID, commentUserID string) {
|
||||
|
@ -389,7 +395,7 @@ func (cs *CommentService) notificationCommentReply(ctx context.Context, replyUse
|
|||
|
||||
func (cs *CommentService) notificationMention(ctx context.Context, mentionUsernameList []string, commentID, commentUserID string) {
|
||||
for _, username := range mentionUsernameList {
|
||||
userInfo, exist, err := cs.userRepo.GetByUsername(ctx, username)
|
||||
userInfo, exist, err := cs.userCommon.GetUserBasicInfoByUserName(ctx, username)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
continue
|
||||
|
|
|
@ -97,5 +97,5 @@ func (ns *NotificationReadService) GetNotificationReadWithPage(ctx context.Conte
|
|||
resp := &[]schema.GetNotificationReadResp{}
|
||||
_ = copier.Copy(resp, notificationReads)
|
||||
|
||||
return pager.NewPageModel(page, pageSize, total, resp), nil
|
||||
return pager.NewPageModel(total, resp), nil
|
||||
}
|
||||
|
|
|
@ -5,9 +5,13 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/segmentfault/answer/internal/base/constant"
|
||||
"github.com/segmentfault/answer/internal/base/data"
|
||||
"github.com/segmentfault/answer/internal/base/pager"
|
||||
"github.com/segmentfault/answer/internal/base/translator"
|
||||
"github.com/segmentfault/answer/internal/schema"
|
||||
notficationcommon "github.com/segmentfault/answer/internal/service/notification_common"
|
||||
"github.com/segmentfault/pacman/i18n"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
|
||||
|
@ -91,30 +95,33 @@ func (ns *NotificationService) ClearIDUnRead(ctx context.Context, userID string,
|
|||
return nil
|
||||
}
|
||||
|
||||
func (ns *NotificationService) GetList(ctx context.Context, search *schema.NotificationSearch) ([]*schema.NotificationContent, int64, error) {
|
||||
list := make([]*schema.NotificationContent, 0)
|
||||
func (ns *NotificationService) GetList(ctx context.Context, search *schema.NotificationSearch) (
|
||||
pageModel *pager.PageModel, err error) {
|
||||
resp := make([]*schema.NotificationContent, 0)
|
||||
searchType, ok := schema.NotificationType[search.TypeStr]
|
||||
if !ok {
|
||||
return list, 0, nil
|
||||
return pager.NewPageModel(0, resp), nil
|
||||
}
|
||||
search.Type = searchType
|
||||
dblist, count, err := ns.notificationRepo.SearchList(ctx, search)
|
||||
notifications, count, err := ns.notificationRepo.SearchList(ctx, search)
|
||||
if err != nil {
|
||||
return list, count, err
|
||||
return nil, err
|
||||
}
|
||||
for _, dbitem := range dblist {
|
||||
for _, notificationInfo := range notifications {
|
||||
item := &schema.NotificationContent{}
|
||||
err := json.Unmarshal([]byte(dbitem.Content), item)
|
||||
err := json.Unmarshal([]byte(notificationInfo.Content), item)
|
||||
if err != nil {
|
||||
log.Error("NotificationContent Unmarshal Error", err.Error())
|
||||
continue
|
||||
}
|
||||
item.ID = dbitem.ID
|
||||
item.UpdateTime = dbitem.UpdatedAt.Unix()
|
||||
if dbitem.IsRead == schema.NotificationRead {
|
||||
lang, _ := ctx.Value(constant.AcceptLanguageFlag).(i18n.Language)
|
||||
item.NotificationAction = translator.GlobalTrans.Tr(lang, item.NotificationAction)
|
||||
item.ID = notificationInfo.ID
|
||||
item.UpdateTime = notificationInfo.UpdatedAt.Unix()
|
||||
if notificationInfo.IsRead == schema.NotificationRead {
|
||||
item.IsRead = true
|
||||
}
|
||||
list = append(list, item)
|
||||
resp = append(resp, item)
|
||||
}
|
||||
return list, count, nil
|
||||
return pager.NewPageModel(count, resp), nil
|
||||
}
|
||||
|
|
|
@ -265,7 +265,7 @@ func (qs *QuestionService) SearchUserList(ctx context.Context, userName, order s
|
|||
search.Order = order
|
||||
search.Page = page
|
||||
search.PageSize = pageSize
|
||||
search.UserID = userinfo.UserId
|
||||
search.UserID = userinfo.ID
|
||||
questionlist, count, err := qs.SearchList(ctx, search, loginUserID)
|
||||
if err != nil {
|
||||
return userlist, 0, err
|
||||
|
@ -289,7 +289,7 @@ func (qs *QuestionService) SearchUserAnswerList(ctx context.Context, userName, o
|
|||
return userAnswerlist, 0, nil
|
||||
}
|
||||
answersearch := &entity.AnswerSearch{}
|
||||
answersearch.UserID = userinfo.UserId
|
||||
answersearch.UserID = userinfo.ID
|
||||
answersearch.PageSize = pageSize
|
||||
answersearch.Page = page
|
||||
if order == "newest" {
|
||||
|
@ -337,7 +337,7 @@ func (qs *QuestionService) SearchUserCollectionList(ctx context.Context, page, p
|
|||
return list, 0, nil
|
||||
}
|
||||
collectionSearch := &entity.CollectionSearch{}
|
||||
collectionSearch.UserID = userinfo.UserId
|
||||
collectionSearch.UserID = userinfo.ID
|
||||
collectionSearch.Page = page
|
||||
collectionSearch.PageSize = pageSize
|
||||
collectionlist, count, err := qs.collectionCommon.SearchList(ctx, collectionSearch)
|
||||
|
@ -384,13 +384,13 @@ func (qs *QuestionService) SearchUserTopList(ctx context.Context, userName strin
|
|||
search.Order = "score"
|
||||
search.Page = 0
|
||||
search.PageSize = 5
|
||||
search.UserID = userinfo.UserId
|
||||
search.UserID = userinfo.ID
|
||||
questionlist, _, err := qs.SearchList(ctx, search, loginUserID)
|
||||
if err != nil {
|
||||
return userQuestionlist, userAnswerlist, err
|
||||
}
|
||||
answersearch := &entity.AnswerSearch{}
|
||||
answersearch.UserID = userinfo.UserId
|
||||
answersearch.UserID = userinfo.ID
|
||||
answersearch.PageSize = 5
|
||||
answersearch.Order = entity.Answer_Search_OrderBy_Vote
|
||||
questionIDs := make([]string, 0)
|
||||
|
@ -494,7 +494,7 @@ func (qs *QuestionService) SearchList(ctx context.Context, req *schema.QuestionS
|
|||
if !exist {
|
||||
return list, 0, err
|
||||
}
|
||||
req.UserID = userinfo.UserId
|
||||
req.UserID = userinfo.ID
|
||||
}
|
||||
questionList, count, err := qs.questionRepo.SearchList(ctx, req)
|
||||
if err != nil {
|
||||
|
|
|
@ -47,7 +47,7 @@ type UserRankRepo interface {
|
|||
|
||||
// RankService rank service
|
||||
type RankService struct {
|
||||
userRepo usercommon.UserRepo
|
||||
userCommon *usercommon.UserCommon
|
||||
configRepo config.ConfigRepo
|
||||
userRankRepo UserRankRepo
|
||||
objectInfoService *object_info.ObjService
|
||||
|
@ -55,12 +55,12 @@ type RankService struct {
|
|||
|
||||
// NewRankService new rank service
|
||||
func NewRankService(
|
||||
userRepo usercommon.UserRepo,
|
||||
userCommon *usercommon.UserCommon,
|
||||
userRankRepo UserRankRepo,
|
||||
objectInfoService *object_info.ObjService,
|
||||
configRepo config.ConfigRepo) *RankService {
|
||||
return &RankService{
|
||||
userRepo: userRepo,
|
||||
userCommon: userCommon,
|
||||
configRepo: configRepo,
|
||||
userRankRepo: userRankRepo,
|
||||
objectInfoService: objectInfoService,
|
||||
|
@ -74,7 +74,7 @@ func (rs *RankService) CheckRankPermission(ctx context.Context, userID string, a
|
|||
}
|
||||
|
||||
// get the rank of the current user
|
||||
userInfo, exist, err := rs.userRepo.GetByUserID(ctx, userID)
|
||||
userInfo, exist, err := rs.userCommon.GetUserBasicInfoByID(ctx, userID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ func (rs *RankService) CheckRankPermission(ctx context.Context, userID string, a
|
|||
func (rs *RankService) GetRankPersonalWithPage(ctx context.Context, req *schema.GetRankPersonalWithPageReq) (
|
||||
pageModel *pager.PageModel, err error) {
|
||||
if len(req.Username) > 0 {
|
||||
userInfo, exist, err := rs.userRepo.GetByUsername(ctx, req.Username)
|
||||
userInfo, exist, err := rs.userCommon.GetUserBasicInfoByUserName(ctx, req.Username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -140,5 +140,5 @@ func (rs *RankService) GetRankPersonalWithPage(ctx context.Context, req *schema.
|
|||
}
|
||||
resp = append(resp, commentResp)
|
||||
}
|
||||
return pager.NewPageModel(req.Page, req.PageSize, total, resp), nil
|
||||
return pager.NewPageModel(total, resp), nil
|
||||
}
|
||||
|
|
|
@ -2,9 +2,10 @@ package report_backyard
|
|||
|
||||
import (
|
||||
"context"
|
||||
"github.com/segmentfault/answer/internal/service/config"
|
||||
"strings"
|
||||
|
||||
"github.com/segmentfault/answer/internal/service/config"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/segmentfault/answer/internal/base/pager"
|
||||
"github.com/segmentfault/answer/internal/base/reason"
|
||||
|
@ -93,7 +94,7 @@ func (rs *ReportBackyardService) ListReportPage(ctx context.Context, dto schema.
|
|||
}
|
||||
|
||||
rs.parseObject(ctx, &resp)
|
||||
return pager.NewPageModel(dto.Page, dto.PageSize, total, resp), nil
|
||||
return pager.NewPageModel(total, resp), nil
|
||||
}
|
||||
|
||||
// HandleReported handle the reported object
|
||||
|
|
|
@ -18,19 +18,19 @@ import (
|
|||
// RevisionService user service
|
||||
type RevisionService struct {
|
||||
revisionRepo revision.RevisionRepo
|
||||
userRepo usercommon.UserRepo
|
||||
userCommon *usercommon.UserCommon
|
||||
questionCommon *questioncommon.QuestionCommon
|
||||
answerService *AnswerService
|
||||
}
|
||||
|
||||
func NewRevisionService(
|
||||
revisionRepo revision.RevisionRepo,
|
||||
userRepo usercommon.UserRepo,
|
||||
userCommon *usercommon.UserCommon,
|
||||
questionCommon *questioncommon.QuestionCommon,
|
||||
answerService *AnswerService) *RevisionService {
|
||||
return &RevisionService{
|
||||
revisionRepo: revisionRepo,
|
||||
userRepo: userRepo,
|
||||
userCommon: userCommon,
|
||||
questionCommon: questionCommon,
|
||||
answerService: answerService,
|
||||
}
|
||||
|
@ -69,7 +69,6 @@ func (rs *RevisionService) GetRevisionList(ctx context.Context, req *schema.GetR
|
|||
)
|
||||
|
||||
resp = []schema.GetRevisionResp{}
|
||||
|
||||
_ = copier.Copy(&rev, req)
|
||||
|
||||
revs, err = rs.revisionRepo.GetRevisionList(ctx, &rev)
|
||||
|
@ -79,29 +78,24 @@ func (rs *RevisionService) GetRevisionList(ctx context.Context, req *schema.GetR
|
|||
|
||||
for _, r := range revs {
|
||||
var (
|
||||
userInfo *entity.User
|
||||
uinfo schema.UserBasicInfo
|
||||
item schema.GetRevisionResp
|
||||
exists bool
|
||||
uinfo schema.UserBasicInfo
|
||||
item schema.GetRevisionResp
|
||||
)
|
||||
|
||||
_ = copier.Copy(&item, r)
|
||||
rs.parseItem(ctx, &item)
|
||||
|
||||
// get user info
|
||||
userInfo, exists, err = rs.userRepo.GetByUserID(ctx, item.UserID)
|
||||
if err != nil {
|
||||
return
|
||||
userInfo, exists, e := rs.userCommon.GetUserBasicInfoByID(ctx, item.UserID)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
if exists {
|
||||
err = copier.Copy(&uinfo, userInfo)
|
||||
item.UserInfo = uinfo
|
||||
}
|
||||
|
||||
resp = append(resp, item)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -5,25 +5,24 @@ import (
|
|||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/segmentfault/answer/internal/entity"
|
||||
"github.com/segmentfault/answer/internal/schema"
|
||||
"github.com/segmentfault/answer/internal/service/search_common"
|
||||
usercommon "github.com/segmentfault/answer/internal/service/user_common"
|
||||
)
|
||||
|
||||
type AuthorSearch struct {
|
||||
repo search_common.SearchRepo
|
||||
userRepo usercommon.UserRepo
|
||||
exp string
|
||||
w string
|
||||
page int
|
||||
size int
|
||||
repo search_common.SearchRepo
|
||||
userCommon *usercommon.UserCommon
|
||||
exp string
|
||||
w string
|
||||
page int
|
||||
size int
|
||||
}
|
||||
|
||||
func NewAuthorSearch(repo search_common.SearchRepo, userRepo usercommon.UserRepo) *AuthorSearch {
|
||||
func NewAuthorSearch(repo search_common.SearchRepo, userCommon *usercommon.UserCommon) *AuthorSearch {
|
||||
return &AuthorSearch{
|
||||
repo: repo,
|
||||
userRepo: userRepo,
|
||||
repo: repo,
|
||||
userCommon: userCommon,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,9 +36,6 @@ func (s *AuthorSearch) Parse(dto *schema.SearchDTO) (ok bool) {
|
|||
p,
|
||||
me,
|
||||
name string
|
||||
user *entity.User
|
||||
has bool
|
||||
err error
|
||||
)
|
||||
exp = ""
|
||||
q = dto.Query
|
||||
|
@ -51,8 +47,7 @@ func (s *AuthorSearch) Parse(dto *schema.SearchDTO) (ok bool) {
|
|||
res := re.FindStringSubmatch(q)
|
||||
if len(res) == 2 {
|
||||
name = res[1]
|
||||
user, has, err = s.userRepo.GetByUsername(nil, name)
|
||||
|
||||
user, has, err := s.userCommon.GetUserBasicInfoByUserName(nil, name)
|
||||
if err == nil && has {
|
||||
exp = user.ID
|
||||
trimLen := len(res[0])
|
||||
|
|
|
@ -35,14 +35,14 @@ type SearchService struct {
|
|||
func NewSearchService(
|
||||
searchRepo search_common.SearchRepo,
|
||||
tagRepo tagcommon.TagRepo,
|
||||
userRepo usercommon.UserRepo,
|
||||
userCommon *usercommon.UserCommon,
|
||||
followCommon activity_common.FollowRepo,
|
||||
) *SearchService {
|
||||
return &SearchService{
|
||||
searchRepo: searchRepo,
|
||||
tagSearch: search.NewTagSearch(searchRepo, tagRepo, followCommon),
|
||||
withinSearch: search.NewWithinSearch(searchRepo),
|
||||
authorSearch: search.NewAuthorSearch(searchRepo, userRepo),
|
||||
authorSearch: search.NewAuthorSearch(searchRepo, userCommon),
|
||||
scoreSearch: search.NewScoreSearch(searchRepo),
|
||||
answersSearch: search.NewAnswersSearch(searchRepo),
|
||||
acceptedAnswerSearch: search.NewAcceptedAnswerSearch(searchRepo),
|
||||
|
|
|
@ -41,12 +41,11 @@ func NewTagService(
|
|||
|
||||
// SearchTagLike get tag list all
|
||||
func (ts *TagService) SearchTagLike(ctx context.Context, req *schema.SearchTagLikeReq) (resp []string, err error) {
|
||||
tags, err := ts.tagRepo.GetTagListByName(ctx, req.Tag)
|
||||
tags, err := ts.tagRepo.GetTagListByName(ctx, req.Tag, 5)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, tag := range tags {
|
||||
//resp = append(resp, tag.DisplayName)
|
||||
resp = append(resp, tag.SlugName)
|
||||
}
|
||||
return resp, nil
|
||||
|
@ -358,7 +357,7 @@ func (ts *TagService) GetTagWithPage(ctx context.Context, req *schema.GetTagWith
|
|||
UpdatedAt: tag.UpdatedAt.Unix(),
|
||||
})
|
||||
}
|
||||
return pager.NewPageModel(page, pageSize, total, resp), nil
|
||||
return pager.NewPageModel(total, resp), nil
|
||||
}
|
||||
|
||||
// checkTagIsFollow get tag list page
|
||||
|
|
|
@ -16,7 +16,7 @@ type TagRepo interface {
|
|||
AddTagList(ctx context.Context, tagList []*entity.Tag) (err error)
|
||||
GetTagListByIDs(ctx context.Context, ids []string) (tagList []*entity.Tag, err error)
|
||||
GetTagBySlugName(ctx context.Context, slugName string) (tagInfo *entity.Tag, exist bool, err error)
|
||||
GetTagListByName(ctx context.Context, name string) (tagList []*entity.Tag, err error)
|
||||
GetTagListByName(ctx context.Context, name string, limit int) (tagList []*entity.Tag, err error)
|
||||
GetTagListByNames(ctx context.Context, names []string) (tagList []*entity.Tag, err error)
|
||||
RemoveTag(ctx context.Context, tagID string) (err error)
|
||||
UpdateTag(ctx context.Context, tag *entity.Tag) (err error)
|
||||
|
|
|
@ -108,17 +108,17 @@ func (us *UserBackyardService) GetUserPage(ctx context.Context, req *schema.GetU
|
|||
Avatar: u.Avatar,
|
||||
}
|
||||
if u.Status == entity.UserStatusDeleted {
|
||||
t.Status = schema.Deleted
|
||||
t.Status = schema.UserDeleted
|
||||
t.DeletedAt = u.DeletedAt.Unix()
|
||||
} else if u.Status == entity.UserStatusSuspended {
|
||||
t.Status = schema.Suspended
|
||||
t.Status = schema.UserSuspended
|
||||
t.SuspendedAt = u.SuspendedAt.Unix()
|
||||
} else if u.MailStatus == entity.EmailStatusToBeVerified {
|
||||
t.Status = schema.Inactive
|
||||
t.Status = schema.UserInactive
|
||||
} else {
|
||||
t.Status = schema.Normal
|
||||
t.Status = schema.UserNormal
|
||||
}
|
||||
resp = append(resp, t)
|
||||
}
|
||||
return pager.NewPageModel(req.Page, req.PageSize, total, resp), nil
|
||||
return pager.NewPageModel(total, resp), nil
|
||||
}
|
||||
|
|
|
@ -35,12 +35,12 @@ func NewUserCommon(userRepo UserRepo) *UserCommon {
|
|||
}
|
||||
|
||||
func (us *UserCommon) GetUserBasicInfoByID(ctx context.Context, ID string) (*schema.UserBasicInfo, bool, error) {
|
||||
dbInfo, has, err := us.userRepo.GetByUserID(ctx, ID)
|
||||
userInfo, exist, err := us.userRepo.GetByUserID(ctx, ID)
|
||||
if err != nil {
|
||||
return nil, has, err
|
||||
return nil, exist, err
|
||||
}
|
||||
info := us.UserBasicInfoFormat(ctx, dbInfo)
|
||||
return info, has, nil
|
||||
info := us.UserBasicInfoFormat(ctx, userInfo)
|
||||
return info, exist, nil
|
||||
}
|
||||
|
||||
func (us *UserCommon) GetUserBasicInfoByUserName(ctx context.Context, username string) (*schema.UserBasicInfo, bool, error) {
|
||||
|
@ -74,16 +74,20 @@ func (us *UserCommon) BatchUserBasicInfoByID(ctx context.Context, IDs []string)
|
|||
}
|
||||
|
||||
// UserBasicInfoFormat
|
||||
func (us *UserCommon) UserBasicInfoFormat(ctx context.Context, dbinfo *entity.User) *schema.UserBasicInfo {
|
||||
info := new(schema.UserBasicInfo)
|
||||
info.UserId = dbinfo.ID
|
||||
info.UserName = dbinfo.Username
|
||||
info.Rank = dbinfo.Rank
|
||||
info.DisplayName = dbinfo.DisplayName
|
||||
info.Avatar = dbinfo.Avatar
|
||||
info.Website = dbinfo.Website
|
||||
info.Location = dbinfo.Location
|
||||
info.IpInfo = dbinfo.IPInfo
|
||||
info.Status = dbinfo.Status
|
||||
return info
|
||||
func (us *UserCommon) UserBasicInfoFormat(ctx context.Context, userInfo *entity.User) *schema.UserBasicInfo {
|
||||
userBasicInfo := &schema.UserBasicInfo{}
|
||||
userBasicInfo.ID = userInfo.ID
|
||||
userBasicInfo.Username = userInfo.Username
|
||||
userBasicInfo.Rank = userInfo.Rank
|
||||
userBasicInfo.DisplayName = userInfo.DisplayName
|
||||
userBasicInfo.Avatar = userInfo.Avatar
|
||||
userBasicInfo.Website = userInfo.Website
|
||||
userBasicInfo.Location = userInfo.Location
|
||||
userBasicInfo.IpInfo = userInfo.IPInfo
|
||||
userBasicInfo.Status = schema.UserStatusShow[userInfo.Status]
|
||||
if userBasicInfo.Status == schema.UserDeleted {
|
||||
userBasicInfo.Avatar = ""
|
||||
userBasicInfo.DisplayName = "Anonymous"
|
||||
}
|
||||
return userBasicInfo
|
||||
}
|
||||
|
|
|
@ -97,5 +97,5 @@ func (us *UserGroupService) GetUserGroupWithPage(ctx context.Context, req *schem
|
|||
resp := &[]schema.GetUserGroupResp{}
|
||||
_ = copier.Copy(resp, userGroups)
|
||||
|
||||
return pager.NewPageModel(page, pageSize, total, resp), nil
|
||||
return pager.NewPageModel(total, resp), nil
|
||||
}
|
||||
|
|
|
@ -235,30 +235,8 @@ func (us *UserService) UserModifyPassWord(ctx context.Context, request *schema.U
|
|||
|
||||
// UpdateInfo
|
||||
func (us *UserService) UpdateInfo(ctx context.Context, request *schema.UpdateInfoRequest) error {
|
||||
// formatName, pass := us.CheckUserName(ctx, request.Username)
|
||||
// if !pass {
|
||||
// return fmt.Errorf("username format error")
|
||||
// }
|
||||
// dbuserinfo, has, err := us.userRepo.GetUserInfoByUserID(ctx, request.UserID)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// if !has {
|
||||
// return fmt.Errorf("user does not exist")
|
||||
// }
|
||||
// dbNameUserInfo, has, err := us.userRepo.GetOtherUserInfoByUsername(ctx, formatName)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// if has {
|
||||
// if dbuserinfo.TagID != dbNameUserInfo.TagID {
|
||||
// return fmt.Errorf("username already exists")
|
||||
// }
|
||||
// }
|
||||
|
||||
userinfo := entity.User{}
|
||||
userinfo.ID = request.UserId
|
||||
//userinfo.Username = formatName
|
||||
userinfo.Avatar = request.Avatar
|
||||
userinfo.DisplayName = request.DisplayName
|
||||
userinfo.Bio = request.Bio
|
||||
|
|
|
@ -193,5 +193,5 @@ func (vs *VoteService) ListUserVotes(ctx context.Context, req schema.GetVoteWith
|
|||
resp = append(resp, item)
|
||||
}
|
||||
|
||||
return pager.NewPageModel(req.Page, req.PageSize, total, resp), err
|
||||
return pager.NewPageModel(total, resp), err
|
||||
}
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
package captcha
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"sync"
|
||||
|
||||
"github.com/mojocn/base64Captcha"
|
||||
)
|
||||
|
||||
var store base64Captcha.Store
|
||||
var once sync.Once
|
||||
|
||||
func NewCaptcha() {
|
||||
once.Do(func() {
|
||||
//var err error
|
||||
//RedisDb, err = arch.App.Cache.GetQuestion("cache")
|
||||
//if err != nil {
|
||||
// store = base64Captcha.DefaultMemStore
|
||||
// return
|
||||
//}
|
||||
//var ctx = context.Background()
|
||||
//_, err = RedisDb.Ping(ctx).Result()
|
||||
//
|
||||
//if err != nil {
|
||||
// store = base64Captcha.DefaultMemStore
|
||||
// return
|
||||
//}
|
||||
store = RedisStore{}
|
||||
})
|
||||
}
|
||||
|
||||
// CaptchaClient
|
||||
type CaptchaClient struct {
|
||||
}
|
||||
|
||||
// NewCaptchaClient
|
||||
func NewCaptchaClient() *CaptchaClient {
|
||||
return &CaptchaClient{}
|
||||
}
|
||||
|
||||
func MakeCaptcha() (id, b64s string, err error) {
|
||||
var driver base64Captcha.Driver
|
||||
//Configure the parameters of the CAPTCHA
|
||||
driverString := base64Captcha.DriverString{
|
||||
Height: 40,
|
||||
Width: 100,
|
||||
NoiseCount: 0,
|
||||
ShowLineOptions: 2 | 4,
|
||||
Length: 4,
|
||||
Source: "1234567890qwertyuioplkjhgfdsazxcvbnm",
|
||||
BgColor: &color.RGBA{R: 3, G: 102, B: 214, A: 125},
|
||||
Fonts: []string{"wqy-microhei.ttc"},
|
||||
}
|
||||
//ConvertFonts Load fonts by name
|
||||
driver = driverString.ConvertFonts()
|
||||
//Create Captcha
|
||||
captcha := base64Captcha.NewCaptcha(driver, store)
|
||||
//Generate
|
||||
id, b64s, err = captcha.Generate()
|
||||
return id, b64s, err
|
||||
|
||||
}
|
||||
|
||||
// VerifyCaptcha Verification code
|
||||
func VerifyCaptcha(id string, VerifyValue string) bool {
|
||||
fmt.Println(id, VerifyValue)
|
||||
if store.Verify(id, VerifyValue, true) {
|
||||
//verify successfully
|
||||
return true
|
||||
} else {
|
||||
//Verification failed
|
||||
return false
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package captcha
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/segmentfault/pacman/contrib/cache/memory"
|
||||
)
|
||||
|
||||
const CAPTCHA = "captcha:"
|
||||
|
||||
var RedisDb = memory.NewCache()
|
||||
|
||||
type RedisStore struct {
|
||||
}
|
||||
|
||||
func (r RedisStore) Set(id string, value string) error {
|
||||
key := CAPTCHA + id
|
||||
ctx := context.Background()
|
||||
err := RedisDb.SetString(ctx, key, value, 2)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r RedisStore) Get(id string, clear bool) string {
|
||||
key := CAPTCHA + id
|
||||
ctx := context.Background()
|
||||
val, err := RedisDb.GetString(ctx, key)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return ""
|
||||
}
|
||||
if clear {
|
||||
err := RedisDb.Del(ctx, key)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func (r RedisStore) Verify(id, answer string, clear bool) bool {
|
||||
v := RedisStore{}.Get(id, clear)
|
||||
fmt.Println("key:" + id + ";value:" + v + ";answer:" + answer)
|
||||
return v == answer
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
package email
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/smtp"
|
||||
"text/template"
|
||||
|
||||
"github.com/jordan-wright/email"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
|
||||
// EmailClient
|
||||
type EmailClient struct {
|
||||
email *email.Email
|
||||
config *Config
|
||||
}
|
||||
|
||||
// Config .
|
||||
type Config struct {
|
||||
WebName string `json:"web_name"`
|
||||
WebHost string `json:"web_host"`
|
||||
SecretKey string `json:"secret_key"`
|
||||
UserSessionKey string `json:"user_session_key"`
|
||||
EmailFrom string `json:"email_from"`
|
||||
EmailFromPass string `json:"email_from_pass"`
|
||||
EmailFromHostname string `json:"email_from_hostname"`
|
||||
EmailFromSMTP string `json:"email_from_smtp"`
|
||||
EmailFromName string `json:"email_from_name"`
|
||||
RegisterTitle string `json:"register_title"`
|
||||
RegisterBody string `json:"register_body"`
|
||||
PassResetTitle string `json:"pass_reset_title"`
|
||||
PassResetBody string `json:"pass_reset_body"`
|
||||
}
|
||||
|
||||
// NewEmailClient
|
||||
func NewEmailClient() *EmailClient {
|
||||
return &EmailClient{
|
||||
email: email.NewEmail(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *EmailClient) Send(ToEmail, Title, Body string) {
|
||||
from := s.config.EmailFrom
|
||||
fromPass := s.config.EmailFromPass
|
||||
fromName := s.config.EmailFromName
|
||||
fromSmtp := s.config.EmailFromSMTP
|
||||
fromHostName := s.config.EmailFromHostname
|
||||
s.email.From = fmt.Sprintf("%s <%s>", fromName, from)
|
||||
s.email.To = []string{ToEmail}
|
||||
s.email.Subject = Title
|
||||
s.email.HTML = []byte(Body)
|
||||
err := s.email.Send(fromSmtp, smtp.PlainAuth("", from, fromPass, fromHostName))
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *EmailClient) RegisterTemplate(RegisterUrl string) (Title, Body string, err error) {
|
||||
webName := s.config.WebName
|
||||
templateData := RegisterTemplateData{webName, RegisterUrl}
|
||||
tmpl, err := template.New("register_title").Parse(s.config.RegisterTitle)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
title := new(bytes.Buffer)
|
||||
body := new(bytes.Buffer)
|
||||
err = tmpl.Execute(title, templateData)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
tmpl, err = template.New("register_body").Parse(s.config.RegisterBody)
|
||||
err = tmpl.Execute(body, templateData)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return title.String(), body.String(), nil
|
||||
}
|
||||
|
||||
func (s *EmailClient) PassResetTemplate(PassResetUrl string) (Title, Body string, err error) {
|
||||
webName := s.config.WebName
|
||||
templateData := PassResetTemplateData{webName, PassResetUrl}
|
||||
tmpl, err := template.New("pass_reset_title").Parse(s.config.PassResetTitle)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
title := new(bytes.Buffer)
|
||||
body := new(bytes.Buffer)
|
||||
err = tmpl.Execute(title, templateData)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
tmpl, err = template.New("pass_reset_body").Parse(s.config.PassResetBody)
|
||||
err = tmpl.Execute(body, templateData)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return title.String(), body.String(), nil
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package email
|
||||
|
||||
type RegisterTemplateData struct {
|
||||
SiteName string
|
||||
RegisterUrl string
|
||||
}
|
||||
|
||||
type PassResetTemplateData struct {
|
||||
SiteName string
|
||||
PassResetUrl string
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/bash
|
||||
if [ ! -f "/data/config.yaml" ]; then
|
||||
/usr/bin/answer init
|
||||
fi
|
||||
/usr/bin/answer run -c /data/config.yaml
|
|
@ -1,9 +0,0 @@
|
|||
# The MIT License
|
||||
|
||||
Copyright 2022 robin, contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -49,9 +49,9 @@ when cloning repo, and run `pnpm install` to init dependencies. you can use proj
|
|||
|
||||
## 🖥 Environment Support
|
||||
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Edge / IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Safari |
|
||||
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| Edge, IE11 | last 2 versions | last 2 versions | last 2 versions |
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Safari |
|
||||
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| last 2 versions | last 2 versions | last 2 versions | last 2 versions |
|
||||
|
||||
## Build with
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ module.exports = {
|
|||
'@answer/hooks': path.resolve(__dirname, 'src/hooks'),
|
||||
'@answer/utils': path.resolve(__dirname, 'src/utils'),
|
||||
'@answer/common': path.resolve(__dirname, 'src/common'),
|
||||
'@answer/services': path.resolve(__dirname, 'src/services'),
|
||||
'@answer/api': path.resolve(__dirname, 'src/services/api'),
|
||||
};
|
||||
|
||||
return config;
|
||||
|
@ -26,8 +26,8 @@ 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.20.84:8080",
|
||||
target: 'http://10.0.10.98:2060',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "./node_modules/cz-conventional-changelog"
|
||||
"path": "ui/node_modules/cz-conventional-changelog"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -46,7 +46,7 @@
|
|||
"react": "^18.2.0",
|
||||
"react-bootstrap": "^2.5.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-helmet-async": "^1.3.0",
|
||||
"react-i18next": "^11.18.3",
|
||||
"react-router-dom": "^6.4.0",
|
||||
"swr": "^1.3.0",
|
||||
|
@ -102,9 +102,10 @@
|
|||
"typescript": "*",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"license": "MIT",
|
||||
"packageManager": "pnpm@7.9.5",
|
||||
"engines": {
|
||||
"node": ">=16.17",
|
||||
"pnpm": ">=7"
|
||||
}
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
|
@ -67,7 +67,7 @@ specifiers:
|
|||
react-app-rewired: ^2.2.1
|
||||
react-bootstrap: ^2.5.0
|
||||
react-dom: ^18.2.0
|
||||
react-helmet: ^6.1.0
|
||||
react-helmet-async: ^1.3.0
|
||||
react-i18next: ^11.18.3
|
||||
react-router-dom: ^6.4.0
|
||||
react-scripts: 5.0.1
|
||||
|
@ -102,7 +102,7 @@ dependencies:
|
|||
react: 18.2.0
|
||||
react-bootstrap: 2.5.0_7ey2zzynotv32rpkwno45fsx4e
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
react-helmet: 6.1.0_react@18.2.0
|
||||
react-helmet-async: 1.3.0_biqbaboplfbrettd7655fr4n2y
|
||||
react-i18next: 11.18.6_ulhmqqxshznzmtuvahdi5nasbq
|
||||
react-router-dom: 6.4.0_biqbaboplfbrettd7655fr4n2y
|
||||
swr: 1.3.0_react@18.2.0
|
||||
|
@ -9581,16 +9581,19 @@ packages:
|
|||
resolution: {integrity: sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==}
|
||||
dev: false
|
||||
|
||||
/react-helmet/6.1.0_react@18.2.0:
|
||||
resolution: {integrity: sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==}
|
||||
/react-helmet-async/1.3.0_biqbaboplfbrettd7655fr4n2y:
|
||||
resolution: {integrity: sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg==}
|
||||
peerDependencies:
|
||||
react: '>=16.3.0'
|
||||
react: ^16.6.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.6.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
object-assign: 4.1.1
|
||||
'@babel/runtime': 7.19.0
|
||||
invariant: 2.2.4
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
react-fast-compare: 3.2.0
|
||||
react-side-effect: 2.1.2_react@18.2.0
|
||||
shallowequal: 1.1.0
|
||||
dev: false
|
||||
|
||||
/react-i18next/11.18.6_ulhmqqxshznzmtuvahdi5nasbq:
|
||||
|
@ -9747,14 +9750,6 @@ packages:
|
|||
- webpack-hot-middleware
|
||||
- webpack-plugin-serve
|
||||
|
||||
/react-side-effect/2.1.2_react@18.2.0:
|
||||
resolution: {integrity: sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==}
|
||||
peerDependencies:
|
||||
react: ^16.3.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/react-transition-group/4.4.5_biqbaboplfbrettd7655fr4n2y:
|
||||
resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
|
||||
peerDependencies:
|
||||
|
@ -10257,6 +10252,10 @@ packages:
|
|||
/setprototypeof/1.2.0:
|
||||
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
|
||||
|
||||
/shallowequal/1.1.0:
|
||||
resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==}
|
||||
dev: false
|
||||
|
||||
/shebang-command/2.0.0:
|
||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||
engines: {node: '>=8'}
|
||||
|
|
|
@ -34,3 +34,25 @@ export const ADMIN_LIST_STATUS = {
|
|||
name: 'deleted',
|
||||
},
|
||||
};
|
||||
|
||||
export const ADMIN_NAV_MENUS = [
|
||||
{
|
||||
name: 'dashboard',
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
name: 'contents',
|
||||
child: [{ name: 'questions' }, { name: 'answers' }],
|
||||
},
|
||||
{
|
||||
name: 'users',
|
||||
},
|
||||
{
|
||||
name: 'flags',
|
||||
// badgeContent: 5,
|
||||
},
|
||||
{
|
||||
name: 'settings',
|
||||
child: [{ name: 'general' }, { name: 'interface' }],
|
||||
},
|
||||
];
|
|
@ -7,3 +7,302 @@ export interface FormValue<T = any> {
|
|||
export interface FormDataType {
|
||||
[prop: string]: FormValue;
|
||||
}
|
||||
|
||||
export interface Paging {
|
||||
page: number;
|
||||
page_size?: number;
|
||||
}
|
||||
|
||||
export type ReportType = 'question' | 'answer' | 'comment' | 'user';
|
||||
export type ReportAction = 'close' | 'flag' | 'review';
|
||||
export interface ReportParams {
|
||||
type: ReportType;
|
||||
action: ReportAction;
|
||||
}
|
||||
|
||||
export interface TagBase {
|
||||
display_name: string;
|
||||
slug_name: string;
|
||||
}
|
||||
|
||||
export interface Tag extends TagBase {
|
||||
main_tag_slug_name?: string;
|
||||
original_text?: string;
|
||||
parsed_text?: string;
|
||||
}
|
||||
|
||||
export interface SynonymsTag extends Tag {
|
||||
tag_id: string;
|
||||
tag?: string;
|
||||
}
|
||||
|
||||
export interface TagInfo extends TagBase {
|
||||
tag_id: string;
|
||||
original_text: string;
|
||||
parsed_text: string;
|
||||
follow_count: number;
|
||||
question_count: number;
|
||||
is_follower: boolean;
|
||||
member_actions;
|
||||
created_at?;
|
||||
updated_at?;
|
||||
main_tag_slug_name?: string;
|
||||
excerpt?;
|
||||
}
|
||||
export interface QuestionParams {
|
||||
title: string;
|
||||
content: string;
|
||||
html: string;
|
||||
tags: Tag[];
|
||||
}
|
||||
|
||||
export interface ListResult<T = any> {
|
||||
count: number;
|
||||
list: T[];
|
||||
}
|
||||
|
||||
export interface AnswerParams {
|
||||
content: string;
|
||||
html: string;
|
||||
question_id: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface LoginReqParams {
|
||||
e_mail: string;
|
||||
/** password */
|
||||
pass: string;
|
||||
captcha_id?: string;
|
||||
captcha_code?: string;
|
||||
}
|
||||
|
||||
export interface RegisterReqParams extends LoginReqParams {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface ModifyPasswordReq {
|
||||
old_pass: string;
|
||||
pass: string;
|
||||
}
|
||||
|
||||
/** User */
|
||||
export interface ModifyUserReq {
|
||||
display_name: string;
|
||||
username?: string;
|
||||
avatar: string;
|
||||
bio: string;
|
||||
bio_html?: string;
|
||||
location: string;
|
||||
website: string;
|
||||
}
|
||||
|
||||
export interface UserInfoBase {
|
||||
avatar: string;
|
||||
username: string;
|
||||
display_name: string;
|
||||
rank: number;
|
||||
website: string;
|
||||
location: string;
|
||||
ip_info?: string;
|
||||
/** 'forbidden' | 'normal' | 'delete'
|
||||
*/
|
||||
status?: string;
|
||||
/** roles */
|
||||
is_admin?: true;
|
||||
}
|
||||
|
||||
export interface UserInfoRes extends UserInfoBase {
|
||||
bio: string;
|
||||
bio_html: string;
|
||||
create_time?: string;
|
||||
/** value = 1 active; value = 2 inactivated
|
||||
*/
|
||||
mail_status: number;
|
||||
e_mail?: string;
|
||||
[prop: string]: any;
|
||||
}
|
||||
|
||||
export interface AvatarUploadReq {
|
||||
file: FormData;
|
||||
}
|
||||
|
||||
export interface ImgCodeReq {
|
||||
captcha_id?: string;
|
||||
captcha_code?: string;
|
||||
}
|
||||
|
||||
export interface ImgCodeRes {
|
||||
captcha_id: string;
|
||||
captcha_img: string;
|
||||
verify: boolean;
|
||||
}
|
||||
|
||||
export interface PasswordResetReq extends ImgCodeReq {
|
||||
e_mail: string;
|
||||
}
|
||||
|
||||
export interface CheckImgReq {
|
||||
action: 'login' | 'e_mail' | 'find_pass';
|
||||
}
|
||||
|
||||
export interface SetNoticeReq {
|
||||
notice_switch: boolean;
|
||||
}
|
||||
|
||||
export interface QuestionDetailRes {
|
||||
id: string;
|
||||
title: string;
|
||||
content: string;
|
||||
html: string;
|
||||
tags: any[];
|
||||
view_count: number;
|
||||
unique_view_count?: number;
|
||||
answer_count: number;
|
||||
favorites_count: number;
|
||||
follow_counts: 0;
|
||||
accepted_answer_id: string;
|
||||
last_answer_id: string;
|
||||
create_time: string;
|
||||
update_time: string;
|
||||
user_info: UserInfoBase;
|
||||
answered: boolean;
|
||||
collected: boolean;
|
||||
|
||||
[prop: string]: any;
|
||||
}
|
||||
|
||||
export interface AnswersReq extends Paging {
|
||||
order?: 'default' | 'updated';
|
||||
question_id: string;
|
||||
}
|
||||
|
||||
export interface AnswerItem {
|
||||
id: string;
|
||||
question_id: string;
|
||||
content: string;
|
||||
html: string;
|
||||
create_time: string;
|
||||
update_time: string;
|
||||
user_info: UserInfoBase;
|
||||
|
||||
[prop: string]: any;
|
||||
}
|
||||
|
||||
export interface PostAnswerReq {
|
||||
content: string;
|
||||
html: string;
|
||||
question_id: string;
|
||||
}
|
||||
|
||||
export interface PageUser {
|
||||
id;
|
||||
displayName;
|
||||
userName?;
|
||||
avatar_url?;
|
||||
}
|
||||
|
||||
export interface LangsType {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description interface for Question
|
||||
*/
|
||||
export type QuestionOrderBy =
|
||||
| 'newest'
|
||||
| 'active'
|
||||
| 'frequent'
|
||||
| 'score'
|
||||
| 'unanswered';
|
||||
|
||||
export interface QueryQuestionsReq extends Paging {
|
||||
order: QuestionOrderBy;
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
export type AdminQuestionStatus = 'available' | 'closed' | 'deleted';
|
||||
|
||||
export type AdminContentsFilterBy = 'normal' | 'closed' | 'deleted';
|
||||
|
||||
export interface AdminContentsReq extends Paging {
|
||||
status: AdminContentsFilterBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description interface for Answer
|
||||
*/
|
||||
export type AdminAnswerStatus = 'available' | 'deleted';
|
||||
|
||||
/**
|
||||
* @description interface for Users
|
||||
*/
|
||||
export type UserFilterBy = 'all' | 'inactive' | 'suspended' | 'deleted';
|
||||
|
||||
/**
|
||||
* @description interface for Flags
|
||||
*/
|
||||
export type FlagStatus = 'pending' | 'completed';
|
||||
export type FlagType = 'all' | 'question' | 'answer' | 'comment';
|
||||
export interface AdminFlagsReq extends Paging {
|
||||
status: FlagStatus;
|
||||
object_type: FlagType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description interface for Admin Settings
|
||||
*/
|
||||
export interface AdminSettingsGeneral {
|
||||
name: string;
|
||||
short_description: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface AdminSettingsInterface {
|
||||
logo: string;
|
||||
language: string;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
export interface SiteSettings {
|
||||
general: AdminSettingsGeneral;
|
||||
interface: AdminSettingsInterface;
|
||||
}
|
||||
/**
|
||||
* @description interface for Activity
|
||||
*/
|
||||
export interface FollowParams {
|
||||
is_cancel: boolean;
|
||||
object_id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description search request params
|
||||
*/
|
||||
export interface SearchParams {
|
||||
q: string;
|
||||
order: string;
|
||||
page: number;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description search response data
|
||||
*/
|
||||
export interface SearchResItem {
|
||||
object_type: string;
|
||||
object: {
|
||||
id: string;
|
||||
title: string;
|
||||
excerpt: string;
|
||||
created_at: number;
|
||||
user_info: UserInfoBase;
|
||||
vote_count: number;
|
||||
answer_count: number;
|
||||
accepted: boolean;
|
||||
tags: TagBase[];
|
||||
};
|
||||
}
|
||||
export interface SearchRes extends ListResult<SearchResItem> {
|
||||
extra: any;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
|
|||
import classNames from 'classnames';
|
||||
|
||||
import { Icon } from '@answer/components';
|
||||
import { bookmark, postVote } from '@answer/services/api';
|
||||
import { bookmark, postVote } from '@answer/api';
|
||||
import { isLogin } from '@answer/utils';
|
||||
import { userInfoStore } from '@answer/stores';
|
||||
import { useToast } from '@answer/hooks';
|
||||
|
|
|
@ -5,23 +5,35 @@ import { Avatar } from '@answer/components';
|
|||
|
||||
interface Props {
|
||||
data: any;
|
||||
showAvatar?: boolean;
|
||||
avatarSize?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Index: FC<Props> = ({
|
||||
data,
|
||||
showAvatar = true,
|
||||
avatarSize = '20px',
|
||||
className = 'fs-14',
|
||||
}) => {
|
||||
return (
|
||||
<div className={`text-secondary ${className}`}>
|
||||
<Link to={`/users/${data?.username}`}>
|
||||
<Avatar avatar={data?.avatar} size={avatarSize} className="me-1" />
|
||||
</Link>
|
||||
<Link to={`/users/${data?.username}`} className="me-1 text-break">
|
||||
{data?.display_name}
|
||||
</Link>
|
||||
{data?.status !== 'deleted' ? (
|
||||
<Link to={`/users/${data?.username}`}>
|
||||
{showAvatar && (
|
||||
<Avatar avatar={data?.avatar} size={avatarSize} className="me-1" />
|
||||
)}
|
||||
<span className="me-1 text-break">{data?.display_name}</span>
|
||||
</Link>
|
||||
) : (
|
||||
<>
|
||||
{showAvatar && (
|
||||
<Avatar avatar={data?.avatar} size={avatarSize} className="me-1" />
|
||||
)}
|
||||
<span className="me-1 text-break">{data?.display_name}</span>
|
||||
</>
|
||||
)}
|
||||
|
||||
<span className="fw-bold">{data?.rank}</span>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -17,15 +17,20 @@ const ActionBar = ({
|
|||
onReply,
|
||||
onVote,
|
||||
onAction,
|
||||
userStatus = '',
|
||||
}) => {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'comment' });
|
||||
|
||||
return (
|
||||
<div className="d-flex justify-content-between fs-14">
|
||||
<div className="d-flex align-items-center">
|
||||
<Link to={`/users/${username}`}>{nickName}</Link>
|
||||
<span className="mx-1">·</span>
|
||||
<FormatTime time={createdAt} className="text-secondary me-3" />
|
||||
<div className="d-flex align-items-center text-secondary">
|
||||
{userStatus !== 'deleted' ? (
|
||||
<Link to={`/users/${username}`}>{nickName}</Link>
|
||||
) : (
|
||||
<span>{nickName}</span>
|
||||
)}
|
||||
<span className="mx-1">•</span>
|
||||
<FormatTime time={createdAt} className="me-3" />
|
||||
<Button
|
||||
variant="link"
|
||||
size="sm"
|
||||
|
|
|
@ -36,7 +36,7 @@ const Form = ({
|
|||
<Mentions pageUsers={pageUsers.getUsers()}>
|
||||
<TextArea size="sm" value={value} onChange={handleChange} />
|
||||
</Mentions>
|
||||
<div className="text-muted fs-14">{t(`tip_${mode}`)}</div>
|
||||
<div className="form-text">{t(`tip_${mode}`)}</div>
|
||||
</div>
|
||||
{type === 'edit' ? (
|
||||
<div className="d-flex flex-column">
|
||||
|
|
|
@ -22,7 +22,7 @@ const Form = ({ userName, onSendReply, onCancel, mode }) => {
|
|||
<Mentions pageUsers={pageUsers.getUsers()}>
|
||||
<TextArea size="sm" value={value} onChange={handleChange} />
|
||||
</Mentions>
|
||||
<div className="text-muted fs-14">{t(`tip_${mode}`)}</div>
|
||||
<div className="form-text">{t(`tip_${mode}`)}</div>
|
||||
</div>
|
||||
<div className="d-flex flex-column">
|
||||
<Button
|
||||
|
|
|
@ -7,16 +7,16 @@ import classNames from 'classnames';
|
|||
import { unionBy } from 'lodash';
|
||||
import { marked } from 'marked';
|
||||
|
||||
import * as Types from '@answer/common/interface';
|
||||
import {
|
||||
useQueryComments,
|
||||
addComment,
|
||||
deleteComment,
|
||||
updateComment,
|
||||
postVote,
|
||||
} from '@answer/services/api';
|
||||
} from '@answer/api';
|
||||
import { Modal } from '@answer/components';
|
||||
import { usePageUsers, useReportModal } from '@answer/hooks';
|
||||
import * as Types from '@answer/services/types';
|
||||
import { matchedUsers, parseUserInfo, isLogin } from '@answer/utils';
|
||||
|
||||
import { Form, ActionBar, Reply } from './components';
|
||||
|
@ -269,6 +269,7 @@ const Comment = ({ objectId, mode }) => {
|
|||
voteCount={item.vote_count}
|
||||
isVote={item.is_vote}
|
||||
memberActions={item.member_actions}
|
||||
userStatus={item.user_status}
|
||||
onReply={() => {
|
||||
handleReply(item.comment_id);
|
||||
}}
|
||||
|
|
|
@ -90,7 +90,7 @@ const Editor = ({
|
|||
return;
|
||||
}
|
||||
if (editor.getValue() !== value) {
|
||||
// editor.setValue(value);
|
||||
editor.setValue(value);
|
||||
}
|
||||
}, [editor, value]);
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Button, Form, Modal, Tab, Tabs } from 'react-bootstrap';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Modal as AnswerModal } from '@answer/components';
|
||||
import { uploadImage } from '@answer/services/api';
|
||||
import { uploadImage } from '@answer/api';
|
||||
import ToolItem from '../toolItem';
|
||||
import { IEditorContext } from '../types';
|
||||
|
||||
|
@ -232,10 +232,12 @@ const Image: FC<IEditorContext> = ({ editor }) => {
|
|||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<Tabs onSelect={handleSelect}>
|
||||
<Tab eventKey="localImage" title={t('image.tab_1')}>
|
||||
<Tab eventKey="localImage" title={t('image.tab_image')}>
|
||||
<Form className="mt-3" onSubmit={handleClick}>
|
||||
<Form.Group controlId="editor.imgLink" className="mb-3">
|
||||
<Form.Label>{t('image.form1.fields.file.label')}</Form.Label>
|
||||
<Form.Label>
|
||||
{t('image.form_image.fields.file.label')}
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
type="file"
|
||||
onChange={onUpload}
|
||||
|
@ -243,13 +245,13 @@ const Image: FC<IEditorContext> = ({ editor }) => {
|
|||
/>
|
||||
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{t('image.form1.fields.file.msg.empty')}
|
||||
{t('image.form_image.fields.file.msg.empty')}
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group controlId="editor.imgDescription" className="mb-3">
|
||||
<Form.Label>
|
||||
{t('image.form1.fields.description.label')}
|
||||
{t('image.form_image.fields.description.label')}
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
type="text"
|
||||
|
@ -262,10 +264,12 @@ const Image: FC<IEditorContext> = ({ editor }) => {
|
|||
</Form.Group>
|
||||
</Form>
|
||||
</Tab>
|
||||
<Tab eventKey="remoteImage" title={t('image.tab_2')}>
|
||||
<Tab eventKey="remoteImage" title={t('image.tab_url')}>
|
||||
<Form className="mt-3" onSubmit={handleClick}>
|
||||
<Form.Group controlId="editor.imgUrl" className="mb-3">
|
||||
<Form.Label>{t('image.form2.fields.url.label')}</Form.Label>
|
||||
<Form.Label>
|
||||
{t('image.form_url.fields.url.label')}
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
type="text"
|
||||
value={link.value}
|
||||
|
@ -275,12 +279,14 @@ const Image: FC<IEditorContext> = ({ editor }) => {
|
|||
isInvalid={currentTab === 'remoteImage' && link.isInvalid}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{t('image.form2.fields.url.msg.empty')}
|
||||
{t('image.form_url.fields.url.msg.empty')}
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group controlId="editor.imgName" className="mb-3">
|
||||
<Form.Label>{t('image.form2.fields.name.label')}</Form.Label>
|
||||
<Form.Label>
|
||||
{t('image.form_url.fields.name.label')}
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
type="text"
|
||||
value={imageName.value}
|
||||
|
|
|
@ -14,7 +14,7 @@ const Link: FC<IEditorContext> = ({ editor }) => {
|
|||
};
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [link, setLink] = useState({
|
||||
value: 'http://',
|
||||
value: 'https://',
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
});
|
||||
|
|
|
@ -5,8 +5,7 @@ import { NavLink } from 'react-router-dom';
|
|||
|
||||
import { TagSelector, Tag } from '@answer/components';
|
||||
import { isLogin } from '@answer/utils';
|
||||
|
||||
import { useFollowingTags, followTags } from '@/services/tag.api';
|
||||
import { useFollowingTags, followTags } from '@answer/api';
|
||||
|
||||
const Index: FC = () => {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'question' });
|
||||
|
|
|
@ -10,6 +10,10 @@
|
|||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
&.icon-link {
|
||||
width: 46px;
|
||||
height: 38px;
|
||||
}
|
||||
}
|
||||
.placeholder-search::placeholder {
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
|
|
|
@ -14,8 +14,7 @@ import { useSearchParams, NavLink, Link, useNavigate } from 'react-router-dom';
|
|||
|
||||
import { Avatar, Icon } from '@answer/components';
|
||||
import { userInfoStore, siteInfoStore, interfaceStore } from '@answer/stores';
|
||||
import { logout } from '@answer/services/api';
|
||||
import { useQueryNotificationRedDot } from '@answer/services/notification.api';
|
||||
import { logout, useQueryNotificationStatus } from '@answer/api';
|
||||
import Storage from '@answer/utils/storage';
|
||||
|
||||
import './index.scss';
|
||||
|
@ -29,7 +28,7 @@ const Header: FC = () => {
|
|||
const [searchStr, setSearch] = useState('');
|
||||
const siteInfo = siteInfoStore((state) => state.siteInfo);
|
||||
const { interface: interfaceInfo } = interfaceStore();
|
||||
const { data: redDot } = useQueryNotificationRedDot();
|
||||
const { data: redDot } = useQueryNotificationStatus();
|
||||
const handleInput = (val) => {
|
||||
setSearch(val);
|
||||
};
|
||||
|
@ -108,8 +107,8 @@ const Header: FC = () => {
|
|||
<Nav.Link
|
||||
as={NavLink}
|
||||
to="/users/notifications/inbox"
|
||||
className="me-2 position-relative">
|
||||
<div className="px-2 text-white text-opacity-75">
|
||||
className="icon-link d-flex align-items-center justify-content-center p-0 me-2 position-relative">
|
||||
<div className="text-white text-opacity-75">
|
||||
<Icon name="bell-fill" className="fs-5" />
|
||||
</div>
|
||||
{(redDot?.inbox || 0) > 0 && (
|
||||
|
@ -120,8 +119,8 @@ const Header: FC = () => {
|
|||
<Nav.Link
|
||||
as={Link}
|
||||
to="/users/notifications/achievement"
|
||||
className="me-2 position-relative">
|
||||
<div className="px-2 text-white text-opacity-75">
|
||||
className="icon-link d-flex align-items-center justify-content-center p-0 me-2 position-relative">
|
||||
<div className="text-white text-opacity-75">
|
||||
<Icon name="trophy-fill" className="fs-5" />
|
||||
</div>
|
||||
{(redDot?.achievement || 0) > 0 && (
|
||||
|
@ -135,7 +134,7 @@ const Header: FC = () => {
|
|||
id="dropdown-basic"
|
||||
as="a"
|
||||
className="no-toggle pointer">
|
||||
<Avatar size="38px" avatar={user?.avatar} />
|
||||
<Avatar size="36px" avatar={user?.avatar} />
|
||||
</Dropdown.Toggle>
|
||||
|
||||
<Dropdown.Menu>
|
||||
|
|
|
@ -3,8 +3,8 @@ import { Card, ListGroup, ListGroupItem } from 'react-bootstrap';
|
|||
import { Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useHotQuestions } from '@/services/question.api';
|
||||
import { Icon } from '@/components';
|
||||
import { useHotQuestions } from '@answer/api';
|
||||
import { Icon } from '@answer/components';
|
||||
|
||||
const HotQuestions: FC = () => {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'question' });
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useEffect, useRef, useState, FC } from 'react';
|
||||
import { Dropdown } from 'react-bootstrap';
|
||||
|
||||
import * as Types from '@answer/services/types';
|
||||
import * as Types from '@answer/common/interface';
|
||||
|
||||
interface IProps {
|
||||
children: React.ReactNode;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue