From 59265e185b9b3b286268835dac274e521028bc72 Mon Sep 17 00:00:00 2001 From: Jonson Petard <41122242+greenhat616@users.noreply.github.com> Date: Sun, 24 Sep 2023 23:16:15 +0800 Subject: [PATCH] feat(admin, hitokoto): admin hitokoto management --- .golangci.yml | 4 + api/admin/admin.go | 16 - api/admin/v1/grant_authority.go | 24 -- api/admin/v1/modify_sentence.go | 16 - api/hitokoto/adminV1/delete_list.go | 10 + api/hitokoto/adminV1/get_list.go | 14 + api/hitokoto/adminV1/move.go | 14 + api/hitokoto/adminV1/update_one.go | 15 + api/hitokoto/hitokoto.go | 8 + api/hitokoto/v1/hitokoto.go | 4 +- go.mod | 5 +- go.sum | 14 +- internal/cmd/cmd.go | 4 +- internal/consts/poll.go | 1 + internal/controller/admin/admin.go | 5 - internal/controller/admin/admin_new.go | 15 - .../admin/admin_v1_grant_user_authority.go | 14 - .../admin/admin_v1_modify_sentence.go | 14 - .../hitokoto/hitokoto_adminV1_delete_list.go | 20 ++ .../hitokoto_adminV1_move_sentence.go | 27 ++ .../hitokoto/hitokoto_adminV1_update_one.go | 27 ++ .../hitokoto/hitokoto_admin_v1_get_list.go | 33 +++ internal/controller/hitokoto/hitokoto_new.go | 6 + .../hitokoto/hitokoto_v1_get_hitokoto.go | 4 +- .../poll/poll_v1_get_poll_detail.go | 3 +- internal/controller/poll/poll_v1_new_poll.go | 27 +- internal/logic/cache/cache.go | 50 ---- internal/logic/cache/hitokoto.go | 20 ++ internal/logic/cache/poll.go | 66 +++++ internal/logic/hitokoto/convert.go | 43 ++- internal/logic/hitokoto/hitokoto.go | 276 +++++++++++++++++- internal/logic/poll/poll.go | 5 + internal/logic/poll/ruling.go | 49 ++-- internal/model/hitokoto.go | 41 ++- internal/model/page.go | 8 + internal/model/poll.go | 27 +- internal/model/poll_log.go | 2 +- internal/model/user.go | 25 +- internal/service/cache.go | 1 + internal/service/hitokoto.go | 21 +- internal/service/poll.go | 1 + 41 files changed, 715 insertions(+), 264 deletions(-) delete mode 100644 api/admin/admin.go delete mode 100644 api/admin/v1/grant_authority.go delete mode 100644 api/admin/v1/modify_sentence.go create mode 100644 api/hitokoto/adminV1/delete_list.go create mode 100644 api/hitokoto/adminV1/get_list.go create mode 100644 api/hitokoto/adminV1/move.go create mode 100644 api/hitokoto/adminV1/update_one.go delete mode 100644 internal/controller/admin/admin.go delete mode 100644 internal/controller/admin/admin_new.go delete mode 100644 internal/controller/admin/admin_v1_grant_user_authority.go delete mode 100644 internal/controller/admin/admin_v1_modify_sentence.go create mode 100644 internal/controller/hitokoto/hitokoto_adminV1_delete_list.go create mode 100644 internal/controller/hitokoto/hitokoto_adminV1_move_sentence.go create mode 100644 internal/controller/hitokoto/hitokoto_adminV1_update_one.go create mode 100644 internal/controller/hitokoto/hitokoto_admin_v1_get_list.go create mode 100644 internal/logic/cache/hitokoto.go create mode 100644 internal/logic/cache/poll.go create mode 100644 internal/model/page.go diff --git a/.golangci.yml b/.golangci.yml index 36083d9..1143994 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -79,6 +79,10 @@ linters-settings: - name: unused-parameter severity: warning disabled: true + stylecheck: + checks: + - all + - '-ST1003' linters: disable-all: true diff --git a/api/admin/admin.go b/api/admin/admin.go deleted file mode 100644 index 80ce068..0000000 --- a/api/admin/admin.go +++ /dev/null @@ -1,16 +0,0 @@ -// ================================================================================= -// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. -// ================================================================================= - -package admin - -import ( - "context" - - "github.com/hitokoto-osc/reviewer/api/admin/v1" -) - -type IAdminV1 interface { - GrantUserAuthority(ctx context.Context, req *v1.GrantUserAuthorityReq) (res *v1.GrantUserAuthorityRes, err error) - ModifySentence(ctx context.Context, req *v1.ModifySentenceReq) (res *v1.ModifySentenceRes, err error) -} diff --git a/api/admin/v1/grant_authority.go b/api/admin/v1/grant_authority.go deleted file mode 100644 index 634187b..0000000 --- a/api/admin/v1/grant_authority.go +++ /dev/null @@ -1,24 +0,0 @@ -package v1 - -import ( - "github.com/gogf/gf/v2/frame/g" - "github.com/hitokoto-osc/reviewer/internal/consts" - "github.com/hitokoto-osc/reviewer/utility/time" -) - -type GrantUserAuthorityReq struct { - g.Meta `path:"/admin/grant_authority" tags:"Admin" method:"post" summary:"授予用户权限"` - Account string `json:"account" dc:"用户账号" v:"required"` - AccountType consts.UserLoginType `json:"account_type" dc:"用户账号类型" v:"required|enum#uid,email,token,username"` -} - -type GrantUserAuthorityRes struct { - ID uint `json:"id" dc:"用户 ID"` - Name string `json:"name" dc:"用户名"` - Email string `json:"email" dc:"邮箱"` - Token string `json:"token" dc:"Token"` - Status consts.UserStatus `json:"status" dc:"用户状态"` - Role consts.UserRole `json:"role" dc:"角色"` - CreatedAt *time.Time `json:"created_at" dc:"创建时间"` - UpdatedAt *time.Time `json:"updated_at" dc:"更新时间"` -} diff --git a/api/admin/v1/modify_sentence.go b/api/admin/v1/modify_sentence.go deleted file mode 100644 index da8299a..0000000 --- a/api/admin/v1/modify_sentence.go +++ /dev/null @@ -1,16 +0,0 @@ -package v1 - -import ( - "github.com/gogf/gf/v2/frame/g" - "github.com/hitokoto-osc/reviewer/internal/model" -) - -type ModifySentenceReq struct { - g.Meta `path:"/admin/sentence/{uuid}" tags:"Admin" method:"put" summary:"修改句子"` - SentenceUUID string `json:"sentence_uuid" dc:"句子 UUID" v:"required|length:36"` - Params model.ModifySentenceParams `json:"params" dc:"句子参数"` -} - -type ModifySentenceRes struct { - model.PollElement -} diff --git a/api/hitokoto/adminV1/delete_list.go b/api/hitokoto/adminV1/delete_list.go new file mode 100644 index 0000000..19f6ff1 --- /dev/null +++ b/api/hitokoto/adminV1/delete_list.go @@ -0,0 +1,10 @@ +package adminV1 + +import "github.com/gogf/gf/v2/frame/g" + +type DeleteListReq struct { + g.Meta `path:"/hitokoto" method:"DELETE" summary:"批量删除句子" tags:"句子"` + UUIDs []string `json:"uuids" dc:"句子 UUID" v:"required|length:1,100" in:"query"` +} + +type DeleteListRes struct{} diff --git a/api/hitokoto/adminV1/get_list.go b/api/hitokoto/adminV1/get_list.go new file mode 100644 index 0000000..51501c5 --- /dev/null +++ b/api/hitokoto/adminV1/get_list.go @@ -0,0 +1,14 @@ +package adminV1 + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/hitokoto-osc/reviewer/internal/model" +) + +// GetListReq 获取句子列表 +type GetListReq struct { + g.Meta `path:"/hitokoto" method:"GET" summary:"获取句子列表" tags:"句子"` + model.GetHitokotoV1SchemaListInput +} + +type GetListRes model.GetHitokotoV1SchemaListOutput diff --git a/api/hitokoto/adminV1/move.go b/api/hitokoto/adminV1/move.go new file mode 100644 index 0000000..8693b35 --- /dev/null +++ b/api/hitokoto/adminV1/move.go @@ -0,0 +1,14 @@ +package adminV1 + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/hitokoto-osc/reviewer/internal/consts" +) + +type MoveSentenceReq struct { + g.Meta `path:"/hitokoto/:uuid" method:"POST" summary:"移动句子" tags:"句子"` + UUID string `json:"uuid" dc:"句子 UUID" v:"required|regex:^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$#请输入 UUID|UUID 非法" in:"path"` //nolint:lll + Target consts.HitokotoStatus `json:"target" dc:"目标状态" v:"required|in:pending,approved,rejected" in:"query"` +} + +type MoveSentenceRes struct{} diff --git a/api/hitokoto/adminV1/update_one.go b/api/hitokoto/adminV1/update_one.go new file mode 100644 index 0000000..e374acf --- /dev/null +++ b/api/hitokoto/adminV1/update_one.go @@ -0,0 +1,15 @@ +package adminV1 + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/hitokoto-osc/reviewer/internal/model" +) + +type UpdateOneReq struct { + g.Meta `path:"/hitokoto/:uuid" method:"PUT" summary:"更新句子" tags:"句子"` + UUID string `json:"uuid" dc:"句子 UUID" v:"required|regex:^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$#请输入 UUID|UUID 非法" in:"path"` //nolint:lll + // 句子相关 + model.DoHitokotoV1Update +} + +type UpdateOneRes struct{} diff --git a/api/hitokoto/hitokoto.go b/api/hitokoto/hitokoto.go index a1f2b76..1d04302 100644 --- a/api/hitokoto/hitokoto.go +++ b/api/hitokoto/hitokoto.go @@ -7,9 +7,17 @@ package hitokoto import ( "context" + "github.com/hitokoto-osc/reviewer/api/hitokoto/adminV1" "github.com/hitokoto-osc/reviewer/api/hitokoto/v1" ) +type IHitokotoAdminV1 interface { + DeleteList(ctx context.Context, req *adminV1.DeleteListReq) (res *adminV1.DeleteListRes, err error) + GetList(ctx context.Context, req *adminV1.GetListReq) (res *adminV1.GetListRes, err error) + MoveSentence(ctx context.Context, req *adminV1.MoveSentenceReq) (res *adminV1.MoveSentenceRes, err error) + UpdateOne(ctx context.Context, req *adminV1.UpdateOneReq) (res *adminV1.UpdateOneRes, err error) +} + type IHitokotoV1 interface { GetHitokoto(ctx context.Context, req *v1.GetHitokotoReq) (res *v1.GetHitokotoRes, err error) } diff --git a/api/hitokoto/v1/hitokoto.go b/api/hitokoto/v1/hitokoto.go index 576fa18..c283e9b 100644 --- a/api/hitokoto/v1/hitokoto.go +++ b/api/hitokoto/v1/hitokoto.go @@ -10,6 +10,4 @@ type GetHitokotoReq struct { UUID string `json:"uuid" dc:"一言 UUID" v:"required|regex:^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$#请输入 UUID|UUID 非法" in:"path"` //nolint:lll } -type GetHitokotoRes struct { - model.HitokotoV1Schema -} +type GetHitokotoRes model.HitokotoV1WithPoll diff --git a/go.mod b/go.mod index 603fd03..5feb0e0 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,14 @@ module github.com/hitokoto-osc/reviewer go 1.20 require ( + github.com/duke-git/lancet/v2 v2.2.6 github.com/gogf/gf/contrib/drivers/mysql/v2 v2.5.4 github.com/gogf/gf/contrib/nosql/redis/v2 v2.5.4 github.com/gogf/gf/v2 v2.5.4 github.com/google/uuid v1.3.1 github.com/meilisearch/meilisearch-go v0.25.1 github.com/rabbitmq/amqp091-go v1.8.1 + github.com/samber/lo v1.38.1 golang.org/x/sync v0.3.0 ) @@ -34,7 +36,7 @@ require ( github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/redis/go-redis/v9 v9.1.0 // indirect + github.com/redis/go-redis/v9 v9.2.0 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.50.0 // indirect @@ -42,6 +44,7 @@ require ( go.opentelemetry.io/otel/metric v1.18.0 // indirect go.opentelemetry.io/otel/sdk v1.18.0 // indirect go.opentelemetry.io/otel/trace v1.18.0 // indirect + golang.org/x/exp v0.0.0-20221208152030-732eee02a75a // indirect golang.org/x/net v0.15.0 // indirect golang.org/x/sys v0.12.0 // indirect golang.org/x/text v0.13.0 // indirect diff --git a/go.sum b/go.sum index 524852f..e7c3316 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,8 @@ github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/bsm/ginkgo/v2 v2.9.5 h1:rtVBYPs3+TC5iLUVOis1B9tjLTup7Cj5IfzosKtvTJ0= -github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= @@ -14,6 +14,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/duke-git/lancet/v2 v2.2.6 h1:i0p8J85ujU1OslVIeEn+006OqVMdR7C+O7CHnMML+qI= +github.com/duke-git/lancet/v2 v2.2.6/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= @@ -71,11 +73,13 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rabbitmq/amqp091-go v1.8.1 h1:RejT1SBUim5doqcL6s7iN6SBmsQqyTgXb1xMlH0h1hA= github.com/rabbitmq/amqp091-go v1.8.1/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc= -github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY= -github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c= +github.com/redis/go-redis/v9 v9.2.0 h1:zwMdX0A4eVzse46YN18QhuDiM4uf3JmkOB4VZrdt5uI= +github.com/redis/go-redis/v9 v9.2.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= +github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -100,6 +104,8 @@ go.opentelemetry.io/otel/trace v1.18.0/go.mod h1:T2+SGJGuYZY3bjj5rgh/hN7KIrlpWC5 go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20221208152030-732eee02a75a h1:4iLhBPcpqFmylhnkbY3W0ONLUYYkDAW9xMFLfxgsvCw= +golang.org/x/exp v0.0.0-20221208152030-732eee02a75a/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index d33ad7a..2c640c0 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -16,8 +16,6 @@ import ( "github.com/hitokoto-osc/reviewer/internal/consts" - "github.com/hitokoto-osc/reviewer/internal/controller/admin" - "github.com/hitokoto-osc/reviewer/internal/service" "github.com/hitokoto-osc/reviewer/internal/controller/index" @@ -104,7 +102,7 @@ var ( group.GET("/poll/mark", poll.NewV1().GetPollMarks) // 此路由无需验证审核员权限 group.Group("/admin", func(group *ghttp.RouterGroup) { group.Middleware(service.Middleware().GuardV1(consts.UserRoleAdmin)) - group.Bind(admin.NewV1()) + group.Bind(hitokoto.NewAdminV1()) }) }) }) diff --git a/internal/consts/poll.go b/internal/consts/poll.go index 75a1e76..3556acb 100644 --- a/internal/consts/poll.go +++ b/internal/consts/poll.go @@ -28,6 +28,7 @@ const ( type PollStatus int const ( + PollStatusUnknown PollStatus = -1 // 未知,比如投票不存在 PollStatusNotOpen PollStatus = 0 // 未开放投票 PollStatusOpen PollStatus = 1 // 投票正常开放 PollStatusProcessing PollStatus = 2 // 处理中,停止投票 diff --git a/internal/controller/admin/admin.go b/internal/controller/admin/admin.go deleted file mode 100644 index f122676..0000000 --- a/internal/controller/admin/admin.go +++ /dev/null @@ -1,5 +0,0 @@ -// ================================================================================= -// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. -// ================================================================================= - -package admin diff --git a/internal/controller/admin/admin_new.go b/internal/controller/admin/admin_new.go deleted file mode 100644 index f0c8085..0000000 --- a/internal/controller/admin/admin_new.go +++ /dev/null @@ -1,15 +0,0 @@ -// ================================================================================= -// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. -// ================================================================================= - -package admin - -import ( - "github.com/hitokoto-osc/reviewer/api/admin" -) - -type ControllerV1 struct{} - -func NewV1() admin.IAdminV1 { - return &ControllerV1{} -} diff --git a/internal/controller/admin/admin_v1_grant_user_authority.go b/internal/controller/admin/admin_v1_grant_user_authority.go deleted file mode 100644 index 5961736..0000000 --- a/internal/controller/admin/admin_v1_grant_user_authority.go +++ /dev/null @@ -1,14 +0,0 @@ -package admin - -import ( - "context" - - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" - - v1 "github.com/hitokoto-osc/reviewer/api/admin/v1" -) - -func (c *ControllerV1) GrantUserAuthority(ctx context.Context, req *v1.GrantUserAuthorityReq) (res *v1.GrantUserAuthorityRes, err error) { - return nil, gerror.NewCode(gcode.CodeNotImplemented) -} diff --git a/internal/controller/admin/admin_v1_modify_sentence.go b/internal/controller/admin/admin_v1_modify_sentence.go deleted file mode 100644 index 57146e7..0000000 --- a/internal/controller/admin/admin_v1_modify_sentence.go +++ /dev/null @@ -1,14 +0,0 @@ -package admin - -import ( - "context" - - "github.com/gogf/gf/v2/errors/gcode" - "github.com/gogf/gf/v2/errors/gerror" - - v1 "github.com/hitokoto-osc/reviewer/api/admin/v1" -) - -func (c *ControllerV1) ModifySentence(ctx context.Context, req *v1.ModifySentenceReq) (res *v1.ModifySentenceRes, err error) { - return nil, gerror.NewCode(gcode.CodeNotImplemented) -} diff --git a/internal/controller/hitokoto/hitokoto_adminV1_delete_list.go b/internal/controller/hitokoto/hitokoto_adminV1_delete_list.go new file mode 100644 index 0000000..02c4c96 --- /dev/null +++ b/internal/controller/hitokoto/hitokoto_adminV1_delete_list.go @@ -0,0 +1,20 @@ +package hitokoto + +import ( + "context" + + "github.com/hitokoto-osc/reviewer/internal/service" + + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + + "github.com/hitokoto-osc/reviewer/api/hitokoto/adminV1" +) + +func (c *ControllerAdminV1) DeleteList(ctx context.Context, req *adminV1.DeleteListReq) (res *adminV1.DeleteListRes, err error) { + err = service.Hitokoto().Delete(ctx, req.UUIDs) + if err != nil { + return nil, gerror.WrapCode(gcode.CodeInternalError, err) + } + return nil, nil +} diff --git a/internal/controller/hitokoto/hitokoto_adminV1_move_sentence.go b/internal/controller/hitokoto/hitokoto_adminV1_move_sentence.go new file mode 100644 index 0000000..38cdfc1 --- /dev/null +++ b/internal/controller/hitokoto/hitokoto_adminV1_move_sentence.go @@ -0,0 +1,27 @@ +package hitokoto + +import ( + "context" + + "github.com/hitokoto-osc/reviewer/internal/service" + + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + + "github.com/hitokoto-osc/reviewer/api/hitokoto/adminV1" +) + +func (c *ControllerAdminV1) MoveSentence(ctx context.Context, req *adminV1.MoveSentenceReq) (res *adminV1.MoveSentenceRes, err error) { + sentence, err := service.Hitokoto().GetHitokotoV1SchemaByUUID(ctx, req.UUID) + if err != nil { + return nil, gerror.WrapCode(gcode.CodeInternalError, err) + } + if sentence == nil { + return nil, gerror.NewCode(gcode.CodeNotFound, "未找到指定的句子") + } + err = service.Hitokoto().Move(ctx, sentence, req.Target) + if err != nil { + return nil, err + } + return nil, nil +} diff --git a/internal/controller/hitokoto/hitokoto_adminV1_update_one.go b/internal/controller/hitokoto/hitokoto_adminV1_update_one.go new file mode 100644 index 0000000..3752bf2 --- /dev/null +++ b/internal/controller/hitokoto/hitokoto_adminV1_update_one.go @@ -0,0 +1,27 @@ +package hitokoto + +import ( + "context" + + "github.com/hitokoto-osc/reviewer/internal/service" + + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + + "github.com/hitokoto-osc/reviewer/api/hitokoto/adminV1" +) + +func (c *ControllerAdminV1) UpdateOne(ctx context.Context, req *adminV1.UpdateOneReq) (res *adminV1.UpdateOneRes, err error) { + sentence, err := service.Hitokoto().GetHitokotoV1SchemaByUUID(ctx, req.UUID) + if err != nil { + return nil, gerror.WrapCode(gcode.CodeInternalError, err) + } + if sentence == nil { + return nil, gerror.NewCode(gcode.CodeNotFound, "未找到指定的句子") + } + err = service.Hitokoto().UpdateByUUID(ctx, sentence, &req.DoHitokotoV1Update) + if err != nil { + return nil, err + } + return nil, nil +} diff --git a/internal/controller/hitokoto/hitokoto_admin_v1_get_list.go b/internal/controller/hitokoto/hitokoto_admin_v1_get_list.go new file mode 100644 index 0000000..7e99fd8 --- /dev/null +++ b/internal/controller/hitokoto/hitokoto_admin_v1_get_list.go @@ -0,0 +1,33 @@ +package hitokoto + +import ( + "context" + + "github.com/hitokoto-osc/reviewer/internal/model" + "github.com/hitokoto-osc/reviewer/internal/service" + + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + + "github.com/hitokoto-osc/reviewer/api/hitokoto/adminV1" +) + +func (c *ControllerAdminV1) GetList(ctx context.Context, req *adminV1.GetListReq) (res *adminV1.GetListRes, err error) { + out, err := service.Hitokoto().GetList(ctx, &model.GetHitokotoV1SchemaListInput{ + Page: req.Page, + PageSize: req.PageSize, + Order: req.Order, + UUID: req.UUID, + Keywords: req.Keywords, + Creator: req.Creator, + From: req.From, + FromWho: req.FromWho, + Type: req.Type, + Status: req.Status, + }) + if err != nil { + return nil, gerror.WrapCode(gcode.CodeInternalError, err, "获取句子列表失败") + } + res = (*adminV1.GetListRes)(out) + return +} diff --git a/internal/controller/hitokoto/hitokoto_new.go b/internal/controller/hitokoto/hitokoto_new.go index 1023aaf..aff9831 100644 --- a/internal/controller/hitokoto/hitokoto_new.go +++ b/internal/controller/hitokoto/hitokoto_new.go @@ -13,3 +13,9 @@ type ControllerV1 struct{} func NewV1() hitokoto.IHitokotoV1 { return &ControllerV1{} } + +type ControllerAdminV1 struct{} + +func NewAdminV1() hitokoto.IHitokotoAdminV1 { + return &ControllerAdminV1{} +} diff --git a/internal/controller/hitokoto/hitokoto_v1_get_hitokoto.go b/internal/controller/hitokoto/hitokoto_v1_get_hitokoto.go index e43715d..ec7d30a 100644 --- a/internal/controller/hitokoto/hitokoto_v1_get_hitokoto.go +++ b/internal/controller/hitokoto/hitokoto_v1_get_hitokoto.go @@ -19,8 +19,6 @@ func (c *ControllerV1) GetHitokoto(ctx context.Context, req *v1.GetHitokotoReq) if hitokoto == nil { return nil, gerror.NewCode(gcode.CodeNotFound, "一言不存在") } - res = &v1.GetHitokotoRes{ - HitokotoV1Schema: *hitokoto, - } + res = (*v1.GetHitokotoRes)(hitokoto) return } diff --git a/internal/controller/poll/poll_v1_get_poll_detail.go b/internal/controller/poll/poll_v1_get_poll_detail.go index a1f16d7..356d490 100644 --- a/internal/controller/poll/poll_v1_get_poll_detail.go +++ b/internal/controller/poll/poll_v1_get_poll_detail.go @@ -2,6 +2,7 @@ package poll import ( "context" + "github.com/gogf/gf/v2/crypto/gmd5" "github.com/hitokoto-osc/reviewer/internal/model/entity" @@ -30,7 +31,7 @@ func (c *ControllerV1) GetPollDetail(ctx context.Context, req *v1.GetPollDetailR // fetch logs and sentence var ( logs []entity.PollLog - sentence *model.HitokotoV1Schema + sentence *model.HitokotoV1WithPoll marks []int polledData *model.PolledData ) diff --git a/internal/controller/poll/poll_v1_new_poll.go b/internal/controller/poll/poll_v1_new_poll.go index 9dd5696..a43daeb 100644 --- a/internal/controller/poll/poll_v1_new_poll.go +++ b/internal/controller/poll/poll_v1_new_poll.go @@ -52,19 +52,22 @@ func (c *ControllerV1) NewPoll(ctx context.Context, req *v1.NewPollReq) (res *v1 res = &v1.NewPollRes{ Poll: model.PollElement{ SentenceUUID: poll.SentenceUuid, - Sentence: &model.HitokotoV1Schema{ - ID: uint(topPending.Id), - UUID: topPending.Uuid, - Hitokoto: topPending.Hitokoto, - Type: consts.HitokotoType(topPending.Type), - From: topPending.From, - FromWho: topPending.FromWho, - Creator: topPending.Creator, - CreatorUID: uint(topPending.CreatorUid), - Reviewer: uint(topPending.Reviewer), - Status: consts.HitokotoStatusPending, + Sentence: &model.HitokotoV1WithPoll{ + HitokotoV1: model.HitokotoV1{ + ID: uint(topPending.Id), + UUID: topPending.Uuid, + Hitokoto: topPending.Hitokoto, + Type: consts.HitokotoType(topPending.Type), + From: topPending.From, + FromWho: topPending.FromWho, + Creator: topPending.Creator, + CreatorUID: uint(topPending.CreatorUid), + Reviewer: uint(topPending.Reviewer), + CommitFrom: topPending.CommitFrom, + Status: consts.HitokotoStatusPending, + CreatedAt: topPending.CreatedAt, + }, PollStatus: consts.PollStatus(poll.Status), - CreatedAt: topPending.CreatedAt, }, Status: consts.PollStatus(poll.Status), Approve: poll.Accept, diff --git a/internal/logic/cache/cache.go b/internal/logic/cache/cache.go index d4c97c1..35a3b97 100644 --- a/internal/logic/cache/cache.go +++ b/internal/logic/cache/cache.go @@ -3,9 +3,6 @@ package cache import ( "context" - "github.com/gogf/gf/v2/errors/gerror" - "github.com/gogf/gf/v2/util/gconv" - "github.com/hitokoto-osc/reviewer/internal/service" "github.com/gogf/gf/v2/frame/g" @@ -62,50 +59,3 @@ func (s *sCache) RemovePrefixes(ctx context.Context, prefixes []string) error { } return err } - -func (s *sCache) ClearCacheAfterPollUpdated(ctx context.Context, userID, pollID uint, sentenceUUID string) { - if e := service.Cache().RemovePrefixes(ctx, []string{ - "SelectCache:poll:list", - "SelectCache:poll_logs:uid:" + gconv.String(userID), - // "SelectCache:user:score:records:uid:" + gconv.String(userID), - }); e != nil { - e = gerror.Wrap(e, "failed to remove cache: ") - g.Log().Error(ctx, e) - } - if e := g.DB().GetCache().Removes(ctx, g.Slice{ - "SelectCache:poll:id:" + gconv.String(pollID), - "SelectCache:poll:sentence_uuid:" + sentenceUUID, - "SelectCache:poll_log:id:" + gconv.String(pollID), - "SelectCache:poll_log:uuid:" + sentenceUUID, - "SelectCache:poll_marks:pid:" + gconv.String(pollID), - "SelectCache:user:poll:uid:" + gconv.String(userID), - "SelectCache:user:poll:unreviewed:uid:" + gconv.String(userID) + ":count", - }); e != nil { - e = gerror.Wrap(e, "failed to remove cache: ") - g.Log().Error(ctx, e) - } -} - -func (s *sCache) ClearPollListCache(ctx context.Context) { - if e := service.Cache().RemovePrefix(ctx, "SelectCache:poll:list"); e != nil { - e = gerror.Wrap(e, "failed to remove cache: ") - g.Log().Error(ctx, e) - } -} - -func (s *sCache) ClearPollUserCache(ctx context.Context, userID uint) { - if e := service.Cache().RemovePrefixes(ctx, []string{ - "SelectCache:poll_logs:uid:" + gconv.String(userID), - "SelectCache:user:score:records:uid:" + gconv.String(userID), - }); e != nil { - e = gerror.Wrap(e, "failed to remove cache: ") - g.Log().Error(ctx, e) - } - if e := g.DB().GetCache().Removes(ctx, g.Slice{ - "SelectCache:user:poll:uid:" + gconv.String(userID), - "SelectCache:user:poll:unreviewed:uid:" + gconv.String(userID) + ":count", - }); e != nil { - e = gerror.Wrap(e, "failed to remove cache: ") - g.Log().Error(ctx, e) - } -} diff --git a/internal/logic/cache/hitokoto.go b/internal/logic/cache/hitokoto.go new file mode 100644 index 0000000..ddd04f3 --- /dev/null +++ b/internal/logic/cache/hitokoto.go @@ -0,0 +1,20 @@ +package cache + +import ( + "context" + + "github.com/samber/lo" + + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" +) + +func (s *sCache) ClearCacheAfterHitokotoUpdated(ctx context.Context, uuids []string) { + prefixes := lo.Map(uuids, func(uuid string, index int) string { + return "hitokoto:uuid:" + uuid + }) + if e := s.RemovePrefixes(ctx, prefixes); e != nil { + e = gerror.Wrap(e, "failed to remove cache: ") + g.Log().Error(ctx, e) + } +} diff --git a/internal/logic/cache/poll.go b/internal/logic/cache/poll.go new file mode 100644 index 0000000..08ada01 --- /dev/null +++ b/internal/logic/cache/poll.go @@ -0,0 +1,66 @@ +package cache + +import ( + "context" + + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" + "github.com/hitokoto-osc/reviewer/internal/service" +) + +func (s *sCache) ClearCacheAfterPollUpdated(ctx context.Context, userID, pollID uint, sentenceUUID string) { + if sentenceUUID == "" { + poll, e := service.Poll().GetPollByID(ctx, int(pollID)) + if e != nil { + g.Log().Warningf(ctx, "failed to get poll by id: %s", e.Error()) + return + } + sentenceUUID = poll.SentenceUuid + } + if e := service.Cache().RemovePrefixes(ctx, []string{ + "SelectCache:poll:list", + "SelectCache:poll_logs:uid:" + gconv.String(userID), + // "SelectCache:user:score:records:uid:" + gconv.String(userID), + }); e != nil { + e = gerror.Wrap(e, "failed to remove cache: ") + g.Log().Error(ctx, e) + } + if e := g.DB().GetCache().Removes(ctx, g.Slice{ + "SelectCache:poll:id:" + gconv.String(pollID), + "SelectCache:poll:sentence_uuid:" + sentenceUUID, + "SelectCache:poll_log:id:" + gconv.String(pollID), + "SelectCache:poll_log:uuid:" + sentenceUUID, + "SelectCache:poll_marks:pid:" + gconv.String(pollID), + "SelectCache:user:poll:uid:" + gconv.String(userID), + "SelectCache:user:poll:unreviewed:uid:" + gconv.String(userID) + ":count", + "hitokoto:uuid:" + sentenceUUID, + }); e != nil { + e = gerror.Wrap(e, "failed to remove cache: ") + g.Log().Error(ctx, e) + } +} + +func (s *sCache) ClearPollListCache(ctx context.Context) { + if e := s.RemovePrefix(ctx, "SelectCache:poll:list"); e != nil { + e = gerror.Wrap(e, "failed to remove cache: ") + g.Log().Error(ctx, e) + } +} + +func (s *sCache) ClearPollUserCache(ctx context.Context, userID uint) { + if e := s.RemovePrefixes(ctx, []string{ + "SelectCache:poll_logs:uid:" + gconv.String(userID), + "SelectCache:user:score:records:uid:" + gconv.String(userID), + }); e != nil { + e = gerror.Wrap(e, "failed to remove cache: ") + g.Log().Error(ctx, e) + } + if e := g.DB().GetCache().Removes(ctx, g.Slice{ + "SelectCache:user:poll:uid:" + gconv.String(userID), + "SelectCache:user:poll:unreviewed:uid:" + gconv.String(userID) + ":count", + }); e != nil { + e = gerror.Wrap(e, "failed to remove cache: ") + g.Log().Error(ctx, e) + } +} diff --git a/internal/logic/hitokoto/convert.go b/internal/logic/hitokoto/convert.go index f59ebd2..89ed41d 100644 --- a/internal/logic/hitokoto/convert.go +++ b/internal/logic/hitokoto/convert.go @@ -12,7 +12,7 @@ import ( ) // convertToSchemaV1 将 Pending/Sentence/Refuse 数据转换为 Schema V1,此操作需要数据库操作查询投票状态 -func (s *sHitokoto) convertToSchemaV1(ctx context.Context, data any) (*model.HitokotoV1Schema, error) { +func (s *sHitokoto) convertToSchemaV1(ctx context.Context, data any) (*model.HitokotoV1WithPoll, error) { var ( sentenceUUID string sentenceStatus consts.HitokotoStatus @@ -57,34 +57,47 @@ func (s *sHitokoto) convertToSchemaV1(ctx context.Context, data any) (*model.Hit pollStatus = consts.PollStatus(poll.Status) } o := reflect.ValueOf(data).Elem() - hitokoto := &model.HitokotoV1Schema{ - ID: uint(o.FieldByName("Id").Int()), - UUID: o.FieldByName("Uuid").String(), - Hitokoto: o.FieldByName("Hitokoto").String(), - Type: consts.HitokotoType(o.FieldByName("Type").String()), - From: o.FieldByName("From").String(), - FromWho: o.FieldByName("FromWho").Interface().(*string), - Creator: o.FieldByName("Creator").String(), - CreatorUID: uint(o.FieldByName("CreatorUid").Int()), - Status: sentenceStatus, + hitokoto := &model.HitokotoV1WithPoll{ + HitokotoV1: model.HitokotoV1{ + ID: uint(o.FieldByName("Id").Int()), + UUID: o.FieldByName("Uuid").String(), + Hitokoto: o.FieldByName("Hitokoto").String(), + Type: consts.HitokotoType(o.FieldByName("Type").String()), + From: o.FieldByName("From").String(), + FromWho: o.FieldByName("FromWho").Interface().(*string), + Creator: o.FieldByName("Creator").String(), + CreatorUID: uint(o.FieldByName("CreatorUid").Int()), + Status: sentenceStatus, + CreatedAt: o.FieldByName("CreatedAt").Interface().(string), + CommitFrom: o.FieldByName("CommitFrom").String(), + }, PollStatus: pollStatus, - CreatedAt: o.FieldByName("CreatedAt").Interface().(string), } return hitokoto, nil } // ConvertPendingToSchemaV1 将 Pending 数据转换为 Schema V1 -func (s *sHitokoto) ConvertPendingToSchemaV1(ctx context.Context, pending *entity.Pending) (*model.HitokotoV1Schema, error) { +func (s *sHitokoto) ConvertPendingToSchemaV1(ctx context.Context, pending *entity.Pending) (*model.HitokotoV1WithPoll, error) { return s.convertToSchemaV1(ctx, pending) } // ConvertSentenceToSchemaV1 将 Sentence 数据转换为 Schema V1 -func (s *sHitokoto) ConvertSentenceToSchemaV1(ctx context.Context, sentence *entity.Sentence) (*model.HitokotoV1Schema, error) { +func (s *sHitokoto) ConvertSentenceToSchemaV1(ctx context.Context, sentence *entity.Sentence) (*model.HitokotoV1WithPoll, error) { return s.convertToSchemaV1(ctx, sentence) } // ConvertRefuseToSchemaV1 将 Refuse 数据转换为 Schema V1 -func (s *sHitokoto) ConvertRefuseToSchemaV1(ctx context.Context, refuse *entity.Refuse) (*model.HitokotoV1Schema, error) { +func (s *sHitokoto) ConvertRefuseToSchemaV1(ctx context.Context, refuse *entity.Refuse) (*model.HitokotoV1WithPoll, error) { return s.convertToSchemaV1(ctx, refuse) } + +var statusToPollStatus = map[consts.HitokotoStatus]consts.PollStatus{ + consts.HitokotoStatusApproved: consts.PollStatusApproved, + consts.HitokotoStatusPending: consts.PollStatusOpen, + consts.HitokotoStatusRejected: consts.PollStatusRejected, +} + +func (s *sHitokoto) ConvertStatusToPollStatus(status consts.HitokotoStatus) consts.PollStatus { + return statusToPollStatus[status] +} diff --git a/internal/logic/hitokoto/hitokoto.go b/internal/logic/hitokoto/hitokoto.go index b000f2c..7f8e4e2 100644 --- a/internal/logic/hitokoto/hitokoto.go +++ b/internal/logic/hitokoto/hitokoto.go @@ -2,6 +2,18 @@ package hitokoto import ( "context" + "strings" + + "golang.org/x/sync/errgroup" + + "github.com/samber/lo" + + "github.com/duke-git/lancet/v2/validator" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/hitokoto-osc/reviewer/internal/dao" + + "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/hitokoto-osc/reviewer/internal/consts" @@ -22,9 +34,9 @@ func New() service.IHitokoto { return &sHitokoto{} } -func (s *sHitokoto) GetHitokotoV1SchemaByUUID(ctx context.Context, uuid string) (*model.HitokotoV1Schema, error) { +func (s *sHitokoto) GetHitokotoV1SchemaByUUID(ctx context.Context, uuid string) (*model.HitokotoV1WithPoll, error) { var ( - hitokoto *model.HitokotoV1Schema + hitokoto *model.HitokotoV1WithPoll err error ) // 获取顺序 Cache -> Pending -> Sentence -> Refuse @@ -32,7 +44,7 @@ func (s *sHitokoto) GetHitokotoV1SchemaByUUID(ctx context.Context, uuid string) if err != nil { return nil, err } else if !v.IsNil() { - return v.Val().(*model.HitokotoV1Schema), nil + return v.Val().(*model.HitokotoV1WithPoll), nil } // 从 Pending 中获取 hitokotoInPending, err := s.GetPendingByUUID(ctx, uuid) @@ -82,3 +94,261 @@ func (s *sHitokoto) GetHitokotoV1SchemaByUUID(ctx context.Context, uuid string) } return hitokoto, nil } + +var queryFields = []string{ + dao.Poll.Columns().Id, + dao.Sentence.Columns().Uuid, + dao.Sentence.Columns().Hitokoto, + dao.Sentence.Columns().Type, + dao.Sentence.Columns().From, + dao.Sentence.Columns().FromWho, + dao.Sentence.Columns().Creator, + dao.Sentence.Columns().CreatorUid, + dao.Sentence.Columns().Reviewer, + dao.Sentence.Columns().CommitFrom, + dao.Sentence.Columns().CreatedAt, +} + +// GetList 获取句子列表 +// +//nolint:gocyclo +func (s *sHitokoto) GetList(ctx context.Context, in *model.GetHitokotoV1SchemaListInput) (*model.GetHitokotoV1SchemaListOutput, error) { + if in == nil { + return nil, gerror.New("参数错误") + } + var query *gdb.Model + + // 判断是否需要获取所有状态的句子 + if in.Status == nil { + query = g.DB().Ctx(ctx).Union( + dao.Sentence.Ctx(ctx).Fields(append(queryFields, `"approved" AS status`)), + dao.Pending.Ctx(ctx).Fields(append(queryFields, `"pending" AS status`)), + dao.Refuse.Ctx(ctx).Fields(append(queryFields, `"refuse" AS status`)), + ) + } else { + switch *in.Status { + case consts.HitokotoStatusApproved: + query = dao.Sentence.Ctx(ctx).Fields(append(queryFields, `"approved" AS status`)) + case consts.HitokotoStatusPending: + query = dao.Pending.Ctx(ctx).Fields(append(queryFields, `"pending" AS status`)) + case consts.HitokotoStatusRejected: + query = dao.Refuse.Ctx(ctx).Fields(append(queryFields, `"rejected" AS status`)) + default: + return nil, gerror.New("参数错误") + } + } + + if in.UUID != nil { + query = query.Where(dao.Sentence.Columns().Uuid, *in.UUID) + goto startQuery + } + + query = query.Page(in.Page, in.PageSize) + query = query.Order(`dao.Sentence.Columns().CreatedAt ` + in.Order) + + if in.Type != nil { + query = query.Where(dao.Sentence.Columns().Type, *in.Type) + } + + if in.Keywords != nil { + keywords := strings.Split(*in.Keywords, " ") + for _, keyword := range keywords { + query = query.WhereOrLike(dao.Sentence.Columns().Hitokoto, "%"+keyword+"%") + } + } + + if in.From != nil { + query = query.WhereOrLike(dao.Sentence.Columns().From, "%"+*in.From+"%") + } + + if in.FromWho != nil { + query = query.WhereOrLike(dao.Sentence.Columns().FromWho, "%"+*in.FromWho+"%") + } + + if in.Creator != nil { + query = query.WhereOrLike(dao.Sentence.Columns().Creator, "%"+*in.Creator+"%") + if validator.IsInt(*in.Creator) { + query = query.WhereOr(dao.Sentence.Columns().CreatorUid, *in.Creator) + } + } + +startQuery: + var results []model.HitokotoV1 + var total int + err := query.ScanAndCount(&results, &total, false) + if err != nil { + return nil, gerror.Wrap(err, "查询句子失败") + } + items := make([]model.HitokotoV1WithPoll, len(results)) + // 生成需要查询的 UUID 列表 + uuids := lo.FilterMap(results, func(item model.HitokotoV1, index int) (string, bool) { + // copy properties + items[index] = model.HitokotoV1WithPoll{ + HitokotoV1: item, + } + if item.Status == consts.HitokotoStatusPending { + return item.UUID, true + } + if item.Status == consts.HitokotoStatusApproved { + items[index].PollStatus = consts.PollStatusApproved + } else if item.Status == consts.HitokotoStatusRejected { + items[index].PollStatus = consts.PollStatusRejected + } + return "", false // 不需要查询 + }) + // 查询投票状态 + polls, err := service.Poll().GetPollsBySentenceUUIDs(ctx, uuids) + if err != nil { + return nil, err + } + for i := range items { + item := &items[i] + if item.Status != consts.HitokotoStatusPending { + continue // 跳过已经处理的句子 + } + items[i].PollStatus = consts.PollStatusUnknown // 默认为未知 + for j := range polls { + poll := &polls[j] + if poll.SentenceUuid == item.UUID { + item.PollStatus = consts.PollStatus(poll.Status) + break + } + } + } + out := &model.GetHitokotoV1SchemaListOutput{ + Total: total, + Page: in.Page, + PageSize: in.PageSize, + Collection: items, + } + return out, nil +} + +// Move 移动句子 +// +//nolint:gocyclo +func (s *sHitokoto) Move(ctx context.Context, sentence *model.HitokotoV1WithPoll, target consts.HitokotoStatus) error { + user := service.BizCtx().GetUser(ctx) // 获取用户 + if user == nil { + return gerror.New("未登录") + } + + if sentence == nil { + return gerror.New("句子不存在") + } + + var sentenceShouldBeRemoved *gdb.Model + switch sentence.Status { + case consts.HitokotoStatusApproved: + sentenceShouldBeRemoved = dao.Sentence.Ctx(ctx).Where(dao.Sentence.Columns().Uuid, sentence.UUID) + case consts.HitokotoStatusPending: + break + case consts.HitokotoStatusRejected: + sentenceShouldBeRemoved = dao.Refuse.Ctx(ctx).Where(dao.Refuse.Columns().Uuid, sentence.UUID) + default: + return gerror.New("未知的句子状态") + } + // 移动句子 + if sentence.Status == consts.HitokotoStatusPending { + // 只需要做裁决 + poll, e := service.Poll().GetPollBySentenceUUID(ctx, sentence.UUID) + if e != nil { + return gerror.Wrap(e, "获取投票失败") + } + e = service.Poll().DoRuling(ctx, poll, s.ConvertStatusToPollStatus(target)) + if e != nil { + return gerror.Wrap(e, "处理投票失败") + } + return nil // 已经移动完成 + } + + var targetQuery *gdb.Model + switch target { + case consts.HitokotoStatusApproved: + targetQuery = dao.Sentence.Ctx(ctx) + case consts.HitokotoStatusPending: + targetQuery = dao.Pending.Ctx(ctx) + case consts.HitokotoStatusRejected: + targetQuery = dao.Refuse.Ctx(ctx) + default: + return gerror.New("未知的句子目的状态") + } + + e := g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + result, e := sentenceShouldBeRemoved.TX(tx).Delete() // 删除原始句子 + if e != nil { + return gerror.Wrap(e, "删除原始句子失败") + } else if lo.Must(result.RowsAffected()) == 0 { + return gerror.New("删除原始句子失败") + } + // 插入新的句子 + _, e = targetQuery.TX(tx).Unscoped().Insert(g.Map{ + dao.Sentence.Columns().Id: sentence.ID, + dao.Sentence.Columns().Uuid: sentence.UUID, + dao.Sentence.Columns().Hitokoto: sentence.Hitokoto, + dao.Sentence.Columns().Type: sentence.Type, + dao.Sentence.Columns().From: sentence.From, + dao.Sentence.Columns().FromWho: sentence.FromWho, + dao.Sentence.Columns().Creator: sentence.Creator, + dao.Sentence.Columns().CreatorUid: sentence.CreatorUID, + dao.Sentence.Columns().Reviewer: user.Id, + dao.Sentence.Columns().CommitFrom: sentence.CommitFrom, + dao.Sentence.Columns().CreatedAt: sentence.CreatedAt, + }) + if e != nil { + return gerror.Wrap(e, "插入新的句子失败") + } + return nil + }) + if e != nil { + return e + } + service.Cache().ClearCacheAfterHitokotoUpdated(ctx, []string{sentence.UUID}) + return nil +} + +// Delete 使用 UUIDs 删除句子 +func (s *sHitokoto) Delete(ctx context.Context, uuids []string) error { + if len(uuids) == 0 { + return gerror.New("参数不能为空") + } + defer service.Cache().ClearCacheAfterHitokotoUpdated(ctx, uuids) + return g.DB().Ctx(ctx).Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + eg, egCtx := errgroup.WithContext(ctx) + eg.Go(func() error { + _, err := dao.Sentence.Ctx(egCtx).TX(tx).Delete(dao.Sentence.Columns().Uuid, uuids) + return err + }) + eg.Go(func() error { + _, err := dao.Pending.Ctx(egCtx).TX(tx).Delete(dao.Pending.Columns().Uuid, uuids) + return err + }) + eg.Go(func() error { + _, err := dao.Refuse.Ctx(egCtx).TX(tx).Delete(dao.Refuse.Columns().Uuid, uuids) + return err + }) + return eg.Wait() + }) +} + +func (s *sHitokoto) UpdateByUUID(ctx context.Context, sentence *model.HitokotoV1WithPoll, do *model.DoHitokotoV1Update) error { + var query *gdb.Model + switch sentence.Status { + case consts.HitokotoStatusApproved: + query = dao.Sentence.Ctx(ctx).Where(dao.Sentence.Columns().Uuid, sentence.UUID).Unscoped() + case consts.HitokotoStatusPending: + query = dao.Pending.Ctx(ctx).Where(dao.Pending.Columns().Uuid, sentence.UUID).Unscoped() + case consts.HitokotoStatusRejected: + query = dao.Refuse.Ctx(ctx).Where(dao.Refuse.Columns().Uuid, sentence.UUID).Unscoped() + } + + affectedRows, err := query.UpdateAndGetAffected(*do) + if err != nil { + return gerror.Wrap(err, "更新句子失败") + } + if affectedRows == 0 { + return gerror.New("更新句子失败:句子不存在") + } + service.Cache().ClearCacheAfterHitokotoUpdated(ctx, []string{sentence.UUID}) + return nil +} diff --git a/internal/logic/poll/poll.go b/internal/logic/poll/poll.go index e296462..5937b1a 100644 --- a/internal/logic/poll/poll.go +++ b/internal/logic/poll/poll.go @@ -59,6 +59,11 @@ func (s *sPoll) GetPollBySentenceUUID(ctx context.Context, sentenceUUID string) return } +func (s *sPoll) GetPollsBySentenceUUIDs(ctx context.Context, sentenceUUIDs []string) (polls []entity.Poll, err error) { + err = dao.Poll.Ctx(ctx).Where(dao.Poll.Columns().SentenceUuid, sentenceUUIDs).Scan(&polls) + return +} + func (s *sPoll) CountOpenedPoll(ctx context.Context) (int, error) { return dao.Poll.Ctx(ctx).Where(dao.Poll.Columns().Status, consts.PollStatusOpen).Count() } diff --git a/internal/logic/poll/ruling.go b/internal/logic/poll/ruling.go index ae92bdd..5d3bc7f 100644 --- a/internal/logic/poll/ruling.go +++ b/internal/logic/poll/ruling.go @@ -5,18 +5,17 @@ import ( "math" "github.com/gogf/gf/v2/os/gtime" + "github.com/hitokoto-osc/reviewer/internal/dao" + "github.com/hitokoto-osc/reviewer/internal/model/do" + "github.com/hitokoto-osc/reviewer/utility/time" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" - "github.com/hitokoto-osc/reviewer/internal/dao" + "github.com/hitokoto-osc/reviewer/internal/consts" "github.com/hitokoto-osc/reviewer/internal/model" - "github.com/hitokoto-osc/reviewer/internal/model/do" "github.com/hitokoto-osc/reviewer/internal/model/entity" "github.com/hitokoto-osc/reviewer/internal/service" - "github.com/hitokoto-osc/reviewer/utility/time" - - "github.com/hitokoto-osc/reviewer/internal/consts" ) func (s *sPoll) GetRulingThreshold(isExpandedPoll bool, totalTickets int) int { @@ -48,7 +47,13 @@ func (s *sPoll) DoRuling( if target != consts.PollStatusRejected && target != consts.PollStatusApproved && target != consts.PollStatusNeedModify { //nolint:lll return gerror.New("无效的投票状态") } - // 判断是否需要修改 + + reviewerUID := consts.PollRulingUserID + user := service.BizCtx().GetUser(ctx) + if user != nil { + reviewerUID = int(user.Id) // 如果是管理员投票,则使用管理员的 UID + } + var ( pollLogs []entity.PollLog err error @@ -85,7 +90,7 @@ func (s *sPoll) DoRuling( FromWho: pending.FromWho, Creator: pending.Creator, CreatorUid: pending.CreatorUid, - Reviewer: consts.PollRulingUserID, + Reviewer: reviewerUID, CommitFrom: pending.CommitFrom, Owner: pending.Owner, CreatedAt: pending.CreatedAt, @@ -100,7 +105,7 @@ func (s *sPoll) DoRuling( Creator: pending.Creator, CreatorUid: pending.CreatorUid, Owner: pending.Owner, - Reviewer: consts.PollRulingUserID, + Reviewer: reviewerUID, CommitFrom: pending.CommitFrom, CreatedAt: pending.CreatedAt, }) @@ -111,7 +116,7 @@ func (s *sPoll) DoRuling( } else { // 亟待修改 _, e = dao.Pending.Ctx(ctx).TX(tx).Where(dao.Pending.Columns().Uuid, poll.SentenceUuid).Unscoped().Update(do.Pending{ PollStatus: int(consts.PollStatusNeedModify), - Reviewer: consts.PollRulingUserID, + Reviewer: reviewerUID, }) if e != nil { return gerror.Wrapf(e, "更新句子 %s 的 pending 信息失败", poll.SentenceUuid) @@ -137,19 +142,21 @@ func (s *sPoll) DoRuling( pollElement := &model.PollElement{ ID: uint(poll.Id), SentenceUUID: poll.SentenceUuid, - Sentence: &model.HitokotoV1Schema{ - ID: uint(pending.Id), - UUID: pending.Uuid, - Hitokoto: pending.Hitokoto, - Type: consts.HitokotoType(pending.Type), - From: pending.From, - FromWho: pending.FromWho, - Creator: pending.Creator, - CreatorUID: uint(pending.CreatorUid), - Reviewer: 0, - Status: consts.HitokotoStatusPending, + Sentence: &model.HitokotoV1WithPoll{ + HitokotoV1: model.HitokotoV1{ + ID: uint(pending.Id), + UUID: pending.Uuid, + Hitokoto: pending.Hitokoto, + Type: consts.HitokotoType(pending.Type), + From: pending.From, + FromWho: pending.FromWho, + Creator: pending.Creator, + CreatorUID: uint(pending.CreatorUid), + Reviewer: uint(reviewerUID), + Status: consts.HitokotoStatusPending, // FIXME: 应该和 target 一致 + CreatedAt: pending.CreatedAt, + }, PollStatus: consts.PollStatus(poll.Status), - CreatedAt: pending.CreatedAt, }, Status: consts.PollStatus(poll.Status), Approve: poll.Accept, diff --git a/internal/model/hitokoto.go b/internal/model/hitokoto.go index 16fe9c9..4173b74 100644 --- a/internal/model/hitokoto.go +++ b/internal/model/hitokoto.go @@ -1,6 +1,8 @@ package model -import "github.com/hitokoto-osc/reviewer/internal/consts" +import ( + "github.com/hitokoto-osc/reviewer/internal/consts" +) type ModifySentenceParams struct { Hitokoto string `json:"hitokoto" dc:"句子内容" v:"length:1,1000#句子长度应该在 1-1000 之间"` @@ -10,7 +12,7 @@ type ModifySentenceParams struct { Creator string `json:"creator" dc:"句子创建者" v:"length:1,100#创建者长度应该在 1-100 之间"` } -type HitokotoV1Schema struct { +type HitokotoV1 struct { ID uint `json:"id" dc:"句子 ID"` UUID string `json:"uuid" dc:"句子 UUID"` Hitokoto string `json:"hitokoto" dc:"句子内容"` @@ -20,7 +22,40 @@ type HitokotoV1Schema struct { Creator string `json:"creator" dc:"句子创建者"` CreatorUID uint `json:"creator_uid" dc:"句子创建者 ID"` Reviewer uint `json:"reviewer" dc:"句子审查者 ID"` + CommitFrom string `json:"commit_from" dc:"句子提交来源"` Status consts.HitokotoStatus `json:"status" dc:"句子状态"` - PollStatus consts.PollStatus `json:"poll_status" dc:"句子投票状态"` CreatedAt string `json:"created_at" dc:"创建时间"` } + +type HitokotoV1WithPoll struct { + HitokotoV1 + PollStatus consts.PollStatus `json:"poll_status" dc:"句子投票状态"` +} + +type GetHitokotoV1SchemaListInput struct { + Page int `json:"page" v:"min:1" d:"1" dc:"页码"` + PageSize int `json:"page_size" v:"min:0|max:1000" d:"30" dc:"每页数量"` + Order string `json:"order" v:"in:desc,asc" d:"desc" dc:"排序方式"` + // 搜索参数 + UUID *string `json:"uuid" dc:"句子 UUID"` + Keywords *string `json:"keyword" dc:"句子内容"` + Creator *string `json:"creator" dc:"句子创建者(或 ID)"` + From *string `json:"from" dc:"句子来源"` + FromWho *string `json:"from_who" dc:"句子来源者"` + Type *string `json:"type" dc:"句子类型"` + Status *consts.HitokotoStatus `json:"status" dc:"句子状态" v:"in:pending,approved,refused"` +} + +type GetHitokotoV1SchemaListOutput Page[HitokotoV1WithPoll] + +type DoHitokotoV1Update struct { + Hitokoto *string `json:"hitokoto" dc:"句子内容" v:"required|length:1,255" in:"query"` + From *string `json:"from" dc:"句子来源" v:"required|length:1,255" in:"query"` + FromWho *string `json:"from_who" dc:"句子来源者" v:"required|length:1,255" in:"query"` + Type *string `json:"type" dc:"句子类型" v:"required|length:1,255" in:"query"` + CommitFrom *string `json:"commit_from" dc:"句子提交来源" v:"required|length:1,255" in:"query"` + Creator *string `json:"creator" dc:"句子创建者" v:"required|length:1,255" in:"query"` + CreatorUID *uint `json:"creator_uid" dc:"句子创建者 ID" v:"required|length:1,255" in:"query"` + Reviewer *uint `json:"reviewer" dc:"句子审查者 ID" v:"required|length:1,255" in:"query"` + CreatedAt *string `json:"created_at" dc:"创建时间" v:"required|length:1,255" in:"query"` +} diff --git a/internal/model/page.go b/internal/model/page.go new file mode 100644 index 0000000..06ebe74 --- /dev/null +++ b/internal/model/page.go @@ -0,0 +1,8 @@ +package model + +type Page[T any] struct { + Total int `json:"total" dc:"总数"` + Page int `json:"page" dc:"页码"` + PageSize int `json:"page_size" dc:"每页数量"` + Collection []T `json:"collection" dc:"数据"` +} diff --git a/internal/model/poll.go b/internal/model/poll.go index 2e19f5c..98b5ab5 100644 --- a/internal/model/poll.go +++ b/internal/model/poll.go @@ -32,16 +32,16 @@ type PolledData struct { } type PollElement struct { - ID uint `json:"id" dc:"投票 ID"` - SentenceUUID string `json:"sentence_uuid" dc:"句子 UUID"` - Sentence *HitokotoV1Schema `json:"sentence" dc:"句子"` - Status consts.PollStatus `json:"status" dc:"投票状态"` - Approve int `json:"approve" dc:"赞同票数"` - Reject int `json:"reject" dc:"反对票数"` - NeedModify int `json:"need_edited" dc:"需要修改票数"` - NeedCommonUserPoll int `json:"need_common_user_poll" dc:"需要普通用户投票"` - CreatedAt *time.Time `json:"created_at" dc:"创建时间"` - UpdatedAt *time.Time `json:"updated_at" dc:"更新时间"` + ID uint `json:"id" dc:"投票 ID"` + SentenceUUID string `json:"sentence_uuid" dc:"句子 UUID"` + Sentence *HitokotoV1WithPoll `json:"sentence" dc:"句子"` + Status consts.PollStatus `json:"status" dc:"投票状态"` + Approve int `json:"approve" dc:"赞同票数"` + Reject int `json:"reject" dc:"反对票数"` + NeedModify int `json:"need_edited" dc:"需要修改票数"` + NeedCommonUserPoll int `json:"need_common_user_poll" dc:"需要普通用户投票"` + CreatedAt *time.Time `json:"created_at" dc:"创建时间"` + UpdatedAt *time.Time `json:"updated_at" dc:"更新时间"` } type GetPollListInput struct { @@ -71,12 +71,7 @@ type PollListElement struct { Records []PollRecord `json:"records" dc:"评论列表"` } -type GetPollListOutput struct { - Collection []PollListElement `json:"collection" dc:"投票列表"` - Total int `json:"total" dc:"总数"` // poll 总数 - Page int `json:"page" dc:"页码"` - PageSize int `json:"page_size" dc:"每页数量"` -} +type GetPollListOutput Page[PollListElement] type PollInput struct { Method consts.PollMethod `json:"method" dc:"投票方式"` diff --git a/internal/model/poll_log.go b/internal/model/poll_log.go index 8e16682..2d9bf00 100644 --- a/internal/model/poll_log.go +++ b/internal/model/poll_log.go @@ -4,5 +4,5 @@ import "github.com/hitokoto-osc/reviewer/internal/model/entity" type PollLogWithSentence struct { entity.PollLog - Sentence *HitokotoV1Schema // Nullable + Sentence *HitokotoV1WithPoll // Nullable } diff --git a/internal/model/user.go b/internal/model/user.go index 416e6c2..9cd9f9d 100644 --- a/internal/model/user.go +++ b/internal/model/user.go @@ -50,8 +50,8 @@ type UserPollLog struct { type UserPollLogWithSentenceAndUserMarks struct { UserPollLog - Sentence *HitokotoV1Schema `json:"sentence" dc:"句子信息"` - UserMarks []int `json:"user_marks" dc:"用户投票标记"` + Sentence *HitokotoV1WithPoll `json:"sentence" dc:"句子信息"` + UserMarks []int `json:"user_marks" dc:"用户投票标记"` } type UserPollElement struct { @@ -73,30 +73,15 @@ type GetUserPollLogsInput struct { WithCache bool } -type GetUserPollLogsOutput struct { - Collection []UserPollLog `json:"collection" dc:"数据"` - Total int `json:"total" dc:"总数"` - Page int `json:"page" dc:"当前页数"` - PageSize int `json:"page_size" dc:"每页数量"` -} +type GetUserPollLogsOutput Page[UserPollLog] type GetUserPollLogsWithSentenceInput = GetUserPollLogsInput -type GetUserPollLogsWithSentenceOutput struct { - Collection []UserPollLogWithSentenceAndUserMarks `json:"collection" dc:"数据"` - Total int `json:"total" dc:"总数"` - Page int `json:"page" dc:"当前页数"` - PageSize int `json:"page_size" dc:"每页数量"` -} +type GetUserPollLogsWithSentenceOutput Page[UserPollLogWithSentenceAndUserMarks] type GetUserPollLogsWithPollResultInput = GetUserPollLogsInput -type GetUserPollLogsWithPollResultOutput struct { - Collection []UserPollElement `json:"collection" dc:"数据"` - Total int `json:"total" dc:"总数"` - Page int `json:"page" dc:"当前页数"` - PageSize int `json:"page_size" dc:"每页数量"` -} +type GetUserPollLogsWithPollResultOutput Page[UserPollElement] type UserPollScoreInput struct { UserID uint diff --git a/internal/service/cache.go b/internal/service/cache.go index 3a78247..04d2a05 100644 --- a/internal/service/cache.go +++ b/internal/service/cache.go @@ -13,6 +13,7 @@ type ( ICache interface { RemovePrefix(ctx context.Context, prefix string) error RemovePrefixes(ctx context.Context, prefixes []string) error + ClearCacheAfterHitokotoUpdated(ctx context.Context, uuids []string) ClearCacheAfterPollUpdated(ctx context.Context, userID, pollID uint, sentenceUUID string) ClearPollListCache(ctx context.Context) ClearPollUserCache(ctx context.Context, userID uint) diff --git a/internal/service/hitokoto.go b/internal/service/hitokoto.go index 5ffb0f4..dfeabfc 100644 --- a/internal/service/hitokoto.go +++ b/internal/service/hitokoto.go @@ -8,6 +8,7 @@ package service import ( "context" + "github.com/hitokoto-osc/reviewer/internal/consts" "github.com/hitokoto-osc/reviewer/internal/model" "github.com/hitokoto-osc/reviewer/internal/model/entity" ) @@ -15,12 +16,24 @@ import ( type ( IHitokoto interface { // ConvertPendingToSchemaV1 将 Pending 数据转换为 Schema V1 - ConvertPendingToSchemaV1(ctx context.Context, pending *entity.Pending) (*model.HitokotoV1Schema, error) + ConvertPendingToSchemaV1(ctx context.Context, pending *entity.Pending) (*model.HitokotoV1WithPoll, error) // ConvertSentenceToSchemaV1 将 Sentence 数据转换为 Schema V1 - ConvertSentenceToSchemaV1(ctx context.Context, sentence *entity.Sentence) (*model.HitokotoV1Schema, error) + ConvertSentenceToSchemaV1(ctx context.Context, sentence *entity.Sentence) (*model.HitokotoV1WithPoll, error) // ConvertRefuseToSchemaV1 将 Refuse 数据转换为 Schema V1 - ConvertRefuseToSchemaV1(ctx context.Context, refuse *entity.Refuse) (*model.HitokotoV1Schema, error) - GetHitokotoV1SchemaByUUID(ctx context.Context, uuid string) (*model.HitokotoV1Schema, error) + ConvertRefuseToSchemaV1(ctx context.Context, refuse *entity.Refuse) (*model.HitokotoV1WithPoll, error) + ConvertStatusToPollStatus(status consts.HitokotoStatus) consts.PollStatus + GetHitokotoV1SchemaByUUID(ctx context.Context, uuid string) (*model.HitokotoV1WithPoll, error) + // GetList 获取句子列表 + // + //nolint:gocyclo + GetList(ctx context.Context, in *model.GetHitokotoV1SchemaListInput) (*model.GetHitokotoV1SchemaListOutput, error) + // Move 移动句子 + // + //nolint:gocyclo + Move(ctx context.Context, sentence *model.HitokotoV1WithPoll, target consts.HitokotoStatus) error + // Delete 使用 UUIDs 删除句子 + Delete(ctx context.Context, uuids []string) error + UpdateByUUID(ctx context.Context, sentence *model.HitokotoV1WithPoll, do *model.DoHitokotoV1Update) error GetPendingByUUID(ctx context.Context, uuid string) (hitokoto *entity.Pending, err error) TopPendingPollNotOpen(ctx context.Context) (hitokoto *entity.Pending, err error) CountPendingPollNotOpen(ctx context.Context) (count int, err error) diff --git a/internal/service/poll.go b/internal/service/poll.go index 878811d..bc0443f 100644 --- a/internal/service/poll.go +++ b/internal/service/poll.go @@ -21,6 +21,7 @@ type ( GetPollByID(ctx context.Context, pid int) (poll *entity.Poll, err error) // GetPollBySentenceUUID 根据 Sentence UUID 获取最新发起的投票 GetPollBySentenceUUID(ctx context.Context, sentenceUUID string) (poll *entity.Poll, err error) + GetPollsBySentenceUUIDs(ctx context.Context, sentenceUUIDs []string) (polls []entity.Poll, err error) CountOpenedPoll(ctx context.Context) (int, error) CreatePollByPending(ctx context.Context, pending *entity.Pending) (*entity.Poll, error) //nolint:gocyclo