Merge branch 'feat/1.2.5/notification' into test
# Conflicts: # i18n/en_US.yaml # i18n/zh_CN.yaml
This commit is contained in:
+6
-2
@@ -47,7 +47,9 @@ RUN mkdir -p /data/uploads && chmod 777 /data/uploads \
|
||||
FROM alpine
|
||||
LABEL maintainer="linkinstar@apache.org"
|
||||
|
||||
ENV TZ "Asia/Shanghai"
|
||||
ARG TIMEZONE
|
||||
ENV TIMEZONE=${TIMEZONE:-"Asia/Shanghai"}
|
||||
|
||||
RUN apk update \
|
||||
&& apk --no-cache add \
|
||||
bash \
|
||||
@@ -58,7 +60,9 @@ RUN apk update \
|
||||
openssh \
|
||||
sqlite \
|
||||
gnupg \
|
||||
&& echo "Asia/Shanghai" > /etc/timezone
|
||||
tzdata \
|
||||
&& ln -sf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime \
|
||||
&& echo "${TIMEZONE}" > /etc/timezone
|
||||
|
||||
COPY --from=golang-builder /usr/bin/answer /usr/bin/answer
|
||||
COPY --from=golang-builder /data /data
|
||||
|
||||
+7
-5
@@ -201,8 +201,8 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
||||
collectionController := controller.NewCollectionController(collectionService)
|
||||
answerActivityRepo := activity.NewAnswerActivityRepo(dataData, activityRepo, userRankRepo, notificationQueueService)
|
||||
answerActivityService := activity2.NewAnswerActivityService(answerActivityRepo, configService)
|
||||
externalNotificationService := notification.NewExternalNotificationService(dataData, userNotificationConfigRepo, followRepo, emailService, userRepo, externalNotificationQueueService)
|
||||
questionService := service.NewQuestionService(questionRepo, tagCommonService, questionCommon, userCommon, userRepo, revisionService, metaService, collectionCommon, answerActivityService, emailService, notificationQueueService, externalNotificationQueueService, activityQueueService, siteInfoCommonService, externalNotificationService)
|
||||
externalNotificationService := notification.NewExternalNotificationService(dataData, userNotificationConfigRepo, followRepo, emailService, userRepo, externalNotificationQueueService, userExternalLoginRepo, siteInfoCommonService)
|
||||
questionService := service.NewQuestionService(questionRepo, tagCommonService, questionCommon, userCommon, userRepo, userRoleRelService, revisionService, metaService, collectionCommon, answerActivityService, emailService, notificationQueueService, externalNotificationQueueService, activityQueueService, siteInfoCommonService, externalNotificationService)
|
||||
answerService := service.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo, emailService, userRoleRelService, notificationQueueService, externalNotificationQueueService, activityQueueService)
|
||||
questionController := controller.NewQuestionController(questionService, answerService, rankService, siteInfoCommonService, captchaService, rateLimitMiddleware)
|
||||
answerController := controller.NewAnswerController(answerService, rankService, captchaService, siteInfoCommonService, rateLimitMiddleware)
|
||||
@@ -227,7 +227,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
||||
siteInfoController := controller_admin.NewSiteInfoController(siteInfoService)
|
||||
controllerSiteInfoController := controller.NewSiteInfoController(siteInfoCommonService)
|
||||
notificationRepo := notification2.NewNotificationRepo(dataData)
|
||||
notificationCommon := notificationcommon.NewNotificationCommon(dataData, notificationRepo, userCommon, activityRepo, followRepo, objService, notificationQueueService)
|
||||
notificationCommon := notificationcommon.NewNotificationCommon(dataData, notificationRepo, userCommon, activityRepo, followRepo, objService, notificationQueueService, userExternalLoginRepo, siteInfoCommonService)
|
||||
notificationService := notification.NewNotificationService(dataData, notificationRepo, notificationCommon, revisionService, userRepo)
|
||||
notificationController := controller.NewNotificationController(notificationService, rankService)
|
||||
dashboardService := dashboard.NewDashboardService(questionRepo, answerRepo, commentCommonRepo, voteRepo, userRepo, reportRepo, configService, siteInfoCommonService, serviceConf, dataData)
|
||||
@@ -241,10 +241,12 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
||||
activityController := controller.NewActivityController(activityService)
|
||||
roleController := controller_admin.NewRoleController(roleService)
|
||||
pluginConfigRepo := plugin_config.NewPluginConfigRepo(dataData)
|
||||
pluginCommonService := plugin_common.NewPluginCommonService(pluginConfigRepo, configService, dataData)
|
||||
pluginUserConfigRepo := plugin_config.NewPluginUserConfigRepo(dataData)
|
||||
pluginCommonService := plugin_common.NewPluginCommonService(pluginConfigRepo, pluginUserConfigRepo, configService, dataData)
|
||||
pluginController := controller_admin.NewPluginController(pluginCommonService)
|
||||
permissionController := controller.NewPermissionController(rankService)
|
||||
answerAPIRouter := router.NewAnswerAPIRouter(langController, userController, commentController, reportController, voteController, tagController, followController, collectionController, questionController, answerController, searchController, revisionController, rankController, controller_adminReportController, userAdminController, reasonController, themeController, siteInfoController, controllerSiteInfoController, notificationController, dashboardController, uploadController, activityController, roleController, pluginController, permissionController)
|
||||
userPluginController := controller.NewUserPluginController(pluginCommonService)
|
||||
answerAPIRouter := router.NewAnswerAPIRouter(langController, userController, commentController, reportController, voteController, tagController, followController, collectionController, questionController, answerController, searchController, revisionController, rankController, controller_adminReportController, userAdminController, reasonController, themeController, siteInfoController, controllerSiteInfoController, notificationController, dashboardController, uploadController, activityController, roleController, pluginController, permissionController, userPluginController)
|
||||
swaggerRouter := router.NewSwaggerRouter(swaggerConf)
|
||||
uiRouter := router.NewUIRouter(controllerSiteInfoController, siteInfoCommonService)
|
||||
authUserMiddleware := middleware.NewAuthUserMiddleware(authService, siteInfoCommonService)
|
||||
|
||||
+159
-24
@@ -5876,6 +5876,132 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/user/plugin/config": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "get user plugin config",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"UserPlugin"
|
||||
],
|
||||
"summary": "get user plugin config",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "plugin_slug_name",
|
||||
"name": "plugin_slug_name",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/schema.GetPluginConfigResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "update user plugin config",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"UserPlugin"
|
||||
],
|
||||
"summary": "update user plugin config",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "UpdatePluginConfigReq",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/schema.UpdateUserPluginConfigReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/user/plugin/configs": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "get plugin list that used for user.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"UserPlugin"
|
||||
],
|
||||
"summary": "get plugin list that used for user.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/schema.GetUserPluginListResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/user/ranking": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -7803,22 +7929,13 @@ const docTemplate = `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"all_new_question": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/schema.NotificationChannelConfig"
|
||||
}
|
||||
"$ref": "#/definitions/schema.NotificationChannelConfig"
|
||||
},
|
||||
"all_new_question_for_following_tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/schema.NotificationChannelConfig"
|
||||
}
|
||||
"$ref": "#/definitions/schema.NotificationChannelConfig"
|
||||
},
|
||||
"inbox": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/schema.NotificationChannelConfig"
|
||||
}
|
||||
"$ref": "#/definitions/schema.NotificationChannelConfig"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -7875,6 +7992,17 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.GetUserPluginListResp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"slug_name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.GetVoteWithPageResp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -9409,22 +9537,13 @@ const docTemplate = `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"all_new_question": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/schema.NotificationChannelConfig"
|
||||
}
|
||||
"$ref": "#/definitions/schema.NotificationChannelConfig"
|
||||
},
|
||||
"all_new_question_for_following_tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/schema.NotificationChannelConfig"
|
||||
}
|
||||
"$ref": "#/definitions/schema.NotificationChannelConfig"
|
||||
},
|
||||
"inbox": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/schema.NotificationChannelConfig"
|
||||
}
|
||||
"$ref": "#/definitions/schema.NotificationChannelConfig"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -9445,6 +9564,22 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.UpdateUserPluginConfigReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"plugin_slug_name"
|
||||
],
|
||||
"properties": {
|
||||
"config_fields": {
|
||||
"type": "object",
|
||||
"additionalProperties": {}
|
||||
},
|
||||
"plugin_slug_name": {
|
||||
"type": "string",
|
||||
"maxLength": 100
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.UpdateUserRoleReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
+159
-24
@@ -5864,6 +5864,132 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/user/plugin/config": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "get user plugin config",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"UserPlugin"
|
||||
],
|
||||
"summary": "get user plugin config",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "plugin_slug_name",
|
||||
"name": "plugin_slug_name",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/schema.GetPluginConfigResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "update user plugin config",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"UserPlugin"
|
||||
],
|
||||
"summary": "update user plugin config",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "UpdatePluginConfigReq",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/schema.UpdateUserPluginConfigReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/user/plugin/configs": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "get plugin list that used for user.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"UserPlugin"
|
||||
],
|
||||
"summary": "get plugin list that used for user.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/schema.GetUserPluginListResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/user/ranking": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -7791,22 +7917,13 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"all_new_question": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/schema.NotificationChannelConfig"
|
||||
}
|
||||
"$ref": "#/definitions/schema.NotificationChannelConfig"
|
||||
},
|
||||
"all_new_question_for_following_tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/schema.NotificationChannelConfig"
|
||||
}
|
||||
"$ref": "#/definitions/schema.NotificationChannelConfig"
|
||||
},
|
||||
"inbox": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/schema.NotificationChannelConfig"
|
||||
}
|
||||
"$ref": "#/definitions/schema.NotificationChannelConfig"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -7863,6 +7980,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.GetUserPluginListResp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"slug_name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.GetVoteWithPageResp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -9397,22 +9525,13 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"all_new_question": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/schema.NotificationChannelConfig"
|
||||
}
|
||||
"$ref": "#/definitions/schema.NotificationChannelConfig"
|
||||
},
|
||||
"all_new_question_for_following_tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/schema.NotificationChannelConfig"
|
||||
}
|
||||
"$ref": "#/definitions/schema.NotificationChannelConfig"
|
||||
},
|
||||
"inbox": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/schema.NotificationChannelConfig"
|
||||
}
|
||||
"$ref": "#/definitions/schema.NotificationChannelConfig"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -9433,6 +9552,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.UpdateUserPluginConfigReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"plugin_slug_name"
|
||||
],
|
||||
"properties": {
|
||||
"config_fields": {
|
||||
"type": "object",
|
||||
"additionalProperties": {}
|
||||
},
|
||||
"plugin_slug_name": {
|
||||
"type": "string",
|
||||
"maxLength": 100
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.UpdateUserRoleReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
+97
-18
@@ -991,17 +991,11 @@ definitions:
|
||||
schema.GetUserNotificationConfigResp:
|
||||
properties:
|
||||
all_new_question:
|
||||
items:
|
||||
$ref: '#/definitions/schema.NotificationChannelConfig'
|
||||
type: array
|
||||
$ref: '#/definitions/schema.NotificationChannelConfig'
|
||||
all_new_question_for_following_tags:
|
||||
items:
|
||||
$ref: '#/definitions/schema.NotificationChannelConfig'
|
||||
type: array
|
||||
$ref: '#/definitions/schema.NotificationChannelConfig'
|
||||
inbox:
|
||||
items:
|
||||
$ref: '#/definitions/schema.NotificationChannelConfig'
|
||||
type: array
|
||||
$ref: '#/definitions/schema.NotificationChannelConfig'
|
||||
type: object
|
||||
schema.GetUserPageResp:
|
||||
properties:
|
||||
@@ -1042,6 +1036,13 @@ definitions:
|
||||
description: username
|
||||
type: string
|
||||
type: object
|
||||
schema.GetUserPluginListResp:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
slug_name:
|
||||
type: string
|
||||
type: object
|
||||
schema.GetVoteWithPageResp:
|
||||
properties:
|
||||
answer_id:
|
||||
@@ -2099,17 +2100,11 @@ definitions:
|
||||
schema.UpdateUserNotificationConfigReq:
|
||||
properties:
|
||||
all_new_question:
|
||||
items:
|
||||
$ref: '#/definitions/schema.NotificationChannelConfig'
|
||||
type: array
|
||||
$ref: '#/definitions/schema.NotificationChannelConfig'
|
||||
all_new_question_for_following_tags:
|
||||
items:
|
||||
$ref: '#/definitions/schema.NotificationChannelConfig'
|
||||
type: array
|
||||
$ref: '#/definitions/schema.NotificationChannelConfig'
|
||||
inbox:
|
||||
items:
|
||||
$ref: '#/definitions/schema.NotificationChannelConfig'
|
||||
type: array
|
||||
$ref: '#/definitions/schema.NotificationChannelConfig'
|
||||
type: object
|
||||
schema.UpdateUserPasswordReq:
|
||||
properties:
|
||||
@@ -2123,6 +2118,17 @@ definitions:
|
||||
- password
|
||||
- user_id
|
||||
type: object
|
||||
schema.UpdateUserPluginConfigReq:
|
||||
properties:
|
||||
config_fields:
|
||||
additionalProperties: {}
|
||||
type: object
|
||||
plugin_slug_name:
|
||||
maxLength: 100
|
||||
type: string
|
||||
required:
|
||||
- plugin_slug_name
|
||||
type: object
|
||||
schema.UpdateUserRoleReq:
|
||||
properties:
|
||||
role_id:
|
||||
@@ -5993,6 +5999,79 @@ paths:
|
||||
summary: RetrievePassWord
|
||||
tags:
|
||||
- User
|
||||
/answer/api/v1/user/plugin/config:
|
||||
get:
|
||||
description: get user plugin config
|
||||
parameters:
|
||||
- description: plugin_slug_name
|
||||
in: query
|
||||
name: plugin_slug_name
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/handler.RespBody'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/schema.GetPluginConfigResp'
|
||||
type: object
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: get user plugin config
|
||||
tags:
|
||||
- UserPlugin
|
||||
put:
|
||||
consumes:
|
||||
- application/json
|
||||
description: update user plugin config
|
||||
parameters:
|
||||
- description: UpdatePluginConfigReq
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/schema.UpdateUserPluginConfigReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handler.RespBody'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: update user plugin config
|
||||
tags:
|
||||
- UserPlugin
|
||||
/answer/api/v1/user/plugin/configs:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: get plugin list that used for user.
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/handler.RespBody'
|
||||
- properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/definitions/schema.GetUserPluginListResp'
|
||||
type: array
|
||||
type: object
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: get plugin list that used for user.
|
||||
tags:
|
||||
- UserPlugin
|
||||
/answer/api/v1/user/ranking:
|
||||
get:
|
||||
consumes:
|
||||
|
||||
@@ -46,4 +46,5 @@ var ProviderSetController = wire.NewSet(
|
||||
NewConnectorController,
|
||||
NewUserCenterController,
|
||||
NewPermissionController,
|
||||
NewUserPluginController,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/apache/incubator-answer/internal/base/middleware"
|
||||
"github.com/apache/incubator-answer/internal/base/reason"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/apache/incubator-answer/internal/base/handler"
|
||||
"github.com/apache/incubator-answer/internal/schema"
|
||||
"github.com/apache/incubator-answer/internal/service/plugin_common"
|
||||
"github.com/apache/incubator-answer/plugin"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// UserPluginController role controller
|
||||
type UserPluginController struct {
|
||||
pluginCommonService *plugin_common.PluginCommonService
|
||||
}
|
||||
|
||||
// NewUserPluginController new controller
|
||||
func NewUserPluginController(pluginCommonService *plugin_common.PluginCommonService) *UserPluginController {
|
||||
return &UserPluginController{pluginCommonService: pluginCommonService}
|
||||
}
|
||||
|
||||
// GetUserPluginList get plugin list that used for user.
|
||||
// @Summary get plugin list that used for user.
|
||||
// @Description get plugin list that used for user.
|
||||
// @Tags UserPlugin
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} handler.RespBody{data=[]schema.GetUserPluginListResp}
|
||||
// @Router /answer/api/v1/user/plugin/configs [get]
|
||||
func (pc *UserPluginController) GetUserPluginList(ctx *gin.Context) {
|
||||
resp := make([]*schema.GetUserPluginListResp, 0)
|
||||
_ = plugin.CallUserConfig(func(base plugin.UserConfig) error {
|
||||
info := base.Info()
|
||||
if plugin.StatusManager.IsEnabled(info.SlugName) {
|
||||
resp = append(resp, &schema.GetUserPluginListResp{
|
||||
Name: info.Name.Translate(ctx),
|
||||
SlugName: info.SlugName,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
handler.HandleResponse(ctx, nil, resp)
|
||||
}
|
||||
|
||||
// GetUserPluginConfig get user plugin config
|
||||
// @Summary get user plugin config
|
||||
// @Description get user plugin config
|
||||
// @Tags UserPlugin
|
||||
// @Security ApiKeyAuth
|
||||
// @Produce json
|
||||
// @Param plugin_slug_name query string true "plugin_slug_name"
|
||||
// @Success 200 {object} handler.RespBody{data=schema.GetPluginConfigResp}
|
||||
// @Router /answer/api/v1/user/plugin/config [get]
|
||||
func (pc *UserPluginController) GetUserPluginConfig(ctx *gin.Context) {
|
||||
req := &schema.GetUserPluginConfigReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
|
||||
resp := &schema.GetUserPluginConfigResp{}
|
||||
_ = plugin.CallUserConfig(func(fn plugin.UserConfig) error {
|
||||
if fn.Info().SlugName != req.PluginSlugName {
|
||||
return nil
|
||||
}
|
||||
info := fn.Info()
|
||||
resp.Name = info.Name.Translate(ctx)
|
||||
resp.SlugName = info.SlugName
|
||||
resp.SetConfigFields(ctx, fn.UserConfigFields())
|
||||
return nil
|
||||
})
|
||||
|
||||
configValue, err := pc.pluginCommonService.GetUserPluginConfig(ctx, req)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
}
|
||||
if len(configValue) > 0 {
|
||||
configValueMapping := make(map[string]any)
|
||||
_ = json.Unmarshal([]byte(configValue), &configValueMapping)
|
||||
for _, field := range resp.ConfigFields {
|
||||
if value, ok := configValueMapping[field.Name]; ok {
|
||||
field.Value = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
||||
// UpdatePluginUserConfig update user plugin config
|
||||
// @Summary update user plugin config
|
||||
// @Description update user plugin config
|
||||
// @Tags UserPlugin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param data body schema.UpdateUserPluginConfigReq true "UpdatePluginConfigReq"
|
||||
// @Success 200 {object} handler.RespBody
|
||||
// @Router /answer/api/v1/user/plugin/config [put]
|
||||
func (pc *UserPluginController) UpdatePluginUserConfig(ctx *gin.Context) {
|
||||
req := &schema.UpdateUserPluginConfigReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
if !plugin.StatusManager.IsEnabled(req.PluginSlugName) {
|
||||
handler.HandleResponse(ctx, errors.New(http.StatusBadRequest, reason.RequestFormatError), nil)
|
||||
return
|
||||
}
|
||||
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
|
||||
configFields, _ := json.Marshal(req.ConfigFields)
|
||||
err := plugin.CallUserConfig(func(fn plugin.UserConfig) error {
|
||||
if fn.Info().SlugName == req.PluginSlugName {
|
||||
return fn.UserConfigReceiver(req.UserID, configFields)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
}
|
||||
|
||||
err = pc.pluginCommonService.UpdatePluginUserConfig(ctx, req)
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
@@ -31,12 +31,12 @@ import (
|
||||
|
||||
// PluginController role controller
|
||||
type PluginController struct {
|
||||
PluginCommonService *plugin_common.PluginCommonService
|
||||
pluginCommonService *plugin_common.PluginCommonService
|
||||
}
|
||||
|
||||
// NewPluginController new controller
|
||||
func NewPluginController(PluginCommonService *plugin_common.PluginCommonService) *PluginController {
|
||||
return &PluginController{PluginCommonService: PluginCommonService}
|
||||
func NewPluginController(pluginCommonService *plugin_common.PluginCommonService) *PluginController {
|
||||
return &PluginController{pluginCommonService: pluginCommonService}
|
||||
}
|
||||
|
||||
// GetAllPluginStatus get all plugins status
|
||||
@@ -150,7 +150,7 @@ func (pc *PluginController) UpdatePluginStatus(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
plugin.StatusManager.Enable(req.PluginSlugName, req.Enabled)
|
||||
err := pc.PluginCommonService.UpdatePluginStatus(ctx)
|
||||
err := pc.pluginCommonService.UpdatePluginStatus(ctx)
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
|
||||
@@ -220,6 +220,6 @@ func (pc *PluginController) UpdatePluginConfig(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
err = pc.PluginCommonService.UpdatePluginConfig(ctx, req)
|
||||
err = pc.pluginCommonService.UpdatePluginConfig(ctx, req)
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package entity
|
||||
|
||||
// PluginUserConfig plugin config
|
||||
type PluginUserConfig struct {
|
||||
ID int `xorm:"not null pk autoincr INT(11) id"`
|
||||
UserID string `xorm:"not null default 0 BIGINT(20) UNIQUE(uk_up) user_id"`
|
||||
PluginSlugName string `xorm:"VARCHAR(128) UNIQUE(uk_up) plugin_slug_name"`
|
||||
Value string `xorm:"TEXT value"`
|
||||
}
|
||||
|
||||
// TableName config table name
|
||||
func (PluginUserConfig) TableName() string {
|
||||
return "plugin_user_config"
|
||||
}
|
||||
@@ -67,6 +67,7 @@ var (
|
||||
&entity.PluginConfig{},
|
||||
&entity.UserExternalLogin{},
|
||||
&entity.UserNotificationConfig{},
|
||||
&entity.PluginUserConfig{},
|
||||
}
|
||||
|
||||
roles = []*entity.Role{
|
||||
|
||||
@@ -94,6 +94,7 @@ var migrations = []Migration{
|
||||
NewMigration("v1.1.3", "set default user notification config", setDefaultUserNotificationConfig, false),
|
||||
NewMigration("v1.2.0", "add recover answer permission", addRecoverPermission, true),
|
||||
NewMigration("v1.2.1", "add password login control", addPasswordLoginControl, true),
|
||||
NewMigration("v1.2.5", "add notification plugin", addNotificationPlugin, true),
|
||||
}
|
||||
|
||||
func GetMigrations() []Migration {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/apache/incubator-answer/internal/entity"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func addNotificationPlugin(ctx context.Context, x *xorm.Engine) error {
|
||||
return x.Context(ctx).Sync(new(entity.PluginUserConfig))
|
||||
}
|
||||
@@ -50,7 +50,7 @@ func (ur *pluginConfigRepo) SavePluginConfig(ctx context.Context, pluginSlugName
|
||||
old.Value = configValue
|
||||
_, err = ur.data.DB.Context(ctx).ID(old.ID).Update(old)
|
||||
} else {
|
||||
_, err = ur.data.DB.Context(ctx).InsertOne(&entity.PluginConfig{PluginSlugName: pluginSlugName, Value: configValue})
|
||||
_, err = ur.data.DB.Context(ctx).Insert(&entity.PluginConfig{PluginSlugName: pluginSlugName, Value: configValue})
|
||||
}
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package plugin_config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"xorm.io/xorm"
|
||||
|
||||
"github.com/apache/incubator-answer/internal/base/data"
|
||||
"github.com/apache/incubator-answer/internal/base/reason"
|
||||
"github.com/apache/incubator-answer/internal/entity"
|
||||
"github.com/apache/incubator-answer/internal/service/plugin_common"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
)
|
||||
|
||||
type pluginUserConfigRepo struct {
|
||||
data *data.Data
|
||||
}
|
||||
|
||||
// NewPluginUserConfigRepo new repository
|
||||
func NewPluginUserConfigRepo(data *data.Data) plugin_common.PluginUserConfigRepo {
|
||||
return &pluginUserConfigRepo{
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
func (ur *pluginUserConfigRepo) SaveUserPluginConfig(ctx context.Context, userID string,
|
||||
pluginSlugName, configValue string) (err error) {
|
||||
_, err = ur.data.DB.Transaction(func(session *xorm.Session) (interface{}, error) {
|
||||
session = session.Context(ctx)
|
||||
old := &entity.PluginUserConfig{
|
||||
UserID: userID,
|
||||
PluginSlugName: pluginSlugName,
|
||||
}
|
||||
exist, err := session.Get(old)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exist {
|
||||
old.Value = configValue
|
||||
_, err = session.ID(old.ID).Update(old)
|
||||
} else {
|
||||
_, err = session.Insert(&entity.PluginUserConfig{
|
||||
UserID: userID,
|
||||
PluginSlugName: pluginSlugName,
|
||||
Value: configValue,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ur *pluginUserConfigRepo) GetPluginUserConfig(ctx context.Context, userID, pluginSlugName string) (
|
||||
pluginUserConfig *entity.PluginUserConfig, exist bool, err error) {
|
||||
pluginUserConfig = &entity.PluginUserConfig{
|
||||
UserID: userID,
|
||||
PluginSlugName: pluginSlugName,
|
||||
}
|
||||
exist, err = ur.data.DB.Context(ctx).Get(pluginUserConfig)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return pluginUserConfig, exist, err
|
||||
}
|
||||
@@ -96,4 +96,5 @@ var ProviderSetRepo = wire.NewSet(
|
||||
plugin_config.NewPluginConfigRepo,
|
||||
user_notification_config.NewUserNotificationConfigRepo,
|
||||
limit.NewRateLimitRepo,
|
||||
plugin_config.NewPluginUserConfigRepo,
|
||||
)
|
||||
|
||||
@@ -23,18 +23,13 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/apache/incubator-answer/plugin"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
"xorm.io/xorm"
|
||||
|
||||
"github.com/apache/incubator-answer/internal/base/handler"
|
||||
"xorm.io/builder"
|
||||
|
||||
"github.com/apache/incubator-answer/internal/base/constant"
|
||||
"github.com/apache/incubator-answer/internal/base/data"
|
||||
"github.com/apache/incubator-answer/internal/base/handler"
|
||||
"github.com/apache/incubator-answer/internal/base/pager"
|
||||
"github.com/apache/incubator-answer/internal/base/reason"
|
||||
"github.com/apache/incubator-answer/internal/entity"
|
||||
@@ -43,8 +38,11 @@ import (
|
||||
"github.com/apache/incubator-answer/internal/service/unique"
|
||||
"github.com/apache/incubator-answer/pkg/htmltext"
|
||||
"github.com/apache/incubator-answer/pkg/uid"
|
||||
|
||||
"github.com/apache/incubator-answer/plugin"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// questionRepo question repository
|
||||
@@ -77,7 +75,6 @@ func (qr *questionRepo) AddQuestion(ctx context.Context, question *entity.Questi
|
||||
if handler.GetEnableShortID(ctx) {
|
||||
question.ID = uid.EnShortID(question.ID)
|
||||
}
|
||||
_ = qr.updateSearch(ctx, question.ID)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -101,7 +98,7 @@ func (qr *questionRepo) UpdateQuestion(ctx context.Context, question *entity.Que
|
||||
if handler.GetEnableShortID(ctx) {
|
||||
question.ID = uid.EnShortID(question.ID)
|
||||
}
|
||||
_ = qr.updateSearch(ctx, question.ID)
|
||||
_ = qr.UpdateSearch(ctx, question.ID)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -112,7 +109,7 @@ func (qr *questionRepo) UpdatePvCount(ctx context.Context, questionID string) (e
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
_ = qr.updateSearch(ctx, question.ID)
|
||||
_ = qr.UpdateSearch(ctx, question.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -124,7 +121,7 @@ func (qr *questionRepo) UpdateAnswerCount(ctx context.Context, questionID string
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
_ = qr.updateSearch(ctx, question.ID)
|
||||
_ = qr.UpdateSearch(ctx, question.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -156,7 +153,7 @@ func (qr *questionRepo) UpdateQuestionStatus(ctx context.Context, questionID str
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
_ = qr.updateSearch(ctx, questionID)
|
||||
_ = qr.UpdateSearch(ctx, questionID)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -166,7 +163,7 @@ func (qr *questionRepo) UpdateQuestionStatusWithOutUpdateTime(ctx context.Contex
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
_ = qr.updateSearch(ctx, question.ID)
|
||||
_ = qr.UpdateSearch(ctx, question.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -176,7 +173,7 @@ func (qr *questionRepo) RecoverQuestion(ctx context.Context, questionID string)
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
_ = qr.updateSearch(ctx, questionID)
|
||||
_ = qr.UpdateSearch(ctx, questionID)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -195,7 +192,7 @@ func (qr *questionRepo) UpdateAccepted(ctx context.Context, question *entity.Que
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
_ = qr.updateSearch(ctx, question.ID)
|
||||
_ = qr.UpdateSearch(ctx, question.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -205,7 +202,7 @@ func (qr *questionRepo) UpdateLastAnswer(ctx context.Context, question *entity.Q
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
_ = qr.updateSearch(ctx, question.ID)
|
||||
_ = qr.UpdateSearch(ctx, question.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -347,19 +344,22 @@ func (qr *questionRepo) SitemapQuestions(ctx context.Context, page, pageSize int
|
||||
}
|
||||
|
||||
// GetQuestionPage query question page
|
||||
func (qr *questionRepo) GetQuestionPage(ctx context.Context, page, pageSize int, userID, tagID, orderCond string, inDays int) (
|
||||
func (qr *questionRepo) GetQuestionPage(ctx context.Context, page, pageSize int, tagIDs []string, userID, orderCond string, inDays int, showHidden bool) (
|
||||
questionList []*entity.Question, total int64, err error) {
|
||||
questionList = make([]*entity.Question, 0)
|
||||
|
||||
session := qr.data.DB.Context(ctx).Where("question.status = ? OR question.status = ?",
|
||||
entity.QuestionStatusAvailable, entity.QuestionStatusClosed)
|
||||
if len(tagID) > 0 {
|
||||
if len(tagIDs) > 0 {
|
||||
session.Join("LEFT", "tag_rel", "question.id = tag_rel.object_id")
|
||||
session.And("tag_rel.tag_id = ?", tagID)
|
||||
session.In("tag_rel.tag_id", tagIDs)
|
||||
session.And("tag_rel.status = ?", entity.TagRelStatusAvailable)
|
||||
}
|
||||
if len(userID) > 0 {
|
||||
session.And("question.user_id = ?", userID)
|
||||
if !showHidden {
|
||||
session.And("question.show = ?", entity.QuestionShow)
|
||||
}
|
||||
} else {
|
||||
session.And("question.show = ?", entity.QuestionShow)
|
||||
}
|
||||
@@ -462,8 +462,8 @@ func (qr *questionRepo) AdminQuestionPage(ctx context.Context, search *schema.Ad
|
||||
return rows, count, nil
|
||||
}
|
||||
|
||||
// updateSearch update search, if search plugin not enable, do nothing
|
||||
func (qr *questionRepo) updateSearch(ctx context.Context, questionID string) (err error) {
|
||||
// UpdateSearch update search, if search plugin not enable, do nothing
|
||||
func (qr *questionRepo) UpdateSearch(ctx context.Context, questionID string) (err error) {
|
||||
// check search plugin
|
||||
var s plugin.Search
|
||||
_ = plugin.CallSearch(func(search plugin.Search) error {
|
||||
@@ -544,7 +544,7 @@ func (qr *questionRepo) RemoveAllUserQuestion(ctx context.Context, userID string
|
||||
|
||||
// update search content
|
||||
for _, id := range questionIDs {
|
||||
_ = qr.updateSearch(ctx, id)
|
||||
_ = qr.UpdateSearch(ctx, id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ func NewSearchRepo(
|
||||
}
|
||||
|
||||
// SearchContents search question and answer data
|
||||
func (sr *searchRepo) SearchContents(ctx context.Context, words []string, tagIDs []string, userID string, votes int, page, size int, order string) (resp []*schema.SearchResult, total int64, err error) {
|
||||
func (sr *searchRepo) SearchContents(ctx context.Context, words []string, tagIDs [][]string, userID string, votes int, page, size int, order string) (resp []*schema.SearchResult, total int64, err error) {
|
||||
words = filterWords(words)
|
||||
|
||||
var (
|
||||
@@ -152,16 +152,20 @@ func (sr *searchRepo) SearchContents(ctx context.Context, words []string, tagIDs
|
||||
ast := "tag_rel" + strconv.Itoa(ti)
|
||||
b.Join("INNER", "tag_rel as "+ast, "question.id = "+ast+".object_id").
|
||||
And(builder.Eq{
|
||||
ast + ".tag_id": tagID,
|
||||
ast + ".status": entity.TagRelStatusAvailable,
|
||||
})
|
||||
}).
|
||||
And(builder.In(ast+".tag_id", tagID))
|
||||
ub.Join("INNER", "tag_rel as "+ast, "question_id = "+ast+".object_id").
|
||||
And(builder.Eq{
|
||||
ast + ".tag_id": tagID,
|
||||
ast + ".status": entity.TagRelStatusAvailable,
|
||||
})
|
||||
argsQ = append(argsQ, entity.TagRelStatusAvailable, tagID)
|
||||
argsA = append(argsA, entity.TagRelStatusAvailable, tagID)
|
||||
}).
|
||||
And(builder.In(ast+".tag_id", tagID))
|
||||
argsQ = append(argsQ, entity.TagRelStatusAvailable)
|
||||
argsA = append(argsA, entity.TagRelStatusAvailable)
|
||||
for _, t := range tagID {
|
||||
argsQ = append(argsQ, t)
|
||||
argsA = append(argsA, t)
|
||||
}
|
||||
}
|
||||
|
||||
// check user
|
||||
@@ -236,7 +240,7 @@ func (sr *searchRepo) SearchContents(ctx context.Context, words []string, tagIDs
|
||||
}
|
||||
|
||||
// SearchQuestions search question data
|
||||
func (sr *searchRepo) SearchQuestions(ctx context.Context, words []string, tagIDs []string, notAccepted bool, views, answers int, page, size int, order string) (resp []*schema.SearchResult, total int64, err error) {
|
||||
func (sr *searchRepo) SearchQuestions(ctx context.Context, words []string, tagIDs [][]string, notAccepted bool, views, answers int, page, size int, order string) (resp []*schema.SearchResult, total int64, err error) {
|
||||
words = filterWords(words)
|
||||
var (
|
||||
qfs = qFields
|
||||
@@ -269,10 +273,13 @@ func (sr *searchRepo) SearchQuestions(ctx context.Context, words []string, tagID
|
||||
ast := "tag_rel" + strconv.Itoa(ti)
|
||||
b.Join("INNER", "tag_rel as "+ast, "question.id = "+ast+".object_id").
|
||||
And(builder.Eq{
|
||||
ast + ".tag_id": tagID,
|
||||
ast + ".status": entity.TagRelStatusAvailable,
|
||||
})
|
||||
args = append(args, entity.TagRelStatusAvailable, tagID)
|
||||
}).
|
||||
And(builder.In(ast+".tag_id", tagID))
|
||||
args = append(args, entity.TagRelStatusAvailable)
|
||||
for _, t := range tagID {
|
||||
args = append(args, t)
|
||||
}
|
||||
}
|
||||
|
||||
// check need filter has not accepted
|
||||
@@ -343,7 +350,7 @@ func (sr *searchRepo) SearchQuestions(ctx context.Context, words []string, tagID
|
||||
}
|
||||
|
||||
// SearchAnswers search answer data
|
||||
func (sr *searchRepo) SearchAnswers(ctx context.Context, words []string, tagIDs []string, accepted bool, questionID string, page, size int, order string) (resp []*schema.SearchResult, total int64, err error) {
|
||||
func (sr *searchRepo) SearchAnswers(ctx context.Context, words []string, tagIDs [][]string, accepted bool, questionID string, page, size int, order string) (resp []*schema.SearchResult, total int64, err error) {
|
||||
words = filterWords(words)
|
||||
|
||||
var (
|
||||
@@ -378,10 +385,13 @@ func (sr *searchRepo) SearchAnswers(ctx context.Context, words []string, tagIDs
|
||||
ast := "tag_rel" + strconv.Itoa(ti)
|
||||
b.Join("INNER", "tag_rel as "+ast, "question_id = "+ast+".object_id").
|
||||
And(builder.Eq{
|
||||
ast + ".tag_id": tagID,
|
||||
ast + ".status": entity.TagRelStatusAvailable,
|
||||
})
|
||||
args = append(args, entity.TagRelStatusAvailable, tagID)
|
||||
}).
|
||||
And(builder.In(ast+".tag_id", tagID))
|
||||
args = append(args, entity.TagRelStatusAvailable)
|
||||
for _, t := range tagID {
|
||||
args = append(args, t)
|
||||
}
|
||||
}
|
||||
|
||||
// check limit accepted
|
||||
|
||||
@@ -21,6 +21,7 @@ package tag
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/apache/incubator-answer/internal/base/data"
|
||||
"github.com/apache/incubator-answer/internal/base/reason"
|
||||
"github.com/apache/incubator-answer/internal/entity"
|
||||
@@ -118,6 +119,15 @@ func (tr *tagRepo) GetTagSynonymCount(ctx context.Context, tagID string) (count
|
||||
return
|
||||
}
|
||||
|
||||
func (tr *tagRepo) GetIDsByMainTagId(ctx context.Context, mainTagID string) (tagIDs []string, err error) {
|
||||
session := tr.data.DB.Context(ctx).Table(entity.Tag{}.TableName()).Where(builder.Eq{"status": entity.TagStatusAvailable, "main_tag_id": converter.StringToInt64(mainTagID)}).Cols("id")
|
||||
err = session.Find(&tagIDs)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetTagList get tag list all
|
||||
func (tr *tagRepo) GetTagList(ctx context.Context, tag *entity.Tag) (tagList []*entity.Tag, err error) {
|
||||
tagList = make([]*entity.Tag, 0)
|
||||
|
||||
@@ -72,6 +72,17 @@ func (ur *userExternalLoginRepo) GetByExternalID(ctx context.Context, provider,
|
||||
return
|
||||
}
|
||||
|
||||
// GetByUserID get by user ID
|
||||
func (ur *userExternalLoginRepo) GetByUserID(ctx context.Context, provider, userID string) (
|
||||
userInfo *entity.UserExternalLogin, exist bool, err error) {
|
||||
userInfo = &entity.UserExternalLogin{}
|
||||
exist, err = ur.data.DB.Context(ctx).Where("user_id = ?", userID).Where("provider = ?", provider).Get(userInfo)
|
||||
if err != nil {
|
||||
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetUserExternalLoginList get by external ID
|
||||
func (ur *userExternalLoginRepo) GetUserExternalLoginList(ctx context.Context, userID string) (
|
||||
resp []*entity.UserExternalLogin, err error) {
|
||||
|
||||
@@ -53,6 +53,7 @@ type AnswerAPIRouter struct {
|
||||
roleController *controller_admin.RoleController
|
||||
pluginController *controller_admin.PluginController
|
||||
permissionController *controller.PermissionController
|
||||
userPluginController *controller.UserPluginController
|
||||
}
|
||||
|
||||
func NewAnswerAPIRouter(
|
||||
@@ -82,6 +83,7 @@ func NewAnswerAPIRouter(
|
||||
roleController *controller_admin.RoleController,
|
||||
pluginController *controller_admin.PluginController,
|
||||
permissionController *controller.PermissionController,
|
||||
userPluginController *controller.UserPluginController,
|
||||
) *AnswerAPIRouter {
|
||||
return &AnswerAPIRouter{
|
||||
langController: langController,
|
||||
@@ -110,6 +112,7 @@ func NewAnswerAPIRouter(
|
||||
roleController: roleController,
|
||||
pluginController: pluginController,
|
||||
permissionController: permissionController,
|
||||
userPluginController: userPluginController,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,6 +271,10 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) {
|
||||
r.GET("/activity/timeline", a.activityController.GetObjectTimeline)
|
||||
r.GET("/activity/timeline/detail", a.activityController.GetObjectTimelineDetail)
|
||||
|
||||
// plugin
|
||||
r.GET("/user/plugin/configs", a.userPluginController.GetUserPluginList)
|
||||
r.GET("/user/plugin/config", a.userPluginController.GetUserPluginConfig)
|
||||
r.PUT("/user/plugin/config", a.userPluginController.UpdatePluginUserConfig)
|
||||
}
|
||||
|
||||
func (a *AnswerAPIRouter) RegisterAnswerAdminAPIRouter(r *gin.RouterGroup) {
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package schema
|
||||
|
||||
import (
|
||||
"github.com/apache/incubator-answer/plugin"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type GetUserPluginListResp struct {
|
||||
Name string `json:"name"`
|
||||
SlugName string `json:"slug_name"`
|
||||
}
|
||||
|
||||
type UpdateUserPluginReq struct {
|
||||
PluginSlugName string `validate:"required,gt=1,lte=100" json:"plugin_slug_name"`
|
||||
UserID string `json:"-"`
|
||||
}
|
||||
|
||||
type GetUserPluginConfigReq struct {
|
||||
PluginSlugName string `validate:"required,gt=1,lte=100" form:"plugin_slug_name"`
|
||||
UserID string `json:"-"`
|
||||
}
|
||||
|
||||
type GetUserPluginConfigResp struct {
|
||||
Name string `json:"name"`
|
||||
SlugName string `json:"slug_name"`
|
||||
ConfigFields []*ConfigField `json:"config_fields"`
|
||||
}
|
||||
|
||||
func (g *GetUserPluginConfigResp) SetConfigFields(ctx *gin.Context, fields []plugin.ConfigField) {
|
||||
for _, field := range fields {
|
||||
configField := &ConfigField{
|
||||
Name: field.Name,
|
||||
Type: string(field.Type),
|
||||
Title: field.Title.Translate(ctx),
|
||||
Description: field.Description.Translate(ctx),
|
||||
Required: field.Required,
|
||||
Value: field.Value,
|
||||
UIOptions: ConfigFieldUIOptions{
|
||||
Rows: field.UIOptions.Rows,
|
||||
InputType: string(field.UIOptions.InputType),
|
||||
Variant: field.UIOptions.Variant,
|
||||
},
|
||||
}
|
||||
configField.UIOptions.Placeholder = field.UIOptions.Placeholder.Translate(ctx)
|
||||
configField.UIOptions.Label = field.UIOptions.Label.Translate(ctx)
|
||||
configField.UIOptions.Text = field.UIOptions.Text.Translate(ctx)
|
||||
if field.UIOptions.Action != nil {
|
||||
uiOptionAction := &UIOptionAction{
|
||||
Url: field.UIOptions.Action.Url,
|
||||
Method: field.UIOptions.Action.Method,
|
||||
}
|
||||
if field.UIOptions.Action.Loading != nil {
|
||||
uiOptionAction.Loading = &LoadingAction{
|
||||
Text: field.UIOptions.Action.Loading.Text.Translate(ctx),
|
||||
State: string(field.UIOptions.Action.Loading.State),
|
||||
}
|
||||
}
|
||||
if field.UIOptions.Action.OnComplete != nil {
|
||||
uiOptionAction.OnCompleteAction = &OnCompleteAction{
|
||||
ToastReturnMessage: field.UIOptions.Action.OnComplete.ToastReturnMessage,
|
||||
RefreshFormConfig: field.UIOptions.Action.OnComplete.RefreshFormConfig,
|
||||
}
|
||||
}
|
||||
configField.UIOptions.Action = uiOptionAction
|
||||
}
|
||||
|
||||
for _, option := range field.Options {
|
||||
configField.Options = append(configField.Options, ConfigFieldOption{
|
||||
Label: option.Label.Translate(ctx),
|
||||
Value: option.Value,
|
||||
})
|
||||
}
|
||||
g.ConfigFields = append(g.ConfigFields, configField)
|
||||
}
|
||||
}
|
||||
|
||||
type UpdateUserPluginConfigReq struct {
|
||||
PluginSlugName string `validate:"required,gt=1,lte=100" json:"plugin_slug_name"`
|
||||
ConfigFields map[string]any `json:"config_fields"`
|
||||
UserID string `json:"-"`
|
||||
}
|
||||
@@ -64,7 +64,7 @@ type SearchCondition struct {
|
||||
// only show this question's answer
|
||||
QuestionID string
|
||||
// search query tags
|
||||
Tags []string
|
||||
Tags [][]string
|
||||
// search query keywords
|
||||
Words []string
|
||||
}
|
||||
|
||||
@@ -38,41 +38,13 @@ func NewNotificationChannelsFormJson(jsonStr string) NotificationChannels {
|
||||
return list
|
||||
}
|
||||
|
||||
func (n *NotificationChannels) Format(sequences []constant.NotificationChannelKey) {
|
||||
if n == nil {
|
||||
*n = make([]*NotificationChannelConfig, 0)
|
||||
return
|
||||
func NewNotificationChannelConfigFormJson(jsonStr string) NotificationChannelConfig {
|
||||
var list NotificationChannels
|
||||
_ = json.Unmarshal([]byte(jsonStr), &list)
|
||||
if len(list) > 0 {
|
||||
return *list[0]
|
||||
}
|
||||
mapping := make(map[constant.NotificationChannelKey]*NotificationChannelConfig)
|
||||
for _, item := range *n {
|
||||
mapping[item.Key] = &NotificationChannelConfig{
|
||||
Key: item.Key,
|
||||
Enable: item.Enable,
|
||||
}
|
||||
}
|
||||
newList := make([]*NotificationChannelConfig, 0)
|
||||
for _, ch := range sequences {
|
||||
if c, ok := mapping[ch]; ok {
|
||||
newList = append(newList, c)
|
||||
} else {
|
||||
newList = append(newList, &NotificationChannelConfig{
|
||||
Key: ch,
|
||||
})
|
||||
}
|
||||
}
|
||||
*n = newList
|
||||
}
|
||||
|
||||
func (n *NotificationChannels) CheckEnable(ch constant.NotificationChannelKey) bool {
|
||||
if n == nil {
|
||||
return false
|
||||
}
|
||||
for _, item := range *n {
|
||||
if item.Key == ch {
|
||||
return item.Enable
|
||||
}
|
||||
}
|
||||
return false
|
||||
return NotificationChannelConfig{}
|
||||
}
|
||||
|
||||
func (n *NotificationChannels) ToJsonString() string {
|
||||
@@ -81,62 +53,39 @@ func (n *NotificationChannels) ToJsonString() string {
|
||||
}
|
||||
|
||||
type NotificationConfig struct {
|
||||
Inbox NotificationChannels `json:"inbox"`
|
||||
AllNewQuestion NotificationChannels `json:"all_new_question"`
|
||||
AllNewQuestionForFollowingTags NotificationChannels `json:"all_new_question_for_following_tags"`
|
||||
}
|
||||
|
||||
func (n *NotificationConfig) ToJsonString() string {
|
||||
data, _ := json.Marshal(n)
|
||||
return string(data)
|
||||
Inbox NotificationChannelConfig `json:"inbox"`
|
||||
AllNewQuestion NotificationChannelConfig `json:"all_new_question"`
|
||||
AllNewQuestionForFollowingTags NotificationChannelConfig `json:"all_new_question_for_following_tags"`
|
||||
}
|
||||
|
||||
func NewNotificationConfig(configs []*entity.UserNotificationConfig) NotificationConfig {
|
||||
nc := NotificationConfig{}
|
||||
nc.Inbox = make([]*NotificationChannelConfig, 0)
|
||||
nc.AllNewQuestion = make([]*NotificationChannelConfig, 0)
|
||||
nc.AllNewQuestionForFollowingTags = make([]*NotificationChannelConfig, 0)
|
||||
for _, item := range configs {
|
||||
switch item.Source {
|
||||
case string(constant.InboxSource):
|
||||
nc.Inbox = NewNotificationChannelsFormJson(item.Channels)
|
||||
nc.Inbox = NewNotificationChannelConfigFormJson(item.Channels)
|
||||
case string(constant.AllNewQuestionSource):
|
||||
nc.AllNewQuestion = NewNotificationChannelsFormJson(item.Channels)
|
||||
nc.AllNewQuestion = NewNotificationChannelConfigFormJson(item.Channels)
|
||||
case string(constant.AllNewQuestionForFollowingTagsSource):
|
||||
nc.AllNewQuestionForFollowingTags = NewNotificationChannelsFormJson(item.Channels)
|
||||
nc.AllNewQuestionForFollowingTags = NewNotificationChannelConfigFormJson(item.Channels)
|
||||
}
|
||||
}
|
||||
return nc
|
||||
}
|
||||
|
||||
func (n *NotificationConfig) FromJsonString(data string) {
|
||||
if len(data) > 0 {
|
||||
_ = json.Unmarshal([]byte(data), n)
|
||||
return
|
||||
}
|
||||
n.Inbox = make([]*NotificationChannelConfig, 0)
|
||||
n.AllNewQuestion = make([]*NotificationChannelConfig, 0)
|
||||
n.AllNewQuestionForFollowingTags = make([]*NotificationChannelConfig, 0)
|
||||
return
|
||||
}
|
||||
|
||||
func (n *NotificationConfig) Format() {
|
||||
n.Inbox.Format([]constant.NotificationChannelKey{constant.EmailChannel})
|
||||
n.AllNewQuestion.Format([]constant.NotificationChannelKey{constant.EmailChannel})
|
||||
n.AllNewQuestionForFollowingTags.Format([]constant.NotificationChannelKey{constant.EmailChannel})
|
||||
}
|
||||
|
||||
func (n *NotificationConfig) CheckEnable(
|
||||
source constant.NotificationSource, channel constant.NotificationChannelKey) bool {
|
||||
switch source {
|
||||
case constant.InboxSource:
|
||||
return n.Inbox.CheckEnable(channel)
|
||||
case constant.AllNewQuestionSource:
|
||||
return n.AllNewQuestion.CheckEnable(channel)
|
||||
case constant.AllNewQuestionForFollowingTagsSource:
|
||||
return n.AllNewQuestionForFollowingTags.CheckEnable(channel)
|
||||
if n.Inbox.Key == "" {
|
||||
n.Inbox.Key = constant.EmailChannel
|
||||
n.Inbox.Enable = false
|
||||
}
|
||||
if n.AllNewQuestion.Key == "" {
|
||||
n.AllNewQuestion.Key = constant.EmailChannel
|
||||
n.AllNewQuestion.Enable = false
|
||||
}
|
||||
if n.AllNewQuestionForFollowingTags.Key == "" {
|
||||
n.AllNewQuestionForFollowingTags.Key = constant.EmailChannel
|
||||
n.AllNewQuestionForFollowingTags.Enable = false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// UpdateUserNotificationConfigReq update user notification config request
|
||||
|
||||
@@ -325,6 +325,7 @@ type UserBasicInfo struct {
|
||||
Avatar string `json:"avatar"`
|
||||
Website string `json:"website"`
|
||||
Location string `json:"location"`
|
||||
Language string `json:"language"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
|
||||
@@ -21,14 +21,20 @@ package notification
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/apache/incubator-answer/internal/base/constant"
|
||||
"github.com/apache/incubator-answer/internal/base/data"
|
||||
"github.com/apache/incubator-answer/internal/schema"
|
||||
"github.com/apache/incubator-answer/internal/service/activity_common"
|
||||
"github.com/apache/incubator-answer/internal/service/export"
|
||||
"github.com/apache/incubator-answer/internal/service/notice_queue"
|
||||
"github.com/apache/incubator-answer/internal/service/siteinfo_common"
|
||||
usercommon "github.com/apache/incubator-answer/internal/service/user_common"
|
||||
"github.com/apache/incubator-answer/internal/service/user_external_login"
|
||||
"github.com/apache/incubator-answer/internal/service/user_notification_config"
|
||||
"github.com/apache/incubator-answer/pkg/display"
|
||||
"github.com/apache/incubator-answer/plugin"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ExternalNotificationService struct {
|
||||
@@ -38,6 +44,8 @@ type ExternalNotificationService struct {
|
||||
emailService *export.EmailService
|
||||
userRepo usercommon.UserRepo
|
||||
notificationQueueService notice_queue.ExternalNotificationQueueService
|
||||
userExternalLoginRepo user_external_login.UserExternalLoginRepo
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||
}
|
||||
|
||||
func NewExternalNotificationService(
|
||||
@@ -47,6 +55,8 @@ func NewExternalNotificationService(
|
||||
emailService *export.EmailService,
|
||||
userRepo usercommon.UserRepo,
|
||||
notificationQueueService notice_queue.ExternalNotificationQueueService,
|
||||
userExternalLoginRepo user_external_login.UserExternalLoginRepo,
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService,
|
||||
) *ExternalNotificationService {
|
||||
n := &ExternalNotificationService{
|
||||
data: data,
|
||||
@@ -55,6 +65,8 @@ func NewExternalNotificationService(
|
||||
emailService: emailService,
|
||||
userRepo: userRepo,
|
||||
notificationQueueService: notificationQueueService,
|
||||
userExternalLoginRepo: userExternalLoginRepo,
|
||||
siteInfoService: siteInfoService,
|
||||
}
|
||||
notificationQueueService.RegisterHandler(n.Handler)
|
||||
return n
|
||||
@@ -78,3 +90,64 @@ func (ns *ExternalNotificationService) Handler(ctx context.Context, msg *schema.
|
||||
log.Errorf("unknown notification message: %+v", msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *ExternalNotificationService) syncNotificationToPlugin(ctx context.Context,
|
||||
source constant.NotificationSource, msg *schema.ExternalNotificationMsg) {
|
||||
pluginNotificationMsg := &plugin.NotificationMessage{
|
||||
ReceiverUserID: msg.ReceiverUserID,
|
||||
ReceiverLang: msg.ReceiverLang,
|
||||
}
|
||||
|
||||
switch source {
|
||||
case constant.InboxSource:
|
||||
return
|
||||
case constant.AllNewQuestionSource:
|
||||
pluginNotificationMsg.Type = plugin.NotificationNewQuestion
|
||||
pluginNotificationMsg = ns.newPluginQuestionNotification(ctx, msg)
|
||||
case constant.AllNewQuestionForFollowingTagsSource:
|
||||
pluginNotificationMsg.Type = plugin.NotificationNewQuestionFollowedTag
|
||||
pluginNotificationMsg = ns.newPluginQuestionNotification(ctx, msg)
|
||||
}
|
||||
|
||||
if len(msg.ReceiverLang) == 0 && len(msg.ReceiverUserID) > 0 {
|
||||
userInfo, _, _ := ns.userRepo.GetByUserID(ctx, msg.ReceiverUserID)
|
||||
if userInfo != nil {
|
||||
pluginNotificationMsg.ReceiverLang = userInfo.Language
|
||||
}
|
||||
}
|
||||
|
||||
_ = plugin.CallNotification(func(fn plugin.Notification) error {
|
||||
userInfo, exist, err := ns.userExternalLoginRepo.GetByUserID(ctx, fn.Info().SlugName, msg.ReceiverUserID)
|
||||
if err != nil {
|
||||
log.Errorf("get user external login info failed: %v", err)
|
||||
return nil
|
||||
}
|
||||
if exist {
|
||||
pluginNotificationMsg.ReceiverExternalID = userInfo.ExternalID
|
||||
}
|
||||
fn.Notify(pluginNotificationMsg)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (ns *ExternalNotificationService) newPluginQuestionNotification(
|
||||
ctx context.Context, msg *schema.ExternalNotificationMsg) (raw *plugin.NotificationMessage) {
|
||||
raw = &plugin.NotificationMessage{
|
||||
ReceiverUserID: msg.ReceiverUserID,
|
||||
ReceiverLang: msg.ReceiverLang,
|
||||
QuestionTitle: msg.NewQuestionTemplateRawData.QuestionTitle,
|
||||
QuestionTags: strings.Join(msg.NewQuestionTemplateRawData.Tags, ","),
|
||||
}
|
||||
siteInfo, err := ns.siteInfoService.GetSiteGeneral(ctx)
|
||||
if err != nil {
|
||||
return raw
|
||||
}
|
||||
seoInfo, err := ns.siteInfoService.GetSiteSeo(ctx)
|
||||
if err != nil {
|
||||
return raw
|
||||
}
|
||||
raw.QuestionUrl = display.QuestionURL(
|
||||
seoInfo.Permalink, siteInfo.SiteUrl,
|
||||
msg.NewQuestionTemplateRawData.QuestionID, msg.NewQuestionTemplateRawData.QuestionTitle)
|
||||
return raw
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ func (ns *ExternalNotificationService) handleInviteAnswerNotification(ctx contex
|
||||
ns.sendInviteAnswerNotificationEmail(ctx, msg.ReceiverUserID, msg.ReceiverEmail, msg.ReceiverLang, msg.NewInviteAnswerTemplateRawData)
|
||||
}
|
||||
}
|
||||
ns.syncNotificationToPlugin(ctx, constant.InboxSource, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ func (ns *ExternalNotificationService) handleNewAnswerNotification(ctx context.C
|
||||
ns.sendNewAnswerNotificationEmail(ctx, msg.ReceiverUserID, msg.ReceiverEmail, msg.ReceiverLang, msg.NewAnswerTemplateRawData)
|
||||
}
|
||||
}
|
||||
ns.syncNotificationToPlugin(ctx, constant.InboxSource, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ func (ns *ExternalNotificationService) handleNewCommentNotification(ctx context.
|
||||
ns.sendNewCommentNotificationEmail(ctx, msg.ReceiverUserID, msg.ReceiverEmail, msg.ReceiverLang, msg.NewCommentTemplateRawData)
|
||||
}
|
||||
}
|
||||
|
||||
ns.syncNotificationToPlugin(ctx, constant.InboxSource, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -24,14 +24,16 @@ import (
|
||||
"github.com/apache/incubator-answer/internal/base/constant"
|
||||
"github.com/apache/incubator-answer/internal/schema"
|
||||
"github.com/apache/incubator-answer/pkg/token"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/segmentfault/pacman/i18n"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"time"
|
||||
)
|
||||
|
||||
type NewQuestionSubscriber struct {
|
||||
UserID string `json:"user_id"`
|
||||
Channels schema.NotificationChannels `json:"channels"`
|
||||
UserID string `json:"user_id"`
|
||||
Channels schema.NotificationChannels `json:"channels"`
|
||||
NotificationSource constant.NotificationSource `json:"notification_source"`
|
||||
}
|
||||
|
||||
func (ns *ExternalNotificationService) handleNewQuestionNotification(ctx context.Context,
|
||||
@@ -59,6 +61,10 @@ func (ns *ExternalNotificationService) handleNewQuestionNotification(ctx context
|
||||
})
|
||||
}
|
||||
}
|
||||
pluginMsg := &schema.ExternalNotificationMsg{}
|
||||
_ = copier.Copy(pluginMsg, msg)
|
||||
pluginMsg.ReceiverUserID = subscriber.UserID
|
||||
ns.syncNotificationToPlugin(ctx, subscriber.NotificationSource, pluginMsg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -94,8 +100,9 @@ func (ns *ExternalNotificationService) getNewQuestionSubscribers(ctx context.Con
|
||||
continue
|
||||
}
|
||||
subscribersMapping[userNotificationConfig.UserID] = &NewQuestionSubscriber{
|
||||
UserID: userNotificationConfig.UserID,
|
||||
Channels: schema.NewNotificationChannelsFormJson(userNotificationConfig.Channels),
|
||||
UserID: userNotificationConfig.UserID,
|
||||
Channels: schema.NewNotificationChannelsFormJson(userNotificationConfig.Channels),
|
||||
NotificationSource: constant.AllNewQuestionForFollowingTagsSource,
|
||||
}
|
||||
}
|
||||
log.Debugf("get %d subscribers from tags", len(subscribersMapping))
|
||||
@@ -113,8 +120,9 @@ func (ns *ExternalNotificationService) getNewQuestionSubscribers(ctx context.Con
|
||||
continue
|
||||
}
|
||||
subscribersMapping[notificationConfig.UserID] = &NewQuestionSubscriber{
|
||||
UserID: notificationConfig.UserID,
|
||||
Channels: schema.NewNotificationChannelsFormJson(notificationConfig.Channels),
|
||||
UserID: notificationConfig.UserID,
|
||||
Channels: schema.NewNotificationChannelsFormJson(notificationConfig.Channels),
|
||||
NotificationSource: constant.AllNewQuestionSource,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,9 @@ package notificationcommon
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/apache/incubator-answer/internal/service/siteinfo_common"
|
||||
"github.com/apache/incubator-answer/internal/service/user_external_login"
|
||||
"github.com/apache/incubator-answer/pkg/display"
|
||||
"time"
|
||||
|
||||
"github.com/apache/incubator-answer/internal/base/constant"
|
||||
@@ -59,6 +62,8 @@ type NotificationCommon struct {
|
||||
userCommon *usercommon.UserCommon
|
||||
objectInfoService *object_info.ObjService
|
||||
notificationQueueService notice_queue.NotificationQueueService
|
||||
userExternalLoginRepo user_external_login.UserExternalLoginRepo
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||
}
|
||||
|
||||
func NewNotificationCommon(
|
||||
@@ -69,6 +74,8 @@ func NewNotificationCommon(
|
||||
followRepo activity_common.FollowRepo,
|
||||
objectInfoService *object_info.ObjService,
|
||||
notificationQueueService notice_queue.NotificationQueueService,
|
||||
userExternalLoginRepo user_external_login.UserExternalLoginRepo,
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService,
|
||||
) *NotificationCommon {
|
||||
notification := &NotificationCommon{
|
||||
data: data,
|
||||
@@ -78,6 +85,8 @@ func NewNotificationCommon(
|
||||
userCommon: userCommon,
|
||||
objectInfoService: objectInfoService,
|
||||
notificationQueueService: notificationQueueService,
|
||||
userExternalLoginRepo: userExternalLoginRepo,
|
||||
siteInfoService: siteInfoService,
|
||||
}
|
||||
notificationQueueService.RegisterHandler(notification.AddNotification)
|
||||
return notification
|
||||
@@ -183,6 +192,10 @@ func (ns *NotificationCommon) AddNotification(ctx context.Context, msg *schema.N
|
||||
}
|
||||
|
||||
go ns.SendNotificationToAllFollower(ctx, msg, questionID)
|
||||
|
||||
if msg.Type == schema.NotificationTypeInbox {
|
||||
ns.syncNotificationToPlugin(ctx, objInfo, msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -226,3 +239,72 @@ func (ns *NotificationCommon) SendNotificationToAllFollower(ctx context.Context,
|
||||
ns.notificationQueueService.Send(ctx, t)
|
||||
}
|
||||
}
|
||||
|
||||
func (ns *NotificationCommon) syncNotificationToPlugin(ctx context.Context, objInfo *schema.SimpleObjectInfo,
|
||||
msg *schema.NotificationMsg) {
|
||||
siteInfo, err := ns.siteInfoService.GetSiteGeneral(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("get site general info failed: %v", err)
|
||||
return
|
||||
}
|
||||
seoInfo, err := ns.siteInfoService.GetSiteSeo(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("get site seo info failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
objInfo.QuestionID = uid.DeShortID(objInfo.QuestionID)
|
||||
objInfo.AnswerID = uid.DeShortID(objInfo.AnswerID)
|
||||
pluginNotificationMsg := &plugin.NotificationMessage{
|
||||
Type: plugin.NotificationType(msg.NotificationAction),
|
||||
ReceiverUserID: msg.ReceiverUserID,
|
||||
TriggerUserID: msg.TriggerUserID,
|
||||
QuestionTitle: objInfo.Title,
|
||||
}
|
||||
|
||||
if len(objInfo.QuestionID) > 0 {
|
||||
pluginNotificationMsg.QuestionUrl =
|
||||
display.QuestionURL(seoInfo.Permalink, siteInfo.SiteUrl, objInfo.QuestionID, objInfo.Title)
|
||||
}
|
||||
if len(objInfo.AnswerID) > 0 {
|
||||
pluginNotificationMsg.AnswerUrl =
|
||||
display.AnswerURL(seoInfo.Permalink, siteInfo.SiteUrl, objInfo.QuestionID, objInfo.Title, objInfo.AnswerID)
|
||||
}
|
||||
if len(objInfo.CommentID) > 0 {
|
||||
pluginNotificationMsg.CommentUrl =
|
||||
display.CommentURL(seoInfo.Permalink, siteInfo.SiteUrl, objInfo.QuestionID, objInfo.Title, objInfo.AnswerID, objInfo.CommentID)
|
||||
}
|
||||
|
||||
if len(msg.TriggerUserID) > 0 {
|
||||
triggerUser, exist, err := ns.userCommon.GetUserBasicInfoByID(ctx, msg.TriggerUserID)
|
||||
if err != nil {
|
||||
log.Errorf("get trigger user basic info failed: %v", err)
|
||||
return
|
||||
}
|
||||
if exist {
|
||||
pluginNotificationMsg.TriggerUserID = triggerUser.ID
|
||||
pluginNotificationMsg.TriggerUserDisplayName = triggerUser.DisplayName
|
||||
pluginNotificationMsg.TriggerUserUrl = display.UserURL(siteInfo.SiteUrl, triggerUser.Username)
|
||||
}
|
||||
}
|
||||
|
||||
if len(pluginNotificationMsg.ReceiverLang) == 0 && len(msg.ReceiverUserID) > 0 {
|
||||
userInfo, _, _ := ns.userCommon.GetUserBasicInfoByID(ctx, msg.ReceiverUserID)
|
||||
if userInfo != nil {
|
||||
pluginNotificationMsg.ReceiverLang = userInfo.Language
|
||||
}
|
||||
}
|
||||
|
||||
_ = plugin.CallNotification(func(fn plugin.Notification) error {
|
||||
userInfo, exist, err := ns.userExternalLoginRepo.GetByUserID(ctx, fn.Info().SlugName, msg.ReceiverUserID)
|
||||
if err != nil {
|
||||
log.Errorf("get user external login info failed: %v", err)
|
||||
return nil
|
||||
}
|
||||
if exist {
|
||||
pluginNotificationMsg.ReceiverExternalID = userInfo.ExternalID
|
||||
}
|
||||
fn.Notify(pluginNotificationMsg)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -22,12 +22,11 @@ package plugin_common
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/apache/incubator-answer/internal/base/data"
|
||||
"github.com/apache/incubator-answer/internal/repo/search_sync"
|
||||
|
||||
"github.com/apache/incubator-answer/internal/base/constant"
|
||||
"github.com/apache/incubator-answer/internal/base/data"
|
||||
"github.com/apache/incubator-answer/internal/base/reason"
|
||||
"github.com/apache/incubator-answer/internal/entity"
|
||||
"github.com/apache/incubator-answer/internal/repo/search_sync"
|
||||
"github.com/apache/incubator-answer/internal/schema"
|
||||
"github.com/apache/incubator-answer/internal/service/config"
|
||||
"github.com/apache/incubator-answer/plugin"
|
||||
@@ -40,16 +39,24 @@ type PluginConfigRepo interface {
|
||||
GetPluginConfigAll(ctx context.Context) (pluginConfigs []*entity.PluginConfig, err error)
|
||||
}
|
||||
|
||||
type PluginUserConfigRepo interface {
|
||||
SaveUserPluginConfig(ctx context.Context, userID string, pluginSlugName, configValue string) (err error)
|
||||
GetPluginUserConfig(ctx context.Context, userID, pluginSlugName string) (
|
||||
pluginUserConfig *entity.PluginUserConfig, exist bool, err error)
|
||||
}
|
||||
|
||||
// PluginCommonService user service
|
||||
type PluginCommonService struct {
|
||||
configService *config.ConfigService
|
||||
pluginConfigRepo PluginConfigRepo
|
||||
data *data.Data
|
||||
configService *config.ConfigService
|
||||
pluginConfigRepo PluginConfigRepo
|
||||
pluginUserConfigRepo PluginUserConfigRepo
|
||||
data *data.Data
|
||||
}
|
||||
|
||||
// NewPluginCommonService new report service
|
||||
func NewPluginCommonService(
|
||||
pluginConfigRepo PluginConfigRepo,
|
||||
pluginUserConfigRepo PluginUserConfigRepo,
|
||||
configService *config.ConfigService,
|
||||
data *data.Data,
|
||||
) *PluginCommonService {
|
||||
@@ -82,10 +89,24 @@ func NewPluginCommonService(
|
||||
}
|
||||
}
|
||||
|
||||
// init plugin user config
|
||||
plugin.RegisterGetPluginUserConfigFunc(func(userID, pluginSlugName string) []byte {
|
||||
pluginUserConfig, exist, err := pluginUserConfigRepo.GetPluginUserConfig(context.Background(), userID, pluginSlugName)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return nil
|
||||
}
|
||||
if !exist {
|
||||
return nil
|
||||
}
|
||||
return []byte(pluginUserConfig.Value)
|
||||
})
|
||||
|
||||
return &PluginCommonService{
|
||||
configService: configService,
|
||||
pluginConfigRepo: pluginConfigRepo,
|
||||
data: data,
|
||||
configService: configService,
|
||||
pluginConfigRepo: pluginConfigRepo,
|
||||
pluginUserConfigRepo: pluginUserConfigRepo,
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,3 +135,26 @@ func (ps *PluginCommonService) UpdatePluginConfig(ctx context.Context, req *sche
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdatePluginUserConfig update plugin config
|
||||
func (ps *PluginCommonService) UpdatePluginUserConfig(ctx context.Context, req *schema.UpdateUserPluginConfigReq) (err error) {
|
||||
configValue, _ := json.Marshal(req.ConfigFields)
|
||||
err = ps.pluginUserConfigRepo.SaveUserPluginConfig(ctx, req.UserID, req.PluginSlugName, string(configValue))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserPluginConfig get user plugin config
|
||||
func (ps *PluginCommonService) GetUserPluginConfig(ctx context.Context, req *schema.GetUserPluginConfigReq) (
|
||||
configValue string, err error) {
|
||||
pluginUserConfig, exist, err := ps.pluginUserConfigRepo.GetPluginUserConfig(ctx, req.UserID, req.PluginSlugName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !exist {
|
||||
return "", nil
|
||||
}
|
||||
return pluginUserConfig.Value, nil
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ type QuestionRepo interface {
|
||||
UpdateQuestion(ctx context.Context, question *entity.Question, Cols []string) (err error)
|
||||
GetQuestion(ctx context.Context, id string) (question *entity.Question, exist bool, err error)
|
||||
GetQuestionList(ctx context.Context, question *entity.Question) (questions []*entity.Question, err error)
|
||||
GetQuestionPage(ctx context.Context, page, pageSize int, userID, tagID, orderCond string, inDays int) (
|
||||
GetQuestionPage(ctx context.Context, page, pageSize int, tagIDs []string, userID, orderCond string, inDays int, showHidden bool) (
|
||||
questionList []*entity.Question, total int64, err error)
|
||||
UpdateQuestionStatus(ctx context.Context, questionID string, status int) (err error)
|
||||
UpdateQuestionStatusWithOutUpdateTime(ctx context.Context, question *entity.Question) (err error)
|
||||
@@ -72,6 +72,7 @@ type QuestionRepo interface {
|
||||
GetUserQuestionCount(ctx context.Context, userID string) (count int64, err error)
|
||||
SitemapQuestions(ctx context.Context, page, pageSize int) (questionIDList []*schema.SiteMapQuestionInfo, err error)
|
||||
RemoveAllUserQuestion(ctx context.Context, userID string) (err error)
|
||||
UpdateSearch(ctx context.Context, questionID string) (err error)
|
||||
}
|
||||
|
||||
// QuestionCommon user service
|
||||
|
||||
@@ -22,9 +22,6 @@ package service
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/apache/incubator-answer/internal/service/notification"
|
||||
"github.com/apache/incubator-answer/internal/service/siteinfo_common"
|
||||
"github.com/apache/incubator-answer/pkg/token"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -42,13 +39,17 @@ import (
|
||||
"github.com/apache/incubator-answer/internal/service/export"
|
||||
"github.com/apache/incubator-answer/internal/service/meta"
|
||||
"github.com/apache/incubator-answer/internal/service/notice_queue"
|
||||
"github.com/apache/incubator-answer/internal/service/notification"
|
||||
"github.com/apache/incubator-answer/internal/service/permission"
|
||||
questioncommon "github.com/apache/incubator-answer/internal/service/question_common"
|
||||
"github.com/apache/incubator-answer/internal/service/revision_common"
|
||||
"github.com/apache/incubator-answer/internal/service/role"
|
||||
"github.com/apache/incubator-answer/internal/service/siteinfo_common"
|
||||
tagcommon "github.com/apache/incubator-answer/internal/service/tag_common"
|
||||
usercommon "github.com/apache/incubator-answer/internal/service/user_common"
|
||||
"github.com/apache/incubator-answer/pkg/converter"
|
||||
"github.com/apache/incubator-answer/pkg/htmltext"
|
||||
"github.com/apache/incubator-answer/pkg/token"
|
||||
"github.com/apache/incubator-answer/pkg/uid"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
@@ -65,6 +66,7 @@ type QuestionService struct {
|
||||
questioncommon *questioncommon.QuestionCommon
|
||||
userCommon *usercommon.UserCommon
|
||||
userRepo usercommon.UserRepo
|
||||
userRoleRelService *role.UserRoleRelService
|
||||
revisionService *revision_common.RevisionService
|
||||
metaService *meta.MetaService
|
||||
collectionCommon *collectioncommon.CollectionCommon
|
||||
@@ -83,6 +85,7 @@ func NewQuestionService(
|
||||
questioncommon *questioncommon.QuestionCommon,
|
||||
userCommon *usercommon.UserCommon,
|
||||
userRepo usercommon.UserRepo,
|
||||
userRoleRelService *role.UserRoleRelService,
|
||||
revisionService *revision_common.RevisionService,
|
||||
metaService *meta.MetaService,
|
||||
collectionCommon *collectioncommon.CollectionCommon,
|
||||
@@ -100,6 +103,7 @@ func NewQuestionService(
|
||||
questioncommon: questioncommon,
|
||||
userCommon: userCommon,
|
||||
userRepo: userRepo,
|
||||
userRoleRelService: userRoleRelService,
|
||||
revisionService: revisionService,
|
||||
metaService: metaService,
|
||||
collectionCommon: collectionCommon,
|
||||
@@ -314,6 +318,7 @@ func (qs *QuestionService) AddQuestion(ctx context.Context, req *schema.Question
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = qs.questionRepo.UpdateSearch(ctx, question.ID)
|
||||
|
||||
revisionDTO := &schema.AddRevisionDTO{
|
||||
UserID: question.UserID,
|
||||
@@ -1237,15 +1242,31 @@ func (qs *QuestionService) SimilarQuestion(ctx context.Context, questionID strin
|
||||
func (qs *QuestionService) GetQuestionPage(ctx context.Context, req *schema.QuestionPageReq) (
|
||||
questions []*schema.QuestionPageResp, total int64, err error) {
|
||||
questions = make([]*schema.QuestionPageResp, 0)
|
||||
|
||||
// query by user role
|
||||
showHidden := false
|
||||
if req.LoginUserID != "" && req.UserIDBeSearched != "" {
|
||||
showHidden = req.LoginUserID == req.UserIDBeSearched
|
||||
if !showHidden {
|
||||
userRole, err := qs.userRoleRelService.GetUserRole(ctx, req.LoginUserID)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
showHidden = userRole == role.RoleAdminID || userRole == role.RoleModeratorID
|
||||
}
|
||||
}
|
||||
// query by tag condition
|
||||
var tagIDs = make([]string, 0)
|
||||
if len(req.Tag) > 0 {
|
||||
tagInfo, exist, err := qs.tagCommon.GetTagBySlugName(ctx, strings.ToLower(req.Tag))
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if exist {
|
||||
req.TagID = tagInfo.ID
|
||||
synTagIds, err := qs.tagCommon.GetTagIDsByMainTagID(ctx, tagInfo.ID)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
tagIDs = append(synTagIds, tagInfo.ID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1262,7 +1283,7 @@ func (qs *QuestionService) GetQuestionPage(ctx context.Context, req *schema.Ques
|
||||
}
|
||||
|
||||
questionList, total, err := qs.questionRepo.GetQuestionPage(ctx, req.Page, req.PageSize,
|
||||
req.UserIDBeSearched, req.TagID, req.OrderCond, req.InDays)
|
||||
tagIDs, req.UserIDBeSearched, req.OrderCond, req.InDays, showHidden)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ import (
|
||||
)
|
||||
|
||||
type SearchRepo interface {
|
||||
SearchContents(ctx context.Context, words []string, tagIDs []string, userID string, votes, page, size int, order string) (resp []*schema.SearchResult, total int64, err error)
|
||||
SearchQuestions(ctx context.Context, words []string, tagIDs []string, notAccepted bool, views, answers int, page, size int, order string) (resp []*schema.SearchResult, total int64, err error)
|
||||
SearchAnswers(ctx context.Context, words []string, tagIDs []string, accepted bool, questionID string, page, size int, order string) (resp []*schema.SearchResult, total int64, err error)
|
||||
SearchContents(ctx context.Context, words []string, tagIDs [][]string, userID string, votes, page, size int, order string) (resp []*schema.SearchResult, total int64, err error)
|
||||
SearchQuestions(ctx context.Context, words []string, tagIDs [][]string, notAccepted bool, views, answers int, page, size int, order string) (resp []*schema.SearchResult, total int64, err error)
|
||||
SearchAnswers(ctx context.Context, words []string, tagIDs [][]string, accepted bool, questionID string, page, size int, order string) (resp []*schema.SearchResult, total int64, err error)
|
||||
ParseSearchPluginResult(ctx context.Context, sres []plugin.SearchResult) (resp []*schema.SearchResult, err error)
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ func (sp *SearchParser) ParseStructure(ctx context.Context, dto *schema.SearchDT
|
||||
}
|
||||
|
||||
// parseTags parse search tags, return tag ids array
|
||||
func (sp *SearchParser) parseTags(ctx context.Context, query *string) (tags []string) {
|
||||
func (sp *SearchParser) parseTags(ctx context.Context, query *string) (tags [][]string) {
|
||||
var (
|
||||
// expire tag pattern
|
||||
exprTag = `\[(.*?)\]`
|
||||
@@ -119,17 +119,25 @@ func (sp *SearchParser) parseTags(ctx context.Context, query *string) (tags []st
|
||||
return
|
||||
}
|
||||
|
||||
tags = []string{}
|
||||
tags = make([][]string, 0)
|
||||
for _, item := range res {
|
||||
tagGroup := make([]string, 0)
|
||||
tag, exists, err := sp.tagCommonService.GetTagBySlugName(ctx, item[1])
|
||||
if err != nil || !exists {
|
||||
continue
|
||||
}
|
||||
tagGroup = append(tagGroup, tag.ID)
|
||||
if tag.MainTagID > 0 {
|
||||
tags = append(tags, fmt.Sprintf("%d", tag.MainTagID))
|
||||
} else {
|
||||
tags = append(tags, tag.ID)
|
||||
tagGroup = append(tagGroup, fmt.Sprintf("%d", tag.MainTagID))
|
||||
}
|
||||
synIDs, err := sp.tagCommonService.GetTagIDsByMainTagID(ctx, tag.ID)
|
||||
if err != nil || !exists {
|
||||
continue
|
||||
}
|
||||
tagGroup = append(tagGroup, tag.ID)
|
||||
tagGroup = append(tagGroup, synIDs...)
|
||||
tagGroup = converter.UniqueArray(tagGroup)
|
||||
tags = append(tags, tagGroup)
|
||||
}
|
||||
|
||||
// limit maximum 5 tags
|
||||
|
||||
@@ -60,6 +60,7 @@ type TagRepo interface {
|
||||
MustGetTagByNameOrID(ctx context.Context, tagID, slugName string) (tag *entity.Tag, exist bool, err error)
|
||||
UpdateTagSynonym(ctx context.Context, tagSlugNameList []string, mainTagID int64, mainTagSlugName string) (err error)
|
||||
GetTagSynonymCount(ctx context.Context, tagID string) (count int64, err error)
|
||||
GetIDsByMainTagId(ctx context.Context, mainTagID string) (tagIDs []string, err error)
|
||||
GetTagList(ctx context.Context, tag *entity.Tag) (tagList []*entity.Tag, err error)
|
||||
}
|
||||
|
||||
@@ -364,6 +365,12 @@ func (ts *TagCommonService) GetTagByID(ctx context.Context, tagID string) (tag *
|
||||
return
|
||||
}
|
||||
|
||||
// GetTagIDsByMainTagID get object tag
|
||||
func (ts *TagCommonService) GetTagIDsByMainTagID(ctx context.Context, tagID string) (tagIDs []string, err error) {
|
||||
tagIDs, err = ts.tagRepo.GetIDsByMainTagId(ctx, tagID)
|
||||
return
|
||||
}
|
||||
|
||||
// GetTagBySlugName get object tag
|
||||
func (ts *TagCommonService) GetTagBySlugName(ctx context.Context, slugName string) (tag *entity.Tag, exist bool, err error) {
|
||||
tag, exist, err = ts.tagCommonRepo.GetTagBySlugName(ctx, slugName)
|
||||
|
||||
@@ -153,6 +153,7 @@ func (us *UserCommon) FormatUserBasicInfo(ctx context.Context, userInfo *entity.
|
||||
userBasicInfo.DisplayName = userInfo.DisplayName
|
||||
userBasicInfo.Website = userInfo.Website
|
||||
userBasicInfo.Location = userInfo.Location
|
||||
userBasicInfo.Language = userInfo.Language
|
||||
userBasicInfo.Status = constant.ConvertUserStatus(userInfo.Status, userInfo.MailStatus)
|
||||
if userBasicInfo.Status == constant.UserDeleted {
|
||||
userBasicInfo.Avatar = ""
|
||||
|
||||
@@ -49,6 +49,7 @@ type UserExternalLoginRepo interface {
|
||||
AddUserExternalLogin(ctx context.Context, user *entity.UserExternalLogin) (err error)
|
||||
UpdateInfo(ctx context.Context, userInfo *entity.UserExternalLogin) (err error)
|
||||
GetByExternalID(ctx context.Context, provider, externalID string) (userInfo *entity.UserExternalLogin, exist bool, err error)
|
||||
GetByUserID(ctx context.Context, provider, userID string) (userInfo *entity.UserExternalLogin, exist bool, err error)
|
||||
GetUserExternalLoginList(ctx context.Context, userID string) (resp []*entity.UserExternalLogin, err error)
|
||||
DeleteUserExternalLogin(ctx context.Context, userID, externalID string) (err error)
|
||||
SetCacheUserExternalLoginInfo(ctx context.Context, key string, info *schema.ExternalLoginUserInfoCache) (err error)
|
||||
|
||||
@@ -96,7 +96,9 @@ func (us *UserNotificationConfigService) SetDefaultUserNotificationConfig(ctx co
|
||||
}
|
||||
|
||||
func (us *UserNotificationConfigService) convertToEntity(ctx context.Context, userID string,
|
||||
source constant.NotificationSource, channels schema.NotificationChannels) (c *entity.UserNotificationConfig) {
|
||||
source constant.NotificationSource, channel schema.NotificationChannelConfig) (c *entity.UserNotificationConfig) {
|
||||
var channels schema.NotificationChannels
|
||||
channels = append(channels, &channel)
|
||||
c = &entity.UserNotificationConfig{
|
||||
UserID: userID,
|
||||
Source: string(source),
|
||||
@@ -110,17 +112,3 @@ func (us *UserNotificationConfigService) convertToEntity(ctx context.Context, us
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (us *UserNotificationConfigService) CheckEnable(
|
||||
ctx context.Context, userID string, source constant.NotificationSource,
|
||||
channel constant.NotificationChannelKey) (enable bool, err error) {
|
||||
conf, exist, err := us.userNotificationConfigRepo.GetByUserIDAndSource(ctx, userID, source)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !exist {
|
||||
return false, nil
|
||||
}
|
||||
notificationChannels := schema.NewNotificationChannelsFormJson(conf.Channels)
|
||||
return notificationChannels.CheckEnable(channel), nil
|
||||
}
|
||||
|
||||
@@ -32,3 +32,15 @@ func ArrayNotInArray(original []string, search []string) []string {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func UniqueArray[T comparable](input []T) []T {
|
||||
result := make([]T, 0, len(input))
|
||||
seen := make(map[T]bool, len(input))
|
||||
for _, element := range input {
|
||||
if !seen[element] {
|
||||
result = append(result, element)
|
||||
seen[element] = true
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -58,3 +58,8 @@ func CommentURL(permalink int, siteUrl, questionID, title, answerID, commentID s
|
||||
}
|
||||
return QuestionURL(permalink, siteUrl, questionID, title) + "?commentId=" + commentID
|
||||
}
|
||||
|
||||
// UserURL get user url
|
||||
func UserURL(siteUrl, username string) string {
|
||||
return siteUrl + "/users/" + username
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package plugin
|
||||
|
||||
// NotificationType is the type of the notification
|
||||
type NotificationType string
|
||||
|
||||
const (
|
||||
NotificationUpdateQuestion NotificationType = "notification.action.update_question"
|
||||
NotificationAnswerTheQuestion NotificationType = "notification.action.answer_the_question"
|
||||
NotificationUpVotedTheQuestion NotificationType = "notification.action.up_voted_question"
|
||||
NotificationDownVotedTheQuestion NotificationType = "notification.action.down_voted_question"
|
||||
NotificationUpdateAnswer NotificationType = "notification.action.update_answer"
|
||||
NotificationAcceptAnswer NotificationType = "notification.action.accept_answer"
|
||||
NotificationUpVotedTheAnswer NotificationType = "notification.action.up_voted_answer"
|
||||
NotificationDownVotedTheAnswer NotificationType = "notification.action.down_voted_answer"
|
||||
NotificationCommentQuestion NotificationType = "notification.action.comment_question"
|
||||
NotificationCommentAnswer NotificationType = "notification.action.comment_answer"
|
||||
NotificationUpVotedTheComment NotificationType = "notification.action.up_voted_comment"
|
||||
NotificationReplyToYou NotificationType = "notification.action.reply_to_you"
|
||||
NotificationMentionYou NotificationType = "notification.action.mention_you"
|
||||
NotificationYourQuestionIsClosed NotificationType = "notification.action.your_question_is_closed"
|
||||
NotificationYourQuestionWasDeleted NotificationType = "notification.action.your_question_was_deleted"
|
||||
NotificationYourAnswerWasDeleted NotificationType = "notification.action.your_answer_was_deleted"
|
||||
NotificationYourCommentWasDeleted NotificationType = "notification.action.your_comment_was_deleted"
|
||||
NotificationInvitedYouToAnswer NotificationType = "notification.action.invited_you_to_answer"
|
||||
NotificationNewQuestion NotificationType = "notification.action.new_question"
|
||||
NotificationNewQuestionFollowedTag NotificationType = "notification.action.new_question_followed_tag"
|
||||
)
|
||||
|
||||
type Notification interface {
|
||||
Base
|
||||
|
||||
// Notify sends a notification to the user
|
||||
Notify(msg *NotificationMessage)
|
||||
}
|
||||
|
||||
type NotificationMessage struct {
|
||||
// the type of the notification
|
||||
Type NotificationType `json:"notification_type"`
|
||||
// the receiver user id
|
||||
ReceiverUserID string `json:"receiver_user_id"`
|
||||
// the receiver user using language
|
||||
ReceiverLang string `json:"receiver_lang"`
|
||||
// the receiver user external id (optional)
|
||||
ReceiverExternalID string `json:"receiver_external_id"`
|
||||
|
||||
// Who triggered the notification (optional, admin or system operation will not have this field)
|
||||
TriggerUserID string `json:"trigger_user_id"`
|
||||
// The trigger user's display name (optional, admin or system operation will not have this field)
|
||||
TriggerUserDisplayName string `json:"trigger_user_display_name"`
|
||||
// The trigger user's url (optional, admin or system operation will not have this field)
|
||||
TriggerUserUrl string `json:"trigger_user_url"`
|
||||
|
||||
// the question title
|
||||
QuestionTitle string `json:"question_title"`
|
||||
// the question url
|
||||
QuestionUrl string `json:"question_url"`
|
||||
// the question tags (comma separated, optional, only for new question notification)
|
||||
QuestionTags string `json:"tags"`
|
||||
|
||||
// the answer url (optional, only for new answer notification)
|
||||
AnswerUrl string `json:"answer_url"`
|
||||
// the comment url (optional, only for new comment notification)
|
||||
CommentUrl string `json:"comment_url"`
|
||||
}
|
||||
|
||||
var (
|
||||
// CallNotification is a function that calls all registered notification plugins
|
||||
CallNotification,
|
||||
registerNotification = MakePlugin[Notification](false)
|
||||
)
|
||||
@@ -21,6 +21,7 @@ package plugin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/segmentfault/pacman/i18n"
|
||||
|
||||
"github.com/apache/incubator-answer/internal/base/handler"
|
||||
"github.com/apache/incubator-answer/internal/base/translator"
|
||||
@@ -48,6 +49,10 @@ func Register(p Base) {
|
||||
registerConfig(p.(Config))
|
||||
}
|
||||
|
||||
if _, ok := p.(UserConfig); ok {
|
||||
registerUserConfig(p.(UserConfig))
|
||||
}
|
||||
|
||||
if _, ok := p.(Connector); ok {
|
||||
registerConnector(p.(Connector))
|
||||
}
|
||||
@@ -79,6 +84,10 @@ func Register(p Base) {
|
||||
if _, ok := p.(Search); ok {
|
||||
registerSearch(p.(Search))
|
||||
}
|
||||
|
||||
if _, ok := p.(Notification); ok {
|
||||
registerNotification(p.(Notification))
|
||||
}
|
||||
}
|
||||
|
||||
type Stack[T Base] struct {
|
||||
@@ -153,6 +162,11 @@ func Translate(ctx *GinContext, key string) string {
|
||||
return translator.Tr(handler.GetLang(ctx), key)
|
||||
}
|
||||
|
||||
// TranslateWithData translates the key to the language with data
|
||||
func TranslateWithData(lang i18n.Language, key string, data any) string {
|
||||
return translator.TrWithData(lang, key, data)
|
||||
}
|
||||
|
||||
// TranslateFn presents a generator of translated string.
|
||||
// We use it to delegate the translation work outside the plugin.
|
||||
type TranslateFn func(ctx *GinContext) string
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ type SearchBasicCond struct {
|
||||
// The keywords for search.
|
||||
Words []string
|
||||
// TagIDs is a list of tag IDs.
|
||||
TagIDs []string
|
||||
TagIDs [][]string
|
||||
// The object's owner user ID.
|
||||
UserID string
|
||||
// The order of the search result.
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package plugin
|
||||
|
||||
type UserConfig interface {
|
||||
Base
|
||||
|
||||
// UserConfigFields returns the list of config fields
|
||||
UserConfigFields() []ConfigField
|
||||
// UserConfigReceiver receives the config data, it calls when the config is saved or initialized.
|
||||
// We recommend to unmarshal the data to a struct, and then use the struct to do something.
|
||||
// The config is encoded in JSON format.
|
||||
// It depends on the definition of ConfigFields.
|
||||
UserConfigReceiver(userID string, config []byte) error
|
||||
}
|
||||
|
||||
var (
|
||||
// CallUserConfig is a function that calls all registered config plugins
|
||||
CallUserConfig,
|
||||
registerUserConfig = MakePlugin[UserConfig](false)
|
||||
getPluginUserConfigFn func(userID, pluginSlugName string) []byte
|
||||
)
|
||||
|
||||
// GetPluginUserConfig returns the user config of the given user id
|
||||
func GetPluginUserConfig(userID, pluginSlugName string) []byte {
|
||||
if getPluginUserConfigFn != nil {
|
||||
return getPluginUserConfigFn(userID, pluginSlugName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterGetPluginUserConfigFunc registers a function to get the user config of the given user id
|
||||
func RegisterGetPluginUserConfigFunc(fn func(userID, pluginSlugName string) []byte) {
|
||||
getPluginUserConfigFn = fn
|
||||
}
|
||||
Reference in New Issue
Block a user