From 29ec29bde770542efe2a0f299f00d7466f4fb8c9 Mon Sep 17 00:00:00 2001 From: kumfo Date: Tue, 20 Jan 2026 14:54:52 +0800 Subject: [PATCH 01/19] feat(menu): update admin menu settings to include questions, tags, and advanced options --- cmd/wire_gen.go | 74 +++----- go.mod | 2 +- internal/base/constant/site_type.go | 3 + internal/controller/answer_controller.go | 2 +- internal/controller/siteinfo_controller.go | 10 +- .../controller_admin/siteinfo_controller.go | 94 ++++++++-- internal/migrations/migrations.go | 1 + internal/migrations/v30.go | 173 ++++++++++++++++++ internal/router/answer_api_router.go | 10 +- internal/schema/siteinfo_schema.go | 41 ++++- internal/service/mock/siteinfo_repo_mock.go | 81 ++++++-- internal/service/question_common/question.go | 2 +- internal/service/siteinfo/siteinfo_service.go | 51 ++++-- .../siteinfo_common/siteinfo_service.go | 30 +++ internal/service/tag_common/tag_common.go | 8 +- internal/service/uploader/upload.go | 32 ++-- 16 files changed, 490 insertions(+), 124 deletions(-) create mode 100644 internal/migrations/v30.go diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go index 22a70f29d..2808619b3 100644 --- a/cmd/wire_gen.go +++ b/cmd/wire_gen.go @@ -1,28 +1,8 @@ -//go:build !wireinject -// +build !wireinject - -/* - * 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. - */ - // Code generated by Wire. DO NOT EDIT. -//go:generate go run github.com/google/wire/cmd/wire +//go:generate go run -mod=mod github.com/google/wire/cmd/wire +//go:build !wireinject +// +build !wireinject package answercmd @@ -172,29 +152,29 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database, tagRepo := tag.NewTagRepo(dataData, uniqueIDRepo) revisionRepo := revision.NewRevisionRepo(dataData, uniqueIDRepo) revisionService := revision_common.NewRevisionService(revisionRepo, userRepo) - v := activityqueue.NewService() - tagCommonService := tag_common2.NewTagCommonService(tagCommonRepo, tagRelRepo, tagRepo, revisionService, siteInfoCommonService, v) + service := activityqueue.NewService() + tagCommonService := tag_common2.NewTagCommonService(tagCommonRepo, tagRelRepo, tagRepo, revisionService, siteInfoCommonService, service) collectionRepo := collection.NewCollectionRepo(dataData, uniqueIDRepo) collectionCommon := collectioncommon.NewCollectionCommon(collectionRepo) answerCommon := answercommon.NewAnswerCommon(answerRepo) metaRepo := meta.NewMetaRepo(dataData) metaCommonService := metacommon.NewMetaCommonService(metaRepo) - questionCommon := questioncommon.NewQuestionCommon(questionRepo, answerRepo, voteRepo, followRepo, tagCommonService, userCommon, collectionCommon, answerCommon, metaCommonService, configService, v, revisionRepo, siteInfoCommonService, dataData) - v2 := eventqueue.NewService() + questionCommon := questioncommon.NewQuestionCommon(questionRepo, answerRepo, voteRepo, followRepo, tagCommonService, userCommon, collectionCommon, answerCommon, metaCommonService, configService, service, revisionRepo, siteInfoCommonService, dataData) + eventqueueService := eventqueue.NewService() fileRecordRepo := file_record.NewFileRecordRepo(dataData) fileRecordService := file_record2.NewFileRecordService(fileRecordRepo, revisionRepo, serviceConf, siteInfoCommonService, userCommon) - userService := content.NewUserService(userRepo, userActiveActivityRepo, activityRepo, emailService, authService, siteInfoCommonService, userRoleRelService, userCommon, userExternalLoginService, userNotificationConfigRepo, userNotificationConfigService, questionCommon, v2, fileRecordService) + userService := content.NewUserService(userRepo, userActiveActivityRepo, activityRepo, emailService, authService, siteInfoCommonService, userRoleRelService, userCommon, userExternalLoginService, userNotificationConfigRepo, userNotificationConfigService, questionCommon, eventqueueService, fileRecordService) captchaRepo := captcha.NewCaptchaRepo(dataData) captchaService := action.NewCaptchaService(captchaRepo) userController := controller.NewUserController(authService, userService, captchaService, emailService, siteInfoCommonService, userNotificationConfigService) commentRepo := comment.NewCommentRepo(dataData, uniqueIDRepo) commentCommonRepo := comment.NewCommentCommonRepo(dataData, uniqueIDRepo) objService := object_info.NewObjService(answerRepo, questionRepo, commentCommonRepo, tagCommonRepo, tagCommonService) - v3 := noticequeue.NewService() - v4 := noticequeue.NewExternalService() + noticequeueService := noticequeue.NewService() + externalService := noticequeue.NewExternalService() reviewRepo := review.NewReviewRepo(dataData) - reviewService := review2.NewReviewService(reviewRepo, objService, userCommon, userRepo, questionRepo, answerRepo, userRoleRelService, v4, tagCommonService, questionCommon, v3, siteInfoCommonService, commentCommonRepo) - commentService := comment2.NewCommentService(commentRepo, commentCommonRepo, userCommon, objService, voteRepo, emailService, userRepo, v3, v4, v, v2, reviewService) + reviewService := review2.NewReviewService(reviewRepo, objService, userCommon, userRepo, questionRepo, answerRepo, userRoleRelService, externalService, tagCommonService, questionCommon, noticequeueService, siteInfoCommonService, commentCommonRepo) + commentService := comment2.NewCommentService(commentRepo, commentCommonRepo, userCommon, objService, voteRepo, emailService, userRepo, noticequeueService, externalService, service, eventqueueService, reviewService) rolePowerRelRepo := role.NewRolePowerRelRepo(dataData) rolePowerRelService := role2.NewRolePowerRelService(rolePowerRelRepo, userRoleRelService) rankService := rank2.NewRankService(userCommon, userRankRepo, objService, userRoleRelService, rolePowerRelService, configService) @@ -202,17 +182,17 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database, rateLimitMiddleware := middleware.NewRateLimitMiddleware(limitRepo) commentController := controller.NewCommentController(commentService, rankService, captchaService, rateLimitMiddleware) reportRepo := report.NewReportRepo(dataData, uniqueIDRepo) - tagService := tag2.NewTagService(tagRepo, tagCommonService, revisionService, followRepo, siteInfoCommonService, v) - answerActivityRepo := activity.NewAnswerActivityRepo(dataData, activityRepo, userRankRepo, v3) + tagService := tag2.NewTagService(tagRepo, tagCommonService, revisionService, followRepo, siteInfoCommonService, service) + answerActivityRepo := activity.NewAnswerActivityRepo(dataData, activityRepo, userRankRepo, noticequeueService) answerActivityService := activity2.NewAnswerActivityService(answerActivityRepo, configService) - externalNotificationService := notification.NewExternalNotificationService(dataData, userNotificationConfigRepo, followRepo, emailService, userRepo, v4, userExternalLoginRepo, siteInfoCommonService) - questionService := content.NewQuestionService(activityRepo, questionRepo, answerRepo, tagCommonService, tagService, questionCommon, userCommon, userRepo, userRoleRelService, revisionService, metaCommonService, collectionCommon, answerActivityService, emailService, v3, v4, v, siteInfoCommonService, externalNotificationService, reviewService, configService, v2, reviewRepo) - answerService := content.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo, emailService, userRoleRelService, v3, v4, v, reviewService, v2) + externalNotificationService := notification.NewExternalNotificationService(dataData, userNotificationConfigRepo, followRepo, emailService, userRepo, externalService, userExternalLoginRepo, siteInfoCommonService) + questionService := content.NewQuestionService(activityRepo, questionRepo, answerRepo, tagCommonService, tagService, questionCommon, userCommon, userRepo, userRoleRelService, revisionService, metaCommonService, collectionCommon, answerActivityService, emailService, noticequeueService, externalService, service, siteInfoCommonService, externalNotificationService, reviewService, configService, eventqueueService, reviewRepo) + answerService := content.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo, emailService, userRoleRelService, noticequeueService, externalService, service, reviewService, eventqueueService) reportHandle := report_handle.NewReportHandle(questionService, answerService, commentService) - reportService := report2.NewReportService(reportRepo, objService, userCommon, answerRepo, questionRepo, commentCommonRepo, reportHandle, configService, v2) + reportService := report2.NewReportService(reportRepo, objService, userCommon, answerRepo, questionRepo, commentCommonRepo, reportHandle, configService, eventqueueService) reportController := controller.NewReportController(reportService, rankService, captchaService) - contentVoteRepo := activity.NewVoteRepo(dataData, activityRepo, userRankRepo, v3) - voteService := content.NewVoteService(contentVoteRepo, configService, questionRepo, answerRepo, commentCommonRepo, objService, v2) + contentVoteRepo := activity.NewVoteRepo(dataData, activityRepo, userRankRepo, noticequeueService) + voteService := content.NewVoteService(contentVoteRepo, configService, questionRepo, answerRepo, commentCommonRepo, objService, eventqueueService) voteController := controller.NewVoteController(voteService, rankService, captchaService) tagController := controller.NewTagController(tagService, tagCommonService, rankService) followFollowRepo := activity.NewFollowRepo(dataData, uniqueIDRepo, activityRepo) @@ -228,7 +208,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database, searchService := content.NewSearchService(searchParser, searchRepo) searchController := controller.NewSearchController(searchService, captchaService) reviewActivityRepo := activity.NewReviewActivityRepo(dataData, activityRepo, userRankRepo, configService) - contentRevisionService := content.NewRevisionService(revisionRepo, userCommon, questionCommon, answerService, objService, questionRepo, answerRepo, tagRepo, tagCommonService, v3, v, reportRepo, reviewService, reviewActivityRepo) + contentRevisionService := content.NewRevisionService(revisionRepo, userCommon, questionCommon, answerService, objService, questionRepo, answerRepo, tagRepo, tagCommonService, noticequeueService, service, reportRepo, reviewService, reviewActivityRepo) revisionController := controller.NewRevisionController(contentRevisionService, rankService) rankController := controller.NewRankController(rankService) userAdminRepo := user.NewUserAdminRepo(dataData, authRepo) @@ -244,7 +224,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database, siteInfoService := siteinfo.NewSiteInfoService(siteInfoRepo, siteInfoCommonService, emailService, tagCommonService, configService, questionCommon, fileRecordService) siteInfoController := controller_admin.NewSiteInfoController(siteInfoService) controllerSiteInfoController := controller.NewSiteInfoController(siteInfoCommonService) - notificationCommon := notificationcommon.NewNotificationCommon(dataData, notificationRepo, userCommon, activityRepo, followRepo, objService, v3, userExternalLoginRepo, siteInfoCommonService) + notificationCommon := notificationcommon.NewNotificationCommon(dataData, notificationRepo, userCommon, activityRepo, followRepo, objService, noticequeueService, userExternalLoginRepo, siteInfoCommonService) badgeRepo := badge.NewBadgeRepo(dataData, uniqueIDRepo) notificationService := notification.NewNotificationService(dataData, notificationRepo, notificationCommon, revisionService, userRepo, reportRepo, reviewService, badgeRepo) notificationController := controller.NewNotificationController(notificationService, rankService) @@ -253,7 +233,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database, uploaderService := uploader.NewUploaderService(serviceConf, siteInfoCommonService, fileRecordService) uploadController := controller.NewUploadController(uploaderService) activityActivityRepo := activity.NewActivityRepo(dataData, configService) - activityCommon := activity_common2.NewActivityCommon(activityRepo, v) + activityCommon := activity_common2.NewActivityCommon(activityRepo, service) commentCommonService := comment_common.NewCommentCommonService(commentCommonRepo) activityService := activity2.NewActivityService(activityActivityRepo, userCommon, activityCommon, tagCommonService, objService, commentCommonService, revisionService, metaCommonService, configService) activityController := controller.NewActivityController(activityService) @@ -265,12 +245,12 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database, permissionController := controller.NewPermissionController(rankService) userPluginController := controller.NewUserPluginController(pluginCommonService) reviewController := controller.NewReviewController(reviewService, rankService, captchaService) - metaService := meta2.NewMetaService(metaCommonService, userCommon, answerRepo, questionRepo, v2) + metaService := meta2.NewMetaService(metaCommonService, userCommon, answerRepo, questionRepo, eventqueueService) metaController := controller.NewMetaController(metaService) badgeGroupRepo := badge_group.NewBadgeGroupRepo(dataData, uniqueIDRepo) eventRuleRepo := badge.NewEventRuleRepo(dataData) - badgeAwardService := badge2.NewBadgeAwardService(badgeAwardRepo, badgeRepo, userCommon, objService, v3) - badgeEventService := badge2.NewBadgeEventService(dataData, v2, badgeRepo, eventRuleRepo, badgeAwardService) + badgeAwardService := badge2.NewBadgeAwardService(badgeAwardRepo, badgeRepo, userCommon, objService, noticequeueService) + badgeEventService := badge2.NewBadgeEventService(dataData, eventqueueService, badgeRepo, eventRuleRepo, badgeAwardService) badgeService := badge2.NewBadgeService(badgeRepo, badgeGroupRepo, badgeAwardRepo, badgeEventService, siteInfoCommonService) badgeController := controller.NewBadgeController(badgeService, badgeAwardService) controller_adminBadgeController := controller_admin.NewBadgeController(badgeService) @@ -281,7 +261,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database, avatarMiddleware := middleware.NewAvatarMiddleware(serviceConf, uploaderService) shortIDMiddleware := middleware.NewShortIDMiddleware(siteInfoCommonService) templateRenderController := templaterender.NewTemplateRenderController(questionService, userService, tagService, answerService, commentService, siteInfoCommonService, questionRepo) - templateController := controller.NewTemplateController(templateRenderController, siteInfoCommonService, v2, userService, questionService) + templateController := controller.NewTemplateController(templateRenderController, siteInfoCommonService, eventqueueService, userService, questionService) templateRouter := router.NewTemplateRouter(templateController, templateRenderController, siteInfoController, authUserMiddleware) connectorController := controller.NewConnectorController(siteInfoCommonService, emailService, userExternalLoginService) userCenterLoginService := user_external_login2.NewUserCenterLoginService(userRepo, userCommon, userExternalLoginRepo, userActiveActivityRepo, siteInfoCommonService) diff --git a/go.mod b/go.mod index 52d64c738..d24302954 100644 --- a/go.mod +++ b/go.mod @@ -37,6 +37,7 @@ require ( github.com/grokify/html-strip-tags-go v0.1.0 github.com/jinzhu/copier v0.4.0 github.com/jinzhu/now v1.1.5 + github.com/joho/godotenv v1.5.1 github.com/lib/pq v1.10.9 github.com/microcosm-cc/bluemonday v1.0.27 github.com/mozillazg/go-pinyin v0.20.0 @@ -117,7 +118,6 @@ require ( github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/joho/godotenv v1.5.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect diff --git a/internal/base/constant/site_type.go b/internal/base/constant/site_type.go index 65b487c5d..6106b190c 100644 --- a/internal/base/constant/site_type.go +++ b/internal/base/constant/site_type.go @@ -31,4 +31,7 @@ const ( SiteTypeTheme = "theme" SiteTypePrivileges = "privileges" SiteTypeUsers = "users" + SiteTypeAdvanced = "advanced" + SiteTypeQuestions = "questions" + SiteTypeTags = "tags" ) diff --git a/internal/controller/answer_controller.go b/internal/controller/answer_controller.go index e76b02ccc..99c89b778 100644 --- a/internal/controller/answer_controller.go +++ b/internal/controller/answer_controller.go @@ -242,7 +242,7 @@ func (ac *AnswerController) AddAnswer(ctx *gin.Context) { return } - write, err := ac.siteInfoCommonService.GetSiteWrite(ctx) + write, err := ac.siteInfoCommonService.GetSiteQuestion(ctx) if err != nil { handler.HandleResponse(ctx, err, nil) return diff --git a/internal/controller/siteinfo_controller.go b/internal/controller/siteinfo_controller.go index a336fb242..8035275a6 100644 --- a/internal/controller/siteinfo_controller.go +++ b/internal/controller/siteinfo_controller.go @@ -87,7 +87,15 @@ func (sc *SiteInfoController) GetSiteInfo(ctx *gin.Context) { if err != nil { log.Error(err) } - resp.Write, err = sc.siteInfoService.GetSiteWrite(ctx) + resp.Questions, err = sc.siteInfoService.GetSiteQuestion(ctx) + if err != nil { + log.Error(err) + } + resp.Tags, err = sc.siteInfoService.GetSiteTag(ctx) + if err != nil { + log.Error(err) + } + resp.Advanced, err = sc.siteInfoService.GetSiteAdvanced(ctx) if err != nil { log.Error(err) } diff --git a/internal/controller_admin/siteinfo_controller.go b/internal/controller_admin/siteinfo_controller.go index 8a92daba3..bbab97942 100644 --- a/internal/controller_admin/siteinfo_controller.go +++ b/internal/controller_admin/siteinfo_controller.go @@ -82,16 +82,42 @@ func (sc *SiteInfoController) GetSiteBranding(ctx *gin.Context) { handler.HandleResponse(ctx, err, resp) } -// GetSiteWrite get site interface -// @Summary get site interface -// @Description get site interface +// GetSiteTag get site tags setting +// @Summary get site tags setting +// @Description get site tags setting +// @Security ApiKeyAuth +// @Tags admin +// @Produce json +// @Success 200 {object} handler.RespBody{data=schema.SiteTagsResp} +// @Router /answer/admin/api/siteinfo/tag [get] +func (sc *SiteInfoController) GetSiteTag(ctx *gin.Context) { + resp, err := sc.siteInfoService.GetSiteTag(ctx) + handler.HandleResponse(ctx, err, resp) +} + +// GetSiteQuestion get site questions setting +// @Summary get site questions setting +// @Description get site questions setting +// @Security ApiKeyAuth +// @Tags admin +// @Produce json +// @Success 200 {object} handler.RespBody{data=schema.SiteQuestionsResp} +// @Router /answer/admin/api/siteinfo/question [get] +func (sc *SiteInfoController) GetSiteQuestion(ctx *gin.Context) { + resp, err := sc.siteInfoService.GetSiteQuestion(ctx) + handler.HandleResponse(ctx, err, resp) +} + +// GetSiteAdvanced get site advanced setting +// @Summary get site advanced setting +// @Description get site advanced setting // @Security ApiKeyAuth // @Tags admin // @Produce json -// @Success 200 {object} handler.RespBody{data=schema.SiteWriteResp} -// @Router /answer/admin/api/siteinfo/write [get] -func (sc *SiteInfoController) GetSiteWrite(ctx *gin.Context) { - resp, err := sc.siteInfoService.GetSiteWrite(ctx) +// @Success 200 {object} handler.RespBody{data=schema.SiteAdvancedResp} +// @Router /answer/admin/api/siteinfo/advanced [get] +func (sc *SiteInfoController) GetSiteAdvanced(ctx *gin.Context) { + resp, err := sc.siteInfoService.GetSiteAdvanced(ctx) handler.HandleResponse(ctx, err, resp) } @@ -288,23 +314,61 @@ func (sc *SiteInfoController) UpdateBranding(ctx *gin.Context) { handler.HandleResponse(ctx, saveErr, nil) } -// UpdateSiteWrite update site write info -// @Summary update site write info -// @Description update site write info +// UpdateSiteQuestion update site question settings +// @Summary update site question settings +// @Description update site question settings +// @Security ApiKeyAuth +// @Tags admin +// @Produce json +// @Param data body schema.SiteQuestionsReq true "questions settings" +// @Success 200 {object} handler.RespBody{} +// @Router /answer/admin/api/siteinfo/question [put] +func (sc *SiteInfoController) UpdateSiteQuestion(ctx *gin.Context) { + req := &schema.SiteQuestionsReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + resp, err := sc.siteInfoService.SaveSiteQuestions(ctx, req) + handler.HandleResponse(ctx, err, resp) +} + +// UpdateSiteTag update site tag settings +// @Summary update site tag settings +// @Description update site tag settings // @Security ApiKeyAuth // @Tags admin // @Produce json -// @Param data body schema.SiteWriteReq true "write info" +// @Param data body schema.SiteTagsReq true "tags settings" // @Success 200 {object} handler.RespBody{} -// @Router /answer/admin/api/siteinfo/write [put] -func (sc *SiteInfoController) UpdateSiteWrite(ctx *gin.Context) { - req := &schema.SiteWriteReq{} +// @Router /answer/admin/api/siteinfo/tag [put] +func (sc *SiteInfoController) UpdateSiteTag(ctx *gin.Context) { + req := &schema.SiteTagsReq{} if handler.BindAndCheck(ctx, req) { return } req.UserID = middleware.GetLoginUserIDFromContext(ctx) - resp, err := sc.siteInfoService.SaveSiteWrite(ctx, req) + resp, err := sc.siteInfoService.SaveSiteTags(ctx, req) + handler.HandleResponse(ctx, err, resp) +} + +// UpdateSiteAdvanced update site advanced info +// @Summary update site advanced info +// @Description update site advanced info +// @Security ApiKeyAuth +// @Tags admin +// @Produce json +// @Param data body schema.SiteAdvancedReq true "advanced settings" +// @Success 200 {object} handler.RespBody{} +// @Router /answer/admin/api/siteinfo/advanced [put] +func (sc *SiteInfoController) UpdateSiteAdvanced(ctx *gin.Context) { + req := &schema.SiteAdvancedReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + resp, err := sc.siteInfoService.SaveSiteAdvanced(ctx, req) handler.HandleResponse(ctx, err, resp) } diff --git a/internal/migrations/migrations.go b/internal/migrations/migrations.go index 9fda8c34d..783332df9 100644 --- a/internal/migrations/migrations.go +++ b/internal/migrations/migrations.go @@ -105,6 +105,7 @@ var migrations = []Migration{ NewMigration("v1.6.0", "move user config to interface", moveUserConfigToInterface, true), NewMigration("v1.7.0", "add optional tags", addOptionalTags, true), NewMigration("v1.7.2", "expand avatar column length", expandAvatarColumnLength, false), + NewMigration("v1.8.0", "change admin menu", updateAdminMenuSettings, true), } func GetMigrations() []Migration { diff --git a/internal/migrations/v30.go b/internal/migrations/v30.go new file mode 100644 index 000000000..5d5d5223f --- /dev/null +++ b/internal/migrations/v30.go @@ -0,0 +1,173 @@ +/* + * 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" + "encoding/json" + + "github.com/apache/answer/internal/base/constant" + "github.com/apache/answer/internal/base/reason" + "github.com/apache/answer/internal/entity" + "github.com/apache/answer/internal/schema" + "github.com/segmentfault/pacman/errors" + "xorm.io/builder" + "xorm.io/xorm" +) + +func updateAdminMenuSettings(ctx context.Context, x *xorm.Engine) (err error) { + err = splitWriteMenu(ctx, x) + if err != nil { + return + } + return +} + +// splitWriteMenu splits the site write settings into advanced, questions, and tags settings +func splitWriteMenu(ctx context.Context, x *xorm.Engine) error { + var ( + siteInfo = &entity.SiteInfo{} + siteInfoAdvanced = &entity.SiteInfo{} + siteInfoQuestions = &entity.SiteInfo{} + siteInfoTags = &entity.SiteInfo{} + ) + exist, err := x.Context(ctx).Where(builder.Eq{"type": constant.SiteTypeWrite}).Get(siteInfo) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return err + } + if !exist { + return nil + } + siteWrite := &schema.SiteWriteResp{} + if err := json.Unmarshal([]byte(siteInfo.Content), siteWrite); err != nil { + return err + } + // site advanced settings + siteAdvanced := &schema.SiteAdvancedResp{ + MaxImageSize: siteWrite.MaxImageSize, + MaxAttachmentSize: siteWrite.MaxAttachmentSize, + MaxImageMegapixel: siteWrite.MaxImageMegapixel, + AuthorizedImageExtensions: siteWrite.AuthorizedImageExtensions, + AuthorizedAttachmentExtensions: siteWrite.AuthorizedAttachmentExtensions, + } + // site questions settings + siteQuestions := &schema.SiteQuestionsResp{ + MinimumContent: siteWrite.MinimumContent, + RestrictAnswer: siteWrite.RestrictAnswer, + } + // site tags settings + siteTags := &schema.SiteTagsResp{ + ReservedTags: siteWrite.ReservedTags, + RecommendTags: siteWrite.RecommendTags, + MinimumTags: siteWrite.MinimumTags, + RequiredTag: siteWrite.RequiredTag, + } + + // save site settings + // save advanced settings + existsAdvanced, err := x.Context(ctx).Where(builder.Eq{"type": constant.SiteTypeWrite}).Get(siteInfoAdvanced) + if err != nil { + return err + } + advancedContent, err := json.Marshal(siteAdvanced) + if err != nil { + return err + } + if existsAdvanced { + _, err = x.Context(ctx).ID(siteInfoAdvanced.ID).Update(&entity.SiteInfo{ + Type: constant.SiteTypeAdvanced, + Content: string(advancedContent), + Status: 1, + }) + if err != nil { + return err + } + } else { + _, err = x.Context(ctx).Insert(&entity.SiteInfo{ + Type: constant.SiteTypeAdvanced, + Content: string(advancedContent), + Status: 1, + }) + if err != nil { + return err + } + } + + // save questions settings + existsQuestions, err := x.Context(ctx).Where(builder.Eq{"type": constant.SiteTypeQuestions}).Get(siteInfoQuestions) + if err != nil { + return err + } + questionsContent, err := json.Marshal(siteQuestions) + if err != nil { + return err + } + if existsQuestions { + _, err = x.Context(ctx).ID(siteInfoQuestions.ID).Update(&entity.SiteInfo{ + Type: constant.SiteTypeQuestions, + Content: string(questionsContent), + Status: 1, + }) + if err != nil { + return err + } + } else { + _, err = x.Context(ctx).Insert(&entity.SiteInfo{ + Type: constant.SiteTypeQuestions, + Content: string(questionsContent), + Status: 1, + }) + if err != nil { + return err + } + } + + // save tags settings + existsTags, err := x.Context(ctx).Where(builder.Eq{"type": constant.SiteTypeTags}).Get(siteInfoTags) + if err != nil { + return err + } + tagsContent, err := json.Marshal(siteTags) + if err != nil { + return err + } + if existsTags { + _, err = x.Context(ctx).ID(siteInfoTags.ID).Update(&entity.SiteInfo{ + Type: constant.SiteTypeTags, + Content: string(tagsContent), + Status: 1, + }) + if err != nil { + return err + } + } else { + _, err = x.Context(ctx).Insert(&entity.SiteInfo{ + Type: constant.SiteTypeTags, + Content: string(tagsContent), + Status: 1, + }) + if err != nil { + return err + } + } + + return nil +} diff --git a/internal/router/answer_api_router.go b/internal/router/answer_api_router.go index 1191492b9..d717bc9d3 100644 --- a/internal/router/answer_api_router.go +++ b/internal/router/answer_api_router.go @@ -347,8 +347,14 @@ func (a *AnswerAPIRouter) RegisterAnswerAdminAPIRouter(r *gin.RouterGroup) { r.PUT("/siteinfo/interface", a.adminSiteInfoController.UpdateInterface) r.GET("/siteinfo/branding", a.adminSiteInfoController.GetSiteBranding) r.PUT("/siteinfo/branding", a.adminSiteInfoController.UpdateBranding) - r.GET("/siteinfo/write", a.adminSiteInfoController.GetSiteWrite) - r.PUT("/siteinfo/write", a.adminSiteInfoController.UpdateSiteWrite) + + r.GET("/siteinfo/question", a.adminSiteInfoController.GetSiteQuestion) + r.PUT("/siteinfo/question", a.adminSiteInfoController.UpdateSiteQuestion) + r.GET("/siteinfo/tag", a.adminSiteInfoController.GetSiteTag) + r.PUT("/siteinfo/tag", a.adminSiteInfoController.UpdateSiteTag) + r.GET("/siteinfo/advanced", a.adminSiteInfoController.GetSiteAdvanced) + r.PUT("/siteinfo/advanced", a.adminSiteInfoController.UpdateSiteAdvanced) + r.GET("/siteinfo/legal", a.adminSiteInfoController.GetSiteLegal) r.PUT("/siteinfo/legal", a.adminSiteInfoController.UpdateSiteLegal) r.GET("/siteinfo/seo", a.adminSiteInfoController.GetSeo) diff --git a/internal/schema/siteinfo_schema.go b/internal/schema/siteinfo_schema.go index 7ab657512..daf49146c 100644 --- a/internal/schema/siteinfo_schema.go +++ b/internal/schema/siteinfo_schema.go @@ -89,21 +89,47 @@ type SiteWriteReq struct { UserID string `json:"-"` } -func (s *SiteWriteResp) GetMaxImageSize() int64 { +type SiteWriteResp SiteWriteReq + +// SiteQuestionsReq site questions settings request +type SiteQuestionsReq struct { + MinimumContent int `validate:"omitempty,gte=0,lte=65535" json:"min_content"` + RestrictAnswer bool `validate:"omitempty" json:"restrict_answer"` +} + +// SiteAdvancedReq site advanced settings request +type SiteAdvancedReq struct { + MaxImageSize int `validate:"omitempty,gt=0" json:"max_image_size"` + MaxAttachmentSize int `validate:"omitempty,gt=0" json:"max_attachment_size"` + MaxImageMegapixel int `validate:"omitempty,gt=0" json:"max_image_megapixel"` + AuthorizedImageExtensions []string `validate:"omitempty" json:"authorized_image_extensions"` + AuthorizedAttachmentExtensions []string `validate:"omitempty" json:"authorized_attachment_extensions"` +} + +// SiteTagsReq site tags settings request +type SiteTagsReq struct { + ReservedTags []*SiteWriteTag `validate:"omitempty,dive" json:"reserved_tags"` + RecommendTags []*SiteWriteTag `validate:"omitempty,dive" json:"recommend_tags"` + MinimumTags int `validate:"omitempty,gte=0,lte=5" json:"min_tags"` + RequiredTag bool `validate:"omitempty" json:"required_tag"` + UserID string `json:"-"` +} + +func (s *SiteAdvancedResp) GetMaxImageSize() int64 { if s.MaxImageSize <= 0 { return constant.DefaultMaxImageSize } return int64(s.MaxImageSize) * 1024 * 1024 } -func (s *SiteWriteResp) GetMaxAttachmentSize() int64 { +func (s *SiteAdvancedResp) GetMaxAttachmentSize() int64 { if s.MaxAttachmentSize <= 0 { return constant.DefaultMaxAttachmentSize } return int64(s.MaxAttachmentSize) * 1024 * 1024 } -func (s *SiteWriteResp) GetMaxImageMegapixel() int { +func (s *SiteAdvancedResp) GetMaxImageMegapixel() int { if s.MaxImageMegapixel <= 0 { return constant.DefaultMaxImageMegapixel } @@ -236,8 +262,9 @@ type ThemeOption struct { Value string `json:"value"` } -// SiteWriteResp site write response -type SiteWriteResp SiteWriteReq +type SiteQuestionsResp SiteQuestionsReq +type SiteAdvancedResp SiteAdvancedReq +type SiteTagsResp SiteTagsReq // SiteLegalResp site write response type SiteLegalResp SiteLegalReq @@ -260,7 +287,9 @@ type SiteInfoResp struct { CustomCssHtml *SiteCustomCssHTMLResp `json:"custom_css_html"` SiteSeo *SiteSeoResp `json:"site_seo"` SiteUsers *SiteUsersResp `json:"site_users"` - Write *SiteWriteResp `json:"site_write"` + Advanced *SiteAdvancedResp `json:"site_advanced"` + Questions *SiteQuestionsResp `json:"site_questions"` + Tags *SiteTagsResp `json:"site_tags"` Legal *SiteLegalSimpleResp `json:"site_legal"` Version string `json:"version"` Revision string `json:"revision"` diff --git a/internal/service/mock/siteinfo_repo_mock.go b/internal/service/mock/siteinfo_repo_mock.go index a98ceb68c..5d5429ba4 100644 --- a/internal/service/mock/siteinfo_repo_mock.go +++ b/internal/service/mock/siteinfo_repo_mock.go @@ -41,7 +41,6 @@ import ( type MockSiteInfoRepo struct { ctrl *gomock.Controller recorder *MockSiteInfoRepoMockRecorder - isgomock struct{} } // MockSiteInfoRepoMockRecorder is the mock recorder for MockSiteInfoRepo. @@ -72,7 +71,7 @@ func (m *MockSiteInfoRepo) GetByType(ctx context.Context, siteType string) (*ent } // GetByType indicates an expected call of GetByType. -func (mr *MockSiteInfoRepoMockRecorder) GetByType(ctx, siteType any) *gomock.Call { +func (mr *MockSiteInfoRepoMockRecorder) GetByType(ctx, siteType interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByType", reflect.TypeOf((*MockSiteInfoRepo)(nil).GetByType), ctx, siteType) } @@ -87,7 +86,7 @@ func (m *MockSiteInfoRepo) IsBrandingFileUsed(ctx context.Context, filePath stri } // IsBrandingFileUsed indicates an expected call of IsBrandingFileUsed. -func (mr *MockSiteInfoRepoMockRecorder) IsBrandingFileUsed(ctx, filePath any) *gomock.Call { +func (mr *MockSiteInfoRepoMockRecorder) IsBrandingFileUsed(ctx, filePath interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsBrandingFileUsed", reflect.TypeOf((*MockSiteInfoRepo)(nil).IsBrandingFileUsed), ctx, filePath) } @@ -101,7 +100,7 @@ func (m *MockSiteInfoRepo) SaveByType(ctx context.Context, siteType string, data } // SaveByType indicates an expected call of SaveByType. -func (mr *MockSiteInfoRepoMockRecorder) SaveByType(ctx, siteType, data any) *gomock.Call { +func (mr *MockSiteInfoRepoMockRecorder) SaveByType(ctx, siteType, data interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveByType", reflect.TypeOf((*MockSiteInfoRepo)(nil).SaveByType), ctx, siteType, data) } @@ -110,7 +109,6 @@ func (mr *MockSiteInfoRepoMockRecorder) SaveByType(ctx, siteType, data any) *gom type MockSiteInfoCommonService struct { ctrl *gomock.Controller recorder *MockSiteInfoCommonServiceMockRecorder - isgomock struct{} } // MockSiteInfoCommonServiceMockRecorder is the mock recorder for MockSiteInfoCommonService. @@ -139,7 +137,7 @@ func (m *MockSiteInfoCommonService) FormatAvatar(ctx context.Context, originalAv } // FormatAvatar indicates an expected call of FormatAvatar. -func (mr *MockSiteInfoCommonServiceMockRecorder) FormatAvatar(ctx, originalAvatarData, email, userStatus any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) FormatAvatar(ctx, originalAvatarData, email, userStatus interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FormatAvatar", reflect.TypeOf((*MockSiteInfoCommonService)(nil).FormatAvatar), ctx, originalAvatarData, email, userStatus) } @@ -153,11 +151,26 @@ func (m *MockSiteInfoCommonService) FormatListAvatar(ctx context.Context, userLi } // FormatListAvatar indicates an expected call of FormatListAvatar. -func (mr *MockSiteInfoCommonServiceMockRecorder) FormatListAvatar(ctx, userList any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) FormatListAvatar(ctx, userList interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FormatListAvatar", reflect.TypeOf((*MockSiteInfoCommonService)(nil).FormatListAvatar), ctx, userList) } +// GetSiteAdvanced mocks base method. +func (m *MockSiteInfoCommonService) GetSiteAdvanced(ctx context.Context) (*schema.SiteAdvancedResp, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSiteAdvanced", ctx) + ret0, _ := ret[0].(*schema.SiteAdvancedResp) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSiteAdvanced indicates an expected call of GetSiteAdvanced. +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteAdvanced(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteAdvanced", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteAdvanced), ctx) +} + // GetSiteBranding mocks base method. func (m *MockSiteInfoCommonService) GetSiteBranding(ctx context.Context) (*schema.SiteBrandingResp, error) { m.ctrl.T.Helper() @@ -168,7 +181,7 @@ func (m *MockSiteInfoCommonService) GetSiteBranding(ctx context.Context) (*schem } // GetSiteBranding indicates an expected call of GetSiteBranding. -func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteBranding(ctx any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteBranding(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteBranding", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteBranding), ctx) } @@ -183,7 +196,7 @@ func (m *MockSiteInfoCommonService) GetSiteCustomCssHTML(ctx context.Context) (* } // GetSiteCustomCssHTML indicates an expected call of GetSiteCustomCssHTML. -func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteCustomCssHTML(ctx any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteCustomCssHTML(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteCustomCssHTML", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteCustomCssHTML), ctx) } @@ -198,7 +211,7 @@ func (m *MockSiteInfoCommonService) GetSiteGeneral(ctx context.Context) (*schema } // GetSiteGeneral indicates an expected call of GetSiteGeneral. -func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteGeneral(ctx any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteGeneral(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteGeneral", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteGeneral), ctx) } @@ -212,7 +225,7 @@ func (m *MockSiteInfoCommonService) GetSiteInfoByType(ctx context.Context, siteT } // GetSiteInfoByType indicates an expected call of GetSiteInfoByType. -func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteInfoByType(ctx, siteType, resp any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteInfoByType(ctx, siteType, resp interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteInfoByType", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteInfoByType), ctx, siteType, resp) } @@ -227,7 +240,7 @@ func (m *MockSiteInfoCommonService) GetSiteInterface(ctx context.Context) (*sche } // GetSiteInterface indicates an expected call of GetSiteInterface. -func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteInterface(ctx any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteInterface(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteInterface", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteInterface), ctx) } @@ -242,7 +255,7 @@ func (m *MockSiteInfoCommonService) GetSiteLegal(ctx context.Context) (*schema.S } // GetSiteLegal indicates an expected call of GetSiteLegal. -func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteLegal(ctx any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteLegal(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteLegal", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteLegal), ctx) } @@ -257,11 +270,26 @@ func (m *MockSiteInfoCommonService) GetSiteLogin(ctx context.Context) (*schema.S } // GetSiteLogin indicates an expected call of GetSiteLogin. -func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteLogin(ctx any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteLogin(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteLogin", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteLogin), ctx) } +// GetSiteQuestion mocks base method. +func (m *MockSiteInfoCommonService) GetSiteQuestion(ctx context.Context) (*schema.SiteQuestionsResp, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSiteQuestion", ctx) + ret0, _ := ret[0].(*schema.SiteQuestionsResp) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSiteQuestion indicates an expected call of GetSiteQuestion. +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteQuestion(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteQuestion", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteQuestion), ctx) +} + // GetSiteSeo mocks base method. func (m *MockSiteInfoCommonService) GetSiteSeo(ctx context.Context) (*schema.SiteSeoResp, error) { m.ctrl.T.Helper() @@ -272,11 +300,26 @@ func (m *MockSiteInfoCommonService) GetSiteSeo(ctx context.Context) (*schema.Sit } // GetSiteSeo indicates an expected call of GetSiteSeo. -func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteSeo(ctx any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteSeo(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteSeo", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteSeo), ctx) } +// GetSiteTag mocks base method. +func (m *MockSiteInfoCommonService) GetSiteTag(ctx context.Context) (*schema.SiteTagsResp, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSiteTag", ctx) + ret0, _ := ret[0].(*schema.SiteTagsResp) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSiteTag indicates an expected call of GetSiteTag. +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteTag(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteTag", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteTag), ctx) +} + // GetSiteTheme mocks base method. func (m *MockSiteInfoCommonService) GetSiteTheme(ctx context.Context) (*schema.SiteThemeResp, error) { m.ctrl.T.Helper() @@ -287,7 +330,7 @@ func (m *MockSiteInfoCommonService) GetSiteTheme(ctx context.Context) (*schema.S } // GetSiteTheme indicates an expected call of GetSiteTheme. -func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteTheme(ctx any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteTheme(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteTheme", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteTheme), ctx) } @@ -302,7 +345,7 @@ func (m *MockSiteInfoCommonService) GetSiteUsers(ctx context.Context) (*schema.S } // GetSiteUsers indicates an expected call of GetSiteUsers. -func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteUsers(ctx any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteUsers(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteUsers", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteUsers), ctx) } @@ -317,7 +360,7 @@ func (m *MockSiteInfoCommonService) GetSiteWrite(ctx context.Context) (*schema.S } // GetSiteWrite indicates an expected call of GetSiteWrite. -func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteWrite(ctx any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteWrite(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteWrite", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteWrite), ctx) } @@ -331,7 +374,7 @@ func (m *MockSiteInfoCommonService) IsBrandingFileUsed(ctx context.Context, file } // IsBrandingFileUsed indicates an expected call of IsBrandingFileUsed. -func (mr *MockSiteInfoCommonServiceMockRecorder) IsBrandingFileUsed(ctx, filePath any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) IsBrandingFileUsed(ctx, filePath interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsBrandingFileUsed", reflect.TypeOf((*MockSiteInfoCommonService)(nil).IsBrandingFileUsed), ctx, filePath) } diff --git a/internal/service/question_common/question.go b/internal/service/question_common/question.go index 557a5db15..3a7306342 100644 --- a/internal/service/question_common/question.go +++ b/internal/service/question_common/question.go @@ -900,7 +900,7 @@ func (qs *QuestionCommon) tryToGetQuestionIDFromMsg(ctx context.Context, closeMs } func (qs *QuestionCommon) GetMinimumContentLength(ctx context.Context) (int, error) { - siteInfo, err := qs.siteInfoService.GetSiteWrite(ctx) + siteInfo, err := qs.siteInfoService.GetSiteQuestion(ctx) if err != nil { return 6, err } diff --git a/internal/service/siteinfo/siteinfo_service.go b/internal/service/siteinfo/siteinfo_service.go index f355d09f4..b633ed421 100644 --- a/internal/service/siteinfo/siteinfo_service.go +++ b/internal/service/siteinfo/siteinfo_service.go @@ -103,17 +103,14 @@ func (s *SiteInfoService) GetSiteUsers(ctx context.Context) (resp *schema.SiteUs return s.siteInfoCommonService.GetSiteUsers(ctx) } -// GetSiteWrite get site info write -func (s *SiteInfoService) GetSiteWrite(ctx context.Context) (resp *schema.SiteWriteResp, err error) { - resp = &schema.SiteWriteResp{} - siteInfo, exist, err := s.siteInfoRepo.GetByType(ctx, constant.SiteTypeWrite) +// GetSiteTag get site info write +func (s *SiteInfoService) GetSiteTag(ctx context.Context) (resp *schema.SiteTagsResp, err error) { + resp = &schema.SiteTagsResp{} + _, err = s.siteInfoCommonService.GetSiteTag(ctx) if err != nil { log.Error(err) return resp, nil } - if exist { - _ = json.Unmarshal([]byte(siteInfo.Content), resp) - } resp.RecommendTags, err = s.tagCommonService.GetSiteWriteRecommendTag(ctx) if err != nil { @@ -126,6 +123,16 @@ func (s *SiteInfoService) GetSiteWrite(ctx context.Context) (resp *schema.SiteWr return resp, nil } +// GetSiteQuestion get site questions settings +func (s *SiteInfoService) GetSiteQuestion(ctx context.Context) (resp *schema.SiteQuestionsResp, err error) { + return s.siteInfoCommonService.GetSiteQuestion(ctx) +} + +// GetSiteAdvanced get site advanced settings +func (s *SiteInfoService) GetSiteAdvanced(ctx context.Context) (resp *schema.SiteAdvancedResp, err error) { + return s.siteInfoCommonService.GetSiteAdvanced(ctx) +} + // GetSiteLegal get site legal info func (s *SiteInfoService) GetSiteLegal(ctx context.Context) (resp *schema.SiteLegalResp, err error) { return s.siteInfoCommonService.GetSiteLegal(ctx) @@ -182,8 +189,30 @@ func (s *SiteInfoService) SaveSiteBranding(ctx context.Context, req *schema.Site return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeBranding, data) } -// SaveSiteWrite save site configuration about write -func (s *SiteInfoService) SaveSiteWrite(ctx context.Context, req *schema.SiteWriteReq) (resp any, err error) { +// SaveSiteAdvanced save site advanced configuration +func (s *SiteInfoService) SaveSiteAdvanced(ctx context.Context, req *schema.SiteAdvancedReq) (resp any, err error) { + content, _ := json.Marshal(req) + data := &entity.SiteInfo{ + Type: constant.SiteTypeAdvanced, + Content: string(content), + Status: 1, + } + return nil, s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeAdvanced, data) +} + +// SaveSiteQuestions save site questions configuration +func (s *SiteInfoService) SaveSiteQuestions(ctx context.Context, req *schema.SiteQuestionsReq) (resp any, err error) { + content, _ := json.Marshal(req) + data := &entity.SiteInfo{ + Type: constant.SiteTypeQuestions, + Content: string(content), + Status: 1, + } + return nil, s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeQuestions, data) +} + +// SaveSiteTags save site tags configuration +func (s *SiteInfoService) SaveSiteTags(ctx context.Context, req *schema.SiteTagsReq) (resp any, err error) { recommendTags, reservedTags := make([]string, 0), make([]string, 0) recommendTagMapping, reservedTagMapping := make(map[string]bool), make(map[string]bool) for _, tag := range req.ReservedTags { @@ -210,11 +239,11 @@ func (s *SiteInfoService) SaveSiteWrite(ctx context.Context, req *schema.SiteWri content, _ := json.Marshal(req) data := &entity.SiteInfo{ - Type: constant.SiteTypeWrite, + Type: constant.SiteTypeTags, Content: string(content), Status: 1, } - return nil, s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeWrite, data) + return nil, s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeTags, data) } // SaveSiteLegal save site legal configuration diff --git a/internal/service/siteinfo_common/siteinfo_service.go b/internal/service/siteinfo_common/siteinfo_service.go index fda117229..87bc7ee15 100644 --- a/internal/service/siteinfo_common/siteinfo_service.go +++ b/internal/service/siteinfo_common/siteinfo_service.go @@ -51,6 +51,9 @@ type SiteInfoCommonService interface { FormatAvatar(ctx context.Context, originalAvatarData, email string, userStatus int) *schema.AvatarInfo FormatListAvatar(ctx context.Context, userList []*entity.User) (userID2AvatarMapping map[string]*schema.AvatarInfo) GetSiteWrite(ctx context.Context) (resp *schema.SiteWriteResp, err error) + GetSiteAdvanced(ctx context.Context) (resp *schema.SiteAdvancedResp, err error) + GetSiteQuestion(ctx context.Context) (resp *schema.SiteQuestionsResp, err error) + GetSiteTag(ctx context.Context) (resp *schema.SiteTagsResp, err error) GetSiteLegal(ctx context.Context) (resp *schema.SiteLegalResp, err error) GetSiteLogin(ctx context.Context) (resp *schema.SiteLoginResp, err error) GetSiteCustomCssHTML(ctx context.Context) (resp *schema.SiteCustomCssHTMLResp, err error) @@ -167,6 +170,33 @@ func (s *siteInfoCommonService) GetSiteWrite(ctx context.Context) (resp *schema. return resp, nil } +// GetSiteAdvanced get site info advanced +func (s *siteInfoCommonService) GetSiteAdvanced(ctx context.Context) (resp *schema.SiteAdvancedResp, err error) { + resp = &schema.SiteAdvancedResp{} + if err = s.GetSiteInfoByType(ctx, constant.SiteTypeAdvanced, resp); err != nil { + return nil, err + } + return resp, nil +} + +// GetSiteQuestion get site info question +func (s *siteInfoCommonService) GetSiteQuestion(ctx context.Context) (resp *schema.SiteQuestionsResp, err error) { + resp = &schema.SiteQuestionsResp{} + if err = s.GetSiteInfoByType(ctx, constant.SiteTypeQuestions, resp); err != nil { + return nil, err + } + return resp, nil +} + +// GetSiteTag get site info tag +func (s *siteInfoCommonService) GetSiteTag(ctx context.Context) (resp *schema.SiteTagsResp, err error) { + resp = &schema.SiteTagsResp{} + if err = s.GetSiteInfoByType(ctx, constant.SiteTypeTags, resp); err != nil { + return nil, err + } + return resp, nil +} + // GetSiteLegal get site info write func (s *siteInfoCommonService) GetSiteLegal(ctx context.Context) (resp *schema.SiteLegalResp, err error) { resp = &schema.SiteLegalResp{} diff --git a/internal/service/tag_common/tag_common.go b/internal/service/tag_common/tag_common.go index 9ca8e100f..0da9f6fd5 100644 --- a/internal/service/tag_common/tag_common.go +++ b/internal/service/tag_common/tag_common.go @@ -270,7 +270,7 @@ func (ts *TagCommonService) GetTagListByNames(ctx context.Context, tagNames []st } func (ts *TagCommonService) ExistRecommend(ctx context.Context, tags []*schema.TagItem) (bool, error) { - taginfo, err := ts.siteInfoService.GetSiteWrite(ctx) + taginfo, err := ts.siteInfoService.GetSiteTag(ctx) if err != nil { return false, err } @@ -295,7 +295,7 @@ func (ts *TagCommonService) ExistRecommend(ctx context.Context, tags []*schema.T } func (ts *TagCommonService) GetMinimumTags(ctx context.Context) (int, error) { - siteInfo, err := ts.siteInfoService.GetSiteWrite(ctx) + siteInfo, err := ts.siteInfoService.GetSiteTag(ctx) if err != nil { return 1, err } @@ -469,7 +469,7 @@ func (ts *TagCommonService) TagsFormatRecommendAndReserved(ctx context.Context, if len(tagList) == 0 { return } - tagConfig, err := ts.siteInfoService.GetSiteWrite(ctx) + tagConfig, err := ts.siteInfoService.GetSiteTag(ctx) if err != nil { log.Error(err) return @@ -485,7 +485,7 @@ func (ts *TagCommonService) tagFormatRecommendAndReserved(ctx context.Context, t if tag == nil { return } - tagConfig, err := ts.siteInfoService.GetSiteWrite(ctx) + tagConfig, err := ts.siteInfoService.GetSiteTag(ctx) if err != nil { log.Error(err) return diff --git a/internal/service/uploader/upload.go b/internal/service/uploader/upload.go index 8dea746ce..58f808468 100644 --- a/internal/service/uploader/upload.go +++ b/internal/service/uploader/upload.go @@ -109,12 +109,12 @@ func (us *uploaderService) UploadAvatarFile(ctx *gin.Context, userID string) (ur return url, nil } - siteWrite, err := us.siteInfoService.GetSiteWrite(ctx) + siteAdvanced, err := us.siteInfoService.GetSiteAdvanced(ctx) if err != nil { return "", err } - ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, siteWrite.GetMaxImageSize()) + ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, siteAdvanced.GetMaxImageSize()) file, fileHeader, err := ctx.Request.FormFile("file") if err != nil { return "", errors.BadRequest(reason.RequestFormatError).WithError(err) @@ -201,12 +201,12 @@ func (us *uploaderService) UploadPostFile(ctx *gin.Context, userID string) ( return url, nil } - siteWrite, err := us.siteInfoService.GetSiteWrite(ctx) + siteAdvanced, err := us.siteInfoService.GetSiteAdvanced(ctx) if err != nil { return "", err } - ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, siteWrite.GetMaxImageSize()) + ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, siteAdvanced.GetMaxImageSize()) file, fileHeader, err := ctx.Request.FormFile("file") if err != nil { return "", errors.BadRequest(reason.RequestFormatError).WithError(err) @@ -214,7 +214,7 @@ func (us *uploaderService) UploadPostFile(ctx *gin.Context, userID string) ( defer func() { _ = file.Close() }() - if checker.IsUnAuthorizedExtension(fileHeader.Filename, siteWrite.AuthorizedImageExtensions) { + if checker.IsUnAuthorizedExtension(fileHeader.Filename, siteAdvanced.AuthorizedImageExtensions) { return "", errors.BadRequest(reason.RequestFormatError).WithError(err) } @@ -239,7 +239,7 @@ func (us *uploaderService) UploadPostAttachment(ctx *gin.Context, userID string) return url, nil } - resp, err := us.siteInfoService.GetSiteWrite(ctx) + resp, err := us.siteInfoService.GetSiteAdvanced(ctx) if err != nil { return "", err } @@ -277,12 +277,12 @@ func (us *uploaderService) UploadBrandingFile(ctx *gin.Context, userID string) ( return url, nil } - siteWrite, err := us.siteInfoService.GetSiteWrite(ctx) + siteAdvanced, err := us.siteInfoService.GetSiteAdvanced(ctx) if err != nil { return "", err } - ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, siteWrite.GetMaxImageSize()) + ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, siteAdvanced.GetMaxImageSize()) file, fileHeader, err := ctx.Request.FormFile("file") if err != nil { return "", errors.BadRequest(reason.RequestFormatError).WithError(err) @@ -311,7 +311,7 @@ func (us *uploaderService) uploadImageFile(ctx *gin.Context, file *multipart.Fil if err != nil { return "", err } - siteWrite, err := us.siteInfoService.GetSiteWrite(ctx) + siteAdvanced, err := us.siteInfoService.GetSiteAdvanced(ctx) if err != nil { return "", err } @@ -328,7 +328,7 @@ func (us *uploaderService) uploadImageFile(ctx *gin.Context, file *multipart.Fil _ = src.Close() }() - if !checker.DecodeAndCheckImageFile(filePath, siteWrite.GetMaxImageMegapixel()) { + if !checker.DecodeAndCheckImageFile(filePath, siteAdvanced.GetMaxImageMegapixel()) { return "", errors.BadRequest(reason.UploadFileUnsupportedFileFormat) } @@ -364,17 +364,17 @@ func (us *uploaderService) uploadAttachmentFile(ctx *gin.Context, file *multipar func (us *uploaderService) tryToUploadByPlugin(ctx *gin.Context, source plugin.UploadSource) ( url string, err error) { - siteWrite, err := us.siteInfoService.GetSiteWrite(ctx) + siteAdvanced, err := us.siteInfoService.GetSiteAdvanced(ctx) if err != nil { return "", err } cond := plugin.UploadFileCondition{ Source: source, - MaxImageSize: siteWrite.MaxImageSize, - MaxAttachmentSize: siteWrite.MaxAttachmentSize, - MaxImageMegapixel: siteWrite.MaxImageMegapixel, - AuthorizedImageExtensions: siteWrite.AuthorizedImageExtensions, - AuthorizedAttachmentExtensions: siteWrite.AuthorizedAttachmentExtensions, + MaxImageSize: siteAdvanced.MaxImageSize, + MaxAttachmentSize: siteAdvanced.MaxAttachmentSize, + MaxImageMegapixel: siteAdvanced.MaxImageMegapixel, + AuthorizedImageExtensions: siteAdvanced.AuthorizedImageExtensions, + AuthorizedAttachmentExtensions: siteAdvanced.AuthorizedAttachmentExtensions, } _ = plugin.CallStorage(func(fn plugin.Storage) error { resp := fn.UploadFile(ctx, cond) From 3cd3e4a888dca7829b3489efc7cf844e8f0403e5 Mon Sep 17 00:00:00 2001 From: kumfo Date: Tue, 20 Jan 2026 15:32:45 +0800 Subject: [PATCH 02/19] feat(menu): update admin menu settings to include questions, tags, and advanced options --- docs/docs.go | 457 ++++++++++++++++++++++++++++++---------------- docs/swagger.json | 438 ++++++++++++++++++++++++++++++-------------- docs/swagger.yaml | 315 ++++++++++++++++++++------------ 3 files changed, 804 insertions(+), 406 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index 5e9d5b39d..73d85436c 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1,22 +1,3 @@ -/* - * 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 docs Code generated by swaggo/swag. DO NOT EDIT package docs @@ -875,6 +856,77 @@ const docTemplate = `{ } } }, + "/answer/admin/api/siteinfo/advanced": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get site advanced setting", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "get site advanced setting", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SiteAdvancedResp" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "update site advanced info", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "update site advanced info", + "parameters": [ + { + "description": "advanced settings", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.SiteAdvancedReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, "/answer/admin/api/siteinfo/branding": { "get": { "security": [ @@ -1301,6 +1353,77 @@ const docTemplate = `{ } } }, + "/answer/admin/api/siteinfo/question": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get site questions setting", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "get site questions setting", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SiteQuestionsResp" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "update site question settings", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "update site question settings", + "parameters": [ + { + "description": "questions settings", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.SiteQuestionsReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, "/answer/admin/api/siteinfo/seo": { "get": { "security": [ @@ -1372,21 +1495,21 @@ const docTemplate = `{ } } }, - "/answer/admin/api/siteinfo/theme": { + "/answer/admin/api/siteinfo/tag": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "get site info theme config", + "description": "get site tags setting", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "get site info theme config", + "summary": "get site tags setting", "responses": { "200": { "description": "OK", @@ -1399,7 +1522,7 @@ const docTemplate = `{ "type": "object", "properties": { "data": { - "$ref": "#/definitions/schema.SiteThemeResp" + "$ref": "#/definitions/schema.SiteTagsResp" } } } @@ -1414,22 +1537,22 @@ const docTemplate = `{ "ApiKeyAuth": [] } ], - "description": "update site custom css html config", + "description": "update site tag settings", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "update site custom css html config", + "summary": "update site tag settings", "parameters": [ { - "description": "login info", + "description": "tags settings", "name": "data", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/schema.SiteThemeReq" + "$ref": "#/definitions/schema.SiteTagsReq" } } ], @@ -1443,21 +1566,21 @@ const docTemplate = `{ } } }, - "/answer/admin/api/siteinfo/users": { + "/answer/admin/api/siteinfo/theme": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "get site user config", + "description": "get site info theme config", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "get site user config", + "summary": "get site info theme config", "responses": { "200": { "description": "OK", @@ -1470,7 +1593,7 @@ const docTemplate = `{ "type": "object", "properties": { "data": { - "$ref": "#/definitions/schema.SiteUsersResp" + "$ref": "#/definitions/schema.SiteThemeResp" } } } @@ -1485,22 +1608,22 @@ const docTemplate = `{ "ApiKeyAuth": [] } ], - "description": "update site info config about users", + "description": "update site custom css html config", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "update site info config about users", + "summary": "update site custom css html config", "parameters": [ { - "description": "users info", + "description": "login info", "name": "data", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/schema.SiteUsersReq" + "$ref": "#/definitions/schema.SiteThemeReq" } } ], @@ -1514,21 +1637,21 @@ const docTemplate = `{ } } }, - "/answer/admin/api/siteinfo/write": { + "/answer/admin/api/siteinfo/users": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "get site interface", + "description": "get site user config", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "get site interface", + "summary": "get site user config", "responses": { "200": { "description": "OK", @@ -1541,7 +1664,7 @@ const docTemplate = `{ "type": "object", "properties": { "data": { - "$ref": "#/definitions/schema.SiteWriteResp" + "$ref": "#/definitions/schema.SiteUsersResp" } } } @@ -1556,22 +1679,22 @@ const docTemplate = `{ "ApiKeyAuth": [] } ], - "description": "update site write info", + "description": "update site info config about users", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "update site write info", + "summary": "update site info config about users", "parameters": [ { - "description": "write info", + "description": "users info", "name": "data", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/schema.SiteWriteReq" + "$ref": "#/definitions/schema.SiteUsersReq" } } ], @@ -10497,6 +10620,58 @@ const docTemplate = `{ } } }, + "schema.SiteAdvancedReq": { + "type": "object", + "properties": { + "authorized_attachment_extensions": { + "type": "array", + "items": { + "type": "string" + } + }, + "authorized_image_extensions": { + "type": "array", + "items": { + "type": "string" + } + }, + "max_attachment_size": { + "type": "integer" + }, + "max_image_megapixel": { + "type": "integer" + }, + "max_image_size": { + "type": "integer" + } + } + }, + "schema.SiteAdvancedResp": { + "type": "object", + "properties": { + "authorized_attachment_extensions": { + "type": "array", + "items": { + "type": "string" + } + }, + "authorized_image_extensions": { + "type": "array", + "items": { + "type": "string" + } + }, + "max_attachment_size": { + "type": "integer" + }, + "max_image_megapixel": { + "type": "integer" + }, + "max_image_size": { + "type": "integer" + } + } + }, "schema.SiteBrandingReq": { "type": "object", "properties": { @@ -10676,18 +10851,24 @@ const docTemplate = `{ "revision": { "type": "string" }, + "site_advanced": { + "$ref": "#/definitions/schema.SiteAdvancedResp" + }, "site_legal": { "$ref": "#/definitions/schema.SiteLegalSimpleResp" }, + "site_questions": { + "$ref": "#/definitions/schema.SiteQuestionsResp" + }, "site_seo": { "$ref": "#/definitions/schema.SiteSeoResp" }, + "site_tags": { + "$ref": "#/definitions/schema.SiteTagsResp" + }, "site_users": { "$ref": "#/definitions/schema.SiteUsersResp" }, - "site_write": { - "$ref": "#/definitions/schema.SiteWriteResp" - }, "theme": { "$ref": "#/definitions/schema.SiteThemeResp" }, @@ -10867,6 +11048,32 @@ const docTemplate = `{ } } }, + "schema.SiteQuestionsReq": { + "type": "object", + "properties": { + "min_content": { + "type": "integer", + "maximum": 65535, + "minimum": 0 + }, + "restrict_answer": { + "type": "boolean" + } + } + }, + "schema.SiteQuestionsResp": { + "type": "object", + "properties": { + "min_content": { + "type": "integer", + "maximum": 65535, + "minimum": 0 + }, + "restrict_answer": { + "type": "boolean" + } + } + }, "schema.SiteSeoReq": { "type": "object", "required": [ @@ -10901,6 +11108,56 @@ const docTemplate = `{ } } }, + "schema.SiteTagsReq": { + "type": "object", + "properties": { + "min_tags": { + "type": "integer", + "maximum": 5, + "minimum": 0 + }, + "recommend_tags": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.SiteWriteTag" + } + }, + "required_tag": { + "type": "boolean" + }, + "reserved_tags": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.SiteWriteTag" + } + } + } + }, + "schema.SiteTagsResp": { + "type": "object", + "properties": { + "min_tags": { + "type": "integer", + "maximum": 5, + "minimum": 0 + }, + "recommend_tags": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.SiteWriteTag" + } + }, + "required_tag": { + "type": "boolean" + }, + "reserved_tags": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.SiteWriteTag" + } + } + } + }, "schema.SiteThemeReq": { "type": "object", "required": [ @@ -11014,114 +11271,6 @@ const docTemplate = `{ } } }, - "schema.SiteWriteReq": { - "type": "object", - "properties": { - "authorized_attachment_extensions": { - "type": "array", - "items": { - "type": "string" - } - }, - "authorized_image_extensions": { - "type": "array", - "items": { - "type": "string" - } - }, - "max_attachment_size": { - "type": "integer" - }, - "max_image_megapixel": { - "type": "integer" - }, - "max_image_size": { - "type": "integer" - }, - "min_content": { - "type": "integer", - "maximum": 65535, - "minimum": 0 - }, - "min_tags": { - "type": "integer", - "maximum": 5, - "minimum": 0 - }, - "recommend_tags": { - "type": "array", - "items": { - "$ref": "#/definitions/schema.SiteWriteTag" - } - }, - "required_tag": { - "type": "boolean" - }, - "reserved_tags": { - "type": "array", - "items": { - "$ref": "#/definitions/schema.SiteWriteTag" - } - }, - "restrict_answer": { - "type": "boolean" - } - } - }, - "schema.SiteWriteResp": { - "type": "object", - "properties": { - "authorized_attachment_extensions": { - "type": "array", - "items": { - "type": "string" - } - }, - "authorized_image_extensions": { - "type": "array", - "items": { - "type": "string" - } - }, - "max_attachment_size": { - "type": "integer" - }, - "max_image_megapixel": { - "type": "integer" - }, - "max_image_size": { - "type": "integer" - }, - "min_content": { - "type": "integer", - "maximum": 65535, - "minimum": 0 - }, - "min_tags": { - "type": "integer", - "maximum": 5, - "minimum": 0 - }, - "recommend_tags": { - "type": "array", - "items": { - "$ref": "#/definitions/schema.SiteWriteTag" - } - }, - "required_tag": { - "type": "boolean" - }, - "reserved_tags": { - "type": "array", - "items": { - "$ref": "#/definitions/schema.SiteWriteTag" - } - }, - "restrict_answer": { - "type": "boolean" - } - } - }, "schema.SiteWriteTag": { "type": "object", "required": [ diff --git a/docs/swagger.json b/docs/swagger.json index e0f6378e4..6dbecc50a 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -848,6 +848,77 @@ } } }, + "/answer/admin/api/siteinfo/advanced": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get site advanced setting", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "get site advanced setting", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SiteAdvancedResp" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "update site advanced info", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "update site advanced info", + "parameters": [ + { + "description": "advanced settings", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.SiteAdvancedReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, "/answer/admin/api/siteinfo/branding": { "get": { "security": [ @@ -1274,6 +1345,77 @@ } } }, + "/answer/admin/api/siteinfo/question": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get site questions setting", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "get site questions setting", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SiteQuestionsResp" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "update site question settings", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "update site question settings", + "parameters": [ + { + "description": "questions settings", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.SiteQuestionsReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, "/answer/admin/api/siteinfo/seo": { "get": { "security": [ @@ -1345,21 +1487,21 @@ } } }, - "/answer/admin/api/siteinfo/theme": { + "/answer/admin/api/siteinfo/tag": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "get site info theme config", + "description": "get site tags setting", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "get site info theme config", + "summary": "get site tags setting", "responses": { "200": { "description": "OK", @@ -1372,7 +1514,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/definitions/schema.SiteThemeResp" + "$ref": "#/definitions/schema.SiteTagsResp" } } } @@ -1387,22 +1529,22 @@ "ApiKeyAuth": [] } ], - "description": "update site custom css html config", + "description": "update site tag settings", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "update site custom css html config", + "summary": "update site tag settings", "parameters": [ { - "description": "login info", + "description": "tags settings", "name": "data", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/schema.SiteThemeReq" + "$ref": "#/definitions/schema.SiteTagsReq" } } ], @@ -1416,21 +1558,21 @@ } } }, - "/answer/admin/api/siteinfo/users": { + "/answer/admin/api/siteinfo/theme": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "get site user config", + "description": "get site info theme config", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "get site user config", + "summary": "get site info theme config", "responses": { "200": { "description": "OK", @@ -1443,7 +1585,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/definitions/schema.SiteUsersResp" + "$ref": "#/definitions/schema.SiteThemeResp" } } } @@ -1458,22 +1600,22 @@ "ApiKeyAuth": [] } ], - "description": "update site info config about users", + "description": "update site custom css html config", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "update site info config about users", + "summary": "update site custom css html config", "parameters": [ { - "description": "users info", + "description": "login info", "name": "data", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/schema.SiteUsersReq" + "$ref": "#/definitions/schema.SiteThemeReq" } } ], @@ -1487,21 +1629,21 @@ } } }, - "/answer/admin/api/siteinfo/write": { + "/answer/admin/api/siteinfo/users": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "get site interface", + "description": "get site user config", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "get site interface", + "summary": "get site user config", "responses": { "200": { "description": "OK", @@ -1514,7 +1656,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/definitions/schema.SiteWriteResp" + "$ref": "#/definitions/schema.SiteUsersResp" } } } @@ -1529,22 +1671,22 @@ "ApiKeyAuth": [] } ], - "description": "update site write info", + "description": "update site info config about users", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "update site write info", + "summary": "update site info config about users", "parameters": [ { - "description": "write info", + "description": "users info", "name": "data", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/schema.SiteWriteReq" + "$ref": "#/definitions/schema.SiteUsersReq" } } ], @@ -10470,6 +10612,58 @@ } } }, + "schema.SiteAdvancedReq": { + "type": "object", + "properties": { + "authorized_attachment_extensions": { + "type": "array", + "items": { + "type": "string" + } + }, + "authorized_image_extensions": { + "type": "array", + "items": { + "type": "string" + } + }, + "max_attachment_size": { + "type": "integer" + }, + "max_image_megapixel": { + "type": "integer" + }, + "max_image_size": { + "type": "integer" + } + } + }, + "schema.SiteAdvancedResp": { + "type": "object", + "properties": { + "authorized_attachment_extensions": { + "type": "array", + "items": { + "type": "string" + } + }, + "authorized_image_extensions": { + "type": "array", + "items": { + "type": "string" + } + }, + "max_attachment_size": { + "type": "integer" + }, + "max_image_megapixel": { + "type": "integer" + }, + "max_image_size": { + "type": "integer" + } + } + }, "schema.SiteBrandingReq": { "type": "object", "properties": { @@ -10649,18 +10843,24 @@ "revision": { "type": "string" }, + "site_advanced": { + "$ref": "#/definitions/schema.SiteAdvancedResp" + }, "site_legal": { "$ref": "#/definitions/schema.SiteLegalSimpleResp" }, + "site_questions": { + "$ref": "#/definitions/schema.SiteQuestionsResp" + }, "site_seo": { "$ref": "#/definitions/schema.SiteSeoResp" }, + "site_tags": { + "$ref": "#/definitions/schema.SiteTagsResp" + }, "site_users": { "$ref": "#/definitions/schema.SiteUsersResp" }, - "site_write": { - "$ref": "#/definitions/schema.SiteWriteResp" - }, "theme": { "$ref": "#/definitions/schema.SiteThemeResp" }, @@ -10840,6 +11040,32 @@ } } }, + "schema.SiteQuestionsReq": { + "type": "object", + "properties": { + "min_content": { + "type": "integer", + "maximum": 65535, + "minimum": 0 + }, + "restrict_answer": { + "type": "boolean" + } + } + }, + "schema.SiteQuestionsResp": { + "type": "object", + "properties": { + "min_content": { + "type": "integer", + "maximum": 65535, + "minimum": 0 + }, + "restrict_answer": { + "type": "boolean" + } + } + }, "schema.SiteSeoReq": { "type": "object", "required": [ @@ -10874,6 +11100,56 @@ } } }, + "schema.SiteTagsReq": { + "type": "object", + "properties": { + "min_tags": { + "type": "integer", + "maximum": 5, + "minimum": 0 + }, + "recommend_tags": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.SiteWriteTag" + } + }, + "required_tag": { + "type": "boolean" + }, + "reserved_tags": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.SiteWriteTag" + } + } + } + }, + "schema.SiteTagsResp": { + "type": "object", + "properties": { + "min_tags": { + "type": "integer", + "maximum": 5, + "minimum": 0 + }, + "recommend_tags": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.SiteWriteTag" + } + }, + "required_tag": { + "type": "boolean" + }, + "reserved_tags": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.SiteWriteTag" + } + } + } + }, "schema.SiteThemeReq": { "type": "object", "required": [ @@ -10987,114 +11263,6 @@ } } }, - "schema.SiteWriteReq": { - "type": "object", - "properties": { - "authorized_attachment_extensions": { - "type": "array", - "items": { - "type": "string" - } - }, - "authorized_image_extensions": { - "type": "array", - "items": { - "type": "string" - } - }, - "max_attachment_size": { - "type": "integer" - }, - "max_image_megapixel": { - "type": "integer" - }, - "max_image_size": { - "type": "integer" - }, - "min_content": { - "type": "integer", - "maximum": 65535, - "minimum": 0 - }, - "min_tags": { - "type": "integer", - "maximum": 5, - "minimum": 0 - }, - "recommend_tags": { - "type": "array", - "items": { - "$ref": "#/definitions/schema.SiteWriteTag" - } - }, - "required_tag": { - "type": "boolean" - }, - "reserved_tags": { - "type": "array", - "items": { - "$ref": "#/definitions/schema.SiteWriteTag" - } - }, - "restrict_answer": { - "type": "boolean" - } - } - }, - "schema.SiteWriteResp": { - "type": "object", - "properties": { - "authorized_attachment_extensions": { - "type": "array", - "items": { - "type": "string" - } - }, - "authorized_image_extensions": { - "type": "array", - "items": { - "type": "string" - } - }, - "max_attachment_size": { - "type": "integer" - }, - "max_image_megapixel": { - "type": "integer" - }, - "max_image_size": { - "type": "integer" - }, - "min_content": { - "type": "integer", - "maximum": 65535, - "minimum": 0 - }, - "min_tags": { - "type": "integer", - "maximum": 5, - "minimum": 0 - }, - "recommend_tags": { - "type": "array", - "items": { - "$ref": "#/definitions/schema.SiteWriteTag" - } - }, - "required_tag": { - "type": "boolean" - }, - "reserved_tags": { - "type": "array", - "items": { - "$ref": "#/definitions/schema.SiteWriteTag" - } - }, - "restrict_answer": { - "type": "boolean" - } - } - }, "schema.SiteWriteTag": { "type": "object", "required": [ diff --git a/docs/swagger.yaml b/docs/swagger.yaml index e0244083b..f4107cd6b 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,20 +1,3 @@ -# 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. - basePath: / definitions: constant.NotificationChannelKey: @@ -2072,6 +2055,40 @@ definitions: required: - user_id type: object + schema.SiteAdvancedReq: + properties: + authorized_attachment_extensions: + items: + type: string + type: array + authorized_image_extensions: + items: + type: string + type: array + max_attachment_size: + type: integer + max_image_megapixel: + type: integer + max_image_size: + type: integer + type: object + schema.SiteAdvancedResp: + properties: + authorized_attachment_extensions: + items: + type: string + type: array + authorized_image_extensions: + items: + type: string + type: array + max_attachment_size: + type: integer + max_image_megapixel: + type: integer + max_image_size: + type: integer + type: object schema.SiteBrandingReq: properties: favicon: @@ -2200,14 +2217,18 @@ definitions: $ref: '#/definitions/schema.SiteLoginResp' revision: type: string + site_advanced: + $ref: '#/definitions/schema.SiteAdvancedResp' site_legal: $ref: '#/definitions/schema.SiteLegalSimpleResp' + site_questions: + $ref: '#/definitions/schema.SiteQuestionsResp' site_seo: $ref: '#/definitions/schema.SiteSeoResp' + site_tags: + $ref: '#/definitions/schema.SiteTagsResp' site_users: $ref: '#/definitions/schema.SiteUsersResp' - site_write: - $ref: '#/definitions/schema.SiteWriteResp' theme: $ref: '#/definitions/schema.SiteThemeResp' version: @@ -2329,6 +2350,24 @@ definitions: login_required: type: boolean type: object + schema.SiteQuestionsReq: + properties: + min_content: + maximum: 65535 + minimum: 0 + type: integer + restrict_answer: + type: boolean + type: object + schema.SiteQuestionsResp: + properties: + min_content: + maximum: 65535 + minimum: 0 + type: integer + restrict_answer: + type: boolean + type: object schema.SiteSeoReq: properties: permalink: @@ -2353,6 +2392,40 @@ definitions: - permalink - robots type: object + schema.SiteTagsReq: + properties: + min_tags: + maximum: 5 + minimum: 0 + type: integer + recommend_tags: + items: + $ref: '#/definitions/schema.SiteWriteTag' + type: array + required_tag: + type: boolean + reserved_tags: + items: + $ref: '#/definitions/schema.SiteWriteTag' + type: array + type: object + schema.SiteTagsResp: + properties: + min_tags: + maximum: 5 + minimum: 0 + type: integer + recommend_tags: + items: + $ref: '#/definitions/schema.SiteWriteTag' + type: array + required_tag: + type: boolean + reserved_tags: + items: + $ref: '#/definitions/schema.SiteWriteTag' + type: array + type: object schema.SiteThemeReq: properties: color_scheme: @@ -2429,80 +2502,6 @@ definitions: required: - default_avatar type: object - schema.SiteWriteReq: - properties: - authorized_attachment_extensions: - items: - type: string - type: array - authorized_image_extensions: - items: - type: string - type: array - max_attachment_size: - type: integer - max_image_megapixel: - type: integer - max_image_size: - type: integer - min_content: - maximum: 65535 - minimum: 0 - type: integer - min_tags: - maximum: 5 - minimum: 0 - type: integer - recommend_tags: - items: - $ref: '#/definitions/schema.SiteWriteTag' - type: array - required_tag: - type: boolean - reserved_tags: - items: - $ref: '#/definitions/schema.SiteWriteTag' - type: array - restrict_answer: - type: boolean - type: object - schema.SiteWriteResp: - properties: - authorized_attachment_extensions: - items: - type: string - type: array - authorized_image_extensions: - items: - type: string - type: array - max_attachment_size: - type: integer - max_image_megapixel: - type: integer - max_image_size: - type: integer - min_content: - maximum: 65535 - minimum: 0 - type: integer - min_tags: - maximum: 5 - minimum: 0 - type: integer - recommend_tags: - items: - $ref: '#/definitions/schema.SiteWriteTag' - type: array - required_tag: - type: boolean - reserved_tags: - items: - $ref: '#/definitions/schema.SiteWriteTag' - type: array - restrict_answer: - type: boolean - type: object schema.SiteWriteTag: properties: display_name: @@ -3702,6 +3701,47 @@ paths: summary: update smtp config tags: - admin + /answer/admin/api/siteinfo/advanced: + get: + description: get site advanced setting + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + $ref: '#/definitions/schema.SiteAdvancedResp' + type: object + security: + - ApiKeyAuth: [] + summary: get site advanced setting + tags: + - admin + put: + description: update site advanced info + parameters: + - description: advanced settings + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.SiteAdvancedReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: update site advanced info + tags: + - admin /answer/admin/api/siteinfo/branding: get: description: get site interface @@ -3948,6 +3988,47 @@ paths: summary: update site login tags: - admin + /answer/admin/api/siteinfo/question: + get: + description: get site questions setting + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + $ref: '#/definitions/schema.SiteQuestionsResp' + type: object + security: + - ApiKeyAuth: [] + summary: get site questions setting + tags: + - admin + put: + description: update site question settings + parameters: + - description: questions settings + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.SiteQuestionsReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: update site question settings + tags: + - admin /answer/admin/api/siteinfo/seo: get: description: get site seo information @@ -3989,9 +4070,9 @@ paths: summary: update site seo information tags: - admin - /answer/admin/api/siteinfo/theme: + /answer/admin/api/siteinfo/tag: get: - description: get site info theme config + description: get site tags setting produces: - application/json responses: @@ -4002,22 +4083,22 @@ paths: - $ref: '#/definitions/handler.RespBody' - properties: data: - $ref: '#/definitions/schema.SiteThemeResp' + $ref: '#/definitions/schema.SiteTagsResp' type: object security: - ApiKeyAuth: [] - summary: get site info theme config + summary: get site tags setting tags: - admin put: - description: update site custom css html config + description: update site tag settings parameters: - - description: login info + - description: tags settings in: body name: data required: true schema: - $ref: '#/definitions/schema.SiteThemeReq' + $ref: '#/definitions/schema.SiteTagsReq' produces: - application/json responses: @@ -4027,12 +4108,12 @@ paths: $ref: '#/definitions/handler.RespBody' security: - ApiKeyAuth: [] - summary: update site custom css html config + summary: update site tag settings tags: - admin - /answer/admin/api/siteinfo/users: + /answer/admin/api/siteinfo/theme: get: - description: get site user config + description: get site info theme config produces: - application/json responses: @@ -4043,22 +4124,22 @@ paths: - $ref: '#/definitions/handler.RespBody' - properties: data: - $ref: '#/definitions/schema.SiteUsersResp' + $ref: '#/definitions/schema.SiteThemeResp' type: object security: - ApiKeyAuth: [] - summary: get site user config + summary: get site info theme config tags: - admin put: - description: update site info config about users + description: update site custom css html config parameters: - - description: users info + - description: login info in: body name: data required: true schema: - $ref: '#/definitions/schema.SiteUsersReq' + $ref: '#/definitions/schema.SiteThemeReq' produces: - application/json responses: @@ -4068,12 +4149,12 @@ paths: $ref: '#/definitions/handler.RespBody' security: - ApiKeyAuth: [] - summary: update site info config about users + summary: update site custom css html config tags: - admin - /answer/admin/api/siteinfo/write: + /answer/admin/api/siteinfo/users: get: - description: get site interface + description: get site user config produces: - application/json responses: @@ -4084,22 +4165,22 @@ paths: - $ref: '#/definitions/handler.RespBody' - properties: data: - $ref: '#/definitions/schema.SiteWriteResp' + $ref: '#/definitions/schema.SiteUsersResp' type: object security: - ApiKeyAuth: [] - summary: get site interface + summary: get site user config tags: - admin put: - description: update site write info + description: update site info config about users parameters: - - description: write info + - description: users info in: body name: data required: true schema: - $ref: '#/definitions/schema.SiteWriteReq' + $ref: '#/definitions/schema.SiteUsersReq' produces: - application/json responses: @@ -4109,7 +4190,7 @@ paths: $ref: '#/definitions/handler.RespBody' security: - ApiKeyAuth: [] - summary: update site write info + summary: update site info config about users tags: - admin /answer/admin/api/theme/options: From 0d7979e901983ae14e97a2404f3e7a83bc51a812 Mon Sep 17 00:00:00 2001 From: kumfo Date: Wed, 21 Jan 2026 09:39:37 +0800 Subject: [PATCH 03/19] feat(siteinfo): add users settings endpoint and update interface settings structure --- docs/docs.go | 127 ++++++++++++++++-- docs/swagger.json | 127 ++++++++++++++++-- docs/swagger.yaml | 81 +++++++++-- internal/base/constant/site_type.go | 18 ++- internal/controller/siteinfo_controller.go | 5 + .../controller_admin/siteinfo_controller.go | 33 ++++- internal/migrations/v30.go | 96 +++++++++++++ internal/router/answer_api_router.go | 4 + internal/schema/siteinfo_schema.go | 58 +++++--- internal/service/siteinfo/siteinfo_service.go | 21 ++- .../siteinfo_common/siteinfo_service.go | 20 ++- 11 files changed, 516 insertions(+), 74 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index 73d85436c..b030c3d3d 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1167,7 +1167,7 @@ const docTemplate = `{ "type": "object", "properties": { "data": { - "$ref": "#/definitions/schema.SiteInterfaceResp" + "$ref": "#/definitions/schema.SiteInterfaceSettingsResp" } } } @@ -1708,6 +1708,77 @@ const docTemplate = `{ } } }, + "/answer/admin/api/siteinfo/users-settings": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get site interface", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "get site interface", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SiteUsersSettingsResp" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "update site info users settings", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "update site info users settings", + "parameters": [ + { + "description": "general", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.SiteUsersSettingsReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, "/answer/admin/api/theme/options": { "get": { "security": [ @@ -10843,7 +10914,7 @@ const docTemplate = `{ "$ref": "#/definitions/schema.SiteGeneralResp" }, "interface": { - "$ref": "#/definitions/schema.SiteInterfaceResp" + "$ref": "#/definitions/schema.SiteInterfaceSettingsResp" }, "login": { "$ref": "#/definitions/schema.SiteLoginResp" @@ -10872,6 +10943,9 @@ const docTemplate = `{ "theme": { "$ref": "#/definitions/schema.SiteThemeResp" }, + "users_settings": { + "$ref": "#/definitions/schema.SiteUsersSettingsResp" + }, "version": { "type": "string" } @@ -10905,24 +10979,13 @@ const docTemplate = `{ } } }, - "schema.SiteInterfaceResp": { + "schema.SiteInterfaceSettingsResp": { "type": "object", "required": [ - "default_avatar", "language", "time_zone" ], "properties": { - "default_avatar": { - "type": "string", - "enum": [ - "system", - "gravatar" - ] - }, - "gravatar_base_url": { - "type": "string" - }, "language": { "type": "string", "maxLength": 128 @@ -11271,6 +11334,42 @@ const docTemplate = `{ } } }, + "schema.SiteUsersSettingsReq": { + "type": "object", + "required": [ + "default_avatar" + ], + "properties": { + "default_avatar": { + "type": "string", + "enum": [ + "system", + "gravatar" + ] + }, + "gravatar_base_url": { + "type": "string" + } + } + }, + "schema.SiteUsersSettingsResp": { + "type": "object", + "required": [ + "default_avatar" + ], + "properties": { + "default_avatar": { + "type": "string", + "enum": [ + "system", + "gravatar" + ] + }, + "gravatar_base_url": { + "type": "string" + } + } + }, "schema.SiteWriteTag": { "type": "object", "required": [ diff --git a/docs/swagger.json b/docs/swagger.json index 6dbecc50a..879302fa8 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1159,7 +1159,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/definitions/schema.SiteInterfaceResp" + "$ref": "#/definitions/schema.SiteInterfaceSettingsResp" } } } @@ -1700,6 +1700,77 @@ } } }, + "/answer/admin/api/siteinfo/users-settings": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get site interface", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "get site interface", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SiteUsersSettingsResp" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "update site info users settings", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "update site info users settings", + "parameters": [ + { + "description": "general", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.SiteUsersSettingsReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, "/answer/admin/api/theme/options": { "get": { "security": [ @@ -10835,7 +10906,7 @@ "$ref": "#/definitions/schema.SiteGeneralResp" }, "interface": { - "$ref": "#/definitions/schema.SiteInterfaceResp" + "$ref": "#/definitions/schema.SiteInterfaceSettingsResp" }, "login": { "$ref": "#/definitions/schema.SiteLoginResp" @@ -10864,6 +10935,9 @@ "theme": { "$ref": "#/definitions/schema.SiteThemeResp" }, + "users_settings": { + "$ref": "#/definitions/schema.SiteUsersSettingsResp" + }, "version": { "type": "string" } @@ -10897,24 +10971,13 @@ } } }, - "schema.SiteInterfaceResp": { + "schema.SiteInterfaceSettingsResp": { "type": "object", "required": [ - "default_avatar", "language", "time_zone" ], "properties": { - "default_avatar": { - "type": "string", - "enum": [ - "system", - "gravatar" - ] - }, - "gravatar_base_url": { - "type": "string" - }, "language": { "type": "string", "maxLength": 128 @@ -11263,6 +11326,42 @@ } } }, + "schema.SiteUsersSettingsReq": { + "type": "object", + "required": [ + "default_avatar" + ], + "properties": { + "default_avatar": { + "type": "string", + "enum": [ + "system", + "gravatar" + ] + }, + "gravatar_base_url": { + "type": "string" + } + } + }, + "schema.SiteUsersSettingsResp": { + "type": "object", + "required": [ + "default_avatar" + ], + "properties": { + "default_avatar": { + "type": "string", + "enum": [ + "system", + "gravatar" + ] + }, + "gravatar_base_url": { + "type": "string" + } + } + }, "schema.SiteWriteTag": { "type": "object", "required": [ diff --git a/docs/swagger.yaml b/docs/swagger.yaml index f4107cd6b..ce16da75e 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2212,7 +2212,7 @@ definitions: general: $ref: '#/definitions/schema.SiteGeneralResp' interface: - $ref: '#/definitions/schema.SiteInterfaceResp' + $ref: '#/definitions/schema.SiteInterfaceSettingsResp' login: $ref: '#/definitions/schema.SiteLoginResp' revision: @@ -2231,6 +2231,8 @@ definitions: $ref: '#/definitions/schema.SiteUsersResp' theme: $ref: '#/definitions/schema.SiteThemeResp' + users_settings: + $ref: '#/definitions/schema.SiteUsersSettingsResp' version: type: string type: object @@ -2254,15 +2256,8 @@ definitions: - language - time_zone type: object - schema.SiteInterfaceResp: + schema.SiteInterfaceSettingsResp: properties: - default_avatar: - enum: - - system - - gravatar - type: string - gravatar_base_url: - type: string language: maxLength: 128 type: string @@ -2270,7 +2265,6 @@ definitions: maxLength: 128 type: string required: - - default_avatar - language - time_zone type: object @@ -2502,6 +2496,30 @@ definitions: required: - default_avatar type: object + schema.SiteUsersSettingsReq: + properties: + default_avatar: + enum: + - system + - gravatar + type: string + gravatar_base_url: + type: string + required: + - default_avatar + type: object + schema.SiteUsersSettingsResp: + properties: + default_avatar: + enum: + - system + - gravatar + type: string + gravatar_base_url: + type: string + required: + - default_avatar + type: object schema.SiteWriteTag: properties: display_name: @@ -3878,7 +3896,7 @@ paths: - $ref: '#/definitions/handler.RespBody' - properties: data: - $ref: '#/definitions/schema.SiteInterfaceResp' + $ref: '#/definitions/schema.SiteInterfaceSettingsResp' type: object security: - ApiKeyAuth: [] @@ -4193,6 +4211,47 @@ paths: summary: update site info config about users tags: - admin + /answer/admin/api/siteinfo/users-settings: + get: + description: get site interface + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + $ref: '#/definitions/schema.SiteUsersSettingsResp' + type: object + security: + - ApiKeyAuth: [] + summary: get site interface + tags: + - admin + put: + description: update site info users settings + parameters: + - description: general + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.SiteUsersSettingsReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: update site info users settings + tags: + - admin /answer/admin/api/theme/options: get: description: Get theme options diff --git a/internal/base/constant/site_type.go b/internal/base/constant/site_type.go index 6106b190c..9bb4e10af 100644 --- a/internal/base/constant/site_type.go +++ b/internal/base/constant/site_type.go @@ -20,9 +20,11 @@ package constant const ( - SiteTypeGeneral = "general" - SiteTypeInterface = "interface" - SiteTypeBranding = "branding" + SiteTypeGeneral = "general" + // Deprecated: split SiteTypeInterfaceSettings and SiteTypeUsersSettings for better clarity + SiteTypeInterface = "interface" + SiteTypeBranding = "branding" + // Deprecated: use SiteTypeAdvanced, SiteTypeQuestions, and SiteTypeTags instead SiteTypeWrite = "write" SiteTypeLegal = "legal" SiteTypeSeo = "seo" @@ -31,7 +33,11 @@ const ( SiteTypeTheme = "theme" SiteTypePrivileges = "privileges" SiteTypeUsers = "users" - SiteTypeAdvanced = "advanced" - SiteTypeQuestions = "questions" - SiteTypeTags = "tags" + + SiteTypeAdvanced = "advanced" + SiteTypeQuestions = "questions" + SiteTypeTags = "tags" + + SiteTypeUsersSettings = "users_settings" + SiteTypeInterfaceSettings = "interface_settings" ) diff --git a/internal/controller/siteinfo_controller.go b/internal/controller/siteinfo_controller.go index 8035275a6..503bfeb68 100644 --- a/internal/controller/siteinfo_controller.go +++ b/internal/controller/siteinfo_controller.go @@ -60,6 +60,11 @@ func (sc *SiteInfoController) GetSiteInfo(ctx *gin.Context) { log.Error(err) } + resp.UsersSettings, err = sc.siteInfoService.GetSiteUsersSettings(ctx) + if err != nil { + log.Error(err) + } + resp.Branding, err = sc.siteInfoService.GetSiteBranding(ctx) if err != nil { log.Error(err) diff --git a/internal/controller_admin/siteinfo_controller.go b/internal/controller_admin/siteinfo_controller.go index bbab97942..056b01ae2 100644 --- a/internal/controller_admin/siteinfo_controller.go +++ b/internal/controller_admin/siteinfo_controller.go @@ -62,13 +62,26 @@ func (sc *SiteInfoController) GetGeneral(ctx *gin.Context) { // @Security ApiKeyAuth // @Tags admin // @Produce json -// @Success 200 {object} handler.RespBody{data=schema.SiteInterfaceResp} +// @Success 200 {object} handler.RespBody{data=schema.SiteInterfaceSettingsResp} // @Router /answer/admin/api/siteinfo/interface [get] func (sc *SiteInfoController) GetInterface(ctx *gin.Context) { resp, err := sc.siteInfoService.GetSiteInterface(ctx) handler.HandleResponse(ctx, err, resp) } +// GetUsersSettings get site interface +// @Summary get site interface +// @Description get site interface +// @Security ApiKeyAuth +// @Tags admin +// @Produce json +// @Success 200 {object} handler.RespBody{data=schema.SiteUsersSettingsResp} +// @Router /answer/admin/api/siteinfo/users-settings [get] +func (sc *SiteInfoController) GetUsersSettings(ctx *gin.Context) { + resp, err := sc.siteInfoService.GetSiteUsersSettings(ctx) + handler.HandleResponse(ctx, err, resp) +} + // GetSiteBranding get site interface // @Summary get site interface // @Description get site interface @@ -287,6 +300,24 @@ func (sc *SiteInfoController) UpdateInterface(ctx *gin.Context) { handler.HandleResponse(ctx, err, nil) } +// UpdateUsersSettings update users settings +// @Summary update site info users settings +// @Description update site info users settings +// @Security ApiKeyAuth +// @Tags admin +// @Produce json +// @Param data body schema.SiteUsersSettingsReq true "general" +// @Success 200 {object} handler.RespBody{} +// @Router /answer/admin/api/siteinfo/users-settings [put] +func (sc *SiteInfoController) UpdateUsersSettings(ctx *gin.Context) { + req := schema.SiteUsersSettingsReq{} + if handler.BindAndCheck(ctx, &req) { + return + } + err := sc.siteInfoService.SaveSiteUsersSettings(ctx, req) + handler.HandleResponse(ctx, err, nil) +} + // UpdateBranding update site branding // @Summary update site info branding // @Description update site info branding diff --git a/internal/migrations/v30.go b/internal/migrations/v30.go index 5d5d5223f..4d0f11304 100644 --- a/internal/migrations/v30.go +++ b/internal/migrations/v30.go @@ -171,3 +171,99 @@ func splitWriteMenu(ctx context.Context, x *xorm.Engine) error { return nil } + +func splitInterfaceMenu(ctx context.Context, x *xorm.Engine) error { + var ( + siteInfo = &entity.SiteInfo{} + siteInfoInterface = &entity.SiteInfo{} + siteInfoUsers = &entity.SiteInfo{} + ) + type SiteInterface struct { + Language string `validate:"required,gt=1,lte=128" form:"language" json:"language"` + TimeZone string `validate:"required,gt=1,lte=128" form:"time_zone" json:"time_zone"` + DefaultAvatar string `validate:"required,oneof=system gravatar" json:"default_avatar"` + GravatarBaseURL string `validate:"omitempty" json:"gravatar_base_url"` + } + + exist, err := x.Context(ctx).Where(builder.Eq{"type": constant.SiteTypeInterface}).Get(siteInfo) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return err + } + if !exist { + return nil + } + oldSiteInterface := &SiteInterface{} + if err := json.Unmarshal([]byte(siteInfo.Content), oldSiteInterface); err != nil { + return err + } + siteUser := &schema.SiteUsersSettingsResp{ + DefaultAvatar: oldSiteInterface.DefaultAvatar, + GravatarBaseURL: oldSiteInterface.GravatarBaseURL, + } + siteInterface := &schema.SiteInterfaceResp{ + Language: oldSiteInterface.Language, + TimeZone: oldSiteInterface.TimeZone, + } + + // save settings + // save user settings + existsUsers, err := x.Context(ctx).Where(builder.Eq{"type": constant.SiteTypeUsersSettings}).Get(siteInfoUsers) + if err != nil { + return err + } + userContent, err := json.Marshal(siteUser) + if err != nil { + return err + } + if existsUsers { + _, err = x.Context(ctx).ID(siteInfoUsers.ID).Update(&entity.SiteInfo{ + Type: constant.SiteTypeUsersSettings, + Content: string(userContent), + Status: 1, + }) + if err != nil { + return err + } + } else { + _, err = x.Context(ctx).Insert(&entity.SiteInfo{ + Type: constant.SiteTypeUsersSettings, + Content: string(userContent), + Status: 1, + }) + if err != nil { + return err + } + } + + // save interface settings + existsInterface, err := x.Context(ctx).Where(builder.Eq{"type": constant.SiteTypeInterfaceSettings}).Get(siteInfoInterface) + if err != nil { + return err + } + interfaceContent, err := json.Marshal(siteInterface) + if err != nil { + return err + } + if existsInterface { + _, err = x.Context(ctx).ID(siteInfoInterface.ID).Update(&entity.SiteInfo{ + Type: constant.SiteTypeInterfaceSettings, + Content: string(interfaceContent), + Status: 1, + }) + if err != nil { + return err + } + } else { + _, err = x.Context(ctx).Insert(&entity.SiteInfo{ + Type: constant.SiteTypeInterfaceSettings, + Content: string(interfaceContent), + Status: 1, + }) + if err != nil { + return err + } + } + + return nil +} diff --git a/internal/router/answer_api_router.go b/internal/router/answer_api_router.go index d717bc9d3..c776b44f0 100644 --- a/internal/router/answer_api_router.go +++ b/internal/router/answer_api_router.go @@ -343,8 +343,12 @@ func (a *AnswerAPIRouter) RegisterAnswerAdminAPIRouter(r *gin.RouterGroup) { // siteinfo r.GET("/siteinfo/general", a.adminSiteInfoController.GetGeneral) r.PUT("/siteinfo/general", a.adminSiteInfoController.UpdateGeneral) + r.GET("/siteinfo/interface", a.adminSiteInfoController.GetInterface) r.PUT("/siteinfo/interface", a.adminSiteInfoController.UpdateInterface) + r.GET("/siteinfo/users-settings", a.adminSiteInfoController.GetUsersSettings) + r.PUT("/siteinfo/users-settings", a.adminSiteInfoController.UpdateUsersSettings) + r.GET("/siteinfo/branding", a.adminSiteInfoController.GetSiteBranding) r.PUT("/siteinfo/branding", a.adminSiteInfoController.UpdateBranding) diff --git a/internal/schema/siteinfo_schema.go b/internal/schema/siteinfo_schema.go index daf49146c..a060558a9 100644 --- a/internal/schema/siteinfo_schema.go +++ b/internal/schema/siteinfo_schema.go @@ -65,6 +65,21 @@ type SiteInterfaceReq struct { GravatarBaseURL string `validate:"omitempty" json:"gravatar_base_url"` } +// SiteInterfaceSettingsReq site interface settings request +type SiteInterfaceSettingsReq struct { + Language string `validate:"required,gt=1,lte=128" json:"language"` + TimeZone string `validate:"required,gt=1,lte=128" json:"time_zone"` +} + +type SiteInterfaceSettingsResp SiteInterfaceSettingsReq + +type SiteUsersSettingsReq struct { + DefaultAvatar string `validate:"required,oneof=system gravatar" json:"default_avatar"` + GravatarBaseURL string `validate:"omitempty" json:"gravatar_base_url"` +} + +type SiteUsersSettingsResp SiteUsersSettingsReq + // SiteBrandingReq site branding request type SiteBrandingReq struct { Logo string `validate:"omitempty,gt=0,lte=512" form:"logo" json:"logo"` @@ -279,27 +294,30 @@ type SiteSeoResp SiteSeoReq // SiteInfoResp get site info response type SiteInfoResp struct { - General *SiteGeneralResp `json:"general"` - Interface *SiteInterfaceResp `json:"interface"` - Branding *SiteBrandingResp `json:"branding"` - Login *SiteLoginResp `json:"login"` - Theme *SiteThemeResp `json:"theme"` - CustomCssHtml *SiteCustomCssHTMLResp `json:"custom_css_html"` - SiteSeo *SiteSeoResp `json:"site_seo"` - SiteUsers *SiteUsersResp `json:"site_users"` - Advanced *SiteAdvancedResp `json:"site_advanced"` - Questions *SiteQuestionsResp `json:"site_questions"` - Tags *SiteTagsResp `json:"site_tags"` - Legal *SiteLegalSimpleResp `json:"site_legal"` - Version string `json:"version"` - Revision string `json:"revision"` -} + General *SiteGeneralResp `json:"general"` + Interface *SiteInterfaceSettingsResp `json:"interface"` + UsersSettings *SiteUsersSettingsResp `json:"users_settings"` + Branding *SiteBrandingResp `json:"branding"` + Login *SiteLoginResp `json:"login"` + Theme *SiteThemeResp `json:"theme"` + CustomCssHtml *SiteCustomCssHTMLResp `json:"custom_css_html"` + SiteSeo *SiteSeoResp `json:"site_seo"` + SiteUsers *SiteUsersResp `json:"site_users"` + Advanced *SiteAdvancedResp `json:"site_advanced"` + Questions *SiteQuestionsResp `json:"site_questions"` + Tags *SiteTagsResp `json:"site_tags"` + Legal *SiteLegalSimpleResp `json:"site_legal"` + Version string `json:"version"` + Revision string `json:"revision"` +} + +// todo: 检查模板使用 type TemplateSiteInfoResp struct { - General *SiteGeneralResp `json:"general"` - Interface *SiteInterfaceResp `json:"interface"` - Branding *SiteBrandingResp `json:"branding"` - SiteSeo *SiteSeoResp `json:"site_seo"` - CustomCssHtml *SiteCustomCssHTMLResp `json:"custom_css_html"` + General *SiteGeneralResp `json:"general"` + Interface *SiteInterfaceSettingsResp `json:"interface"` + Branding *SiteBrandingResp `json:"branding"` + SiteSeo *SiteSeoResp `json:"site_seo"` + CustomCssHtml *SiteCustomCssHTMLResp `json:"custom_css_html"` Title string Year string Canonical string diff --git a/internal/service/siteinfo/siteinfo_service.go b/internal/service/siteinfo/siteinfo_service.go index b633ed421..956511cee 100644 --- a/internal/service/siteinfo/siteinfo_service.go +++ b/internal/service/siteinfo/siteinfo_service.go @@ -89,10 +89,15 @@ func (s *SiteInfoService) GetSiteGeneral(ctx context.Context) (resp *schema.Site } // GetSiteInterface get site info interface -func (s *SiteInfoService) GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceResp, err error) { +func (s *SiteInfoService) GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceSettingsResp, err error) { return s.siteInfoCommonService.GetSiteInterface(ctx) } +// GetSiteUsersSettings get site info users settings +func (s *SiteInfoService) GetSiteUsersSettings(ctx context.Context) (resp *schema.SiteUsersSettingsResp, err error) { + return s.siteInfoCommonService.GetSiteUsersSettings(ctx) +} + // GetSiteBranding get site info branding func (s *SiteInfoService) GetSiteBranding(ctx context.Context) (resp *schema.SiteBrandingResp, err error) { return s.siteInfoCommonService.GetSiteBranding(ctx) @@ -172,10 +177,20 @@ func (s *SiteInfoService) SaveSiteInterface(ctx context.Context, req schema.Site content, _ := json.Marshal(req) data := entity.SiteInfo{ - Type: constant.SiteTypeInterface, + Type: constant.SiteTypeInterfaceSettings, + Content: string(content), + } + return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeInterfaceSettings, &data) +} + +// SaveSiteUsersSettings save site users settings +func (s *SiteInfoService) SaveSiteUsersSettings(ctx context.Context, req schema.SiteUsersSettingsReq) (err error) { + content, _ := json.Marshal(req) + data := entity.SiteInfo{ + Type: constant.SiteTypeInterfaceSettings, Content: string(content), } - return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeInterface, &data) + return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeUsersSettings, &data) } // SaveSiteBranding save site branding information diff --git a/internal/service/siteinfo_common/siteinfo_service.go b/internal/service/siteinfo_common/siteinfo_service.go index 87bc7ee15..dde0114bd 100644 --- a/internal/service/siteinfo_common/siteinfo_service.go +++ b/internal/service/siteinfo_common/siteinfo_service.go @@ -45,7 +45,8 @@ type siteInfoCommonService struct { type SiteInfoCommonService interface { GetSiteGeneral(ctx context.Context) (resp *schema.SiteGeneralResp, err error) - GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceResp, err error) + GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceSettingsResp, err error) + GetSiteUsersSettings(ctx context.Context) (resp *schema.SiteUsersSettingsResp, err error) GetSiteBranding(ctx context.Context) (resp *schema.SiteBrandingResp, err error) GetSiteUsers(ctx context.Context) (resp *schema.SiteUsersResp, err error) FormatAvatar(ctx context.Context, originalAvatarData, email string, userStatus int) *schema.AvatarInfo @@ -81,9 +82,18 @@ func (s *siteInfoCommonService) GetSiteGeneral(ctx context.Context) (resp *schem } // GetSiteInterface get site info interface -func (s *siteInfoCommonService) GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceResp, err error) { - resp = &schema.SiteInterfaceResp{} - if err = s.GetSiteInfoByType(ctx, constant.SiteTypeInterface, resp); err != nil { +func (s *siteInfoCommonService) GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceSettingsResp, err error) { + resp = &schema.SiteInterfaceSettingsResp{} + if err = s.GetSiteInfoByType(ctx, constant.SiteTypeInterfaceSettings, resp); err != nil { + return nil, err + } + return resp, nil +} + +// GetSiteUsersSettings get site info interface +func (s *siteInfoCommonService) GetSiteUsersSettings(ctx context.Context) (resp *schema.SiteUsersSettingsResp, err error) { + resp = &schema.SiteUsersSettingsResp{} + if err = s.GetSiteInfoByType(ctx, constant.SiteTypeUsersSettings, resp); err != nil { return nil, err } return resp, nil @@ -126,7 +136,7 @@ func (s *siteInfoCommonService) FormatListAvatar(ctx context.Context, userList [ func (s *siteInfoCommonService) getAvatarDefaultConfig(ctx context.Context) (string, string) { gravatarBaseURL, defaultAvatar := constant.DefaultGravatarBaseURL, constant.DefaultAvatar - usersConfig, err := s.GetSiteInterface(ctx) + usersConfig, err := s.GetSiteUsersSettings(ctx) if err != nil { log.Error(err) } From f0636d43693897de38d61d221a972c7d03f187a6 Mon Sep 17 00:00:00 2001 From: kumfo Date: Wed, 21 Jan 2026 16:06:17 +0800 Subject: [PATCH 04/19] feat(siteinfo): refactor site legal and security settings to use new policies and security endpoints --- docs/docs.go | 250 ++++++++++++------ docs/swagger.json | 250 ++++++++++++------ docs/swagger.yaml | 171 +++++++----- internal/base/constant/site_type.go | 6 +- internal/base/middleware/auth.go | 4 +- internal/base/middleware/visit_img_auth.go | 4 +- internal/controller/siteinfo_controller.go | 4 +- internal/controller/template_controller.go | 2 +- .../controller_admin/siteinfo_controller.go | 61 +++-- internal/migrations/init.go | 85 ++++-- internal/migrations/v30.go | 217 +++++++++++---- internal/router/answer_api_router.go | 7 +- internal/schema/siteinfo_schema.go | 21 +- .../service/dashboard/dashboard_service.go | 17 +- internal/service/mock/siteinfo_repo_mock.go | 62 +++-- internal/service/siteinfo/siteinfo_service.go | 34 ++- .../siteinfo_common/siteinfo_service.go | 22 +- 17 files changed, 855 insertions(+), 362 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index b030c3d3d..a70cdab09 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1211,21 +1211,21 @@ const docTemplate = `{ } } }, - "/answer/admin/api/siteinfo/legal": { + "/answer/admin/api/siteinfo/login": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "Set the legal information for the site", + "description": "get site info login config", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "Set the legal information for the site", + "summary": "get site info login config", "responses": { "200": { "description": "OK", @@ -1238,7 +1238,7 @@ const docTemplate = `{ "type": "object", "properties": { "data": { - "$ref": "#/definitions/schema.SiteLegalResp" + "$ref": "#/definitions/schema.SiteLoginResp" } } } @@ -1253,22 +1253,22 @@ const docTemplate = `{ "ApiKeyAuth": [] } ], - "description": "update site legal info", + "description": "update site login", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "update site legal info", + "summary": "update site login", "parameters": [ { - "description": "write info", + "description": "login info", "name": "data", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/schema.SiteLegalReq" + "$ref": "#/definitions/schema.SiteLoginReq" } } ], @@ -1282,21 +1282,21 @@ const docTemplate = `{ } } }, - "/answer/admin/api/siteinfo/login": { + "/answer/admin/api/siteinfo/polices": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "get site info login config", + "description": "Get the policies information for the site", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "get site info login config", + "summary": "Get the policies information for the site", "responses": { "200": { "description": "OK", @@ -1309,7 +1309,7 @@ const docTemplate = `{ "type": "object", "properties": { "data": { - "$ref": "#/definitions/schema.SiteLoginResp" + "$ref": "#/definitions/schema.SitePoliciesResp" } } } @@ -1324,22 +1324,22 @@ const docTemplate = `{ "ApiKeyAuth": [] } ], - "description": "update site login", + "description": "update site policies configuration", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "update site login", + "summary": "update site policies configuration", "parameters": [ { - "description": "login info", + "description": "write info", "name": "data", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/schema.SiteLoginReq" + "$ref": "#/definitions/schema.SitePoliciesReq" } } ], @@ -1424,6 +1424,77 @@ const docTemplate = `{ } } }, + "/answer/admin/api/siteinfo/security": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get the security information for the site", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get the security information for the site", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SiteSecurityResp" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "update site security configuration", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "update site security configuration", + "parameters": [ + { + "description": "write info", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.SiteSecurityReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, "/answer/admin/api/siteinfo/seo": { "get": { "security": [ @@ -10843,9 +10914,6 @@ const docTemplate = `{ "site_url" ], "properties": { - "check_update": { - "type": "boolean" - }, "contact_email": { "type": "string", "maxLength": 512 @@ -10876,9 +10944,6 @@ const docTemplate = `{ "site_url" ], "properties": { - "check_update": { - "type": "boolean" - }, "contact_email": { "type": "string", "maxLength": 512 @@ -10917,7 +10982,12 @@ const docTemplate = `{ "$ref": "#/definitions/schema.SiteInterfaceSettingsResp" }, "login": { - "$ref": "#/definitions/schema.SiteLoginResp" + "description": "todo", + "allOf": [ + { + "$ref": "#/definitions/schema.SiteLoginResp" + } + ] }, "revision": { "type": "string" @@ -10996,60 +11066,6 @@ const docTemplate = `{ } } }, - "schema.SiteLegalReq": { - "type": "object", - "required": [ - "external_content_display" - ], - "properties": { - "external_content_display": { - "type": "string", - "enum": [ - "always_display", - "ask_before_display" - ] - }, - "privacy_policy_original_text": { - "type": "string" - }, - "privacy_policy_parsed_text": { - "type": "string" - }, - "terms_of_service_original_text": { - "type": "string" - }, - "terms_of_service_parsed_text": { - "type": "string" - } - } - }, - "schema.SiteLegalResp": { - "type": "object", - "required": [ - "external_content_display" - ], - "properties": { - "external_content_display": { - "type": "string", - "enum": [ - "always_display", - "ask_before_display" - ] - }, - "privacy_policy_original_text": { - "type": "string" - }, - "privacy_policy_parsed_text": { - "type": "string" - }, - "terms_of_service_original_text": { - "type": "string" - }, - "terms_of_service_parsed_text": { - "type": "string" - } - } - }, "schema.SiteLegalSimpleResp": { "type": "object", "required": [ @@ -11082,9 +11098,6 @@ const docTemplate = `{ }, "allow_password_login": { "type": "boolean" - }, - "login_required": { - "type": "boolean" } } }, @@ -11105,9 +11118,40 @@ const docTemplate = `{ }, "allow_password_login": { "type": "boolean" + } + } + }, + "schema.SitePoliciesReq": { + "type": "object", + "properties": { + "privacy_policy_original_text": { + "type": "string" }, - "login_required": { - "type": "boolean" + "privacy_policy_parsed_text": { + "type": "string" + }, + "terms_of_service_original_text": { + "type": "string" + }, + "terms_of_service_parsed_text": { + "type": "string" + } + } + }, + "schema.SitePoliciesResp": { + "type": "object", + "properties": { + "privacy_policy_original_text": { + "type": "string" + }, + "privacy_policy_parsed_text": { + "type": "string" + }, + "terms_of_service_original_text": { + "type": "string" + }, + "terms_of_service_parsed_text": { + "type": "string" } } }, @@ -11137,6 +11181,48 @@ const docTemplate = `{ } } }, + "schema.SiteSecurityReq": { + "type": "object", + "required": [ + "external_content_display" + ], + "properties": { + "check_update": { + "type": "boolean" + }, + "external_content_display": { + "type": "string", + "enum": [ + "always_display", + "ask_before_display" + ] + }, + "login_required": { + "type": "boolean" + } + } + }, + "schema.SiteSecurityResp": { + "type": "object", + "required": [ + "external_content_display" + ], + "properties": { + "check_update": { + "type": "boolean" + }, + "external_content_display": { + "type": "string", + "enum": [ + "always_display", + "ask_before_display" + ] + }, + "login_required": { + "type": "boolean" + } + } + }, "schema.SiteSeoReq": { "type": "object", "required": [ diff --git a/docs/swagger.json b/docs/swagger.json index 879302fa8..05e3302c6 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1203,21 +1203,21 @@ } } }, - "/answer/admin/api/siteinfo/legal": { + "/answer/admin/api/siteinfo/login": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "Set the legal information for the site", + "description": "get site info login config", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "Set the legal information for the site", + "summary": "get site info login config", "responses": { "200": { "description": "OK", @@ -1230,7 +1230,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/definitions/schema.SiteLegalResp" + "$ref": "#/definitions/schema.SiteLoginResp" } } } @@ -1245,22 +1245,22 @@ "ApiKeyAuth": [] } ], - "description": "update site legal info", + "description": "update site login", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "update site legal info", + "summary": "update site login", "parameters": [ { - "description": "write info", + "description": "login info", "name": "data", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/schema.SiteLegalReq" + "$ref": "#/definitions/schema.SiteLoginReq" } } ], @@ -1274,21 +1274,21 @@ } } }, - "/answer/admin/api/siteinfo/login": { + "/answer/admin/api/siteinfo/polices": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "get site info login config", + "description": "Get the policies information for the site", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "get site info login config", + "summary": "Get the policies information for the site", "responses": { "200": { "description": "OK", @@ -1301,7 +1301,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/definitions/schema.SiteLoginResp" + "$ref": "#/definitions/schema.SitePoliciesResp" } } } @@ -1316,22 +1316,22 @@ "ApiKeyAuth": [] } ], - "description": "update site login", + "description": "update site policies configuration", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "update site login", + "summary": "update site policies configuration", "parameters": [ { - "description": "login info", + "description": "write info", "name": "data", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/schema.SiteLoginReq" + "$ref": "#/definitions/schema.SitePoliciesReq" } } ], @@ -1416,6 +1416,77 @@ } } }, + "/answer/admin/api/siteinfo/security": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get the security information for the site", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get the security information for the site", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SiteSecurityResp" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "update site security configuration", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "update site security configuration", + "parameters": [ + { + "description": "write info", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.SiteSecurityReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, "/answer/admin/api/siteinfo/seo": { "get": { "security": [ @@ -10835,9 +10906,6 @@ "site_url" ], "properties": { - "check_update": { - "type": "boolean" - }, "contact_email": { "type": "string", "maxLength": 512 @@ -10868,9 +10936,6 @@ "site_url" ], "properties": { - "check_update": { - "type": "boolean" - }, "contact_email": { "type": "string", "maxLength": 512 @@ -10909,7 +10974,12 @@ "$ref": "#/definitions/schema.SiteInterfaceSettingsResp" }, "login": { - "$ref": "#/definitions/schema.SiteLoginResp" + "description": "todo", + "allOf": [ + { + "$ref": "#/definitions/schema.SiteLoginResp" + } + ] }, "revision": { "type": "string" @@ -10988,60 +11058,6 @@ } } }, - "schema.SiteLegalReq": { - "type": "object", - "required": [ - "external_content_display" - ], - "properties": { - "external_content_display": { - "type": "string", - "enum": [ - "always_display", - "ask_before_display" - ] - }, - "privacy_policy_original_text": { - "type": "string" - }, - "privacy_policy_parsed_text": { - "type": "string" - }, - "terms_of_service_original_text": { - "type": "string" - }, - "terms_of_service_parsed_text": { - "type": "string" - } - } - }, - "schema.SiteLegalResp": { - "type": "object", - "required": [ - "external_content_display" - ], - "properties": { - "external_content_display": { - "type": "string", - "enum": [ - "always_display", - "ask_before_display" - ] - }, - "privacy_policy_original_text": { - "type": "string" - }, - "privacy_policy_parsed_text": { - "type": "string" - }, - "terms_of_service_original_text": { - "type": "string" - }, - "terms_of_service_parsed_text": { - "type": "string" - } - } - }, "schema.SiteLegalSimpleResp": { "type": "object", "required": [ @@ -11074,9 +11090,6 @@ }, "allow_password_login": { "type": "boolean" - }, - "login_required": { - "type": "boolean" } } }, @@ -11097,9 +11110,40 @@ }, "allow_password_login": { "type": "boolean" + } + } + }, + "schema.SitePoliciesReq": { + "type": "object", + "properties": { + "privacy_policy_original_text": { + "type": "string" }, - "login_required": { - "type": "boolean" + "privacy_policy_parsed_text": { + "type": "string" + }, + "terms_of_service_original_text": { + "type": "string" + }, + "terms_of_service_parsed_text": { + "type": "string" + } + } + }, + "schema.SitePoliciesResp": { + "type": "object", + "properties": { + "privacy_policy_original_text": { + "type": "string" + }, + "privacy_policy_parsed_text": { + "type": "string" + }, + "terms_of_service_original_text": { + "type": "string" + }, + "terms_of_service_parsed_text": { + "type": "string" } } }, @@ -11129,6 +11173,48 @@ } } }, + "schema.SiteSecurityReq": { + "type": "object", + "required": [ + "external_content_display" + ], + "properties": { + "check_update": { + "type": "boolean" + }, + "external_content_display": { + "type": "string", + "enum": [ + "always_display", + "ask_before_display" + ] + }, + "login_required": { + "type": "boolean" + } + } + }, + "schema.SiteSecurityResp": { + "type": "object", + "required": [ + "external_content_display" + ], + "properties": { + "check_update": { + "type": "boolean" + }, + "external_content_display": { + "type": "string", + "enum": [ + "always_display", + "ask_before_display" + ] + }, + "login_required": { + "type": "boolean" + } + } + }, "schema.SiteSeoReq": { "type": "object", "required": [ diff --git a/docs/swagger.yaml b/docs/swagger.yaml index ce16da75e..85a05baf0 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2157,8 +2157,6 @@ definitions: type: object schema.SiteGeneralReq: properties: - check_update: - type: boolean contact_email: maxLength: 512 type: string @@ -2181,8 +2179,6 @@ definitions: type: object schema.SiteGeneralResp: properties: - check_update: - type: boolean contact_email: maxLength: 512 type: string @@ -2214,7 +2210,9 @@ definitions: interface: $ref: '#/definitions/schema.SiteInterfaceSettingsResp' login: - $ref: '#/definitions/schema.SiteLoginResp' + allOf: + - $ref: '#/definitions/schema.SiteLoginResp' + description: todo revision: type: string site_advanced: @@ -2268,42 +2266,6 @@ definitions: - language - time_zone type: object - schema.SiteLegalReq: - properties: - external_content_display: - enum: - - always_display - - ask_before_display - type: string - privacy_policy_original_text: - type: string - privacy_policy_parsed_text: - type: string - terms_of_service_original_text: - type: string - terms_of_service_parsed_text: - type: string - required: - - external_content_display - type: object - schema.SiteLegalResp: - properties: - external_content_display: - enum: - - always_display - - ask_before_display - type: string - privacy_policy_original_text: - type: string - privacy_policy_parsed_text: - type: string - terms_of_service_original_text: - type: string - terms_of_service_parsed_text: - type: string - required: - - external_content_display - type: object schema.SiteLegalSimpleResp: properties: external_content_display: @@ -2326,8 +2288,6 @@ definitions: type: boolean allow_password_login: type: boolean - login_required: - type: boolean type: object schema.SiteLoginResp: properties: @@ -2341,8 +2301,28 @@ definitions: type: boolean allow_password_login: type: boolean - login_required: - type: boolean + type: object + schema.SitePoliciesReq: + properties: + privacy_policy_original_text: + type: string + privacy_policy_parsed_text: + type: string + terms_of_service_original_text: + type: string + terms_of_service_parsed_text: + type: string + type: object + schema.SitePoliciesResp: + properties: + privacy_policy_original_text: + type: string + privacy_policy_parsed_text: + type: string + terms_of_service_original_text: + type: string + terms_of_service_parsed_text: + type: string type: object schema.SiteQuestionsReq: properties: @@ -2362,6 +2342,34 @@ definitions: restrict_answer: type: boolean type: object + schema.SiteSecurityReq: + properties: + check_update: + type: boolean + external_content_display: + enum: + - always_display + - ask_before_display + type: string + login_required: + type: boolean + required: + - external_content_display + type: object + schema.SiteSecurityResp: + properties: + check_update: + type: boolean + external_content_display: + enum: + - always_display + - ask_before_display + type: string + login_required: + type: boolean + required: + - external_content_display + type: object schema.SiteSeoReq: properties: permalink: @@ -3924,9 +3932,9 @@ paths: summary: update site info interface tags: - admin - /answer/admin/api/siteinfo/legal: + /answer/admin/api/siteinfo/login: get: - description: Set the legal information for the site + description: get site info login config produces: - application/json responses: @@ -3937,22 +3945,22 @@ paths: - $ref: '#/definitions/handler.RespBody' - properties: data: - $ref: '#/definitions/schema.SiteLegalResp' + $ref: '#/definitions/schema.SiteLoginResp' type: object security: - ApiKeyAuth: [] - summary: Set the legal information for the site + summary: get site info login config tags: - admin put: - description: update site legal info + description: update site login parameters: - - description: write info + - description: login info in: body name: data required: true schema: - $ref: '#/definitions/schema.SiteLegalReq' + $ref: '#/definitions/schema.SiteLoginReq' produces: - application/json responses: @@ -3962,12 +3970,12 @@ paths: $ref: '#/definitions/handler.RespBody' security: - ApiKeyAuth: [] - summary: update site legal info + summary: update site login tags: - admin - /answer/admin/api/siteinfo/login: + /answer/admin/api/siteinfo/polices: get: - description: get site info login config + description: Get the policies information for the site produces: - application/json responses: @@ -3978,22 +3986,22 @@ paths: - $ref: '#/definitions/handler.RespBody' - properties: data: - $ref: '#/definitions/schema.SiteLoginResp' + $ref: '#/definitions/schema.SitePoliciesResp' type: object security: - ApiKeyAuth: [] - summary: get site info login config + summary: Get the policies information for the site tags: - admin put: - description: update site login + description: update site policies configuration parameters: - - description: login info + - description: write info in: body name: data required: true schema: - $ref: '#/definitions/schema.SiteLoginReq' + $ref: '#/definitions/schema.SitePoliciesReq' produces: - application/json responses: @@ -4003,7 +4011,7 @@ paths: $ref: '#/definitions/handler.RespBody' security: - ApiKeyAuth: [] - summary: update site login + summary: update site policies configuration tags: - admin /answer/admin/api/siteinfo/question: @@ -4047,6 +4055,47 @@ paths: summary: update site question settings tags: - admin + /answer/admin/api/siteinfo/security: + get: + description: Get the security information for the site + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + $ref: '#/definitions/schema.SiteSecurityResp' + type: object + security: + - ApiKeyAuth: [] + summary: Get the security information for the site + tags: + - admin + put: + description: update site security configuration + parameters: + - description: write info + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.SiteSecurityReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: update site security configuration + tags: + - admin /answer/admin/api/siteinfo/seo: get: description: get site seo information diff --git a/internal/base/constant/site_type.go b/internal/base/constant/site_type.go index 9bb4e10af..44cd0abd7 100644 --- a/internal/base/constant/site_type.go +++ b/internal/base/constant/site_type.go @@ -25,7 +25,8 @@ const ( SiteTypeInterface = "interface" SiteTypeBranding = "branding" // Deprecated: use SiteTypeAdvanced, SiteTypeQuestions, and SiteTypeTags instead - SiteTypeWrite = "write" + SiteTypeWrite = "write" + // Deprecated: use SiteTypePolicies and SiteTypeSecurity instead SiteTypeLegal = "legal" SiteTypeSeo = "seo" SiteTypeLogin = "login" @@ -40,4 +41,7 @@ const ( SiteTypeUsersSettings = "users_settings" SiteTypeInterfaceSettings = "interface_settings" + + SiteTypePolicies = "policies" + SiteTypeSecurity = "security" ) diff --git a/internal/base/middleware/auth.go b/internal/base/middleware/auth.go index f21837b60..57bbaae21 100644 --- a/internal/base/middleware/auth.go +++ b/internal/base/middleware/auth.go @@ -80,7 +80,7 @@ func (am *AuthUserMiddleware) Auth() gin.HandlerFunc { func (am *AuthUserMiddleware) EjectUserBySiteInfo() gin.HandlerFunc { return func(ctx *gin.Context) { mustLogin := false - siteInfo, _ := am.siteInfoCommonService.GetSiteLogin(ctx) + siteInfo, _ := am.siteInfoCommonService.GetSiteSecurity(ctx) if siteInfo != nil { mustLogin = siteInfo.LoginRequired } @@ -197,7 +197,7 @@ func (am *AuthUserMiddleware) AdminAuth() gin.HandlerFunc { func (am *AuthUserMiddleware) CheckPrivateMode() gin.HandlerFunc { return func(ctx *gin.Context) { - resp, err := am.siteInfoCommonService.GetSiteLogin(ctx) + resp, err := am.siteInfoCommonService.GetSiteSecurity(ctx) if err != nil { ShowIndexPage(ctx) ctx.Abort() diff --git a/internal/base/middleware/visit_img_auth.go b/internal/base/middleware/visit_img_auth.go index 33b62172f..bfd157a92 100644 --- a/internal/base/middleware/visit_img_auth.go +++ b/internal/base/middleware/visit_img_auth.go @@ -41,11 +41,11 @@ func (am *AuthUserMiddleware) VisitAuth() gin.HandlerFunc { return } - siteLogin, err := am.siteInfoCommonService.GetSiteLogin(ctx) + siteSecurity, err := am.siteInfoCommonService.GetSiteSecurity(ctx) if err != nil { return } - if !siteLogin.LoginRequired { + if !siteSecurity.LoginRequired { ctx.Next() return } diff --git a/internal/controller/siteinfo_controller.go b/internal/controller/siteinfo_controller.go index 503bfeb68..64aa02ce7 100644 --- a/internal/controller/siteinfo_controller.go +++ b/internal/controller/siteinfo_controller.go @@ -104,7 +104,7 @@ func (sc *SiteInfoController) GetSiteInfo(ctx *gin.Context) { if err != nil { log.Error(err) } - if legal, err := sc.siteInfoService.GetSiteLegal(ctx); err == nil { + if legal, err := sc.siteInfoService.GetSiteSecurity(ctx); err == nil { resp.Legal = &schema.SiteLegalSimpleResp{ExternalContentDisplay: legal.ExternalContentDisplay} } @@ -124,7 +124,7 @@ func (sc *SiteInfoController) GetSiteLegalInfo(ctx *gin.Context) { if handler.BindAndCheck(ctx, req) { return } - siteLegal, err := sc.siteInfoService.GetSiteLegal(ctx) + siteLegal, err := sc.siteInfoService.GetSitePolicies(ctx) if err != nil { handler.HandleResponse(ctx, err, nil) return diff --git a/internal/controller/template_controller.go b/internal/controller/template_controller.go index e6b94f4f2..31cc5152a 100644 --- a/internal/controller/template_controller.go +++ b/internal/controller/template_controller.go @@ -656,7 +656,7 @@ func (tc *TemplateController) SitemapPage(ctx *gin.Context) { } func (tc *TemplateController) checkPrivateMode(ctx *gin.Context) bool { - resp, err := tc.siteInfoService.GetSiteLogin(ctx) + resp, err := tc.siteInfoService.GetSiteSecurity(ctx) if err != nil { log.Error(err) return false diff --git a/internal/controller_admin/siteinfo_controller.go b/internal/controller_admin/siteinfo_controller.go index 056b01ae2..339d2caa2 100644 --- a/internal/controller_admin/siteinfo_controller.go +++ b/internal/controller_admin/siteinfo_controller.go @@ -134,16 +134,29 @@ func (sc *SiteInfoController) GetSiteAdvanced(ctx *gin.Context) { handler.HandleResponse(ctx, err, resp) } -// GetSiteLegal Set the legal information for the site -// @Summary Set the legal information for the site -// @Description Set the legal information for the site +// GetSitePolicies Get the policies information for the site +// @Summary Get the policies information for the site +// @Description Get the policies information for the site // @Security ApiKeyAuth // @Tags admin // @Produce json -// @Success 200 {object} handler.RespBody{data=schema.SiteLegalResp} -// @Router /answer/admin/api/siteinfo/legal [get] -func (sc *SiteInfoController) GetSiteLegal(ctx *gin.Context) { - resp, err := sc.siteInfoService.GetSiteLegal(ctx) +// @Success 200 {object} handler.RespBody{data=schema.SitePoliciesResp} +// @Router /answer/admin/api/siteinfo/polices [get] +func (sc *SiteInfoController) GetSitePolicies(ctx *gin.Context) { + resp, err := sc.siteInfoService.GetSitePolicies(ctx) + handler.HandleResponse(ctx, err, resp) +} + +// GetSiteSecurity Get the security information for the site +// @Summary Get the security information for the site +// @Description Get the security information for the site +// @Security ApiKeyAuth +// @Tags admin +// @Produce json +// @Success 200 {object} handler.RespBody{data=schema.SiteSecurityResp} +// @Router /answer/admin/api/siteinfo/security [get] +func (sc *SiteInfoController) GetSiteSecurity(ctx *gin.Context) { + resp, err := sc.siteInfoService.GetSiteSecurity(ctx) handler.HandleResponse(ctx, err, resp) } @@ -403,21 +416,39 @@ func (sc *SiteInfoController) UpdateSiteAdvanced(ctx *gin.Context) { handler.HandleResponse(ctx, err, resp) } -// UpdateSiteLegal update site legal info -// @Summary update site legal info -// @Description update site legal info +// UpdateSitePolices update site policies configuration +// @Summary update site policies configuration +// @Description update site policies configuration +// @Security ApiKeyAuth +// @Tags admin +// @Produce json +// @Param data body schema.SitePoliciesReq true "write info" +// @Success 200 {object} handler.RespBody{} +// @Router /answer/admin/api/siteinfo/polices [put] +func (sc *SiteInfoController) UpdateSitePolices(ctx *gin.Context) { + req := &schema.SitePoliciesReq{} + if handler.BindAndCheck(ctx, req) { + return + } + err := sc.siteInfoService.SaveSitePolicies(ctx, req) + handler.HandleResponse(ctx, err, nil) +} + +// UpdateSiteSecurity update site security configuration +// @Summary update site security configuration +// @Description update site security configuration // @Security ApiKeyAuth // @Tags admin // @Produce json -// @Param data body schema.SiteLegalReq true "write info" +// @Param data body schema.SiteSecurityReq true "write info" // @Success 200 {object} handler.RespBody{} -// @Router /answer/admin/api/siteinfo/legal [put] -func (sc *SiteInfoController) UpdateSiteLegal(ctx *gin.Context) { - req := &schema.SiteLegalReq{} +// @Router /answer/admin/api/siteinfo/security [put] +func (sc *SiteInfoController) UpdateSiteSecurity(ctx *gin.Context) { + req := &schema.SiteSecurityReq{} if handler.BindAndCheck(ctx, req) { return } - err := sc.siteInfoService.SaveSiteLegal(ctx, req) + err := sc.siteInfoService.SaveSiteSecurity(ctx, req) handler.HandleResponse(ctx, err, nil) } diff --git a/internal/migrations/init.go b/internal/migrations/init.go index 8a72794fe..97b32a3c8 100644 --- a/internal/migrations/init.go +++ b/internal/migrations/init.go @@ -74,14 +74,17 @@ func (m *Mentor) InitDB() error { m.do("init role power rel", m.initRolePowerRel) m.do("init admin user role rel", m.initAdminUserRoleRel) m.do("init site info interface", m.initSiteInfoInterface) + m.do("init site info users settings", m.initSiteInfoUsersSettings) m.do("init site info general config", m.initSiteInfoGeneralData) m.do("init site info login config", m.initSiteInfoLoginConfig) m.do("init site info theme config", m.initSiteInfoThemeConfig) m.do("init site info seo config", m.initSiteInfoSEOConfig) m.do("init site info user config", m.initSiteInfoUsersConfig) m.do("init site info privilege rank", m.initSiteInfoPrivilegeRank) - m.do("init site info write", m.initSiteInfoWrite) - m.do("init site info legal", m.initSiteInfoLegalConfig) + m.do("init site info write", m.initSiteInfoAdvanced) + m.do("init site info write", m.initSiteInfoQuestions) + m.do("init site info write", m.initSiteInfoTags) + m.do("init site info security", m.initSiteInfoSecurityConfig) m.do("init default content", m.initDefaultContent) m.do("init default badges", m.initDefaultBadges) return m.err @@ -181,19 +184,30 @@ func (m *Mentor) initSiteInfoInterface() { } interfaceData := map[string]string{ - "language": m.userData.Language, - "time_zone": localTimezone, - "default_avatar": "gravatar", - "gravatar_base_url": "https://www.gravatar.com/avatar/", + "language": m.userData.Language, + "time_zone": localTimezone, } interfaceDataBytes, _ := json.Marshal(interfaceData) _, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{ - Type: "interface", + Type: "interface_settings", Content: string(interfaceDataBytes), Status: 1, }) } +func (m *Mentor) initSiteInfoUsersSettings() { + usersSettings := map[string]any{ + "default_avatar": "gravatar", + "gravatar_base_url": "https://www.gravatar.com/avatar/", + } + usersSettingsDataBytes, _ := json.Marshal(usersSettings) + _, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{ + Type: "users_settings", + Content: string(usersSettingsDataBytes), + Status: 1, + }) +} + func (m *Mentor) initSiteInfoGeneralData() { generalData := map[string]string{ "name": m.userData.SiteName, @@ -213,7 +227,6 @@ func (m *Mentor) initSiteInfoLoginConfig() { "allow_new_registrations": true, "allow_email_registrations": true, "allow_password_login": true, - "login_required": m.userData.LoginRequired, } loginConfigDataBytes, _ := json.Marshal(loginConfig) _, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{ @@ -223,14 +236,16 @@ func (m *Mentor) initSiteInfoLoginConfig() { }) } -func (m *Mentor) initSiteInfoLegalConfig() { - legalConfig := map[string]any{ +func (m *Mentor) initSiteInfoSecurityConfig() { + securityConfig := map[string]any{ + "login_required": m.userData.LoginRequired, "external_content_display": m.userData.ExternalContentDisplay, + "check_update": true, } - legalConfigDataBytes, _ := json.Marshal(legalConfig) + securityConfigDataBytes, _ := json.Marshal(securityConfig) _, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{ - Type: "legal", - Content: string(legalConfigDataBytes), + Type: "security", + Content: string(securityConfigDataBytes), Status: 1, }) } @@ -288,24 +303,46 @@ func (m *Mentor) initSiteInfoPrivilegeRank() { }) } -func (m *Mentor) initSiteInfoWrite() { - writeData := map[string]any{ - "min_content": 6, - "restrict_answer": true, - "min_tags": 1, - "required_tag": false, - "recommend_tags": []string{}, - "reserved_tags": []string{}, +func (m *Mentor) initSiteInfoAdvanced() { + advancedData := map[string]any{ "max_image_size": 4, "max_attachment_size": 8, "max_image_megapixel": 40, "authorized_image_extensions": []string{"jpg", "jpeg", "png", "gif", "webp"}, "authorized_attachment_extensions": []string{}, } - writeDataBytes, _ := json.Marshal(writeData) + advancedDataBytes, _ := json.Marshal(advancedData) + _, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{ + Type: "advanced", + Content: string(advancedDataBytes), + Status: 1, + }) +} + +func (m *Mentor) initSiteInfoQuestions() { + questionsData := map[string]any{ + "min_tags": 1, + "min_content": 6, + "restrict_answer": true, + } + questionsDataBytes, _ := json.Marshal(questionsData) + _, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{ + Type: "questions", + Content: string(questionsDataBytes), + Status: 1, + }) +} + +func (m *Mentor) initSiteInfoTags() { + tagsData := map[string]any{ + "required_tag": false, + "recommend_tags": []string{}, + "reserved_tags": []string{}, + } + tagsDataBytes, _ := json.Marshal(tagsData) _, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{ - Type: "write", - Content: string(writeDataBytes), + Type: "tags", + Content: string(tagsDataBytes), Status: 1, }) } diff --git a/internal/migrations/v30.go b/internal/migrations/v30.go index 4d0f11304..c73f501d5 100644 --- a/internal/migrations/v30.go +++ b/internal/migrations/v30.go @@ -37,6 +37,16 @@ func updateAdminMenuSettings(ctx context.Context, x *xorm.Engine) (err error) { if err != nil { return } + + err = splitInterfaceMenu(ctx, x) + if err != nil { + return + } + + err = splitLegalMenu(ctx, x) + if err != nil { + return + } return } @@ -91,16 +101,7 @@ func splitWriteMenu(ctx context.Context, x *xorm.Engine) error { if err != nil { return err } - if existsAdvanced { - _, err = x.Context(ctx).ID(siteInfoAdvanced.ID).Update(&entity.SiteInfo{ - Type: constant.SiteTypeAdvanced, - Content: string(advancedContent), - Status: 1, - }) - if err != nil { - return err - } - } else { + if !existsAdvanced { _, err = x.Context(ctx).Insert(&entity.SiteInfo{ Type: constant.SiteTypeAdvanced, Content: string(advancedContent), @@ -120,16 +121,7 @@ func splitWriteMenu(ctx context.Context, x *xorm.Engine) error { if err != nil { return err } - if existsQuestions { - _, err = x.Context(ctx).ID(siteInfoQuestions.ID).Update(&entity.SiteInfo{ - Type: constant.SiteTypeQuestions, - Content: string(questionsContent), - Status: 1, - }) - if err != nil { - return err - } - } else { + if !existsQuestions { _, err = x.Context(ctx).Insert(&entity.SiteInfo{ Type: constant.SiteTypeQuestions, Content: string(questionsContent), @@ -149,16 +141,7 @@ func splitWriteMenu(ctx context.Context, x *xorm.Engine) error { if err != nil { return err } - if existsTags { - _, err = x.Context(ctx).ID(siteInfoTags.ID).Update(&entity.SiteInfo{ - Type: constant.SiteTypeTags, - Content: string(tagsContent), - Status: 1, - }) - if err != nil { - return err - } - } else { + if !existsTags { _, err = x.Context(ctx).Insert(&entity.SiteInfo{ Type: constant.SiteTypeTags, Content: string(tagsContent), @@ -172,6 +155,7 @@ func splitWriteMenu(ctx context.Context, x *xorm.Engine) error { return nil } +// splitInterfaceMenu splits the site interface settings into interface and user settings func splitInterfaceMenu(ctx context.Context, x *xorm.Engine) error { var ( siteInfo = &entity.SiteInfo{} @@ -216,16 +200,7 @@ func splitInterfaceMenu(ctx context.Context, x *xorm.Engine) error { if err != nil { return err } - if existsUsers { - _, err = x.Context(ctx).ID(siteInfoUsers.ID).Update(&entity.SiteInfo{ - Type: constant.SiteTypeUsersSettings, - Content: string(userContent), - Status: 1, - }) - if err != nil { - return err - } - } else { + if !existsUsers { _, err = x.Context(ctx).Insert(&entity.SiteInfo{ Type: constant.SiteTypeUsersSettings, Content: string(userContent), @@ -245,8 +220,8 @@ func splitInterfaceMenu(ctx context.Context, x *xorm.Engine) error { if err != nil { return err } - if existsInterface { - _, err = x.Context(ctx).ID(siteInfoInterface.ID).Update(&entity.SiteInfo{ + if !existsInterface { + _, err = x.Context(ctx).Insert(&entity.SiteInfo{ Type: constant.SiteTypeInterfaceSettings, Content: string(interfaceContent), Status: 1, @@ -254,10 +229,150 @@ func splitInterfaceMenu(ctx context.Context, x *xorm.Engine) error { if err != nil { return err } - } else { + } + + return nil +} + +// splitLegalMenu splits the site legal settings into policies and security settings +func splitLegalMenu(ctx context.Context, x *xorm.Engine) error { + var ( + siteInfo = &entity.SiteInfo{} + siteInfoPolices = &entity.SiteInfo{} + siteInfoSecurity = &entity.SiteInfo{} + siteInfoLogin = &entity.SiteInfo{} + siteInfoGeneral = &entity.SiteInfo{} + ) + + type SiteLogin struct { + AllowNewRegistrations bool `json:"allow_new_registrations"` + AllowEmailRegistrations bool `json:"allow_email_registrations"` + AllowPasswordLogin bool `json:"allow_password_login"` + LoginRequired bool `json:"login_required"` + AllowEmailDomains []string `json:"allow_email_domains"` + } + + type SiteGeneral struct { + Name string `validate:"required,sanitizer,gt=1,lte=128" form:"name" json:"name"` + ShortDescription string `validate:"omitempty,sanitizer,gt=3,lte=255" form:"short_description" json:"short_description"` + Description string `validate:"omitempty,sanitizer,gt=3,lte=2000" form:"description" json:"description"` + SiteUrl string `validate:"required,sanitizer,gt=1,lte=512,url" form:"site_url" json:"site_url"` + ContactEmail string `validate:"required,sanitizer,gt=1,lte=512,email" form:"contact_email" json:"contact_email"` + CheckUpdate bool `validate:"omitempty,sanitizer" form:"check_update" json:"check_update"` + } + + // find old site legal settings + exist, err := x.Context(ctx).Where(builder.Eq{"type": constant.SiteTypeLegal}).Get(siteInfo) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return err + } + if !exist { + return nil + } + oldSiteLegal := &schema.SiteLegalResp{} + if err := json.Unmarshal([]byte(siteInfo.Content), oldSiteLegal); err != nil { + return err + } + + // find old site login settings + existsLogin, err := x.Context(ctx).Where(builder.Eq{"type": constant.SiteTypeLogin}).Get(siteInfoLogin) + if err != nil { + return err + } + oldSiteLogin := &SiteLogin{} + if err := json.Unmarshal([]byte(siteInfoLogin.Content), oldSiteLogin); err != nil { + return err + } + + // find old site general settings + existGeneral, err := x.Context(ctx).Where(builder.Eq{"type": constant.SiteTypeGeneral}).Get(siteInfoGeneral) + if err != nil { + return err + } + oldSiteGeneral := &SiteGeneral{} + if err := json.Unmarshal([]byte(siteInfoLogin.Content), oldSiteGeneral); err != nil { + return err + } + + sitePolicies := &schema.SitePoliciesResp{ + TermsOfServiceOriginalText: oldSiteLegal.TermsOfServiceOriginalText, + TermsOfServiceParsedText: oldSiteLegal.TermsOfServiceParsedText, + PrivacyPolicyOriginalText: oldSiteLegal.PrivacyPolicyOriginalText, + PrivacyPolicyParsedText: oldSiteLegal.PrivacyPolicyParsedText, + } + siteLogin := &schema.SiteLoginResp{ + AllowNewRegistrations: oldSiteLogin.AllowNewRegistrations, + AllowEmailRegistrations: oldSiteLogin.AllowEmailRegistrations, + AllowPasswordLogin: oldSiteLogin.AllowPasswordLogin, + AllowEmailDomains: oldSiteLogin.AllowEmailDomains, + } + siteGeneral := &schema.SiteGeneralReq{ + Name: oldSiteGeneral.Name, + ShortDescription: oldSiteGeneral.ShortDescription, + Description: oldSiteGeneral.Description, + SiteUrl: oldSiteGeneral.SiteUrl, + ContactEmail: oldSiteGeneral.ContactEmail, + } + siteSecurity := &schema.SiteSecurityResp{ + LoginRequired: oldSiteLogin.LoginRequired, + ExternalContentDisplay: oldSiteLegal.ExternalContentDisplay, + CheckUpdate: oldSiteGeneral.CheckUpdate, + } + if !existsLogin { + siteSecurity.LoginRequired = false + } + if !existGeneral { + siteSecurity.CheckUpdate = true + } + + // save settings + // save policies settings + existsPolicies, err := x.Context(ctx).Where(builder.Eq{"type": constant.SiteTypePolicies}).Get(siteInfoPolices) + if err != nil { + return err + } + policiesContent, err := json.Marshal(sitePolicies) + if err != nil { + return err + } + if !existsPolicies { + _, err = x.Context(ctx).Insert(&entity.SiteInfo{ + Type: constant.SiteTypePolicies, + Content: string(policiesContent), + Status: 1, + }) + if err != nil { + return err + } + } + + // save security settings + existsSecurity, err := x.Context(ctx).Where(builder.Eq{"type": constant.SiteTypeSecurity}).Get(siteInfoSecurity) + if err != nil { + return err + } + securityContent, err := json.Marshal(siteSecurity) + if err != nil { + return err + } + if !existsSecurity { _, err = x.Context(ctx).Insert(&entity.SiteInfo{ - Type: constant.SiteTypeInterfaceSettings, - Content: string(interfaceContent), + Type: constant.SiteTypeSecurity, + Content: string(securityContent), + Status: 1, + }) + if err != nil { + return err + } + } + + // save login settings + if existsLogin { + loginContent, err := json.Marshal(siteLogin) + _, err = x.Context(ctx).ID(siteInfoLogin.ID).Update(&entity.SiteInfo{ + Type: constant.SiteTypeLogin, + Content: string(loginContent), Status: 1, }) if err != nil { @@ -265,5 +380,17 @@ func splitInterfaceMenu(ctx context.Context, x *xorm.Engine) error { } } + // save general settings + if existGeneral { + generalContent, err := json.Marshal(siteGeneral) + _, err = x.Context(ctx).ID(siteInfoGeneral.ID).Update(&entity.SiteInfo{ + Type: constant.SiteTypeGeneral, + Content: string(generalContent), + Status: 1, + }) + if err != nil { + return err + } + } return nil } diff --git a/internal/router/answer_api_router.go b/internal/router/answer_api_router.go index c776b44f0..1fe8bdb14 100644 --- a/internal/router/answer_api_router.go +++ b/internal/router/answer_api_router.go @@ -359,8 +359,11 @@ func (a *AnswerAPIRouter) RegisterAnswerAdminAPIRouter(r *gin.RouterGroup) { r.GET("/siteinfo/advanced", a.adminSiteInfoController.GetSiteAdvanced) r.PUT("/siteinfo/advanced", a.adminSiteInfoController.UpdateSiteAdvanced) - r.GET("/siteinfo/legal", a.adminSiteInfoController.GetSiteLegal) - r.PUT("/siteinfo/legal", a.adminSiteInfoController.UpdateSiteLegal) + r.GET("/siteinfo/polices", a.adminSiteInfoController.GetSitePolicies) + r.PUT("/siteinfo/polices", a.adminSiteInfoController.UpdateSitePolices) + r.GET("/siteinfo/security", a.adminSiteInfoController.GetSiteSecurity) + r.PUT("/siteinfo/security", a.adminSiteInfoController.UpdateSiteSecurity) + r.GET("/siteinfo/seo", a.adminSiteInfoController.GetSeo) r.PUT("/siteinfo/seo", a.adminSiteInfoController.UpdateSeo) r.GET("/siteinfo/login", a.adminSiteInfoController.GetSiteLogin) diff --git a/internal/schema/siteinfo_schema.go b/internal/schema/siteinfo_schema.go index a060558a9..563b0cc11 100644 --- a/internal/schema/siteinfo_schema.go +++ b/internal/schema/siteinfo_schema.go @@ -42,7 +42,6 @@ type SiteGeneralReq struct { Description string `validate:"omitempty,sanitizer,gt=3,lte=2000" form:"description" json:"description"` SiteUrl string `validate:"required,sanitizer,gt=1,lte=512,url" form:"site_url" json:"site_url"` ContactEmail string `validate:"required,sanitizer,gt=1,lte=512,email" form:"contact_email" json:"contact_email"` - CheckUpdate bool `validate:"omitempty,sanitizer" form:"check_update" json:"check_update"` } func (r *SiteGeneralReq) FormatSiteUrl() { @@ -158,6 +157,7 @@ type SiteWriteTag struct { } // SiteLegalReq site branding request +// Deprecated: use SitePoliciesReq and SiteSecurityReq instead type SiteLegalReq struct { TermsOfServiceOriginalText string `json:"terms_of_service_original_text"` TermsOfServiceParsedText string `json:"terms_of_service_parsed_text"` @@ -166,6 +166,22 @@ type SiteLegalReq struct { ExternalContentDisplay string `validate:"required,oneof=always_display ask_before_display" json:"external_content_display"` } +type SitePoliciesReq struct { + TermsOfServiceOriginalText string `json:"terms_of_service_original_text"` + TermsOfServiceParsedText string `json:"terms_of_service_parsed_text"` + PrivacyPolicyOriginalText string `json:"privacy_policy_original_text"` + PrivacyPolicyParsedText string `json:"privacy_policy_parsed_text"` +} + +type SiteSecurityReq struct { + LoginRequired bool `json:"login_required"` + ExternalContentDisplay string `validate:"required,oneof=always_display ask_before_display" json:"external_content_display"` + CheckUpdate bool `validate:"omitempty,sanitizer" form:"check_update" json:"check_update"` +} + +type SitePoliciesResp SitePoliciesReq +type SiteSecurityResp SiteSecurityReq + // GetSiteLegalInfoReq site site legal request type GetSiteLegalInfoReq struct { InfoType string `validate:"required,oneof=tos privacy" form:"info_type"` @@ -204,7 +220,6 @@ type SiteLoginReq struct { AllowNewRegistrations bool `json:"allow_new_registrations"` AllowEmailRegistrations bool `json:"allow_email_registrations"` AllowPasswordLogin bool `json:"allow_password_login"` - LoginRequired bool `json:"login_required"` AllowEmailDomains []string `json:"allow_email_domains"` } @@ -282,6 +297,7 @@ type SiteAdvancedResp SiteAdvancedReq type SiteTagsResp SiteTagsReq // SiteLegalResp site write response +// Deprecated: use SitePoliciesResp and SiteSecurityResp instead type SiteLegalResp SiteLegalReq // SiteLegalSimpleResp site write response @@ -311,7 +327,6 @@ type SiteInfoResp struct { Revision string `json:"revision"` } -// todo: 检查模板使用 type TemplateSiteInfoResp struct { General *SiteGeneralResp `json:"general"` Interface *SiteInterfaceSettingsResp `json:"interface"` diff --git a/internal/service/dashboard/dashboard_service.go b/internal/service/dashboard/dashboard_service.go index 8b08ba02f..a6ac76e63 100644 --- a/internal/service/dashboard/dashboard_service.go +++ b/internal/service/dashboard/dashboard_service.go @@ -101,6 +101,12 @@ type DashboardService interface { func (ds *dashboardService) Statistical(ctx context.Context) (*schema.DashboardInfo, error) { dashboardInfo := ds.getFromCache(ctx) + security, err := ds.siteInfoService.GetSiteSecurity(ctx) + if err != nil { + log.Errorf("get general site info failed: %s", err) + return dashboardInfo, nil + } + if dashboardInfo == nil { dashboardInfo = &schema.DashboardInfo{} dashboardInfo.AnswerCount = ds.answerCount(ctx) @@ -108,12 +114,7 @@ func (ds *dashboardService) Statistical(ctx context.Context) (*schema.DashboardI dashboardInfo.UserCount = ds.userCount(ctx) dashboardInfo.VoteCount = ds.voteCount(ctx) dashboardInfo.OccupyingStorageSpace = ds.calculateStorage() - general, err := ds.siteInfoService.GetSiteGeneral(ctx) - if err != nil { - log.Errorf("get general site info failed: %s", err) - return dashboardInfo, nil - } - if general.CheckUpdate { + if security.CheckUpdate { dashboardInfo.VersionInfo.RemoteVersion = ds.remoteVersion(ctx) } dashboardInfo.DatabaseVersion = ds.getDatabaseInfo() @@ -141,9 +142,7 @@ func (ds *dashboardService) Statistical(ctx context.Context) (*schema.DashboardI dashboardInfo.VersionInfo.Version = constant.Version dashboardInfo.VersionInfo.Revision = constant.Revision dashboardInfo.GoVersion = constant.GoVersion - if siteLogin, err := ds.siteInfoService.GetSiteLogin(ctx); err == nil { - dashboardInfo.LoginRequired = siteLogin.LoginRequired - } + dashboardInfo.LoginRequired = security.LoginRequired ds.setCache(ctx, dashboardInfo) return dashboardInfo, nil diff --git a/internal/service/mock/siteinfo_repo_mock.go b/internal/service/mock/siteinfo_repo_mock.go index 5d5429ba4..a4d9d97b2 100644 --- a/internal/service/mock/siteinfo_repo_mock.go +++ b/internal/service/mock/siteinfo_repo_mock.go @@ -231,10 +231,10 @@ func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteInfoByType(ctx, siteType } // GetSiteInterface mocks base method. -func (m *MockSiteInfoCommonService) GetSiteInterface(ctx context.Context) (*schema.SiteInterfaceResp, error) { +func (m *MockSiteInfoCommonService) GetSiteInterface(ctx context.Context) (*schema.SiteInterfaceSettingsResp, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetSiteInterface", ctx) - ret0, _ := ret[0].(*schema.SiteInterfaceResp) + ret0, _ := ret[0].(*schema.SiteInterfaceSettingsResp) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -245,34 +245,34 @@ func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteInterface(ctx interface{ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteInterface", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteInterface), ctx) } -// GetSiteLegal mocks base method. -func (m *MockSiteInfoCommonService) GetSiteLegal(ctx context.Context) (*schema.SiteLegalResp, error) { +// GetSiteLogin mocks base method. +func (m *MockSiteInfoCommonService) GetSiteLogin(ctx context.Context) (*schema.SiteLoginResp, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetSiteLegal", ctx) - ret0, _ := ret[0].(*schema.SiteLegalResp) + ret := m.ctrl.Call(m, "GetSiteLogin", ctx) + ret0, _ := ret[0].(*schema.SiteLoginResp) ret1, _ := ret[1].(error) return ret0, ret1 } -// GetSiteLegal indicates an expected call of GetSiteLegal. -func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteLegal(ctx interface{}) *gomock.Call { +// GetSiteLogin indicates an expected call of GetSiteLogin. +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteLogin(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteLegal", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteLegal), ctx) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteLogin", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteLogin), ctx) } -// GetSiteLogin mocks base method. -func (m *MockSiteInfoCommonService) GetSiteLogin(ctx context.Context) (*schema.SiteLoginResp, error) { +// GetSitePolicies mocks base method. +func (m *MockSiteInfoCommonService) GetSitePolicies(ctx context.Context) (*schema.SitePoliciesResp, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetSiteLogin", ctx) - ret0, _ := ret[0].(*schema.SiteLoginResp) + ret := m.ctrl.Call(m, "GetSitePolicies", ctx) + ret0, _ := ret[0].(*schema.SitePoliciesResp) ret1, _ := ret[1].(error) return ret0, ret1 } -// GetSiteLogin indicates an expected call of GetSiteLogin. -func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteLogin(ctx interface{}) *gomock.Call { +// GetSitePolicies indicates an expected call of GetSitePolicies. +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSitePolicies(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteLogin", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteLogin), ctx) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSitePolicies", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSitePolicies), ctx) } // GetSiteQuestion mocks base method. @@ -290,6 +290,21 @@ func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteQuestion(ctx interface{} return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteQuestion", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteQuestion), ctx) } +// GetSiteSecurity mocks base method. +func (m *MockSiteInfoCommonService) GetSiteSecurity(ctx context.Context) (*schema.SiteSecurityResp, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSiteSecurity", ctx) + ret0, _ := ret[0].(*schema.SiteSecurityResp) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSiteSecurity indicates an expected call of GetSiteSecurity. +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteSecurity(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteSecurity", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteSecurity), ctx) +} + // GetSiteSeo mocks base method. func (m *MockSiteInfoCommonService) GetSiteSeo(ctx context.Context) (*schema.SiteSeoResp, error) { m.ctrl.T.Helper() @@ -350,6 +365,21 @@ func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteUsers(ctx interface{}) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteUsers", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteUsers), ctx) } +// GetSiteUsersSettings mocks base method. +func (m *MockSiteInfoCommonService) GetSiteUsersSettings(ctx context.Context) (*schema.SiteUsersSettingsResp, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSiteUsersSettings", ctx) + ret0, _ := ret[0].(*schema.SiteUsersSettingsResp) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSiteUsersSettings indicates an expected call of GetSiteUsersSettings. +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteUsersSettings(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteUsersSettings", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteUsersSettings), ctx) +} + // GetSiteWrite mocks base method. func (m *MockSiteInfoCommonService) GetSiteWrite(ctx context.Context) (*schema.SiteWriteResp, error) { m.ctrl.T.Helper() diff --git a/internal/service/siteinfo/siteinfo_service.go b/internal/service/siteinfo/siteinfo_service.go index 956511cee..d374008cb 100644 --- a/internal/service/siteinfo/siteinfo_service.go +++ b/internal/service/siteinfo/siteinfo_service.go @@ -138,9 +138,14 @@ func (s *SiteInfoService) GetSiteAdvanced(ctx context.Context) (resp *schema.Sit return s.siteInfoCommonService.GetSiteAdvanced(ctx) } -// GetSiteLegal get site legal info -func (s *SiteInfoService) GetSiteLegal(ctx context.Context) (resp *schema.SiteLegalResp, err error) { - return s.siteInfoCommonService.GetSiteLegal(ctx) +// GetSitePolicies get site legal info +func (s *SiteInfoService) GetSitePolicies(ctx context.Context) (resp *schema.SitePoliciesResp, err error) { + return s.siteInfoCommonService.GetSitePolicies(ctx) +} + +// GetSiteSecurity get site security info +func (s *SiteInfoService) GetSiteSecurity(ctx context.Context) (resp *schema.SiteSecurityResp, err error) { + return s.siteInfoCommonService.GetSiteSecurity(ctx) } // GetSiteLogin get site login info @@ -261,15 +266,26 @@ func (s *SiteInfoService) SaveSiteTags(ctx context.Context, req *schema.SiteTags return nil, s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeTags, data) } -// SaveSiteLegal save site legal configuration -func (s *SiteInfoService) SaveSiteLegal(ctx context.Context, req *schema.SiteLegalReq) (err error) { +// SaveSitePolicies save site policies configuration +func (s *SiteInfoService) SaveSitePolicies(ctx context.Context, req *schema.SitePoliciesReq) (err error) { + content, _ := json.Marshal(req) + data := &entity.SiteInfo{ + Type: constant.SiteTypePolicies, + Content: string(content), + Status: 1, + } + return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypePolicies, data) +} + +// SaveSiteSecurity save site security configuration +func (s *SiteInfoService) SaveSiteSecurity(ctx context.Context, req *schema.SiteSecurityReq) (err error) { content, _ := json.Marshal(req) data := &entity.SiteInfo{ - Type: constant.SiteTypeLegal, + Type: constant.SiteTypeSecurity, Content: string(content), Status: 1, } - return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeLegal, data) + return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeSecurity, data) } // SaveSiteLogin save site legal configuration @@ -361,13 +377,13 @@ func (s *SiteInfoService) GetSeo(ctx context.Context) (resp *schema.SiteSeoReq, if err = s.siteInfoCommonService.GetSiteInfoByType(ctx, constant.SiteTypeSeo, resp); err != nil { return resp, err } - loginConfig, err := s.GetSiteLogin(ctx) + siteSecurity, err := s.GetSiteSecurity(ctx) if err != nil { log.Error(err) return resp, nil } // If the site is set to privacy mode, prohibit crawling any page. - if loginConfig.LoginRequired { + if siteSecurity.LoginRequired { resp.Robots = "User-agent: *\nDisallow: /" return resp, nil } diff --git a/internal/service/siteinfo_common/siteinfo_service.go b/internal/service/siteinfo_common/siteinfo_service.go index dde0114bd..5ed399136 100644 --- a/internal/service/siteinfo_common/siteinfo_service.go +++ b/internal/service/siteinfo_common/siteinfo_service.go @@ -55,7 +55,8 @@ type SiteInfoCommonService interface { GetSiteAdvanced(ctx context.Context) (resp *schema.SiteAdvancedResp, err error) GetSiteQuestion(ctx context.Context) (resp *schema.SiteQuestionsResp, err error) GetSiteTag(ctx context.Context) (resp *schema.SiteTagsResp, err error) - GetSiteLegal(ctx context.Context) (resp *schema.SiteLegalResp, err error) + GetSitePolicies(ctx context.Context) (resp *schema.SitePoliciesResp, err error) + GetSiteSecurity(ctx context.Context) (resp *schema.SiteSecurityResp, err error) GetSiteLogin(ctx context.Context) (resp *schema.SiteLoginResp, err error) GetSiteCustomCssHTML(ctx context.Context) (resp *schema.SiteCustomCssHTMLResp, err error) GetSiteTheme(ctx context.Context) (resp *schema.SiteThemeResp, err error) @@ -73,7 +74,7 @@ func NewSiteInfoCommonService(siteInfoRepo SiteInfoRepo) SiteInfoCommonService { // GetSiteGeneral get site info general func (s *siteInfoCommonService) GetSiteGeneral(ctx context.Context) (resp *schema.SiteGeneralResp, err error) { - resp = &schema.SiteGeneralResp{CheckUpdate: true} + resp = &schema.SiteGeneralResp{} if err = s.GetSiteInfoByType(ctx, constant.SiteTypeGeneral, resp); err != nil { return nil, err } @@ -207,10 +208,19 @@ func (s *siteInfoCommonService) GetSiteTag(ctx context.Context) (resp *schema.Si return resp, nil } -// GetSiteLegal get site info write -func (s *siteInfoCommonService) GetSiteLegal(ctx context.Context) (resp *schema.SiteLegalResp, err error) { - resp = &schema.SiteLegalResp{} - if err = s.GetSiteInfoByType(ctx, constant.SiteTypeLegal, resp); err != nil { +// GetSitePolicies get site info policies +func (s *siteInfoCommonService) GetSitePolicies(ctx context.Context) (resp *schema.SitePoliciesResp, err error) { + resp = &schema.SitePoliciesResp{} + if err = s.GetSiteInfoByType(ctx, constant.SiteTypePolicies, resp); err != nil { + return nil, err + } + return resp, nil +} + +// GetSiteSecurity get site security config +func (s *siteInfoCommonService) GetSiteSecurity(ctx context.Context) (resp *schema.SiteSecurityResp, err error) { + resp = &schema.SiteSecurityResp{CheckUpdate: true} + if err = s.GetSiteInfoByType(ctx, constant.SiteTypeSecurity, resp); err != nil { return nil, err } return resp, nil From 94de21361e60c4653c229f0d3ad14fa8f0f58cf9 Mon Sep 17 00:00:00 2001 From: kumfo Date: Wed, 21 Jan 2026 16:29:08 +0800 Subject: [PATCH 05/19] feat(menu): update schema to remove deprecated min_tags and add MinimumTags to SiteQuestionsReq --- docs/docs.go | 27 +++++++++-------------- docs/swagger.json | 27 +++++++++-------------- docs/swagger.yaml | 20 ++++++++--------- internal/migrations/v30.go | 2 +- internal/schema/siteinfo_schema.go | 3 ++- internal/service/tag_common/tag_common.go | 2 +- 6 files changed, 35 insertions(+), 46 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index a70cdab09..c624133bd 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -10982,12 +10982,7 @@ const docTemplate = `{ "$ref": "#/definitions/schema.SiteInterfaceSettingsResp" }, "login": { - "description": "todo", - "allOf": [ - { - "$ref": "#/definitions/schema.SiteLoginResp" - } - ] + "$ref": "#/definitions/schema.SiteLoginResp" }, "revision": { "type": "string" @@ -11163,6 +11158,11 @@ const docTemplate = `{ "maximum": 65535, "minimum": 0 }, + "min_tags": { + "type": "integer", + "maximum": 5, + "minimum": 0 + }, "restrict_answer": { "type": "boolean" } @@ -11176,6 +11176,11 @@ const docTemplate = `{ "maximum": 65535, "minimum": 0 }, + "min_tags": { + "type": "integer", + "maximum": 5, + "minimum": 0 + }, "restrict_answer": { "type": "boolean" } @@ -11260,11 +11265,6 @@ const docTemplate = `{ "schema.SiteTagsReq": { "type": "object", "properties": { - "min_tags": { - "type": "integer", - "maximum": 5, - "minimum": 0 - }, "recommend_tags": { "type": "array", "items": { @@ -11285,11 +11285,6 @@ const docTemplate = `{ "schema.SiteTagsResp": { "type": "object", "properties": { - "min_tags": { - "type": "integer", - "maximum": 5, - "minimum": 0 - }, "recommend_tags": { "type": "array", "items": { diff --git a/docs/swagger.json b/docs/swagger.json index 05e3302c6..58f385790 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -10974,12 +10974,7 @@ "$ref": "#/definitions/schema.SiteInterfaceSettingsResp" }, "login": { - "description": "todo", - "allOf": [ - { - "$ref": "#/definitions/schema.SiteLoginResp" - } - ] + "$ref": "#/definitions/schema.SiteLoginResp" }, "revision": { "type": "string" @@ -11155,6 +11150,11 @@ "maximum": 65535, "minimum": 0 }, + "min_tags": { + "type": "integer", + "maximum": 5, + "minimum": 0 + }, "restrict_answer": { "type": "boolean" } @@ -11168,6 +11168,11 @@ "maximum": 65535, "minimum": 0 }, + "min_tags": { + "type": "integer", + "maximum": 5, + "minimum": 0 + }, "restrict_answer": { "type": "boolean" } @@ -11252,11 +11257,6 @@ "schema.SiteTagsReq": { "type": "object", "properties": { - "min_tags": { - "type": "integer", - "maximum": 5, - "minimum": 0 - }, "recommend_tags": { "type": "array", "items": { @@ -11277,11 +11277,6 @@ "schema.SiteTagsResp": { "type": "object", "properties": { - "min_tags": { - "type": "integer", - "maximum": 5, - "minimum": 0 - }, "recommend_tags": { "type": "array", "items": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 85a05baf0..39235a336 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2210,9 +2210,7 @@ definitions: interface: $ref: '#/definitions/schema.SiteInterfaceSettingsResp' login: - allOf: - - $ref: '#/definitions/schema.SiteLoginResp' - description: todo + $ref: '#/definitions/schema.SiteLoginResp' revision: type: string site_advanced: @@ -2330,6 +2328,10 @@ definitions: maximum: 65535 minimum: 0 type: integer + min_tags: + maximum: 5 + minimum: 0 + type: integer restrict_answer: type: boolean type: object @@ -2339,6 +2341,10 @@ definitions: maximum: 65535 minimum: 0 type: integer + min_tags: + maximum: 5 + minimum: 0 + type: integer restrict_answer: type: boolean type: object @@ -2396,10 +2402,6 @@ definitions: type: object schema.SiteTagsReq: properties: - min_tags: - maximum: 5 - minimum: 0 - type: integer recommend_tags: items: $ref: '#/definitions/schema.SiteWriteTag' @@ -2413,10 +2415,6 @@ definitions: type: object schema.SiteTagsResp: properties: - min_tags: - maximum: 5 - minimum: 0 - type: integer recommend_tags: items: $ref: '#/definitions/schema.SiteWriteTag' diff --git a/internal/migrations/v30.go b/internal/migrations/v30.go index c73f501d5..aa2f48b36 100644 --- a/internal/migrations/v30.go +++ b/internal/migrations/v30.go @@ -80,6 +80,7 @@ func splitWriteMenu(ctx context.Context, x *xorm.Engine) error { } // site questions settings siteQuestions := &schema.SiteQuestionsResp{ + MinimumTags: siteWrite.MinimumTags, MinimumContent: siteWrite.MinimumContent, RestrictAnswer: siteWrite.RestrictAnswer, } @@ -87,7 +88,6 @@ func splitWriteMenu(ctx context.Context, x *xorm.Engine) error { siteTags := &schema.SiteTagsResp{ ReservedTags: siteWrite.ReservedTags, RecommendTags: siteWrite.RecommendTags, - MinimumTags: siteWrite.MinimumTags, RequiredTag: siteWrite.RequiredTag, } diff --git a/internal/schema/siteinfo_schema.go b/internal/schema/siteinfo_schema.go index 563b0cc11..b2d2c4b31 100644 --- a/internal/schema/siteinfo_schema.go +++ b/internal/schema/siteinfo_schema.go @@ -88,6 +88,7 @@ type SiteBrandingReq struct { } // SiteWriteReq site write request +// Deprecated: use SiteQuestionsReq, SiteAdvancedReq and SiteTagsReq instead type SiteWriteReq struct { MinimumContent int `validate:"omitempty,gte=0,lte=65535" json:"min_content"` RestrictAnswer bool `validate:"omitempty" json:"restrict_answer"` @@ -107,6 +108,7 @@ type SiteWriteResp SiteWriteReq // SiteQuestionsReq site questions settings request type SiteQuestionsReq struct { + MinimumTags int `validate:"omitempty,gte=0,lte=5" json:"min_tags"` MinimumContent int `validate:"omitempty,gte=0,lte=65535" json:"min_content"` RestrictAnswer bool `validate:"omitempty" json:"restrict_answer"` } @@ -124,7 +126,6 @@ type SiteAdvancedReq struct { type SiteTagsReq struct { ReservedTags []*SiteWriteTag `validate:"omitempty,dive" json:"reserved_tags"` RecommendTags []*SiteWriteTag `validate:"omitempty,dive" json:"recommend_tags"` - MinimumTags int `validate:"omitempty,gte=0,lte=5" json:"min_tags"` RequiredTag bool `validate:"omitempty" json:"required_tag"` UserID string `json:"-"` } diff --git a/internal/service/tag_common/tag_common.go b/internal/service/tag_common/tag_common.go index 0da9f6fd5..87b53f396 100644 --- a/internal/service/tag_common/tag_common.go +++ b/internal/service/tag_common/tag_common.go @@ -295,7 +295,7 @@ func (ts *TagCommonService) ExistRecommend(ctx context.Context, tags []*schema.T } func (ts *TagCommonService) GetMinimumTags(ctx context.Context) (int, error) { - siteInfo, err := ts.siteInfoService.GetSiteTag(ctx) + siteInfo, err := ts.siteInfoService.GetSiteQuestion(ctx) if err != nil { return 1, err } From 0bb33e7ea43abbe80138942fc6ccef9c551016a7 Mon Sep 17 00:00:00 2001 From: kumfo Date: Wed, 21 Jan 2026 17:21:28 +0800 Subject: [PATCH 06/19] feat(siteinfo): fix GetSiteTag method to correctly assign response from siteInfoCommonService --- internal/service/siteinfo/siteinfo_service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/siteinfo/siteinfo_service.go b/internal/service/siteinfo/siteinfo_service.go index d374008cb..acd5baed1 100644 --- a/internal/service/siteinfo/siteinfo_service.go +++ b/internal/service/siteinfo/siteinfo_service.go @@ -111,7 +111,7 @@ func (s *SiteInfoService) GetSiteUsers(ctx context.Context) (resp *schema.SiteUs // GetSiteTag get site info write func (s *SiteInfoService) GetSiteTag(ctx context.Context) (resp *schema.SiteTagsResp, err error) { resp = &schema.SiteTagsResp{} - _, err = s.siteInfoCommonService.GetSiteTag(ctx) + resp, err = s.siteInfoCommonService.GetSiteTag(ctx) if err != nil { log.Error(err) return resp, nil From 60f8cd1803ed707c87caf9fe394cdaec55511478 Mon Sep 17 00:00:00 2001 From: kumfo Date: Thu, 22 Jan 2026 10:59:28 +0800 Subject: [PATCH 07/19] feat(siteinfo): add site_security to response structure and update related schemas --- docs/docs.go | 3 +++ docs/swagger.json | 3 +++ docs/swagger.yaml | 2 ++ internal/controller/siteinfo_controller.go | 3 +++ internal/schema/siteinfo_schema.go | 1 + 5 files changed, 12 insertions(+) diff --git a/docs/docs.go b/docs/docs.go index c624133bd..23398e509 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -10996,6 +10996,9 @@ const docTemplate = `{ "site_questions": { "$ref": "#/definitions/schema.SiteQuestionsResp" }, + "site_security": { + "$ref": "#/definitions/schema.SiteSecurityResp" + }, "site_seo": { "$ref": "#/definitions/schema.SiteSeoResp" }, diff --git a/docs/swagger.json b/docs/swagger.json index 58f385790..b9dc0b0b1 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -10988,6 +10988,9 @@ "site_questions": { "$ref": "#/definitions/schema.SiteQuestionsResp" }, + "site_security": { + "$ref": "#/definitions/schema.SiteSecurityResp" + }, "site_seo": { "$ref": "#/definitions/schema.SiteSeoResp" }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 39235a336..4a680ec49 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2219,6 +2219,8 @@ definitions: $ref: '#/definitions/schema.SiteLegalSimpleResp' site_questions: $ref: '#/definitions/schema.SiteQuestionsResp' + site_security: + $ref: '#/definitions/schema.SiteSecurityResp' site_seo: $ref: '#/definitions/schema.SiteSeoResp' site_tags: diff --git a/internal/controller/siteinfo_controller.go b/internal/controller/siteinfo_controller.go index 64aa02ce7..320c1b475 100644 --- a/internal/controller/siteinfo_controller.go +++ b/internal/controller/siteinfo_controller.go @@ -107,6 +107,9 @@ func (sc *SiteInfoController) GetSiteInfo(ctx *gin.Context) { if legal, err := sc.siteInfoService.GetSiteSecurity(ctx); err == nil { resp.Legal = &schema.SiteLegalSimpleResp{ExternalContentDisplay: legal.ExternalContentDisplay} } + if security, err := sc.siteInfoService.GetSiteSecurity(ctx); err == nil { + resp.Security = security + } handler.HandleResponse(ctx, nil, resp) } diff --git a/internal/schema/siteinfo_schema.go b/internal/schema/siteinfo_schema.go index b2d2c4b31..19382d102 100644 --- a/internal/schema/siteinfo_schema.go +++ b/internal/schema/siteinfo_schema.go @@ -324,6 +324,7 @@ type SiteInfoResp struct { Questions *SiteQuestionsResp `json:"site_questions"` Tags *SiteTagsResp `json:"site_tags"` Legal *SiteLegalSimpleResp `json:"site_legal"` + Security *SiteSecurityResp `json:"site_security"` Version string `json:"version"` Revision string `json:"revision"` } From 81511e386a36188ef9418fe5521d3ce9c29de3a3 Mon Sep 17 00:00:00 2001 From: kumfo Date: Thu, 22 Jan 2026 11:49:50 +0800 Subject: [PATCH 08/19] feat(menu): deprecate default_avatar and gravatar_base_url in SiteInterfaceReq schema --- docs/docs.go | 11 ----------- docs/swagger.json | 11 ----------- docs/swagger.yaml | 8 -------- internal/schema/siteinfo_schema.go | 10 ++++++---- 4 files changed, 6 insertions(+), 34 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index 23398e509..00eb75ce2 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -11022,21 +11022,10 @@ const docTemplate = `{ "schema.SiteInterfaceReq": { "type": "object", "required": [ - "default_avatar", "language", "time_zone" ], "properties": { - "default_avatar": { - "type": "string", - "enum": [ - "system", - "gravatar" - ] - }, - "gravatar_base_url": { - "type": "string" - }, "language": { "type": "string", "maxLength": 128 diff --git a/docs/swagger.json b/docs/swagger.json index b9dc0b0b1..c181109d2 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -11014,21 +11014,10 @@ "schema.SiteInterfaceReq": { "type": "object", "required": [ - "default_avatar", "language", "time_zone" ], "properties": { - "default_avatar": { - "type": "string", - "enum": [ - "system", - "gravatar" - ] - }, - "gravatar_base_url": { - "type": "string" - }, "language": { "type": "string", "maxLength": 128 diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 4a680ec49..caab11876 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2236,13 +2236,6 @@ definitions: type: object schema.SiteInterfaceReq: properties: - default_avatar: - enum: - - system - - gravatar - type: string - gravatar_base_url: - type: string language: maxLength: 128 type: string @@ -2250,7 +2243,6 @@ definitions: maxLength: 128 type: string required: - - default_avatar - language - time_zone type: object diff --git a/internal/schema/siteinfo_schema.go b/internal/schema/siteinfo_schema.go index 19382d102..0b5ad0620 100644 --- a/internal/schema/siteinfo_schema.go +++ b/internal/schema/siteinfo_schema.go @@ -58,10 +58,12 @@ func (r *SiteGeneralReq) FormatSiteUrl() { // SiteInterfaceReq site interface request type SiteInterfaceReq struct { - Language string `validate:"required,gt=1,lte=128" form:"language" json:"language"` - TimeZone string `validate:"required,gt=1,lte=128" form:"time_zone" json:"time_zone"` - DefaultAvatar string `validate:"required,oneof=system gravatar" json:"default_avatar"` - GravatarBaseURL string `validate:"omitempty" json:"gravatar_base_url"` + Language string `validate:"required,gt=1,lte=128" form:"language" json:"language"` + TimeZone string `validate:"required,gt=1,lte=128" form:"time_zone" json:"time_zone"` + // Deperecated: use SiteUsersSettingsReq instead + DefaultAvatar string `validate:"omitempty" json:"-"` + // Deperecated: use SiteUsersSettingsReq instead + GravatarBaseURL string `validate:"omitempty" json:"-"` } // SiteInterfaceSettingsReq site interface settings request From 8b8550e9ca3b5e85ef1009bb709eb4bedbdd7ab4 Mon Sep 17 00:00:00 2001 From: kumfo Date: Thu, 22 Jan 2026 12:18:57 +0800 Subject: [PATCH 09/19] feat(docs): add Apache License 2.0 header to docs.go and swagger.yaml --- docs/docs.go | 19 +++++++++++++++++++ docs/swagger.yaml | 17 +++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/docs/docs.go b/docs/docs.go index 00eb75ce2..cfe48366f 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1,3 +1,22 @@ +/* + * 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 docs Code generated by swaggo/swag. DO NOT EDIT package docs diff --git a/docs/swagger.yaml b/docs/swagger.yaml index caab11876..13e9575eb 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,3 +1,20 @@ +# 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. + basePath: / definitions: constant.NotificationChannelKey: From d65e257f92f352e08b8457efd9b490312c3fe297 Mon Sep 17 00:00:00 2001 From: kumfo Date: Thu, 22 Jan 2026 12:21:02 +0800 Subject: [PATCH 10/19] feat(docs): add Apache License 2.0 header to wire_gen.go --- cmd/wire_gen.go | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go index 2808619b3..5b9990213 100644 --- a/cmd/wire_gen.go +++ b/cmd/wire_gen.go @@ -1,9 +1,29 @@ -// Code generated by Wire. DO NOT EDIT. - -//go:generate go run -mod=mod github.com/google/wire/cmd/wire //go:build !wireinject // +build !wireinject +/* + * 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. + */ + +// Code generated by Wire. DO NOT EDIT. + +//go:generate go run github.com/google/wire/cmd/wire + package answercmd import ( From f05f1eb80d10124c252b1b3f812aa26e95aad8f8 Mon Sep 17 00:00:00 2001 From: kumfo Date: Tue, 20 Jan 2026 14:54:52 +0800 Subject: [PATCH 11/19] feat(menu): update admin menu settings to include questions, tags, and advanced options --- cmd/wire_gen.go | 24 ++- go.mod | 2 +- internal/base/constant/site_type.go | 3 + internal/controller/answer_controller.go | 2 +- internal/controller/siteinfo_controller.go | 10 +- .../controller_admin/siteinfo_controller.go | 94 ++++++++-- internal/migrations/migrations.go | 1 + internal/migrations/v30.go | 173 ++++++++++++++++++ internal/router/answer_api_router.go | 10 +- internal/schema/siteinfo_schema.go | 41 ++++- internal/service/mock/siteinfo_repo_mock.go | 81 ++++++-- internal/service/question_common/question.go | 2 +- internal/service/siteinfo/siteinfo_service.go | 51 ++++-- .../siteinfo_common/siteinfo_service.go | 30 +++ internal/service/tag_common/tag_common.go | 8 +- internal/service/uploader/upload.go | 32 ++-- 16 files changed, 485 insertions(+), 79 deletions(-) create mode 100644 internal/migrations/v30.go diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go index 8435cf310..22a70f29d 100644 --- a/cmd/wire_gen.go +++ b/cmd/wire_gen.go @@ -1,8 +1,28 @@ +//go:build !wireinject +// +build !wireinject + +/* + * 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. + */ + // Code generated by Wire. DO NOT EDIT. //go:generate go run github.com/google/wire/cmd/wire -//go:build !wireinject -// +build !wireinject package answercmd diff --git a/go.mod b/go.mod index 52d64c738..d24302954 100644 --- a/go.mod +++ b/go.mod @@ -37,6 +37,7 @@ require ( github.com/grokify/html-strip-tags-go v0.1.0 github.com/jinzhu/copier v0.4.0 github.com/jinzhu/now v1.1.5 + github.com/joho/godotenv v1.5.1 github.com/lib/pq v1.10.9 github.com/microcosm-cc/bluemonday v1.0.27 github.com/mozillazg/go-pinyin v0.20.0 @@ -117,7 +118,6 @@ require ( github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/joho/godotenv v1.5.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect diff --git a/internal/base/constant/site_type.go b/internal/base/constant/site_type.go index 65b487c5d..6106b190c 100644 --- a/internal/base/constant/site_type.go +++ b/internal/base/constant/site_type.go @@ -31,4 +31,7 @@ const ( SiteTypeTheme = "theme" SiteTypePrivileges = "privileges" SiteTypeUsers = "users" + SiteTypeAdvanced = "advanced" + SiteTypeQuestions = "questions" + SiteTypeTags = "tags" ) diff --git a/internal/controller/answer_controller.go b/internal/controller/answer_controller.go index e76b02ccc..99c89b778 100644 --- a/internal/controller/answer_controller.go +++ b/internal/controller/answer_controller.go @@ -242,7 +242,7 @@ func (ac *AnswerController) AddAnswer(ctx *gin.Context) { return } - write, err := ac.siteInfoCommonService.GetSiteWrite(ctx) + write, err := ac.siteInfoCommonService.GetSiteQuestion(ctx) if err != nil { handler.HandleResponse(ctx, err, nil) return diff --git a/internal/controller/siteinfo_controller.go b/internal/controller/siteinfo_controller.go index a336fb242..8035275a6 100644 --- a/internal/controller/siteinfo_controller.go +++ b/internal/controller/siteinfo_controller.go @@ -87,7 +87,15 @@ func (sc *SiteInfoController) GetSiteInfo(ctx *gin.Context) { if err != nil { log.Error(err) } - resp.Write, err = sc.siteInfoService.GetSiteWrite(ctx) + resp.Questions, err = sc.siteInfoService.GetSiteQuestion(ctx) + if err != nil { + log.Error(err) + } + resp.Tags, err = sc.siteInfoService.GetSiteTag(ctx) + if err != nil { + log.Error(err) + } + resp.Advanced, err = sc.siteInfoService.GetSiteAdvanced(ctx) if err != nil { log.Error(err) } diff --git a/internal/controller_admin/siteinfo_controller.go b/internal/controller_admin/siteinfo_controller.go index 8a92daba3..bbab97942 100644 --- a/internal/controller_admin/siteinfo_controller.go +++ b/internal/controller_admin/siteinfo_controller.go @@ -82,16 +82,42 @@ func (sc *SiteInfoController) GetSiteBranding(ctx *gin.Context) { handler.HandleResponse(ctx, err, resp) } -// GetSiteWrite get site interface -// @Summary get site interface -// @Description get site interface +// GetSiteTag get site tags setting +// @Summary get site tags setting +// @Description get site tags setting +// @Security ApiKeyAuth +// @Tags admin +// @Produce json +// @Success 200 {object} handler.RespBody{data=schema.SiteTagsResp} +// @Router /answer/admin/api/siteinfo/tag [get] +func (sc *SiteInfoController) GetSiteTag(ctx *gin.Context) { + resp, err := sc.siteInfoService.GetSiteTag(ctx) + handler.HandleResponse(ctx, err, resp) +} + +// GetSiteQuestion get site questions setting +// @Summary get site questions setting +// @Description get site questions setting +// @Security ApiKeyAuth +// @Tags admin +// @Produce json +// @Success 200 {object} handler.RespBody{data=schema.SiteQuestionsResp} +// @Router /answer/admin/api/siteinfo/question [get] +func (sc *SiteInfoController) GetSiteQuestion(ctx *gin.Context) { + resp, err := sc.siteInfoService.GetSiteQuestion(ctx) + handler.HandleResponse(ctx, err, resp) +} + +// GetSiteAdvanced get site advanced setting +// @Summary get site advanced setting +// @Description get site advanced setting // @Security ApiKeyAuth // @Tags admin // @Produce json -// @Success 200 {object} handler.RespBody{data=schema.SiteWriteResp} -// @Router /answer/admin/api/siteinfo/write [get] -func (sc *SiteInfoController) GetSiteWrite(ctx *gin.Context) { - resp, err := sc.siteInfoService.GetSiteWrite(ctx) +// @Success 200 {object} handler.RespBody{data=schema.SiteAdvancedResp} +// @Router /answer/admin/api/siteinfo/advanced [get] +func (sc *SiteInfoController) GetSiteAdvanced(ctx *gin.Context) { + resp, err := sc.siteInfoService.GetSiteAdvanced(ctx) handler.HandleResponse(ctx, err, resp) } @@ -288,23 +314,61 @@ func (sc *SiteInfoController) UpdateBranding(ctx *gin.Context) { handler.HandleResponse(ctx, saveErr, nil) } -// UpdateSiteWrite update site write info -// @Summary update site write info -// @Description update site write info +// UpdateSiteQuestion update site question settings +// @Summary update site question settings +// @Description update site question settings +// @Security ApiKeyAuth +// @Tags admin +// @Produce json +// @Param data body schema.SiteQuestionsReq true "questions settings" +// @Success 200 {object} handler.RespBody{} +// @Router /answer/admin/api/siteinfo/question [put] +func (sc *SiteInfoController) UpdateSiteQuestion(ctx *gin.Context) { + req := &schema.SiteQuestionsReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + resp, err := sc.siteInfoService.SaveSiteQuestions(ctx, req) + handler.HandleResponse(ctx, err, resp) +} + +// UpdateSiteTag update site tag settings +// @Summary update site tag settings +// @Description update site tag settings // @Security ApiKeyAuth // @Tags admin // @Produce json -// @Param data body schema.SiteWriteReq true "write info" +// @Param data body schema.SiteTagsReq true "tags settings" // @Success 200 {object} handler.RespBody{} -// @Router /answer/admin/api/siteinfo/write [put] -func (sc *SiteInfoController) UpdateSiteWrite(ctx *gin.Context) { - req := &schema.SiteWriteReq{} +// @Router /answer/admin/api/siteinfo/tag [put] +func (sc *SiteInfoController) UpdateSiteTag(ctx *gin.Context) { + req := &schema.SiteTagsReq{} if handler.BindAndCheck(ctx, req) { return } req.UserID = middleware.GetLoginUserIDFromContext(ctx) - resp, err := sc.siteInfoService.SaveSiteWrite(ctx, req) + resp, err := sc.siteInfoService.SaveSiteTags(ctx, req) + handler.HandleResponse(ctx, err, resp) +} + +// UpdateSiteAdvanced update site advanced info +// @Summary update site advanced info +// @Description update site advanced info +// @Security ApiKeyAuth +// @Tags admin +// @Produce json +// @Param data body schema.SiteAdvancedReq true "advanced settings" +// @Success 200 {object} handler.RespBody{} +// @Router /answer/admin/api/siteinfo/advanced [put] +func (sc *SiteInfoController) UpdateSiteAdvanced(ctx *gin.Context) { + req := &schema.SiteAdvancedReq{} + if handler.BindAndCheck(ctx, req) { + return + } + + resp, err := sc.siteInfoService.SaveSiteAdvanced(ctx, req) handler.HandleResponse(ctx, err, resp) } diff --git a/internal/migrations/migrations.go b/internal/migrations/migrations.go index 9fda8c34d..783332df9 100644 --- a/internal/migrations/migrations.go +++ b/internal/migrations/migrations.go @@ -105,6 +105,7 @@ var migrations = []Migration{ NewMigration("v1.6.0", "move user config to interface", moveUserConfigToInterface, true), NewMigration("v1.7.0", "add optional tags", addOptionalTags, true), NewMigration("v1.7.2", "expand avatar column length", expandAvatarColumnLength, false), + NewMigration("v1.8.0", "change admin menu", updateAdminMenuSettings, true), } func GetMigrations() []Migration { diff --git a/internal/migrations/v30.go b/internal/migrations/v30.go new file mode 100644 index 000000000..5d5d5223f --- /dev/null +++ b/internal/migrations/v30.go @@ -0,0 +1,173 @@ +/* + * 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" + "encoding/json" + + "github.com/apache/answer/internal/base/constant" + "github.com/apache/answer/internal/base/reason" + "github.com/apache/answer/internal/entity" + "github.com/apache/answer/internal/schema" + "github.com/segmentfault/pacman/errors" + "xorm.io/builder" + "xorm.io/xorm" +) + +func updateAdminMenuSettings(ctx context.Context, x *xorm.Engine) (err error) { + err = splitWriteMenu(ctx, x) + if err != nil { + return + } + return +} + +// splitWriteMenu splits the site write settings into advanced, questions, and tags settings +func splitWriteMenu(ctx context.Context, x *xorm.Engine) error { + var ( + siteInfo = &entity.SiteInfo{} + siteInfoAdvanced = &entity.SiteInfo{} + siteInfoQuestions = &entity.SiteInfo{} + siteInfoTags = &entity.SiteInfo{} + ) + exist, err := x.Context(ctx).Where(builder.Eq{"type": constant.SiteTypeWrite}).Get(siteInfo) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return err + } + if !exist { + return nil + } + siteWrite := &schema.SiteWriteResp{} + if err := json.Unmarshal([]byte(siteInfo.Content), siteWrite); err != nil { + return err + } + // site advanced settings + siteAdvanced := &schema.SiteAdvancedResp{ + MaxImageSize: siteWrite.MaxImageSize, + MaxAttachmentSize: siteWrite.MaxAttachmentSize, + MaxImageMegapixel: siteWrite.MaxImageMegapixel, + AuthorizedImageExtensions: siteWrite.AuthorizedImageExtensions, + AuthorizedAttachmentExtensions: siteWrite.AuthorizedAttachmentExtensions, + } + // site questions settings + siteQuestions := &schema.SiteQuestionsResp{ + MinimumContent: siteWrite.MinimumContent, + RestrictAnswer: siteWrite.RestrictAnswer, + } + // site tags settings + siteTags := &schema.SiteTagsResp{ + ReservedTags: siteWrite.ReservedTags, + RecommendTags: siteWrite.RecommendTags, + MinimumTags: siteWrite.MinimumTags, + RequiredTag: siteWrite.RequiredTag, + } + + // save site settings + // save advanced settings + existsAdvanced, err := x.Context(ctx).Where(builder.Eq{"type": constant.SiteTypeWrite}).Get(siteInfoAdvanced) + if err != nil { + return err + } + advancedContent, err := json.Marshal(siteAdvanced) + if err != nil { + return err + } + if existsAdvanced { + _, err = x.Context(ctx).ID(siteInfoAdvanced.ID).Update(&entity.SiteInfo{ + Type: constant.SiteTypeAdvanced, + Content: string(advancedContent), + Status: 1, + }) + if err != nil { + return err + } + } else { + _, err = x.Context(ctx).Insert(&entity.SiteInfo{ + Type: constant.SiteTypeAdvanced, + Content: string(advancedContent), + Status: 1, + }) + if err != nil { + return err + } + } + + // save questions settings + existsQuestions, err := x.Context(ctx).Where(builder.Eq{"type": constant.SiteTypeQuestions}).Get(siteInfoQuestions) + if err != nil { + return err + } + questionsContent, err := json.Marshal(siteQuestions) + if err != nil { + return err + } + if existsQuestions { + _, err = x.Context(ctx).ID(siteInfoQuestions.ID).Update(&entity.SiteInfo{ + Type: constant.SiteTypeQuestions, + Content: string(questionsContent), + Status: 1, + }) + if err != nil { + return err + } + } else { + _, err = x.Context(ctx).Insert(&entity.SiteInfo{ + Type: constant.SiteTypeQuestions, + Content: string(questionsContent), + Status: 1, + }) + if err != nil { + return err + } + } + + // save tags settings + existsTags, err := x.Context(ctx).Where(builder.Eq{"type": constant.SiteTypeTags}).Get(siteInfoTags) + if err != nil { + return err + } + tagsContent, err := json.Marshal(siteTags) + if err != nil { + return err + } + if existsTags { + _, err = x.Context(ctx).ID(siteInfoTags.ID).Update(&entity.SiteInfo{ + Type: constant.SiteTypeTags, + Content: string(tagsContent), + Status: 1, + }) + if err != nil { + return err + } + } else { + _, err = x.Context(ctx).Insert(&entity.SiteInfo{ + Type: constant.SiteTypeTags, + Content: string(tagsContent), + Status: 1, + }) + if err != nil { + return err + } + } + + return nil +} diff --git a/internal/router/answer_api_router.go b/internal/router/answer_api_router.go index 1191492b9..d717bc9d3 100644 --- a/internal/router/answer_api_router.go +++ b/internal/router/answer_api_router.go @@ -347,8 +347,14 @@ func (a *AnswerAPIRouter) RegisterAnswerAdminAPIRouter(r *gin.RouterGroup) { r.PUT("/siteinfo/interface", a.adminSiteInfoController.UpdateInterface) r.GET("/siteinfo/branding", a.adminSiteInfoController.GetSiteBranding) r.PUT("/siteinfo/branding", a.adminSiteInfoController.UpdateBranding) - r.GET("/siteinfo/write", a.adminSiteInfoController.GetSiteWrite) - r.PUT("/siteinfo/write", a.adminSiteInfoController.UpdateSiteWrite) + + r.GET("/siteinfo/question", a.adminSiteInfoController.GetSiteQuestion) + r.PUT("/siteinfo/question", a.adminSiteInfoController.UpdateSiteQuestion) + r.GET("/siteinfo/tag", a.adminSiteInfoController.GetSiteTag) + r.PUT("/siteinfo/tag", a.adminSiteInfoController.UpdateSiteTag) + r.GET("/siteinfo/advanced", a.adminSiteInfoController.GetSiteAdvanced) + r.PUT("/siteinfo/advanced", a.adminSiteInfoController.UpdateSiteAdvanced) + r.GET("/siteinfo/legal", a.adminSiteInfoController.GetSiteLegal) r.PUT("/siteinfo/legal", a.adminSiteInfoController.UpdateSiteLegal) r.GET("/siteinfo/seo", a.adminSiteInfoController.GetSeo) diff --git a/internal/schema/siteinfo_schema.go b/internal/schema/siteinfo_schema.go index 9eb919a0c..deacf08fb 100644 --- a/internal/schema/siteinfo_schema.go +++ b/internal/schema/siteinfo_schema.go @@ -89,21 +89,47 @@ type SiteWriteReq struct { UserID string `json:"-"` } -func (s *SiteWriteResp) GetMaxImageSize() int64 { +type SiteWriteResp SiteWriteReq + +// SiteQuestionsReq site questions settings request +type SiteQuestionsReq struct { + MinimumContent int `validate:"omitempty,gte=0,lte=65535" json:"min_content"` + RestrictAnswer bool `validate:"omitempty" json:"restrict_answer"` +} + +// SiteAdvancedReq site advanced settings request +type SiteAdvancedReq struct { + MaxImageSize int `validate:"omitempty,gt=0" json:"max_image_size"` + MaxAttachmentSize int `validate:"omitempty,gt=0" json:"max_attachment_size"` + MaxImageMegapixel int `validate:"omitempty,gt=0" json:"max_image_megapixel"` + AuthorizedImageExtensions []string `validate:"omitempty" json:"authorized_image_extensions"` + AuthorizedAttachmentExtensions []string `validate:"omitempty" json:"authorized_attachment_extensions"` +} + +// SiteTagsReq site tags settings request +type SiteTagsReq struct { + ReservedTags []*SiteWriteTag `validate:"omitempty,dive" json:"reserved_tags"` + RecommendTags []*SiteWriteTag `validate:"omitempty,dive" json:"recommend_tags"` + MinimumTags int `validate:"omitempty,gte=0,lte=5" json:"min_tags"` + RequiredTag bool `validate:"omitempty" json:"required_tag"` + UserID string `json:"-"` +} + +func (s *SiteAdvancedResp) GetMaxImageSize() int64 { if s.MaxImageSize <= 0 { return constant.DefaultMaxImageSize } return int64(s.MaxImageSize) * 1024 * 1024 } -func (s *SiteWriteResp) GetMaxAttachmentSize() int64 { +func (s *SiteAdvancedResp) GetMaxAttachmentSize() int64 { if s.MaxAttachmentSize <= 0 { return constant.DefaultMaxAttachmentSize } return int64(s.MaxAttachmentSize) * 1024 * 1024 } -func (s *SiteWriteResp) GetMaxImageMegapixel() int { +func (s *SiteAdvancedResp) GetMaxImageMegapixel() int { if s.MaxImageMegapixel <= 0 { return constant.DefaultMaxImageMegapixel } @@ -238,8 +264,9 @@ type ThemeOption struct { Value string `json:"value"` } -// SiteWriteResp site write response -type SiteWriteResp SiteWriteReq +type SiteQuestionsResp SiteQuestionsReq +type SiteAdvancedResp SiteAdvancedReq +type SiteTagsResp SiteTagsReq // SiteLegalResp site write response type SiteLegalResp SiteLegalReq @@ -262,7 +289,9 @@ type SiteInfoResp struct { CustomCssHtml *SiteCustomCssHTMLResp `json:"custom_css_html"` SiteSeo *SiteSeoResp `json:"site_seo"` SiteUsers *SiteUsersResp `json:"site_users"` - Write *SiteWriteResp `json:"site_write"` + Advanced *SiteAdvancedResp `json:"site_advanced"` + Questions *SiteQuestionsResp `json:"site_questions"` + Tags *SiteTagsResp `json:"site_tags"` Legal *SiteLegalSimpleResp `json:"site_legal"` Version string `json:"version"` Revision string `json:"revision"` diff --git a/internal/service/mock/siteinfo_repo_mock.go b/internal/service/mock/siteinfo_repo_mock.go index 0a1b31e84..b809d8aef 100644 --- a/internal/service/mock/siteinfo_repo_mock.go +++ b/internal/service/mock/siteinfo_repo_mock.go @@ -22,7 +22,6 @@ import ( type MockSiteInfoRepo struct { ctrl *gomock.Controller recorder *MockSiteInfoRepoMockRecorder - isgomock struct{} } // MockSiteInfoRepoMockRecorder is the mock recorder for MockSiteInfoRepo. @@ -53,7 +52,7 @@ func (m *MockSiteInfoRepo) GetByType(ctx context.Context, siteType string) (*ent } // GetByType indicates an expected call of GetByType. -func (mr *MockSiteInfoRepoMockRecorder) GetByType(ctx, siteType any) *gomock.Call { +func (mr *MockSiteInfoRepoMockRecorder) GetByType(ctx, siteType interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByType", reflect.TypeOf((*MockSiteInfoRepo)(nil).GetByType), ctx, siteType) } @@ -68,7 +67,7 @@ func (m *MockSiteInfoRepo) IsBrandingFileUsed(ctx context.Context, filePath stri } // IsBrandingFileUsed indicates an expected call of IsBrandingFileUsed. -func (mr *MockSiteInfoRepoMockRecorder) IsBrandingFileUsed(ctx, filePath any) *gomock.Call { +func (mr *MockSiteInfoRepoMockRecorder) IsBrandingFileUsed(ctx, filePath interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsBrandingFileUsed", reflect.TypeOf((*MockSiteInfoRepo)(nil).IsBrandingFileUsed), ctx, filePath) } @@ -82,7 +81,7 @@ func (m *MockSiteInfoRepo) SaveByType(ctx context.Context, siteType string, data } // SaveByType indicates an expected call of SaveByType. -func (mr *MockSiteInfoRepoMockRecorder) SaveByType(ctx, siteType, data any) *gomock.Call { +func (mr *MockSiteInfoRepoMockRecorder) SaveByType(ctx, siteType, data interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveByType", reflect.TypeOf((*MockSiteInfoRepo)(nil).SaveByType), ctx, siteType, data) } @@ -91,7 +90,6 @@ func (mr *MockSiteInfoRepoMockRecorder) SaveByType(ctx, siteType, data any) *gom type MockSiteInfoCommonService struct { ctrl *gomock.Controller recorder *MockSiteInfoCommonServiceMockRecorder - isgomock struct{} } // MockSiteInfoCommonServiceMockRecorder is the mock recorder for MockSiteInfoCommonService. @@ -120,7 +118,7 @@ func (m *MockSiteInfoCommonService) FormatAvatar(ctx context.Context, originalAv } // FormatAvatar indicates an expected call of FormatAvatar. -func (mr *MockSiteInfoCommonServiceMockRecorder) FormatAvatar(ctx, originalAvatarData, email, userStatus any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) FormatAvatar(ctx, originalAvatarData, email, userStatus interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FormatAvatar", reflect.TypeOf((*MockSiteInfoCommonService)(nil).FormatAvatar), ctx, originalAvatarData, email, userStatus) } @@ -134,11 +132,26 @@ func (m *MockSiteInfoCommonService) FormatListAvatar(ctx context.Context, userLi } // FormatListAvatar indicates an expected call of FormatListAvatar. -func (mr *MockSiteInfoCommonServiceMockRecorder) FormatListAvatar(ctx, userList any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) FormatListAvatar(ctx, userList interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FormatListAvatar", reflect.TypeOf((*MockSiteInfoCommonService)(nil).FormatListAvatar), ctx, userList) } +// GetSiteAdvanced mocks base method. +func (m *MockSiteInfoCommonService) GetSiteAdvanced(ctx context.Context) (*schema.SiteAdvancedResp, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSiteAdvanced", ctx) + ret0, _ := ret[0].(*schema.SiteAdvancedResp) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSiteAdvanced indicates an expected call of GetSiteAdvanced. +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteAdvanced(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteAdvanced", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteAdvanced), ctx) +} + // GetSiteBranding mocks base method. func (m *MockSiteInfoCommonService) GetSiteBranding(ctx context.Context) (*schema.SiteBrandingResp, error) { m.ctrl.T.Helper() @@ -149,7 +162,7 @@ func (m *MockSiteInfoCommonService) GetSiteBranding(ctx context.Context) (*schem } // GetSiteBranding indicates an expected call of GetSiteBranding. -func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteBranding(ctx any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteBranding(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteBranding", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteBranding), ctx) } @@ -164,7 +177,7 @@ func (m *MockSiteInfoCommonService) GetSiteCustomCssHTML(ctx context.Context) (* } // GetSiteCustomCssHTML indicates an expected call of GetSiteCustomCssHTML. -func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteCustomCssHTML(ctx any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteCustomCssHTML(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteCustomCssHTML", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteCustomCssHTML), ctx) } @@ -179,7 +192,7 @@ func (m *MockSiteInfoCommonService) GetSiteGeneral(ctx context.Context) (*schema } // GetSiteGeneral indicates an expected call of GetSiteGeneral. -func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteGeneral(ctx any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteGeneral(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteGeneral", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteGeneral), ctx) } @@ -193,7 +206,7 @@ func (m *MockSiteInfoCommonService) GetSiteInfoByType(ctx context.Context, siteT } // GetSiteInfoByType indicates an expected call of GetSiteInfoByType. -func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteInfoByType(ctx, siteType, resp any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteInfoByType(ctx, siteType, resp interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteInfoByType", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteInfoByType), ctx, siteType, resp) } @@ -208,7 +221,7 @@ func (m *MockSiteInfoCommonService) GetSiteInterface(ctx context.Context) (*sche } // GetSiteInterface indicates an expected call of GetSiteInterface. -func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteInterface(ctx any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteInterface(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteInterface", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteInterface), ctx) } @@ -223,7 +236,7 @@ func (m *MockSiteInfoCommonService) GetSiteLegal(ctx context.Context) (*schema.S } // GetSiteLegal indicates an expected call of GetSiteLegal. -func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteLegal(ctx any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteLegal(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteLegal", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteLegal), ctx) } @@ -238,11 +251,26 @@ func (m *MockSiteInfoCommonService) GetSiteLogin(ctx context.Context) (*schema.S } // GetSiteLogin indicates an expected call of GetSiteLogin. -func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteLogin(ctx any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteLogin(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteLogin", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteLogin), ctx) } +// GetSiteQuestion mocks base method. +func (m *MockSiteInfoCommonService) GetSiteQuestion(ctx context.Context) (*schema.SiteQuestionsResp, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSiteQuestion", ctx) + ret0, _ := ret[0].(*schema.SiteQuestionsResp) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSiteQuestion indicates an expected call of GetSiteQuestion. +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteQuestion(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteQuestion", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteQuestion), ctx) +} + // GetSiteSeo mocks base method. func (m *MockSiteInfoCommonService) GetSiteSeo(ctx context.Context) (*schema.SiteSeoResp, error) { m.ctrl.T.Helper() @@ -253,11 +281,26 @@ func (m *MockSiteInfoCommonService) GetSiteSeo(ctx context.Context) (*schema.Sit } // GetSiteSeo indicates an expected call of GetSiteSeo. -func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteSeo(ctx any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteSeo(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteSeo", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteSeo), ctx) } +// GetSiteTag mocks base method. +func (m *MockSiteInfoCommonService) GetSiteTag(ctx context.Context) (*schema.SiteTagsResp, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSiteTag", ctx) + ret0, _ := ret[0].(*schema.SiteTagsResp) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSiteTag indicates an expected call of GetSiteTag. +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteTag(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteTag", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteTag), ctx) +} + // GetSiteTheme mocks base method. func (m *MockSiteInfoCommonService) GetSiteTheme(ctx context.Context) (*schema.SiteThemeResp, error) { m.ctrl.T.Helper() @@ -268,7 +311,7 @@ func (m *MockSiteInfoCommonService) GetSiteTheme(ctx context.Context) (*schema.S } // GetSiteTheme indicates an expected call of GetSiteTheme. -func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteTheme(ctx any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteTheme(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteTheme", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteTheme), ctx) } @@ -283,7 +326,7 @@ func (m *MockSiteInfoCommonService) GetSiteUsers(ctx context.Context) (*schema.S } // GetSiteUsers indicates an expected call of GetSiteUsers. -func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteUsers(ctx any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteUsers(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteUsers", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteUsers), ctx) } @@ -298,7 +341,7 @@ func (m *MockSiteInfoCommonService) GetSiteWrite(ctx context.Context) (*schema.S } // GetSiteWrite indicates an expected call of GetSiteWrite. -func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteWrite(ctx any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteWrite(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteWrite", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteWrite), ctx) } @@ -312,7 +355,7 @@ func (m *MockSiteInfoCommonService) IsBrandingFileUsed(ctx context.Context, file } // IsBrandingFileUsed indicates an expected call of IsBrandingFileUsed. -func (mr *MockSiteInfoCommonServiceMockRecorder) IsBrandingFileUsed(ctx, filePath any) *gomock.Call { +func (mr *MockSiteInfoCommonServiceMockRecorder) IsBrandingFileUsed(ctx, filePath interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsBrandingFileUsed", reflect.TypeOf((*MockSiteInfoCommonService)(nil).IsBrandingFileUsed), ctx, filePath) } diff --git a/internal/service/question_common/question.go b/internal/service/question_common/question.go index 557a5db15..3a7306342 100644 --- a/internal/service/question_common/question.go +++ b/internal/service/question_common/question.go @@ -900,7 +900,7 @@ func (qs *QuestionCommon) tryToGetQuestionIDFromMsg(ctx context.Context, closeMs } func (qs *QuestionCommon) GetMinimumContentLength(ctx context.Context) (int, error) { - siteInfo, err := qs.siteInfoService.GetSiteWrite(ctx) + siteInfo, err := qs.siteInfoService.GetSiteQuestion(ctx) if err != nil { return 6, err } diff --git a/internal/service/siteinfo/siteinfo_service.go b/internal/service/siteinfo/siteinfo_service.go index 1eb187067..480606633 100644 --- a/internal/service/siteinfo/siteinfo_service.go +++ b/internal/service/siteinfo/siteinfo_service.go @@ -103,17 +103,14 @@ func (s *SiteInfoService) GetSiteUsers(ctx context.Context) (resp *schema.SiteUs return s.siteInfoCommonService.GetSiteUsers(ctx) } -// GetSiteWrite get site info write -func (s *SiteInfoService) GetSiteWrite(ctx context.Context) (resp *schema.SiteWriteResp, err error) { - resp = &schema.SiteWriteResp{} - siteInfo, exist, err := s.siteInfoRepo.GetByType(ctx, constant.SiteTypeWrite) +// GetSiteTag get site info write +func (s *SiteInfoService) GetSiteTag(ctx context.Context) (resp *schema.SiteTagsResp, err error) { + resp = &schema.SiteTagsResp{} + _, err = s.siteInfoCommonService.GetSiteTag(ctx) if err != nil { log.Error(err) return resp, nil } - if exist { - _ = json.Unmarshal([]byte(siteInfo.Content), resp) - } resp.RecommendTags, err = s.tagCommonService.GetSiteWriteRecommendTag(ctx) if err != nil { @@ -126,6 +123,16 @@ func (s *SiteInfoService) GetSiteWrite(ctx context.Context) (resp *schema.SiteWr return resp, nil } +// GetSiteQuestion get site questions settings +func (s *SiteInfoService) GetSiteQuestion(ctx context.Context) (resp *schema.SiteQuestionsResp, err error) { + return s.siteInfoCommonService.GetSiteQuestion(ctx) +} + +// GetSiteAdvanced get site advanced settings +func (s *SiteInfoService) GetSiteAdvanced(ctx context.Context) (resp *schema.SiteAdvancedResp, err error) { + return s.siteInfoCommonService.GetSiteAdvanced(ctx) +} + // GetSiteLegal get site legal info func (s *SiteInfoService) GetSiteLegal(ctx context.Context) (resp *schema.SiteLegalResp, err error) { return s.siteInfoCommonService.GetSiteLegal(ctx) @@ -182,8 +189,30 @@ func (s *SiteInfoService) SaveSiteBranding(ctx context.Context, req *schema.Site return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeBranding, data) } -// SaveSiteWrite save site configuration about write -func (s *SiteInfoService) SaveSiteWrite(ctx context.Context, req *schema.SiteWriteReq) (resp any, err error) { +// SaveSiteAdvanced save site advanced configuration +func (s *SiteInfoService) SaveSiteAdvanced(ctx context.Context, req *schema.SiteAdvancedReq) (resp any, err error) { + content, _ := json.Marshal(req) + data := &entity.SiteInfo{ + Type: constant.SiteTypeAdvanced, + Content: string(content), + Status: 1, + } + return nil, s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeAdvanced, data) +} + +// SaveSiteQuestions save site questions configuration +func (s *SiteInfoService) SaveSiteQuestions(ctx context.Context, req *schema.SiteQuestionsReq) (resp any, err error) { + content, _ := json.Marshal(req) + data := &entity.SiteInfo{ + Type: constant.SiteTypeQuestions, + Content: string(content), + Status: 1, + } + return nil, s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeQuestions, data) +} + +// SaveSiteTags save site tags configuration +func (s *SiteInfoService) SaveSiteTags(ctx context.Context, req *schema.SiteTagsReq) (resp any, err error) { recommendTags, reservedTags := make([]string, 0), make([]string, 0) recommendTagMapping, reservedTagMapping := make(map[string]bool), make(map[string]bool) for _, tag := range req.ReservedTags { @@ -210,11 +239,11 @@ func (s *SiteInfoService) SaveSiteWrite(ctx context.Context, req *schema.SiteWri content, _ := json.Marshal(req) data := &entity.SiteInfo{ - Type: constant.SiteTypeWrite, + Type: constant.SiteTypeTags, Content: string(content), Status: 1, } - return nil, s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeWrite, data) + return nil, s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeTags, data) } // SaveSiteLegal save site legal configuration diff --git a/internal/service/siteinfo_common/siteinfo_service.go b/internal/service/siteinfo_common/siteinfo_service.go index 8a4b13669..5c1c790ad 100644 --- a/internal/service/siteinfo_common/siteinfo_service.go +++ b/internal/service/siteinfo_common/siteinfo_service.go @@ -51,6 +51,9 @@ type SiteInfoCommonService interface { FormatAvatar(ctx context.Context, originalAvatarData, email string, userStatus int) *schema.AvatarInfo FormatListAvatar(ctx context.Context, userList []*entity.User) (userID2AvatarMapping map[string]*schema.AvatarInfo) GetSiteWrite(ctx context.Context) (resp *schema.SiteWriteResp, err error) + GetSiteAdvanced(ctx context.Context) (resp *schema.SiteAdvancedResp, err error) + GetSiteQuestion(ctx context.Context) (resp *schema.SiteQuestionsResp, err error) + GetSiteTag(ctx context.Context) (resp *schema.SiteTagsResp, err error) GetSiteLegal(ctx context.Context) (resp *schema.SiteLegalResp, err error) GetSiteLogin(ctx context.Context) (resp *schema.SiteLoginResp, err error) GetSiteCustomCssHTML(ctx context.Context) (resp *schema.SiteCustomCssHTMLResp, err error) @@ -167,6 +170,33 @@ func (s *siteInfoCommonService) GetSiteWrite(ctx context.Context) (resp *schema. return resp, nil } +// GetSiteAdvanced get site info advanced +func (s *siteInfoCommonService) GetSiteAdvanced(ctx context.Context) (resp *schema.SiteAdvancedResp, err error) { + resp = &schema.SiteAdvancedResp{} + if err = s.GetSiteInfoByType(ctx, constant.SiteTypeAdvanced, resp); err != nil { + return nil, err + } + return resp, nil +} + +// GetSiteQuestion get site info question +func (s *siteInfoCommonService) GetSiteQuestion(ctx context.Context) (resp *schema.SiteQuestionsResp, err error) { + resp = &schema.SiteQuestionsResp{} + if err = s.GetSiteInfoByType(ctx, constant.SiteTypeQuestions, resp); err != nil { + return nil, err + } + return resp, nil +} + +// GetSiteTag get site info tag +func (s *siteInfoCommonService) GetSiteTag(ctx context.Context) (resp *schema.SiteTagsResp, err error) { + resp = &schema.SiteTagsResp{} + if err = s.GetSiteInfoByType(ctx, constant.SiteTypeTags, resp); err != nil { + return nil, err + } + return resp, nil +} + // GetSiteLegal get site info write func (s *siteInfoCommonService) GetSiteLegal(ctx context.Context) (resp *schema.SiteLegalResp, err error) { resp = &schema.SiteLegalResp{} diff --git a/internal/service/tag_common/tag_common.go b/internal/service/tag_common/tag_common.go index 9ca8e100f..0da9f6fd5 100644 --- a/internal/service/tag_common/tag_common.go +++ b/internal/service/tag_common/tag_common.go @@ -270,7 +270,7 @@ func (ts *TagCommonService) GetTagListByNames(ctx context.Context, tagNames []st } func (ts *TagCommonService) ExistRecommend(ctx context.Context, tags []*schema.TagItem) (bool, error) { - taginfo, err := ts.siteInfoService.GetSiteWrite(ctx) + taginfo, err := ts.siteInfoService.GetSiteTag(ctx) if err != nil { return false, err } @@ -295,7 +295,7 @@ func (ts *TagCommonService) ExistRecommend(ctx context.Context, tags []*schema.T } func (ts *TagCommonService) GetMinimumTags(ctx context.Context) (int, error) { - siteInfo, err := ts.siteInfoService.GetSiteWrite(ctx) + siteInfo, err := ts.siteInfoService.GetSiteTag(ctx) if err != nil { return 1, err } @@ -469,7 +469,7 @@ func (ts *TagCommonService) TagsFormatRecommendAndReserved(ctx context.Context, if len(tagList) == 0 { return } - tagConfig, err := ts.siteInfoService.GetSiteWrite(ctx) + tagConfig, err := ts.siteInfoService.GetSiteTag(ctx) if err != nil { log.Error(err) return @@ -485,7 +485,7 @@ func (ts *TagCommonService) tagFormatRecommendAndReserved(ctx context.Context, t if tag == nil { return } - tagConfig, err := ts.siteInfoService.GetSiteWrite(ctx) + tagConfig, err := ts.siteInfoService.GetSiteTag(ctx) if err != nil { log.Error(err) return diff --git a/internal/service/uploader/upload.go b/internal/service/uploader/upload.go index 8dea746ce..58f808468 100644 --- a/internal/service/uploader/upload.go +++ b/internal/service/uploader/upload.go @@ -109,12 +109,12 @@ func (us *uploaderService) UploadAvatarFile(ctx *gin.Context, userID string) (ur return url, nil } - siteWrite, err := us.siteInfoService.GetSiteWrite(ctx) + siteAdvanced, err := us.siteInfoService.GetSiteAdvanced(ctx) if err != nil { return "", err } - ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, siteWrite.GetMaxImageSize()) + ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, siteAdvanced.GetMaxImageSize()) file, fileHeader, err := ctx.Request.FormFile("file") if err != nil { return "", errors.BadRequest(reason.RequestFormatError).WithError(err) @@ -201,12 +201,12 @@ func (us *uploaderService) UploadPostFile(ctx *gin.Context, userID string) ( return url, nil } - siteWrite, err := us.siteInfoService.GetSiteWrite(ctx) + siteAdvanced, err := us.siteInfoService.GetSiteAdvanced(ctx) if err != nil { return "", err } - ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, siteWrite.GetMaxImageSize()) + ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, siteAdvanced.GetMaxImageSize()) file, fileHeader, err := ctx.Request.FormFile("file") if err != nil { return "", errors.BadRequest(reason.RequestFormatError).WithError(err) @@ -214,7 +214,7 @@ func (us *uploaderService) UploadPostFile(ctx *gin.Context, userID string) ( defer func() { _ = file.Close() }() - if checker.IsUnAuthorizedExtension(fileHeader.Filename, siteWrite.AuthorizedImageExtensions) { + if checker.IsUnAuthorizedExtension(fileHeader.Filename, siteAdvanced.AuthorizedImageExtensions) { return "", errors.BadRequest(reason.RequestFormatError).WithError(err) } @@ -239,7 +239,7 @@ func (us *uploaderService) UploadPostAttachment(ctx *gin.Context, userID string) return url, nil } - resp, err := us.siteInfoService.GetSiteWrite(ctx) + resp, err := us.siteInfoService.GetSiteAdvanced(ctx) if err != nil { return "", err } @@ -277,12 +277,12 @@ func (us *uploaderService) UploadBrandingFile(ctx *gin.Context, userID string) ( return url, nil } - siteWrite, err := us.siteInfoService.GetSiteWrite(ctx) + siteAdvanced, err := us.siteInfoService.GetSiteAdvanced(ctx) if err != nil { return "", err } - ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, siteWrite.GetMaxImageSize()) + ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, siteAdvanced.GetMaxImageSize()) file, fileHeader, err := ctx.Request.FormFile("file") if err != nil { return "", errors.BadRequest(reason.RequestFormatError).WithError(err) @@ -311,7 +311,7 @@ func (us *uploaderService) uploadImageFile(ctx *gin.Context, file *multipart.Fil if err != nil { return "", err } - siteWrite, err := us.siteInfoService.GetSiteWrite(ctx) + siteAdvanced, err := us.siteInfoService.GetSiteAdvanced(ctx) if err != nil { return "", err } @@ -328,7 +328,7 @@ func (us *uploaderService) uploadImageFile(ctx *gin.Context, file *multipart.Fil _ = src.Close() }() - if !checker.DecodeAndCheckImageFile(filePath, siteWrite.GetMaxImageMegapixel()) { + if !checker.DecodeAndCheckImageFile(filePath, siteAdvanced.GetMaxImageMegapixel()) { return "", errors.BadRequest(reason.UploadFileUnsupportedFileFormat) } @@ -364,17 +364,17 @@ func (us *uploaderService) uploadAttachmentFile(ctx *gin.Context, file *multipar func (us *uploaderService) tryToUploadByPlugin(ctx *gin.Context, source plugin.UploadSource) ( url string, err error) { - siteWrite, err := us.siteInfoService.GetSiteWrite(ctx) + siteAdvanced, err := us.siteInfoService.GetSiteAdvanced(ctx) if err != nil { return "", err } cond := plugin.UploadFileCondition{ Source: source, - MaxImageSize: siteWrite.MaxImageSize, - MaxAttachmentSize: siteWrite.MaxAttachmentSize, - MaxImageMegapixel: siteWrite.MaxImageMegapixel, - AuthorizedImageExtensions: siteWrite.AuthorizedImageExtensions, - AuthorizedAttachmentExtensions: siteWrite.AuthorizedAttachmentExtensions, + MaxImageSize: siteAdvanced.MaxImageSize, + MaxAttachmentSize: siteAdvanced.MaxAttachmentSize, + MaxImageMegapixel: siteAdvanced.MaxImageMegapixel, + AuthorizedImageExtensions: siteAdvanced.AuthorizedImageExtensions, + AuthorizedAttachmentExtensions: siteAdvanced.AuthorizedAttachmentExtensions, } _ = plugin.CallStorage(func(fn plugin.Storage) error { resp := fn.UploadFile(ctx, cond) From 9efa9471bd841ddcad2f5e97bf5def12ad96756e Mon Sep 17 00:00:00 2001 From: kumfo Date: Tue, 20 Jan 2026 15:32:45 +0800 Subject: [PATCH 12/19] feat(menu): update admin menu settings to include questions, tags, and advanced options --- docs/docs.go | 438 ++++++++++++++++++++++++++++++++-------------- docs/swagger.json | 438 ++++++++++++++++++++++++++++++++-------------- docs/swagger.yaml | 298 ++++++++++++++++++++----------- 3 files changed, 804 insertions(+), 370 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index fc4d9909e..73d85436c 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -856,6 +856,77 @@ const docTemplate = `{ } } }, + "/answer/admin/api/siteinfo/advanced": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get site advanced setting", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "get site advanced setting", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SiteAdvancedResp" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "update site advanced info", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "update site advanced info", + "parameters": [ + { + "description": "advanced settings", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.SiteAdvancedReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, "/answer/admin/api/siteinfo/branding": { "get": { "security": [ @@ -1282,6 +1353,77 @@ const docTemplate = `{ } } }, + "/answer/admin/api/siteinfo/question": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get site questions setting", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "get site questions setting", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SiteQuestionsResp" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "update site question settings", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "update site question settings", + "parameters": [ + { + "description": "questions settings", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.SiteQuestionsReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, "/answer/admin/api/siteinfo/seo": { "get": { "security": [ @@ -1353,21 +1495,21 @@ const docTemplate = `{ } } }, - "/answer/admin/api/siteinfo/theme": { + "/answer/admin/api/siteinfo/tag": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "get site info theme config", + "description": "get site tags setting", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "get site info theme config", + "summary": "get site tags setting", "responses": { "200": { "description": "OK", @@ -1380,7 +1522,7 @@ const docTemplate = `{ "type": "object", "properties": { "data": { - "$ref": "#/definitions/schema.SiteThemeResp" + "$ref": "#/definitions/schema.SiteTagsResp" } } } @@ -1395,22 +1537,22 @@ const docTemplate = `{ "ApiKeyAuth": [] } ], - "description": "update site custom css html config", + "description": "update site tag settings", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "update site custom css html config", + "summary": "update site tag settings", "parameters": [ { - "description": "login info", + "description": "tags settings", "name": "data", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/schema.SiteThemeReq" + "$ref": "#/definitions/schema.SiteTagsReq" } } ], @@ -1424,21 +1566,21 @@ const docTemplate = `{ } } }, - "/answer/admin/api/siteinfo/users": { + "/answer/admin/api/siteinfo/theme": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "get site user config", + "description": "get site info theme config", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "get site user config", + "summary": "get site info theme config", "responses": { "200": { "description": "OK", @@ -1451,7 +1593,7 @@ const docTemplate = `{ "type": "object", "properties": { "data": { - "$ref": "#/definitions/schema.SiteUsersResp" + "$ref": "#/definitions/schema.SiteThemeResp" } } } @@ -1466,22 +1608,22 @@ const docTemplate = `{ "ApiKeyAuth": [] } ], - "description": "update site info config about users", + "description": "update site custom css html config", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "update site info config about users", + "summary": "update site custom css html config", "parameters": [ { - "description": "users info", + "description": "login info", "name": "data", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/schema.SiteUsersReq" + "$ref": "#/definitions/schema.SiteThemeReq" } } ], @@ -1495,21 +1637,21 @@ const docTemplate = `{ } } }, - "/answer/admin/api/siteinfo/write": { + "/answer/admin/api/siteinfo/users": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "get site interface", + "description": "get site user config", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "get site interface", + "summary": "get site user config", "responses": { "200": { "description": "OK", @@ -1522,7 +1664,7 @@ const docTemplate = `{ "type": "object", "properties": { "data": { - "$ref": "#/definitions/schema.SiteWriteResp" + "$ref": "#/definitions/schema.SiteUsersResp" } } } @@ -1537,22 +1679,22 @@ const docTemplate = `{ "ApiKeyAuth": [] } ], - "description": "update site write info", + "description": "update site info config about users", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "update site write info", + "summary": "update site info config about users", "parameters": [ { - "description": "write info", + "description": "users info", "name": "data", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/schema.SiteWriteReq" + "$ref": "#/definitions/schema.SiteUsersReq" } } ], @@ -10478,6 +10620,58 @@ const docTemplate = `{ } } }, + "schema.SiteAdvancedReq": { + "type": "object", + "properties": { + "authorized_attachment_extensions": { + "type": "array", + "items": { + "type": "string" + } + }, + "authorized_image_extensions": { + "type": "array", + "items": { + "type": "string" + } + }, + "max_attachment_size": { + "type": "integer" + }, + "max_image_megapixel": { + "type": "integer" + }, + "max_image_size": { + "type": "integer" + } + } + }, + "schema.SiteAdvancedResp": { + "type": "object", + "properties": { + "authorized_attachment_extensions": { + "type": "array", + "items": { + "type": "string" + } + }, + "authorized_image_extensions": { + "type": "array", + "items": { + "type": "string" + } + }, + "max_attachment_size": { + "type": "integer" + }, + "max_image_megapixel": { + "type": "integer" + }, + "max_image_size": { + "type": "integer" + } + } + }, "schema.SiteBrandingReq": { "type": "object", "properties": { @@ -10657,18 +10851,24 @@ const docTemplate = `{ "revision": { "type": "string" }, + "site_advanced": { + "$ref": "#/definitions/schema.SiteAdvancedResp" + }, "site_legal": { "$ref": "#/definitions/schema.SiteLegalSimpleResp" }, + "site_questions": { + "$ref": "#/definitions/schema.SiteQuestionsResp" + }, "site_seo": { "$ref": "#/definitions/schema.SiteSeoResp" }, + "site_tags": { + "$ref": "#/definitions/schema.SiteTagsResp" + }, "site_users": { "$ref": "#/definitions/schema.SiteUsersResp" }, - "site_write": { - "$ref": "#/definitions/schema.SiteWriteResp" - }, "theme": { "$ref": "#/definitions/schema.SiteThemeResp" }, @@ -10848,6 +11048,32 @@ const docTemplate = `{ } } }, + "schema.SiteQuestionsReq": { + "type": "object", + "properties": { + "min_content": { + "type": "integer", + "maximum": 65535, + "minimum": 0 + }, + "restrict_answer": { + "type": "boolean" + } + } + }, + "schema.SiteQuestionsResp": { + "type": "object", + "properties": { + "min_content": { + "type": "integer", + "maximum": 65535, + "minimum": 0 + }, + "restrict_answer": { + "type": "boolean" + } + } + }, "schema.SiteSeoReq": { "type": "object", "required": [ @@ -10882,6 +11108,56 @@ const docTemplate = `{ } } }, + "schema.SiteTagsReq": { + "type": "object", + "properties": { + "min_tags": { + "type": "integer", + "maximum": 5, + "minimum": 0 + }, + "recommend_tags": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.SiteWriteTag" + } + }, + "required_tag": { + "type": "boolean" + }, + "reserved_tags": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.SiteWriteTag" + } + } + } + }, + "schema.SiteTagsResp": { + "type": "object", + "properties": { + "min_tags": { + "type": "integer", + "maximum": 5, + "minimum": 0 + }, + "recommend_tags": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.SiteWriteTag" + } + }, + "required_tag": { + "type": "boolean" + }, + "reserved_tags": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.SiteWriteTag" + } + } + } + }, "schema.SiteThemeReq": { "type": "object", "required": [ @@ -10995,114 +11271,6 @@ const docTemplate = `{ } } }, - "schema.SiteWriteReq": { - "type": "object", - "properties": { - "authorized_attachment_extensions": { - "type": "array", - "items": { - "type": "string" - } - }, - "authorized_image_extensions": { - "type": "array", - "items": { - "type": "string" - } - }, - "max_attachment_size": { - "type": "integer" - }, - "max_image_megapixel": { - "type": "integer" - }, - "max_image_size": { - "type": "integer" - }, - "min_content": { - "type": "integer", - "maximum": 65535, - "minimum": 0 - }, - "min_tags": { - "type": "integer", - "maximum": 5, - "minimum": 0 - }, - "recommend_tags": { - "type": "array", - "items": { - "$ref": "#/definitions/schema.SiteWriteTag" - } - }, - "required_tag": { - "type": "boolean" - }, - "reserved_tags": { - "type": "array", - "items": { - "$ref": "#/definitions/schema.SiteWriteTag" - } - }, - "restrict_answer": { - "type": "boolean" - } - } - }, - "schema.SiteWriteResp": { - "type": "object", - "properties": { - "authorized_attachment_extensions": { - "type": "array", - "items": { - "type": "string" - } - }, - "authorized_image_extensions": { - "type": "array", - "items": { - "type": "string" - } - }, - "max_attachment_size": { - "type": "integer" - }, - "max_image_megapixel": { - "type": "integer" - }, - "max_image_size": { - "type": "integer" - }, - "min_content": { - "type": "integer", - "maximum": 65535, - "minimum": 0 - }, - "min_tags": { - "type": "integer", - "maximum": 5, - "minimum": 0 - }, - "recommend_tags": { - "type": "array", - "items": { - "$ref": "#/definitions/schema.SiteWriteTag" - } - }, - "required_tag": { - "type": "boolean" - }, - "reserved_tags": { - "type": "array", - "items": { - "$ref": "#/definitions/schema.SiteWriteTag" - } - }, - "restrict_answer": { - "type": "boolean" - } - } - }, "schema.SiteWriteTag": { "type": "object", "required": [ diff --git a/docs/swagger.json b/docs/swagger.json index e0f6378e4..6dbecc50a 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -848,6 +848,77 @@ } } }, + "/answer/admin/api/siteinfo/advanced": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get site advanced setting", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "get site advanced setting", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SiteAdvancedResp" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "update site advanced info", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "update site advanced info", + "parameters": [ + { + "description": "advanced settings", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.SiteAdvancedReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, "/answer/admin/api/siteinfo/branding": { "get": { "security": [ @@ -1274,6 +1345,77 @@ } } }, + "/answer/admin/api/siteinfo/question": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get site questions setting", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "get site questions setting", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SiteQuestionsResp" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "update site question settings", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "update site question settings", + "parameters": [ + { + "description": "questions settings", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.SiteQuestionsReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, "/answer/admin/api/siteinfo/seo": { "get": { "security": [ @@ -1345,21 +1487,21 @@ } } }, - "/answer/admin/api/siteinfo/theme": { + "/answer/admin/api/siteinfo/tag": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "get site info theme config", + "description": "get site tags setting", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "get site info theme config", + "summary": "get site tags setting", "responses": { "200": { "description": "OK", @@ -1372,7 +1514,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/definitions/schema.SiteThemeResp" + "$ref": "#/definitions/schema.SiteTagsResp" } } } @@ -1387,22 +1529,22 @@ "ApiKeyAuth": [] } ], - "description": "update site custom css html config", + "description": "update site tag settings", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "update site custom css html config", + "summary": "update site tag settings", "parameters": [ { - "description": "login info", + "description": "tags settings", "name": "data", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/schema.SiteThemeReq" + "$ref": "#/definitions/schema.SiteTagsReq" } } ], @@ -1416,21 +1558,21 @@ } } }, - "/answer/admin/api/siteinfo/users": { + "/answer/admin/api/siteinfo/theme": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "get site user config", + "description": "get site info theme config", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "get site user config", + "summary": "get site info theme config", "responses": { "200": { "description": "OK", @@ -1443,7 +1585,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/definitions/schema.SiteUsersResp" + "$ref": "#/definitions/schema.SiteThemeResp" } } } @@ -1458,22 +1600,22 @@ "ApiKeyAuth": [] } ], - "description": "update site info config about users", + "description": "update site custom css html config", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "update site info config about users", + "summary": "update site custom css html config", "parameters": [ { - "description": "users info", + "description": "login info", "name": "data", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/schema.SiteUsersReq" + "$ref": "#/definitions/schema.SiteThemeReq" } } ], @@ -1487,21 +1629,21 @@ } } }, - "/answer/admin/api/siteinfo/write": { + "/answer/admin/api/siteinfo/users": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "get site interface", + "description": "get site user config", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "get site interface", + "summary": "get site user config", "responses": { "200": { "description": "OK", @@ -1514,7 +1656,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/definitions/schema.SiteWriteResp" + "$ref": "#/definitions/schema.SiteUsersResp" } } } @@ -1529,22 +1671,22 @@ "ApiKeyAuth": [] } ], - "description": "update site write info", + "description": "update site info config about users", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "update site write info", + "summary": "update site info config about users", "parameters": [ { - "description": "write info", + "description": "users info", "name": "data", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/schema.SiteWriteReq" + "$ref": "#/definitions/schema.SiteUsersReq" } } ], @@ -10470,6 +10612,58 @@ } } }, + "schema.SiteAdvancedReq": { + "type": "object", + "properties": { + "authorized_attachment_extensions": { + "type": "array", + "items": { + "type": "string" + } + }, + "authorized_image_extensions": { + "type": "array", + "items": { + "type": "string" + } + }, + "max_attachment_size": { + "type": "integer" + }, + "max_image_megapixel": { + "type": "integer" + }, + "max_image_size": { + "type": "integer" + } + } + }, + "schema.SiteAdvancedResp": { + "type": "object", + "properties": { + "authorized_attachment_extensions": { + "type": "array", + "items": { + "type": "string" + } + }, + "authorized_image_extensions": { + "type": "array", + "items": { + "type": "string" + } + }, + "max_attachment_size": { + "type": "integer" + }, + "max_image_megapixel": { + "type": "integer" + }, + "max_image_size": { + "type": "integer" + } + } + }, "schema.SiteBrandingReq": { "type": "object", "properties": { @@ -10649,18 +10843,24 @@ "revision": { "type": "string" }, + "site_advanced": { + "$ref": "#/definitions/schema.SiteAdvancedResp" + }, "site_legal": { "$ref": "#/definitions/schema.SiteLegalSimpleResp" }, + "site_questions": { + "$ref": "#/definitions/schema.SiteQuestionsResp" + }, "site_seo": { "$ref": "#/definitions/schema.SiteSeoResp" }, + "site_tags": { + "$ref": "#/definitions/schema.SiteTagsResp" + }, "site_users": { "$ref": "#/definitions/schema.SiteUsersResp" }, - "site_write": { - "$ref": "#/definitions/schema.SiteWriteResp" - }, "theme": { "$ref": "#/definitions/schema.SiteThemeResp" }, @@ -10840,6 +11040,32 @@ } } }, + "schema.SiteQuestionsReq": { + "type": "object", + "properties": { + "min_content": { + "type": "integer", + "maximum": 65535, + "minimum": 0 + }, + "restrict_answer": { + "type": "boolean" + } + } + }, + "schema.SiteQuestionsResp": { + "type": "object", + "properties": { + "min_content": { + "type": "integer", + "maximum": 65535, + "minimum": 0 + }, + "restrict_answer": { + "type": "boolean" + } + } + }, "schema.SiteSeoReq": { "type": "object", "required": [ @@ -10874,6 +11100,56 @@ } } }, + "schema.SiteTagsReq": { + "type": "object", + "properties": { + "min_tags": { + "type": "integer", + "maximum": 5, + "minimum": 0 + }, + "recommend_tags": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.SiteWriteTag" + } + }, + "required_tag": { + "type": "boolean" + }, + "reserved_tags": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.SiteWriteTag" + } + } + } + }, + "schema.SiteTagsResp": { + "type": "object", + "properties": { + "min_tags": { + "type": "integer", + "maximum": 5, + "minimum": 0 + }, + "recommend_tags": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.SiteWriteTag" + } + }, + "required_tag": { + "type": "boolean" + }, + "reserved_tags": { + "type": "array", + "items": { + "$ref": "#/definitions/schema.SiteWriteTag" + } + } + } + }, "schema.SiteThemeReq": { "type": "object", "required": [ @@ -10987,114 +11263,6 @@ } } }, - "schema.SiteWriteReq": { - "type": "object", - "properties": { - "authorized_attachment_extensions": { - "type": "array", - "items": { - "type": "string" - } - }, - "authorized_image_extensions": { - "type": "array", - "items": { - "type": "string" - } - }, - "max_attachment_size": { - "type": "integer" - }, - "max_image_megapixel": { - "type": "integer" - }, - "max_image_size": { - "type": "integer" - }, - "min_content": { - "type": "integer", - "maximum": 65535, - "minimum": 0 - }, - "min_tags": { - "type": "integer", - "maximum": 5, - "minimum": 0 - }, - "recommend_tags": { - "type": "array", - "items": { - "$ref": "#/definitions/schema.SiteWriteTag" - } - }, - "required_tag": { - "type": "boolean" - }, - "reserved_tags": { - "type": "array", - "items": { - "$ref": "#/definitions/schema.SiteWriteTag" - } - }, - "restrict_answer": { - "type": "boolean" - } - } - }, - "schema.SiteWriteResp": { - "type": "object", - "properties": { - "authorized_attachment_extensions": { - "type": "array", - "items": { - "type": "string" - } - }, - "authorized_image_extensions": { - "type": "array", - "items": { - "type": "string" - } - }, - "max_attachment_size": { - "type": "integer" - }, - "max_image_megapixel": { - "type": "integer" - }, - "max_image_size": { - "type": "integer" - }, - "min_content": { - "type": "integer", - "maximum": 65535, - "minimum": 0 - }, - "min_tags": { - "type": "integer", - "maximum": 5, - "minimum": 0 - }, - "recommend_tags": { - "type": "array", - "items": { - "$ref": "#/definitions/schema.SiteWriteTag" - } - }, - "required_tag": { - "type": "boolean" - }, - "reserved_tags": { - "type": "array", - "items": { - "$ref": "#/definitions/schema.SiteWriteTag" - } - }, - "restrict_answer": { - "type": "boolean" - } - } - }, "schema.SiteWriteTag": { "type": "object", "required": [ diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 42df5cbf1..f4107cd6b 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2055,6 +2055,40 @@ definitions: required: - user_id type: object + schema.SiteAdvancedReq: + properties: + authorized_attachment_extensions: + items: + type: string + type: array + authorized_image_extensions: + items: + type: string + type: array + max_attachment_size: + type: integer + max_image_megapixel: + type: integer + max_image_size: + type: integer + type: object + schema.SiteAdvancedResp: + properties: + authorized_attachment_extensions: + items: + type: string + type: array + authorized_image_extensions: + items: + type: string + type: array + max_attachment_size: + type: integer + max_image_megapixel: + type: integer + max_image_size: + type: integer + type: object schema.SiteBrandingReq: properties: favicon: @@ -2183,14 +2217,18 @@ definitions: $ref: '#/definitions/schema.SiteLoginResp' revision: type: string + site_advanced: + $ref: '#/definitions/schema.SiteAdvancedResp' site_legal: $ref: '#/definitions/schema.SiteLegalSimpleResp' + site_questions: + $ref: '#/definitions/schema.SiteQuestionsResp' site_seo: $ref: '#/definitions/schema.SiteSeoResp' + site_tags: + $ref: '#/definitions/schema.SiteTagsResp' site_users: $ref: '#/definitions/schema.SiteUsersResp' - site_write: - $ref: '#/definitions/schema.SiteWriteResp' theme: $ref: '#/definitions/schema.SiteThemeResp' version: @@ -2312,6 +2350,24 @@ definitions: login_required: type: boolean type: object + schema.SiteQuestionsReq: + properties: + min_content: + maximum: 65535 + minimum: 0 + type: integer + restrict_answer: + type: boolean + type: object + schema.SiteQuestionsResp: + properties: + min_content: + maximum: 65535 + minimum: 0 + type: integer + restrict_answer: + type: boolean + type: object schema.SiteSeoReq: properties: permalink: @@ -2336,6 +2392,40 @@ definitions: - permalink - robots type: object + schema.SiteTagsReq: + properties: + min_tags: + maximum: 5 + minimum: 0 + type: integer + recommend_tags: + items: + $ref: '#/definitions/schema.SiteWriteTag' + type: array + required_tag: + type: boolean + reserved_tags: + items: + $ref: '#/definitions/schema.SiteWriteTag' + type: array + type: object + schema.SiteTagsResp: + properties: + min_tags: + maximum: 5 + minimum: 0 + type: integer + recommend_tags: + items: + $ref: '#/definitions/schema.SiteWriteTag' + type: array + required_tag: + type: boolean + reserved_tags: + items: + $ref: '#/definitions/schema.SiteWriteTag' + type: array + type: object schema.SiteThemeReq: properties: color_scheme: @@ -2412,80 +2502,6 @@ definitions: required: - default_avatar type: object - schema.SiteWriteReq: - properties: - authorized_attachment_extensions: - items: - type: string - type: array - authorized_image_extensions: - items: - type: string - type: array - max_attachment_size: - type: integer - max_image_megapixel: - type: integer - max_image_size: - type: integer - min_content: - maximum: 65535 - minimum: 0 - type: integer - min_tags: - maximum: 5 - minimum: 0 - type: integer - recommend_tags: - items: - $ref: '#/definitions/schema.SiteWriteTag' - type: array - required_tag: - type: boolean - reserved_tags: - items: - $ref: '#/definitions/schema.SiteWriteTag' - type: array - restrict_answer: - type: boolean - type: object - schema.SiteWriteResp: - properties: - authorized_attachment_extensions: - items: - type: string - type: array - authorized_image_extensions: - items: - type: string - type: array - max_attachment_size: - type: integer - max_image_megapixel: - type: integer - max_image_size: - type: integer - min_content: - maximum: 65535 - minimum: 0 - type: integer - min_tags: - maximum: 5 - minimum: 0 - type: integer - recommend_tags: - items: - $ref: '#/definitions/schema.SiteWriteTag' - type: array - required_tag: - type: boolean - reserved_tags: - items: - $ref: '#/definitions/schema.SiteWriteTag' - type: array - restrict_answer: - type: boolean - type: object schema.SiteWriteTag: properties: display_name: @@ -3685,6 +3701,47 @@ paths: summary: update smtp config tags: - admin + /answer/admin/api/siteinfo/advanced: + get: + description: get site advanced setting + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + $ref: '#/definitions/schema.SiteAdvancedResp' + type: object + security: + - ApiKeyAuth: [] + summary: get site advanced setting + tags: + - admin + put: + description: update site advanced info + parameters: + - description: advanced settings + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.SiteAdvancedReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: update site advanced info + tags: + - admin /answer/admin/api/siteinfo/branding: get: description: get site interface @@ -3931,6 +3988,47 @@ paths: summary: update site login tags: - admin + /answer/admin/api/siteinfo/question: + get: + description: get site questions setting + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + $ref: '#/definitions/schema.SiteQuestionsResp' + type: object + security: + - ApiKeyAuth: [] + summary: get site questions setting + tags: + - admin + put: + description: update site question settings + parameters: + - description: questions settings + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.SiteQuestionsReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: update site question settings + tags: + - admin /answer/admin/api/siteinfo/seo: get: description: get site seo information @@ -3972,9 +4070,9 @@ paths: summary: update site seo information tags: - admin - /answer/admin/api/siteinfo/theme: + /answer/admin/api/siteinfo/tag: get: - description: get site info theme config + description: get site tags setting produces: - application/json responses: @@ -3985,22 +4083,22 @@ paths: - $ref: '#/definitions/handler.RespBody' - properties: data: - $ref: '#/definitions/schema.SiteThemeResp' + $ref: '#/definitions/schema.SiteTagsResp' type: object security: - ApiKeyAuth: [] - summary: get site info theme config + summary: get site tags setting tags: - admin put: - description: update site custom css html config + description: update site tag settings parameters: - - description: login info + - description: tags settings in: body name: data required: true schema: - $ref: '#/definitions/schema.SiteThemeReq' + $ref: '#/definitions/schema.SiteTagsReq' produces: - application/json responses: @@ -4010,12 +4108,12 @@ paths: $ref: '#/definitions/handler.RespBody' security: - ApiKeyAuth: [] - summary: update site custom css html config + summary: update site tag settings tags: - admin - /answer/admin/api/siteinfo/users: + /answer/admin/api/siteinfo/theme: get: - description: get site user config + description: get site info theme config produces: - application/json responses: @@ -4026,22 +4124,22 @@ paths: - $ref: '#/definitions/handler.RespBody' - properties: data: - $ref: '#/definitions/schema.SiteUsersResp' + $ref: '#/definitions/schema.SiteThemeResp' type: object security: - ApiKeyAuth: [] - summary: get site user config + summary: get site info theme config tags: - admin put: - description: update site info config about users + description: update site custom css html config parameters: - - description: users info + - description: login info in: body name: data required: true schema: - $ref: '#/definitions/schema.SiteUsersReq' + $ref: '#/definitions/schema.SiteThemeReq' produces: - application/json responses: @@ -4051,12 +4149,12 @@ paths: $ref: '#/definitions/handler.RespBody' security: - ApiKeyAuth: [] - summary: update site info config about users + summary: update site custom css html config tags: - admin - /answer/admin/api/siteinfo/write: + /answer/admin/api/siteinfo/users: get: - description: get site interface + description: get site user config produces: - application/json responses: @@ -4067,22 +4165,22 @@ paths: - $ref: '#/definitions/handler.RespBody' - properties: data: - $ref: '#/definitions/schema.SiteWriteResp' + $ref: '#/definitions/schema.SiteUsersResp' type: object security: - ApiKeyAuth: [] - summary: get site interface + summary: get site user config tags: - admin put: - description: update site write info + description: update site info config about users parameters: - - description: write info + - description: users info in: body name: data required: true schema: - $ref: '#/definitions/schema.SiteWriteReq' + $ref: '#/definitions/schema.SiteUsersReq' produces: - application/json responses: @@ -4092,7 +4190,7 @@ paths: $ref: '#/definitions/handler.RespBody' security: - ApiKeyAuth: [] - summary: update site write info + summary: update site info config about users tags: - admin /answer/admin/api/theme/options: From c2a0bee7dc5e6066ab20c6417fe9f89f6c553a86 Mon Sep 17 00:00:00 2001 From: kumfo Date: Wed, 21 Jan 2026 09:39:37 +0800 Subject: [PATCH 13/19] feat(siteinfo): add users settings endpoint and update interface settings structure --- docs/docs.go | 127 ++++++++++++++++-- docs/swagger.json | 127 ++++++++++++++++-- docs/swagger.yaml | 81 +++++++++-- internal/base/constant/site_type.go | 18 ++- internal/controller/siteinfo_controller.go | 5 + .../controller_admin/siteinfo_controller.go | 33 ++++- internal/migrations/v30.go | 96 +++++++++++++ internal/router/answer_api_router.go | 4 + internal/schema/siteinfo_schema.go | 58 +++++--- internal/service/siteinfo/siteinfo_service.go | 21 ++- .../siteinfo_common/siteinfo_service.go | 20 ++- 11 files changed, 516 insertions(+), 74 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index 73d85436c..b030c3d3d 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1167,7 +1167,7 @@ const docTemplate = `{ "type": "object", "properties": { "data": { - "$ref": "#/definitions/schema.SiteInterfaceResp" + "$ref": "#/definitions/schema.SiteInterfaceSettingsResp" } } } @@ -1708,6 +1708,77 @@ const docTemplate = `{ } } }, + "/answer/admin/api/siteinfo/users-settings": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get site interface", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "get site interface", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SiteUsersSettingsResp" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "update site info users settings", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "update site info users settings", + "parameters": [ + { + "description": "general", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.SiteUsersSettingsReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, "/answer/admin/api/theme/options": { "get": { "security": [ @@ -10843,7 +10914,7 @@ const docTemplate = `{ "$ref": "#/definitions/schema.SiteGeneralResp" }, "interface": { - "$ref": "#/definitions/schema.SiteInterfaceResp" + "$ref": "#/definitions/schema.SiteInterfaceSettingsResp" }, "login": { "$ref": "#/definitions/schema.SiteLoginResp" @@ -10872,6 +10943,9 @@ const docTemplate = `{ "theme": { "$ref": "#/definitions/schema.SiteThemeResp" }, + "users_settings": { + "$ref": "#/definitions/schema.SiteUsersSettingsResp" + }, "version": { "type": "string" } @@ -10905,24 +10979,13 @@ const docTemplate = `{ } } }, - "schema.SiteInterfaceResp": { + "schema.SiteInterfaceSettingsResp": { "type": "object", "required": [ - "default_avatar", "language", "time_zone" ], "properties": { - "default_avatar": { - "type": "string", - "enum": [ - "system", - "gravatar" - ] - }, - "gravatar_base_url": { - "type": "string" - }, "language": { "type": "string", "maxLength": 128 @@ -11271,6 +11334,42 @@ const docTemplate = `{ } } }, + "schema.SiteUsersSettingsReq": { + "type": "object", + "required": [ + "default_avatar" + ], + "properties": { + "default_avatar": { + "type": "string", + "enum": [ + "system", + "gravatar" + ] + }, + "gravatar_base_url": { + "type": "string" + } + } + }, + "schema.SiteUsersSettingsResp": { + "type": "object", + "required": [ + "default_avatar" + ], + "properties": { + "default_avatar": { + "type": "string", + "enum": [ + "system", + "gravatar" + ] + }, + "gravatar_base_url": { + "type": "string" + } + } + }, "schema.SiteWriteTag": { "type": "object", "required": [ diff --git a/docs/swagger.json b/docs/swagger.json index 6dbecc50a..879302fa8 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1159,7 +1159,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/definitions/schema.SiteInterfaceResp" + "$ref": "#/definitions/schema.SiteInterfaceSettingsResp" } } } @@ -1700,6 +1700,77 @@ } } }, + "/answer/admin/api/siteinfo/users-settings": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get site interface", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "get site interface", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SiteUsersSettingsResp" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "update site info users settings", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "update site info users settings", + "parameters": [ + { + "description": "general", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.SiteUsersSettingsReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, "/answer/admin/api/theme/options": { "get": { "security": [ @@ -10835,7 +10906,7 @@ "$ref": "#/definitions/schema.SiteGeneralResp" }, "interface": { - "$ref": "#/definitions/schema.SiteInterfaceResp" + "$ref": "#/definitions/schema.SiteInterfaceSettingsResp" }, "login": { "$ref": "#/definitions/schema.SiteLoginResp" @@ -10864,6 +10935,9 @@ "theme": { "$ref": "#/definitions/schema.SiteThemeResp" }, + "users_settings": { + "$ref": "#/definitions/schema.SiteUsersSettingsResp" + }, "version": { "type": "string" } @@ -10897,24 +10971,13 @@ } } }, - "schema.SiteInterfaceResp": { + "schema.SiteInterfaceSettingsResp": { "type": "object", "required": [ - "default_avatar", "language", "time_zone" ], "properties": { - "default_avatar": { - "type": "string", - "enum": [ - "system", - "gravatar" - ] - }, - "gravatar_base_url": { - "type": "string" - }, "language": { "type": "string", "maxLength": 128 @@ -11263,6 +11326,42 @@ } } }, + "schema.SiteUsersSettingsReq": { + "type": "object", + "required": [ + "default_avatar" + ], + "properties": { + "default_avatar": { + "type": "string", + "enum": [ + "system", + "gravatar" + ] + }, + "gravatar_base_url": { + "type": "string" + } + } + }, + "schema.SiteUsersSettingsResp": { + "type": "object", + "required": [ + "default_avatar" + ], + "properties": { + "default_avatar": { + "type": "string", + "enum": [ + "system", + "gravatar" + ] + }, + "gravatar_base_url": { + "type": "string" + } + } + }, "schema.SiteWriteTag": { "type": "object", "required": [ diff --git a/docs/swagger.yaml b/docs/swagger.yaml index f4107cd6b..ce16da75e 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2212,7 +2212,7 @@ definitions: general: $ref: '#/definitions/schema.SiteGeneralResp' interface: - $ref: '#/definitions/schema.SiteInterfaceResp' + $ref: '#/definitions/schema.SiteInterfaceSettingsResp' login: $ref: '#/definitions/schema.SiteLoginResp' revision: @@ -2231,6 +2231,8 @@ definitions: $ref: '#/definitions/schema.SiteUsersResp' theme: $ref: '#/definitions/schema.SiteThemeResp' + users_settings: + $ref: '#/definitions/schema.SiteUsersSettingsResp' version: type: string type: object @@ -2254,15 +2256,8 @@ definitions: - language - time_zone type: object - schema.SiteInterfaceResp: + schema.SiteInterfaceSettingsResp: properties: - default_avatar: - enum: - - system - - gravatar - type: string - gravatar_base_url: - type: string language: maxLength: 128 type: string @@ -2270,7 +2265,6 @@ definitions: maxLength: 128 type: string required: - - default_avatar - language - time_zone type: object @@ -2502,6 +2496,30 @@ definitions: required: - default_avatar type: object + schema.SiteUsersSettingsReq: + properties: + default_avatar: + enum: + - system + - gravatar + type: string + gravatar_base_url: + type: string + required: + - default_avatar + type: object + schema.SiteUsersSettingsResp: + properties: + default_avatar: + enum: + - system + - gravatar + type: string + gravatar_base_url: + type: string + required: + - default_avatar + type: object schema.SiteWriteTag: properties: display_name: @@ -3878,7 +3896,7 @@ paths: - $ref: '#/definitions/handler.RespBody' - properties: data: - $ref: '#/definitions/schema.SiteInterfaceResp' + $ref: '#/definitions/schema.SiteInterfaceSettingsResp' type: object security: - ApiKeyAuth: [] @@ -4193,6 +4211,47 @@ paths: summary: update site info config about users tags: - admin + /answer/admin/api/siteinfo/users-settings: + get: + description: get site interface + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + $ref: '#/definitions/schema.SiteUsersSettingsResp' + type: object + security: + - ApiKeyAuth: [] + summary: get site interface + tags: + - admin + put: + description: update site info users settings + parameters: + - description: general + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.SiteUsersSettingsReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: update site info users settings + tags: + - admin /answer/admin/api/theme/options: get: description: Get theme options diff --git a/internal/base/constant/site_type.go b/internal/base/constant/site_type.go index 6106b190c..9bb4e10af 100644 --- a/internal/base/constant/site_type.go +++ b/internal/base/constant/site_type.go @@ -20,9 +20,11 @@ package constant const ( - SiteTypeGeneral = "general" - SiteTypeInterface = "interface" - SiteTypeBranding = "branding" + SiteTypeGeneral = "general" + // Deprecated: split SiteTypeInterfaceSettings and SiteTypeUsersSettings for better clarity + SiteTypeInterface = "interface" + SiteTypeBranding = "branding" + // Deprecated: use SiteTypeAdvanced, SiteTypeQuestions, and SiteTypeTags instead SiteTypeWrite = "write" SiteTypeLegal = "legal" SiteTypeSeo = "seo" @@ -31,7 +33,11 @@ const ( SiteTypeTheme = "theme" SiteTypePrivileges = "privileges" SiteTypeUsers = "users" - SiteTypeAdvanced = "advanced" - SiteTypeQuestions = "questions" - SiteTypeTags = "tags" + + SiteTypeAdvanced = "advanced" + SiteTypeQuestions = "questions" + SiteTypeTags = "tags" + + SiteTypeUsersSettings = "users_settings" + SiteTypeInterfaceSettings = "interface_settings" ) diff --git a/internal/controller/siteinfo_controller.go b/internal/controller/siteinfo_controller.go index 8035275a6..503bfeb68 100644 --- a/internal/controller/siteinfo_controller.go +++ b/internal/controller/siteinfo_controller.go @@ -60,6 +60,11 @@ func (sc *SiteInfoController) GetSiteInfo(ctx *gin.Context) { log.Error(err) } + resp.UsersSettings, err = sc.siteInfoService.GetSiteUsersSettings(ctx) + if err != nil { + log.Error(err) + } + resp.Branding, err = sc.siteInfoService.GetSiteBranding(ctx) if err != nil { log.Error(err) diff --git a/internal/controller_admin/siteinfo_controller.go b/internal/controller_admin/siteinfo_controller.go index bbab97942..056b01ae2 100644 --- a/internal/controller_admin/siteinfo_controller.go +++ b/internal/controller_admin/siteinfo_controller.go @@ -62,13 +62,26 @@ func (sc *SiteInfoController) GetGeneral(ctx *gin.Context) { // @Security ApiKeyAuth // @Tags admin // @Produce json -// @Success 200 {object} handler.RespBody{data=schema.SiteInterfaceResp} +// @Success 200 {object} handler.RespBody{data=schema.SiteInterfaceSettingsResp} // @Router /answer/admin/api/siteinfo/interface [get] func (sc *SiteInfoController) GetInterface(ctx *gin.Context) { resp, err := sc.siteInfoService.GetSiteInterface(ctx) handler.HandleResponse(ctx, err, resp) } +// GetUsersSettings get site interface +// @Summary get site interface +// @Description get site interface +// @Security ApiKeyAuth +// @Tags admin +// @Produce json +// @Success 200 {object} handler.RespBody{data=schema.SiteUsersSettingsResp} +// @Router /answer/admin/api/siteinfo/users-settings [get] +func (sc *SiteInfoController) GetUsersSettings(ctx *gin.Context) { + resp, err := sc.siteInfoService.GetSiteUsersSettings(ctx) + handler.HandleResponse(ctx, err, resp) +} + // GetSiteBranding get site interface // @Summary get site interface // @Description get site interface @@ -287,6 +300,24 @@ func (sc *SiteInfoController) UpdateInterface(ctx *gin.Context) { handler.HandleResponse(ctx, err, nil) } +// UpdateUsersSettings update users settings +// @Summary update site info users settings +// @Description update site info users settings +// @Security ApiKeyAuth +// @Tags admin +// @Produce json +// @Param data body schema.SiteUsersSettingsReq true "general" +// @Success 200 {object} handler.RespBody{} +// @Router /answer/admin/api/siteinfo/users-settings [put] +func (sc *SiteInfoController) UpdateUsersSettings(ctx *gin.Context) { + req := schema.SiteUsersSettingsReq{} + if handler.BindAndCheck(ctx, &req) { + return + } + err := sc.siteInfoService.SaveSiteUsersSettings(ctx, req) + handler.HandleResponse(ctx, err, nil) +} + // UpdateBranding update site branding // @Summary update site info branding // @Description update site info branding diff --git a/internal/migrations/v30.go b/internal/migrations/v30.go index 5d5d5223f..4d0f11304 100644 --- a/internal/migrations/v30.go +++ b/internal/migrations/v30.go @@ -171,3 +171,99 @@ func splitWriteMenu(ctx context.Context, x *xorm.Engine) error { return nil } + +func splitInterfaceMenu(ctx context.Context, x *xorm.Engine) error { + var ( + siteInfo = &entity.SiteInfo{} + siteInfoInterface = &entity.SiteInfo{} + siteInfoUsers = &entity.SiteInfo{} + ) + type SiteInterface struct { + Language string `validate:"required,gt=1,lte=128" form:"language" json:"language"` + TimeZone string `validate:"required,gt=1,lte=128" form:"time_zone" json:"time_zone"` + DefaultAvatar string `validate:"required,oneof=system gravatar" json:"default_avatar"` + GravatarBaseURL string `validate:"omitempty" json:"gravatar_base_url"` + } + + exist, err := x.Context(ctx).Where(builder.Eq{"type": constant.SiteTypeInterface}).Get(siteInfo) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return err + } + if !exist { + return nil + } + oldSiteInterface := &SiteInterface{} + if err := json.Unmarshal([]byte(siteInfo.Content), oldSiteInterface); err != nil { + return err + } + siteUser := &schema.SiteUsersSettingsResp{ + DefaultAvatar: oldSiteInterface.DefaultAvatar, + GravatarBaseURL: oldSiteInterface.GravatarBaseURL, + } + siteInterface := &schema.SiteInterfaceResp{ + Language: oldSiteInterface.Language, + TimeZone: oldSiteInterface.TimeZone, + } + + // save settings + // save user settings + existsUsers, err := x.Context(ctx).Where(builder.Eq{"type": constant.SiteTypeUsersSettings}).Get(siteInfoUsers) + if err != nil { + return err + } + userContent, err := json.Marshal(siteUser) + if err != nil { + return err + } + if existsUsers { + _, err = x.Context(ctx).ID(siteInfoUsers.ID).Update(&entity.SiteInfo{ + Type: constant.SiteTypeUsersSettings, + Content: string(userContent), + Status: 1, + }) + if err != nil { + return err + } + } else { + _, err = x.Context(ctx).Insert(&entity.SiteInfo{ + Type: constant.SiteTypeUsersSettings, + Content: string(userContent), + Status: 1, + }) + if err != nil { + return err + } + } + + // save interface settings + existsInterface, err := x.Context(ctx).Where(builder.Eq{"type": constant.SiteTypeInterfaceSettings}).Get(siteInfoInterface) + if err != nil { + return err + } + interfaceContent, err := json.Marshal(siteInterface) + if err != nil { + return err + } + if existsInterface { + _, err = x.Context(ctx).ID(siteInfoInterface.ID).Update(&entity.SiteInfo{ + Type: constant.SiteTypeInterfaceSettings, + Content: string(interfaceContent), + Status: 1, + }) + if err != nil { + return err + } + } else { + _, err = x.Context(ctx).Insert(&entity.SiteInfo{ + Type: constant.SiteTypeInterfaceSettings, + Content: string(interfaceContent), + Status: 1, + }) + if err != nil { + return err + } + } + + return nil +} diff --git a/internal/router/answer_api_router.go b/internal/router/answer_api_router.go index d717bc9d3..c776b44f0 100644 --- a/internal/router/answer_api_router.go +++ b/internal/router/answer_api_router.go @@ -343,8 +343,12 @@ func (a *AnswerAPIRouter) RegisterAnswerAdminAPIRouter(r *gin.RouterGroup) { // siteinfo r.GET("/siteinfo/general", a.adminSiteInfoController.GetGeneral) r.PUT("/siteinfo/general", a.adminSiteInfoController.UpdateGeneral) + r.GET("/siteinfo/interface", a.adminSiteInfoController.GetInterface) r.PUT("/siteinfo/interface", a.adminSiteInfoController.UpdateInterface) + r.GET("/siteinfo/users-settings", a.adminSiteInfoController.GetUsersSettings) + r.PUT("/siteinfo/users-settings", a.adminSiteInfoController.UpdateUsersSettings) + r.GET("/siteinfo/branding", a.adminSiteInfoController.GetSiteBranding) r.PUT("/siteinfo/branding", a.adminSiteInfoController.UpdateBranding) diff --git a/internal/schema/siteinfo_schema.go b/internal/schema/siteinfo_schema.go index deacf08fb..e4669561b 100644 --- a/internal/schema/siteinfo_schema.go +++ b/internal/schema/siteinfo_schema.go @@ -65,6 +65,21 @@ type SiteInterfaceReq struct { GravatarBaseURL string `validate:"omitempty" json:"gravatar_base_url"` } +// SiteInterfaceSettingsReq site interface settings request +type SiteInterfaceSettingsReq struct { + Language string `validate:"required,gt=1,lte=128" json:"language"` + TimeZone string `validate:"required,gt=1,lte=128" json:"time_zone"` +} + +type SiteInterfaceSettingsResp SiteInterfaceSettingsReq + +type SiteUsersSettingsReq struct { + DefaultAvatar string `validate:"required,oneof=system gravatar" json:"default_avatar"` + GravatarBaseURL string `validate:"omitempty" json:"gravatar_base_url"` +} + +type SiteUsersSettingsResp SiteUsersSettingsReq + // SiteBrandingReq site branding request type SiteBrandingReq struct { Logo string `validate:"omitempty,gt=0,lte=512" form:"logo" json:"logo"` @@ -281,27 +296,30 @@ type SiteSeoResp SiteSeoReq // SiteInfoResp get site info response type SiteInfoResp struct { - General *SiteGeneralResp `json:"general"` - Interface *SiteInterfaceResp `json:"interface"` - Branding *SiteBrandingResp `json:"branding"` - Login *SiteLoginResp `json:"login"` - Theme *SiteThemeResp `json:"theme"` - CustomCssHtml *SiteCustomCssHTMLResp `json:"custom_css_html"` - SiteSeo *SiteSeoResp `json:"site_seo"` - SiteUsers *SiteUsersResp `json:"site_users"` - Advanced *SiteAdvancedResp `json:"site_advanced"` - Questions *SiteQuestionsResp `json:"site_questions"` - Tags *SiteTagsResp `json:"site_tags"` - Legal *SiteLegalSimpleResp `json:"site_legal"` - Version string `json:"version"` - Revision string `json:"revision"` -} + General *SiteGeneralResp `json:"general"` + Interface *SiteInterfaceSettingsResp `json:"interface"` + UsersSettings *SiteUsersSettingsResp `json:"users_settings"` + Branding *SiteBrandingResp `json:"branding"` + Login *SiteLoginResp `json:"login"` + Theme *SiteThemeResp `json:"theme"` + CustomCssHtml *SiteCustomCssHTMLResp `json:"custom_css_html"` + SiteSeo *SiteSeoResp `json:"site_seo"` + SiteUsers *SiteUsersResp `json:"site_users"` + Advanced *SiteAdvancedResp `json:"site_advanced"` + Questions *SiteQuestionsResp `json:"site_questions"` + Tags *SiteTagsResp `json:"site_tags"` + Legal *SiteLegalSimpleResp `json:"site_legal"` + Version string `json:"version"` + Revision string `json:"revision"` +} + +// todo: 检查模板使用 type TemplateSiteInfoResp struct { - General *SiteGeneralResp `json:"general"` - Interface *SiteInterfaceResp `json:"interface"` - Branding *SiteBrandingResp `json:"branding"` - SiteSeo *SiteSeoResp `json:"site_seo"` - CustomCssHtml *SiteCustomCssHTMLResp `json:"custom_css_html"` + General *SiteGeneralResp `json:"general"` + Interface *SiteInterfaceSettingsResp `json:"interface"` + Branding *SiteBrandingResp `json:"branding"` + SiteSeo *SiteSeoResp `json:"site_seo"` + CustomCssHtml *SiteCustomCssHTMLResp `json:"custom_css_html"` Title string Year string Canonical string diff --git a/internal/service/siteinfo/siteinfo_service.go b/internal/service/siteinfo/siteinfo_service.go index 480606633..dd4d81225 100644 --- a/internal/service/siteinfo/siteinfo_service.go +++ b/internal/service/siteinfo/siteinfo_service.go @@ -89,10 +89,15 @@ func (s *SiteInfoService) GetSiteGeneral(ctx context.Context) (resp *schema.Site } // GetSiteInterface get site info interface -func (s *SiteInfoService) GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceResp, err error) { +func (s *SiteInfoService) GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceSettingsResp, err error) { return s.siteInfoCommonService.GetSiteInterface(ctx) } +// GetSiteUsersSettings get site info users settings +func (s *SiteInfoService) GetSiteUsersSettings(ctx context.Context) (resp *schema.SiteUsersSettingsResp, err error) { + return s.siteInfoCommonService.GetSiteUsersSettings(ctx) +} + // GetSiteBranding get site info branding func (s *SiteInfoService) GetSiteBranding(ctx context.Context) (resp *schema.SiteBrandingResp, err error) { return s.siteInfoCommonService.GetSiteBranding(ctx) @@ -172,10 +177,20 @@ func (s *SiteInfoService) SaveSiteInterface(ctx context.Context, req schema.Site content, _ := json.Marshal(req) data := entity.SiteInfo{ - Type: constant.SiteTypeInterface, + Type: constant.SiteTypeInterfaceSettings, + Content: string(content), + } + return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeInterfaceSettings, &data) +} + +// SaveSiteUsersSettings save site users settings +func (s *SiteInfoService) SaveSiteUsersSettings(ctx context.Context, req schema.SiteUsersSettingsReq) (err error) { + content, _ := json.Marshal(req) + data := entity.SiteInfo{ + Type: constant.SiteTypeInterfaceSettings, Content: string(content), } - return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeInterface, &data) + return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeUsersSettings, &data) } // SaveSiteBranding save site branding information diff --git a/internal/service/siteinfo_common/siteinfo_service.go b/internal/service/siteinfo_common/siteinfo_service.go index 5c1c790ad..1689d32fd 100644 --- a/internal/service/siteinfo_common/siteinfo_service.go +++ b/internal/service/siteinfo_common/siteinfo_service.go @@ -45,7 +45,8 @@ type siteInfoCommonService struct { type SiteInfoCommonService interface { GetSiteGeneral(ctx context.Context) (resp *schema.SiteGeneralResp, err error) - GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceResp, err error) + GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceSettingsResp, err error) + GetSiteUsersSettings(ctx context.Context) (resp *schema.SiteUsersSettingsResp, err error) GetSiteBranding(ctx context.Context) (resp *schema.SiteBrandingResp, err error) GetSiteUsers(ctx context.Context) (resp *schema.SiteUsersResp, err error) FormatAvatar(ctx context.Context, originalAvatarData, email string, userStatus int) *schema.AvatarInfo @@ -81,9 +82,18 @@ func (s *siteInfoCommonService) GetSiteGeneral(ctx context.Context) (resp *schem } // GetSiteInterface get site info interface -func (s *siteInfoCommonService) GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceResp, err error) { - resp = &schema.SiteInterfaceResp{} - if err = s.GetSiteInfoByType(ctx, constant.SiteTypeInterface, resp); err != nil { +func (s *siteInfoCommonService) GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceSettingsResp, err error) { + resp = &schema.SiteInterfaceSettingsResp{} + if err = s.GetSiteInfoByType(ctx, constant.SiteTypeInterfaceSettings, resp); err != nil { + return nil, err + } + return resp, nil +} + +// GetSiteUsersSettings get site info interface +func (s *siteInfoCommonService) GetSiteUsersSettings(ctx context.Context) (resp *schema.SiteUsersSettingsResp, err error) { + resp = &schema.SiteUsersSettingsResp{} + if err = s.GetSiteInfoByType(ctx, constant.SiteTypeUsersSettings, resp); err != nil { return nil, err } return resp, nil @@ -126,7 +136,7 @@ func (s *siteInfoCommonService) FormatListAvatar(ctx context.Context, userList [ func (s *siteInfoCommonService) getAvatarDefaultConfig(ctx context.Context) (string, string) { gravatarBaseURL, defaultAvatar := constant.DefaultGravatarBaseURL, constant.DefaultAvatar - usersConfig, err := s.GetSiteInterface(ctx) + usersConfig, err := s.GetSiteUsersSettings(ctx) if err != nil { log.Error(err) } From 3264fdd1d90f8845dac1632b772bc86d2d61a554 Mon Sep 17 00:00:00 2001 From: kumfo Date: Wed, 21 Jan 2026 16:06:17 +0800 Subject: [PATCH 14/19] feat(siteinfo): refactor site legal and security settings to use new policies and security endpoints --- docs/docs.go | 250 ++++++++++++------ docs/swagger.json | 250 ++++++++++++------ docs/swagger.yaml | 171 +++++++----- internal/base/constant/site_type.go | 6 +- internal/base/middleware/auth.go | 4 +- internal/base/middleware/visit_img_auth.go | 4 +- internal/controller/siteinfo_controller.go | 4 +- internal/controller/template_controller.go | 2 +- .../controller_admin/siteinfo_controller.go | 61 +++-- internal/migrations/init.go | 85 ++++-- internal/migrations/v30.go | 217 +++++++++++---- internal/router/answer_api_router.go | 7 +- internal/schema/siteinfo_schema.go | 21 +- .../service/dashboard/dashboard_service.go | 17 +- internal/service/mock/siteinfo_repo_mock.go | 62 +++-- internal/service/siteinfo/siteinfo_service.go | 34 ++- .../siteinfo_common/siteinfo_service.go | 22 +- 17 files changed, 855 insertions(+), 362 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index b030c3d3d..a70cdab09 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1211,21 +1211,21 @@ const docTemplate = `{ } } }, - "/answer/admin/api/siteinfo/legal": { + "/answer/admin/api/siteinfo/login": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "Set the legal information for the site", + "description": "get site info login config", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "Set the legal information for the site", + "summary": "get site info login config", "responses": { "200": { "description": "OK", @@ -1238,7 +1238,7 @@ const docTemplate = `{ "type": "object", "properties": { "data": { - "$ref": "#/definitions/schema.SiteLegalResp" + "$ref": "#/definitions/schema.SiteLoginResp" } } } @@ -1253,22 +1253,22 @@ const docTemplate = `{ "ApiKeyAuth": [] } ], - "description": "update site legal info", + "description": "update site login", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "update site legal info", + "summary": "update site login", "parameters": [ { - "description": "write info", + "description": "login info", "name": "data", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/schema.SiteLegalReq" + "$ref": "#/definitions/schema.SiteLoginReq" } } ], @@ -1282,21 +1282,21 @@ const docTemplate = `{ } } }, - "/answer/admin/api/siteinfo/login": { + "/answer/admin/api/siteinfo/polices": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "get site info login config", + "description": "Get the policies information for the site", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "get site info login config", + "summary": "Get the policies information for the site", "responses": { "200": { "description": "OK", @@ -1309,7 +1309,7 @@ const docTemplate = `{ "type": "object", "properties": { "data": { - "$ref": "#/definitions/schema.SiteLoginResp" + "$ref": "#/definitions/schema.SitePoliciesResp" } } } @@ -1324,22 +1324,22 @@ const docTemplate = `{ "ApiKeyAuth": [] } ], - "description": "update site login", + "description": "update site policies configuration", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "update site login", + "summary": "update site policies configuration", "parameters": [ { - "description": "login info", + "description": "write info", "name": "data", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/schema.SiteLoginReq" + "$ref": "#/definitions/schema.SitePoliciesReq" } } ], @@ -1424,6 +1424,77 @@ const docTemplate = `{ } } }, + "/answer/admin/api/siteinfo/security": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get the security information for the site", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get the security information for the site", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SiteSecurityResp" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "update site security configuration", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "update site security configuration", + "parameters": [ + { + "description": "write info", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.SiteSecurityReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, "/answer/admin/api/siteinfo/seo": { "get": { "security": [ @@ -10843,9 +10914,6 @@ const docTemplate = `{ "site_url" ], "properties": { - "check_update": { - "type": "boolean" - }, "contact_email": { "type": "string", "maxLength": 512 @@ -10876,9 +10944,6 @@ const docTemplate = `{ "site_url" ], "properties": { - "check_update": { - "type": "boolean" - }, "contact_email": { "type": "string", "maxLength": 512 @@ -10917,7 +10982,12 @@ const docTemplate = `{ "$ref": "#/definitions/schema.SiteInterfaceSettingsResp" }, "login": { - "$ref": "#/definitions/schema.SiteLoginResp" + "description": "todo", + "allOf": [ + { + "$ref": "#/definitions/schema.SiteLoginResp" + } + ] }, "revision": { "type": "string" @@ -10996,60 +11066,6 @@ const docTemplate = `{ } } }, - "schema.SiteLegalReq": { - "type": "object", - "required": [ - "external_content_display" - ], - "properties": { - "external_content_display": { - "type": "string", - "enum": [ - "always_display", - "ask_before_display" - ] - }, - "privacy_policy_original_text": { - "type": "string" - }, - "privacy_policy_parsed_text": { - "type": "string" - }, - "terms_of_service_original_text": { - "type": "string" - }, - "terms_of_service_parsed_text": { - "type": "string" - } - } - }, - "schema.SiteLegalResp": { - "type": "object", - "required": [ - "external_content_display" - ], - "properties": { - "external_content_display": { - "type": "string", - "enum": [ - "always_display", - "ask_before_display" - ] - }, - "privacy_policy_original_text": { - "type": "string" - }, - "privacy_policy_parsed_text": { - "type": "string" - }, - "terms_of_service_original_text": { - "type": "string" - }, - "terms_of_service_parsed_text": { - "type": "string" - } - } - }, "schema.SiteLegalSimpleResp": { "type": "object", "required": [ @@ -11082,9 +11098,6 @@ const docTemplate = `{ }, "allow_password_login": { "type": "boolean" - }, - "login_required": { - "type": "boolean" } } }, @@ -11105,9 +11118,40 @@ const docTemplate = `{ }, "allow_password_login": { "type": "boolean" + } + } + }, + "schema.SitePoliciesReq": { + "type": "object", + "properties": { + "privacy_policy_original_text": { + "type": "string" }, - "login_required": { - "type": "boolean" + "privacy_policy_parsed_text": { + "type": "string" + }, + "terms_of_service_original_text": { + "type": "string" + }, + "terms_of_service_parsed_text": { + "type": "string" + } + } + }, + "schema.SitePoliciesResp": { + "type": "object", + "properties": { + "privacy_policy_original_text": { + "type": "string" + }, + "privacy_policy_parsed_text": { + "type": "string" + }, + "terms_of_service_original_text": { + "type": "string" + }, + "terms_of_service_parsed_text": { + "type": "string" } } }, @@ -11137,6 +11181,48 @@ const docTemplate = `{ } } }, + "schema.SiteSecurityReq": { + "type": "object", + "required": [ + "external_content_display" + ], + "properties": { + "check_update": { + "type": "boolean" + }, + "external_content_display": { + "type": "string", + "enum": [ + "always_display", + "ask_before_display" + ] + }, + "login_required": { + "type": "boolean" + } + } + }, + "schema.SiteSecurityResp": { + "type": "object", + "required": [ + "external_content_display" + ], + "properties": { + "check_update": { + "type": "boolean" + }, + "external_content_display": { + "type": "string", + "enum": [ + "always_display", + "ask_before_display" + ] + }, + "login_required": { + "type": "boolean" + } + } + }, "schema.SiteSeoReq": { "type": "object", "required": [ diff --git a/docs/swagger.json b/docs/swagger.json index 879302fa8..05e3302c6 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1203,21 +1203,21 @@ } } }, - "/answer/admin/api/siteinfo/legal": { + "/answer/admin/api/siteinfo/login": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "Set the legal information for the site", + "description": "get site info login config", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "Set the legal information for the site", + "summary": "get site info login config", "responses": { "200": { "description": "OK", @@ -1230,7 +1230,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/definitions/schema.SiteLegalResp" + "$ref": "#/definitions/schema.SiteLoginResp" } } } @@ -1245,22 +1245,22 @@ "ApiKeyAuth": [] } ], - "description": "update site legal info", + "description": "update site login", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "update site legal info", + "summary": "update site login", "parameters": [ { - "description": "write info", + "description": "login info", "name": "data", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/schema.SiteLegalReq" + "$ref": "#/definitions/schema.SiteLoginReq" } } ], @@ -1274,21 +1274,21 @@ } } }, - "/answer/admin/api/siteinfo/login": { + "/answer/admin/api/siteinfo/polices": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "get site info login config", + "description": "Get the policies information for the site", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "get site info login config", + "summary": "Get the policies information for the site", "responses": { "200": { "description": "OK", @@ -1301,7 +1301,7 @@ "type": "object", "properties": { "data": { - "$ref": "#/definitions/schema.SiteLoginResp" + "$ref": "#/definitions/schema.SitePoliciesResp" } } } @@ -1316,22 +1316,22 @@ "ApiKeyAuth": [] } ], - "description": "update site login", + "description": "update site policies configuration", "produces": [ "application/json" ], "tags": [ "admin" ], - "summary": "update site login", + "summary": "update site policies configuration", "parameters": [ { - "description": "login info", + "description": "write info", "name": "data", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/schema.SiteLoginReq" + "$ref": "#/definitions/schema.SitePoliciesReq" } } ], @@ -1416,6 +1416,77 @@ } } }, + "/answer/admin/api/siteinfo/security": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get the security information for the site", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get the security information for the site", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/handler.RespBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.SiteSecurityResp" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "update site security configuration", + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "update site security configuration", + "parameters": [ + { + "description": "write info", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/schema.SiteSecurityReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/handler.RespBody" + } + } + } + } + }, "/answer/admin/api/siteinfo/seo": { "get": { "security": [ @@ -10835,9 +10906,6 @@ "site_url" ], "properties": { - "check_update": { - "type": "boolean" - }, "contact_email": { "type": "string", "maxLength": 512 @@ -10868,9 +10936,6 @@ "site_url" ], "properties": { - "check_update": { - "type": "boolean" - }, "contact_email": { "type": "string", "maxLength": 512 @@ -10909,7 +10974,12 @@ "$ref": "#/definitions/schema.SiteInterfaceSettingsResp" }, "login": { - "$ref": "#/definitions/schema.SiteLoginResp" + "description": "todo", + "allOf": [ + { + "$ref": "#/definitions/schema.SiteLoginResp" + } + ] }, "revision": { "type": "string" @@ -10988,60 +11058,6 @@ } } }, - "schema.SiteLegalReq": { - "type": "object", - "required": [ - "external_content_display" - ], - "properties": { - "external_content_display": { - "type": "string", - "enum": [ - "always_display", - "ask_before_display" - ] - }, - "privacy_policy_original_text": { - "type": "string" - }, - "privacy_policy_parsed_text": { - "type": "string" - }, - "terms_of_service_original_text": { - "type": "string" - }, - "terms_of_service_parsed_text": { - "type": "string" - } - } - }, - "schema.SiteLegalResp": { - "type": "object", - "required": [ - "external_content_display" - ], - "properties": { - "external_content_display": { - "type": "string", - "enum": [ - "always_display", - "ask_before_display" - ] - }, - "privacy_policy_original_text": { - "type": "string" - }, - "privacy_policy_parsed_text": { - "type": "string" - }, - "terms_of_service_original_text": { - "type": "string" - }, - "terms_of_service_parsed_text": { - "type": "string" - } - } - }, "schema.SiteLegalSimpleResp": { "type": "object", "required": [ @@ -11074,9 +11090,6 @@ }, "allow_password_login": { "type": "boolean" - }, - "login_required": { - "type": "boolean" } } }, @@ -11097,9 +11110,40 @@ }, "allow_password_login": { "type": "boolean" + } + } + }, + "schema.SitePoliciesReq": { + "type": "object", + "properties": { + "privacy_policy_original_text": { + "type": "string" }, - "login_required": { - "type": "boolean" + "privacy_policy_parsed_text": { + "type": "string" + }, + "terms_of_service_original_text": { + "type": "string" + }, + "terms_of_service_parsed_text": { + "type": "string" + } + } + }, + "schema.SitePoliciesResp": { + "type": "object", + "properties": { + "privacy_policy_original_text": { + "type": "string" + }, + "privacy_policy_parsed_text": { + "type": "string" + }, + "terms_of_service_original_text": { + "type": "string" + }, + "terms_of_service_parsed_text": { + "type": "string" } } }, @@ -11129,6 +11173,48 @@ } } }, + "schema.SiteSecurityReq": { + "type": "object", + "required": [ + "external_content_display" + ], + "properties": { + "check_update": { + "type": "boolean" + }, + "external_content_display": { + "type": "string", + "enum": [ + "always_display", + "ask_before_display" + ] + }, + "login_required": { + "type": "boolean" + } + } + }, + "schema.SiteSecurityResp": { + "type": "object", + "required": [ + "external_content_display" + ], + "properties": { + "check_update": { + "type": "boolean" + }, + "external_content_display": { + "type": "string", + "enum": [ + "always_display", + "ask_before_display" + ] + }, + "login_required": { + "type": "boolean" + } + } + }, "schema.SiteSeoReq": { "type": "object", "required": [ diff --git a/docs/swagger.yaml b/docs/swagger.yaml index ce16da75e..85a05baf0 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2157,8 +2157,6 @@ definitions: type: object schema.SiteGeneralReq: properties: - check_update: - type: boolean contact_email: maxLength: 512 type: string @@ -2181,8 +2179,6 @@ definitions: type: object schema.SiteGeneralResp: properties: - check_update: - type: boolean contact_email: maxLength: 512 type: string @@ -2214,7 +2210,9 @@ definitions: interface: $ref: '#/definitions/schema.SiteInterfaceSettingsResp' login: - $ref: '#/definitions/schema.SiteLoginResp' + allOf: + - $ref: '#/definitions/schema.SiteLoginResp' + description: todo revision: type: string site_advanced: @@ -2268,42 +2266,6 @@ definitions: - language - time_zone type: object - schema.SiteLegalReq: - properties: - external_content_display: - enum: - - always_display - - ask_before_display - type: string - privacy_policy_original_text: - type: string - privacy_policy_parsed_text: - type: string - terms_of_service_original_text: - type: string - terms_of_service_parsed_text: - type: string - required: - - external_content_display - type: object - schema.SiteLegalResp: - properties: - external_content_display: - enum: - - always_display - - ask_before_display - type: string - privacy_policy_original_text: - type: string - privacy_policy_parsed_text: - type: string - terms_of_service_original_text: - type: string - terms_of_service_parsed_text: - type: string - required: - - external_content_display - type: object schema.SiteLegalSimpleResp: properties: external_content_display: @@ -2326,8 +2288,6 @@ definitions: type: boolean allow_password_login: type: boolean - login_required: - type: boolean type: object schema.SiteLoginResp: properties: @@ -2341,8 +2301,28 @@ definitions: type: boolean allow_password_login: type: boolean - login_required: - type: boolean + type: object + schema.SitePoliciesReq: + properties: + privacy_policy_original_text: + type: string + privacy_policy_parsed_text: + type: string + terms_of_service_original_text: + type: string + terms_of_service_parsed_text: + type: string + type: object + schema.SitePoliciesResp: + properties: + privacy_policy_original_text: + type: string + privacy_policy_parsed_text: + type: string + terms_of_service_original_text: + type: string + terms_of_service_parsed_text: + type: string type: object schema.SiteQuestionsReq: properties: @@ -2362,6 +2342,34 @@ definitions: restrict_answer: type: boolean type: object + schema.SiteSecurityReq: + properties: + check_update: + type: boolean + external_content_display: + enum: + - always_display + - ask_before_display + type: string + login_required: + type: boolean + required: + - external_content_display + type: object + schema.SiteSecurityResp: + properties: + check_update: + type: boolean + external_content_display: + enum: + - always_display + - ask_before_display + type: string + login_required: + type: boolean + required: + - external_content_display + type: object schema.SiteSeoReq: properties: permalink: @@ -3924,9 +3932,9 @@ paths: summary: update site info interface tags: - admin - /answer/admin/api/siteinfo/legal: + /answer/admin/api/siteinfo/login: get: - description: Set the legal information for the site + description: get site info login config produces: - application/json responses: @@ -3937,22 +3945,22 @@ paths: - $ref: '#/definitions/handler.RespBody' - properties: data: - $ref: '#/definitions/schema.SiteLegalResp' + $ref: '#/definitions/schema.SiteLoginResp' type: object security: - ApiKeyAuth: [] - summary: Set the legal information for the site + summary: get site info login config tags: - admin put: - description: update site legal info + description: update site login parameters: - - description: write info + - description: login info in: body name: data required: true schema: - $ref: '#/definitions/schema.SiteLegalReq' + $ref: '#/definitions/schema.SiteLoginReq' produces: - application/json responses: @@ -3962,12 +3970,12 @@ paths: $ref: '#/definitions/handler.RespBody' security: - ApiKeyAuth: [] - summary: update site legal info + summary: update site login tags: - admin - /answer/admin/api/siteinfo/login: + /answer/admin/api/siteinfo/polices: get: - description: get site info login config + description: Get the policies information for the site produces: - application/json responses: @@ -3978,22 +3986,22 @@ paths: - $ref: '#/definitions/handler.RespBody' - properties: data: - $ref: '#/definitions/schema.SiteLoginResp' + $ref: '#/definitions/schema.SitePoliciesResp' type: object security: - ApiKeyAuth: [] - summary: get site info login config + summary: Get the policies information for the site tags: - admin put: - description: update site login + description: update site policies configuration parameters: - - description: login info + - description: write info in: body name: data required: true schema: - $ref: '#/definitions/schema.SiteLoginReq' + $ref: '#/definitions/schema.SitePoliciesReq' produces: - application/json responses: @@ -4003,7 +4011,7 @@ paths: $ref: '#/definitions/handler.RespBody' security: - ApiKeyAuth: [] - summary: update site login + summary: update site policies configuration tags: - admin /answer/admin/api/siteinfo/question: @@ -4047,6 +4055,47 @@ paths: summary: update site question settings tags: - admin + /answer/admin/api/siteinfo/security: + get: + description: Get the security information for the site + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/handler.RespBody' + - properties: + data: + $ref: '#/definitions/schema.SiteSecurityResp' + type: object + security: + - ApiKeyAuth: [] + summary: Get the security information for the site + tags: + - admin + put: + description: update site security configuration + parameters: + - description: write info + in: body + name: data + required: true + schema: + $ref: '#/definitions/schema.SiteSecurityReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/handler.RespBody' + security: + - ApiKeyAuth: [] + summary: update site security configuration + tags: + - admin /answer/admin/api/siteinfo/seo: get: description: get site seo information diff --git a/internal/base/constant/site_type.go b/internal/base/constant/site_type.go index 9bb4e10af..44cd0abd7 100644 --- a/internal/base/constant/site_type.go +++ b/internal/base/constant/site_type.go @@ -25,7 +25,8 @@ const ( SiteTypeInterface = "interface" SiteTypeBranding = "branding" // Deprecated: use SiteTypeAdvanced, SiteTypeQuestions, and SiteTypeTags instead - SiteTypeWrite = "write" + SiteTypeWrite = "write" + // Deprecated: use SiteTypePolicies and SiteTypeSecurity instead SiteTypeLegal = "legal" SiteTypeSeo = "seo" SiteTypeLogin = "login" @@ -40,4 +41,7 @@ const ( SiteTypeUsersSettings = "users_settings" SiteTypeInterfaceSettings = "interface_settings" + + SiteTypePolicies = "policies" + SiteTypeSecurity = "security" ) diff --git a/internal/base/middleware/auth.go b/internal/base/middleware/auth.go index f21837b60..57bbaae21 100644 --- a/internal/base/middleware/auth.go +++ b/internal/base/middleware/auth.go @@ -80,7 +80,7 @@ func (am *AuthUserMiddleware) Auth() gin.HandlerFunc { func (am *AuthUserMiddleware) EjectUserBySiteInfo() gin.HandlerFunc { return func(ctx *gin.Context) { mustLogin := false - siteInfo, _ := am.siteInfoCommonService.GetSiteLogin(ctx) + siteInfo, _ := am.siteInfoCommonService.GetSiteSecurity(ctx) if siteInfo != nil { mustLogin = siteInfo.LoginRequired } @@ -197,7 +197,7 @@ func (am *AuthUserMiddleware) AdminAuth() gin.HandlerFunc { func (am *AuthUserMiddleware) CheckPrivateMode() gin.HandlerFunc { return func(ctx *gin.Context) { - resp, err := am.siteInfoCommonService.GetSiteLogin(ctx) + resp, err := am.siteInfoCommonService.GetSiteSecurity(ctx) if err != nil { ShowIndexPage(ctx) ctx.Abort() diff --git a/internal/base/middleware/visit_img_auth.go b/internal/base/middleware/visit_img_auth.go index 33b62172f..bfd157a92 100644 --- a/internal/base/middleware/visit_img_auth.go +++ b/internal/base/middleware/visit_img_auth.go @@ -41,11 +41,11 @@ func (am *AuthUserMiddleware) VisitAuth() gin.HandlerFunc { return } - siteLogin, err := am.siteInfoCommonService.GetSiteLogin(ctx) + siteSecurity, err := am.siteInfoCommonService.GetSiteSecurity(ctx) if err != nil { return } - if !siteLogin.LoginRequired { + if !siteSecurity.LoginRequired { ctx.Next() return } diff --git a/internal/controller/siteinfo_controller.go b/internal/controller/siteinfo_controller.go index 503bfeb68..64aa02ce7 100644 --- a/internal/controller/siteinfo_controller.go +++ b/internal/controller/siteinfo_controller.go @@ -104,7 +104,7 @@ func (sc *SiteInfoController) GetSiteInfo(ctx *gin.Context) { if err != nil { log.Error(err) } - if legal, err := sc.siteInfoService.GetSiteLegal(ctx); err == nil { + if legal, err := sc.siteInfoService.GetSiteSecurity(ctx); err == nil { resp.Legal = &schema.SiteLegalSimpleResp{ExternalContentDisplay: legal.ExternalContentDisplay} } @@ -124,7 +124,7 @@ func (sc *SiteInfoController) GetSiteLegalInfo(ctx *gin.Context) { if handler.BindAndCheck(ctx, req) { return } - siteLegal, err := sc.siteInfoService.GetSiteLegal(ctx) + siteLegal, err := sc.siteInfoService.GetSitePolicies(ctx) if err != nil { handler.HandleResponse(ctx, err, nil) return diff --git a/internal/controller/template_controller.go b/internal/controller/template_controller.go index e6b94f4f2..31cc5152a 100644 --- a/internal/controller/template_controller.go +++ b/internal/controller/template_controller.go @@ -656,7 +656,7 @@ func (tc *TemplateController) SitemapPage(ctx *gin.Context) { } func (tc *TemplateController) checkPrivateMode(ctx *gin.Context) bool { - resp, err := tc.siteInfoService.GetSiteLogin(ctx) + resp, err := tc.siteInfoService.GetSiteSecurity(ctx) if err != nil { log.Error(err) return false diff --git a/internal/controller_admin/siteinfo_controller.go b/internal/controller_admin/siteinfo_controller.go index 056b01ae2..339d2caa2 100644 --- a/internal/controller_admin/siteinfo_controller.go +++ b/internal/controller_admin/siteinfo_controller.go @@ -134,16 +134,29 @@ func (sc *SiteInfoController) GetSiteAdvanced(ctx *gin.Context) { handler.HandleResponse(ctx, err, resp) } -// GetSiteLegal Set the legal information for the site -// @Summary Set the legal information for the site -// @Description Set the legal information for the site +// GetSitePolicies Get the policies information for the site +// @Summary Get the policies information for the site +// @Description Get the policies information for the site // @Security ApiKeyAuth // @Tags admin // @Produce json -// @Success 200 {object} handler.RespBody{data=schema.SiteLegalResp} -// @Router /answer/admin/api/siteinfo/legal [get] -func (sc *SiteInfoController) GetSiteLegal(ctx *gin.Context) { - resp, err := sc.siteInfoService.GetSiteLegal(ctx) +// @Success 200 {object} handler.RespBody{data=schema.SitePoliciesResp} +// @Router /answer/admin/api/siteinfo/polices [get] +func (sc *SiteInfoController) GetSitePolicies(ctx *gin.Context) { + resp, err := sc.siteInfoService.GetSitePolicies(ctx) + handler.HandleResponse(ctx, err, resp) +} + +// GetSiteSecurity Get the security information for the site +// @Summary Get the security information for the site +// @Description Get the security information for the site +// @Security ApiKeyAuth +// @Tags admin +// @Produce json +// @Success 200 {object} handler.RespBody{data=schema.SiteSecurityResp} +// @Router /answer/admin/api/siteinfo/security [get] +func (sc *SiteInfoController) GetSiteSecurity(ctx *gin.Context) { + resp, err := sc.siteInfoService.GetSiteSecurity(ctx) handler.HandleResponse(ctx, err, resp) } @@ -403,21 +416,39 @@ func (sc *SiteInfoController) UpdateSiteAdvanced(ctx *gin.Context) { handler.HandleResponse(ctx, err, resp) } -// UpdateSiteLegal update site legal info -// @Summary update site legal info -// @Description update site legal info +// UpdateSitePolices update site policies configuration +// @Summary update site policies configuration +// @Description update site policies configuration +// @Security ApiKeyAuth +// @Tags admin +// @Produce json +// @Param data body schema.SitePoliciesReq true "write info" +// @Success 200 {object} handler.RespBody{} +// @Router /answer/admin/api/siteinfo/polices [put] +func (sc *SiteInfoController) UpdateSitePolices(ctx *gin.Context) { + req := &schema.SitePoliciesReq{} + if handler.BindAndCheck(ctx, req) { + return + } + err := sc.siteInfoService.SaveSitePolicies(ctx, req) + handler.HandleResponse(ctx, err, nil) +} + +// UpdateSiteSecurity update site security configuration +// @Summary update site security configuration +// @Description update site security configuration // @Security ApiKeyAuth // @Tags admin // @Produce json -// @Param data body schema.SiteLegalReq true "write info" +// @Param data body schema.SiteSecurityReq true "write info" // @Success 200 {object} handler.RespBody{} -// @Router /answer/admin/api/siteinfo/legal [put] -func (sc *SiteInfoController) UpdateSiteLegal(ctx *gin.Context) { - req := &schema.SiteLegalReq{} +// @Router /answer/admin/api/siteinfo/security [put] +func (sc *SiteInfoController) UpdateSiteSecurity(ctx *gin.Context) { + req := &schema.SiteSecurityReq{} if handler.BindAndCheck(ctx, req) { return } - err := sc.siteInfoService.SaveSiteLegal(ctx, req) + err := sc.siteInfoService.SaveSiteSecurity(ctx, req) handler.HandleResponse(ctx, err, nil) } diff --git a/internal/migrations/init.go b/internal/migrations/init.go index 5ffb37711..ae2eeb9a3 100644 --- a/internal/migrations/init.go +++ b/internal/migrations/init.go @@ -74,14 +74,17 @@ func (m *Mentor) InitDB() error { m.do("init role power rel", m.initRolePowerRel) m.do("init admin user role rel", m.initAdminUserRoleRel) m.do("init site info interface", m.initSiteInfoInterface) + m.do("init site info users settings", m.initSiteInfoUsersSettings) m.do("init site info general config", m.initSiteInfoGeneralData) m.do("init site info login config", m.initSiteInfoLoginConfig) m.do("init site info theme config", m.initSiteInfoThemeConfig) m.do("init site info seo config", m.initSiteInfoSEOConfig) m.do("init site info user config", m.initSiteInfoUsersConfig) m.do("init site info privilege rank", m.initSiteInfoPrivilegeRank) - m.do("init site info write", m.initSiteInfoWrite) - m.do("init site info legal", m.initSiteInfoLegalConfig) + m.do("init site info write", m.initSiteInfoAdvanced) + m.do("init site info write", m.initSiteInfoQuestions) + m.do("init site info write", m.initSiteInfoTags) + m.do("init site info security", m.initSiteInfoSecurityConfig) m.do("init default content", m.initDefaultContent) m.do("init default badges", m.initDefaultBadges) return m.err @@ -181,19 +184,30 @@ func (m *Mentor) initSiteInfoInterface() { } interfaceData := map[string]string{ - "language": m.userData.Language, - "time_zone": localTimezone, - "default_avatar": "gravatar", - "gravatar_base_url": "https://www.gravatar.com/avatar/", + "language": m.userData.Language, + "time_zone": localTimezone, } interfaceDataBytes, _ := json.Marshal(interfaceData) _, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{ - Type: "interface", + Type: "interface_settings", Content: string(interfaceDataBytes), Status: 1, }) } +func (m *Mentor) initSiteInfoUsersSettings() { + usersSettings := map[string]any{ + "default_avatar": "gravatar", + "gravatar_base_url": "https://www.gravatar.com/avatar/", + } + usersSettingsDataBytes, _ := json.Marshal(usersSettings) + _, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{ + Type: "users_settings", + Content: string(usersSettingsDataBytes), + Status: 1, + }) +} + func (m *Mentor) initSiteInfoGeneralData() { generalData := map[string]string{ "name": m.userData.SiteName, @@ -213,7 +227,6 @@ func (m *Mentor) initSiteInfoLoginConfig() { "allow_new_registrations": true, "allow_email_registrations": true, "allow_password_login": true, - "login_required": m.userData.LoginRequired, } loginConfigDataBytes, _ := json.Marshal(loginConfig) _, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{ @@ -223,14 +236,16 @@ func (m *Mentor) initSiteInfoLoginConfig() { }) } -func (m *Mentor) initSiteInfoLegalConfig() { - legalConfig := map[string]any{ +func (m *Mentor) initSiteInfoSecurityConfig() { + securityConfig := map[string]any{ + "login_required": m.userData.LoginRequired, "external_content_display": m.userData.ExternalContentDisplay, + "check_update": true, } - legalConfigDataBytes, _ := json.Marshal(legalConfig) + securityConfigDataBytes, _ := json.Marshal(securityConfig) _, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{ - Type: "legal", - Content: string(legalConfigDataBytes), + Type: "security", + Content: string(securityConfigDataBytes), Status: 1, }) } @@ -288,24 +303,46 @@ func (m *Mentor) initSiteInfoPrivilegeRank() { }) } -func (m *Mentor) initSiteInfoWrite() { - writeData := map[string]any{ - "min_content": 6, - "restrict_answer": true, - "min_tags": 1, - "required_tag": false, - "recommend_tags": []string{}, - "reserved_tags": []string{}, +func (m *Mentor) initSiteInfoAdvanced() { + advancedData := map[string]any{ "max_image_size": 4, "max_attachment_size": 8, "max_image_megapixel": 40, "authorized_image_extensions": []string{"jpg", "jpeg", "png", "gif", "webp"}, "authorized_attachment_extensions": []string{}, } - writeDataBytes, _ := json.Marshal(writeData) + advancedDataBytes, _ := json.Marshal(advancedData) + _, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{ + Type: "advanced", + Content: string(advancedDataBytes), + Status: 1, + }) +} + +func (m *Mentor) initSiteInfoQuestions() { + questionsData := map[string]any{ + "min_tags": 1, + "min_content": 6, + "restrict_answer": true, + } + questionsDataBytes, _ := json.Marshal(questionsData) + _, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{ + Type: "questions", + Content: string(questionsDataBytes), + Status: 1, + }) +} + +func (m *Mentor) initSiteInfoTags() { + tagsData := map[string]any{ + "required_tag": false, + "recommend_tags": []string{}, + "reserved_tags": []string{}, + } + tagsDataBytes, _ := json.Marshal(tagsData) _, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{ - Type: "write", - Content: string(writeDataBytes), + Type: "tags", + Content: string(tagsDataBytes), Status: 1, }) } diff --git a/internal/migrations/v30.go b/internal/migrations/v30.go index 4d0f11304..c73f501d5 100644 --- a/internal/migrations/v30.go +++ b/internal/migrations/v30.go @@ -37,6 +37,16 @@ func updateAdminMenuSettings(ctx context.Context, x *xorm.Engine) (err error) { if err != nil { return } + + err = splitInterfaceMenu(ctx, x) + if err != nil { + return + } + + err = splitLegalMenu(ctx, x) + if err != nil { + return + } return } @@ -91,16 +101,7 @@ func splitWriteMenu(ctx context.Context, x *xorm.Engine) error { if err != nil { return err } - if existsAdvanced { - _, err = x.Context(ctx).ID(siteInfoAdvanced.ID).Update(&entity.SiteInfo{ - Type: constant.SiteTypeAdvanced, - Content: string(advancedContent), - Status: 1, - }) - if err != nil { - return err - } - } else { + if !existsAdvanced { _, err = x.Context(ctx).Insert(&entity.SiteInfo{ Type: constant.SiteTypeAdvanced, Content: string(advancedContent), @@ -120,16 +121,7 @@ func splitWriteMenu(ctx context.Context, x *xorm.Engine) error { if err != nil { return err } - if existsQuestions { - _, err = x.Context(ctx).ID(siteInfoQuestions.ID).Update(&entity.SiteInfo{ - Type: constant.SiteTypeQuestions, - Content: string(questionsContent), - Status: 1, - }) - if err != nil { - return err - } - } else { + if !existsQuestions { _, err = x.Context(ctx).Insert(&entity.SiteInfo{ Type: constant.SiteTypeQuestions, Content: string(questionsContent), @@ -149,16 +141,7 @@ func splitWriteMenu(ctx context.Context, x *xorm.Engine) error { if err != nil { return err } - if existsTags { - _, err = x.Context(ctx).ID(siteInfoTags.ID).Update(&entity.SiteInfo{ - Type: constant.SiteTypeTags, - Content: string(tagsContent), - Status: 1, - }) - if err != nil { - return err - } - } else { + if !existsTags { _, err = x.Context(ctx).Insert(&entity.SiteInfo{ Type: constant.SiteTypeTags, Content: string(tagsContent), @@ -172,6 +155,7 @@ func splitWriteMenu(ctx context.Context, x *xorm.Engine) error { return nil } +// splitInterfaceMenu splits the site interface settings into interface and user settings func splitInterfaceMenu(ctx context.Context, x *xorm.Engine) error { var ( siteInfo = &entity.SiteInfo{} @@ -216,16 +200,7 @@ func splitInterfaceMenu(ctx context.Context, x *xorm.Engine) error { if err != nil { return err } - if existsUsers { - _, err = x.Context(ctx).ID(siteInfoUsers.ID).Update(&entity.SiteInfo{ - Type: constant.SiteTypeUsersSettings, - Content: string(userContent), - Status: 1, - }) - if err != nil { - return err - } - } else { + if !existsUsers { _, err = x.Context(ctx).Insert(&entity.SiteInfo{ Type: constant.SiteTypeUsersSettings, Content: string(userContent), @@ -245,8 +220,8 @@ func splitInterfaceMenu(ctx context.Context, x *xorm.Engine) error { if err != nil { return err } - if existsInterface { - _, err = x.Context(ctx).ID(siteInfoInterface.ID).Update(&entity.SiteInfo{ + if !existsInterface { + _, err = x.Context(ctx).Insert(&entity.SiteInfo{ Type: constant.SiteTypeInterfaceSettings, Content: string(interfaceContent), Status: 1, @@ -254,10 +229,150 @@ func splitInterfaceMenu(ctx context.Context, x *xorm.Engine) error { if err != nil { return err } - } else { + } + + return nil +} + +// splitLegalMenu splits the site legal settings into policies and security settings +func splitLegalMenu(ctx context.Context, x *xorm.Engine) error { + var ( + siteInfo = &entity.SiteInfo{} + siteInfoPolices = &entity.SiteInfo{} + siteInfoSecurity = &entity.SiteInfo{} + siteInfoLogin = &entity.SiteInfo{} + siteInfoGeneral = &entity.SiteInfo{} + ) + + type SiteLogin struct { + AllowNewRegistrations bool `json:"allow_new_registrations"` + AllowEmailRegistrations bool `json:"allow_email_registrations"` + AllowPasswordLogin bool `json:"allow_password_login"` + LoginRequired bool `json:"login_required"` + AllowEmailDomains []string `json:"allow_email_domains"` + } + + type SiteGeneral struct { + Name string `validate:"required,sanitizer,gt=1,lte=128" form:"name" json:"name"` + ShortDescription string `validate:"omitempty,sanitizer,gt=3,lte=255" form:"short_description" json:"short_description"` + Description string `validate:"omitempty,sanitizer,gt=3,lte=2000" form:"description" json:"description"` + SiteUrl string `validate:"required,sanitizer,gt=1,lte=512,url" form:"site_url" json:"site_url"` + ContactEmail string `validate:"required,sanitizer,gt=1,lte=512,email" form:"contact_email" json:"contact_email"` + CheckUpdate bool `validate:"omitempty,sanitizer" form:"check_update" json:"check_update"` + } + + // find old site legal settings + exist, err := x.Context(ctx).Where(builder.Eq{"type": constant.SiteTypeLegal}).Get(siteInfo) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + return err + } + if !exist { + return nil + } + oldSiteLegal := &schema.SiteLegalResp{} + if err := json.Unmarshal([]byte(siteInfo.Content), oldSiteLegal); err != nil { + return err + } + + // find old site login settings + existsLogin, err := x.Context(ctx).Where(builder.Eq{"type": constant.SiteTypeLogin}).Get(siteInfoLogin) + if err != nil { + return err + } + oldSiteLogin := &SiteLogin{} + if err := json.Unmarshal([]byte(siteInfoLogin.Content), oldSiteLogin); err != nil { + return err + } + + // find old site general settings + existGeneral, err := x.Context(ctx).Where(builder.Eq{"type": constant.SiteTypeGeneral}).Get(siteInfoGeneral) + if err != nil { + return err + } + oldSiteGeneral := &SiteGeneral{} + if err := json.Unmarshal([]byte(siteInfoLogin.Content), oldSiteGeneral); err != nil { + return err + } + + sitePolicies := &schema.SitePoliciesResp{ + TermsOfServiceOriginalText: oldSiteLegal.TermsOfServiceOriginalText, + TermsOfServiceParsedText: oldSiteLegal.TermsOfServiceParsedText, + PrivacyPolicyOriginalText: oldSiteLegal.PrivacyPolicyOriginalText, + PrivacyPolicyParsedText: oldSiteLegal.PrivacyPolicyParsedText, + } + siteLogin := &schema.SiteLoginResp{ + AllowNewRegistrations: oldSiteLogin.AllowNewRegistrations, + AllowEmailRegistrations: oldSiteLogin.AllowEmailRegistrations, + AllowPasswordLogin: oldSiteLogin.AllowPasswordLogin, + AllowEmailDomains: oldSiteLogin.AllowEmailDomains, + } + siteGeneral := &schema.SiteGeneralReq{ + Name: oldSiteGeneral.Name, + ShortDescription: oldSiteGeneral.ShortDescription, + Description: oldSiteGeneral.Description, + SiteUrl: oldSiteGeneral.SiteUrl, + ContactEmail: oldSiteGeneral.ContactEmail, + } + siteSecurity := &schema.SiteSecurityResp{ + LoginRequired: oldSiteLogin.LoginRequired, + ExternalContentDisplay: oldSiteLegal.ExternalContentDisplay, + CheckUpdate: oldSiteGeneral.CheckUpdate, + } + if !existsLogin { + siteSecurity.LoginRequired = false + } + if !existGeneral { + siteSecurity.CheckUpdate = true + } + + // save settings + // save policies settings + existsPolicies, err := x.Context(ctx).Where(builder.Eq{"type": constant.SiteTypePolicies}).Get(siteInfoPolices) + if err != nil { + return err + } + policiesContent, err := json.Marshal(sitePolicies) + if err != nil { + return err + } + if !existsPolicies { + _, err = x.Context(ctx).Insert(&entity.SiteInfo{ + Type: constant.SiteTypePolicies, + Content: string(policiesContent), + Status: 1, + }) + if err != nil { + return err + } + } + + // save security settings + existsSecurity, err := x.Context(ctx).Where(builder.Eq{"type": constant.SiteTypeSecurity}).Get(siteInfoSecurity) + if err != nil { + return err + } + securityContent, err := json.Marshal(siteSecurity) + if err != nil { + return err + } + if !existsSecurity { _, err = x.Context(ctx).Insert(&entity.SiteInfo{ - Type: constant.SiteTypeInterfaceSettings, - Content: string(interfaceContent), + Type: constant.SiteTypeSecurity, + Content: string(securityContent), + Status: 1, + }) + if err != nil { + return err + } + } + + // save login settings + if existsLogin { + loginContent, err := json.Marshal(siteLogin) + _, err = x.Context(ctx).ID(siteInfoLogin.ID).Update(&entity.SiteInfo{ + Type: constant.SiteTypeLogin, + Content: string(loginContent), Status: 1, }) if err != nil { @@ -265,5 +380,17 @@ func splitInterfaceMenu(ctx context.Context, x *xorm.Engine) error { } } + // save general settings + if existGeneral { + generalContent, err := json.Marshal(siteGeneral) + _, err = x.Context(ctx).ID(siteInfoGeneral.ID).Update(&entity.SiteInfo{ + Type: constant.SiteTypeGeneral, + Content: string(generalContent), + Status: 1, + }) + if err != nil { + return err + } + } return nil } diff --git a/internal/router/answer_api_router.go b/internal/router/answer_api_router.go index c776b44f0..1fe8bdb14 100644 --- a/internal/router/answer_api_router.go +++ b/internal/router/answer_api_router.go @@ -359,8 +359,11 @@ func (a *AnswerAPIRouter) RegisterAnswerAdminAPIRouter(r *gin.RouterGroup) { r.GET("/siteinfo/advanced", a.adminSiteInfoController.GetSiteAdvanced) r.PUT("/siteinfo/advanced", a.adminSiteInfoController.UpdateSiteAdvanced) - r.GET("/siteinfo/legal", a.adminSiteInfoController.GetSiteLegal) - r.PUT("/siteinfo/legal", a.adminSiteInfoController.UpdateSiteLegal) + r.GET("/siteinfo/polices", a.adminSiteInfoController.GetSitePolicies) + r.PUT("/siteinfo/polices", a.adminSiteInfoController.UpdateSitePolices) + r.GET("/siteinfo/security", a.adminSiteInfoController.GetSiteSecurity) + r.PUT("/siteinfo/security", a.adminSiteInfoController.UpdateSiteSecurity) + r.GET("/siteinfo/seo", a.adminSiteInfoController.GetSeo) r.PUT("/siteinfo/seo", a.adminSiteInfoController.UpdateSeo) r.GET("/siteinfo/login", a.adminSiteInfoController.GetSiteLogin) diff --git a/internal/schema/siteinfo_schema.go b/internal/schema/siteinfo_schema.go index e4669561b..07747d954 100644 --- a/internal/schema/siteinfo_schema.go +++ b/internal/schema/siteinfo_schema.go @@ -42,7 +42,6 @@ type SiteGeneralReq struct { Description string `validate:"omitempty,sanitizer,gt=3,lte=2000" form:"description" json:"description"` SiteUrl string `validate:"required,sanitizer,gt=1,lte=512,url" form:"site_url" json:"site_url"` ContactEmail string `validate:"required,sanitizer,gt=1,lte=512,email" form:"contact_email" json:"contact_email"` - CheckUpdate bool `validate:"omitempty,sanitizer" form:"check_update" json:"check_update"` } func (r *SiteGeneralReq) FormatSiteUrl() { @@ -158,6 +157,7 @@ type SiteWriteTag struct { } // SiteLegalReq site branding request +// Deprecated: use SitePoliciesReq and SiteSecurityReq instead type SiteLegalReq struct { TermsOfServiceOriginalText string `json:"terms_of_service_original_text"` TermsOfServiceParsedText string `json:"terms_of_service_parsed_text"` @@ -166,6 +166,22 @@ type SiteLegalReq struct { ExternalContentDisplay string `validate:"required,oneof=always_display ask_before_display" json:"external_content_display"` } +type SitePoliciesReq struct { + TermsOfServiceOriginalText string `json:"terms_of_service_original_text"` + TermsOfServiceParsedText string `json:"terms_of_service_parsed_text"` + PrivacyPolicyOriginalText string `json:"privacy_policy_original_text"` + PrivacyPolicyParsedText string `json:"privacy_policy_parsed_text"` +} + +type SiteSecurityReq struct { + LoginRequired bool `json:"login_required"` + ExternalContentDisplay string `validate:"required,oneof=always_display ask_before_display" json:"external_content_display"` + CheckUpdate bool `validate:"omitempty,sanitizer" form:"check_update" json:"check_update"` +} + +type SitePoliciesResp SitePoliciesReq +type SiteSecurityResp SiteSecurityReq + // GetSiteLegalInfoReq site site legal request type GetSiteLegalInfoReq struct { InfoType string `validate:"required,oneof=tos privacy" form:"info_type"` @@ -204,7 +220,6 @@ type SiteLoginReq struct { AllowNewRegistrations bool `json:"allow_new_registrations"` AllowEmailRegistrations bool `json:"allow_email_registrations"` AllowPasswordLogin bool `json:"allow_password_login"` - LoginRequired bool `json:"login_required"` AllowEmailDomains []string `json:"allow_email_domains"` } @@ -284,6 +299,7 @@ type SiteAdvancedResp SiteAdvancedReq type SiteTagsResp SiteTagsReq // SiteLegalResp site write response +// Deprecated: use SitePoliciesResp and SiteSecurityResp instead type SiteLegalResp SiteLegalReq // SiteLegalSimpleResp site write response @@ -313,7 +329,6 @@ type SiteInfoResp struct { Revision string `json:"revision"` } -// todo: 检查模板使用 type TemplateSiteInfoResp struct { General *SiteGeneralResp `json:"general"` Interface *SiteInterfaceSettingsResp `json:"interface"` diff --git a/internal/service/dashboard/dashboard_service.go b/internal/service/dashboard/dashboard_service.go index 8b08ba02f..a6ac76e63 100644 --- a/internal/service/dashboard/dashboard_service.go +++ b/internal/service/dashboard/dashboard_service.go @@ -101,6 +101,12 @@ type DashboardService interface { func (ds *dashboardService) Statistical(ctx context.Context) (*schema.DashboardInfo, error) { dashboardInfo := ds.getFromCache(ctx) + security, err := ds.siteInfoService.GetSiteSecurity(ctx) + if err != nil { + log.Errorf("get general site info failed: %s", err) + return dashboardInfo, nil + } + if dashboardInfo == nil { dashboardInfo = &schema.DashboardInfo{} dashboardInfo.AnswerCount = ds.answerCount(ctx) @@ -108,12 +114,7 @@ func (ds *dashboardService) Statistical(ctx context.Context) (*schema.DashboardI dashboardInfo.UserCount = ds.userCount(ctx) dashboardInfo.VoteCount = ds.voteCount(ctx) dashboardInfo.OccupyingStorageSpace = ds.calculateStorage() - general, err := ds.siteInfoService.GetSiteGeneral(ctx) - if err != nil { - log.Errorf("get general site info failed: %s", err) - return dashboardInfo, nil - } - if general.CheckUpdate { + if security.CheckUpdate { dashboardInfo.VersionInfo.RemoteVersion = ds.remoteVersion(ctx) } dashboardInfo.DatabaseVersion = ds.getDatabaseInfo() @@ -141,9 +142,7 @@ func (ds *dashboardService) Statistical(ctx context.Context) (*schema.DashboardI dashboardInfo.VersionInfo.Version = constant.Version dashboardInfo.VersionInfo.Revision = constant.Revision dashboardInfo.GoVersion = constant.GoVersion - if siteLogin, err := ds.siteInfoService.GetSiteLogin(ctx); err == nil { - dashboardInfo.LoginRequired = siteLogin.LoginRequired - } + dashboardInfo.LoginRequired = security.LoginRequired ds.setCache(ctx, dashboardInfo) return dashboardInfo, nil diff --git a/internal/service/mock/siteinfo_repo_mock.go b/internal/service/mock/siteinfo_repo_mock.go index b809d8aef..8feb6a27c 100644 --- a/internal/service/mock/siteinfo_repo_mock.go +++ b/internal/service/mock/siteinfo_repo_mock.go @@ -212,10 +212,10 @@ func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteInfoByType(ctx, siteType } // GetSiteInterface mocks base method. -func (m *MockSiteInfoCommonService) GetSiteInterface(ctx context.Context) (*schema.SiteInterfaceResp, error) { +func (m *MockSiteInfoCommonService) GetSiteInterface(ctx context.Context) (*schema.SiteInterfaceSettingsResp, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetSiteInterface", ctx) - ret0, _ := ret[0].(*schema.SiteInterfaceResp) + ret0, _ := ret[0].(*schema.SiteInterfaceSettingsResp) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -226,34 +226,34 @@ func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteInterface(ctx interface{ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteInterface", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteInterface), ctx) } -// GetSiteLegal mocks base method. -func (m *MockSiteInfoCommonService) GetSiteLegal(ctx context.Context) (*schema.SiteLegalResp, error) { +// GetSiteLogin mocks base method. +func (m *MockSiteInfoCommonService) GetSiteLogin(ctx context.Context) (*schema.SiteLoginResp, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetSiteLegal", ctx) - ret0, _ := ret[0].(*schema.SiteLegalResp) + ret := m.ctrl.Call(m, "GetSiteLogin", ctx) + ret0, _ := ret[0].(*schema.SiteLoginResp) ret1, _ := ret[1].(error) return ret0, ret1 } -// GetSiteLegal indicates an expected call of GetSiteLegal. -func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteLegal(ctx interface{}) *gomock.Call { +// GetSiteLogin indicates an expected call of GetSiteLogin. +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteLogin(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteLegal", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteLegal), ctx) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteLogin", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteLogin), ctx) } -// GetSiteLogin mocks base method. -func (m *MockSiteInfoCommonService) GetSiteLogin(ctx context.Context) (*schema.SiteLoginResp, error) { +// GetSitePolicies mocks base method. +func (m *MockSiteInfoCommonService) GetSitePolicies(ctx context.Context) (*schema.SitePoliciesResp, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetSiteLogin", ctx) - ret0, _ := ret[0].(*schema.SiteLoginResp) + ret := m.ctrl.Call(m, "GetSitePolicies", ctx) + ret0, _ := ret[0].(*schema.SitePoliciesResp) ret1, _ := ret[1].(error) return ret0, ret1 } -// GetSiteLogin indicates an expected call of GetSiteLogin. -func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteLogin(ctx interface{}) *gomock.Call { +// GetSitePolicies indicates an expected call of GetSitePolicies. +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSitePolicies(ctx interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteLogin", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteLogin), ctx) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSitePolicies", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSitePolicies), ctx) } // GetSiteQuestion mocks base method. @@ -271,6 +271,21 @@ func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteQuestion(ctx interface{} return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteQuestion", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteQuestion), ctx) } +// GetSiteSecurity mocks base method. +func (m *MockSiteInfoCommonService) GetSiteSecurity(ctx context.Context) (*schema.SiteSecurityResp, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSiteSecurity", ctx) + ret0, _ := ret[0].(*schema.SiteSecurityResp) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSiteSecurity indicates an expected call of GetSiteSecurity. +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteSecurity(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteSecurity", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteSecurity), ctx) +} + // GetSiteSeo mocks base method. func (m *MockSiteInfoCommonService) GetSiteSeo(ctx context.Context) (*schema.SiteSeoResp, error) { m.ctrl.T.Helper() @@ -331,6 +346,21 @@ func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteUsers(ctx interface{}) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteUsers", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteUsers), ctx) } +// GetSiteUsersSettings mocks base method. +func (m *MockSiteInfoCommonService) GetSiteUsersSettings(ctx context.Context) (*schema.SiteUsersSettingsResp, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSiteUsersSettings", ctx) + ret0, _ := ret[0].(*schema.SiteUsersSettingsResp) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSiteUsersSettings indicates an expected call of GetSiteUsersSettings. +func (mr *MockSiteInfoCommonServiceMockRecorder) GetSiteUsersSettings(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSiteUsersSettings", reflect.TypeOf((*MockSiteInfoCommonService)(nil).GetSiteUsersSettings), ctx) +} + // GetSiteWrite mocks base method. func (m *MockSiteInfoCommonService) GetSiteWrite(ctx context.Context) (*schema.SiteWriteResp, error) { m.ctrl.T.Helper() diff --git a/internal/service/siteinfo/siteinfo_service.go b/internal/service/siteinfo/siteinfo_service.go index dd4d81225..ab4705a8d 100644 --- a/internal/service/siteinfo/siteinfo_service.go +++ b/internal/service/siteinfo/siteinfo_service.go @@ -138,9 +138,14 @@ func (s *SiteInfoService) GetSiteAdvanced(ctx context.Context) (resp *schema.Sit return s.siteInfoCommonService.GetSiteAdvanced(ctx) } -// GetSiteLegal get site legal info -func (s *SiteInfoService) GetSiteLegal(ctx context.Context) (resp *schema.SiteLegalResp, err error) { - return s.siteInfoCommonService.GetSiteLegal(ctx) +// GetSitePolicies get site legal info +func (s *SiteInfoService) GetSitePolicies(ctx context.Context) (resp *schema.SitePoliciesResp, err error) { + return s.siteInfoCommonService.GetSitePolicies(ctx) +} + +// GetSiteSecurity get site security info +func (s *SiteInfoService) GetSiteSecurity(ctx context.Context) (resp *schema.SiteSecurityResp, err error) { + return s.siteInfoCommonService.GetSiteSecurity(ctx) } // GetSiteLogin get site login info @@ -261,15 +266,26 @@ func (s *SiteInfoService) SaveSiteTags(ctx context.Context, req *schema.SiteTags return nil, s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeTags, data) } -// SaveSiteLegal save site legal configuration -func (s *SiteInfoService) SaveSiteLegal(ctx context.Context, req *schema.SiteLegalReq) (err error) { +// SaveSitePolicies save site policies configuration +func (s *SiteInfoService) SaveSitePolicies(ctx context.Context, req *schema.SitePoliciesReq) (err error) { + content, _ := json.Marshal(req) + data := &entity.SiteInfo{ + Type: constant.SiteTypePolicies, + Content: string(content), + Status: 1, + } + return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypePolicies, data) +} + +// SaveSiteSecurity save site security configuration +func (s *SiteInfoService) SaveSiteSecurity(ctx context.Context, req *schema.SiteSecurityReq) (err error) { content, _ := json.Marshal(req) data := &entity.SiteInfo{ - Type: constant.SiteTypeLegal, + Type: constant.SiteTypeSecurity, Content: string(content), Status: 1, } - return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeLegal, data) + return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeSecurity, data) } // SaveSiteLogin save site legal configuration @@ -364,13 +380,13 @@ func (s *SiteInfoService) GetSeo(ctx context.Context) (resp *schema.SiteSeoReq, if err = s.siteInfoCommonService.GetSiteInfoByType(ctx, constant.SiteTypeSeo, resp); err != nil { return resp, err } - loginConfig, err := s.GetSiteLogin(ctx) + siteSecurity, err := s.GetSiteSecurity(ctx) if err != nil { log.Error(err) return resp, nil } // If the site is set to privacy mode, prohibit crawling any page. - if loginConfig.LoginRequired { + if siteSecurity.LoginRequired { resp.Robots = "User-agent: *\nDisallow: /" return resp, nil } diff --git a/internal/service/siteinfo_common/siteinfo_service.go b/internal/service/siteinfo_common/siteinfo_service.go index 1689d32fd..2f2efb799 100644 --- a/internal/service/siteinfo_common/siteinfo_service.go +++ b/internal/service/siteinfo_common/siteinfo_service.go @@ -55,7 +55,8 @@ type SiteInfoCommonService interface { GetSiteAdvanced(ctx context.Context) (resp *schema.SiteAdvancedResp, err error) GetSiteQuestion(ctx context.Context) (resp *schema.SiteQuestionsResp, err error) GetSiteTag(ctx context.Context) (resp *schema.SiteTagsResp, err error) - GetSiteLegal(ctx context.Context) (resp *schema.SiteLegalResp, err error) + GetSitePolicies(ctx context.Context) (resp *schema.SitePoliciesResp, err error) + GetSiteSecurity(ctx context.Context) (resp *schema.SiteSecurityResp, err error) GetSiteLogin(ctx context.Context) (resp *schema.SiteLoginResp, err error) GetSiteCustomCssHTML(ctx context.Context) (resp *schema.SiteCustomCssHTMLResp, err error) GetSiteTheme(ctx context.Context) (resp *schema.SiteThemeResp, err error) @@ -73,7 +74,7 @@ func NewSiteInfoCommonService(siteInfoRepo SiteInfoRepo) SiteInfoCommonService { // GetSiteGeneral get site info general func (s *siteInfoCommonService) GetSiteGeneral(ctx context.Context) (resp *schema.SiteGeneralResp, err error) { - resp = &schema.SiteGeneralResp{CheckUpdate: true} + resp = &schema.SiteGeneralResp{} if err = s.GetSiteInfoByType(ctx, constant.SiteTypeGeneral, resp); err != nil { return nil, err } @@ -207,10 +208,19 @@ func (s *siteInfoCommonService) GetSiteTag(ctx context.Context) (resp *schema.Si return resp, nil } -// GetSiteLegal get site info write -func (s *siteInfoCommonService) GetSiteLegal(ctx context.Context) (resp *schema.SiteLegalResp, err error) { - resp = &schema.SiteLegalResp{} - if err = s.GetSiteInfoByType(ctx, constant.SiteTypeLegal, resp); err != nil { +// GetSitePolicies get site info policies +func (s *siteInfoCommonService) GetSitePolicies(ctx context.Context) (resp *schema.SitePoliciesResp, err error) { + resp = &schema.SitePoliciesResp{} + if err = s.GetSiteInfoByType(ctx, constant.SiteTypePolicies, resp); err != nil { + return nil, err + } + return resp, nil +} + +// GetSiteSecurity get site security config +func (s *siteInfoCommonService) GetSiteSecurity(ctx context.Context) (resp *schema.SiteSecurityResp, err error) { + resp = &schema.SiteSecurityResp{CheckUpdate: true} + if err = s.GetSiteInfoByType(ctx, constant.SiteTypeSecurity, resp); err != nil { return nil, err } return resp, nil From 128c44f5a3477074c106e6a3065fe07ab50dacf8 Mon Sep 17 00:00:00 2001 From: kumfo Date: Wed, 21 Jan 2026 16:29:08 +0800 Subject: [PATCH 15/19] feat(menu): update schema to remove deprecated min_tags and add MinimumTags to SiteQuestionsReq --- docs/docs.go | 27 +++++++++-------------- docs/swagger.json | 27 +++++++++-------------- docs/swagger.yaml | 20 ++++++++--------- internal/migrations/v30.go | 2 +- internal/schema/siteinfo_schema.go | 3 ++- internal/service/tag_common/tag_common.go | 2 +- 6 files changed, 35 insertions(+), 46 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index a70cdab09..c624133bd 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -10982,12 +10982,7 @@ const docTemplate = `{ "$ref": "#/definitions/schema.SiteInterfaceSettingsResp" }, "login": { - "description": "todo", - "allOf": [ - { - "$ref": "#/definitions/schema.SiteLoginResp" - } - ] + "$ref": "#/definitions/schema.SiteLoginResp" }, "revision": { "type": "string" @@ -11163,6 +11158,11 @@ const docTemplate = `{ "maximum": 65535, "minimum": 0 }, + "min_tags": { + "type": "integer", + "maximum": 5, + "minimum": 0 + }, "restrict_answer": { "type": "boolean" } @@ -11176,6 +11176,11 @@ const docTemplate = `{ "maximum": 65535, "minimum": 0 }, + "min_tags": { + "type": "integer", + "maximum": 5, + "minimum": 0 + }, "restrict_answer": { "type": "boolean" } @@ -11260,11 +11265,6 @@ const docTemplate = `{ "schema.SiteTagsReq": { "type": "object", "properties": { - "min_tags": { - "type": "integer", - "maximum": 5, - "minimum": 0 - }, "recommend_tags": { "type": "array", "items": { @@ -11285,11 +11285,6 @@ const docTemplate = `{ "schema.SiteTagsResp": { "type": "object", "properties": { - "min_tags": { - "type": "integer", - "maximum": 5, - "minimum": 0 - }, "recommend_tags": { "type": "array", "items": { diff --git a/docs/swagger.json b/docs/swagger.json index 05e3302c6..58f385790 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -10974,12 +10974,7 @@ "$ref": "#/definitions/schema.SiteInterfaceSettingsResp" }, "login": { - "description": "todo", - "allOf": [ - { - "$ref": "#/definitions/schema.SiteLoginResp" - } - ] + "$ref": "#/definitions/schema.SiteLoginResp" }, "revision": { "type": "string" @@ -11155,6 +11150,11 @@ "maximum": 65535, "minimum": 0 }, + "min_tags": { + "type": "integer", + "maximum": 5, + "minimum": 0 + }, "restrict_answer": { "type": "boolean" } @@ -11168,6 +11168,11 @@ "maximum": 65535, "minimum": 0 }, + "min_tags": { + "type": "integer", + "maximum": 5, + "minimum": 0 + }, "restrict_answer": { "type": "boolean" } @@ -11252,11 +11257,6 @@ "schema.SiteTagsReq": { "type": "object", "properties": { - "min_tags": { - "type": "integer", - "maximum": 5, - "minimum": 0 - }, "recommend_tags": { "type": "array", "items": { @@ -11277,11 +11277,6 @@ "schema.SiteTagsResp": { "type": "object", "properties": { - "min_tags": { - "type": "integer", - "maximum": 5, - "minimum": 0 - }, "recommend_tags": { "type": "array", "items": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 85a05baf0..39235a336 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2210,9 +2210,7 @@ definitions: interface: $ref: '#/definitions/schema.SiteInterfaceSettingsResp' login: - allOf: - - $ref: '#/definitions/schema.SiteLoginResp' - description: todo + $ref: '#/definitions/schema.SiteLoginResp' revision: type: string site_advanced: @@ -2330,6 +2328,10 @@ definitions: maximum: 65535 minimum: 0 type: integer + min_tags: + maximum: 5 + minimum: 0 + type: integer restrict_answer: type: boolean type: object @@ -2339,6 +2341,10 @@ definitions: maximum: 65535 minimum: 0 type: integer + min_tags: + maximum: 5 + minimum: 0 + type: integer restrict_answer: type: boolean type: object @@ -2396,10 +2402,6 @@ definitions: type: object schema.SiteTagsReq: properties: - min_tags: - maximum: 5 - minimum: 0 - type: integer recommend_tags: items: $ref: '#/definitions/schema.SiteWriteTag' @@ -2413,10 +2415,6 @@ definitions: type: object schema.SiteTagsResp: properties: - min_tags: - maximum: 5 - minimum: 0 - type: integer recommend_tags: items: $ref: '#/definitions/schema.SiteWriteTag' diff --git a/internal/migrations/v30.go b/internal/migrations/v30.go index c73f501d5..aa2f48b36 100644 --- a/internal/migrations/v30.go +++ b/internal/migrations/v30.go @@ -80,6 +80,7 @@ func splitWriteMenu(ctx context.Context, x *xorm.Engine) error { } // site questions settings siteQuestions := &schema.SiteQuestionsResp{ + MinimumTags: siteWrite.MinimumTags, MinimumContent: siteWrite.MinimumContent, RestrictAnswer: siteWrite.RestrictAnswer, } @@ -87,7 +88,6 @@ func splitWriteMenu(ctx context.Context, x *xorm.Engine) error { siteTags := &schema.SiteTagsResp{ ReservedTags: siteWrite.ReservedTags, RecommendTags: siteWrite.RecommendTags, - MinimumTags: siteWrite.MinimumTags, RequiredTag: siteWrite.RequiredTag, } diff --git a/internal/schema/siteinfo_schema.go b/internal/schema/siteinfo_schema.go index 07747d954..d3a886fdf 100644 --- a/internal/schema/siteinfo_schema.go +++ b/internal/schema/siteinfo_schema.go @@ -88,6 +88,7 @@ type SiteBrandingReq struct { } // SiteWriteReq site write request +// Deprecated: use SiteQuestionsReq, SiteAdvancedReq and SiteTagsReq instead type SiteWriteReq struct { MinimumContent int `validate:"omitempty,gte=0,lte=65535" json:"min_content"` RestrictAnswer bool `validate:"omitempty" json:"restrict_answer"` @@ -107,6 +108,7 @@ type SiteWriteResp SiteWriteReq // SiteQuestionsReq site questions settings request type SiteQuestionsReq struct { + MinimumTags int `validate:"omitempty,gte=0,lte=5" json:"min_tags"` MinimumContent int `validate:"omitempty,gte=0,lte=65535" json:"min_content"` RestrictAnswer bool `validate:"omitempty" json:"restrict_answer"` } @@ -124,7 +126,6 @@ type SiteAdvancedReq struct { type SiteTagsReq struct { ReservedTags []*SiteWriteTag `validate:"omitempty,dive" json:"reserved_tags"` RecommendTags []*SiteWriteTag `validate:"omitempty,dive" json:"recommend_tags"` - MinimumTags int `validate:"omitempty,gte=0,lte=5" json:"min_tags"` RequiredTag bool `validate:"omitempty" json:"required_tag"` UserID string `json:"-"` } diff --git a/internal/service/tag_common/tag_common.go b/internal/service/tag_common/tag_common.go index 0da9f6fd5..87b53f396 100644 --- a/internal/service/tag_common/tag_common.go +++ b/internal/service/tag_common/tag_common.go @@ -295,7 +295,7 @@ func (ts *TagCommonService) ExistRecommend(ctx context.Context, tags []*schema.T } func (ts *TagCommonService) GetMinimumTags(ctx context.Context) (int, error) { - siteInfo, err := ts.siteInfoService.GetSiteTag(ctx) + siteInfo, err := ts.siteInfoService.GetSiteQuestion(ctx) if err != nil { return 1, err } From 6369056914a5a92f0898e013e1bf2cea03fadba3 Mon Sep 17 00:00:00 2001 From: kumfo Date: Wed, 21 Jan 2026 17:21:28 +0800 Subject: [PATCH 16/19] feat(siteinfo): fix GetSiteTag method to correctly assign response from siteInfoCommonService --- internal/service/siteinfo/siteinfo_service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/siteinfo/siteinfo_service.go b/internal/service/siteinfo/siteinfo_service.go index ab4705a8d..cc0cb5997 100644 --- a/internal/service/siteinfo/siteinfo_service.go +++ b/internal/service/siteinfo/siteinfo_service.go @@ -111,7 +111,7 @@ func (s *SiteInfoService) GetSiteUsers(ctx context.Context) (resp *schema.SiteUs // GetSiteTag get site info write func (s *SiteInfoService) GetSiteTag(ctx context.Context) (resp *schema.SiteTagsResp, err error) { resp = &schema.SiteTagsResp{} - _, err = s.siteInfoCommonService.GetSiteTag(ctx) + resp, err = s.siteInfoCommonService.GetSiteTag(ctx) if err != nil { log.Error(err) return resp, nil From 18b76f3e23bace34b0fe352aab0a8a33549e4928 Mon Sep 17 00:00:00 2001 From: kumfo Date: Thu, 22 Jan 2026 10:59:28 +0800 Subject: [PATCH 17/19] feat(siteinfo): add site_security to response structure and update related schemas --- docs/docs.go | 3 +++ docs/swagger.json | 3 +++ docs/swagger.yaml | 2 ++ internal/controller/siteinfo_controller.go | 3 +++ internal/schema/siteinfo_schema.go | 1 + 5 files changed, 12 insertions(+) diff --git a/docs/docs.go b/docs/docs.go index c624133bd..23398e509 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -10996,6 +10996,9 @@ const docTemplate = `{ "site_questions": { "$ref": "#/definitions/schema.SiteQuestionsResp" }, + "site_security": { + "$ref": "#/definitions/schema.SiteSecurityResp" + }, "site_seo": { "$ref": "#/definitions/schema.SiteSeoResp" }, diff --git a/docs/swagger.json b/docs/swagger.json index 58f385790..b9dc0b0b1 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -10988,6 +10988,9 @@ "site_questions": { "$ref": "#/definitions/schema.SiteQuestionsResp" }, + "site_security": { + "$ref": "#/definitions/schema.SiteSecurityResp" + }, "site_seo": { "$ref": "#/definitions/schema.SiteSeoResp" }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 39235a336..4a680ec49 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2219,6 +2219,8 @@ definitions: $ref: '#/definitions/schema.SiteLegalSimpleResp' site_questions: $ref: '#/definitions/schema.SiteQuestionsResp' + site_security: + $ref: '#/definitions/schema.SiteSecurityResp' site_seo: $ref: '#/definitions/schema.SiteSeoResp' site_tags: diff --git a/internal/controller/siteinfo_controller.go b/internal/controller/siteinfo_controller.go index 64aa02ce7..320c1b475 100644 --- a/internal/controller/siteinfo_controller.go +++ b/internal/controller/siteinfo_controller.go @@ -107,6 +107,9 @@ func (sc *SiteInfoController) GetSiteInfo(ctx *gin.Context) { if legal, err := sc.siteInfoService.GetSiteSecurity(ctx); err == nil { resp.Legal = &schema.SiteLegalSimpleResp{ExternalContentDisplay: legal.ExternalContentDisplay} } + if security, err := sc.siteInfoService.GetSiteSecurity(ctx); err == nil { + resp.Security = security + } handler.HandleResponse(ctx, nil, resp) } diff --git a/internal/schema/siteinfo_schema.go b/internal/schema/siteinfo_schema.go index d3a886fdf..77ab75fc2 100644 --- a/internal/schema/siteinfo_schema.go +++ b/internal/schema/siteinfo_schema.go @@ -326,6 +326,7 @@ type SiteInfoResp struct { Questions *SiteQuestionsResp `json:"site_questions"` Tags *SiteTagsResp `json:"site_tags"` Legal *SiteLegalSimpleResp `json:"site_legal"` + Security *SiteSecurityResp `json:"site_security"` Version string `json:"version"` Revision string `json:"revision"` } From 73cfbace7090b233a51571dabdd26ec47c62c890 Mon Sep 17 00:00:00 2001 From: kumfo Date: Thu, 22 Jan 2026 11:49:50 +0800 Subject: [PATCH 18/19] feat(menu): deprecate default_avatar and gravatar_base_url in SiteInterfaceReq schema --- docs/docs.go | 11 ----------- docs/swagger.json | 11 ----------- docs/swagger.yaml | 8 -------- internal/schema/siteinfo_schema.go | 10 ++++++---- 4 files changed, 6 insertions(+), 34 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index 23398e509..00eb75ce2 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -11022,21 +11022,10 @@ const docTemplate = `{ "schema.SiteInterfaceReq": { "type": "object", "required": [ - "default_avatar", "language", "time_zone" ], "properties": { - "default_avatar": { - "type": "string", - "enum": [ - "system", - "gravatar" - ] - }, - "gravatar_base_url": { - "type": "string" - }, "language": { "type": "string", "maxLength": 128 diff --git a/docs/swagger.json b/docs/swagger.json index b9dc0b0b1..c181109d2 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -11014,21 +11014,10 @@ "schema.SiteInterfaceReq": { "type": "object", "required": [ - "default_avatar", "language", "time_zone" ], "properties": { - "default_avatar": { - "type": "string", - "enum": [ - "system", - "gravatar" - ] - }, - "gravatar_base_url": { - "type": "string" - }, "language": { "type": "string", "maxLength": 128 diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 4a680ec49..caab11876 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2236,13 +2236,6 @@ definitions: type: object schema.SiteInterfaceReq: properties: - default_avatar: - enum: - - system - - gravatar - type: string - gravatar_base_url: - type: string language: maxLength: 128 type: string @@ -2250,7 +2243,6 @@ definitions: maxLength: 128 type: string required: - - default_avatar - language - time_zone type: object diff --git a/internal/schema/siteinfo_schema.go b/internal/schema/siteinfo_schema.go index 77ab75fc2..bf1c7713e 100644 --- a/internal/schema/siteinfo_schema.go +++ b/internal/schema/siteinfo_schema.go @@ -58,10 +58,12 @@ func (r *SiteGeneralReq) FormatSiteUrl() { // SiteInterfaceReq site interface request type SiteInterfaceReq struct { - Language string `validate:"required,gt=1,lte=128" form:"language" json:"language"` - TimeZone string `validate:"required,gt=1,lte=128" form:"time_zone" json:"time_zone"` - DefaultAvatar string `validate:"required,oneof=system gravatar" json:"default_avatar"` - GravatarBaseURL string `validate:"omitempty" json:"gravatar_base_url"` + Language string `validate:"required,gt=1,lte=128" form:"language" json:"language"` + TimeZone string `validate:"required,gt=1,lte=128" form:"time_zone" json:"time_zone"` + // Deperecated: use SiteUsersSettingsReq instead + DefaultAvatar string `validate:"omitempty" json:"-"` + // Deperecated: use SiteUsersSettingsReq instead + GravatarBaseURL string `validate:"omitempty" json:"-"` } // SiteInterfaceSettingsReq site interface settings request From 86c2d64dbf7f9b592e7026f0f432759d552fce45 Mon Sep 17 00:00:00 2001 From: kumfo Date: Thu, 22 Jan 2026 12:18:57 +0800 Subject: [PATCH 19/19] feat(docs): add Apache License 2.0 header to docs.go and swagger.yaml --- docs/docs.go | 19 +++++++++++++++++++ docs/swagger.yaml | 17 +++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/docs/docs.go b/docs/docs.go index 00eb75ce2..cfe48366f 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1,3 +1,22 @@ +/* + * 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 docs Code generated by swaggo/swag. DO NOT EDIT package docs diff --git a/docs/swagger.yaml b/docs/swagger.yaml index caab11876..13e9575eb 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,3 +1,20 @@ +# 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. + basePath: / definitions: constant.NotificationChannelKey: