Skip to content

Commit

Permalink
支持 QuestionSet 中的 Candidate 接口
Browse files Browse the repository at this point in the history
  • Loading branch information
flycash committed Jul 27, 2024
1 parent 9ee358e commit 6f5d5e2
Show file tree
Hide file tree
Showing 13 changed files with 271 additions and 24 deletions.
74 changes: 74 additions & 0 deletions internal/question/internal/integration/admin_set_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,80 @@ func (s *AdminSetHandlerTestSuite) TestQuestionSet_Save() {
}
}

func (s *AdminSetHandlerTestSuite) TestQuestionSet_Candidates() {
testCases := []struct {
name string

before func(t *testing.T)
req web.CandidateReq

wantCode int
wantResp test.Result[web.QuestionList]
}{
{
name: "获取成功",
before: func(t *testing.T) {
// 准备数据
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
// 创建一个空题集
id, err := s.questionSetDAO.Create(ctx, dao.QuestionSet{
Id: 1,
Uid: uid,
Title: "Go",
Description: "Go题集",
Biz: "roadmap",
BizId: 2,
Utime: 123,
})
require.NoError(t, err)
// 添加问题
questions := []dao.Question{
s.buildQuestion(1),
s.buildQuestion(2),
s.buildQuestion(3),
s.buildQuestion(4),
s.buildQuestion(5),
s.buildQuestion(6),
}
err = s.db.WithContext(ctx).Create(&questions).Error
require.NoError(t, err)
qids := []int64{1, 2, 3}
require.NoError(t, s.questionSetDAO.UpdateQuestionsByID(ctx, id, qids))
},
req: web.CandidateReq{
QSID: 1,
Offset: 1,
Limit: 2,
},
wantCode: 200,
wantResp: test.Result[web.QuestionList]{
Data: web.QuestionList{
Total: 3,
Questions: []web.Question{
s.buildWebQuestion(5),
s.buildWebQuestion(4),
},
},
},
},
}

for _, tc := range testCases {
s.T().Run(tc.name, func(t *testing.T) {
tc.before(t)
req, err := http.NewRequest(http.MethodPost,
"/question-sets/candidate", iox.NewJSONReader(tc.req))
req.Header.Set("content-type", "application/json")
require.NoError(t, err)
recorder := test.NewJSONResponseRecorder[web.QuestionList]()
s.server.ServeHTTP(recorder, req)
require.Equal(t, tc.wantCode, recorder.Code)
assert.Equal(t, tc.wantResp, recorder.MustScan())
})
}
}

func (s *AdminSetHandlerTestSuite) TestQuestionSet_UpdateQuestions() {
testCases := []struct {
name string
Expand Down
26 changes: 26 additions & 0 deletions internal/question/internal/integration/base_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
"fmt"
"testing"

"github.com/ecodeclub/webook/internal/question/internal/domain"

"github.com/ecodeclub/webook/internal/question/internal/web"

"github.com/ecodeclub/webook/internal/interactive"
Expand Down Expand Up @@ -92,6 +94,30 @@ func (s *BaseTestSuite) mockInteractive(biz string, id int64) interactive.Intera
}
}

func (s *BaseTestSuite) buildQuestion(id int64) dao.Question {
return dao.Question{
Id: id,
Uid: uid,
Biz: domain.DefaultBiz,
BizId: id,
Title: fmt.Sprintf("标题%d", id),
Content: fmt.Sprintf("内容%d", id),
Ctime: 123 + id,
Utime: 123 + id,
}
}

func (s *BaseTestSuite) buildWebQuestion(id int64) web.Question {
return web.Question{
Id: id,
Biz: domain.DefaultBiz,
BizId: id,
Title: fmt.Sprintf("标题%d", id),
Content: fmt.Sprintf("内容%d", id),
Utime: 123 + id,
}
}

func (s *BaseTestSuite) buildDAOAnswerEle(
qid int64,
idx int,
Expand Down
6 changes: 3 additions & 3 deletions internal/question/internal/integration/startup/wire_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions internal/question/internal/repository/dao/question.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,31 @@ type QuestionDAO interface {
PubCount(ctx context.Context) (int64, error)
GetPubByID(ctx context.Context, qid int64) (PublishQuestion, []PublishAnswerElement, error)
GetPubByIDs(ctx context.Context, qids []int64) ([]PublishQuestion, error)
NotInTotal(ctx context.Context, ids []int64) (int64, error)
NotIn(ctx context.Context, ids []int64, offset int, limit int) ([]Question, error)
}

type GORMQuestionDAO struct {
db *egorm.Component
}

func (g *GORMQuestionDAO) NotInTotal(ctx context.Context, ids []int64) (int64, error) {
var res int64
err := g.db.WithContext(ctx).
Model(&Question{}).
Where("id NOT IN ?", ids).Count(&res).Error
return res, err
}

func (g *GORMQuestionDAO) NotIn(ctx context.Context, ids []int64, offset int, limit int) ([]Question, error) {
var res []Question
err := g.db.WithContext(ctx).
Model(&Question{}).
Where("id NOT IN ?", ids).Order("utime DESC").
Offset(offset).Limit(limit).Find(&res).Error
return res, err
}

func (g *GORMQuestionDAO) GetPubByIDs(ctx context.Context, qids []int64) ([]PublishQuestion, error) {
var qs []PublishQuestion
db := g.db.WithContext(ctx)
Expand Down
27 changes: 27 additions & 0 deletions internal/question/internal/repository/question.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"context"
"time"

"golang.org/x/sync/errgroup"

"github.com/ecodeclub/ekit/sqlx"

"github.com/ecodeclub/ekit/slice"
Expand All @@ -42,6 +44,8 @@ type Repository interface {
GetById(ctx context.Context, qid int64) (domain.Question, error)
GetPubByID(ctx context.Context, qid int64) (domain.Question, error)
GetPubByIDs(ctx context.Context, ids []int64) ([]domain.Question, error)
// ExcludeQuestions 分页接口,不含这些 id 的问题
ExcludeQuestions(ctx context.Context, ids []int64, offset int, limit int) ([]domain.Question, int64, error)
}

// CachedRepository 支持缓存的 repository 实现
Expand Down Expand Up @@ -71,6 +75,29 @@ func (c *CachedRepository) GetPubByID(ctx context.Context, qid int64) (domain.Qu
return c.toDomainWithAnswer(dao.Question(data), eles), nil
}

func (c *CachedRepository) ExcludeQuestions(ctx context.Context, ids []int64, offset int, limit int) ([]domain.Question, int64, error) {
var (
eg errgroup.Group
cnt int64
data []dao.Question
)
eg.Go(func() error {
var err error
cnt, err = c.dao.NotInTotal(ctx, ids)
return err
})

eg.Go(func() error {
var err error
data, err = c.dao.NotIn(ctx, ids, offset, limit)
return err
})
err := eg.Wait()
return slice.Map(data, func(idx int, src dao.Question) domain.Question {
return c.toDomain(src)
}), cnt, err
}

func (c *CachedRepository) GetById(ctx context.Context, qid int64) (domain.Question, error) {
data, eles, err := c.dao.GetByID(ctx, qid)
if err != nil {
Expand Down
17 changes: 17 additions & 0 deletions internal/question/internal/service/question_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"context"
"time"

"github.com/ecodeclub/ekit/slice"

"github.com/ecodeclub/webook/internal/question/internal/event"
"github.com/gotomicro/ego/core/elog"

Expand All @@ -37,16 +39,29 @@ type QuestionSetService interface {
Detail(ctx context.Context, id int64) (domain.QuestionSet, error)
GetByIds(ctx context.Context, ids []int64) ([]domain.QuestionSet, error)
DetailByBiz(ctx context.Context, biz string, bizId int64) (domain.QuestionSet, error)
GetCandidates(ctx context.Context, id int64, offset int, limit int) ([]domain.Question, int64, error)
}

type questionSetService struct {
repo repository.QuestionSetRepository
queRepo repository.Repository
producer event.SyncDataToSearchEventProducer
intrProducer event.InteractiveEventProducer
logger *elog.Component
syncTimeout time.Duration
}

func (q *questionSetService) GetCandidates(ctx context.Context, id int64, offset int, limit int) ([]domain.Question, int64, error) {
qs, err := q.repo.GetByID(ctx, id)
if err != nil {
return nil, 0, err
}
qids := slice.Map(qs.Questions, func(idx int, src domain.Question) int64 {
return src.Id
})
return q.queRepo.ExcludeQuestions(ctx, qids, offset, limit)
}

func (q *questionSetService) DetailByBiz(ctx context.Context, biz string, bizId int64) (domain.QuestionSet, error) {
return q.repo.GetByBiz(ctx, biz, bizId)
}
Expand Down Expand Up @@ -140,10 +155,12 @@ func (q *questionSetService) syncQuestionSet(id int64) {
}

func NewQuestionSetService(repo repository.QuestionSetRepository,
queRepo repository.Repository,
intrProducer event.InteractiveEventProducer,
producer event.SyncDataToSearchEventProducer) QuestionSetService {
return &questionSetService{
repo: repo,
queRepo: queRepo,
producer: producer,
intrProducer: intrProducer,
logger: elog.DefaultLogger,
Expand Down
41 changes: 41 additions & 0 deletions internal/question/internal/web/admin_base.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2023 ecodeclub
//
// Licensed 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 web

import (
"github.com/ecodeclub/ekit/slice"
"github.com/ecodeclub/webook/internal/question/internal/domain"
)

type AdminBaseHandler struct {
}

func (h AdminBaseHandler) toQuestionList(data []domain.Question, cnt int64) QuestionList {
return QuestionList{
Total: cnt,
Questions: slice.Map(data, func(idx int, src domain.Question) Question {
return Question{
Id: src.Id,
Title: src.Title,
Content: src.Content,
Labels: src.Labels,
Biz: src.Biz,
BizId: src.BizId,
Status: src.Status.ToUint8(),
Utime: src.Utime.UnixMilli(),
}
}),
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,16 @@
package web

import (
"github.com/ecodeclub/ekit/slice"
"github.com/ecodeclub/ginx"
"github.com/ecodeclub/ginx/session"
"github.com/ecodeclub/webook/internal/interactive"
"github.com/ecodeclub/webook/internal/question/internal/domain"
"github.com/ecodeclub/webook/internal/question/internal/service"
"github.com/gin-gonic/gin"
)

// AdminHandler 制作库
type AdminHandler struct {
AdminBaseHandler
svc service.Service
}

Expand Down Expand Up @@ -78,7 +77,6 @@ func (h *AdminHandler) Publish(ctx *ginx.Context, req SaveReq, sess session.Sess
}

func (h *AdminHandler) List(ctx *ginx.Context, req Page) (ginx.Result, error) {
// 制作库不需要统计总数
data, cnt, err := h.svc.List(ctx, req.Offset, req.Limit)
if err != nil {
return systemErrorResult, err
Expand All @@ -88,22 +86,6 @@ func (h *AdminHandler) List(ctx *ginx.Context, req Page) (ginx.Result, error) {
}, nil
}

func (h *AdminHandler) toQuestionList(data []domain.Question, cnt int64) QuestionList {
return QuestionList{
Total: cnt,
Questions: slice.Map(data, func(idx int, src domain.Question) Question {
return Question{
Id: src.Id,
Title: src.Title,
Content: src.Content,
Labels: src.Labels,
Status: src.Status.ToUint8(),
Utime: src.Utime.UnixMilli(),
}
}),
}
}

func (h *AdminHandler) Detail(ctx *ginx.Context, req Qid) (ginx.Result, error) {
detail, err := h.svc.Detail(ctx, req.Qid)
if err != nil {
Expand Down
12 changes: 12 additions & 0 deletions internal/question/internal/web/admin_question_set_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
)

type AdminQuestionSetHandler struct {
AdminBaseHandler
svc service.QuestionSetService
}

Expand All @@ -40,6 +41,17 @@ func (h *AdminQuestionSetHandler) PrivateRoutes(server *gin.Engine) {
g.POST("/questions/save", ginx.BS[UpdateQuestions](h.UpdateQuestions))
g.POST("/list", ginx.B[Page](h.ListQuestionSets))
g.POST("/detail", ginx.B(h.RetrieveQuestionSetDetail))
g.POST("/candidate", ginx.B[CandidateReq](h.Candidate))
}

func (h *AdminQuestionSetHandler) Candidate(ctx *ginx.Context, req CandidateReq) (ginx.Result, error) {
data, cnt, err := h.svc.GetCandidates(ctx, req.QSID, req.Offset, req.Limit)
if err != nil {
return systemErrorResult, err
}
return ginx.Result{
Data: h.toQuestionList(data, cnt),
}, nil
}

// UpdateQuestions 整体更新题集中的所有问题 覆盖式的 前端传递过来的问题集合就是题集中最终的问题集合
Expand Down
Loading

0 comments on commit 6f5d5e2

Please sign in to comment.