diff --git a/api/admin/v1/modify_sentence.go b/api/admin/v1/modify_sentence.go index 5c94aee..da8299a 100644 --- a/api/admin/v1/modify_sentence.go +++ b/api/admin/v1/modify_sentence.go @@ -12,5 +12,5 @@ type ModifySentenceReq struct { } type ModifySentenceRes struct { - model.PollSchema + model.PollElement } diff --git a/api/poll/v1/cancel.go b/api/poll/v1/cancel.go index 275699b..8c41564 100644 --- a/api/poll/v1/cancel.go +++ b/api/poll/v1/cancel.go @@ -11,6 +11,6 @@ type CancelPollReq struct { } type CancelPollRes struct { - model.PollSchema + model.PollElement PollData model.PollData `json:"poll_data" dc:"投票数据"` } diff --git a/api/poll/v1/detail.go b/api/poll/v1/detail.go index a1aaa99..82ecba0 100644 --- a/api/poll/v1/detail.go +++ b/api/poll/v1/detail.go @@ -11,6 +11,6 @@ type GetPollDetailReq struct { } type GetPollDetailRes struct { - model.PollSchema + model.PollElement Records []model.PollRecord `json:"logs" dc:"投票记录"` } diff --git a/api/poll/v1/get.go b/api/poll/v1/get.go index 5076854..7e786f4 100644 --- a/api/poll/v1/get.go +++ b/api/poll/v1/get.go @@ -13,6 +13,6 @@ type GetPollsReq struct { } type GetPollsRes []struct { - model.PollSchema + model.PollElement PollData model.PollData `json:"poll_data" dc:"投票数据"` } diff --git a/api/poll/v1/new.go b/api/poll/v1/new.go index cdefa2b..a36815f 100644 --- a/api/poll/v1/new.go +++ b/api/poll/v1/new.go @@ -10,6 +10,6 @@ type NewPollReq struct { } type NewPollRes struct { - model.PollSchema - RemainPending int `json:"remain_pending" dc:"剩余待处理"` + Poll model.PollElement `json:"poll" dc:"投票内容"` + RemainPending int `json:"remain_pending" dc:"剩余待处理"` } diff --git a/api/poll/v1/poll.go b/api/poll/v1/poll.go index efbed29..c0a5bd2 100644 --- a/api/poll/v1/poll.go +++ b/api/poll/v1/poll.go @@ -16,6 +16,6 @@ type PollReq struct { // PollRes 成功返回句子的投票记录 type PollRes struct { - model.PollSchema + model.PollElement PollData model.PollData `json:"poll_data" dc:"投票数据"` } diff --git a/internal/consts/poll.go b/internal/consts/poll.go index acea9b2..285d8ed 100644 --- a/internal/consts/poll.go +++ b/internal/consts/poll.go @@ -1,5 +1,9 @@ package consts +const ( + PollMaxOpenPolls = 30 // 最大开放投票数 +) + // PollMethod 审核员投票的类型 type PollMethod int diff --git a/internal/controller/poll/poll_v1_new_poll.go b/internal/controller/poll/poll_v1_new_poll.go index a13d597..dc8149b 100644 --- a/internal/controller/poll/poll_v1_new_poll.go +++ b/internal/controller/poll/poll_v1_new_poll.go @@ -3,6 +3,16 @@ package poll import ( "context" + "github.com/hitokoto-osc/reviewer/internal/model" + "github.com/hitokoto-osc/reviewer/utility/time" + + "golang.org/x/sync/errgroup" + + "github.com/hitokoto-osc/reviewer/internal/model/entity" + + "github.com/hitokoto-osc/reviewer/internal/consts" + "github.com/hitokoto-osc/reviewer/internal/service" + "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gerror" @@ -10,5 +20,59 @@ import ( ) func (c *ControllerV1) NewPoll(ctx context.Context, req *v1.NewPollReq) (res *v1.NewPollRes, err error) { - return nil, gerror.NewCode(gcode.CodeNotImplemented) + count, err := service.Poll().CountOpenedPoll(ctx) + if err != nil { + return nil, err + } + if count > consts.PollMaxOpenPolls { + return nil, gerror.NewCode(gcode.CodeOperationFailed, "待投票的句子过多,暂时无法创建新投票。") + } + + var topPending *entity.Pending + eg, egCtx := errgroup.WithContext(ctx) + eg.Go(func() error { + topPending, err = service.Hitokoto().TopPendingPollNotOpen(egCtx) + return err + }) + eg.Go(func() error { + count, err = service.Hitokoto().CountPendingPollNotOpen(egCtx) + return err + }) + if err = eg.Wait(); err != nil { + return nil, gerror.Wrap(err, "get top pending failed") + } else if topPending == nil { + return nil, gerror.NewCode(gcode.CodeOperationFailed, "当前无待投票句子。") + } + poll, err := service.Poll().CreatePollByPending(ctx, topPending) + if err != nil { + return nil, gerror.WrapCode(gcode.CodeOperationFailed, err, "创建投票失败") + } + 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, + PollStatus: consts.PollStatus(poll.Status), + CreatedAt: topPending.CreatedAt, + }, + Status: consts.PollStatus(poll.Status), + Accept: poll.Accept, + Reject: poll.Reject, + NeedModify: poll.NeedEdited, + NeedCommonUserPoll: poll.NeedUserPoll, + CreatedAt: (*time.Time)(poll.CreatedAt), + UpdatedAt: (*time.Time)(poll.UpdatedAt), + }, + RemainPending: count - 1, + } + return res, nil } diff --git a/internal/logic/hitokoto/pending.go b/internal/logic/hitokoto/pending.go index a3c9e31..2b52f55 100644 --- a/internal/logic/hitokoto/pending.go +++ b/internal/logic/hitokoto/pending.go @@ -4,6 +4,8 @@ import ( "context" "time" + "github.com/hitokoto-osc/reviewer/internal/consts" + "github.com/gogf/gf/v2/database/gdb" "github.com/hitokoto-osc/reviewer/internal/dao" "github.com/hitokoto-osc/reviewer/internal/model/do" @@ -18,3 +20,18 @@ func (s *sHitokoto) GetPendingByUUID(ctx context.Context, uuid string) (hitokoto }).Where(do.Pending{Uuid: uuid}).Scan(&hitokoto) return } + +func (s *sHitokoto) TopPendingPollNotOpen(ctx context.Context) (hitokoto *entity.Pending, err error) { + err = dao.Pending.Ctx(ctx). + Where(dao.Pending.Columns().PollStatus, consts.PollStatusNotOpen). + OrderAsc(dao.Pending.Columns().CreatedAt). + Scan(&hitokoto) + return +} + +func (s *sHitokoto) CountPendingPollNotOpen(ctx context.Context) (count int, err error) { + count, err = dao.Pending.Ctx(ctx). + Where(dao.Pending.Columns().PollStatus, consts.PollStatusNotOpen). + Count() + return +} diff --git a/internal/logic/poll/poll.go b/internal/logic/poll/poll.go index c1b3f63..900bdc3 100644 --- a/internal/logic/poll/poll.go +++ b/internal/logic/poll/poll.go @@ -4,6 +4,16 @@ import ( "context" "time" + "github.com/gogf/gf/v2/os/gtime" + + "github.com/google/uuid" + + "github.com/gogf/gf/v2/frame/g" + + "github.com/gogf/gf/v2/errors/gerror" + + "github.com/hitokoto-osc/reviewer/internal/consts" + "github.com/hitokoto-osc/reviewer/internal/service" "github.com/gogf/gf/v2/database/gdb" @@ -22,11 +32,64 @@ func New() service.IPoll { return &sPoll{} } -func (s *sPoll) GetPollBySentenceUUID(ctx context.Context, uuid string) (poll *entity.Poll, err error) { +func (s *sPoll) GetPollBySentenceUUID(ctx context.Context, uuidStr string) (poll *entity.Poll, err error) { err = dao.Poll.Ctx(ctx).Cache(gdb.CacheOption{ Duration: time.Minute * 10, - Name: "poll:uuid:" + uuid, + Name: "poll:uuid:" + uuidStr, Force: false, - }).Where(do.Poll{SentenceUuid: uuid}).Scan(&poll) + }).Where(do.Poll{SentenceUuid: uuidStr}).Scan(&poll) return } + +func (s *sPoll) CountOpenedPoll(ctx context.Context) (int, error) { + return dao.Poll.Ctx(ctx).Where(dao.Poll.Columns().Status, consts.PollStatusOpen).Count() +} + +func (s *sPoll) CreatePollByPending(ctx context.Context, pending *entity.Pending) (*entity.Poll, error) { + if pending == nil { + return nil, gerror.New("pending is nil") + } + var poll *entity.Poll + err := g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + if pending.Uuid == "" { + uuidInstance, err := uuid.NewRandom() + if err != nil { + return nil + } + pending.Uuid = uuidInstance.String() + } + // 修改 pending 状态 + affectedRows, err := dao.Pending.Ctx(ctx).TX(tx).Where(dao.Pending.Columns().Id, pending.Id).Data(g.Map{ + dao.Pending.Columns().PollStatus: consts.PollStatusOpen, + dao.Pending.Columns().Uuid: pending.Uuid, + }).UpdateAndGetAffected() + if err != nil { + return err + } else if affectedRows == 0 { + return gerror.New("pending affectedRows is 0") + } + // 创建投票 + poll = &entity.Poll{ + SentenceUuid: pending.Uuid, + Status: int(consts.PollStatusOpen), + IsExpandedPoll: 0, + Accept: 0, + Reject: 0, + NeedEdited: 0, + NeedUserPoll: 0, + PendingId: pending.Id, + CreatedAt: gtime.Now(), + UpdatedAt: gtime.Now(), + } + insertedID, err := dao.Poll.Ctx(ctx).TX(tx).Data(poll).InsertAndGetId() + if err != nil { + return err + } + poll.Id = int(insertedID) + return nil + }) + if err != nil { + return nil, err + } + return poll, nil +} diff --git a/internal/logic/user/poll_log.go b/internal/logic/user/poll_log.go index a1fdbd7..e703007 100644 --- a/internal/logic/user/poll_log.go +++ b/internal/logic/user/poll_log.go @@ -169,7 +169,7 @@ func (s *sUser) GetUserPollLogsWithPollResult( if e != nil { return e } - collections[index].PollInfo = &model.PollSchema{ + collections[index].PollInfo = &model.PollElement{ SentenceUUID: poll.SentenceUuid, Status: consts.PollStatus(poll.Status), Accept: poll.Accept, diff --git a/internal/model/poll.go b/internal/model/poll.go index bd3dd02..623ca42 100644 --- a/internal/model/poll.go +++ b/internal/model/poll.go @@ -28,8 +28,9 @@ type PollData struct { Method consts.PollMethod `json:"method" dc:"投票方式"` } -type PollSchema struct { +type PollElement struct { SentenceUUID string `json:"sentence_uuid" dc:"句子 UUID"` + Sentence HitokotoV1Schema `json:"sentence" dc:"句子"` Status consts.PollStatus `json:"status" dc:"投票状态"` Accept int `json:"accept" dc:"赞同票数"` Reject int `json:"reject" dc:"反对票数"` diff --git a/internal/model/user.go b/internal/model/user.go index 40273eb..fa3c330 100644 --- a/internal/model/user.go +++ b/internal/model/user.go @@ -45,8 +45,8 @@ type UserPollLogWithSentence struct { type UserPollElement struct { UserPollLogWithSentence - PollInfo *PollSchema `json:"poll_info" dc:"投票信息"` - Marks []int `json:"marks" dc:"投票标记"` + PollInfo *PollElement `json:"poll_info" dc:"投票信息"` + Marks []int `json:"marks" dc:"投票标记"` } type UserPollResult struct { diff --git a/internal/service/hitokoto.go b/internal/service/hitokoto.go index 9bcc2d6..5ffb0f4 100644 --- a/internal/service/hitokoto.go +++ b/internal/service/hitokoto.go @@ -22,6 +22,8 @@ type ( ConvertRefuseToSchemaV1(ctx context.Context, refuse *entity.Refuse) (*model.HitokotoV1Schema, error) GetHitokotoV1SchemaByUUID(ctx context.Context, uuid string) (*model.HitokotoV1Schema, 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) GetRefuseByUUID(ctx context.Context, uuid string) (hitokoto *entity.Refuse, err error) GetSentenceByUUID(ctx context.Context, uuid string) (hitokoto *entity.Sentence, err error) } diff --git a/internal/service/poll.go b/internal/service/poll.go index e875d9a..bdf1917 100644 --- a/internal/service/poll.go +++ b/internal/service/poll.go @@ -13,7 +13,9 @@ import ( type ( IPoll interface { - GetPollBySentenceUUID(ctx context.Context, uuid string) (poll *entity.Poll, err error) + GetPollBySentenceUUID(ctx context.Context, uuidStr string) (poll *entity.Poll, err error) + CountOpenedPoll(ctx context.Context) (int, error) + CreatePollByPending(ctx context.Context, pending *entity.Pending) (*entity.Poll, error) GetPollMarkLabels(ctx context.Context) ([]entity.PollMark, error) // GetPollMarksBySentenceUUID 获取指定投票的标签列表(不带用户信息) GetPollMarksBySentenceUUID(ctx context.Context, uuid string) ([]int, error)