mirror of https://gitee.com/answerdev/answer.git
initial commit
This commit is contained in:
commit
8e6c49a531
|
@ -0,0 +1,20 @@
|
||||||
|
# http://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.go]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[{Makefile, Dockerfile}]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[{*.yml, *.json}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
|
@ -0,0 +1,20 @@
|
||||||
|
*.exe
|
||||||
|
*.orig
|
||||||
|
*.rej
|
||||||
|
*.so
|
||||||
|
*~
|
||||||
|
.DS_Store
|
||||||
|
._*
|
||||||
|
/.idea
|
||||||
|
/.vscode/*.log
|
||||||
|
/cmd/answer/*.sh
|
||||||
|
/cmd/logs
|
||||||
|
/configs/*
|
||||||
|
/go.work*
|
||||||
|
/logs
|
||||||
|
/ui/build
|
||||||
|
/ui/node_modules
|
||||||
|
/vendor
|
||||||
|
Thumbs*.db
|
||||||
|
tmp
|
||||||
|
vendor/
|
|
@ -0,0 +1,66 @@
|
||||||
|
include:
|
||||||
|
- project: "segmentfault/devops/templates"
|
||||||
|
file: ".docker-build-push.yml"
|
||||||
|
- project: "segmentfault/devops/templates"
|
||||||
|
file: ".deploy-helm.yml"
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- compile-html
|
||||||
|
- compile-golang
|
||||||
|
- push
|
||||||
|
- deploy-dev
|
||||||
|
|
||||||
|
"compile the html and other static files":
|
||||||
|
image: node:16
|
||||||
|
stage: compile-html
|
||||||
|
tags:
|
||||||
|
- runner-nanjing
|
||||||
|
before_script:
|
||||||
|
- npm config set registry https://repo.huaweicloud.com/repository/npm/
|
||||||
|
- make install-ui-packages
|
||||||
|
script:
|
||||||
|
- make ui
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- ./build
|
||||||
|
|
||||||
|
"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 build
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- ./answer
|
||||||
|
|
||||||
|
"build docker images and push":
|
||||||
|
stage: push
|
||||||
|
extends: .docker-build-push
|
||||||
|
only:
|
||||||
|
- dev
|
||||||
|
- master
|
||||||
|
- main
|
||||||
|
variables:
|
||||||
|
DockerNamespace: sf_app
|
||||||
|
DockerImage: answer
|
||||||
|
DockerTag: "$CI_COMMIT_SHORT_SHA latest"
|
||||||
|
DockerfilePath: .
|
||||||
|
PushPolicy: qingcloud
|
||||||
|
|
||||||
|
"deploy-to-local-develop-environment":
|
||||||
|
stage: deploy-dev
|
||||||
|
extends: .deploy-helm
|
||||||
|
only:
|
||||||
|
- main
|
||||||
|
variables:
|
||||||
|
LoadBalancerIP: 10.0.10.98
|
||||||
|
KubernetesCluster: dev
|
||||||
|
KubernetesNamespace: "sf-web"
|
||||||
|
InstallArgs: --set service.loadBalancerIP=${LoadBalancerIP} --set image.tag=$CI_COMMIT_SHORT_SHA --set replicaCount=1 --set serivce.targetPort=80
|
||||||
|
ChartName: answer
|
||||||
|
InstallPolicy: replace
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
FROM node:16 AS node-builder
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
FROM golang:1.18 AS golang-builder
|
||||||
|
LABEL maintainer="aichy"
|
||||||
|
|
||||||
|
ENV GOPATH /go
|
||||||
|
ENV GOROOT /usr/local/go
|
||||||
|
ENV PACKAGE github.com/segmentfault/answer
|
||||||
|
ENV GOPROXY https://goproxy.cn,direct
|
||||||
|
ENV BUILD_DIR ${GOPATH}/src/${PACKAGE}
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
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 \
|
||||||
|
&& echo "Asia/Shanghai" > /etc/timezone \
|
||||||
|
&& apt -y update \
|
||||||
|
&& apt -y upgrade \
|
||||||
|
&& apt -y install ca-certificates openssl tzdata curl netcat dumb-init \
|
||||||
|
&& apt -y autoremove
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
ENTRYPOINT ["dumb-init", "/usr/bin/answer", "-c", "/etc/answer.yaml"]
|
|
@ -0,0 +1 @@
|
||||||
|
# How to build and install
|
|
@ -0,0 +1,9 @@
|
||||||
|
# Answer 安装指引
|
||||||
|
|
||||||
|
## 前端安装
|
||||||
|
|
||||||
|
## 后端安装
|
||||||
|
|
||||||
|
## 编译镜像
|
||||||
|
|
||||||
|
## 常见问题
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) since 2022 The Segmentfault Development Team.
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1,31 @@
|
||||||
|
.PHONY: build clean ui
|
||||||
|
|
||||||
|
VERSION=0.0.1
|
||||||
|
BIN=answer
|
||||||
|
DIR_SRC=./cmd/answer
|
||||||
|
DOCKER_CMD=docker
|
||||||
|
|
||||||
|
GO_ENV=CGO_ENABLED=0
|
||||||
|
GO_FLAGS=-ldflags="-X main.Version=$(VERSION) -X 'main.Time=`date`' -extldflags -static"
|
||||||
|
GO=$(GO_ENV) $(shell which go)
|
||||||
|
|
||||||
|
build:
|
||||||
|
@$(GO_ENV) $(GO) build $(GO_FLAGS) -o $(BIN) $(DIR_SRC)
|
||||||
|
|
||||||
|
test:
|
||||||
|
@$(GO) test ./...
|
||||||
|
|
||||||
|
# clean all build result
|
||||||
|
clean:
|
||||||
|
@$(GO) clean ./...
|
||||||
|
@rm -f $(BIN)
|
||||||
|
|
||||||
|
install-ui-packages:
|
||||||
|
@corepack enable
|
||||||
|
@corepack prepare pnpm@v7.12.2 --activate
|
||||||
|
|
||||||
|
ui:
|
||||||
|
@npm config set registry https://repo.huaweicloud.com/repository/npm/
|
||||||
|
@cd ui && pnpm install && pnpm build && cd -
|
||||||
|
|
||||||
|
all: clean build
|
|
@ -0,0 +1,35 @@
|
||||||
|
# answer
|
||||||
|
|
||||||
|
问答社区主项目代码
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# module
|
||||||
|
- email github.com/jordan-wright/email
|
||||||
|
- session github.com/gin-contrib/sessions
|
||||||
|
- Captcha github.com/mojocn/base64Captcha
|
||||||
|
|
||||||
|
# Run
|
||||||
|
```
|
||||||
|
cd cmd
|
||||||
|
export GOPRIVATE=git.backyard.segmentfault.com
|
||||||
|
go mod tidy
|
||||||
|
./dev.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
# pprof
|
||||||
|
|
||||||
|
```
|
||||||
|
# Installation dependency
|
||||||
|
go get -u github.com/google/pprof
|
||||||
|
brew install graphviz
|
||||||
|
```
|
||||||
|
```
|
||||||
|
pprof -http :8082 http://XXX/debug/pprof/profile\?seconds\=10
|
||||||
|
```
|
|
@ -0,0 +1,9 @@
|
||||||
|
# Answer 问答社区
|
||||||
|
|
||||||
|
## 功能说明
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
## 配置
|
||||||
|
|
||||||
|
## 常见问题
|
|
@ -0,0 +1,2 @@
|
||||||
|
# /build
|
||||||
|
Packaging and Continuous Integration.
|
|
@ -0,0 +1,8 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="Go" enabled="true" />
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/charts.iml" filepath="$PROJECT_DIR$/.idea/charts.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,6 @@
|
||||||
|
apiVersion: v2
|
||||||
|
name: answer
|
||||||
|
description: a simple answer deployments for kubernetes
|
||||||
|
type: application
|
||||||
|
version: 0.1.0
|
||||||
|
appVersion: "0.1.0"
|
|
@ -0,0 +1,2 @@
|
||||||
|
# Helm Charts for Answer project
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
{{/*
|
||||||
|
Expand the name of the chart.
|
||||||
|
*/}}
|
||||||
|
{{- define "answer.name" -}}
|
||||||
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create a default fully qualified app name.
|
||||||
|
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||||
|
If release name contains chart name it will be used as a full name.
|
||||||
|
*/}}
|
||||||
|
{{- define "answer.fullname" -}}
|
||||||
|
{{- if .Values.fullnameOverride }}
|
||||||
|
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||||
|
{{- if contains $name .Release.Name }}
|
||||||
|
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create chart name and version as used by the chart label.
|
||||||
|
*/}}
|
||||||
|
{{- define "answer.chart" -}}
|
||||||
|
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Common labels
|
||||||
|
*/}}
|
||||||
|
{{- define "answer.labels" -}}
|
||||||
|
helm.sh/chart: {{ .Release.Name }}
|
||||||
|
{{ include "answer.selectorLabels" . }}
|
||||||
|
{{- if .Chart.AppVersion }}
|
||||||
|
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||||
|
{{- end }}
|
||||||
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Selector labels
|
||||||
|
*/}}
|
||||||
|
{{- define "answer.selectorLabels" -}}
|
||||||
|
app.kubernetes.io/name: answer
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
{{- end }}
|
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: answer-config
|
||||||
|
namespace: {{ .Values.namespace | default "default" | quote }}
|
||||||
|
data:
|
||||||
|
default.yaml: |-
|
||||||
|
#
|
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ include "answer.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "answer.labels" . | nindent 4 }}
|
||||||
|
namespace: {{ .Values.namespace | default "default" | quote }}
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
ports:
|
||||||
|
- name: answer
|
||||||
|
port: 80
|
||||||
|
targetPort: 80
|
||||||
|
protocol: TCP
|
||||||
|
selector:
|
||||||
|
{{- include "answer.selectorLabels" . | nindent 4 }}
|
|
@ -0,0 +1,31 @@
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: answer
|
||||||
|
namespace: {{ .Values.namespace | default "default" | quote }}
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "answer.labels" . | nindent 6 }}
|
||||||
|
serviceName: answer
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
{{- include "answer.labels" . | nindent 8 }}
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: answer
|
||||||
|
image: nginx:stable
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
name: answer-ui
|
||||||
|
volumeMounts:
|
||||||
|
- name: config
|
||||||
|
mountPath: "/etc/answer.yaml"
|
||||||
|
subPath: default.yaml
|
||||||
|
volumes:
|
||||||
|
- name: config
|
||||||
|
configMap:
|
||||||
|
name: answer-config
|
|
@ -0,0 +1 @@
|
||||||
|
namespace: default
|
|
@ -0,0 +1,70 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/segmentfault/answer/internal/base/conf"
|
||||||
|
"github.com/segmentfault/pacman"
|
||||||
|
"github.com/segmentfault/pacman/contrib/conf/viper"
|
||||||
|
"github.com/segmentfault/pacman/contrib/log/zap"
|
||||||
|
"github.com/segmentfault/pacman/contrib/server/http"
|
||||||
|
"github.com/segmentfault/pacman/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// go build -ldflags "-X main.Version=x.y.z"
|
||||||
|
var (
|
||||||
|
// Name is the name of the project
|
||||||
|
Name = "answer"
|
||||||
|
// Version is the version of the project
|
||||||
|
Version string
|
||||||
|
// confFlag is the config flag.
|
||||||
|
confFlag string
|
||||||
|
// log level
|
||||||
|
logLevel = os.Getenv("LOG_LEVEL")
|
||||||
|
// log path
|
||||||
|
logPath = os.Getenv("LOG_PATH")
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.StringVar(&confFlag, "c", "../../configs/config.yaml", "config path, eg: -c config.yaml")
|
||||||
|
}
|
||||||
|
|
||||||
|
// @securityDefinitions.apikey ApiKeyAuth
|
||||||
|
// @in header
|
||||||
|
// @name Authorization
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
log.SetLogger(zap.NewLogger(
|
||||||
|
log.ParseLevel(logLevel), zap.WithName(Name), zap.WithPath(logPath), zap.WithCallerFullPath()))
|
||||||
|
|
||||||
|
// init config
|
||||||
|
c := &conf.AllConfig{}
|
||||||
|
config, err := viper.NewWithPath(confFlag)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err = config.Parse(&c); 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newApplication(serverConf *conf.Server, server *gin.Engine) *pacman.Application {
|
||||||
|
return pacman.NewApp(
|
||||||
|
pacman.WithName(Name),
|
||||||
|
pacman.WithVersion(Version),
|
||||||
|
pacman.WithServer(http.NewServer(server, serverConf.HTTP.Addr)),
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
//go:build wireinject
|
||||||
|
// +build wireinject
|
||||||
|
|
||||||
|
// The build tag makes sure the stub is not built in the final build.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/wire"
|
||||||
|
"github.com/segmentfault/answer/internal/base/conf"
|
||||||
|
"github.com/segmentfault/answer/internal/base/data"
|
||||||
|
"github.com/segmentfault/answer/internal/base/middleware"
|
||||||
|
"github.com/segmentfault/answer/internal/base/server"
|
||||||
|
"github.com/segmentfault/answer/internal/base/translator"
|
||||||
|
"github.com/segmentfault/answer/internal/controller"
|
||||||
|
"github.com/segmentfault/answer/internal/controller_backyard"
|
||||||
|
"github.com/segmentfault/answer/internal/repo"
|
||||||
|
"github.com/segmentfault/answer/internal/router"
|
||||||
|
"github.com/segmentfault/answer/internal/service"
|
||||||
|
"github.com/segmentfault/answer/internal/service/service_config"
|
||||||
|
"github.com/segmentfault/pacman"
|
||||||
|
"github.com/segmentfault/pacman/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// initApplication init application.
|
||||||
|
func initApplication(
|
||||||
|
debug bool,
|
||||||
|
serverConf *conf.Server,
|
||||||
|
dbConf *data.Database,
|
||||||
|
cacheConf *data.CacheConf,
|
||||||
|
i18nConf *translator.I18n,
|
||||||
|
swaggerConf *router.SwaggerConfig,
|
||||||
|
serviceConf *service_config.ServiceConfig,
|
||||||
|
logConf log.Logger) (*pacman.Application, func(), error) {
|
||||||
|
panic(wire.Build(
|
||||||
|
server.ProviderSetServer,
|
||||||
|
router.ProviderSetRouter,
|
||||||
|
controller.ProviderSetController,
|
||||||
|
controller_backyard.ProviderSetController,
|
||||||
|
service.ProviderSetService,
|
||||||
|
repo.ProviderSetRepo,
|
||||||
|
translator.ProviderSet,
|
||||||
|
middleware.ProviderSetMiddleware,
|
||||||
|
newApplication,
|
||||||
|
))
|
||||||
|
}
|
|
@ -0,0 +1,180 @@
|
||||||
|
// Code generated by Wire. DO NOT EDIT.
|
||||||
|
|
||||||
|
//go:generate go run github.com/google/wire/cmd/wire
|
||||||
|
//go:build !wireinject
|
||||||
|
// +build !wireinject
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/segmentfault/answer/internal/base/conf"
|
||||||
|
"github.com/segmentfault/answer/internal/base/data"
|
||||||
|
"github.com/segmentfault/answer/internal/base/middleware"
|
||||||
|
"github.com/segmentfault/answer/internal/base/server"
|
||||||
|
"github.com/segmentfault/answer/internal/base/translator"
|
||||||
|
"github.com/segmentfault/answer/internal/controller"
|
||||||
|
"github.com/segmentfault/answer/internal/controller_backyard"
|
||||||
|
"github.com/segmentfault/answer/internal/repo"
|
||||||
|
"github.com/segmentfault/answer/internal/repo/activity"
|
||||||
|
"github.com/segmentfault/answer/internal/repo/activity_common"
|
||||||
|
"github.com/segmentfault/answer/internal/repo/auth"
|
||||||
|
"github.com/segmentfault/answer/internal/repo/captcha"
|
||||||
|
"github.com/segmentfault/answer/internal/repo/collection"
|
||||||
|
"github.com/segmentfault/answer/internal/repo/comment"
|
||||||
|
"github.com/segmentfault/answer/internal/repo/common"
|
||||||
|
"github.com/segmentfault/answer/internal/repo/config"
|
||||||
|
"github.com/segmentfault/answer/internal/repo/export"
|
||||||
|
"github.com/segmentfault/answer/internal/repo/meta"
|
||||||
|
"github.com/segmentfault/answer/internal/repo/notification"
|
||||||
|
"github.com/segmentfault/answer/internal/repo/rank"
|
||||||
|
"github.com/segmentfault/answer/internal/repo/reason"
|
||||||
|
"github.com/segmentfault/answer/internal/repo/report"
|
||||||
|
"github.com/segmentfault/answer/internal/repo/revision"
|
||||||
|
"github.com/segmentfault/answer/internal/repo/tag"
|
||||||
|
"github.com/segmentfault/answer/internal/repo/unique"
|
||||||
|
"github.com/segmentfault/answer/internal/repo/user"
|
||||||
|
"github.com/segmentfault/answer/internal/router"
|
||||||
|
"github.com/segmentfault/answer/internal/service"
|
||||||
|
"github.com/segmentfault/answer/internal/service/action"
|
||||||
|
activity2 "github.com/segmentfault/answer/internal/service/activity"
|
||||||
|
"github.com/segmentfault/answer/internal/service/answer_common"
|
||||||
|
auth2 "github.com/segmentfault/answer/internal/service/auth"
|
||||||
|
"github.com/segmentfault/answer/internal/service/collection_common"
|
||||||
|
comment2 "github.com/segmentfault/answer/internal/service/comment"
|
||||||
|
export2 "github.com/segmentfault/answer/internal/service/export"
|
||||||
|
"github.com/segmentfault/answer/internal/service/follow"
|
||||||
|
meta2 "github.com/segmentfault/answer/internal/service/meta"
|
||||||
|
notification2 "github.com/segmentfault/answer/internal/service/notification"
|
||||||
|
"github.com/segmentfault/answer/internal/service/notification_common"
|
||||||
|
"github.com/segmentfault/answer/internal/service/object_info"
|
||||||
|
"github.com/segmentfault/answer/internal/service/question_common"
|
||||||
|
rank2 "github.com/segmentfault/answer/internal/service/rank"
|
||||||
|
reason2 "github.com/segmentfault/answer/internal/service/reason"
|
||||||
|
report2 "github.com/segmentfault/answer/internal/service/report"
|
||||||
|
"github.com/segmentfault/answer/internal/service/report_backyard"
|
||||||
|
"github.com/segmentfault/answer/internal/service/report_handle_backyard"
|
||||||
|
"github.com/segmentfault/answer/internal/service/revision_common"
|
||||||
|
"github.com/segmentfault/answer/internal/service/service_config"
|
||||||
|
tag2 "github.com/segmentfault/answer/internal/service/tag"
|
||||||
|
"github.com/segmentfault/answer/internal/service/tag_common"
|
||||||
|
"github.com/segmentfault/answer/internal/service/uploader"
|
||||||
|
"github.com/segmentfault/answer/internal/service/user_backyard"
|
||||||
|
"github.com/segmentfault/answer/internal/service/user_common"
|
||||||
|
"github.com/segmentfault/pacman"
|
||||||
|
"github.com/segmentfault/pacman/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Injectors from wire.go:
|
||||||
|
|
||||||
|
// initApplication init application.
|
||||||
|
func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database, cacheConf *data.CacheConf, i18nConf *translator.I18n, swaggerConf *router.SwaggerConfig, serviceConf *service_config.ServiceConfig, logConf log.Logger) (*pacman.Application, func(), error) {
|
||||||
|
staticRouter := router.NewStaticRouter(serviceConf)
|
||||||
|
i18nTranslator, err := translator.NewTranslator(i18nConf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
langController := controller.NewLangController(i18nTranslator)
|
||||||
|
engine := data.NewDB(debug, dbConf)
|
||||||
|
cache, cleanup, err := data.NewCache(cacheConf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
dataData, cleanup2, err := data.NewData(engine, cache)
|
||||||
|
if err != nil {
|
||||||
|
cleanup()
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
authRepo := auth.NewAuthRepo(dataData)
|
||||||
|
authService := auth2.NewAuthService(authRepo)
|
||||||
|
configRepo := config.NewConfigRepo(dataData)
|
||||||
|
userRepo := user.NewUserRepo(dataData, configRepo)
|
||||||
|
uniqueIDRepo := unique.NewUniqueIDRepo(dataData)
|
||||||
|
activityRepo := repo.NewActivityRepo(dataData, uniqueIDRepo, configRepo)
|
||||||
|
userRankRepo := rank.NewUserRankRepo(dataData, configRepo)
|
||||||
|
userActiveActivityRepo := activity.NewUserActiveActivityRepo(dataData, activityRepo, userRankRepo, configRepo)
|
||||||
|
emailRepo := export.NewEmailRepo(dataData)
|
||||||
|
emailService := export2.NewEmailService(configRepo, emailRepo)
|
||||||
|
userService := service.NewUserService(userRepo, userActiveActivityRepo, emailService, authService, serviceConf)
|
||||||
|
captchaRepo := captcha.NewCaptchaRepo(dataData)
|
||||||
|
captchaService := action.NewCaptchaService(captchaRepo)
|
||||||
|
uploaderService := uploader.NewUploaderService(serviceConf)
|
||||||
|
userController := controller.NewUserController(authService, userService, captchaService, emailService, uploaderService)
|
||||||
|
commentRepo := comment.NewCommentRepo(dataData, uniqueIDRepo)
|
||||||
|
commentCommonRepo := comment.NewCommentCommonRepo(dataData, uniqueIDRepo)
|
||||||
|
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)
|
||||||
|
commentController := controller.NewCommentController(commentService, rankService)
|
||||||
|
reportRepo := report.NewReportRepo(dataData, uniqueIDRepo)
|
||||||
|
reportService := report2.NewReportService(reportRepo, objService)
|
||||||
|
reportController := controller.NewReportController(reportService, rankService)
|
||||||
|
serviceVoteRepo := activity.NewVoteRepo(dataData, uniqueIDRepo, configRepo, activityRepo, userRankRepo, voteRepo)
|
||||||
|
voteService := service.NewVoteService(serviceVoteRepo, uniqueIDRepo, configRepo, questionRepo, answerRepo, commentCommonRepo, objService)
|
||||||
|
voteController := controller.NewVoteController(voteService)
|
||||||
|
revisionRepo := revision.NewRevisionRepo(dataData, uniqueIDRepo)
|
||||||
|
revisionService := revision_common.NewRevisionService(revisionRepo, userRepo)
|
||||||
|
followRepo := activity_common.NewFollowRepo(dataData, uniqueIDRepo, activityRepo)
|
||||||
|
tagService := tag2.NewTagService(tagRepo, revisionService, followRepo)
|
||||||
|
tagController := controller.NewTagController(tagService, rankService)
|
||||||
|
followFollowRepo := activity.NewFollowRepo(dataData, uniqueIDRepo, activityRepo)
|
||||||
|
followService := follow.NewFollowService(followFollowRepo, followRepo, tagRepo)
|
||||||
|
followController := controller.NewFollowController(followService)
|
||||||
|
collectionRepo := collection.NewCollectionRepo(dataData, uniqueIDRepo)
|
||||||
|
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)
|
||||||
|
metaService := meta2.NewMetaService(metaRepo)
|
||||||
|
questionCommon := questioncommon.NewQuestionCommon(questionRepo, answerRepo, voteRepo, followRepo, tagCommonService, userCommon, collectionCommon, answerCommon, metaService, configRepo)
|
||||||
|
collectionService := service.NewCollectionService(collectionRepo, collectionGroupRepo, questionCommon)
|
||||||
|
collectionController := controller.NewCollectionController(collectionService)
|
||||||
|
answerActivityRepo := activity.NewAnswerActivityRepo(dataData, activityRepo, userRankRepo)
|
||||||
|
questionActivityRepo := activity.NewQuestionActivityRepo(dataData, activityRepo, userRankRepo)
|
||||||
|
answerActivityService := activity2.NewAnswerActivityService(answerActivityRepo, questionActivityRepo)
|
||||||
|
questionService := service.NewQuestionService(questionRepo, tagCommonService, questionCommon, userCommon, revisionService, metaService, collectionCommon, answerActivityService)
|
||||||
|
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)
|
||||||
|
searchController := controller.NewSearchController(searchService)
|
||||||
|
serviceRevisionService := service.NewRevisionService(revisionRepo, userRepo, questionCommon, answerService)
|
||||||
|
revisionController := controller.NewRevisionController(serviceRevisionService)
|
||||||
|
rankController := controller.NewRankController(rankService)
|
||||||
|
commonRepo := common.NewCommonRepo(dataData, uniqueIDRepo)
|
||||||
|
reportHandle := report_handle_backyard.NewReportHandle(questionCommon, commentRepo, configRepo)
|
||||||
|
reportBackyardService := report_backyard.NewReportBackyardService(reportRepo, userCommon, commonRepo, answerRepo, questionRepo, commentCommonRepo, reportHandle, configRepo)
|
||||||
|
controller_backyardReportController := controller_backyard.NewReportController(reportBackyardService)
|
||||||
|
userBackyardRepo := user.NewUserBackyardRepo(dataData)
|
||||||
|
userBackyardService := user_backyard.NewUserBackyardService(userBackyardRepo)
|
||||||
|
userBackyardController := controller_backyard.NewUserBackyardController(userBackyardService)
|
||||||
|
reasonRepo := reason.NewReasonRepo(configRepo)
|
||||||
|
reasonService := reason2.NewReasonService(reasonRepo)
|
||||||
|
reasonController := controller.NewReasonController(reasonService)
|
||||||
|
themeController := controller_backyard.NewThemeController()
|
||||||
|
siteInfoRepo := repo.NewSiteInfo(dataData)
|
||||||
|
siteInfoService := service.NewSiteInfoService(siteInfoRepo)
|
||||||
|
siteInfoController := controller_backyard.NewSiteInfoController(siteInfoService)
|
||||||
|
siteinfoController := controller.NewSiteinfoController(siteInfoService)
|
||||||
|
notificationRepo := notification.NewNotificationRepo(dataData)
|
||||||
|
notificationCommon := notificationcommon.NewNotificationCommon(dataData, notificationRepo, userCommon, activityRepo, followRepo, objService)
|
||||||
|
notificationService := notification2.NewNotificationService(dataData, notificationRepo, notificationCommon)
|
||||||
|
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()
|
||||||
|
authUserMiddleware := middleware.NewAuthUserMiddleware(authService)
|
||||||
|
ginEngine := server.NewHTTPServer(debug, staticRouter, answerAPIRouter, swaggerRouter, viewRouter, authUserMiddleware)
|
||||||
|
application := newApplication(serverConf, ginEngine)
|
||||||
|
return application, func() {
|
||||||
|
cleanup2()
|
||||||
|
cleanup()
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
# /deployments
|
||||||
|
|
||||||
|
IaaS, PaaS, system and container orchestration deployment configurations and templates (docker-compose, kubernetes/helm, mesos, terraform, bosh).
|
|
@ -0,0 +1,7 @@
|
||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
answer:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
image: github.com/segmentfault/answer
|
||||||
|
restart: on-failure
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,87 @@
|
||||||
|
module github.com/segmentfault/answer
|
||||||
|
|
||||||
|
go 1.18
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/Chain-Zhang/pinyin v0.1.3
|
||||||
|
github.com/bwmarrin/snowflake v0.3.0
|
||||||
|
github.com/davecgh/go-spew v1.1.1
|
||||||
|
github.com/gin-gonic/gin v1.8.1
|
||||||
|
github.com/go-playground/locales v0.14.0
|
||||||
|
github.com/go-playground/universal-translator v0.18.0
|
||||||
|
github.com/go-playground/validator/v10 v10.11.1
|
||||||
|
github.com/go-sql-driver/mysql v1.6.0
|
||||||
|
github.com/goccy/go-json v0.9.11
|
||||||
|
github.com/google/uuid v1.3.0
|
||||||
|
github.com/google/wire v0.5.0
|
||||||
|
github.com/jinzhu/copier v0.3.5
|
||||||
|
github.com/jinzhu/now v1.1.5
|
||||||
|
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/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
|
||||||
|
xorm.io/builder v0.3.12
|
||||||
|
xorm.io/core v0.7.3
|
||||||
|
xorm.io/xorm v1.3.2
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||||
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||||
|
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||||
|
github.com/go-openapi/spec v0.20.7 // indirect
|
||||||
|
github.com/go-openapi/swag v0.22.3 // indirect
|
||||||
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||||
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/leodido/go-urn v1.2.1 // indirect
|
||||||
|
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible // indirect
|
||||||
|
github.com/lestrrat-go/strftime v1.0.6 // indirect
|
||||||
|
github.com/magiconair/properties v1.8.6 // indirect
|
||||||
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/nicksnyder/go-i18n/v2 v2.2.0 // indirect
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||||
|
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/spf13/afero v1.9.2 // indirect
|
||||||
|
github.com/spf13/cast v1.5.0 // indirect
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/spf13/viper v1.13.0 // indirect
|
||||||
|
github.com/subosito/gotenv v1.4.1 // indirect
|
||||||
|
github.com/syndtr/goleveldb v1.0.0 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.2.7 // indirect
|
||||||
|
go.uber.org/atomic v1.10.0 // indirect
|
||||||
|
go.uber.org/multierr v1.8.0 // indirect
|
||||||
|
go.uber.org/zap v1.23.0 // indirect
|
||||||
|
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20220926163933-8cfa568d3c25 // 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
|
||||||
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,160 @@
|
||||||
|
base:
|
||||||
|
success:
|
||||||
|
other: "success"
|
||||||
|
unknown:
|
||||||
|
other: "unknown error"
|
||||||
|
request_format_error:
|
||||||
|
other: "request format is not valid"
|
||||||
|
unauthorized_error:
|
||||||
|
other: "unauthorized"
|
||||||
|
database_error:
|
||||||
|
other: "data server error"
|
||||||
|
|
||||||
|
email:
|
||||||
|
other: "email"
|
||||||
|
password:
|
||||||
|
other: "password"
|
||||||
|
|
||||||
|
email_or_password_wrong_error: &email_or_password_wrong
|
||||||
|
other: "email or password wrong"
|
||||||
|
|
||||||
|
error:
|
||||||
|
admin:
|
||||||
|
email_or_password_wrong: *email_or_password_wrong
|
||||||
|
answer:
|
||||||
|
not_found:
|
||||||
|
other: "answer not found"
|
||||||
|
comment:
|
||||||
|
edit_without_permission:
|
||||||
|
other: "comment not allowed to edit"
|
||||||
|
not_found:
|
||||||
|
other: "comment not found"
|
||||||
|
email:
|
||||||
|
duplicate:
|
||||||
|
other: "email already exists"
|
||||||
|
need_to_be_verified:
|
||||||
|
other: "email should be verified"
|
||||||
|
verify_url_expired:
|
||||||
|
other: "email verified url is expired, please resend the email"
|
||||||
|
lang:
|
||||||
|
not_found:
|
||||||
|
other: "language not found"
|
||||||
|
object:
|
||||||
|
captcha_verification_failed:
|
||||||
|
other: "captcha wrong"
|
||||||
|
disallow_follow:
|
||||||
|
other: "You are not allowed to follow"
|
||||||
|
disallow_vote:
|
||||||
|
other: "You are not allowed to vote"
|
||||||
|
disallow_vote_your_self:
|
||||||
|
other: "You can't vote for your own post!"
|
||||||
|
not_found:
|
||||||
|
other: "object not found"
|
||||||
|
question:
|
||||||
|
not_found:
|
||||||
|
other: "question not found"
|
||||||
|
rank:
|
||||||
|
fail_to_meet_the_condition:
|
||||||
|
other: "rank fail to meet the condition"
|
||||||
|
report:
|
||||||
|
handle_failed:
|
||||||
|
other: "report handle failed"
|
||||||
|
not_found:
|
||||||
|
other: "report not found"
|
||||||
|
tag:
|
||||||
|
not_found:
|
||||||
|
other: "tag not found"
|
||||||
|
theme:
|
||||||
|
not_found:
|
||||||
|
other: "theme not found"
|
||||||
|
user:
|
||||||
|
email_or_password_wrong:
|
||||||
|
other: *email_or_password_wrong
|
||||||
|
not_found:
|
||||||
|
other: "user not found"
|
||||||
|
suspended:
|
||||||
|
other: "user is suspended"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
report:
|
||||||
|
spam:
|
||||||
|
name:
|
||||||
|
other: "spam"
|
||||||
|
description:
|
||||||
|
other: "This post is an advertisement,or vandalism.It is not useful or relevant to the current topic."
|
||||||
|
rude:
|
||||||
|
name:
|
||||||
|
other: "rude or abusive"
|
||||||
|
description:
|
||||||
|
other: "A reasonable person would find this content inappropriate for respectful discourse."
|
||||||
|
duplicate:
|
||||||
|
name:
|
||||||
|
other: "a duplicate"
|
||||||
|
description:
|
||||||
|
other: "This question has been asked before and already has an answer."
|
||||||
|
not_answer:
|
||||||
|
name:
|
||||||
|
other: "not an answer"
|
||||||
|
description:
|
||||||
|
other: "This was posted as an answer,but it does not attempt to answer the question. It should possibly be an edit,a comment,another question,or deleted altogether."
|
||||||
|
not_need:
|
||||||
|
name:
|
||||||
|
other: "no longer needed"
|
||||||
|
description:
|
||||||
|
other: "This comment is outdated,conversational or not relevant to this post."
|
||||||
|
other:
|
||||||
|
name:
|
||||||
|
other: "something else"
|
||||||
|
description:
|
||||||
|
other: "This post requires staff attention for another reason not listed above."
|
||||||
|
|
||||||
|
question:
|
||||||
|
close:
|
||||||
|
duplicate:
|
||||||
|
name:
|
||||||
|
other: "spam"
|
||||||
|
description:
|
||||||
|
other: "This question has been asked before and already has an answer."
|
||||||
|
guideline:
|
||||||
|
name:
|
||||||
|
other: "a community-specific reason"
|
||||||
|
description:
|
||||||
|
other: "This question doesn't meet a community guideline."
|
||||||
|
multiple:
|
||||||
|
name:
|
||||||
|
other: "needs details or clarity"
|
||||||
|
description:
|
||||||
|
other: "This question currently includes multiple questions in one. It should focus on one problem only."
|
||||||
|
other:
|
||||||
|
name:
|
||||||
|
other: "something else"
|
||||||
|
description:
|
||||||
|
other: "This post requires another reason not listed above."
|
||||||
|
|
||||||
|
notification:
|
||||||
|
action:
|
||||||
|
update_question:
|
||||||
|
other: "update question"
|
||||||
|
answer_the_question:
|
||||||
|
other: "answer the question"
|
||||||
|
update_answer:
|
||||||
|
other: "update answer"
|
||||||
|
adopt_answer:
|
||||||
|
other: "adopt answer"
|
||||||
|
comment_question:
|
||||||
|
other: "comment question"
|
||||||
|
comment_answer:
|
||||||
|
other: "comment answer"
|
||||||
|
reply_to_you:
|
||||||
|
other: "reply to you"
|
||||||
|
mention_you:
|
||||||
|
other: "mention you"
|
||||||
|
your_question_is_closed:
|
||||||
|
other: "your question is closed"
|
||||||
|
your_question_was_deleted:
|
||||||
|
other: "your question was deleted"
|
||||||
|
your_answer_was_deleted:
|
||||||
|
other: "your answer was deleted"
|
||||||
|
your_comment_was_deleted:
|
||||||
|
other: "your comment was deleted"
|
|
@ -0,0 +1,25 @@
|
||||||
|
base:
|
||||||
|
success:
|
||||||
|
other: "成功"
|
||||||
|
unknown:
|
||||||
|
other: "未知错误"
|
||||||
|
request_format_error:
|
||||||
|
other: "请求格式错误"
|
||||||
|
unauthorized_error:
|
||||||
|
other: "未登录"
|
||||||
|
database_error:
|
||||||
|
other: "数据服务异常"
|
||||||
|
|
||||||
|
email:
|
||||||
|
other: "邮箱"
|
||||||
|
password:
|
||||||
|
other: "密码"
|
||||||
|
|
||||||
|
username_or_password_wrong_error: &username_or_password_wrong
|
||||||
|
other: "用户名或密码错误"
|
||||||
|
|
||||||
|
error:
|
||||||
|
user:
|
||||||
|
username_or_password_wrong: *username_or_password_wrong
|
||||||
|
admin:
|
||||||
|
username_or_password_wrong: *username_or_password_wrong
|
|
@ -0,0 +1,71 @@
|
||||||
|
package conf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/segmentfault/answer/internal/base/data"
|
||||||
|
"github.com/segmentfault/answer/internal/base/server"
|
||||||
|
"github.com/segmentfault/answer/internal/base/translator"
|
||||||
|
"github.com/segmentfault/answer/internal/router"
|
||||||
|
"github.com/segmentfault/answer/internal/service/service_config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AllConfig all config
|
||||||
|
type AllConfig struct {
|
||||||
|
Debug bool `json:"debug" mapstructure:"debug"`
|
||||||
|
Data *Data `json:"data" mapstructure:"data"`
|
||||||
|
Server *Server `json:"server" mapstructure:"server"`
|
||||||
|
I18n *translator.I18n `json:"i18n" mapstructure:"i18n"`
|
||||||
|
Swaggerui *router.SwaggerConfig `json:"swaggerui" mapstructure:"swaggerui"`
|
||||||
|
ServiceConfig *service_config.ServiceConfig `json:"service_config" mapstructure:"service_config"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server server config
|
||||||
|
type Server struct {
|
||||||
|
HTTP *server.HTTP `json:"http" mapstructure:"http"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data data config
|
||||||
|
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"`
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package constant
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const (
|
||||||
|
Default_PageSize = 20 //Default number of pages
|
||||||
|
Key_UserID = "_UserID" //session userid
|
||||||
|
LoginUserID = "login_user_id"
|
||||||
|
LoginUserVerify = "login_user_verify"
|
||||||
|
UserStatusChangedCacheKey = "answer:user:status:"
|
||||||
|
UserStatusChangedCacheTime = 7 * 24 * time.Hour
|
||||||
|
UserTokenCacheKey = "answer:user:token:"
|
||||||
|
UserTokenCacheTime = 7 * 24 * time.Hour
|
||||||
|
AdminTokenCacheKey = "answer:admin:token:"
|
||||||
|
AdminTokenCacheTime = 7 * 24 * time.Hour
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
QuestionObjectType = "question"
|
||||||
|
AnswerObjectType = "answer"
|
||||||
|
TagObjectType = "tag"
|
||||||
|
UserObjectType = "user"
|
||||||
|
CollectionObjectType = "collection"
|
||||||
|
CommentObjectType = "comment"
|
||||||
|
ReportObjectType = "report"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ObjectTypeStrMapping key => value
|
||||||
|
// object TagID AnswerList
|
||||||
|
// key equal database's table name
|
||||||
|
var (
|
||||||
|
ObjectTypeStrMapping = map[string]int{
|
||||||
|
QuestionObjectType: 1,
|
||||||
|
AnswerObjectType: 2,
|
||||||
|
TagObjectType: 3,
|
||||||
|
UserObjectType: 4,
|
||||||
|
CollectionObjectType: 6,
|
||||||
|
CommentObjectType: 7,
|
||||||
|
ReportObjectType: 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectTypeNumberMapping = map[int]string{
|
||||||
|
1: QuestionObjectType,
|
||||||
|
2: AnswerObjectType,
|
||||||
|
3: TagObjectType,
|
||||||
|
4: UserObjectType,
|
||||||
|
6: CollectionObjectType,
|
||||||
|
7: CommentObjectType,
|
||||||
|
8: ReportObjectType,
|
||||||
|
}
|
||||||
|
)
|
|
@ -0,0 +1,28 @@
|
||||||
|
package constant
|
||||||
|
|
||||||
|
const (
|
||||||
|
// UpdateQuestion update question
|
||||||
|
UpdateQuestion = "notification.action.update_question"
|
||||||
|
// AnswerTheQuestion answer the question
|
||||||
|
AnswerTheQuestion = "notification.action.answer_the_question"
|
||||||
|
// UpdateAnswer update answer
|
||||||
|
UpdateAnswer = "notification.action.update_answer"
|
||||||
|
// AdoptAnswer adopt answer
|
||||||
|
AdoptAnswer = "notification.action.adopt_answer"
|
||||||
|
// CommentQuestion comment question
|
||||||
|
CommentQuestion = "notification.action.comment_question"
|
||||||
|
// CommentAnswer comment answer
|
||||||
|
CommentAnswer = "notification.action.comment_answer"
|
||||||
|
// ReplyToYou reply to you
|
||||||
|
ReplyToYou = "notification.action.reply_to_you"
|
||||||
|
// MentionYou mention you
|
||||||
|
MentionYou = "notification.action.mention_you"
|
||||||
|
// YourQuestionIsClosed your question is closed
|
||||||
|
YourQuestionIsClosed = "notification.action.your_question_is_closed"
|
||||||
|
// YourQuestionWasDeleted your question was deleted
|
||||||
|
YourQuestionWasDeleted = "notification.action.your_question_was_deleted"
|
||||||
|
// YourAnswerWasDeleted your answer was deleted
|
||||||
|
YourAnswerWasDeleted = "notification.action.your_answer_was_deleted"
|
||||||
|
// YourCommentWasDeleted your comment was deleted
|
||||||
|
YourCommentWasDeleted = "notification.action.your_comment_was_deleted"
|
||||||
|
)
|
|
@ -0,0 +1,34 @@
|
||||||
|
package constant
|
||||||
|
|
||||||
|
const (
|
||||||
|
ReportSpamName = "report.spam.name"
|
||||||
|
ReportSpamDescription = "report.spam.description"
|
||||||
|
ReportRudeName = "report.rude.name"
|
||||||
|
ReportRudeDescription = "report.rude.description"
|
||||||
|
ReportDuplicateName = "report.duplicate.name"
|
||||||
|
ReportDuplicateDescription = "report.duplicate.description"
|
||||||
|
ReportOtherName = "report.other.name"
|
||||||
|
ReportOtherDescription = "report.other.description"
|
||||||
|
ReportNotAnswerName = "report.not_answer.name"
|
||||||
|
ReportNotAnswerDescription = "report.not_answer.description"
|
||||||
|
ReportNotNeedName = "report.not_need.name"
|
||||||
|
ReportNotNeedDescription = "report.not_need.description"
|
||||||
|
//question close
|
||||||
|
QuestionCloseDuplicateName = "question.close.duplicate.name"
|
||||||
|
QuestionCloseDuplicateDescription = "question.close.duplicate.description"
|
||||||
|
QuestionCloseGuidelineName = "question.close.guideline.name"
|
||||||
|
QuestionCloseGuidelineDescription = "question.close.guideline.description"
|
||||||
|
QuestionCloseMultipleName = "question.close.multiple.name"
|
||||||
|
QuestionCloseMultipleDescription = "question.close.multiple.description"
|
||||||
|
QuestionCloseOtherName = "question.close.other.name"
|
||||||
|
QuestionCloseOtherDescription = "question.close.other.description"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// TODO put this in database
|
||||||
|
// TODO need reason controller to resolve
|
||||||
|
QuestionCloseJson = `[{"name":"question.close.duplicate.name","description":"question.close.duplicate.description","source":"question","type":1,"have_content":false,"content_type":""},{"name":"question.close.guideline.name","description":"question.close.guideline.description","source":"question","type":2,"have_content":false,"content_type":""},{"name":"question.close.multiple.name","description":"question.close.multiple.description","source":"question","type":3,"have_content":true,"content_type":"text"},{"name":"question.close.other.name","description":"question.close.other.description","source":"question","type":4,"have_content":true,"content_type":"textarea"}]`
|
||||||
|
QuestionReportJson = `[{"name":"report.spam.name","description":"report.spam.description","source":"question","type":1,"have_content":false,"content_type":""},{"name":"report.rude.name","description":"report.rude.description","source":"question","type":2,"have_content":false,"content_type":""},{"name":"report.duplicate.name","description":"report.duplicate.description","source":"question","type":3,"have_content":true,"content_type":"text"},{"name":"report.other.name","description":"report.other.description","source":"question","type":4,"have_content":true,"content_type":"textarea"}]`
|
||||||
|
AnswerReportJson = `[{"name":"report.spam.name","description":"report.spam.description","source":"answer","type":1,"have_content":false,"content_type":""},{"name":"report.rude.name","description":"report.rude.description","source":"answer","type":2,"have_content":false,"content_type":""},{"name":"report.not_answer.name","description":"report.not_answer.description","source":"answer","type":3,"have_content":false,"content_type":""},{"name":"report.other.name","description":"report.other.description","source":"answer","type":4,"have_content":true,"content_type":"textarea"}]`
|
||||||
|
CommentReportJson = `[{"name":"report.spam.name","description":"report.spam.description","source":"comment","type":1,"have_content":false,"content_type":""},{"name":"report.rude.name","description":"report.rude.description","source":"comment","type":2,"have_content":false,"content_type":""},{"name":"report.not_need.name","description":"report.not_need.description","source":"comment","type":3,"have_content":true,"content_type":"text"},{"name":"report.other.name","description":"report.other.description","source":"comment","type":4,"have_content":true,"content_type":"textarea"}]`
|
||||||
|
)
|
|
@ -0,0 +1,14 @@
|
||||||
|
package data
|
||||||
|
|
||||||
|
// Database database config
|
||||||
|
type Database struct {
|
||||||
|
Connection string `json:"connection" mapstructure:"connection"`
|
||||||
|
ConnMaxLifeTime int `json:"conn_max_life_time" mapstructure:"conn_max_life_time"`
|
||||||
|
MaxOpenConn int `json:"max_open_conn" mapstructure:"max_open_conn"`
|
||||||
|
MaxIdleConn int `json:"max_idle_conn" mapstructure:"max_idle_conn"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CacheConf cache
|
||||||
|
type CacheConf struct {
|
||||||
|
FilePath string `json:"file_path" mapstructure:"file_path"`
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
"github.com/segmentfault/pacman/cache"
|
||||||
|
"github.com/segmentfault/pacman/contrib/cache/memory"
|
||||||
|
"github.com/segmentfault/pacman/log"
|
||||||
|
"xorm.io/core"
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Data data
|
||||||
|
type Data struct {
|
||||||
|
DB *xorm.Engine
|
||||||
|
Cache cache.Cache
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewData new data instance
|
||||||
|
func NewData(db *xorm.Engine, cache cache.Cache) (*Data, func(), error) {
|
||||||
|
cleanup := func() {
|
||||||
|
log.Info("closing the data resources")
|
||||||
|
db.Close()
|
||||||
|
}
|
||||||
|
return &Data{DB: db, Cache: cache}, cleanup, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDB new database instance
|
||||||
|
func NewDB(debug bool, dataConf *Database) *xorm.Engine {
|
||||||
|
engine, err := xorm.NewEngine("mysql", dataConf.Connection)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = engine.Ping(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
engine.ShowSQL(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dataConf.MaxIdleConn > 0 {
|
||||||
|
engine.SetMaxIdleConns(dataConf.MaxIdleConn)
|
||||||
|
}
|
||||||
|
if dataConf.MaxOpenConn > 0 {
|
||||||
|
engine.SetMaxOpenConns(dataConf.MaxOpenConn)
|
||||||
|
}
|
||||||
|
if dataConf.ConnMaxLifeTime > 0 {
|
||||||
|
engine.SetConnMaxLifetime(time.Duration(dataConf.ConnMaxLifeTime) * time.Second)
|
||||||
|
}
|
||||||
|
engine.SetColumnMapper(core.GonicMapper{})
|
||||||
|
return engine
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCache new cache instance
|
||||||
|
func NewCache(c *CacheConf) (cache.Cache, func(), error) {
|
||||||
|
// TODO What cache type should be initialized according to the configuration file
|
||||||
|
memCache := memory.NewCache()
|
||||||
|
|
||||||
|
if len(c.FilePath) > 0 {
|
||||||
|
log.Infof("try to load cache file from %s", c.FilePath)
|
||||||
|
if err := memory.Load(memCache, c.FilePath); err != nil {
|
||||||
|
log.Warn(err)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
ticker := time.Tick(time.Minute)
|
||||||
|
for range ticker {
|
||||||
|
if err := memory.Save(memCache, c.FilePath); err != nil {
|
||||||
|
log.Warn(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
cleanup := func() {
|
||||||
|
log.Infof("try to save cache file to %s", c.FilePath)
|
||||||
|
if err := memory.Save(memCache, c.FilePath); err != nil {
|
||||||
|
log.Warn(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return memCache, cleanup, nil
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/segmentfault/answer/internal/base/reason"
|
||||||
|
"github.com/segmentfault/answer/internal/base/validator"
|
||||||
|
myErrors "github.com/segmentfault/pacman/errors"
|
||||||
|
"github.com/segmentfault/pacman/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandleResponse Handle response body
|
||||||
|
func HandleResponse(ctx *gin.Context, err error, data interface{}) {
|
||||||
|
lang := GetLang(ctx)
|
||||||
|
// no error
|
||||||
|
if err == nil {
|
||||||
|
ctx.JSON(http.StatusOK, NewRespBodyData(http.StatusOK, reason.Success, data).TrMsg(lang))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var myErr *myErrors.Error
|
||||||
|
// unknown error
|
||||||
|
if !errors.As(err, &myErr) {
|
||||||
|
log.Error(err, "\n", myErrors.LogStack(2, 5))
|
||||||
|
ctx.JSON(http.StatusInternalServerError, NewRespBody(
|
||||||
|
http.StatusInternalServerError, reason.UnknownError).TrMsg(lang))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// log internal server error
|
||||||
|
if myErrors.IsInternalServer(myErr) {
|
||||||
|
log.Error(myErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
respBody := NewRespBodyFromError(myErr).TrMsg(lang)
|
||||||
|
if data != nil {
|
||||||
|
respBody.Data = data
|
||||||
|
}
|
||||||
|
ctx.JSON(myErr.Code, respBody)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindAndCheck bind request and check
|
||||||
|
func BindAndCheck(ctx *gin.Context, data interface{}) bool {
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
HandleResponse(ctx, myErrors.New(http.StatusBadRequest, reason.RequestFormatError).WithMsg(err.Error()), errField)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/segmentfault/pacman/i18n"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetLang get language from header
|
||||||
|
func GetLang(ctx *gin.Context) i18n.Language {
|
||||||
|
acceptLanguage := ctx.GetHeader("Accept-Language")
|
||||||
|
switch i18n.Language(acceptLanguage) {
|
||||||
|
case i18n.LanguageChinese:
|
||||||
|
return i18n.LanguageChinese
|
||||||
|
case i18n.LanguageEnglish:
|
||||||
|
return i18n.LanguageEnglish
|
||||||
|
default:
|
||||||
|
return i18n.DefaultLang
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/segmentfault/answer/internal/base/translator"
|
||||||
|
"github.com/segmentfault/pacman/errors"
|
||||||
|
"github.com/segmentfault/pacman/i18n"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RespBody response body.
|
||||||
|
type RespBody struct {
|
||||||
|
// http code
|
||||||
|
Code int `json:"code"`
|
||||||
|
// reason key
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
// response message
|
||||||
|
Message string `json:"msg"`
|
||||||
|
// response data
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrMsg translate the reason cause as a message
|
||||||
|
func (r *RespBody) TrMsg(lang i18n.Language) *RespBody {
|
||||||
|
if len(r.Message) == 0 {
|
||||||
|
r.Message = translator.GlobalTrans.Tr(lang, r.Reason)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRespBody new response body
|
||||||
|
func NewRespBody(code int, reason string) *RespBody {
|
||||||
|
return &RespBody{
|
||||||
|
Code: code,
|
||||||
|
Reason: reason,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRespBodyFromError new response body from error
|
||||||
|
func NewRespBodyFromError(e *errors.Error) *RespBody {
|
||||||
|
return &RespBody{
|
||||||
|
Code: e.Code,
|
||||||
|
Reason: e.Reason,
|
||||||
|
Message: e.Message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRespBodyData new response body with data
|
||||||
|
func NewRespBodyData(code int, reason string, data interface{}) *RespBody {
|
||||||
|
return &RespBody{
|
||||||
|
Code: code,
|
||||||
|
Reason: reason,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"github.com/segmentfault/answer/internal/schema"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/segmentfault/answer/internal/base/handler"
|
||||||
|
"github.com/segmentfault/answer/internal/base/reason"
|
||||||
|
"github.com/segmentfault/answer/internal/entity"
|
||||||
|
"github.com/segmentfault/answer/internal/service/auth"
|
||||||
|
"github.com/segmentfault/answer/pkg/converter"
|
||||||
|
"github.com/segmentfault/pacman/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ctxUuidKey = "ctxUuidKey"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthUserMiddleware auth user middleware
|
||||||
|
type AuthUserMiddleware struct {
|
||||||
|
authService *auth.AuthService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAuthUserMiddleware new auth user middleware
|
||||||
|
func NewAuthUserMiddleware(authService *auth.AuthService) *AuthUserMiddleware {
|
||||||
|
return &AuthUserMiddleware{
|
||||||
|
authService: authService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth get token and auth user, set user info to context if user is already login
|
||||||
|
func (am *AuthUserMiddleware) Auth() gin.HandlerFunc {
|
||||||
|
return func(ctx *gin.Context) {
|
||||||
|
token := ExtractToken(ctx)
|
||||||
|
if len(token) == 0 {
|
||||||
|
ctx.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if token == "888" {
|
||||||
|
userInfo := &entity.UserCacheInfo{}
|
||||||
|
userInfo.UserID = "2"
|
||||||
|
spew.Dump("开发环境 Auth", userInfo)
|
||||||
|
ctx.Set(ctxUuidKey, userInfo)
|
||||||
|
} else {
|
||||||
|
userInfo, err := am.authService.GetUserCacheInfo(ctx, token)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if userInfo != nil {
|
||||||
|
ctx.Set(ctxUuidKey, userInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustAuth auth user info. If the user does not log in, an unauthenticated error is displayed
|
||||||
|
func (am *AuthUserMiddleware) MustAuth() gin.HandlerFunc {
|
||||||
|
return func(ctx *gin.Context) {
|
||||||
|
token := ExtractToken(ctx)
|
||||||
|
if len(token) == 0 {
|
||||||
|
handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil)
|
||||||
|
ctx.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if token == "888" {
|
||||||
|
userInfo := &entity.UserCacheInfo{}
|
||||||
|
userInfo.UserID = "2"
|
||||||
|
spew.Dump("开发环境 MustAuth", userInfo)
|
||||||
|
ctx.Set(ctxUuidKey, userInfo)
|
||||||
|
} else {
|
||||||
|
userInfo, err := am.authService.GetUserCacheInfo(ctx, token)
|
||||||
|
spew.Dump(userInfo, err)
|
||||||
|
if err != nil || userInfo == nil {
|
||||||
|
handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil)
|
||||||
|
ctx.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if userInfo.EmailStatus != entity.EmailStatusAvailable {
|
||||||
|
handler.HandleResponse(ctx, errors.Forbidden(reason.EmailNeedToBeVerified),
|
||||||
|
&schema.ForbiddenResp{Type: schema.ForbiddenReasonTypeInactive})
|
||||||
|
ctx.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if userInfo.UserStatus == entity.UserStatusSuspended {
|
||||||
|
handler.HandleResponse(ctx, errors.Forbidden(reason.UserSuspended),
|
||||||
|
&schema.ForbiddenResp{Type: schema.ForbiddenReasonTypeUserSuspended})
|
||||||
|
ctx.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if userInfo.UserStatus == entity.UserStatusDeleted {
|
||||||
|
handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil)
|
||||||
|
ctx.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Set(ctxUuidKey, userInfo)
|
||||||
|
}
|
||||||
|
ctx.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *AuthUserMiddleware) CmsAuth() gin.HandlerFunc {
|
||||||
|
return func(ctx *gin.Context) {
|
||||||
|
token := ExtractToken(ctx)
|
||||||
|
if len(token) == 0 {
|
||||||
|
handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil)
|
||||||
|
ctx.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if token == "888" {
|
||||||
|
userInfo := &entity.UserCacheInfo{}
|
||||||
|
userInfo.UserID = "2"
|
||||||
|
spew.Dump("开发环境 CmsAuth", userInfo)
|
||||||
|
ctx.Set(ctxUuidKey, userInfo)
|
||||||
|
} else {
|
||||||
|
userInfo, err := am.authService.GetCmsUserCacheInfo(ctx, token)
|
||||||
|
if err != nil {
|
||||||
|
handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil)
|
||||||
|
ctx.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if userInfo != nil {
|
||||||
|
if userInfo.UserStatus == entity.UserStatusDeleted {
|
||||||
|
handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil)
|
||||||
|
ctx.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Set(ctxUuidKey, userInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLoginUserIDFromContext get user id from context
|
||||||
|
func GetLoginUserIDFromContext(ctx *gin.Context) (userID string) {
|
||||||
|
userInfo, exist := ctx.Get(ctxUuidKey)
|
||||||
|
if !exist {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
u, ok := userInfo.(*entity.UserCacheInfo)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return u.UserID
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserInfoFromContext get user info from context
|
||||||
|
func GetUserInfoFromContext(ctx *gin.Context) (u *entity.UserCacheInfo) {
|
||||||
|
userInfo, exist := ctx.Get(ctxUuidKey)
|
||||||
|
if !exist {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
u, ok := userInfo.(*entity.UserCacheInfo)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLoginUserIDInt64FromContext(ctx *gin.Context) (userID int64) {
|
||||||
|
userIDStr := GetLoginUserIDFromContext(ctx)
|
||||||
|
return converter.StringToInt64(userIDStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractToken extract token from context
|
||||||
|
func ExtractToken(ctx *gin.Context) (token string) {
|
||||||
|
token = ctx.GetHeader("Authorization")
|
||||||
|
if len(token) == 0 {
|
||||||
|
token = ctx.Query("Authorization")
|
||||||
|
}
|
||||||
|
return strings.TrimPrefix(token, "Bearer ")
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProviderSetMiddleware is providers.
|
||||||
|
var ProviderSetMiddleware = wire.NewSet(
|
||||||
|
NewAuthUserMiddleware,
|
||||||
|
)
|
|
@ -0,0 +1,21 @@
|
||||||
|
package pager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Help xorm page helper
|
||||||
|
func Help(page, pageSize int, rowsSlicePtr interface{}, rowElement interface{}, session *xorm.Session) (total int64, err error) {
|
||||||
|
page, pageSize = ValPageAndPageSize(page, pageSize)
|
||||||
|
|
||||||
|
sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr))
|
||||||
|
if sliceValue.Kind() != reflect.Slice {
|
||||||
|
return 0, errors.New("not a slice")
|
||||||
|
}
|
||||||
|
|
||||||
|
startNum := (page - 1) * pageSize
|
||||||
|
return session.Limit(pageSize, startNum).FindAndCount(rowsSlicePtr, rowElement)
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package pager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PageModel page model
|
||||||
|
type PageModel struct {
|
||||||
|
Count int64 `json:"count"`
|
||||||
|
List interface{} `json:"list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageCond page condition
|
||||||
|
type PageCond struct {
|
||||||
|
Page int
|
||||||
|
PageSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPageModel new page model
|
||||||
|
func NewPageModel(page, pageSize int, totalRecords int64, records interface{}) *PageModel {
|
||||||
|
sliceValue := reflect.Indirect(reflect.ValueOf(records))
|
||||||
|
if sliceValue.Kind() != reflect.Slice {
|
||||||
|
panic("not a slice")
|
||||||
|
}
|
||||||
|
|
||||||
|
if totalRecords < 0 {
|
||||||
|
totalRecords = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return &PageModel{
|
||||||
|
Count: totalRecords,
|
||||||
|
List: records,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValPageAndPageSize validate page pageSize
|
||||||
|
func ValPageAndPageSize(page, pageSize int) (int, int) {
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if pageSize <= 0 {
|
||||||
|
pageSize = 10
|
||||||
|
}
|
||||||
|
return page, pageSize
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package reason
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Success .
|
||||||
|
Success = "base.success"
|
||||||
|
// UnknownError unknown error
|
||||||
|
UnknownError = "base.unknown"
|
||||||
|
// RequestFormatError request format error
|
||||||
|
RequestFormatError = "base.request_format_error"
|
||||||
|
// UnauthorizedError unauthorized error
|
||||||
|
UnauthorizedError = "base.unauthorized_error"
|
||||||
|
// DatabaseError database error
|
||||||
|
DatabaseError = "base.database_error"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
EmailOrPasswordWrong = "error.user.email_or_password_wrong"
|
||||||
|
CommentNotFound = "error.comment.not_found"
|
||||||
|
QuestionNotFound = "error.question.not_found"
|
||||||
|
AnswerNotFound = "error.answer.not_found"
|
||||||
|
CommentEditWithoutPermission = "error.comment.edit_without_permission"
|
||||||
|
DisallowVote = "error.object.disallow_vote"
|
||||||
|
DisallowFollow = "error.object.disallow_follow"
|
||||||
|
DisallowVoteYourSelf = "error.object.disallow_vote_your_self"
|
||||||
|
CaptchaVerificationFailed = "error.object.captcha_verification_failed"
|
||||||
|
UserNotFound = "error.user.not_found"
|
||||||
|
EmailDuplicate = "error.email.duplicate"
|
||||||
|
EmailVerifyUrlExpired = "error.email.verify_url_expired"
|
||||||
|
EmailNeedToBeVerified = "error.email.need_to_be_verified"
|
||||||
|
UserSuspended = "error.user.suspended"
|
||||||
|
ObjectNotFound = "error.object.not_found"
|
||||||
|
TagNotFound = "error.tag.not_found"
|
||||||
|
RankFailToMeetTheCondition = "error.rank.fail_to_meet_the_condition"
|
||||||
|
ThemeNotFound = "error.theme.not_found"
|
||||||
|
LangNotFound = "error.lang.not_found"
|
||||||
|
ReportHandleFailed = "error.report.handle_failed"
|
||||||
|
ReportNotFound = "error.report.not_found"
|
||||||
|
)
|
|
@ -0,0 +1,6 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
// HTTP http config
|
||||||
|
type HTTP struct {
|
||||||
|
Addr string `json:"addr" mapstructure:"addr"`
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/segmentfault/answer/internal/base/middleware"
|
||||||
|
"github.com/segmentfault/answer/internal/router"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewHTTPServer new http server.
|
||||||
|
func NewHTTPServer(debug bool,
|
||||||
|
staticRouter *router.StaticRouter,
|
||||||
|
answerRouter *router.AnswerAPIRouter,
|
||||||
|
swaggerRouter *router.SwaggerRouter,
|
||||||
|
viewRouter *router.ViewRouter,
|
||||||
|
authUserMiddleware *middleware.AuthUserMiddleware) *gin.Engine {
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
gin.SetMode(gin.DebugMode)
|
||||||
|
} else {
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
}
|
||||||
|
r := gin.New()
|
||||||
|
r.GET("/healthz", func(ctx *gin.Context) { ctx.String(200, "OK") })
|
||||||
|
|
||||||
|
viewRouter.RegisterViewRouter(r)
|
||||||
|
|
||||||
|
rootGroup := r.Group("")
|
||||||
|
swaggerRouter.Register(rootGroup)
|
||||||
|
staticRouter.RegisterStaticRouter(rootGroup)
|
||||||
|
|
||||||
|
// register api that no need to login
|
||||||
|
unAuthV1 := r.Group("/answer/api/v1")
|
||||||
|
unAuthV1.Use(authUserMiddleware.Auth())
|
||||||
|
answerRouter.RegisterUnAuthAnswerAPIRouter(unAuthV1)
|
||||||
|
|
||||||
|
// register api that must be authenticated
|
||||||
|
authV1 := r.Group("/answer/api/v1")
|
||||||
|
authV1.Use(authUserMiddleware.MustAuth())
|
||||||
|
answerRouter.RegisterAnswerAPIRouter(authV1)
|
||||||
|
|
||||||
|
cmsauthV1 := r.Group("/answer/admin/api")
|
||||||
|
cmsauthV1.Use(authUserMiddleware.CmsAuth())
|
||||||
|
answerRouter.RegisterAnswerCmsAPIRouter(cmsauthV1)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import "github.com/google/wire"
|
||||||
|
|
||||||
|
// ProviderSetServer is providers.
|
||||||
|
var ProviderSetServer = wire.NewSet(NewHTTPServer)
|
|
@ -0,0 +1,6 @@
|
||||||
|
package translator
|
||||||
|
|
||||||
|
// I18n i18n config
|
||||||
|
type I18n struct {
|
||||||
|
BundleDir string `json:"bundle_dir" mapstructure:"bundle_dir"`
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package translator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/wire"
|
||||||
|
myTran "github.com/segmentfault/pacman/contrib/i18n"
|
||||||
|
"github.com/segmentfault/pacman/i18n"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProviderSet is providers.
|
||||||
|
var ProviderSet = wire.NewSet(NewTranslator)
|
||||||
|
var GlobalTrans i18n.Translator
|
||||||
|
|
||||||
|
// NewTranslator new a translator
|
||||||
|
func NewTranslator(c *I18n) (tr i18n.Translator, err error) {
|
||||||
|
GlobalTrans, err = myTran.NewTranslator(c.BundleDir)
|
||||||
|
return GlobalTrans, err
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/go-playground/locales"
|
||||||
|
english "github.com/go-playground/locales/en"
|
||||||
|
zhongwen "github.com/go-playground/locales/zh"
|
||||||
|
"github.com/go-playground/universal-translator"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/go-playground/validator/v10/translations/en"
|
||||||
|
"github.com/go-playground/validator/v10/translations/zh"
|
||||||
|
"github.com/segmentfault/answer/internal/base/translator"
|
||||||
|
"github.com/segmentfault/pacman/i18n"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MyValidator my validator
|
||||||
|
type MyValidator struct {
|
||||||
|
Validate *validator.Validate
|
||||||
|
Tran ut.Translator
|
||||||
|
Lang i18n.Language
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorField error field
|
||||||
|
type ErrorField struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// GlobalValidatorMapping is a mapping from validator to translator used
|
||||||
|
GlobalValidatorMapping = make(map[string]*MyValidator, 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
zhTran, zhVal := getTran(zhongwen.New(), i18n.LanguageChinese.Abbr()), createDefaultValidator(i18n.LanguageChinese)
|
||||||
|
if err := zh.RegisterDefaultTranslations(zhVal, zhTran); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
GlobalValidatorMapping[i18n.LanguageChinese.Abbr()] = &MyValidator{Validate: zhVal, Tran: zhTran, Lang: i18n.LanguageChinese}
|
||||||
|
|
||||||
|
enTran, enVal := getTran(english.New(), i18n.LanguageEnglish.Abbr()), createDefaultValidator(i18n.LanguageEnglish)
|
||||||
|
if err := en.RegisterDefaultTranslations(enVal, enTran); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
GlobalValidatorMapping[i18n.LanguageEnglish.Abbr()] = &MyValidator{Validate: enVal, Tran: enTran, Lang: i18n.LanguageEnglish}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTran(lo locales.Translator, la string) ut.Translator {
|
||||||
|
tran, ok := ut.New(lo, lo).GetTranslator(la)
|
||||||
|
if !ok {
|
||||||
|
panic(ok)
|
||||||
|
}
|
||||||
|
return tran
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDefaultValidator(la i18n.Language) *validator.Validate {
|
||||||
|
validate := validator.New()
|
||||||
|
validate.RegisterTagNameFunc(func(fld reflect.StructField) (res string) {
|
||||||
|
defer func() {
|
||||||
|
if len(res) > 0 {
|
||||||
|
res = translator.GlobalTrans.Tr(la, res)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if jsonTag := fld.Tag.Get("json"); len(jsonTag) > 0 {
|
||||||
|
if jsonTag == "-" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return jsonTag
|
||||||
|
}
|
||||||
|
if formTag := fld.Tag.Get("form"); len(formTag) > 0 {
|
||||||
|
return formTag
|
||||||
|
}
|
||||||
|
return fld.Name
|
||||||
|
})
|
||||||
|
return validate
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetValidatorByLang(la string) *MyValidator {
|
||||||
|
if GlobalValidatorMapping[la] != nil {
|
||||||
|
return GlobalValidatorMapping[la]
|
||||||
|
}
|
||||||
|
return GlobalValidatorMapping[i18n.DefaultLang.Abbr()]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check /
|
||||||
|
func (m *MyValidator) Check(value interface{}) (errField *ErrorField, err error) {
|
||||||
|
err = m.Validate.Struct(value)
|
||||||
|
if err != nil {
|
||||||
|
var valErrors validator.ValidationErrors
|
||||||
|
if !errors.As(err, &valErrors) {
|
||||||
|
return nil, errors.New("validate check exception")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fieldError := range valErrors {
|
||||||
|
errField = &ErrorField{
|
||||||
|
Key: translator.GlobalTrans.Tr(m.Lang, fieldError.Field()),
|
||||||
|
Value: fieldError.Translate(m.Tran),
|
||||||
|
}
|
||||||
|
return errField, errors.New(fieldError.Translate(m.Tran))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := value.(Checker); ok {
|
||||||
|
errField, err = v.Check()
|
||||||
|
if err != nil {
|
||||||
|
return errField, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checker .
|
||||||
|
type Checker interface {
|
||||||
|
Check() (errField *ErrorField, err error)
|
||||||
|
}
|
|
@ -0,0 +1,244 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/segmentfault/answer/internal/base/handler"
|
||||||
|
"github.com/segmentfault/answer/internal/base/middleware"
|
||||||
|
"github.com/segmentfault/answer/internal/base/reason"
|
||||||
|
"github.com/segmentfault/answer/internal/entity"
|
||||||
|
"github.com/segmentfault/answer/internal/schema"
|
||||||
|
"github.com/segmentfault/answer/internal/service"
|
||||||
|
"github.com/segmentfault/answer/internal/service/rank"
|
||||||
|
"github.com/segmentfault/pacman/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AnswerController answer controller
|
||||||
|
type AnswerController struct {
|
||||||
|
answerService *service.AnswerService
|
||||||
|
rankService *rank.RankService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAnswerController new controller
|
||||||
|
func NewAnswerController(answerService *service.AnswerService, rankService *rank.RankService) *AnswerController {
|
||||||
|
return &AnswerController{answerService: answerService, rankService: rankService}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAnswer delete answer
|
||||||
|
// @Summary delete answer
|
||||||
|
// @Description delete answer
|
||||||
|
// @Tags api-answer
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body schema.RemoveAnswerReq true "answer"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/api/v1/answer [delete]
|
||||||
|
func (ac *AnswerController) RemoveAnswer(ctx *gin.Context) {
|
||||||
|
req := &schema.RemoveAnswerReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
if can, err := ac.rankService.CheckRankPermission(ctx, req.UserID, rank.AnswerDeleteRank); err != nil || !can {
|
||||||
|
handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := ac.answerService.RemoveAnswer(ctx, req.ID)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get godoc
|
||||||
|
// @Summary Get Answer
|
||||||
|
// @Description Get Answer
|
||||||
|
// @Tags api-answer
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id query string true "Answer TagID" default(1)
|
||||||
|
// @Router /answer/api/v1/answer/info [get]
|
||||||
|
// @Success 200 {string} string ""
|
||||||
|
func (ac *AnswerController) Get(ctx *gin.Context) {
|
||||||
|
id := ctx.Query("id")
|
||||||
|
userId := middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
|
||||||
|
info, questionInfo, has, err := ac.answerService.Get(ctx, id, userId)
|
||||||
|
if err != nil {
|
||||||
|
handler.HandleResponse(ctx, err, gin.H{})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
handler.HandleResponse(ctx, fmt.Errorf(""), gin.H{})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handler.HandleResponse(ctx, err, gin.H{
|
||||||
|
"info": info,
|
||||||
|
"question": questionInfo,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add godoc
|
||||||
|
// @Summary Insert Answer
|
||||||
|
// @Description Insert Answer
|
||||||
|
// @Tags api-answer
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body schema.AnswerAddReq true "AnswerAddReq"
|
||||||
|
// @Success 200 {string} string ""
|
||||||
|
// @Router /answer/api/v1/answer [post]
|
||||||
|
func (ac *AnswerController) Add(ctx *gin.Context) {
|
||||||
|
req := &schema.AnswerAddReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
|
||||||
|
if can, err := ac.rankService.CheckRankPermission(ctx, req.UserID, rank.AnswerAddRank); err != nil || !can {
|
||||||
|
handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
answerId, err := ac.answerService.Insert(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
info, questionInfo, has, err := ac.answerService.Get(ctx, answerId, req.UserID)
|
||||||
|
if err != nil {
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
//todo !has
|
||||||
|
handler.HandleResponse(ctx, nil, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handler.HandleResponse(ctx, nil, gin.H{
|
||||||
|
"info": info,
|
||||||
|
"question": questionInfo,
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update godoc
|
||||||
|
// @Summary Update Answer
|
||||||
|
// @Description Update Answer
|
||||||
|
// @Tags api-answer
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body schema.AnswerUpdateReq true "AnswerUpdateReq"
|
||||||
|
// @Success 200 {string} string ""
|
||||||
|
// @Router /answer/api/v1/answer [put]
|
||||||
|
func (ac *AnswerController) Update(ctx *gin.Context) {
|
||||||
|
req := &schema.AnswerUpdateReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
|
||||||
|
if can, err := ac.rankService.CheckRankPermission(ctx, req.UserID, rank.AnswerEditRank); err != nil || !can {
|
||||||
|
handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := ac.answerService.Update(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
info, questionInfo, has, err := ac.answerService.Get(ctx, req.ID, req.UserID)
|
||||||
|
if err != nil {
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
//todo !has
|
||||||
|
handler.HandleResponse(ctx, nil, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handler.HandleResponse(ctx, nil, gin.H{
|
||||||
|
"info": info,
|
||||||
|
"question": questionInfo,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnswerList godoc
|
||||||
|
// @Summary AnswerList
|
||||||
|
// @Description AnswerList <br> <b>order</b> (default or updated)
|
||||||
|
// @Tags api-answer
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param data body schema.AnswerList true "AnswerList"
|
||||||
|
// @Success 200 {string} string ""
|
||||||
|
// @Router /answer/api/v1/answer/list [post]
|
||||||
|
func (ac *AnswerController) AnswerList(c *gin.Context) {
|
||||||
|
input := new(schema.AnswerList)
|
||||||
|
err := c.BindJSON(input)
|
||||||
|
if err != nil {
|
||||||
|
handler.HandleResponse(c, err, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
userId := middleware.GetLoginUserIDFromContext(c)
|
||||||
|
input.LoginUserID = userId
|
||||||
|
list, count, err := ac.answerService.SearchList(ctx, input)
|
||||||
|
if err != nil {
|
||||||
|
handler.HandleResponse(c, err, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handler.HandleResponse(c, nil, gin.H{
|
||||||
|
"list": list,
|
||||||
|
"count": count,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adopted godoc
|
||||||
|
// @Summary Adopted
|
||||||
|
// @Description Adopted
|
||||||
|
// @Tags api-answer
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body schema.AnswerAdoptedReq true "AnswerAdoptedReq"
|
||||||
|
// @Success 200 {string} string ""
|
||||||
|
// @Router /answer/api/v1/answer/acceptance [post]
|
||||||
|
func (ac *AnswerController) Adopted(ctx *gin.Context) {
|
||||||
|
req := &schema.AnswerAdoptedReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
if can, err := ac.rankService.CheckRankPermission(ctx, req.UserID, rank.AnswerAcceptRank); err != nil || !can {
|
||||||
|
handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := ac.answerService.UpdateAdopted(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminSetAnswerStatus godoc
|
||||||
|
// @Summary AdminSetAnswerStatus
|
||||||
|
// @Description Status:[available,deleted]
|
||||||
|
// @Tags admin
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body entity.AdminSetAnswerStatusRequest true "AdminSetAnswerStatusRequest"
|
||||||
|
// @Router /answer/admin/api/answer/status [put]
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
func (ac *AnswerController) AdminSetAnswerStatus(ctx *gin.Context) {
|
||||||
|
req := &entity.AdminSetAnswerStatusRequest{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := ac.answerService.AdminSetAnswerStatus(ctx, req.AnswerID, req.StatusStr)
|
||||||
|
handler.HandleResponse(ctx, err, gin.H{})
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/jinzhu/copier"
|
||||||
|
"github.com/segmentfault/answer/internal/base/handler"
|
||||||
|
"github.com/segmentfault/answer/internal/base/middleware"
|
||||||
|
"github.com/segmentfault/answer/internal/base/reason"
|
||||||
|
"github.com/segmentfault/answer/internal/schema"
|
||||||
|
"github.com/segmentfault/answer/internal/service"
|
||||||
|
"github.com/segmentfault/answer/pkg/converter"
|
||||||
|
"github.com/segmentfault/pacman/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CollectionController collection controller
|
||||||
|
type CollectionController struct {
|
||||||
|
collectionService *service.CollectionService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCollectionController new controller
|
||||||
|
func NewCollectionController(collectionService *service.CollectionService) *CollectionController {
|
||||||
|
return &CollectionController{collectionService: collectionService}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CollectionSwitch add collection
|
||||||
|
// @Summary add collection
|
||||||
|
// @Description add collection
|
||||||
|
// @Tags Collection
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body schema.CollectionSwitchReq true "collection"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=schema.CollectionSwitchResp}
|
||||||
|
// @Router /answer/api/v1/collection/switch [post]
|
||||||
|
func (cc *CollectionController) CollectionSwitch(ctx *gin.Context) {
|
||||||
|
req := &schema.CollectionSwitchReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dto := &schema.CollectionSwitchDTO{}
|
||||||
|
_ = copier.Copy(dto, req)
|
||||||
|
|
||||||
|
dto.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
|
||||||
|
if converter.StringToInt64(req.ObjectID) < 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if converter.StringToInt64(dto.UserID) < 1 {
|
||||||
|
handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := cc.collectionService.CollectionSwitch(ctx, dto)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/segmentfault/answer/internal/base/handler"
|
||||||
|
"github.com/segmentfault/answer/internal/base/middleware"
|
||||||
|
"github.com/segmentfault/answer/internal/base/reason"
|
||||||
|
"github.com/segmentfault/answer/internal/schema"
|
||||||
|
"github.com/segmentfault/answer/internal/service/comment"
|
||||||
|
"github.com/segmentfault/answer/internal/service/rank"
|
||||||
|
"github.com/segmentfault/pacman/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CommentController comment controller
|
||||||
|
type CommentController struct {
|
||||||
|
commentService *comment.CommentService
|
||||||
|
rankService *rank.RankService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCommentController new controller
|
||||||
|
func NewCommentController(
|
||||||
|
commentService *comment.CommentService,
|
||||||
|
rankService *rank.RankService) *CommentController {
|
||||||
|
return &CommentController{commentService: commentService, rankService: rankService}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddComment add comment
|
||||||
|
// @Summary add comment
|
||||||
|
// @Description add comment
|
||||||
|
// @Tags Comment
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body schema.AddCommentReq true "comment"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=schema.GetCommentResp}
|
||||||
|
// @Router /answer/api/v1/comment [post]
|
||||||
|
func (cc *CommentController) AddComment(ctx *gin.Context) {
|
||||||
|
req := &schema.AddCommentReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
if can, err := cc.rankService.CheckRankPermission(ctx, req.UserID, rank.CommentAddRank); err != nil || !can {
|
||||||
|
handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := cc.commentService.AddComment(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveComment remove comment
|
||||||
|
// @Summary remove comment
|
||||||
|
// @Description remove comment
|
||||||
|
// @Tags Comment
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body schema.RemoveCommentReq true "comment"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/api/v1/comment [delete]
|
||||||
|
func (cc *CommentController) RemoveComment(ctx *gin.Context) {
|
||||||
|
req := &schema.RemoveCommentReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
if can, err := cc.rankService.CheckRankPermission(ctx, req.UserID, rank.CommentDeleteRank); err != nil || !can {
|
||||||
|
handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cc.commentService.RemoveComment(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateComment update comment
|
||||||
|
// @Summary update comment
|
||||||
|
// @Description update comment
|
||||||
|
// @Tags Comment
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body schema.UpdateCommentReq true "comment"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/api/v1/comment [put]
|
||||||
|
func (cc *CommentController) UpdateComment(ctx *gin.Context) {
|
||||||
|
req := &schema.UpdateCommentReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
if can, err := cc.rankService.CheckRankPermission(ctx, req.UserID, rank.CommentEditRank); err != nil || !can {
|
||||||
|
handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cc.commentService.UpdateComment(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCommentWithPage get comment page
|
||||||
|
// @Summary get comment page
|
||||||
|
// @Description get comment page
|
||||||
|
// @Tags Comment
|
||||||
|
// @Produce json
|
||||||
|
// @Param page query int false "page"
|
||||||
|
// @Param page_size query int false "page size"
|
||||||
|
// @Param object_id query string true "object id"
|
||||||
|
// @Param query_cond query string false "query condition" Enums(vote)
|
||||||
|
// @Success 200 {object} handler.RespBody{data=pager.PageModel{list=[]schema.GetCommentResp}}
|
||||||
|
// @Router /answer/api/v1/comment/page [get]
|
||||||
|
func (cc *CommentController) GetCommentWithPage(ctx *gin.Context) {
|
||||||
|
req := &schema.GetCommentWithPageReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
|
||||||
|
resp, err := cc.commentService.GetCommentWithPage(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCommentPersonalWithPage user personal comment list
|
||||||
|
// @Summary user personal comment list
|
||||||
|
// @Description user personal comment list
|
||||||
|
// @Tags Comment
|
||||||
|
// @Produce json
|
||||||
|
// @Param page query int false "page"
|
||||||
|
// @Param page_size query int false "page size"
|
||||||
|
// @Param username query string false "username"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=pager.PageModel{list=[]schema.GetCommentPersonalWithPageResp}}
|
||||||
|
// @Router /answer/api/v1/personal/comment/page [get]
|
||||||
|
func (cc *CommentController) GetCommentPersonalWithPage(ctx *gin.Context) {
|
||||||
|
req := &schema.GetCommentPersonalWithPageReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
|
||||||
|
resp, err := cc.commentService.GetCommentPersonalWithPage(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetComment godoc
|
||||||
|
// @Summary get comment by id
|
||||||
|
// @Description get comment by id
|
||||||
|
// @Tags Comment
|
||||||
|
// @Produce json
|
||||||
|
// @Param id query string true "id"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=pager.PageModel{list=[]schema.GetCommentResp}}
|
||||||
|
// @Router /answer/api/v1/comment [get]
|
||||||
|
func (cc *CommentController) GetComment(ctx *gin.Context) {
|
||||||
|
req := &schema.GetCommentReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
|
||||||
|
resp, err := cc.commentService.GetComment(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import "github.com/google/wire"
|
||||||
|
|
||||||
|
// ProviderSetController is controller providers.
|
||||||
|
var ProviderSetController = wire.NewSet(
|
||||||
|
NewLangController,
|
||||||
|
NewCommentController,
|
||||||
|
NewReportController,
|
||||||
|
NewVoteController,
|
||||||
|
NewTagController,
|
||||||
|
NewFollowController,
|
||||||
|
NewCollectionController,
|
||||||
|
NewUserController,
|
||||||
|
NewQuestionController,
|
||||||
|
NewAnswerController,
|
||||||
|
NewSearchController,
|
||||||
|
NewRevisionController,
|
||||||
|
NewRankController,
|
||||||
|
NewReasonController,
|
||||||
|
NewNotificationController,
|
||||||
|
NewSiteinfoController,
|
||||||
|
)
|
|
@ -0,0 +1,70 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/jinzhu/copier"
|
||||||
|
"github.com/segmentfault/answer/internal/base/handler"
|
||||||
|
"github.com/segmentfault/answer/internal/base/middleware"
|
||||||
|
"github.com/segmentfault/answer/internal/schema"
|
||||||
|
"github.com/segmentfault/answer/internal/service/follow"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FollowController activity controller
|
||||||
|
type FollowController struct {
|
||||||
|
followService *follow.FollowService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFollowController new controller
|
||||||
|
func NewFollowController(followService *follow.FollowService) *FollowController {
|
||||||
|
return &FollowController{followService: followService}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Follow godoc
|
||||||
|
// @Summary follow object or cancel follow operation
|
||||||
|
// @Description follow object or cancel follow operation
|
||||||
|
// @Tags Activity
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body schema.FollowReq true "follow"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=schema.FollowResp}
|
||||||
|
// @Router /answer/api/v1/follow [post]
|
||||||
|
func (fc *FollowController) Follow(ctx *gin.Context) {
|
||||||
|
req := &schema.FollowReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dto := &schema.FollowDTO{}
|
||||||
|
_ = copier.Copy(dto, req)
|
||||||
|
dto.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
|
||||||
|
resp, err := fc.followService.Follow(ctx, dto)
|
||||||
|
if err != nil {
|
||||||
|
handler.HandleResponse(ctx, err, schema.ErrTypeToast)
|
||||||
|
} else {
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateFollowTags update user follow tags
|
||||||
|
// @Summary update user follow tags
|
||||||
|
// @Description update user follow tags
|
||||||
|
// @Tags Activity
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body schema.UpdateFollowTagsReq true "follow"
|
||||||
|
// @Success 200 {object} handler.RespBody{}
|
||||||
|
// @Router /answer/api/v1/follow/tags [put]
|
||||||
|
func (fc *FollowController) UpdateFollowTags(ctx *gin.Context) {
|
||||||
|
req := &schema.UpdateFollowTagsReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
|
||||||
|
err := fc.followService.UpdateFollowTags(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/segmentfault/answer/internal/base/handler"
|
||||||
|
"github.com/segmentfault/answer/internal/schema"
|
||||||
|
"github.com/segmentfault/pacman/i18n"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LangController struct {
|
||||||
|
translator i18n.Translator
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLangController new language controller.
|
||||||
|
func NewLangController(tr i18n.Translator) *LangController {
|
||||||
|
return &LangController{translator: tr}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLangMapping get language config mapping
|
||||||
|
// @Summary get language config mapping
|
||||||
|
// @Description get language config mapping
|
||||||
|
// @Tags Lang
|
||||||
|
// @Param Accept-Language header string true "Accept-Language"
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} handler.RespBody{}
|
||||||
|
// @Router /answer/api/v1/language/config [get]
|
||||||
|
func (u *LangController) GetLangMapping(ctx *gin.Context) {
|
||||||
|
data, _ := u.translator.Dump(handler.GetLang(ctx))
|
||||||
|
var resp map[string]any
|
||||||
|
_ = json.Unmarshal(data, &resp)
|
||||||
|
handler.HandleResponse(ctx, nil, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLangOptions Get language options
|
||||||
|
// @Summary Get language options
|
||||||
|
// @Description Get language options
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Tags Lang
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} handler.RespBody{}
|
||||||
|
// @Router /answer/api/v1/language/options [get]
|
||||||
|
// @Router /answer/admin/api/language/options [get]
|
||||||
|
func (u *LangController) GetLangOptions(ctx *gin.Context) {
|
||||||
|
handler.HandleResponse(ctx, nil, schema.GetLangOptions)
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/segmentfault/answer/internal/base/handler"
|
||||||
|
"github.com/segmentfault/answer/internal/base/middleware"
|
||||||
|
"github.com/segmentfault/answer/internal/schema"
|
||||||
|
"github.com/segmentfault/answer/internal/service/notification"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NotificationController notification controller
|
||||||
|
type NotificationController struct {
|
||||||
|
notificationService *notification.NotificationService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNotificationController new controller
|
||||||
|
func NewNotificationController(notificationService *notification.NotificationService) *NotificationController {
|
||||||
|
return &NotificationController{notificationService: notificationService}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRedDot
|
||||||
|
// @Summary GetRedDot
|
||||||
|
// @Description GetRedDot
|
||||||
|
// @Tags Notification
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/api/v1/notification/status [get]
|
||||||
|
func (nc *NotificationController) GetRedDot(ctx *gin.Context) {
|
||||||
|
userID := middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
RedDot, err := nc.notificationService.GetRedDot(ctx, userID)
|
||||||
|
handler.HandleResponse(ctx, err, RedDot)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearRedDot
|
||||||
|
// @Summary DelRedDot
|
||||||
|
// @Description DelRedDot
|
||||||
|
// @Tags Notification
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body schema.NotificationClearRequest true "NotificationClearRequest"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/api/v1/notification/status [put]
|
||||||
|
func (nc *NotificationController) ClearRedDot(ctx *gin.Context) {
|
||||||
|
req := &schema.NotificationClearRequest{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userID := middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
RedDot, err := nc.notificationService.ClearRedDot(ctx, userID, req.TypeStr)
|
||||||
|
handler.HandleResponse(ctx, err, RedDot)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearUnRead
|
||||||
|
// @Summary ClearUnRead
|
||||||
|
// @Description ClearUnRead
|
||||||
|
// @Tags Notification
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body schema.NotificationClearRequest true "NotificationClearRequest"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/api/v1/notification/read/state/all [put]
|
||||||
|
func (nc *NotificationController) ClearUnRead(ctx *gin.Context) {
|
||||||
|
req := &schema.NotificationClearRequest{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userID := middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
err := nc.notificationService.ClearUnRead(ctx, userID, req.TypeStr)
|
||||||
|
handler.HandleResponse(ctx, err, gin.H{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearIDUnRead
|
||||||
|
// @Summary ClearUnRead
|
||||||
|
// @Description ClearUnRead
|
||||||
|
// @Tags Notification
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body schema.NotificationClearIDRequest true "NotificationClearIDRequest"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/api/v1/notification/read/state [put]
|
||||||
|
func (nc *NotificationController) ClearIDUnRead(ctx *gin.Context) {
|
||||||
|
req := &schema.NotificationClearIDRequest{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userID := middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
err := nc.notificationService.ClearIDUnRead(ctx, userID, req.ID)
|
||||||
|
handler.HandleResponse(ctx, err, gin.H{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetList
|
||||||
|
// @Summary GetRedDot
|
||||||
|
// @Description GetRedDot
|
||||||
|
// @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)
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/api/v1/notification/page [get]
|
||||||
|
func (nc *NotificationController) GetList(ctx *gin.Context) {
|
||||||
|
req := &schema.NotificationSearch{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
list, count, err := nc.notificationService.GetList(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, gin.H{
|
||||||
|
"list": list,
|
||||||
|
"count": count,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,425 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/segmentfault/answer/internal/base/handler"
|
||||||
|
"github.com/segmentfault/answer/internal/base/middleware"
|
||||||
|
"github.com/segmentfault/answer/internal/base/reason"
|
||||||
|
"github.com/segmentfault/answer/internal/entity"
|
||||||
|
"github.com/segmentfault/answer/internal/schema"
|
||||||
|
"github.com/segmentfault/answer/internal/service"
|
||||||
|
"github.com/segmentfault/answer/internal/service/rank"
|
||||||
|
"github.com/segmentfault/answer/pkg/converter"
|
||||||
|
"github.com/segmentfault/pacman/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// QuestionController question controller
|
||||||
|
type QuestionController struct {
|
||||||
|
questionService *service.QuestionService
|
||||||
|
rankService *rank.RankService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewQuestionController new controller
|
||||||
|
func NewQuestionController(questionService *service.QuestionService, rankService *rank.RankService) *QuestionController {
|
||||||
|
return &QuestionController{questionService: questionService, rankService: rankService}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveQuestion delete question
|
||||||
|
// @Summary delete question
|
||||||
|
// @Description delete question
|
||||||
|
// @Tags api-question
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body schema.RemoveQuestionReq true "question"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/api/v1/question [delete]
|
||||||
|
func (qc *QuestionController) RemoveQuestion(ctx *gin.Context) {
|
||||||
|
req := &schema.RemoveQuestionReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
if can, err := qc.rankService.CheckRankPermission(ctx, req.UserID, rank.QuestionDeleteRank); err != nil || !can {
|
||||||
|
handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := qc.questionService.RemoveQuestion(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseQuestion Close question
|
||||||
|
// @Summary Close question
|
||||||
|
// @Description Close question
|
||||||
|
// @Tags api-question
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body schema.CloseQuestionReq true "question"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/api/v1/question/status [put]
|
||||||
|
func (qc *QuestionController) CloseQuestion(ctx *gin.Context) {
|
||||||
|
req := &schema.CloseQuestionReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.UserId = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
err := qc.questionService.CloseQuestion(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetQuestion godoc
|
||||||
|
// @Summary GetQuestion Question
|
||||||
|
// @Description GetQuestion Question
|
||||||
|
// @Tags api-question
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id query string true "Question TagID" default(1)
|
||||||
|
// @Success 200 {string} string ""
|
||||||
|
// @Router /answer/api/v1/question/info [get]
|
||||||
|
func (qc *QuestionController) GetQuestion(c *gin.Context) {
|
||||||
|
id := c.Query("id")
|
||||||
|
ctx := context.Background()
|
||||||
|
userID := middleware.GetLoginUserIDFromContext(c)
|
||||||
|
info, err := qc.questionService.GetQuestion(ctx, id, userID)
|
||||||
|
if err != nil {
|
||||||
|
handler.HandleResponse(c, err, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handler.HandleResponse(c, nil, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimilarQuestion godoc
|
||||||
|
// @Summary Search Similar Question
|
||||||
|
// @Description Search Similar Question
|
||||||
|
// @Tags api-question
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param question_id query string true "question_id" default()
|
||||||
|
// @Success 200 {string} string ""
|
||||||
|
// @Router /answer/api/v1/question/similar/tag [get]
|
||||||
|
func (qc *QuestionController) SimilarQuestion(ctx *gin.Context) {
|
||||||
|
questionID := ctx.Query("question_id")
|
||||||
|
userID := middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
list, count, err := qc.questionService.SimilarQuestion(ctx, questionID, userID)
|
||||||
|
if err != nil {
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handler.HandleResponse(ctx, nil, gin.H{
|
||||||
|
"list": list,
|
||||||
|
"count": count,
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index godoc
|
||||||
|
// @Summary SearchQuestionList
|
||||||
|
// @Description SearchQuestionList <br> "order" Enums(newest, active,frequent,score,unanswered)
|
||||||
|
// @Tags api-question
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param data body schema.QuestionSearch true "QuestionSearch"
|
||||||
|
// @Success 200 {string} string ""
|
||||||
|
// @Router /answer/api/v1/question/page [post]
|
||||||
|
func (qc *QuestionController) Index(ctx *gin.Context) {
|
||||||
|
req := &schema.QuestionSearch{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userID := middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
list, count, err := qc.questionService.SearchList(ctx, req, userID)
|
||||||
|
if err != nil {
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handler.HandleResponse(ctx, nil, gin.H{
|
||||||
|
"list": list,
|
||||||
|
"count": count,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchList godoc
|
||||||
|
// @Summary SearchQuestionList
|
||||||
|
// @Description SearchQuestionList
|
||||||
|
// @Tags api-question
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param data body schema.QuestionSearch true "QuestionSearch"
|
||||||
|
// @Router /answer/api/v1/question/search [post]
|
||||||
|
// @Success 200 {string} string ""
|
||||||
|
func (qc *QuestionController) SearchList(c *gin.Context) {
|
||||||
|
Request := new(schema.QuestionSearch)
|
||||||
|
err := c.BindJSON(Request)
|
||||||
|
if err != nil {
|
||||||
|
handler.HandleResponse(c, err, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
userID := middleware.GetLoginUserIDFromContext(c)
|
||||||
|
list, count, err := qc.questionService.SearchList(ctx, Request, userID)
|
||||||
|
if err != nil {
|
||||||
|
handler.HandleResponse(c, err, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handler.HandleResponse(c, nil, gin.H{
|
||||||
|
"list": list,
|
||||||
|
"count": count,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddQuestion add question
|
||||||
|
// @Summary add question
|
||||||
|
// @Description add question
|
||||||
|
// @Tags api-question
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body schema.QuestionAdd true "question"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/api/v1/question [post]
|
||||||
|
func (qc *QuestionController) AddQuestion(ctx *gin.Context) {
|
||||||
|
req := &schema.QuestionAdd{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
|
||||||
|
if can, err := qc.rankService.CheckRankPermission(ctx, req.UserID, rank.QuestionAddRank); err != nil || !can {
|
||||||
|
handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := qc.questionService.AddQuestion(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateQuestion update question
|
||||||
|
// @Summary update question
|
||||||
|
// @Description update question
|
||||||
|
// @Tags api-question
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body schema.QuestionUpdate true "question"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/api/v1/question [put]
|
||||||
|
func (qc *QuestionController) UpdateQuestion(ctx *gin.Context) {
|
||||||
|
req := &schema.QuestionUpdate{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
|
||||||
|
if can, err := qc.rankService.CheckRankPermission(ctx, req.UserID, rank.QuestionEditRank); err != nil || !can {
|
||||||
|
handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := qc.questionService.UpdateQuestion(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseMsgList close question msg list
|
||||||
|
// @Summary close question msg list
|
||||||
|
// @Description close question msg list
|
||||||
|
// @Tags api-question
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/api/v1/question/closemsglist [get]
|
||||||
|
func (qc *QuestionController) CloseMsgList(ctx *gin.Context) {
|
||||||
|
resp, err := qc.questionService.CloseMsgList(ctx, handler.GetLang(ctx))
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchByTitleLike add question title like
|
||||||
|
// @Summary add question title like
|
||||||
|
// @Description add question title like
|
||||||
|
// @Tags api-question
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param title query string true "title" default(string)
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/api/v1/question/similar [get]
|
||||||
|
func (qc *QuestionController) SearchByTitleLike(ctx *gin.Context) {
|
||||||
|
title := ctx.Query("title")
|
||||||
|
userID := middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
resp, err := qc.questionService.SearchByTitleLike(ctx, title, userID)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserTop godoc
|
||||||
|
// @Summary UserTop
|
||||||
|
// @Description UserTop
|
||||||
|
// @Tags api-question
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param username query string true "username" default(string)
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/api/v1/personal/qa/top [get]
|
||||||
|
func (qc *QuestionController) UserTop(ctx *gin.Context) {
|
||||||
|
userName := ctx.Query("username")
|
||||||
|
userID := middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
questionList, answerList, err := qc.questionService.SearchUserTopList(ctx, userName, userID)
|
||||||
|
handler.HandleResponse(ctx, err, gin.H{
|
||||||
|
"question": questionList,
|
||||||
|
"answer": answerList,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserList godoc
|
||||||
|
// @Summary UserList
|
||||||
|
// @Description UserList
|
||||||
|
// @Tags api-question
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param username query string true "username" default(string)
|
||||||
|
// @Param order query string true "order" Enums(newest,score)
|
||||||
|
// @Param page query string true "page" default(0)
|
||||||
|
// @Param pagesize query string true "pagesize" default(20)
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /personal/question/page [get]
|
||||||
|
func (qc *QuestionController) UserList(ctx *gin.Context) {
|
||||||
|
userName := ctx.Query("username")
|
||||||
|
order := ctx.Query("order")
|
||||||
|
pageStr := ctx.Query("page")
|
||||||
|
pageSizeStr := ctx.Query("pagesize")
|
||||||
|
page := converter.StringToInt(pageStr)
|
||||||
|
pageSize := converter.StringToInt(pageSizeStr)
|
||||||
|
userID := middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
questionList, count, err := qc.questionService.SearchUserList(ctx, userName, order, page, pageSize, userID)
|
||||||
|
handler.HandleResponse(ctx, err, gin.H{
|
||||||
|
"list": questionList,
|
||||||
|
"count": count,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserAnswerList godoc
|
||||||
|
// @Summary UserAnswerList
|
||||||
|
// @Description UserAnswerList
|
||||||
|
// @Tags api-answer
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param username query string true "username" default(string)
|
||||||
|
// @Param order query string true "order" Enums(newest,score)
|
||||||
|
// @Param page query string true "page" default(0)
|
||||||
|
// @Param pagesize query string true "pagesize" default(20)
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/api/v1/personal/answer/page [get]
|
||||||
|
func (qc *QuestionController) UserAnswerList(ctx *gin.Context) {
|
||||||
|
userName := ctx.Query("username")
|
||||||
|
order := ctx.Query("order")
|
||||||
|
pageStr := ctx.Query("page")
|
||||||
|
pageSizeStr := ctx.Query("pagesize")
|
||||||
|
page := converter.StringToInt(pageStr)
|
||||||
|
pageSize := converter.StringToInt(pageSizeStr)
|
||||||
|
userID := middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
questionList, count, err := qc.questionService.SearchUserAnswerList(ctx, userName, order, page, pageSize, userID)
|
||||||
|
handler.HandleResponse(ctx, err, gin.H{
|
||||||
|
"list": questionList,
|
||||||
|
"count": count,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserCollectionList godoc
|
||||||
|
// @Summary UserCollectionList
|
||||||
|
// @Description UserCollectionList
|
||||||
|
// @Tags Collection
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param page query string true "page" default(0)
|
||||||
|
// @Param pagesize query string true "pagesize" default(20)
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/api/v1/personal/collection/page [get]
|
||||||
|
func (qc *QuestionController) UserCollectionList(ctx *gin.Context) {
|
||||||
|
pageStr := ctx.Query("page")
|
||||||
|
pageSizeStr := ctx.Query("pagesize")
|
||||||
|
page := converter.StringToInt(pageStr)
|
||||||
|
pageSize := converter.StringToInt(pageSizeStr)
|
||||||
|
userID := middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
questionList, count, err := qc.questionService.SearchUserCollectionList(ctx, page, pageSize, userID)
|
||||||
|
handler.HandleResponse(ctx, err, gin.H{
|
||||||
|
"list": questionList,
|
||||||
|
"count": count,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmsSearchList godoc
|
||||||
|
// @Summary CmsSearchList
|
||||||
|
// @Description Status:[available,closed,deleted]
|
||||||
|
// @Tags admin
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param page query int false "page size"
|
||||||
|
// @Param page_size query int false "page size"
|
||||||
|
// @Param status query string false "user status" Enums(available, closed, deleted)
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/admin/api/question/page [get]
|
||||||
|
func (qc *QuestionController) CmsSearchList(ctx *gin.Context) {
|
||||||
|
req := &schema.CmsQuestionSearch{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userID := middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
questionList, count, err := qc.questionService.CmsSearchList(ctx, req, userID)
|
||||||
|
handler.HandleResponse(ctx, err, gin.H{
|
||||||
|
"list": questionList,
|
||||||
|
"count": count,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmsSearchAnswerList godoc
|
||||||
|
// @Summary CmsSearchList
|
||||||
|
// @Description Status:[available,deleted]
|
||||||
|
// @Tags admin
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param page query int false "page size"
|
||||||
|
// @Param page_size query int false "page size"
|
||||||
|
// @Param status query string false "user status" Enums(available,deleted)
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/admin/api/answer/page [get]
|
||||||
|
func (qc *QuestionController) CmsSearchAnswerList(ctx *gin.Context) {
|
||||||
|
req := &entity.CmsAnswerSearch{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userID := middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
questionList, count, err := qc.questionService.CmsSearchAnswerList(ctx, req, userID)
|
||||||
|
handler.HandleResponse(ctx, err, gin.H{
|
||||||
|
"list": questionList,
|
||||||
|
"count": count,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminSetQuestionStatus godoc
|
||||||
|
// @Summary AdminSetQuestionStatus
|
||||||
|
// @Description Status:[available,closed,deleted]
|
||||||
|
// @Tags admin
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body schema.AdminSetQuestionStatusRequest true "AdminSetQuestionStatusRequest"
|
||||||
|
// @Router /answer/admin/api/question/status [put]
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
func (qc *QuestionController) AdminSetQuestionStatus(ctx *gin.Context) {
|
||||||
|
req := &schema.AdminSetQuestionStatusRequest{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := qc.questionService.AdminSetQuestionStatus(ctx, req.QuestionID, req.StatusStr)
|
||||||
|
handler.HandleResponse(ctx, err, gin.H{})
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/segmentfault/answer/internal/base/handler"
|
||||||
|
"github.com/segmentfault/answer/internal/base/middleware"
|
||||||
|
"github.com/segmentfault/answer/internal/schema"
|
||||||
|
"github.com/segmentfault/answer/internal/service/rank"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RankController rank controller
|
||||||
|
type RankController struct {
|
||||||
|
rankService *rank.RankService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRankController new controller
|
||||||
|
func NewRankController(
|
||||||
|
rankService *rank.RankService) *RankController {
|
||||||
|
return &RankController{rankService: rankService}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRankPersonalWithPage user personal rank list
|
||||||
|
// @Summary user personal rank list
|
||||||
|
// @Description user personal rank list
|
||||||
|
// @Tags Rank
|
||||||
|
// @Produce json
|
||||||
|
// @Param page query int false "page"
|
||||||
|
// @Param page_size query int false "page size"
|
||||||
|
// @Param username query string false "username"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=pager.PageModel{list=[]schema.GetRankPersonalWithPageResp}}
|
||||||
|
// @Router /answer/api/v1/personal/rank/page [get]
|
||||||
|
func (cc *RankController) GetRankPersonalWithPage(ctx *gin.Context) {
|
||||||
|
req := &schema.GetRankPersonalWithPageReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
|
||||||
|
resp, err := cc.rankService.GetRankPersonalWithPage(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/segmentfault/answer/internal/base/handler"
|
||||||
|
"github.com/segmentfault/answer/internal/schema"
|
||||||
|
"github.com/segmentfault/answer/internal/service/reason"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReasonController answer controller
|
||||||
|
type ReasonController struct {
|
||||||
|
reasonService *reason.ReasonService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReasonController new controller
|
||||||
|
func NewReasonController(answerService *reason.ReasonService) *ReasonController {
|
||||||
|
return &ReasonController{reasonService: answerService}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reasons godoc
|
||||||
|
// @Summary get reasons by object type and action
|
||||||
|
// @Description get reasons by object type and action
|
||||||
|
// @Tags reason
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param object_type query string true "object_type" Enums(question, answer, comment, user)
|
||||||
|
// @Param action query string true "action" Enums(status, close, flag, review)
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/api/v1/reasons [get]
|
||||||
|
// @Router /answer/admin/api/reasons [get]
|
||||||
|
func (rc *ReasonController) Reasons(ctx *gin.Context) {
|
||||||
|
req := &schema.ReasonReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
reasons, err := rc.reasonService.GetReasons(ctx, *req)
|
||||||
|
if err != nil {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
handler.HandleResponse(ctx, err, reasons)
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/segmentfault/answer/internal/base/handler"
|
||||||
|
"github.com/segmentfault/answer/internal/base/middleware"
|
||||||
|
"github.com/segmentfault/answer/internal/base/reason"
|
||||||
|
"github.com/segmentfault/answer/internal/schema"
|
||||||
|
"github.com/segmentfault/answer/internal/service/rank"
|
||||||
|
"github.com/segmentfault/answer/internal/service/report"
|
||||||
|
"github.com/segmentfault/pacman/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReportController report controller
|
||||||
|
type ReportController struct {
|
||||||
|
reportService *report.ReportService
|
||||||
|
rankService *rank.RankService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReportController new controller
|
||||||
|
func NewReportController(reportService *report.ReportService, rankService *rank.RankService) *ReportController {
|
||||||
|
return &ReportController{reportService: reportService, rankService: rankService}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddReport add report
|
||||||
|
// @Summary add report
|
||||||
|
// @Description add report <br> source (question, answer, comment, user)
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Tags Report
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body schema.AddReportReq true "report"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/api/v1/report [post]
|
||||||
|
func (rc *ReportController) AddReport(ctx *gin.Context) {
|
||||||
|
req := &schema.AddReportReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
if can, err := rc.rankService.CheckRankPermission(ctx, req.UserID, rank.ReportAddRank); err != nil || !can {
|
||||||
|
handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := rc.reportService.AddReport(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReportTypeList get report type list
|
||||||
|
// @Summary get report type list
|
||||||
|
// @Description get report type list
|
||||||
|
// @Tags Report
|
||||||
|
// @Produce json
|
||||||
|
// @Param source query string true "report source" Enums(question, answer, comment, user)
|
||||||
|
// @Success 200 {object} handler.RespBody{data=[]schema.GetReportTypeResp}
|
||||||
|
// @Router /answer/api/v1/report/type/list [get]
|
||||||
|
func (rc *ReportController) GetReportTypeList(ctx *gin.Context) {
|
||||||
|
req := &schema.GetReportListReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := rc.reportService.GetReportTypeList(ctx, handler.GetLang(ctx), req)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/segmentfault/answer/internal/base/handler"
|
||||||
|
"github.com/segmentfault/answer/internal/base/reason"
|
||||||
|
"github.com/segmentfault/answer/internal/schema"
|
||||||
|
"github.com/segmentfault/answer/internal/service"
|
||||||
|
"github.com/segmentfault/pacman/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RevisionController revision controller
|
||||||
|
type RevisionController struct {
|
||||||
|
revisionListService *service.RevisionService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRevisionController new controller
|
||||||
|
func NewRevisionController(revisionListService *service.RevisionService) *RevisionController {
|
||||||
|
return &RevisionController{revisionListService: revisionListService}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRevision get revision one
|
||||||
|
// @Summary get revision one
|
||||||
|
// @Description get revision one
|
||||||
|
// @Tags Revision
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "revisionid"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=schema.GetRevisionResp}
|
||||||
|
// Router /revision/{id} [get]
|
||||||
|
func (rc *RevisionController) GetRevision(ctx *gin.Context) {
|
||||||
|
id := ctx.Param("id")
|
||||||
|
if id == "0" {
|
||||||
|
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := rc.revisionListService.GetRevision(ctx, id)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRevisionList godoc
|
||||||
|
// @Summary get revision list
|
||||||
|
// @Description get revision list
|
||||||
|
// @Tags Revision
|
||||||
|
// @Produce json
|
||||||
|
// @Param object_id query string true "object id"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=[]schema.GetRevisionResp}
|
||||||
|
// @Router /answer/api/v1/revisions [get]
|
||||||
|
func (rc *RevisionController) GetRevisionList(ctx *gin.Context) {
|
||||||
|
objectID := ctx.Query("object_id")
|
||||||
|
if objectID == "0" || objectID == "" {
|
||||||
|
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &schema.GetRevisionListReq{
|
||||||
|
ObjectID: objectID,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := rc.revisionListService.GetRevisionList(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/segmentfault/answer/internal/base/handler"
|
||||||
|
"github.com/segmentfault/answer/internal/base/middleware"
|
||||||
|
"github.com/segmentfault/answer/internal/base/reason"
|
||||||
|
"github.com/segmentfault/answer/internal/schema"
|
||||||
|
"github.com/segmentfault/answer/internal/service"
|
||||||
|
"github.com/segmentfault/answer/pkg/converter"
|
||||||
|
"github.com/segmentfault/pacman/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SearchController tag controller
|
||||||
|
type SearchController struct {
|
||||||
|
searchService *service.SearchService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSearchController new controller
|
||||||
|
func NewSearchController(searchService *service.SearchService) *SearchController {
|
||||||
|
return &SearchController{searchService: searchService}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search godoc
|
||||||
|
// @Summary search object
|
||||||
|
// @Description search object
|
||||||
|
// @Tags Search
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param q query string true "query string"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=schema.SearchListResp}
|
||||||
|
// @Router /answer/api/v1/search [get]
|
||||||
|
func (sc *SearchController) Search(ctx *gin.Context) {
|
||||||
|
var (
|
||||||
|
q string
|
||||||
|
page string
|
||||||
|
size string
|
||||||
|
ok bool
|
||||||
|
dto schema.SearchDTO
|
||||||
|
)
|
||||||
|
q, ok = ctx.GetQuery("q")
|
||||||
|
if len(q) == 0 || !ok {
|
||||||
|
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), q)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
page, ok = ctx.GetQuery("page")
|
||||||
|
if !ok {
|
||||||
|
page = "1"
|
||||||
|
}
|
||||||
|
size, ok = ctx.GetQuery("size")
|
||||||
|
if !ok {
|
||||||
|
size = "30"
|
||||||
|
}
|
||||||
|
|
||||||
|
dto = schema.SearchDTO{
|
||||||
|
Query: q,
|
||||||
|
Page: converter.StringToInt(page),
|
||||||
|
Size: converter.StringToInt(size),
|
||||||
|
UserID: middleware.GetLoginUserIDFromContext(ctx),
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, total, extra, err := sc.searchService.Search(ctx, &dto)
|
||||||
|
|
||||||
|
handler.HandleResponse(ctx, err, schema.SearchListResp{
|
||||||
|
Total: total,
|
||||||
|
SearchResp: resp,
|
||||||
|
Extra: extra,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/segmentfault/answer/internal/base/handler"
|
||||||
|
"github.com/segmentfault/answer/internal/schema"
|
||||||
|
"github.com/segmentfault/answer/internal/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SiteinfoController struct {
|
||||||
|
siteInfoService *service.SiteInfoService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSiteinfoController new siteinfo controller.
|
||||||
|
func NewSiteinfoController(siteInfoService *service.SiteInfoService) *SiteinfoController {
|
||||||
|
return &SiteinfoController{
|
||||||
|
siteInfoService: siteInfoService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInfo godoc
|
||||||
|
// @Summary Get siteinfo
|
||||||
|
// @Description Get siteinfo
|
||||||
|
// @Tags site
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} handler.RespBody{data=schema.SiteGeneralResp}
|
||||||
|
// @Router /answer/api/v1/siteinfo [get]
|
||||||
|
func (sc *SiteinfoController) GetInfo(ctx *gin.Context) {
|
||||||
|
var (
|
||||||
|
resp = &schema.SiteInfoResp{}
|
||||||
|
general schema.SiteGeneralResp
|
||||||
|
face schema.SiteInterfaceResp
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
general, err = sc.siteInfoService.GetSiteGeneral(ctx)
|
||||||
|
resp.General = &general
|
||||||
|
if err != nil {
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
face, err = sc.siteInfoService.GetSiteInterface(ctx)
|
||||||
|
resp.Face = &face
|
||||||
|
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
|
@ -0,0 +1,194 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/segmentfault/answer/internal/base/handler"
|
||||||
|
"github.com/segmentfault/answer/internal/base/middleware"
|
||||||
|
"github.com/segmentfault/answer/internal/base/reason"
|
||||||
|
"github.com/segmentfault/answer/internal/schema"
|
||||||
|
"github.com/segmentfault/answer/internal/service/rank"
|
||||||
|
"github.com/segmentfault/answer/internal/service/tag"
|
||||||
|
"github.com/segmentfault/pacman/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TagController tag controller
|
||||||
|
type TagController struct {
|
||||||
|
tagService *tag.TagService
|
||||||
|
rankService *rank.RankService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTagController new controller
|
||||||
|
func NewTagController(tagService *tag.TagService, rankService *rank.RankService) *TagController {
|
||||||
|
return &TagController{tagService: tagService, rankService: rankService}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchTagLike get tag list
|
||||||
|
// @Summary get tag list
|
||||||
|
// @Description get tag list
|
||||||
|
// @Tags Tag
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param tag query string false "tag"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=[]schema.GetTagResp}
|
||||||
|
// @Router /answer/api/v1/question/tags [get]
|
||||||
|
func (tc *TagController) SearchTagLike(ctx *gin.Context) {
|
||||||
|
req := &schema.SearchTagLikeReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := tc.tagService.SearchTagLike(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveTag delete tag
|
||||||
|
// @Summary delete tag
|
||||||
|
// @Description delete tag
|
||||||
|
// @Tags Tag
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param data body schema.RemoveTagReq true "tag"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/api/v1/tag [delete]
|
||||||
|
func (tc *TagController) RemoveTag(ctx *gin.Context) {
|
||||||
|
req := &schema.RemoveTagReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
if can, err := tc.rankService.CheckRankPermission(ctx, req.UserID, rank.TagDeleteRank); err != nil || !can {
|
||||||
|
handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := tc.tagService.RemoveTag(ctx, req.TagID)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTag update tag
|
||||||
|
// @Summary update tag
|
||||||
|
// @Description update tag
|
||||||
|
// @Tags Tag
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param data body schema.UpdateTagReq true "tag"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/api/v1/tag [put]
|
||||||
|
func (tc *TagController) UpdateTag(ctx *gin.Context) {
|
||||||
|
req := &schema.UpdateTagReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
if can, err := tc.rankService.CheckRankPermission(ctx, req.UserID, rank.TagEditRank); err != nil || !can {
|
||||||
|
handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := tc.tagService.UpdateTag(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTagInfo get tag one
|
||||||
|
// @Summary get tag one
|
||||||
|
// @Description get tag one
|
||||||
|
// @Tags Tag
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param tag_id query string true "tag id"
|
||||||
|
// @Param tag_name query string true "tag name"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=schema.GetTagResp}
|
||||||
|
// @Router /answer/api/v1/tag [get]
|
||||||
|
func (tc *TagController) GetTagInfo(ctx *gin.Context) {
|
||||||
|
req := &schema.GetTagInfoReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
|
||||||
|
resp, err := tc.tagService.GetTagInfo(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTagWithPage get tag page
|
||||||
|
// @Summary get tag page
|
||||||
|
// @Description get tag page
|
||||||
|
// @Tags Tag
|
||||||
|
// @Produce json
|
||||||
|
// @Param page query int false "page size"
|
||||||
|
// @Param page_size query int false "page size"
|
||||||
|
// @Param slug_name query string false "slug_name"
|
||||||
|
// @Param query_cond query string false "query condition" Enums(popular, name, newest)
|
||||||
|
// @Success 200 {object} handler.RespBody{data=pager.PageModel{list=[]schema.GetTagPageResp}}
|
||||||
|
// @Router /answer/api/v1/tags/page [get]
|
||||||
|
func (tc *TagController) GetTagWithPage(ctx *gin.Context) {
|
||||||
|
req := &schema.GetTagWithPageReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
|
||||||
|
resp, err := tc.tagService.GetTagWithPage(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFollowingTags get following tag list
|
||||||
|
// @Summary get following tag list
|
||||||
|
// @Description get following tag list
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Tags Tag
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} handler.RespBody{data=[]schema.GetFollowingTagsResp}
|
||||||
|
// @Router /answer/api/v1/tags/following [get]
|
||||||
|
func (tc *TagController) GetFollowingTags(ctx *gin.Context) {
|
||||||
|
userID := middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
resp, err := tc.tagService.GetFollowingTags(ctx, userID)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTagSynonyms get tag synonyms
|
||||||
|
// @Summary get tag synonyms
|
||||||
|
// @Description get tag synonyms
|
||||||
|
// @Tags Tag
|
||||||
|
// @Produce json
|
||||||
|
// @Param tag_id query int true "tag id"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=[]schema.GetTagSynonymsResp}
|
||||||
|
// @Router /answer/api/v1/tag/synonyms [get]
|
||||||
|
func (tc *TagController) GetTagSynonyms(ctx *gin.Context) {
|
||||||
|
req := &schema.GetTagSynonymsReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := tc.tagService.GetTagSynonyms(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTagSynonym update tag
|
||||||
|
// @Summary update tag
|
||||||
|
// @Description update tag
|
||||||
|
// @Tags Tag
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param data body schema.UpdateTagSynonymReq true "tag"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/api/v1/tag/synonym [put]
|
||||||
|
func (tc *TagController) UpdateTagSynonym(ctx *gin.Context) {
|
||||||
|
req := &schema.UpdateTagSynonymReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
if can, err := tc.rankService.CheckRankPermission(ctx, req.UserID, rank.TagSynonymRank); err != nil || !can {
|
||||||
|
handler.HandleResponse(ctx, err, errors.Forbidden(reason.RankFailToMeetTheCondition))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := tc.tagService.UpdateTagSynonym(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package useless
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/segmentfault/answer/internal/base/handler"
|
||||||
|
"github.com/segmentfault/answer/internal/schema"
|
||||||
|
"github.com/segmentfault/answer/internal/service"
|
||||||
|
"github.com/segmentfault/pacman/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CollectionGroupController collectionGroup controller
|
||||||
|
type CollectionGroupController struct {
|
||||||
|
log log.log
|
||||||
|
collectionGroupService *service.CollectionGroupService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCollectionGroupController new controller
|
||||||
|
func NewCollectionGroupController(collectionGroupService *service.CollectionGroupService) *CollectionGroupController {
|
||||||
|
return &CollectionGroupController{collectionGroupService: collectionGroupService}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCollectionGroup add collection group
|
||||||
|
// @Summary add collection group
|
||||||
|
// @Description add collection group
|
||||||
|
// @Tags CollectionGroup
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param data body schema.AddCollectionGroupReq true "collection group"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// Router /collection-group [post]
|
||||||
|
func (cc *CollectionGroupController) AddCollectionGroup(ctx *gin.Context) {
|
||||||
|
req := &schema.AddCollectionGroupReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cc.collectionGroupService.AddCollectionGroup(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCollectionGroup update collection group
|
||||||
|
// @Summary update collection group
|
||||||
|
// @Description update collection group
|
||||||
|
// @Tags CollectionGroup
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param data body schema.UpdateCollectionGroupReq true "collection group"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// Router /collection-group [put]
|
||||||
|
func (cc *CollectionGroupController) UpdateCollectionGroup(ctx *gin.Context) {
|
||||||
|
req := &schema.UpdateCollectionGroupReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cc.collectionGroupService.UpdateCollectionGroup(ctx, req, []string{})
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
package useless
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/segmentfault/answer/internal/base/handler"
|
||||||
|
"github.com/segmentfault/answer/internal/base/reason"
|
||||||
|
"github.com/segmentfault/answer/internal/schema"
|
||||||
|
"github.com/segmentfault/answer/internal/service/notification"
|
||||||
|
"github.com/segmentfault/pacman/errors"
|
||||||
|
"github.com/segmentfault/pacman/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NotificationReadController notificationRead controller
|
||||||
|
type NotificationReadController struct {
|
||||||
|
log log.log
|
||||||
|
notificationReadService *notification.NotificationReadService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNotificationReadController new controller
|
||||||
|
func NewNotificationReadController(notificationReadService *notification.NotificationReadService) *NotificationReadController {
|
||||||
|
return &NotificationReadController{notificationReadService: notificationReadService}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddNotificationRead add notification read record
|
||||||
|
// @Summary add notification read record
|
||||||
|
// @Description add notification read record
|
||||||
|
// @Tags NotificationRead
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param data body schema.AddNotificationReadReq true "notification read record"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// Router /notification-read [post]
|
||||||
|
func (nc *NotificationReadController) AddNotificationRead(ctx *gin.Context) {
|
||||||
|
req := &schema.AddNotificationReadReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := nc.notificationReadService.AddNotificationRead(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveNotificationRead delete notification read record
|
||||||
|
// @Summary delete notification read record
|
||||||
|
// @Description delete notification read record
|
||||||
|
// @Tags NotificationRead
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param data body schema.RemoveNotificationReadReq true "notification read record"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// Router /notification-read [delete]
|
||||||
|
func (nc *NotificationReadController) RemoveNotificationRead(ctx *gin.Context) {
|
||||||
|
req := &schema.RemoveNotificationReadReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := nc.notificationReadService.RemoveNotificationRead(ctx, req.ID)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateNotificationRead update notification read record
|
||||||
|
// @Summary update notification read record
|
||||||
|
// @Description update notification read record
|
||||||
|
// @Tags NotificationRead
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param data body schema.UpdateNotificationReadReq true "notification read record"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// Router /notification-read [put]
|
||||||
|
func (nc *NotificationReadController) UpdateNotificationRead(ctx *gin.Context) {
|
||||||
|
req := &schema.UpdateNotificationReadReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := nc.notificationReadService.UpdateNotificationRead(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNotificationRead get notification read record one
|
||||||
|
// @Summary get notification read record one
|
||||||
|
// @Description get notification read record one
|
||||||
|
// @Tags NotificationRead
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "notification read recordid"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=schema.GetNotificationReadResp}
|
||||||
|
// Router /notification-read/{id} [get]
|
||||||
|
func (nc *NotificationReadController) GetNotificationRead(ctx *gin.Context) {
|
||||||
|
id, _ := strconv.Atoi(ctx.Param("id"))
|
||||||
|
if id == 0 {
|
||||||
|
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := nc.notificationReadService.GetNotificationRead(ctx, id)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNotificationReadList get notification read record list
|
||||||
|
// @Summary get notification read record list
|
||||||
|
// @Description get notification read record list
|
||||||
|
// @Tags NotificationRead
|
||||||
|
// @Produce json
|
||||||
|
// @Param user_id query string false "user id"
|
||||||
|
// @Param message_id query string false "message id"
|
||||||
|
// @Param is_read query string false "read status(unread: 1; read 2)"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=[]schema.GetNotificationReadResp}
|
||||||
|
// Router /notification-reads [get]
|
||||||
|
func (nc *NotificationReadController) GetNotificationReadList(ctx *gin.Context) {
|
||||||
|
req := &schema.GetNotificationReadListReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := nc.notificationReadService.GetNotificationReadList(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNotificationReadWithPage get notification read record page
|
||||||
|
// @Summary get notification read record page
|
||||||
|
// @Description get notification read record page
|
||||||
|
// @Tags NotificationRead
|
||||||
|
// @Produce json
|
||||||
|
// @Param page query int false "page size"
|
||||||
|
// @Param page_size query int false "page size"
|
||||||
|
// @Param user_id query string false "user id"
|
||||||
|
// @Param message_id query string false "message id"
|
||||||
|
// @Param is_read query string false "read status(unread: 1; read 2)"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=pager.PageModel{list=[]schema.GetNotificationReadResp}}
|
||||||
|
// Router /notification-reads/page [get]
|
||||||
|
func (nc *NotificationReadController) GetNotificationReadWithPage(ctx *gin.Context) {
|
||||||
|
req := &schema.GetNotificationReadWithPageReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := nc.notificationReadService.GetNotificationReadWithPage(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
package useless
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/segmentfault/answer/internal/base/handler"
|
||||||
|
"github.com/segmentfault/answer/internal/base/reason"
|
||||||
|
"github.com/segmentfault/answer/internal/schema"
|
||||||
|
"github.com/segmentfault/answer/internal/service"
|
||||||
|
"github.com/segmentfault/pacman/errors"
|
||||||
|
"github.com/segmentfault/pacman/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserGroupController userGroup controller
|
||||||
|
type UserGroupController struct {
|
||||||
|
log log.log
|
||||||
|
userGroupService *service.UserGroupService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUserGroupController new controller
|
||||||
|
func NewUserGroupController(userGroupService *service.UserGroupService) *UserGroupController {
|
||||||
|
return &UserGroupController{userGroupService: userGroupService}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUserGroup add user group
|
||||||
|
// @Summary add user group
|
||||||
|
// @Description add user group
|
||||||
|
// @Tags UserGroup
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param data body schema.AddUserGroupReq true "user group"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// Router /user-group [post]
|
||||||
|
func (uc *UserGroupController) AddUserGroup(ctx *gin.Context) {
|
||||||
|
req := &schema.AddUserGroupReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := uc.userGroupService.AddUserGroup(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveUserGroup delete user group
|
||||||
|
// @Summary delete user group
|
||||||
|
// @Description delete user group
|
||||||
|
// @Tags UserGroup
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param data body schema.RemoveUserGroupReq true "user group"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// Router /user-group [delete]
|
||||||
|
func (uc *UserGroupController) RemoveUserGroup(ctx *gin.Context) {
|
||||||
|
req := &schema.RemoveUserGroupReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := uc.userGroupService.RemoveUserGroup(ctx, int(req.ID))
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserGroup update user group
|
||||||
|
// @Summary update user group
|
||||||
|
// @Description update user group
|
||||||
|
// @Tags UserGroup
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param data body schema.UpdateUserGroupReq true "user group"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// Router /user-group [put]
|
||||||
|
func (uc *UserGroupController) UpdateUserGroup(ctx *gin.Context) {
|
||||||
|
req := &schema.UpdateUserGroupReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := uc.userGroupService.UpdateUserGroup(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserGroup get user group one
|
||||||
|
// @Summary get user group one
|
||||||
|
// @Description get user group one
|
||||||
|
// @Tags UserGroup
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "user groupid"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=schema.GetUserGroupResp}
|
||||||
|
// Router /user-group/{id} [get]
|
||||||
|
func (uc *UserGroupController) GetUserGroup(ctx *gin.Context) {
|
||||||
|
id, _ := strconv.Atoi(ctx.Param("id"))
|
||||||
|
if id == 0 {
|
||||||
|
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := uc.userGroupService.GetUserGroup(ctx, id)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserGroupList get user group list
|
||||||
|
// @Summary get user group list
|
||||||
|
// @Description get user group list
|
||||||
|
// @Tags UserGroup
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} handler.RespBody{data=[]schema.GetUserGroupResp}
|
||||||
|
// Router /user-groups [get]
|
||||||
|
func (uc *UserGroupController) GetUserGroupList(ctx *gin.Context) {
|
||||||
|
req := &schema.GetUserGroupListReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := uc.userGroupService.GetUserGroupList(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserGroupWithPage get user group page
|
||||||
|
// @Summary get user group page
|
||||||
|
// @Description get user group page
|
||||||
|
// @Tags UserGroup
|
||||||
|
// @Produce json
|
||||||
|
// @Param page query int false "page size"
|
||||||
|
// @Param page_size query int false "page size"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=pager.PageModel{list=[]schema.GetUserGroupResp}}
|
||||||
|
// Router /user-groups/page [get]
|
||||||
|
func (uc *UserGroupController) GetUserGroupWithPage(ctx *gin.Context) {
|
||||||
|
req := &schema.GetUserGroupWithPageReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := uc.userGroupService.GetUserGroupWithPage(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
|
@ -0,0 +1,479 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/segmentfault/answer/internal/base/handler"
|
||||||
|
"github.com/segmentfault/answer/internal/base/middleware"
|
||||||
|
"github.com/segmentfault/answer/internal/base/reason"
|
||||||
|
"github.com/segmentfault/answer/internal/schema"
|
||||||
|
"github.com/segmentfault/answer/internal/service"
|
||||||
|
"github.com/segmentfault/answer/internal/service/action"
|
||||||
|
"github.com/segmentfault/answer/internal/service/auth"
|
||||||
|
"github.com/segmentfault/answer/internal/service/export"
|
||||||
|
"github.com/segmentfault/answer/internal/service/uploader"
|
||||||
|
"github.com/segmentfault/pacman/errors"
|
||||||
|
"github.com/segmentfault/pacman/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserController user controller
|
||||||
|
type UserController struct {
|
||||||
|
userService *service.UserService
|
||||||
|
authService *auth.AuthService
|
||||||
|
actionService *action.CaptchaService
|
||||||
|
uploaderService *uploader.UploaderService
|
||||||
|
emailService *export.EmailService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUserController new controller
|
||||||
|
func NewUserController(
|
||||||
|
authService *auth.AuthService,
|
||||||
|
userService *service.UserService,
|
||||||
|
actionService *action.CaptchaService,
|
||||||
|
emailService *export.EmailService,
|
||||||
|
uploaderService *uploader.UploaderService) *UserController {
|
||||||
|
return &UserController{
|
||||||
|
authService: authService,
|
||||||
|
userService: userService,
|
||||||
|
actionService: actionService,
|
||||||
|
uploaderService: uploaderService,
|
||||||
|
emailService: emailService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserInfoByUserID godoc
|
||||||
|
// @Summary GetUserInfoByUserID
|
||||||
|
// @Description GetUserInfoByUserID
|
||||||
|
// @Tags User
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Success 200 {object} handler.RespBody{data=schema.GetUserResp}
|
||||||
|
// @Router /answer/api/v1/user/info [get]
|
||||||
|
func (uc *UserController) GetUserInfoByUserID(ctx *gin.Context) {
|
||||||
|
userID := middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
token := middleware.ExtractToken(ctx)
|
||||||
|
resp, err := uc.userService.GetUserInfoByUserID(ctx, token, userID)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOtherUserInfoByUsername godoc
|
||||||
|
// @Summary GetOtherUserInfoByUsername
|
||||||
|
// @Description GetOtherUserInfoByUsername
|
||||||
|
// @Tags User
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param username query string true "username"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=schema.GetOtherUserInfoResp}
|
||||||
|
// @Router /answer/api/v1/personal/user/info [get]
|
||||||
|
func (uc *UserController) GetOtherUserInfoByUsername(ctx *gin.Context) {
|
||||||
|
req := &schema.GetOtherUserInfoByUsernameReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := uc.userService.GetOtherUserInfoByUsername(ctx, req.Username)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserEmailLogin godoc
|
||||||
|
// @Summary UserEmailLogin
|
||||||
|
// @Description UserEmailLogin
|
||||||
|
// @Tags User
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param data body schema.UserEmailLogin true "UserEmailLogin"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=schema.GetUserResp}
|
||||||
|
// @Router /answer/api/v1/user/login/email [post]
|
||||||
|
func (uc *UserController) UserEmailLogin(ctx *gin.Context) {
|
||||||
|
req := &schema.UserEmailLogin{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecord_Type_Login, ctx.ClientIP(), req.CaptchaID, req.CaptchaCode)
|
||||||
|
if !captchaPass {
|
||||||
|
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), gin.H{
|
||||||
|
"key": "captcha_code",
|
||||||
|
"value": "verification failed",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := uc.userService.EmailLogin(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
_, _ = uc.actionService.ActionRecordAdd(ctx, schema.ActionRecord_Type_Login, ctx.ClientIP())
|
||||||
|
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), gin.H{
|
||||||
|
"key": "e_mail",
|
||||||
|
"value": "Email or password incorrect",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uc.actionService.ActionRecordDel(ctx, schema.ActionRecord_Type_Login, ctx.ClientIP())
|
||||||
|
handler.HandleResponse(ctx, nil, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetrievePassWord godoc
|
||||||
|
// @Summary RetrievePassWord
|
||||||
|
// @Description RetrievePassWord
|
||||||
|
// @Tags User
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param data body schema.UserRetrievePassWordRequest true "UserRetrievePassWordRequest"
|
||||||
|
// @Success 200 {string} string ""
|
||||||
|
// @Router /answer/api/v1/user/password/reset [post]
|
||||||
|
func (uc *UserController) RetrievePassWord(ctx *gin.Context) {
|
||||||
|
req := &schema.UserRetrievePassWordRequest{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecord_Type_Find_Pass, ctx.ClientIP(), req.CaptchaID, req.CaptchaCode)
|
||||||
|
if !captchaPass {
|
||||||
|
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), gin.H{
|
||||||
|
"key": "captcha_code",
|
||||||
|
"value": "verification failed",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, _ = uc.actionService.ActionRecordAdd(ctx, schema.ActionRecord_Type_Find_Pass, ctx.ClientIP())
|
||||||
|
code, err := uc.userService.RetrievePassWord(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseRePassWord godoc
|
||||||
|
// @Summary UseRePassWord
|
||||||
|
// @Description UseRePassWord
|
||||||
|
// @Tags User
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param data body schema.UserRePassWordRequest true "UserRePassWordRequest"
|
||||||
|
// @Success 200 {string} string ""
|
||||||
|
// @Router /answer/api/v1/user/password/replacement [post]
|
||||||
|
func (uc *UserController) UseRePassWord(ctx *gin.Context) {
|
||||||
|
req := &schema.UserRePassWordRequest{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Content = uc.emailService.VerifyUrlExpired(ctx, req.Code)
|
||||||
|
if len(req.Content) == 0 {
|
||||||
|
handler.HandleResponse(ctx, errors.Forbidden(reason.EmailVerifyUrlExpired),
|
||||||
|
&schema.ForbiddenResp{Type: schema.ForbiddenReasonTypeUrlExpired})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := uc.userService.UseRePassWord(ctx, req)
|
||||||
|
uc.actionService.ActionRecordDel(ctx, schema.ActionRecord_Type_Find_Pass, ctx.ClientIP())
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserLogout user logout
|
||||||
|
// @Summary user logout
|
||||||
|
// @Description user logout
|
||||||
|
// @Tags User
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/api/v1/user/logout [get]
|
||||||
|
func (uc *UserController) UserLogout(ctx *gin.Context) {
|
||||||
|
accessToken := middleware.ExtractToken(ctx)
|
||||||
|
_ = uc.authService.RemoveUserCacheInfo(ctx, accessToken)
|
||||||
|
handler.HandleResponse(ctx, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserRegisterByEmail godoc
|
||||||
|
// @Summary UserRegisterByEmail
|
||||||
|
// @Description UserRegisterByEmail
|
||||||
|
// @Tags User
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param data body schema.UserRegister true "UserRegister"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=schema.GetUserResp}
|
||||||
|
// @Router /answer/api/v1/user/register/email [post]
|
||||||
|
func (uc *UserController) UserRegisterByEmail(ctx *gin.Context) {
|
||||||
|
req := &schema.UserRegister{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.IP = ctx.ClientIP()
|
||||||
|
|
||||||
|
resp, err := uc.userService.UserRegisterByEmail(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserVerifyEmail godoc
|
||||||
|
// @Summary UserVerifyEmail
|
||||||
|
// @Description UserVerifyEmail
|
||||||
|
// @Tags User
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param code query string true "code" default()
|
||||||
|
// @Success 200 {object} handler.RespBody{data=schema.GetUserResp}
|
||||||
|
// @Router /answer/api/v1/user/email/verification [post]
|
||||||
|
func (uc *UserController) UserVerifyEmail(ctx *gin.Context) {
|
||||||
|
req := &schema.UserVerifyEmailReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Content = uc.emailService.VerifyUrlExpired(ctx, req.Code)
|
||||||
|
if len(req.Content) == 0 {
|
||||||
|
handler.HandleResponse(ctx, errors.Forbidden(reason.EmailVerifyUrlExpired),
|
||||||
|
&schema.ForbiddenResp{Type: schema.ForbiddenReasonTypeUrlExpired})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := uc.userService.UserVerifyEmail(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uc.actionService.ActionRecordDel(ctx, schema.ActionRecord_Type_Email, ctx.ClientIP())
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserVerifyEmailSend godoc
|
||||||
|
// @Summary UserVerifyEmailSend
|
||||||
|
// @Description UserVerifyEmailSend
|
||||||
|
// @Tags User
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param captcha_id query string false "captcha_id" default()
|
||||||
|
// @Param captcha_code query string false "captcha_code" default()
|
||||||
|
// @Success 200 {string} string ""
|
||||||
|
// @Router /answer/api/v1/user/email/verification/send [post]
|
||||||
|
func (uc *UserController) UserVerifyEmailSend(ctx *gin.Context) {
|
||||||
|
req := &schema.UserVerifyEmailSendReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userInfo := middleware.GetUserInfoFromContext(ctx)
|
||||||
|
if userInfo == nil {
|
||||||
|
handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecord_Type_Email, ctx.ClientIP(),
|
||||||
|
req.CaptchaID, req.CaptchaCode)
|
||||||
|
if !captchaPass {
|
||||||
|
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), gin.H{
|
||||||
|
"key": "captcha_code",
|
||||||
|
"value": "verification failed",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uc.actionService.ActionRecordAdd(ctx, schema.ActionRecord_Type_Email, ctx.ClientIP())
|
||||||
|
err := uc.userService.UserVerifyEmailSend(ctx, userInfo.UserID)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserModifyPassWord godoc
|
||||||
|
// @Summary UserModifyPassWord
|
||||||
|
// @Description UserModifyPassWord
|
||||||
|
// @Tags User
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body schema.UserModifyPassWordRequest true "UserModifyPassWordRequest"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/api/v1/user/password [put]
|
||||||
|
func (uc *UserController) UserModifyPassWord(ctx *gin.Context) {
|
||||||
|
req := &schema.UserModifyPassWordRequest{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.UserId = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
|
||||||
|
oldPassVerification, err := uc.userService.UserModifyPassWordVerification(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !oldPassVerification {
|
||||||
|
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), gin.H{
|
||||||
|
"key": "old_pass",
|
||||||
|
"value": "the old password verification failed",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.OldPass == req.Pass {
|
||||||
|
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), gin.H{
|
||||||
|
"key": "pass",
|
||||||
|
"value": "The new password is the same as the previous setting",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = uc.userService.UserModifyPassWord(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserUpdateInfo godoc
|
||||||
|
// @Summary UserUpdateInfo
|
||||||
|
// @Description UserUpdateInfo
|
||||||
|
// @Tags User
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param Authorization header string true "access-token"
|
||||||
|
// @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) {
|
||||||
|
req := &schema.UpdateInfoRequest{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.UserId = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
err := uc.userService.UpdateInfo(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadUserAvatar godoc
|
||||||
|
// @Summary UserUpdateInfo
|
||||||
|
// @Description UserUpdateInfo
|
||||||
|
// @Tags User
|
||||||
|
// @Accept multipart/form-data
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param file formData file true "file"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=string}
|
||||||
|
// @Router /answer/api/v1/user/avatar/upload [post]
|
||||||
|
func (uc *UserController) UploadUserAvatar(ctx *gin.Context) {
|
||||||
|
// max size
|
||||||
|
ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, 10*1024*1024)
|
||||||
|
_, header, err := ctx.Request.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err.Error())
|
||||||
|
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fileExt := strings.ToLower(path.Ext(header.Filename))
|
||||||
|
if fileExt != ".jpg" && fileExt != ".png" && fileExt != ".jpeg" {
|
||||||
|
log.Errorf("upload file format is not supported: %s", fileExt)
|
||||||
|
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
url, err := uc.uploaderService.UploadAvatarFile(ctx, header, fileExt)
|
||||||
|
handler.HandleResponse(ctx, err, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadUserPostFile godoc
|
||||||
|
// @Summary upload user post file
|
||||||
|
// @Description upload user post file
|
||||||
|
// @Tags User
|
||||||
|
// @Accept multipart/form-data
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param file formData file true "file"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=string}
|
||||||
|
// @Router /answer/api/v1/user/post/file [post]
|
||||||
|
func (uc *UserController) UploadUserPostFile(ctx *gin.Context) {
|
||||||
|
// max size
|
||||||
|
ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, 10*1024*1024)
|
||||||
|
_, header, err := ctx.Request.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err.Error())
|
||||||
|
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fileExt := strings.ToLower(path.Ext(header.Filename))
|
||||||
|
if fileExt != ".jpg" && fileExt != ".png" && fileExt != ".jpeg" {
|
||||||
|
log.Errorf("upload file format is not supported: %s", fileExt)
|
||||||
|
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
url, err := uc.uploaderService.UploadPostFile(ctx, header, fileExt)
|
||||||
|
handler.HandleResponse(ctx, err, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActionRecord godoc
|
||||||
|
// @Summary ActionRecord
|
||||||
|
// @Description ActionRecord
|
||||||
|
// @Tags User
|
||||||
|
// @Param action query string true "action" Enums(login, e_mail, find_pass)
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Success 200 {object} handler.RespBody{data=schema.ActionRecordResp}
|
||||||
|
// @Router /answer/api/v1/user/action/record [get]
|
||||||
|
func (uc *UserController) ActionRecord(ctx *gin.Context) {
|
||||||
|
req := &schema.ActionRecordReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Ip = ctx.ClientIP()
|
||||||
|
|
||||||
|
resp, err := uc.actionService.ActionRecord(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserNoticeSet godoc
|
||||||
|
// @Summary UserNoticeSet
|
||||||
|
// @Description UserNoticeSet
|
||||||
|
// @Tags User
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body schema.UserNoticeSetRequest true "UserNoticeSetRequest"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=schema.UserNoticeSetResp}
|
||||||
|
// @Router /answer/api/v1/user/notice/set [post]
|
||||||
|
func (uc *UserController) UserNoticeSet(ctx *gin.Context) {
|
||||||
|
req := &schema.UserNoticeSetRequest{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.UserId = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
resp, err := uc.userService.UserNoticeSet(ctx, req.UserId, req.NoticeSwitch)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserChangeEmailSendCode send email to the user email then change their email
|
||||||
|
// @Summary send email to the user email then change their email
|
||||||
|
// @Description send email to the user email then change their email
|
||||||
|
// @Tags User
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param data body schema.UserChangeEmailSendCodeReq true "UserChangeEmailSendCodeReq"
|
||||||
|
// @Success 200 {object} handler.RespBody{}
|
||||||
|
// @Router /answer/api/v1/user/email/change/code [post]
|
||||||
|
func (uc *UserController) UserChangeEmailSendCode(ctx *gin.Context) {
|
||||||
|
req := &schema.UserChangeEmailSendCodeReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
|
||||||
|
err := uc.userService.UserChangeEmailSendCode(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserChangeEmailVerify user change email verification
|
||||||
|
// @Summary user change email verification
|
||||||
|
// @Description user change email verification
|
||||||
|
// @Tags User
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body schema.UserChangeEmailVerifyReq true "UserChangeEmailVerifyReq"
|
||||||
|
// @Success 200 {object} handler.RespBody{}
|
||||||
|
// @Router /answer/api/v1/user/email [put]
|
||||||
|
func (uc *UserController) UserChangeEmailVerify(ctx *gin.Context) {
|
||||||
|
req := &schema.UserChangeEmailVerifyReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Content = uc.emailService.VerifyUrlExpired(ctx, req.Code)
|
||||||
|
if len(req.Content) == 0 {
|
||||||
|
handler.HandleResponse(ctx, errors.Forbidden(reason.EmailVerifyUrlExpired),
|
||||||
|
&schema.ForbiddenResp{Type: schema.ForbiddenReasonTypeUrlExpired})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := uc.userService.UserChangeEmailVerify(ctx, req.Content)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/jinzhu/copier"
|
||||||
|
"github.com/segmentfault/answer/internal/base/handler"
|
||||||
|
"github.com/segmentfault/answer/internal/base/middleware"
|
||||||
|
"github.com/segmentfault/answer/internal/schema"
|
||||||
|
"github.com/segmentfault/answer/internal/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VoteController activity controller
|
||||||
|
type VoteController struct {
|
||||||
|
VoteService *service.VoteService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVoteController new controller
|
||||||
|
func NewVoteController(voteService *service.VoteService) *VoteController {
|
||||||
|
return &VoteController{VoteService: voteService}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VoteUp godoc
|
||||||
|
// @Summary vote up
|
||||||
|
// @Description add vote
|
||||||
|
// @Tags Activity
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body schema.VoteReq true "vote"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=schema.VoteResp}
|
||||||
|
// @Router /answer/api/v1/vote/up [post]
|
||||||
|
func (vc *VoteController) VoteUp(ctx *gin.Context) {
|
||||||
|
req := &schema.VoteReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dto := &schema.VoteDTO{}
|
||||||
|
_ = copier.Copy(dto, req)
|
||||||
|
dto.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
resp, err := vc.VoteService.VoteUp(ctx, dto)
|
||||||
|
if err != nil {
|
||||||
|
handler.HandleResponse(ctx, err, schema.ErrTypeToast)
|
||||||
|
} else {
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VoteDown godoc
|
||||||
|
// @Summary vote down
|
||||||
|
// @Description add vote
|
||||||
|
// @Tags Activity
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body schema.VoteReq true "vote"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=schema.VoteResp}
|
||||||
|
// @Router /answer/api/v1/vote/down [post]
|
||||||
|
func (vc *VoteController) VoteDown(ctx *gin.Context) {
|
||||||
|
req := &schema.VoteReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dto := &schema.VoteDTO{}
|
||||||
|
_ = copier.Copy(dto, req)
|
||||||
|
|
||||||
|
dto.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
resp, err := vc.VoteService.VoteDown(ctx, dto)
|
||||||
|
if err != nil {
|
||||||
|
handler.HandleResponse(ctx, err, schema.ErrTypeToast)
|
||||||
|
} else {
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserVotes godoc
|
||||||
|
// @Summary user's votes
|
||||||
|
// @Description user's vote
|
||||||
|
// @Tags Activity
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param page query int false "page size"
|
||||||
|
// @Param page_size query int false "page size"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=pager.PageModel{list=[]schema.GetVoteWithPageResp}}
|
||||||
|
// @Router /answer/api/v1/personal/vote/page [get]
|
||||||
|
func (vc *VoteController) UserVotes(ctx *gin.Context) {
|
||||||
|
req := schema.GetVoteWithPageReq{}
|
||||||
|
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
|
if handler.BindAndCheck(ctx, &req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.Page == 0 {
|
||||||
|
req.Page = 1
|
||||||
|
}
|
||||||
|
if req.PageSize == 0 {
|
||||||
|
req.PageSize = 30
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := vc.VoteService.ListUserVotes(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
handler.HandleResponse(ctx, err, schema.ErrTypeModal)
|
||||||
|
} else {
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package controller_backyard
|
||||||
|
|
||||||
|
import "github.com/google/wire"
|
||||||
|
|
||||||
|
// ProviderSetController is controller providers.
|
||||||
|
var ProviderSetController = wire.NewSet(
|
||||||
|
NewReportController,
|
||||||
|
NewUserBackyardController,
|
||||||
|
NewThemeController,
|
||||||
|
NewSiteInfoController,
|
||||||
|
)
|
|
@ -0,0 +1,77 @@
|
||||||
|
package controller_backyard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/segmentfault/answer/internal/base/handler"
|
||||||
|
"github.com/segmentfault/answer/internal/schema"
|
||||||
|
"github.com/segmentfault/answer/internal/service/report_backyard"
|
||||||
|
"github.com/segmentfault/answer/pkg/converter"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReportController report controller
|
||||||
|
type ReportController struct {
|
||||||
|
reportService *report_backyard.ReportBackyardService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReportController new controller
|
||||||
|
func NewReportController(reportService *report_backyard.ReportBackyardService) *ReportController {
|
||||||
|
return &ReportController{reportService: reportService}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListReportPage godoc
|
||||||
|
// @Summary list report page
|
||||||
|
// @Description list report records
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Tags admin
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param status query string true "status" Enums(pending, completed)
|
||||||
|
// @Param object_type query string true "object_type" Enums(all, question,answer,comment)
|
||||||
|
// @Param page query int false "page size"
|
||||||
|
// @Param page_size query int false "page size"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/admin/api/reports/page [get]
|
||||||
|
func (rc *ReportController) ListReportPage(ctx *gin.Context) {
|
||||||
|
var (
|
||||||
|
objectType = ctx.Query("object_type")
|
||||||
|
status = ctx.Query("status")
|
||||||
|
page = converter.StringToInt(ctx.DefaultQuery("page", "1"))
|
||||||
|
pageSize = converter.StringToInt(ctx.DefaultQuery("page_size", "20"))
|
||||||
|
)
|
||||||
|
|
||||||
|
dto := schema.GetReportListPageDTO{
|
||||||
|
ObjectType: objectType,
|
||||||
|
Status: status,
|
||||||
|
Page: page,
|
||||||
|
PageSize: pageSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := rc.reportService.ListReportPage(ctx, dto)
|
||||||
|
if err != nil {
|
||||||
|
handler.HandleResponse(ctx, err, schema.ErrTypeModal)
|
||||||
|
} else {
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle godoc
|
||||||
|
// @Summary handle flag
|
||||||
|
// @Description handle flag
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Tags admin
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body schema.ReportHandleReq true "flag"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/admin/api/report/ [put]
|
||||||
|
func (rc *ReportController) Handle(ctx *gin.Context) {
|
||||||
|
req := schema.ReportHandleReq{}
|
||||||
|
if handler.BindAndCheck(ctx, &req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := rc.reportService.HandleReported(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package controller_backyard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/segmentfault/answer/internal/base/handler"
|
||||||
|
"github.com/segmentfault/answer/internal/schema"
|
||||||
|
"github.com/segmentfault/answer/internal/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SiteInfoController struct {
|
||||||
|
siteInfoService *service.SiteInfoService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSiteInfoController new siteinfo controller.
|
||||||
|
func NewSiteInfoController(siteInfoService *service.SiteInfoService) *SiteInfoController {
|
||||||
|
return &SiteInfoController{
|
||||||
|
siteInfoService: siteInfoService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGeneral godoc
|
||||||
|
// @Summary Get siteinfo general
|
||||||
|
// @Description Get siteinfo general
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Tags admin
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} handler.RespBody{data=schema.SiteGeneralResp}
|
||||||
|
// @Router /answer/admin/api/siteinfo/general [get]
|
||||||
|
func (sc *SiteInfoController) GetGeneral(ctx *gin.Context) {
|
||||||
|
resp, err := sc.siteInfoService.GetSiteGeneral(ctx)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInterface godoc
|
||||||
|
// @Summary Get siteinfo interface
|
||||||
|
// @Description Get siteinfo interface
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Tags admin
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} handler.RespBody{data=schema.SiteInterfaceResp}
|
||||||
|
// @Router /answer/admin/api/siteinfo/interface [get]
|
||||||
|
// @Param data body schema.AddCommentReq true "general"
|
||||||
|
func (sc *SiteInfoController) GetInterface(ctx *gin.Context) {
|
||||||
|
resp, err := sc.siteInfoService.GetSiteInterface(ctx)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateGeneral godoc
|
||||||
|
// @Summary Get siteinfo interface
|
||||||
|
// @Description Get siteinfo interface
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Tags admin
|
||||||
|
// @Produce json
|
||||||
|
// @Param data body schema.SiteGeneralReq true "general"
|
||||||
|
// @Success 200 {object} handler.RespBody{}
|
||||||
|
// @Router /answer/admin/api/siteinfo/general [put]
|
||||||
|
func (sc *SiteInfoController) UpdateGeneral(ctx *gin.Context) {
|
||||||
|
req := schema.SiteGeneralReq{}
|
||||||
|
handler.BindAndCheck(ctx, &req)
|
||||||
|
err := sc.siteInfoService.SaveSiteGeneral(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateInterface godoc
|
||||||
|
// @Summary Get siteinfo interface
|
||||||
|
// @Description Get siteinfo interface
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Tags admin
|
||||||
|
// @Produce json
|
||||||
|
// @Param data body schema.SiteInterfaceReq true "general"
|
||||||
|
// @Success 200 {object} handler.RespBody{}
|
||||||
|
// @Router /answer/admin/api/siteinfo/interface [put]
|
||||||
|
func (sc *SiteInfoController) UpdateInterface(ctx *gin.Context) {
|
||||||
|
req := schema.SiteInterfaceReq{}
|
||||||
|
handler.BindAndCheck(ctx, &req)
|
||||||
|
err := sc.siteInfoService.SaveSiteInterface(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package controller_backyard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/segmentfault/answer/internal/base/handler"
|
||||||
|
"github.com/segmentfault/answer/internal/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ThemeController struct{}
|
||||||
|
|
||||||
|
// NewThemeController new theme controller.
|
||||||
|
func NewThemeController() *ThemeController {
|
||||||
|
return &ThemeController{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetThemeOptions godoc
|
||||||
|
// @Summary Get theme options
|
||||||
|
// @Description Get theme options
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Tags admin
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} handler.RespBody{}
|
||||||
|
// @Router /answer/admin/api/theme/options [get]
|
||||||
|
func (t *ThemeController) GetThemeOptions(ctx *gin.Context) {
|
||||||
|
handler.HandleResponse(ctx, nil, schema.GetThemeOptions)
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
package controller_backyard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/segmentfault/answer/internal/base/handler"
|
||||||
|
"github.com/segmentfault/answer/internal/base/reason"
|
||||||
|
"github.com/segmentfault/answer/internal/schema"
|
||||||
|
"github.com/segmentfault/answer/internal/service/user_backyard"
|
||||||
|
"github.com/segmentfault/pacman/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserBackyardController user controller
|
||||||
|
type UserBackyardController struct {
|
||||||
|
userService *user_backyard.UserBackyardService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUserBackyardController new controller
|
||||||
|
func NewUserBackyardController(userService *user_backyard.UserBackyardService) *UserBackyardController {
|
||||||
|
return &UserBackyardController{userService: userService}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserStatus update user
|
||||||
|
// @Summary update user
|
||||||
|
// @Description update user
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Tags admin
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param data body schema.UpdateUserStatusReq true "user"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/admin/api/user/status [put]
|
||||||
|
func (uc *UserBackyardController) UpdateUserStatus(ctx *gin.Context) {
|
||||||
|
req := &schema.UpdateUserStatusReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := uc.userService.UpdateUserStatus(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserInfo get user one
|
||||||
|
// @Summary get user one
|
||||||
|
// @Description get user one
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Tags admin
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "userid"
|
||||||
|
// @Success 200 {object} handler.RespBody{data=schema.GetUserInfoResp}
|
||||||
|
// Router /user/{id} [get]
|
||||||
|
func (uc *UserBackyardController) GetUserInfo(ctx *gin.Context) {
|
||||||
|
userID := ctx.Query("user_id")
|
||||||
|
if len(userID) == 0 {
|
||||||
|
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := uc.userService.GetUserInfo(ctx, userID)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserPage get user page
|
||||||
|
// @Summary get user page
|
||||||
|
// @Description get user page
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Tags admin
|
||||||
|
// @Produce json
|
||||||
|
// @Param page query int false "page size"
|
||||||
|
// @Param page_size query int false "page size"
|
||||||
|
// @Param username query string false "username"
|
||||||
|
// @Param e_mail query string false "email"
|
||||||
|
// @Param status query string false "user status" Enums(normal, suspended, deleted, inactive)
|
||||||
|
// @Success 200 {object} handler.RespBody{data=pager.PageModel{records=[]schema.GetUserPageResp}}
|
||||||
|
// @Router /answer/admin/api/users/page [get]
|
||||||
|
func (uc *UserBackyardController) GetUserPage(ctx *gin.Context) {
|
||||||
|
req := &schema.GetUserPageReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := uc.userService.GetUserPage(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, resp)
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const (
|
||||||
|
ActivityAvailable = 0
|
||||||
|
ActivityCancelled = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// Activity activity
|
||||||
|
type Activity struct {
|
||||||
|
ID string `xorm:"not null pk autoincr comment('Activity TagID autoincrement') BIGINT(20) id"`
|
||||||
|
CreatedAt time.Time `xorm:"created comment('create time') TIMESTAMP created_at"`
|
||||||
|
UpdatedAt time.Time `xorm:"updated comment('update time') TIMESTAMP updated_at"`
|
||||||
|
UserID string `xorm:"not null comment('the user ID that generated the activity or affected by the activity') index BIGINT(20) user_id"`
|
||||||
|
TriggerUserID int64 `xorm:"not null default 0 comment('the trigger user TagID that generated the activity or affected by the activity') index BIGINT(20) trigger_user_id"`
|
||||||
|
ObjectID string `xorm:"not null default 0 comment('the object TagID that affected by the activity') index BIGINT(20) object_id"`
|
||||||
|
ActivityType int `xorm:"not null comment('activity type, correspond to config id') INT(11) activity_type"`
|
||||||
|
Cancelled int `xorm:"not null default 0 comment('mark this activity if cancelled or not,default 0(not cancelled)') TINYINT(4) cancelled"`
|
||||||
|
Rank int `xorm:"not null default 0 comment('rank of current operating user affected') INT(11) rank"`
|
||||||
|
HasRank int `xorm:"not null default 0 comment('this activity has rank or not') TINYINT(4) has_rank"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActivityRunkSum struct {
|
||||||
|
Rank int `xorm:"not null default 0 comment('rank of current operating user affected') INT(11) rank"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName activity table name
|
||||||
|
func (Activity) TableName() string {
|
||||||
|
return "activity"
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const (
|
||||||
|
Answer_Search_OrderBy_Default = "default"
|
||||||
|
Answer_Search_OrderBy_Time = "updated"
|
||||||
|
Answer_Search_OrderBy_Vote = "vote"
|
||||||
|
|
||||||
|
AnswerStatusAvailable = 1
|
||||||
|
AnswerStatusDeleted = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
var CmsAnswerSearchStatus = map[string]int{
|
||||||
|
"available": AnswerStatusAvailable,
|
||||||
|
"deleted": AnswerStatusDeleted,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Answer answer
|
||||||
|
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"`
|
||||||
|
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"`
|
||||||
|
ParsedText string `xorm:"not null comment('parsed content') MEDIUMTEXT parsed_text"`
|
||||||
|
Status int `xorm:"not null default 1 comment(' answer status(available: 1; deleted: 10)') INT(11) status"`
|
||||||
|
Adopted int `xorm:"not null default 1 comment('adopted (1 failed 2 adopted)') INT(11) adopted"`
|
||||||
|
CommentCount int `xorm:"not null default 0 comment('comment count') INT(11) comment_count"`
|
||||||
|
VoteCount int `xorm:"not null default 0 comment('vote count') INT(11) vote_count"`
|
||||||
|
RevisionID string `xorm:"not null default 0 BIGINT(20) revision_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AnswerSearch struct {
|
||||||
|
Answer
|
||||||
|
Order string `json:"order_by" ` // default or updated
|
||||||
|
Page int `json:"page" form:"page"` //Query number of pages
|
||||||
|
PageSize int `json:"page_size" form:"page_size"` //Search page size
|
||||||
|
}
|
||||||
|
|
||||||
|
type CmsAnswerSearch 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
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdminSetAnswerStatusRequest struct {
|
||||||
|
StatusStr string `json:"status" form:"status"`
|
||||||
|
AnswerID string `json:"answer_id" form:"answer_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName answer table name
|
||||||
|
func (Answer) TableName() string {
|
||||||
|
return "answer"
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
// UserCacheInfo 用户缓存信息
|
||||||
|
type UserCacheInfo struct {
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
UserStatus int `json:"user_status"`
|
||||||
|
EmailStatus int `json:"email_status"`
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const ()
|
||||||
|
|
||||||
|
// Collection collection
|
||||||
|
type Collection struct {
|
||||||
|
ID string `xorm:"not null pk default 0 comment('collection id') BIGINT(20) id"`
|
||||||
|
UserID string `xorm:"not null default 0 comment('user id') BIGINT(20) user_id"`
|
||||||
|
ObjectID string `xorm:"not null default 0 comment('object id') BIGINT(20) object_id"`
|
||||||
|
UserCollectionGroupID string `xorm:"not null default 0 comment('user collection group id') BIGINT(20) user_collection_group_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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CollectionSearch struct {
|
||||||
|
Collection
|
||||||
|
Page int `json:"page" form:"page"` //Query number of pages
|
||||||
|
PageSize int `json:"page_size" form:"page_size"` //Search page size
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName collection table name
|
||||||
|
func (Collection) TableName() string {
|
||||||
|
return "collection"
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// CollectionGroup collection group
|
||||||
|
type CollectionGroup struct {
|
||||||
|
ID string `xorm:"not null pk autoincr BIGINT(20) id"`
|
||||||
|
UserID string `xorm:"not null default 0 BIGINT(20) user_id"`
|
||||||
|
Name string `xorm:"not null default '' comment('the collection group name') VARCHAR(50) name"`
|
||||||
|
DefaultGroup int `xorm:"not null default 1 comment('mark this group is default, default 1') INT(11) default_group"`
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName collection group table name
|
||||||
|
func (CollectionGroup) TableName() string {
|
||||||
|
return "collection_group"
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/segmentfault/answer/pkg/converter"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CommentStatusAvailable = 1
|
||||||
|
CommentStatusDeleted = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
// Comment comment
|
||||||
|
type Comment struct {
|
||||||
|
ID string `xorm:"not null pk autoincr comment('comment id') BIGINT(20) id"`
|
||||||
|
CreatedAt time.Time `xorm:"created comment('create time') TIMESTAMP created_at"`
|
||||||
|
UpdatedAt time.Time `xorm:"updated comment('update time') TIMESTAMP updated_at"`
|
||||||
|
UserID string `xorm:"not null default 0 comment('user id') BIGINT(20) user_id"`
|
||||||
|
ReplyUserID sql.NullInt64 `xorm:"comment('reply user id') BIGINT(20) reply_user_id"`
|
||||||
|
ReplyCommentID sql.NullInt64 `xorm:"comment('reply comment id') BIGINT(20) reply_comment_id"`
|
||||||
|
ObjectID string `xorm:"not null default 0 comment('object id') BIGINT(20) object_id"`
|
||||||
|
QuestionID string `xorm:"not null default 0 comment('question id') BIGINT(20) question_id"`
|
||||||
|
VoteCount int `xorm:"not null default 0 comment('user vote amount') INT(11) vote_count"`
|
||||||
|
Status int `xorm:"not null default 0 comment('comment status(available: 1; deleted: 10)') TINYINT(4) status"`
|
||||||
|
OriginalText string `xorm:"not null comment('original comment content') MEDIUMTEXT original_text"`
|
||||||
|
ParsedText string `xorm:"not null comment('parsed comment content') MEDIUMTEXT parsed_text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName comment table name
|
||||||
|
func (c *Comment) TableName() string {
|
||||||
|
return "comment"
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReplyUserID get reply user id
|
||||||
|
func (c *Comment) GetReplyUserID() string {
|
||||||
|
if c.ReplyUserID.Valid {
|
||||||
|
return fmt.Sprintf("%d", c.ReplyUserID.Int64)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReplyCommentID get reply comment id
|
||||||
|
func (c *Comment) GetReplyCommentID() string {
|
||||||
|
if c.ReplyCommentID.Valid {
|
||||||
|
return fmt.Sprintf("%d", c.ReplyCommentID.Int64)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReplyUserID set reply user id
|
||||||
|
func (c *Comment) SetReplyUserID(str string) {
|
||||||
|
if len(str) > 0 {
|
||||||
|
c.ReplyUserID = sql.NullInt64{Int64: converter.StringToInt64(str), Valid: true}
|
||||||
|
} else {
|
||||||
|
c.ReplyUserID = sql.NullInt64{Valid: false}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReplyCommentID set reply comment id
|
||||||
|
func (c *Comment) SetReplyCommentID(str string) {
|
||||||
|
if len(str) > 0 {
|
||||||
|
c.ReplyCommentID = sql.NullInt64{Int64: converter.StringToInt64(str), Valid: true}
|
||||||
|
} else {
|
||||||
|
c.ReplyCommentID = sql.NullInt64{Valid: false}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
// Config config
|
||||||
|
type Config struct {
|
||||||
|
ID int `xorm:"not null pk autoincr comment('config id') INT(11) id"`
|
||||||
|
Key string `xorm:"comment('the config key') unique VARCHAR(32) key"`
|
||||||
|
Value string `xorm:"comment('the config value, custom data structures and types') VARCHAR(128) value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName config table name
|
||||||
|
func (Config) TableName() string {
|
||||||
|
return "config"
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const (
|
||||||
|
QuestionEditSummaryKey = "question.edit.summary"
|
||||||
|
QuestionCloseReasonKey = "question.close.reason"
|
||||||
|
AnswerEditSummaryKey = "answer.edit.summary"
|
||||||
|
TagEditSummaryKey = "tag.edit.summary"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Meta meta
|
||||||
|
type Meta struct {
|
||||||
|
ID int `xorm:"not null pk autoincr comment('id') INT(10) id"`
|
||||||
|
CreatedAt time.Time `xorm:"not null default CURRENT_TIMESTAMP created comment('created time') TIMESTAMP created_at"`
|
||||||
|
UpdatedAt time.Time `xorm:"not null default CURRENT_TIMESTAMP updated comment('updated time') TIMESTAMP updated_at"`
|
||||||
|
ObjectID string `xorm:"not null default 0 comment('object id') BIGINT(20) object_id"`
|
||||||
|
Key string `xorm:"not null comment('key') VARCHAR(100) key"`
|
||||||
|
Value string `xorm:"not null comment('value') MEDIUMTEXT value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName meta table name
|
||||||
|
func (Meta) TableName() string {
|
||||||
|
return "meta"
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Notification notification
|
||||||
|
type Notification struct {
|
||||||
|
ID string `xorm:"not null pk autoincr comment('notification id') BIGINT(20) id"`
|
||||||
|
UserID string `xorm:"not null default 0 comment('user id') BIGINT(20) user_id"`
|
||||||
|
ObjectID string `xorm:"not null default 0 comment('object id') index BIGINT(20) object_id"`
|
||||||
|
Content string `xorm:"not null comment('notification content') TEXT content"`
|
||||||
|
Type int `xorm:"not null default 0 comment('notification type( 1 inbox 2 achievement)') INT(11) type"`
|
||||||
|
IsRead int `xorm:"not null default 1 comment('read status(unread: 1; read 2)') INT(11) is_read"`
|
||||||
|
Status int `xorm:"not null default 1 comment('notification status(normal: 1; delete 2)') INT(11) status"`
|
||||||
|
CreatedAt time.Time `xorm:"created comment('create time') TIMESTAMP created_at"`
|
||||||
|
UpdatedAt time.Time `xorm:"comment('update time') TIMESTAMP updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName notification table name
|
||||||
|
func (Notification) TableName() string {
|
||||||
|
return "notification"
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// NotificationRead notification read record
|
||||||
|
type NotificationRead struct {
|
||||||
|
ID int `xorm:"not null pk autoincr comment('id') INT(11) id"`
|
||||||
|
CreatedAt time.Time `xorm:"created comment('create time') TIMESTAMP created_at"`
|
||||||
|
UpdatedAt time.Time `xorm:"updated comment('update time') TIMESTAMP updated_at"`
|
||||||
|
UserID int64 `xorm:"not null default 0 comment('user id') BIGINT(20) user_id"`
|
||||||
|
MessageID int64 `xorm:"not null default 0 comment('message id') BIGINT(20) message_id"`
|
||||||
|
IsRead int `xorm:"not null default 1 comment('read status(unread: 1; read 2)') INT(11) is_read"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName notification read record table name
|
||||||
|
func (NotificationRead) TableName() string {
|
||||||
|
return "notification_read"
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
QuestionStatusAvailable = 1
|
||||||
|
QuestionStatusclosed = 2
|
||||||
|
QuestionStatusDeleted = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
var CmsQuestionSearchStatus = map[string]int{
|
||||||
|
"available": QuestionStatusAvailable,
|
||||||
|
"closed": QuestionStatusclosed,
|
||||||
|
"deleted": QuestionStatusDeleted,
|
||||||
|
}
|
||||||
|
|
||||||
|
type QuestionTag struct {
|
||||||
|
Question `xorm:"extends"`
|
||||||
|
TagRel `xorm:"extends"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Question question
|
||||||
|
type Question struct {
|
||||||
|
ID string `xorm:"not null pk comment('question id') BIGINT(20) id"`
|
||||||
|
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"`
|
||||||
|
ParsedText string `xorm:"not null comment('parsed content') MEDIUMTEXT parsed_text"`
|
||||||
|
Status int `xorm:"not null default 1 comment(' question status(available: 1; deleted: 10)') INT(11) status"`
|
||||||
|
ViewCount int `xorm:"not null default 0 comment('view count') INT(11) view_count"`
|
||||||
|
UniqueViewCount int `xorm:"not null default 0 comment('unique view count') INT(11) unique_view_count"`
|
||||||
|
VoteCount int `xorm:"not null default 0 comment('vote count') INT(11) vote_count"`
|
||||||
|
AnswerCount int `xorm:"not null default 0 comment('answer count') INT(11) answer_count"`
|
||||||
|
CollectionCount int `xorm:"not null default 0 comment('collection count') INT(11) collection_count"`
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName question table name
|
||||||
|
func (Question) TableName() string {
|
||||||
|
return "question"
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const (
|
||||||
|
ReportStatusPending = 1
|
||||||
|
ReportStatusCompleted = 2
|
||||||
|
ReportStatusDeleted = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ReportStatus = map[string]int{
|
||||||
|
"pending": ReportStatusPending,
|
||||||
|
"completed": ReportStatusCompleted,
|
||||||
|
"deleted": ReportStatusDeleted,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Report report
|
||||||
|
type Report struct {
|
||||||
|
ID string `xorm:"not null pk autoincr comment('id') BIGINT(20) id"`
|
||||||
|
CreatedAt time.Time `xorm:"created comment('create time') TIMESTAMP created_at"`
|
||||||
|
UpdatedAt time.Time `xorm:"updated comment('update time') TIMESTAMP updated_at"`
|
||||||
|
UserID string `xorm:"not null comment('reporter user id') BIGINT(20) user_id"`
|
||||||
|
ReportedUserID string `xorm:"not null comment('reported user id') BIGINT(20) reported_user_id"`
|
||||||
|
ObjectID string `xorm:"not null comment('object id') BIGINT(20) object_id"`
|
||||||
|
ObjectType int `xorm:"not null default 0 comment('revision type') INT(11) object_type"`
|
||||||
|
ReportType int `xorm:"not null default 0 comment('report type') INT(11) report_type"`
|
||||||
|
Content string `xorm:"not null comment('report content') TEXT content"`
|
||||||
|
FlagedType int `xorm:"not null default 0 comment('flaged type') INT(11) flaged_type"`
|
||||||
|
FlagedContent string `xorm:"not null comment('flaged content') TEXT flaged_content"`
|
||||||
|
Status int `xorm:"not null default 1 comment('status(normal: 1; delete 2)') INT(11) status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName report table name
|
||||||
|
func (Report) TableName() string {
|
||||||
|
return "report"
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Revision revision
|
||||||
|
type Revision struct {
|
||||||
|
ID string `xorm:"not null pk autoincr comment('id') BIGINT(20) id"`
|
||||||
|
CreatedAt time.Time `xorm:"created comment('create time') TIMESTAMP created_at"`
|
||||||
|
UpdatedAt time.Time `xorm:"updated comment('update time') TIMESTAMP updated_at"`
|
||||||
|
UserID string `xorm:"not null default 0 comment('user id') BIGINT(20) user_id"`
|
||||||
|
ObjectType int `xorm:"not null default 0 comment('revision type(question: 1; answer 2; tag 3)') INT(11) object_type"`
|
||||||
|
ObjectID string `xorm:"not null default 0 comment('object id') BIGINT(20) object_id"`
|
||||||
|
Title string `xorm:"not null default '' comment('title') VARCHAR(255) title"`
|
||||||
|
Content string `xorm:"not null comment('content') TEXT content"`
|
||||||
|
Log string `xorm:"comment('log') VARCHAR(255) log"`
|
||||||
|
Status int `xorm:"not null default 1 comment('revision status(normal: 1; delete 2)') INT(11) status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName revision table name
|
||||||
|
func (Revision) TableName() string {
|
||||||
|
return "revision"
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// SiteInfo site information setting
|
||||||
|
type SiteInfo struct {
|
||||||
|
ID string `xorm:"not null pk autoincr comment('id') INT(11) id"`
|
||||||
|
CreatedAt time.Time `xorm:"created comment('create time') TIMESTAMP created_at"`
|
||||||
|
UpdatedAt time.Time `xorm:"updated comment('update time') TIMESTAMP updated_at"`
|
||||||
|
Type string `xorm:"not null comment('content') VARCHAR(64) type"`
|
||||||
|
Content string `xorm:"not null comment('content') MEDIUMTEXT content"`
|
||||||
|
Status int `xorm:"not null default 1 comment('site info status(available: 1; deleted: 10)') INT(11) status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName table name
|
||||||
|
func (*SiteInfo) TableName() string {
|
||||||
|
return "site_info"
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const (
|
||||||
|
TagStatusAvailable = 1
|
||||||
|
TagStatusDeleted = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tag tag
|
||||||
|
type Tag struct {
|
||||||
|
ID string `xorm:"not null comment('tag_id') BIGINT(20) id"`
|
||||||
|
CreatedAt time.Time `xorm:"created comment('create time') TIMESTAMP created_at"`
|
||||||
|
UpdatedAt time.Time `xorm:"updated comment('update time') TIMESTAMP updated_at"`
|
||||||
|
MainTagID int64 `xorm:"not null default 0 comment('main tag id') BIGINT(20) main_tag_id"`
|
||||||
|
MainTagSlugName string `xorm:"default '' comment('main_tag_slug_name') VARCHAR(50) main_tag_slug_name"`
|
||||||
|
SlugName string `xorm:"default '' comment('slug_name') unique VARCHAR(50) slug_name"`
|
||||||
|
DisplayName string `xorm:"not null default '' comment('display_name') VARCHAR(50) display_name"`
|
||||||
|
OriginalText string `xorm:"not null comment('original comment content') MEDIUMTEXT original_text"`
|
||||||
|
ParsedText string `xorm:"not null comment('parsed comment content') MEDIUMTEXT parsed_text"`
|
||||||
|
FollowCount int `xorm:"not null default 0 comment('associated follow count') INT(11) follow_count"`
|
||||||
|
QuestionCount int `xorm:"not null default 0 comment('associated question count') INT(11) question_count"`
|
||||||
|
Status int `xorm:"not null default 1 comment('tag status(available: 1; deleted: 10)') INT(11) status"`
|
||||||
|
RevisionID string `xorm:"not null default 0 BIGINT(20) revision_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName tag table name
|
||||||
|
func (Tag) TableName() string {
|
||||||
|
return "tag"
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const (
|
||||||
|
TagRelStatusAvailable = 1
|
||||||
|
TagRelStatusDeleted = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
// TagRel tag relation
|
||||||
|
type TagRel struct {
|
||||||
|
ID int64 `xorm:"not null pk autoincr comment('tag_list_id') BIGINT(20) id"`
|
||||||
|
CreatedAt time.Time `xorm:"created comment('create time') TIMESTAMP created_at"`
|
||||||
|
UpdatedAt time.Time `xorm:"updated comment('update time') TIMESTAMP updated_at"`
|
||||||
|
ObjectID string `xorm:"not null comment('object_id') index BIGINT(20) object_id"`
|
||||||
|
TagID string `xorm:"not null comment('tag_id') index BIGINT(20) tag_id"`
|
||||||
|
Status int `xorm:"not null default 1 comment('tag_list_status(available: 1; deleted: 10)') INT(11) status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName tag list table name
|
||||||
|
func (TagRel) TableName() string {
|
||||||
|
return "tag_rel"
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
// Uniqid uniqid
|
||||||
|
type Uniqid struct {
|
||||||
|
ID int64 `xorm:"not null pk autoincr comment('uniqid_id') BIGINT(20) id"`
|
||||||
|
UniqidType int `xorm:"not null default 0 comment('uniqid_type') INT(11) uniqid_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName uniqid table name
|
||||||
|
func (Uniqid) TableName() string {
|
||||||
|
return "uniqid"
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const (
|
||||||
|
UserStatusAvailable = 1
|
||||||
|
UserStatusSuspended = 9
|
||||||
|
UserStatusDeleted = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
EmailStatusAvailable = 1
|
||||||
|
EmailStatusToBeVerified = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
UserAdminFlag = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// User user
|
||||||
|
type User struct {
|
||||||
|
ID string `xorm:"not null pk autoincr comment('user id') BIGINT(20) id"`
|
||||||
|
CreatedAt time.Time `xorm:"created comment('create time') TIMESTAMP created_at"`
|
||||||
|
UpdatedAt time.Time `xorm:"updated comment('update time') TIMESTAMP updated_at"`
|
||||||
|
SuspendedAt time.Time `xorm:"comment('suspended time') TIMESTAMP suspended_at"`
|
||||||
|
DeletedAt time.Time `xorm:"comment('delete time') TIMESTAMP deleted_at"`
|
||||||
|
LastLoginDate time.Time `xorm:"comment('last_login_date') TIMESTAMP last_login_date"`
|
||||||
|
Username string `xorm:"not null default '' comment('username') VARCHAR(50) username"`
|
||||||
|
Pass string `xorm:"not null default '' comment('password') VARCHAR(255) pass"`
|
||||||
|
EMail string `xorm:"not null comment('email') VARCHAR(100) e_mail"`
|
||||||
|
MailStatus int `xorm:"not null default 2 comment('mail status(1 pass 2 to be verified)') TINYINT(4) mail_status"`
|
||||||
|
NoticeStatus int `xorm:"not null default 2 comment('notice status(1 on 2off)') INT(11) notice_status"`
|
||||||
|
FollowCount int `xorm:"not null default 0 comment('follow count') INT(11) follow_count"`
|
||||||
|
AnswerCount int `xorm:"not null default 0 comment('answer count') INT(11) answer_count"`
|
||||||
|
QuestionCount int `xorm:"not null default 0 comment('question count') INT(11) question_count"`
|
||||||
|
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"`
|
||||||
|
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"`
|
||||||
|
BioHtml string `xorm:"not null comment('bio html') TEXT bio_html"`
|
||||||
|
Website string `xorm:"not null default '' comment('website') VARCHAR(255) website"`
|
||||||
|
Location string `xorm:"not null default '' comment('location') VARCHAR(100) location"`
|
||||||
|
IPInfo string `xorm:"not null default '' comment('ip info') VARCHAR(255) ip_info"`
|
||||||
|
IsAdmin bool `xorm:"not null default 0 comment('admin flag') INT(11) is_admin"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName user table name
|
||||||
|
func (User) TableName() string {
|
||||||
|
return "user"
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserSearch struct {
|
||||||
|
User
|
||||||
|
Page int `json:"page" form:"page"` //Query number of pages
|
||||||
|
PageSize int `json:"page_size" form:"page_size"` //Search page size
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
// UserGroup user group
|
||||||
|
type UserGroup struct {
|
||||||
|
ID int64 `xorm:"not null pk autoincr comment('user group id') unique BIGINT(20) id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName user group table name
|
||||||
|
func (UserGroup) TableName() string {
|
||||||
|
return "user_group"
|
||||||
|
}
|
|
@ -0,0 +1,336 @@
|
||||||
|
package activity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/segmentfault/answer/internal/base/constant"
|
||||||
|
"github.com/segmentfault/answer/internal/base/data"
|
||||||
|
"github.com/segmentfault/answer/internal/base/reason"
|
||||||
|
"github.com/segmentfault/answer/internal/entity"
|
||||||
|
"github.com/segmentfault/answer/internal/schema"
|
||||||
|
"github.com/segmentfault/answer/internal/service/activity"
|
||||||
|
"github.com/segmentfault/answer/internal/service/activity_common"
|
||||||
|
"github.com/segmentfault/answer/internal/service/notice_queue"
|
||||||
|
"github.com/segmentfault/answer/internal/service/rank"
|
||||||
|
"github.com/segmentfault/pacman/errors"
|
||||||
|
"github.com/segmentfault/pacman/log"
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AnswerActivityRepo answer accepted
|
||||||
|
type AnswerActivityRepo struct {
|
||||||
|
data *data.Data
|
||||||
|
activityRepo activity_common.ActivityRepo
|
||||||
|
userRankRepo rank.UserRankRepo
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
acceptAction = "accept"
|
||||||
|
acceptedAction = "accepted"
|
||||||
|
acceptCancelAction = "accept_cancel"
|
||||||
|
acceptedCancelAction = "accepted_cancel"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
acceptActionList = []string{acceptAction, acceptedAction}
|
||||||
|
acceptCancelActionList = []string{acceptCancelAction, acceptedCancelAction}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewAnswerActivityRepo new repository
|
||||||
|
func NewAnswerActivityRepo(
|
||||||
|
data *data.Data,
|
||||||
|
activityRepo activity_common.ActivityRepo,
|
||||||
|
userRankRepo rank.UserRankRepo,
|
||||||
|
) activity.AnswerActivityRepo {
|
||||||
|
return &AnswerActivityRepo{
|
||||||
|
|
||||||
|
data: data,
|
||||||
|
activityRepo: activityRepo,
|
||||||
|
userRankRepo: userRankRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewQuestionActivityRepo new repository
|
||||||
|
func NewQuestionActivityRepo(
|
||||||
|
data *data.Data,
|
||||||
|
activityRepo activity_common.ActivityRepo,
|
||||||
|
userRankRepo rank.UserRankRepo,
|
||||||
|
) activity.QuestionActivityRepo {
|
||||||
|
return &AnswerActivityRepo{
|
||||||
|
data: data,
|
||||||
|
activityRepo: activityRepo,
|
||||||
|
userRankRepo: userRankRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ar *AnswerActivityRepo) DeleteQuestion(ctx context.Context, questionID string) (err error) {
|
||||||
|
questionInfo := &entity.Question{}
|
||||||
|
exist, err := ar.data.DB.Where("id = ?", questionID).Get(questionInfo)
|
||||||
|
if err != nil {
|
||||||
|
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
|
}
|
||||||
|
if !exist {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// get all this object activity
|
||||||
|
activityList := make([]*entity.Activity, 0)
|
||||||
|
session := ar.data.DB.Where("has_rank = 1")
|
||||||
|
session.Where("cancelled = ?", entity.ActivityAvailable)
|
||||||
|
err = session.Find(&activityList, &entity.Activity{ObjectID: questionID})
|
||||||
|
if err != nil {
|
||||||
|
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
|
}
|
||||||
|
if len(activityList) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("questionInfo %s deleted will rollback activity %d", questionID, len(activityList))
|
||||||
|
|
||||||
|
_, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||||
|
for _, act := range activityList {
|
||||||
|
log.Infof("user %s rollback rank %d", act.UserID, -act.Rank)
|
||||||
|
_, e := ar.userRankRepo.TriggerUserRank(
|
||||||
|
ctx, session, act.UserID, -act.Rank, act.ActivityType)
|
||||||
|
if e != nil {
|
||||||
|
return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, e := session.Where("id = ?", act.ID).Cols("`cancelled`").
|
||||||
|
Update(&entity.Activity{Cancelled: entity.ActivityCancelled}); e != nil {
|
||||||
|
return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// get all answers
|
||||||
|
answerList := make([]*entity.Answer, 0)
|
||||||
|
err = ar.data.DB.Find(&answerList, &entity.Answer{QuestionID: questionID})
|
||||||
|
if err != nil {
|
||||||
|
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
|
}
|
||||||
|
for _, answerInfo := range answerList {
|
||||||
|
err = ar.DeleteAnswer(ctx, answerInfo.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcceptAnswer accept other answer
|
||||||
|
func (ar *AnswerActivityRepo) AcceptAnswer(ctx context.Context,
|
||||||
|
answerObjID, questionUserID, answerUserID string, isSelf bool) (err error) {
|
||||||
|
addActivityList := make([]*entity.Activity, 0)
|
||||||
|
for _, action := range acceptActionList {
|
||||||
|
// get accept answer need add rank amount
|
||||||
|
activityType, deltaRank, hasRank, e := ar.activityRepo.GetActivityTypeByObjID(ctx, answerObjID, action)
|
||||||
|
if e != nil {
|
||||||
|
return errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||||
|
}
|
||||||
|
addActivity := &entity.Activity{
|
||||||
|
ObjectID: answerObjID,
|
||||||
|
ActivityType: activityType,
|
||||||
|
Rank: deltaRank,
|
||||||
|
HasRank: hasRank,
|
||||||
|
}
|
||||||
|
if action == acceptAction {
|
||||||
|
addActivity.UserID = questionUserID
|
||||||
|
} else {
|
||||||
|
addActivity.UserID = answerUserID
|
||||||
|
}
|
||||||
|
if isSelf {
|
||||||
|
addActivity.Rank = 0
|
||||||
|
}
|
||||||
|
addActivityList = append(addActivityList, addActivity)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||||
|
for _, addActivity := range addActivityList {
|
||||||
|
existsActivity, exists, e := ar.activityRepo.GetActivity(
|
||||||
|
ctx, session, answerObjID, addActivity.UserID, addActivity.ActivityType)
|
||||||
|
if e != nil {
|
||||||
|
return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||||
|
}
|
||||||
|
if exists && existsActivity.Cancelled == entity.ActivityAvailable {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
reachStandard, e := ar.userRankRepo.TriggerUserRank(
|
||||||
|
ctx, session, addActivity.UserID, addActivity.Rank, addActivity.ActivityType)
|
||||||
|
if e != nil {
|
||||||
|
return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||||
|
}
|
||||||
|
if reachStandard {
|
||||||
|
addActivity.Rank = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
if _, e := session.Where("id = ?", existsActivity.ID).Cols("`cancelled`").
|
||||||
|
Update(&entity.Activity{Cancelled: entity.ActivityAvailable}); e != nil {
|
||||||
|
return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, e = session.Insert(addActivity); e != nil {
|
||||||
|
return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, act := range addActivityList {
|
||||||
|
msg := &schema.NotificationMsg{
|
||||||
|
Type: schema.NotificationTypeAchievement,
|
||||||
|
ObjectID: act.ObjectID,
|
||||||
|
ReceiverUserID: act.UserID,
|
||||||
|
}
|
||||||
|
if act.UserID == questionUserID {
|
||||||
|
msg.TriggerUserID = answerUserID
|
||||||
|
msg.ObjectType = constant.QuestionObjectType
|
||||||
|
} else {
|
||||||
|
msg.TriggerUserID = questionUserID
|
||||||
|
msg.ObjectType = constant.AnswerObjectType
|
||||||
|
}
|
||||||
|
notice_queue.AddNotification(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, act := range addActivityList {
|
||||||
|
msg := &schema.NotificationMsg{
|
||||||
|
ReceiverUserID: act.UserID,
|
||||||
|
Type: schema.NotificationTypeInbox,
|
||||||
|
ObjectID: act.ObjectID,
|
||||||
|
}
|
||||||
|
if act.UserID != questionUserID {
|
||||||
|
msg.TriggerUserID = questionUserID
|
||||||
|
msg.ObjectType = constant.AnswerObjectType
|
||||||
|
msg.NotificationAction = constant.AdoptAnswer
|
||||||
|
notice_queue.AddNotification(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CancelAcceptAnswer accept other answer
|
||||||
|
func (ar *AnswerActivityRepo) CancelAcceptAnswer(ctx context.Context,
|
||||||
|
answerObjID, questionUserID, answerUserID string) (err error) {
|
||||||
|
addActivityList := make([]*entity.Activity, 0)
|
||||||
|
for _, action := range acceptActionList {
|
||||||
|
// get accept answer need add rank amount
|
||||||
|
activityType, deltaRank, hasRank, e := ar.activityRepo.GetActivityTypeByObjID(ctx, answerObjID, action)
|
||||||
|
if e != nil {
|
||||||
|
return errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||||
|
}
|
||||||
|
addActivity := &entity.Activity{
|
||||||
|
ObjectID: answerObjID,
|
||||||
|
ActivityType: activityType,
|
||||||
|
Rank: -deltaRank,
|
||||||
|
HasRank: hasRank,
|
||||||
|
}
|
||||||
|
if action == acceptAction {
|
||||||
|
addActivity.UserID = questionUserID
|
||||||
|
} else {
|
||||||
|
addActivity.UserID = answerUserID
|
||||||
|
}
|
||||||
|
addActivityList = append(addActivityList, addActivity)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||||
|
for _, addActivity := range addActivityList {
|
||||||
|
existsActivity, exists, e := ar.activityRepo.GetActivity(
|
||||||
|
ctx, session, answerObjID, addActivity.UserID, addActivity.ActivityType)
|
||||||
|
if e != nil {
|
||||||
|
return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||||
|
}
|
||||||
|
if exists && existsActivity.Cancelled == entity.ActivityCancelled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, e = ar.userRankRepo.TriggerUserRank(
|
||||||
|
ctx, session, addActivity.UserID, addActivity.Rank, addActivity.ActivityType)
|
||||||
|
if e != nil {
|
||||||
|
return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, e := session.Where("id = ?", existsActivity.ID).Cols("`cancelled`").
|
||||||
|
Update(&entity.Activity{Cancelled: entity.ActivityCancelled}); e != nil {
|
||||||
|
return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, act := range addActivityList {
|
||||||
|
msg := &schema.NotificationMsg{
|
||||||
|
ReceiverUserID: act.UserID,
|
||||||
|
Type: schema.NotificationTypeAchievement,
|
||||||
|
ObjectID: act.ObjectID,
|
||||||
|
}
|
||||||
|
if act.UserID == questionUserID {
|
||||||
|
msg.TriggerUserID = answerUserID
|
||||||
|
msg.ObjectType = constant.QuestionObjectType
|
||||||
|
} else {
|
||||||
|
msg.TriggerUserID = questionUserID
|
||||||
|
msg.ObjectType = constant.AnswerObjectType
|
||||||
|
}
|
||||||
|
notice_queue.AddNotification(msg)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ar *AnswerActivityRepo) DeleteAnswer(ctx context.Context, answerID string) (err error) {
|
||||||
|
answerInfo := &entity.Answer{}
|
||||||
|
exist, err := ar.data.DB.Where("id = ?", answerID).Get(answerInfo)
|
||||||
|
if err != nil {
|
||||||
|
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
|
}
|
||||||
|
if !exist {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// get all this object activity
|
||||||
|
activityList := make([]*entity.Activity, 0)
|
||||||
|
session := ar.data.DB.Where("has_rank = 1")
|
||||||
|
session.Where("cancelled = ?", entity.ActivityAvailable)
|
||||||
|
err = session.Find(&activityList, &entity.Activity{ObjectID: answerID})
|
||||||
|
if err != nil {
|
||||||
|
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
|
}
|
||||||
|
if len(activityList) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("answerInfo %s deleted will rollback activity %d", answerID, len(activityList))
|
||||||
|
|
||||||
|
_, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||||
|
for _, act := range activityList {
|
||||||
|
log.Infof("user %s rollback rank %d", act.UserID, -act.Rank)
|
||||||
|
_, e := ar.userRankRepo.TriggerUserRank(
|
||||||
|
ctx, session, act.UserID, -act.Rank, act.ActivityType)
|
||||||
|
if e != nil {
|
||||||
|
return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, e := session.Where("id = ?", act.ID).Cols("`cancelled`").
|
||||||
|
Update(&entity.Activity{Cancelled: entity.ActivityCancelled}); e != nil {
|
||||||
|
return nil, errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
package activity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/segmentfault/answer/internal/service/activity_common"
|
||||||
|
"github.com/segmentfault/answer/internal/service/follow"
|
||||||
|
"github.com/segmentfault/answer/pkg/obj"
|
||||||
|
"github.com/segmentfault/pacman/log"
|
||||||
|
"xorm.io/builder"
|
||||||
|
|
||||||
|
"github.com/segmentfault/answer/internal/base/data"
|
||||||
|
"github.com/segmentfault/answer/internal/base/reason"
|
||||||
|
"github.com/segmentfault/answer/internal/entity"
|
||||||
|
"github.com/segmentfault/answer/internal/service/unique"
|
||||||
|
"github.com/segmentfault/pacman/errors"
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FollowRepo activity repository
|
||||||
|
type FollowRepo struct {
|
||||||
|
data *data.Data
|
||||||
|
uniqueIDRepo unique.UniqueIDRepo
|
||||||
|
activityRepo activity_common.ActivityRepo
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFollowRepo new repository
|
||||||
|
func NewFollowRepo(
|
||||||
|
data *data.Data,
|
||||||
|
uniqueIDRepo unique.UniqueIDRepo,
|
||||||
|
activityRepo activity_common.ActivityRepo,
|
||||||
|
) follow.FollowRepo {
|
||||||
|
return &FollowRepo{
|
||||||
|
data: data,
|
||||||
|
uniqueIDRepo: uniqueIDRepo,
|
||||||
|
activityRepo: activityRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ar *FollowRepo) Follow(ctx context.Context, objectId, userId string) error {
|
||||||
|
activityType, _, _, err := ar.activityRepo.GetActivityTypeByObjID(nil, objectId, "follow")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||||
|
var (
|
||||||
|
existsActivity entity.Activity
|
||||||
|
has bool
|
||||||
|
)
|
||||||
|
result = nil
|
||||||
|
|
||||||
|
has, err = session.Where(builder.Eq{"activity_type": activityType}).
|
||||||
|
And(builder.Eq{"user_id": userId}).
|
||||||
|
And(builder.Eq{"object_id": objectId}).
|
||||||
|
Get(&existsActivity)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if has && existsActivity.Cancelled == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if has {
|
||||||
|
_, err = session.Where(builder.Eq{"id": existsActivity.ID}).
|
||||||
|
Cols(`cancelled`).
|
||||||
|
Update(&entity.Activity{
|
||||||
|
Cancelled: 0,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// update existing activity with new user id and u object id
|
||||||
|
_, err = session.Insert(&entity.Activity{
|
||||||
|
UserID: userId,
|
||||||
|
ObjectID: objectId,
|
||||||
|
ActivityType: activityType,
|
||||||
|
Cancelled: 0,
|
||||||
|
Rank: 0,
|
||||||
|
HasRank: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// start update followers when everything is fine
|
||||||
|
err = ar.updateFollows(ctx, session, objectId, 1)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ar *FollowRepo) FollowCancel(ctx context.Context, objectId, userId string) error {
|
||||||
|
activityType, _, _, err := ar.activityRepo.GetActivityTypeByObjID(nil, objectId, "follow")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = ar.data.DB.Transaction(func(session *xorm.Session) (result any, err error) {
|
||||||
|
var (
|
||||||
|
existsActivity entity.Activity
|
||||||
|
has bool
|
||||||
|
)
|
||||||
|
result = nil
|
||||||
|
|
||||||
|
has, err = session.Where(builder.Eq{"activity_type": activityType}).
|
||||||
|
And(builder.Eq{"user_id": userId}).
|
||||||
|
And(builder.Eq{"object_id": objectId}).
|
||||||
|
Get(&existsActivity)
|
||||||
|
|
||||||
|
if err != nil || !has {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if has && existsActivity.Cancelled == 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = session.Where("id = ?", existsActivity.ID).
|
||||||
|
Cols("cancelled").
|
||||||
|
Update(&entity.Activity{
|
||||||
|
Cancelled: 1,
|
||||||
|
}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = ar.updateFollows(ctx, session, objectId, -1)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ar *FollowRepo) updateFollows(ctx context.Context, session *xorm.Session, objectId string, follows int) error {
|
||||||
|
objectType, err := obj.GetObjectTypeStrByObjectID(objectId)
|
||||||
|
switch objectType {
|
||||||
|
case "question":
|
||||||
|
_, err = session.Where("id = ?", objectId).Incr("follow_count", follows).Update(&entity.Question{})
|
||||||
|
case "user":
|
||||||
|
_, err = session.Where("id = ?", objectId).Incr("follow_count", follows).Update(&entity.User{})
|
||||||
|
case "tag":
|
||||||
|
_, err = session.Where("id = ?", objectId).Incr("follow_count", follows).Update(&entity.Tag{})
|
||||||
|
default:
|
||||||
|
err = errors.InternalServer(reason.DisallowFollow).WithMsg("this object can't be followed")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue