diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go index 4928a122b..c20aa2cfd 100644 --- a/cmd/wire_gen.go +++ b/cmd/wire_gen.go @@ -40,6 +40,9 @@ import ( "github.com/apache/incubator-answer/internal/repo/activity_common" "github.com/apache/incubator-answer/internal/repo/answer" "github.com/apache/incubator-answer/internal/repo/auth" + "github.com/apache/incubator-answer/internal/repo/badge" + "github.com/apache/incubator-answer/internal/repo/badge_award" + "github.com/apache/incubator-answer/internal/repo/badge_group" "github.com/apache/incubator-answer/internal/repo/captcha" "github.com/apache/incubator-answer/internal/repo/collection" "github.com/apache/incubator-answer/internal/repo/comment" @@ -71,6 +74,7 @@ import ( "github.com/apache/incubator-answer/internal/service/activity_queue" "github.com/apache/incubator-answer/internal/service/answer_common" auth2 "github.com/apache/incubator-answer/internal/service/auth" + badge2 "github.com/apache/incubator-answer/internal/service/badge" collection2 "github.com/apache/incubator-answer/internal/service/collection" "github.com/apache/incubator-answer/internal/service/collection_common" comment2 "github.com/apache/incubator-answer/internal/service/comment" @@ -253,7 +257,12 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database, reviewController := controller.NewReviewController(reviewService, rankService, captchaService) metaService := meta2.NewMetaService(metaCommonService, userCommon, answerRepo, questionRepo) metaController := controller.NewMetaController(metaService) - answerAPIRouter := router.NewAnswerAPIRouter(langController, userController, commentController, reportController, voteController, tagController, followController, collectionController, questionController, answerController, searchController, revisionController, rankController, userAdminController, reasonController, themeController, siteInfoController, controllerSiteInfoController, notificationController, dashboardController, uploadController, activityController, roleController, pluginController, permissionController, userPluginController, reviewController, metaController) + badgeRepo := badge.NewBadgeRepo(dataData, uniqueIDRepo) + badgeGroupRepo := badge_group.NewBadgeGroupRepo(dataData, uniqueIDRepo) + badgeAwardRepo := badge_award.NewBadgeAwardRepo(dataData, uniqueIDRepo) + badgeService := badge2.NewBadgeService(badgeRepo, badgeGroupRepo, badgeAwardRepo) + badgeController := controller.NewBadgeController(badgeService) + answerAPIRouter := router.NewAnswerAPIRouter(langController, userController, commentController, reportController, voteController, tagController, followController, collectionController, questionController, answerController, searchController, revisionController, rankController, userAdminController, reasonController, themeController, siteInfoController, controllerSiteInfoController, notificationController, dashboardController, uploadController, activityController, roleController, pluginController, permissionController, userPluginController, reviewController, metaController, badgeController) swaggerRouter := router.NewSwaggerRouter(swaggerConf) uiRouter := router.NewUIRouter(controllerSiteInfoController, siteInfoCommonService) authUserMiddleware := middleware.NewAuthUserMiddleware(authService, siteInfoCommonService) diff --git a/docs/docs.go b/docs/docs.go index cf1264d14..6fc76693f 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -2239,6 +2239,49 @@ const docTemplate = `{ } } }, + "/answer/api/v1/badges": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "list all badges group by group", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-badge" + ], + "summary": "list all badges group by group", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetBadgeListResp" + } + } + } + } + ] + } + } + } + } + }, "/answer/api/v1/collection/switch": { "post": { "security": [ @@ -4410,7 +4453,7 @@ const docTemplate = `{ "data": { "type": "array", "items": { - "$ref": "#/definitions/schema.GetTagResp" + "$ref": "#/definitions/schema.GetTagBasicResp" } } } @@ -5380,7 +5423,7 @@ const docTemplate = `{ }, "/answer/api/v1/tags": { "get": { - "description": "get tags list", + "description": "get tags list by slug name", "produces": [ "application/json" ], @@ -5404,7 +5447,22 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/handler.RespBody" + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetTagBasicResp" + } + } + } + } + ] } } } @@ -7327,6 +7385,26 @@ const docTemplate = `{ } } }, + "schema.BadgeListInfo": { + "type": "object", + "properties": { + "award_count": { + "type": "integer" + }, + "earned": { + "type": "boolean" + }, + "icon": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, "schema.CloseQuestionReq": { "type": "object", "required": [ @@ -7578,6 +7656,20 @@ const docTemplate = `{ } } }, + "schema.GetBadgeListResp": { + "type": "object", + "properties": { + "badges": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.BadgeListInfo" + } + }, + "group_name": { + "type": "string" + } + } + }, "schema.GetCommentPersonalWithPageResp": { "type": "object", "properties": { @@ -8242,6 +8334,23 @@ const docTemplate = `{ } } }, + "schema.GetTagBasicResp": { + "type": "object", + "properties": { + "display_name": { + "type": "string" + }, + "recommend": { + "type": "boolean" + }, + "reserved": { + "type": "boolean" + }, + "slug_name": { + "type": "string" + } + } + }, "schema.GetTagPageResp": { "type": "object", "properties": { @@ -9819,7 +9928,7 @@ const docTemplate = `{ "recommend_tags": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/schema.SiteWriteTag" } }, "required_tag": { @@ -9828,7 +9937,7 @@ const docTemplate = `{ "reserved_tags": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/schema.SiteWriteTag" } }, "restrict_answer": { @@ -9842,7 +9951,7 @@ const docTemplate = `{ "recommend_tags": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/schema.SiteWriteTag" } }, "required_tag": { @@ -9851,7 +9960,7 @@ const docTemplate = `{ "reserved_tags": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/schema.SiteWriteTag" } }, "restrict_answer": { @@ -9859,6 +9968,20 @@ const docTemplate = `{ } } }, + "schema.SiteWriteTag": { + "type": "object", + "required": [ + "slug_name" + ], + "properties": { + "display_name": { + "type": "string" + }, + "slug_name": { + "type": "string" + } + } + }, "schema.TagItem": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index 1e93d2b08..45936363f 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -2209,6 +2209,49 @@ } } }, + "/answer/api/v1/badges": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "list all badges group by group", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "api-badge" + ], + "summary": "list all badges group by group", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetBadgeListResp" + } + } + } + } + ] + } + } + } + } + }, "/answer/api/v1/collection/switch": { "post": { "security": [ @@ -4380,7 +4423,7 @@ "data": { "type": "array", "items": { - "$ref": "#/definitions/schema.GetTagResp" + "$ref": "#/definitions/schema.GetTagBasicResp" } } } @@ -5350,7 +5393,7 @@ }, "/answer/api/v1/tags": { "get": { - "description": "get tags list", + "description": "get tags list by slug name", "produces": [ "application/json" ], @@ -5374,7 +5417,22 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/handler.RespBody" + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.GetTagBasicResp" + } + } + } + } + ] } } } @@ -7297,6 +7355,26 @@ } } }, + "schema.BadgeListInfo": { + "type": "object", + "properties": { + "award_count": { + "type": "integer" + }, + "earned": { + "type": "boolean" + }, + "icon": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, "schema.CloseQuestionReq": { "type": "object", "required": [ @@ -7548,6 +7626,20 @@ } } }, + "schema.GetBadgeListResp": { + "type": "object", + "properties": { + "badges": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.BadgeListInfo" + } + }, + "group_name": { + "type": "string" + } + } + }, "schema.GetCommentPersonalWithPageResp": { "type": "object", "properties": { @@ -8212,6 +8304,23 @@ } } }, + "schema.GetTagBasicResp": { + "type": "object", + "properties": { + "display_name": { + "type": "string" + }, + "recommend": { + "type": "boolean" + }, + "reserved": { + "type": "boolean" + }, + "slug_name": { + "type": "string" + } + } + }, "schema.GetTagPageResp": { "type": "object", "properties": { @@ -8809,7 +8918,7 @@ "enum": [ "newest", "active", - "frequent", + "hot", "score", "unanswered" ] @@ -9789,7 +9898,7 @@ "recommend_tags": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/schema.SiteWriteTag" } }, "required_tag": { @@ -9798,7 +9907,7 @@ "reserved_tags": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/schema.SiteWriteTag" } }, "restrict_answer": { @@ -9812,7 +9921,7 @@ "recommend_tags": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/schema.SiteWriteTag" } }, "required_tag": { @@ -9821,7 +9930,7 @@ "reserved_tags": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/schema.SiteWriteTag" } }, "restrict_answer": { @@ -9829,6 +9938,20 @@ } } }, + "schema.SiteWriteTag": { + "type": "object", + "required": [ + "slug_name" + ], + "properties": { + "display_name": { + "type": "string" + }, + "slug_name": { + "type": "string" + } + } + }, "schema.TagItem": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 93f8116af..c91c987e6 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -342,6 +342,19 @@ definitions: maxLength: 100 type: string type: object + schema.BadgeListInfo: + properties: + award_count: + type: integer + earned: + type: boolean + icon: + type: string + id: + type: string + name: + type: string + type: object schema.CloseQuestionReq: properties: close_msg: @@ -513,6 +526,15 @@ definitions: description: if user is followed object will be true,otherwise false type: boolean type: object + schema.GetBadgeListResp: + properties: + badges: + items: + $ref: '#/definitions/schema.BadgeListInfo' + type: array + group_name: + type: string + type: object schema.GetCommentPersonalWithPageResp: properties: answer_id: @@ -983,6 +1005,17 @@ definitions: terms_of_service_parsed_text: type: string type: object + schema.GetTagBasicResp: + properties: + display_name: + type: string + recommend: + type: boolean + reserved: + type: boolean + slug_name: + type: string + type: object schema.GetTagPageResp: properties: created_at: @@ -1401,7 +1434,7 @@ definitions: enum: - newest - active - - frequent + - hot - score - unanswered type: string @@ -2071,13 +2104,13 @@ definitions: properties: recommend_tags: items: - type: string + $ref: '#/definitions/schema.SiteWriteTag' type: array required_tag: type: boolean reserved_tags: items: - type: string + $ref: '#/definitions/schema.SiteWriteTag' type: array restrict_answer: type: boolean @@ -2086,17 +2119,26 @@ definitions: properties: recommend_tags: items: - type: string + $ref: '#/definitions/schema.SiteWriteTag' type: array required_tag: type: boolean reserved_tags: items: - type: string + $ref: '#/definitions/schema.SiteWriteTag' type: array restrict_answer: type: boolean type: object + schema.SiteWriteTag: + properties: + display_name: + type: string + slug_name: + type: string + required: + - slug_name + type: object schema.TagItem: properties: display_name: @@ -4063,6 +4105,30 @@ paths: summary: recover answer tags: - Answer + /answer/api/v1/badges: + get: + consumes: + - application/json + description: list all badges group by group + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + items: + $ref: '#/definitions/schema.GetBadgeListResp' + type: array + type: object + security: + - ApiKeyAuth: [] + summary: list all badges group by group + tags: + - api-badge /answer/api/v1/collection/switch: post: consumes: @@ -5382,7 +5448,7 @@ paths: - properties: data: items: - $ref: '#/definitions/schema.GetTagResp' + $ref: '#/definitions/schema.GetTagBasicResp' type: array type: object security: @@ -5965,7 +6031,7 @@ paths: - Tag /answer/api/v1/tags: get: - description: get tags list + description: get tags list by slug name parameters: - collectionFormat: csv description: string collection @@ -5980,7 +6046,14 @@ paths: "200": description: OK schema: - $ref: '#/definitions/handler.RespBody' + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + items: + $ref: '#/definitions/schema.GetTagBasicResp' + type: array + type: object summary: get tags list tags: - Tag diff --git a/internal/controller/badge_controller.go b/internal/controller/badge_controller.go new file mode 100644 index 000000000..ebc534ced --- /dev/null +++ b/internal/controller/badge_controller.go @@ -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 controller + +import ( + "github.com/apache/incubator-answer/internal/base/handler" + "github.com/apache/incubator-answer/internal/base/middleware" + "github.com/apache/incubator-answer/internal/service/badge" + "github.com/gin-gonic/gin" +) + +type BadgeController struct { + badgeService *badge.BadgeService +} + +func NewBadgeController(badgeService *badge.BadgeService) *BadgeController { + return &BadgeController{ + badgeService: badgeService, + } +} + +// GetBadgeList list all badges +// @Summary list all badges group by group +// @Description list all badges group by group +// @Tags api-badge +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Success 200 {object} handler.RespBody{data=[]schema.GetBadgeListResp} +// @Router /answer/api/v1/badges [get] +func (b *BadgeController) GetBadgeList(ctx *gin.Context) { + userID := middleware.GetLoginUserIDFromContext(ctx) + resp, err := b.badgeService.ListByGroup(ctx, userID) + handler.HandleResponse(ctx, err, resp) +} diff --git a/internal/controller/controller.go b/internal/controller/controller.go index 9eb64c585..8fad918a1 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -51,4 +51,5 @@ var ProviderSetController = wire.NewSet( NewCaptchaController, NewMetaController, NewEmbedController, + NewBadgeController, ) diff --git a/internal/entity/badge.go b/internal/entity/badge.go deleted file mode 100644 index 976bb47d0..000000000 --- a/internal/entity/badge.go +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 - -import "time" - -const ( - BadgeStatusActive = 1 - BadgeStatusDeleted = 10 - BadgeStatusInactive = 11 - - BadgeLevelBronze = 1 - BadgeLevelSilver = 2 - BadgeLevelGold = 3 - - BadgeSingleAward = 1 - BadgeMultiAward = 2 -) - -// Badge badge -type Badge struct { - ID string `json:"id" xorm:"id"` - CreatedAt time.Time `json:"created_at" xorm:"created not null default CURRENT_TIMESTAMP TIMESTAMP created_at"` - UpdatedAt time.Time `json:"updated_at" xorm:"updated not null default CURRENT_TIMESTAMP TIMESTAMP updated_at"` - Name string `json:"name" xorm:"not null default '' VARCHAR(256) name"` - Icon string `json:"icon" xorm:"not null default '' VARCHAR(1024) icon"` - AwardCount int64 `json:"award_count" xorm:"not null default 0 INT(11) award_count"` - Description string `json:"description" xorm:"not null default '' MEDIUMTEXT description"` - Status int8 `json:"status" xorm:"not null default 1 INT(11) status"` - BadgeGroupId int64 `json:"badge_group_id" xorm:"not null default 0 BIGINT(20) badge_group_id"` - Level int64 `json:"level" xorm:"not null default 1 TINYINT(4) level"` - Single int8 `json:"single" xorm:"not null default 1 TINYINT(4) single"` - Collect string `json:"collect" xorm:"not null default '' VARCHAR(64) collect"` - Handler string `json:"handler" xorm:"not null default '' VARCHAR(64) handler"` - Param string `json:"param" xorm:"not null default '' VARCHAR(128) param"` -} - -// TableName badge table name -func (Badge) TableName() string { - return "badge" -} diff --git a/internal/entity/badge_award.go b/internal/entity/badge_award_entity.go similarity index 88% rename from internal/entity/badge_award.go rename to internal/entity/badge_award_entity.go index 235d369c2..a852f6bd3 100644 --- a/internal/entity/badge_award.go +++ b/internal/entity/badge_award_entity.go @@ -37,3 +37,13 @@ type BadgeAward struct { func (BadgeAward) TableName() string { return "badge_award" } + +type BadgeEarnedCount struct { + BadgeID string `xorm:"badge_id"` + EarnedCount int `xorm:"earned_count"` +} + +// TableName badge_award table name +func (BadgeEarnedCount) TableName() string { + return "badge_award" +} diff --git a/internal/entity/badge_entity.go b/internal/entity/badge_entity.go new file mode 100644 index 000000000..da6cd4dd9 --- /dev/null +++ b/internal/entity/badge_entity.go @@ -0,0 +1,60 @@ +/* + * 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 + +import "time" + +type BadgeLevel int + +const ( + BadgeStatusActive = 1 + BadgeStatusDeleted = 10 + BadgeStatusInactive = 11 + + BadgeLevelBronze BadgeLevel = 1 + BadgeLevelSilver BadgeLevel = 2 + BadgeLevelGold BadgeLevel = 3 + + BadgeSingleAward = 1 + BadgeMultiAward = 2 +) + +// Badge badge +type Badge struct { + ID string `json:"id" xorm:"id"` + CreatedAt time.Time `json:"created_at" xorm:"created not null default CURRENT_TIMESTAMP TIMESTAMP created_at"` + UpdatedAt time.Time `json:"updated_at" xorm:"updated not null default CURRENT_TIMESTAMP TIMESTAMP updated_at"` + Name string `json:"name" xorm:"not null default '' VARCHAR(256) name"` + Icon string `json:"icon" xorm:"not null default '' VARCHAR(1024) icon"` + AwardCount int `json:"award_count" xorm:"not null default 0 INT(11) award_count"` + Description string `json:"description" xorm:"not null default '' MEDIUMTEXT description"` + Status int8 `json:"status" xorm:"not null default 1 INT(11) status"` + BadgeGroupId int64 `json:"badge_group_id" xorm:"not null default 0 BIGINT(20) badge_group_id"` + Level BadgeLevel `json:"level" xorm:"not null default 1 TINYINT(4) level"` + Single int8 `json:"single" xorm:"not null default 1 TINYINT(4) single"` + Collect string `json:"collect" xorm:"not null default '' VARCHAR(64) collect"` + Handler string `json:"handler" xorm:"not null default '' VARCHAR(64) handler"` + Param string `json:"param" xorm:"not null default '' VARCHAR(128) param"` +} + +// TableName badge table name +func (Badge) TableName() string { + return "badge" +} diff --git a/internal/entity/badge_group.go b/internal/entity/badge_group_entity.go similarity index 100% rename from internal/entity/badge_group.go rename to internal/entity/badge_group_entity.go diff --git a/internal/repo/badge/badge_repo.go b/internal/repo/badge/badge_repo.go new file mode 100644 index 000000000..b4cd72f1c --- /dev/null +++ b/internal/repo/badge/badge_repo.go @@ -0,0 +1,76 @@ +/* + * 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 badge + +import ( + "context" + "github.com/apache/incubator-answer/internal/base/data" + "github.com/apache/incubator-answer/internal/entity" + "github.com/apache/incubator-answer/internal/service/badge" + "github.com/apache/incubator-answer/internal/service/unique" +) + +type badgeRepo struct { + data *data.Data + uniqueIDRepo unique.UniqueIDRepo +} + +// NewBadgeRepo creates a new badge repository +func NewBadgeRepo(data *data.Data, uniqueIDRepo unique.UniqueIDRepo) badge.BadgeRepo { + return &badgeRepo{ + data: data, + uniqueIDRepo: uniqueIDRepo, + } +} + +// ListByLevel returns a list of badges by level +func (r *badgeRepo) ListByLevel(ctx context.Context, level entity.BadgeLevel) (badges []*entity.Badge, err error) { + badges = make([]*entity.Badge, 0) + err = r.data.DB.Context(ctx).Where("level = ?", level).Find(&badges) + return +} + +// ListByGroup returns a list of badges by group +func (r *badgeRepo) ListByGroup(ctx context.Context, groupID int64) (badges []*entity.Badge, err error) { + badges = make([]*entity.Badge, 0) + err = r.data.DB.Context(ctx).Where("group_id = ?", groupID).Find(&badges) + return +} + +// ListByLevelAndGroup returns a list of badges by level and group +func (r *badgeRepo) ListByLevelAndGroup(ctx context.Context, level entity.BadgeLevel, groupID int64) (badges []*entity.Badge, err error) { + badges = make([]*entity.Badge, 0) + err = r.data.DB.Context(ctx).Where("level = ? AND group_id = ?", level, groupID).Find(&badges) + return +} + +// ListActivated returns a list of activated badges +func (r *badgeRepo) ListActivated(ctx context.Context) (badges []*entity.Badge, err error) { + badges = make([]*entity.Badge, 0) + err = r.data.DB.Context(ctx).Where("status = ?", entity.BadgeStatusActive).Find(&badges) + return +} + +// ListInactivated returns a list of inactivated badges +func (r *badgeRepo) ListInactivated(ctx context.Context) (badges []*entity.Badge, err error) { + badges = make([]*entity.Badge, 0) + err = r.data.DB.Context(ctx).Where("status = ?", entity.BadgeStatusInactive).Find(&badges) + return +} diff --git a/internal/repo/badge_award/badge_award_repo.go b/internal/repo/badge_award/badge_award_repo.go new file mode 100644 index 000000000..b6f0e95eb --- /dev/null +++ b/internal/repo/badge_award/badge_award_repo.go @@ -0,0 +1,106 @@ +/* + * 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 badge_award + +import ( + "context" + "github.com/apache/incubator-answer/internal/base/data" + "github.com/apache/incubator-answer/internal/entity" + "github.com/apache/incubator-answer/internal/service/badge_award" + "github.com/apache/incubator-answer/internal/service/unique" + "time" +) + +type badgeAwardRepo struct { + data *data.Data + uniqueIDRepo unique.UniqueIDRepo +} + +func NewBadgeAwardRepo(data *data.Data, uniqueIDRepo unique.UniqueIDRepo) badge_award.BadgeAwardRepo { + return &badgeAwardRepo{ + data: data, + uniqueIDRepo: uniqueIDRepo, + } +} + +func (r *badgeAwardRepo) Award(ctx context.Context, badgeID string, userID string, objectID string, force bool, createdAt time.Time) { + return +} +func (r *badgeAwardRepo) CheckIsAward(ctx context.Context, badgeID string, userID string, objectID string) (isAward bool) { + return +} +func (r *badgeAwardRepo) CountByUserIdAndBadgeLevel(ctx context.Context, userID string, badgeLevel entity.BadgeLevel) (awardCount int64) { + return +} +func (r *badgeAwardRepo) CountByUserId(ctx context.Context, userID string) (awardCount int64) { + return +} +func (r *badgeAwardRepo) CountByUserIdAndBadgeId(ctx context.Context, userID string, badgeID string) (awardCount int64) { + return +} +func (r *badgeAwardRepo) CountByObjectId(ctx context.Context, objectID string) (awardCount int64) { + return +} +func (r *badgeAwardRepo) CountByObjectIdAndBadgeId(ctx context.Context, objectID string, badgeID string) (awardCount int64) { + return +} +func (r *badgeAwardRepo) CountBadgesByUserIdAndObjectId(ctx context.Context, userID string, objectID string, badgeID string) (awardCount int64) { + return +} +func (r *badgeAwardRepo) SumUserEarnedGroupByBadgeID(ctx context.Context, userID string) (earnedCounts []*entity.BadgeEarnedCount, err error) { + err = r.data.DB.Context(ctx).Select("badge_id, count(`id`) AS earned_count").Where("user_id = ?", userID).GroupBy("badge_id").Find(&earnedCounts) + return +} +func (r *badgeAwardRepo) ListAllByUserId(ctx context.Context, userID string) (badgeAwards []*entity.BadgeAward) { + return +} +func (r *badgeAwardRepo) ListPagedByBadgeId(ctx context.Context, badgeID string, page int64, pageSize int64) (badgeAwards []*entity.BadgeAward, total int64) { + return +} +func (r *badgeAwardRepo) ListPagedByBadgeIdAndUserId(ctx context.Context, badgeID string, userID string, page int64, pageSize int64) (badgeAwards []*entity.BadgeAward, total int64) { + return +} +func (r *badgeAwardRepo) ListPagedByObjectId(ctx context.Context, badgeID string, objectID string, page int64, pageSize int64) (badgeAwards []*entity.BadgeAward, total int64) { + return +} +func (r *badgeAwardRepo) ListPagedByObjectIdAndUserId(ctx context.Context, badgeID string, objectID string, userID string, page int64, pageSize int64) (badgeAwards []*entity.BadgeAward, total int64) { + return +} +func (r *badgeAwardRepo) ListTagPagedByBadgeId(ctx context.Context, badgeIDs []int64, page int64, pageSize int64, filterUserID int64) (badgeAwards []*entity.BadgeAward, total int64) { + return +} +func (r *badgeAwardRepo) ListTagPagedByBadgeIdAndUserId(ctx context.Context, badgeIDs []int64, userID string, page int64, pageSize int64) (badgeAwards []*entity.BadgeAward, total int64) { + return +} +func (r *badgeAwardRepo) ListPagedLatest(ctx context.Context, page int64, pageSize int64) (badgeAwards []*entity.BadgeAward, total int64) { + return +} +func (r *badgeAwardRepo) ListNewestEarnedByLevel(ctx context.Context, userID string, level entity.BadgeLevel, num int64) (badgeAwards []*entity.BadgeAward, total int64) { + return +} +func (r *badgeAwardRepo) ListNewestByUserIdAndLevel(ctx context.Context, userID string, level int64, page int64, pageSize int64) (badgeAwards []*entity.BadgeAward, total int64) { + return +} +func (r *badgeAwardRepo) GetByUserIdAndBadgeId(ctx context.Context, userID string, badgeID string) (badgeAward *entity.BadgeAward) { + return +} +func (r *badgeAwardRepo) GetByUserIdAndBadgeIdAndObjectId(ctx context.Context, userID string, badgeID string, objectID string) (badgeAward *entity.BadgeAward) { + return +} diff --git a/internal/repo/badge_group/badge_group_repo.go b/internal/repo/badge_group/badge_group_repo.go new file mode 100644 index 000000000..7dfe90fc3 --- /dev/null +++ b/internal/repo/badge_group/badge_group_repo.go @@ -0,0 +1,50 @@ +/* + * 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 badge_group + +import ( + "context" + "github.com/apache/incubator-answer/internal/base/data" + "github.com/apache/incubator-answer/internal/entity" + "github.com/apache/incubator-answer/internal/service/badge_group" + "github.com/apache/incubator-answer/internal/service/unique" +) + +type badgeGroupRepo struct { + data *data.Data + uniqueIDRepo unique.UniqueIDRepo +} + +func NewBadgeGroupRepo(data *data.Data, uniqueIDRepo unique.UniqueIDRepo) badge_group.BadgeGroupRepo { + return &badgeGroupRepo{ + data: data, + uniqueIDRepo: uniqueIDRepo, + } +} + +func (r *badgeGroupRepo) ListGroups(ctx context.Context) (groups []*entity.BadgeGroup, err error) { + groups = make([]*entity.BadgeGroup, 0) + err = r.data.DB.Context(ctx).Find(&groups) + return +} + +func (r *badgeGroupRepo) AddGroup(ctx context.Context, group *entity.BadgeGroup) (err error) { + return +} diff --git a/internal/repo/provider.go b/internal/repo/provider.go index 3a517120e..ee309e027 100644 --- a/internal/repo/provider.go +++ b/internal/repo/provider.go @@ -25,6 +25,9 @@ import ( "github.com/apache/incubator-answer/internal/repo/activity_common" "github.com/apache/incubator-answer/internal/repo/answer" "github.com/apache/incubator-answer/internal/repo/auth" + "github.com/apache/incubator-answer/internal/repo/badge" + "github.com/apache/incubator-answer/internal/repo/badge_award" + "github.com/apache/incubator-answer/internal/repo/badge_group" "github.com/apache/incubator-answer/internal/repo/captcha" "github.com/apache/incubator-answer/internal/repo/collection" "github.com/apache/incubator-answer/internal/repo/comment" @@ -100,4 +103,7 @@ var ProviderSetRepo = wire.NewSet( limit.NewRateLimitRepo, plugin_config.NewPluginUserConfigRepo, review.NewReviewRepo, + badge.NewBadgeRepo, + badge_group.NewBadgeGroupRepo, + badge_award.NewBadgeAwardRepo, ) diff --git a/internal/router/answer_api_router.go b/internal/router/answer_api_router.go index 328541868..dadf2a853 100644 --- a/internal/router/answer_api_router.go +++ b/internal/router/answer_api_router.go @@ -55,6 +55,7 @@ type AnswerAPIRouter struct { userPluginController *controller.UserPluginController reviewController *controller.ReviewController metaController *controller.MetaController + badgeController *controller.BadgeController } func NewAnswerAPIRouter( @@ -86,6 +87,7 @@ func NewAnswerAPIRouter( userPluginController *controller.UserPluginController, reviewController *controller.ReviewController, metaController *controller.MetaController, + badgeController *controller.BadgeController, ) *AnswerAPIRouter { return &AnswerAPIRouter{ langController: langController, @@ -116,6 +118,7 @@ func NewAnswerAPIRouter( userPluginController: userPluginController, reviewController: reviewController, metaController: metaController, + badgeController: badgeController, } } @@ -187,6 +190,9 @@ func (a *AnswerAPIRouter) RegisterUnAuthAnswerAPIRouter(r *gin.RouterGroup) { // reaction r.GET("/meta/reaction", a.metaController.GetReaction) + + // badges + r.GET("/badges", a.badgeController.GetBadgeList) } func (a *AnswerAPIRouter) RegisterAuthUserWithAnyStatusAnswerAPIRouter(r *gin.RouterGroup) { diff --git a/internal/schema/badge.go b/internal/schema/badge.go new file mode 100644 index 000000000..c52a235b4 --- /dev/null +++ b/internal/schema/badge.go @@ -0,0 +1,34 @@ +/* + * 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 + +// BadgeListInfo get badge list response +type BadgeListInfo struct { + ID string `json:"id" ` + Name string `json:"name" ` + Icon string `json:"icon" ` + AwardCount int `json:"award_count" ` + Earned bool `json:"earned" ` +} + +type GetBadgeListResp struct { + Badges []*BadgeListInfo `json:"badges" ` + GroupName string `json:"group_name" ` +} diff --git a/internal/service/badge/badge_service.go b/internal/service/badge/badge_service.go new file mode 100644 index 000000000..1109b4447 --- /dev/null +++ b/internal/service/badge/badge_service.go @@ -0,0 +1,112 @@ +/* + * 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 badge + +import ( + "context" + "github.com/apache/incubator-answer/internal/base/handler" + "github.com/apache/incubator-answer/internal/base/translator" + "github.com/apache/incubator-answer/internal/entity" + "github.com/apache/incubator-answer/internal/schema" + "github.com/apache/incubator-answer/internal/service/badge_award" + "github.com/apache/incubator-answer/internal/service/badge_group" + "github.com/apache/incubator-answer/pkg/converter" +) + +type BadgeRepo interface { + ListByLevel(ctx context.Context, level entity.BadgeLevel) ([]*entity.Badge, error) + ListByGroup(ctx context.Context, groupID int64) ([]*entity.Badge, error) + ListByLevelAndGroup(ctx context.Context, level entity.BadgeLevel, groupID int64) ([]*entity.Badge, error) + ListActivated(ctx context.Context) ([]*entity.Badge, error) + ListInactivated(ctx context.Context) ([]*entity.Badge, error) +} + +type BadgeService struct { + badgeRepo BadgeRepo + badgeGroupRepo badge_group.BadgeGroupRepo + badgeAwardRepo badge_award.BadgeAwardRepo +} + +func NewBadgeService( + badgeRepo BadgeRepo, + badgeGroupRepo badge_group.BadgeGroupRepo, + badgeAwardRepo badge_award.BadgeAwardRepo) *BadgeService { + return &BadgeService{ + badgeRepo: badgeRepo, + badgeGroupRepo: badgeGroupRepo, + badgeAwardRepo: badgeAwardRepo, + } +} + +func (b *BadgeService) ListByGroup(ctx context.Context, userID string) (resp []*schema.GetBadgeListResp, err error) { + var ( + groups []*entity.BadgeGroup + badges []*entity.Badge + earnedCounts []*entity.BadgeEarnedCount + + groupMap = make(map[int64]string, 0) + badgesMap = make(map[int64][]*schema.BadgeListInfo, 0) + ) + resp = make([]*schema.GetBadgeListResp, 0) + + groups, err = b.badgeGroupRepo.ListGroups(ctx) + if err != nil { + return + } + badges, err = b.badgeRepo.ListActivated(ctx) + if err != nil { + return + } + earnedCounts, err = b.badgeAwardRepo.SumUserEarnedGroupByBadgeID(ctx, userID) + + for _, group := range groups { + groupMap[converter.StringToInt64(group.ID)] = group.Name + } + + for _, badge := range badges { + // check is earned + earned := false + if len(earnedCounts) > 0 { + for _, earnedCount := range earnedCounts { + if badge.ID == earnedCount.BadgeID { + earned = true + break + } + } + } + + badgesMap[badge.BadgeGroupId] = append(badgesMap[badge.BadgeGroupId], &schema.BadgeListInfo{ + ID: badge.ID, + Name: translator.Tr(handler.GetLangByCtx(ctx), badge.Name), + Icon: badge.Icon, + AwardCount: badge.AwardCount, + Earned: earned, + }) + } + + for _, group := range groups { + resp = append(resp, &schema.GetBadgeListResp{ + GroupName: group.Name, + Badges: badgesMap[converter.StringToInt64(group.ID)], + }) + } + + return +} diff --git a/internal/service/badge_award/badge_award_service.go b/internal/service/badge_award/badge_award_service.go new file mode 100644 index 000000000..8f84453ee --- /dev/null +++ b/internal/service/badge_award/badge_award_service.go @@ -0,0 +1,64 @@ +/* + * 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 badge_award + +import ( + "context" + "github.com/apache/incubator-answer/internal/entity" + "time" +) + +type BadgeAwardRepo interface { + Award(ctx context.Context, badgeID string, userID string, objectID string, force bool, createdAt time.Time) + CheckIsAward(ctx context.Context, badgeID string, userID string, objectID string) bool + + CountByUserIdAndBadgeLevel(ctx context.Context, userID string, badgeLevel entity.BadgeLevel) (awardCount int64) + CountByUserId(ctx context.Context, userID string) (awardCount int64) + CountByUserIdAndBadgeId(ctx context.Context, userID string, badgeID string) (awardCount int64) + CountByObjectId(ctx context.Context, objectID string) (awardCount int64) + CountByObjectIdAndBadgeId(ctx context.Context, objectID string, badgeID string) int64 + CountBadgesByUserIdAndObjectId(ctx context.Context, userID string, objectID string, badgeID string) (awardCount int64) + + SumUserEarnedGroupByBadgeID(ctx context.Context, userID string) (earnedCounts []*entity.BadgeEarnedCount, err error) + + ListAllByUserId(ctx context.Context, userID string) (badgeAwards []*entity.BadgeAward) + ListPagedByBadgeId(ctx context.Context, badgeID string, page int64, pageSize int64) (badgeAwards []*entity.BadgeAward, total int64) + ListPagedByBadgeIdAndUserId(ctx context.Context, badgeID string, userID string, page int64, pageSize int64) (badgeAwards []*entity.BadgeAward, total int64) + ListPagedByObjectId(ctx context.Context, badgeID string, objectID string, page int64, pageSize int64) (badgeAwards []*entity.BadgeAward, total int64) + ListPagedByObjectIdAndUserId(ctx context.Context, badgeID string, objectID string, userID string, page int64, pageSize int64) (badgeAwards []*entity.BadgeAward, total int64) + ListTagPagedByBadgeId(ctx context.Context, badgeIDs []int64, page int64, pageSize int64, filterUserID int64) (badgeAwards []*entity.BadgeAward, total int64) + ListTagPagedByBadgeIdAndUserId(ctx context.Context, badgeIDs []int64, userID string, page int64, pageSize int64) (badgeAwards []*entity.BadgeAward, total int64) + ListPagedLatest(ctx context.Context, page int64, pageSize int64) (badgeAwards []*entity.BadgeAward, total int64) + ListNewestEarnedByLevel(ctx context.Context, userID string, level entity.BadgeLevel, num int64) (badgeAwards []*entity.BadgeAward, total int64) + ListNewestByUserIdAndLevel(ctx context.Context, userID string, level int64, page int64, pageSize int64) (badgeAwards []*entity.BadgeAward, total int64) + + GetByUserIdAndBadgeId(ctx context.Context, userID string, badgeID string) (badgeAward *entity.BadgeAward) + GetByUserIdAndBadgeIdAndObjectId(ctx context.Context, userID string, badgeID string, objectID string) (badgeAward *entity.BadgeAward) +} + +type BadgeAwardService struct { + badgeAwardRepo BadgeAwardRepo +} + +func NewBadgeAwardService(badgeAwardRepo BadgeAwardRepo) *BadgeAwardService { + return &BadgeAwardService{ + badgeAwardRepo: badgeAwardRepo, + } +} diff --git a/internal/service/badge_group/badge_group_service.go b/internal/service/badge_group/badge_group_service.go new file mode 100644 index 000000000..c78f3e4f6 --- /dev/null +++ b/internal/service/badge_group/badge_group_service.go @@ -0,0 +1,40 @@ +/* + * 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 badge_group + +import ( + "context" + "github.com/apache/incubator-answer/internal/entity" +) + +type BadgeGroupRepo interface { + ListGroups(ctx context.Context) (groups []*entity.BadgeGroup, err error) + AddGroup(ctx context.Context, group *entity.BadgeGroup) (err error) +} + +type BadgeGroupService struct { + badgeGroupRepo BadgeGroupRepo +} + +func NewBadgeGroupService(badgeGroupRepo BadgeGroupRepo) *BadgeGroupService { + return &BadgeGroupService{ + badgeGroupRepo: badgeGroupRepo, + } +} diff --git a/internal/service/provider.go b/internal/service/provider.go index e82d93167..38832923a 100644 --- a/internal/service/provider.go +++ b/internal/service/provider.go @@ -26,6 +26,9 @@ import ( "github.com/apache/incubator-answer/internal/service/activity_queue" answercommon "github.com/apache/incubator-answer/internal/service/answer_common" "github.com/apache/incubator-answer/internal/service/auth" + "github.com/apache/incubator-answer/internal/service/badge" + "github.com/apache/incubator-answer/internal/service/badge_award" + "github.com/apache/incubator-answer/internal/service/badge_group" "github.com/apache/incubator-answer/internal/service/collection" collectioncommon "github.com/apache/incubator-answer/internal/service/collection_common" "github.com/apache/incubator-answer/internal/service/comment" @@ -117,4 +120,7 @@ var ProviderSetService = wire.NewSet( notice_queue.NewNewQuestionNotificationQueueService, review.NewReviewService, meta.NewMetaService, + badge.NewBadgeService, + badge_award.NewBadgeAwardService, + badge_group.NewBadgeGroupService, )