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