From c0cc25b1aea627a3ca3c488a372a633e5db98f07 Mon Sep 17 00:00:00 2001 From: everpcpc Date: Sat, 18 May 2024 02:56:50 +0000 Subject: [PATCH] feat: add get & list person collect --- internal/collections/domain.go | 19 +- internal/collections/infra/mysql_repo.go | 52 ++++- internal/collections/infra/mysql_repo_test.go | 90 ++++++++- internal/mocks/CollectionRepo.go | 186 ++++++++++++++---- web/handler/character/collect.go | 8 +- web/handler/person/collect.go | 8 +- web/handler/user/get_character_collection.go | 81 ++++++++ web/handler/user/get_person_collection.go | 77 ++++++++ .../user/list_character_collections.go | 105 ++++++++++ web/handler/user/list_person_collections.go | 104 ++++++++++ web/handler/user/user.go | 38 ++-- web/res/collection.go | 38 ++-- web/routes.go | 8 +- 13 files changed, 725 insertions(+), 89 deletions(-) create mode 100644 web/handler/user/get_character_collection.go create mode 100644 web/handler/user/get_person_collection.go create mode 100644 web/handler/user/list_character_collections.go create mode 100644 web/handler/user/list_person_collections.go diff --git a/internal/collections/domain.go b/internal/collections/domain.go index 5e3792245..315172987 100644 --- a/internal/collections/domain.go +++ b/internal/collections/domain.go @@ -71,20 +71,33 @@ type Repo interface { //nolint:interfacebloat at time.Time, ) (collection.UserSubjectEpisodesCollection, error) - GetPersonCollect( + GetPersonCollection( ctx context.Context, userID model.UserID, cat collection.PersonCollectCategory, targetID model.PersonID, ) (collection.UserPersonCollection, error) - AddPersonCollect( + AddPersonCollection( ctx context.Context, userID model.UserID, cat collection.PersonCollectCategory, targetID model.PersonID, ) error - RemovePersonCollect( + RemovePersonCollection( ctx context.Context, userID model.UserID, cat collection.PersonCollectCategory, targetID model.PersonID, ) error + + CountPersonCollections( + ctx context.Context, + userID model.UserID, + cat collection.PersonCollectCategory, + ) (int64, error) + + ListPersonCollection( + ctx context.Context, + userID model.UserID, + cat collection.PersonCollectCategory, + limit, offset int, + ) ([]collection.UserPersonCollection, error) } type Update struct { diff --git a/internal/collections/infra/mysql_repo.go b/internal/collections/infra/mysql_repo.go index 7ffb3b291..d00846704 100644 --- a/internal/collections/infra/mysql_repo.go +++ b/internal/collections/infra/mysql_repo.go @@ -461,7 +461,7 @@ func (r mysqlRepo) updateCollectionTime(obj *dao.SubjectCollection, return nil } -func (r mysqlRepo) GetPersonCollect( +func (r mysqlRepo) GetPersonCollection( ctx context.Context, userID model.UserID, cat collection.PersonCollectCategory, targetID model.PersonID, ) (collection.UserPersonCollection, error) { @@ -484,7 +484,7 @@ func (r mysqlRepo) GetPersonCollect( }, nil } -func (r mysqlRepo) AddPersonCollect( +func (r mysqlRepo) AddPersonCollection( ctx context.Context, userID model.UserID, cat collection.PersonCollectCategory, targetID model.PersonID, ) error { @@ -503,7 +503,7 @@ func (r mysqlRepo) AddPersonCollect( return nil } -func (r mysqlRepo) RemovePersonCollect( +func (r mysqlRepo) RemovePersonCollection( ctx context.Context, userID model.UserID, cat collection.PersonCollectCategory, targetID model.PersonID, ) error { @@ -518,6 +518,52 @@ func (r mysqlRepo) RemovePersonCollect( return nil } +func (r mysqlRepo) CountPersonCollections( + ctx context.Context, + userID model.UserID, + cat collection.PersonCollectCategory, +) (int64, error) { + q := r.q.PersonCollect.WithContext(ctx). + Where(r.q.PersonCollect.UserID.Eq(userID), r.q.PersonCollect.Category.Eq(string(cat))) + + c, err := q.Count() + if err != nil { + return 0, errgo.Wrap(err, "dal") + } + + return c, nil +} + +func (r mysqlRepo) ListPersonCollection( + ctx context.Context, + userID model.UserID, + cat collection.PersonCollectCategory, + limit, offset int, +) ([]collection.UserPersonCollection, error) { + q := r.q.PersonCollect.WithContext(ctx). + Order(r.q.PersonCollect.CreatedTime.Desc()). + Where(r.q.PersonCollect.UserID.Eq(userID), r.q.PersonCollect.Category.Eq(string(cat))).Limit(limit).Offset(offset) + + collections, err := q.Find() + if err != nil { + r.log.Error("unexpected error happened", zap.Error(err)) + return nil, errgo.Wrap(err, "dal") + } + + var results = make([]collection.UserPersonCollection, len(collections)) + for i, c := range collections { + results[i] = collection.UserPersonCollection{ + ID: c.ID, + Category: c.Category, + TargetID: c.TargetID, + UserID: c.UserID, + CreatedAt: time.Unix(int64(c.CreatedTime), 0), + } + } + + return results, nil +} + func (r mysqlRepo) UpdateEpisodeCollection( ctx context.Context, userID model.UserID, diff --git a/internal/collections/infra/mysql_repo_test.go b/internal/collections/infra/mysql_repo_test.go index 4fbd53d5b..df4b89bd8 100644 --- a/internal/collections/infra/mysql_repo_test.go +++ b/internal/collections/infra/mysql_repo_test.go @@ -533,7 +533,7 @@ func TestMysqlRepo_GetPersonCollect(t *testing.T) { }) require.NoError(t, err) - r, err := repo.GetPersonCollect(context.Background(), uid, cat, mid) + r, err := repo.GetPersonCollection(context.Background(), uid, cat, mid) require.NoError(t, err) require.Equal(t, uid, r.UserID) require.Equal(t, mid, r.TargetID) @@ -555,7 +555,7 @@ func TestMysqlRepo_AddPersonCollect(t *testing.T) { require.NoError(t, err) }) - err := repo.AddPersonCollect(context.Background(), uid, cat, mid) + err := repo.AddPersonCollection(context.Background(), uid, cat, mid) require.NoError(t, err) r, err := table.WithContext(context.TODO()).Where(table.UserID.Eq(uid)).Take() @@ -590,9 +590,93 @@ func TestMysqlRepo_RemovePersonCollect(t *testing.T) { require.NoError(t, err) require.NotZero(t, r.ID) - err = repo.RemovePersonCollect(context.Background(), uid, cat, mid) + err = repo.RemovePersonCollection(context.Background(), uid, cat, mid) require.NoError(t, err) _, err = table.WithContext(context.TODO()).Where(table.UserID.Eq(uid)).Take() require.ErrorIs(t, err, gorm.ErrRecordNotFound) } + +func TestMysqlRepo_CountPersonCollections(t *testing.T) { + t.Parallel() + test.RequireEnv(t, test.EnvMysql) + + const uid model.UserID = 41000 + const cat = "prsn" + + repo, q := getRepo(t) + test.RunAndCleanup(t, func() { + _, err := q.PersonCollect. + WithContext(context.Background()). + Where(q.PersonCollect.UserID.Eq(uid)). + Delete() + require.NoError(t, err) + }) + + for i := 0; i < 5; i++ { + err := q.PersonCollect. + WithContext(context.Background()). + Create(&dao.PersonCollect{ + UserID: uid, + TargetID: model.PersonID(i + 100), + Category: cat, + CreatedTime: uint32(time.Now().Unix()), + }) + require.NoError(t, err) + } + + count, err := repo.CountPersonCollections(context.Background(), uid, cat) + require.NoError(t, err) + require.EqualValues(t, 5, count) +} + +func TestMysqlRepo_ListPersonCollection(t *testing.T) { + t.Parallel() + test.RequireEnv(t, test.EnvMysql) + + const uid model.UserID = 42000 + const cat = "prsn" + + repo, q := getRepo(t) + + var err error + test.RunAndCleanup(t, func() { + _, err = q.PersonCollect. + WithContext(context.Background()). + Where(q.PersonCollect.UserID.Eq(uid)). + Delete() + require.NoError(t, err) + }) + + data, err := repo.ListPersonCollection(context.Background(), uid, collection.PersonCollectCategory(cat), 5, 0) + require.NoError(t, err) + require.Len(t, data, 0) + + for i := 0; i < 5; i++ { + err = q.PersonCollect. + WithContext(context.Background()). + Create(&dao.PersonCollect{ + UserID: uid, + TargetID: model.PersonID(i + 100), + Category: cat, + CreatedTime: uint32(time.Now().Unix()), + }) + require.NoError(t, err) + } + + for i := 0; i < 2; i++ { + err = q.PersonCollect. + WithContext(context.Background()). + Create(&dao.PersonCollect{ + UserID: uid, + TargetID: model.PersonID(i + 200), + Category: cat, + CreatedTime: uint32(time.Now().Unix()), + }) + require.NoError(t, err) + } + + data, err = repo.ListPersonCollection(context.Background(), uid, collection.PersonCollectCategory(cat), 5, 0) + require.NoError(t, err) + require.Len(t, data, 5) +} diff --git a/internal/mocks/CollectionRepo.go b/internal/mocks/CollectionRepo.go index bad2d3fc4..3fbafd138 100644 --- a/internal/mocks/CollectionRepo.go +++ b/internal/mocks/CollectionRepo.go @@ -30,12 +30,12 @@ func (_m *CollectionRepo) EXPECT() *CollectionRepo_Expecter { return &CollectionRepo_Expecter{mock: &_m.Mock} } -// AddPersonCollect provides a mock function with given fields: ctx, userID, cat, targetID -func (_m *CollectionRepo) AddPersonCollect(ctx context.Context, userID uint32, cat collection.PersonCollectCategory, targetID uint32) error { +// AddPersonCollection provides a mock function with given fields: ctx, userID, cat, targetID +func (_m *CollectionRepo) AddPersonCollection(ctx context.Context, userID uint32, cat collection.PersonCollectCategory, targetID uint32) error { ret := _m.Called(ctx, userID, cat, targetID) if len(ret) == 0 { - panic("no return value specified for AddPersonCollect") + panic("no return value specified for AddPersonCollection") } var r0 error @@ -48,33 +48,91 @@ func (_m *CollectionRepo) AddPersonCollect(ctx context.Context, userID uint32, c return r0 } -// CollectionRepo_AddPersonCollect_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddPersonCollect' -type CollectionRepo_AddPersonCollect_Call struct { +// CollectionRepo_AddPersonCollection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddPersonCollection' +type CollectionRepo_AddPersonCollection_Call struct { *mock.Call } -// AddPersonCollect is a helper method to define mock.On call +// AddPersonCollection is a helper method to define mock.On call // - ctx context.Context // - userID uint32 // - cat collection.PersonCollectCategory // - targetID uint32 -func (_e *CollectionRepo_Expecter) AddPersonCollect(ctx interface{}, userID interface{}, cat interface{}, targetID interface{}) *CollectionRepo_AddPersonCollect_Call { - return &CollectionRepo_AddPersonCollect_Call{Call: _e.mock.On("AddPersonCollect", ctx, userID, cat, targetID)} +func (_e *CollectionRepo_Expecter) AddPersonCollection(ctx interface{}, userID interface{}, cat interface{}, targetID interface{}) *CollectionRepo_AddPersonCollection_Call { + return &CollectionRepo_AddPersonCollection_Call{Call: _e.mock.On("AddPersonCollection", ctx, userID, cat, targetID)} } -func (_c *CollectionRepo_AddPersonCollect_Call) Run(run func(ctx context.Context, userID uint32, cat collection.PersonCollectCategory, targetID uint32)) *CollectionRepo_AddPersonCollect_Call { +func (_c *CollectionRepo_AddPersonCollection_Call) Run(run func(ctx context.Context, userID uint32, cat collection.PersonCollectCategory, targetID uint32)) *CollectionRepo_AddPersonCollection_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(context.Context), args[1].(uint32), args[2].(collection.PersonCollectCategory), args[3].(uint32)) }) return _c } -func (_c *CollectionRepo_AddPersonCollect_Call) Return(_a0 error) *CollectionRepo_AddPersonCollect_Call { +func (_c *CollectionRepo_AddPersonCollection_Call) Return(_a0 error) *CollectionRepo_AddPersonCollection_Call { _c.Call.Return(_a0) return _c } -func (_c *CollectionRepo_AddPersonCollect_Call) RunAndReturn(run func(context.Context, uint32, collection.PersonCollectCategory, uint32) error) *CollectionRepo_AddPersonCollect_Call { +func (_c *CollectionRepo_AddPersonCollection_Call) RunAndReturn(run func(context.Context, uint32, collection.PersonCollectCategory, uint32) error) *CollectionRepo_AddPersonCollection_Call { + _c.Call.Return(run) + return _c +} + +// CountPersonCollections provides a mock function with given fields: ctx, userID, cat +func (_m *CollectionRepo) CountPersonCollections(ctx context.Context, userID uint32, cat collection.PersonCollectCategory) (int64, error) { + ret := _m.Called(ctx, userID, cat) + + if len(ret) == 0 { + panic("no return value specified for CountPersonCollections") + } + + var r0 int64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint32, collection.PersonCollectCategory) (int64, error)); ok { + return rf(ctx, userID, cat) + } + if rf, ok := ret.Get(0).(func(context.Context, uint32, collection.PersonCollectCategory) int64); ok { + r0 = rf(ctx, userID, cat) + } else { + r0 = ret.Get(0).(int64) + } + + if rf, ok := ret.Get(1).(func(context.Context, uint32, collection.PersonCollectCategory) error); ok { + r1 = rf(ctx, userID, cat) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CollectionRepo_CountPersonCollections_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CountPersonCollections' +type CollectionRepo_CountPersonCollections_Call struct { + *mock.Call +} + +// CountPersonCollections is a helper method to define mock.On call +// - ctx context.Context +// - userID uint32 +// - cat collection.PersonCollectCategory +func (_e *CollectionRepo_Expecter) CountPersonCollections(ctx interface{}, userID interface{}, cat interface{}) *CollectionRepo_CountPersonCollections_Call { + return &CollectionRepo_CountPersonCollections_Call{Call: _e.mock.On("CountPersonCollections", ctx, userID, cat)} +} + +func (_c *CollectionRepo_CountPersonCollections_Call) Run(run func(ctx context.Context, userID uint32, cat collection.PersonCollectCategory)) *CollectionRepo_CountPersonCollections_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint32), args[2].(collection.PersonCollectCategory)) + }) + return _c +} + +func (_c *CollectionRepo_CountPersonCollections_Call) Return(_a0 int64, _a1 error) *CollectionRepo_CountPersonCollections_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *CollectionRepo_CountPersonCollections_Call) RunAndReturn(run func(context.Context, uint32, collection.PersonCollectCategory) (int64, error)) *CollectionRepo_CountPersonCollections_Call { _c.Call.Return(run) return _c } @@ -139,12 +197,12 @@ func (_c *CollectionRepo_CountSubjectCollections_Call) RunAndReturn(run func(con return _c } -// GetPersonCollect provides a mock function with given fields: ctx, userID, cat, targetID -func (_m *CollectionRepo) GetPersonCollect(ctx context.Context, userID uint32, cat collection.PersonCollectCategory, targetID uint32) (collection.UserPersonCollection, error) { +// GetPersonCollection provides a mock function with given fields: ctx, userID, cat, targetID +func (_m *CollectionRepo) GetPersonCollection(ctx context.Context, userID uint32, cat collection.PersonCollectCategory, targetID uint32) (collection.UserPersonCollection, error) { ret := _m.Called(ctx, userID, cat, targetID) if len(ret) == 0 { - panic("no return value specified for GetPersonCollect") + panic("no return value specified for GetPersonCollection") } var r0 collection.UserPersonCollection @@ -167,33 +225,33 @@ func (_m *CollectionRepo) GetPersonCollect(ctx context.Context, userID uint32, c return r0, r1 } -// CollectionRepo_GetPersonCollect_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPersonCollect' -type CollectionRepo_GetPersonCollect_Call struct { +// CollectionRepo_GetPersonCollection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPersonCollection' +type CollectionRepo_GetPersonCollection_Call struct { *mock.Call } -// GetPersonCollect is a helper method to define mock.On call +// GetPersonCollection is a helper method to define mock.On call // - ctx context.Context // - userID uint32 // - cat collection.PersonCollectCategory // - targetID uint32 -func (_e *CollectionRepo_Expecter) GetPersonCollect(ctx interface{}, userID interface{}, cat interface{}, targetID interface{}) *CollectionRepo_GetPersonCollect_Call { - return &CollectionRepo_GetPersonCollect_Call{Call: _e.mock.On("GetPersonCollect", ctx, userID, cat, targetID)} +func (_e *CollectionRepo_Expecter) GetPersonCollection(ctx interface{}, userID interface{}, cat interface{}, targetID interface{}) *CollectionRepo_GetPersonCollection_Call { + return &CollectionRepo_GetPersonCollection_Call{Call: _e.mock.On("GetPersonCollection", ctx, userID, cat, targetID)} } -func (_c *CollectionRepo_GetPersonCollect_Call) Run(run func(ctx context.Context, userID uint32, cat collection.PersonCollectCategory, targetID uint32)) *CollectionRepo_GetPersonCollect_Call { +func (_c *CollectionRepo_GetPersonCollection_Call) Run(run func(ctx context.Context, userID uint32, cat collection.PersonCollectCategory, targetID uint32)) *CollectionRepo_GetPersonCollection_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(context.Context), args[1].(uint32), args[2].(collection.PersonCollectCategory), args[3].(uint32)) }) return _c } -func (_c *CollectionRepo_GetPersonCollect_Call) Return(_a0 collection.UserPersonCollection, _a1 error) *CollectionRepo_GetPersonCollect_Call { +func (_c *CollectionRepo_GetPersonCollection_Call) Return(_a0 collection.UserPersonCollection, _a1 error) *CollectionRepo_GetPersonCollection_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *CollectionRepo_GetPersonCollect_Call) RunAndReturn(run func(context.Context, uint32, collection.PersonCollectCategory, uint32) (collection.UserPersonCollection, error)) *CollectionRepo_GetPersonCollect_Call { +func (_c *CollectionRepo_GetPersonCollection_Call) RunAndReturn(run func(context.Context, uint32, collection.PersonCollectCategory, uint32) (collection.UserPersonCollection, error)) *CollectionRepo_GetPersonCollection_Call { _c.Call.Return(run) return _c } @@ -316,6 +374,68 @@ func (_c *CollectionRepo_GetSubjectEpisodesCollection_Call) RunAndReturn(run fun return _c } +// ListPersonCollection provides a mock function with given fields: ctx, userID, cat, limit, offset +func (_m *CollectionRepo) ListPersonCollection(ctx context.Context, userID uint32, cat collection.PersonCollectCategory, limit int, offset int) ([]collection.UserPersonCollection, error) { + ret := _m.Called(ctx, userID, cat, limit, offset) + + if len(ret) == 0 { + panic("no return value specified for ListPersonCollection") + } + + var r0 []collection.UserPersonCollection + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint32, collection.PersonCollectCategory, int, int) ([]collection.UserPersonCollection, error)); ok { + return rf(ctx, userID, cat, limit, offset) + } + if rf, ok := ret.Get(0).(func(context.Context, uint32, collection.PersonCollectCategory, int, int) []collection.UserPersonCollection); ok { + r0 = rf(ctx, userID, cat, limit, offset) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]collection.UserPersonCollection) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint32, collection.PersonCollectCategory, int, int) error); ok { + r1 = rf(ctx, userID, cat, limit, offset) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CollectionRepo_ListPersonCollection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListPersonCollection' +type CollectionRepo_ListPersonCollection_Call struct { + *mock.Call +} + +// ListPersonCollection is a helper method to define mock.On call +// - ctx context.Context +// - userID uint32 +// - cat collection.PersonCollectCategory +// - limit int +// - offset int +func (_e *CollectionRepo_Expecter) ListPersonCollection(ctx interface{}, userID interface{}, cat interface{}, limit interface{}, offset interface{}) *CollectionRepo_ListPersonCollection_Call { + return &CollectionRepo_ListPersonCollection_Call{Call: _e.mock.On("ListPersonCollection", ctx, userID, cat, limit, offset)} +} + +func (_c *CollectionRepo_ListPersonCollection_Call) Run(run func(ctx context.Context, userID uint32, cat collection.PersonCollectCategory, limit int, offset int)) *CollectionRepo_ListPersonCollection_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint32), args[2].(collection.PersonCollectCategory), args[3].(int), args[4].(int)) + }) + return _c +} + +func (_c *CollectionRepo_ListPersonCollection_Call) Return(_a0 []collection.UserPersonCollection, _a1 error) *CollectionRepo_ListPersonCollection_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *CollectionRepo_ListPersonCollection_Call) RunAndReturn(run func(context.Context, uint32, collection.PersonCollectCategory, int, int) ([]collection.UserPersonCollection, error)) *CollectionRepo_ListPersonCollection_Call { + _c.Call.Return(run) + return _c +} + // ListSubjectCollection provides a mock function with given fields: ctx, userID, subjectType, collectionType, showPrivate, limit, offset func (_m *CollectionRepo) ListSubjectCollection(ctx context.Context, userID uint32, subjectType uint8, collectionType collection.SubjectCollection, showPrivate bool, limit int, offset int) ([]collection.UserSubjectCollection, error) { ret := _m.Called(ctx, userID, subjectType, collectionType, showPrivate, limit, offset) @@ -380,12 +500,12 @@ func (_c *CollectionRepo_ListSubjectCollection_Call) RunAndReturn(run func(conte return _c } -// RemovePersonCollect provides a mock function with given fields: ctx, userID, cat, targetID -func (_m *CollectionRepo) RemovePersonCollect(ctx context.Context, userID uint32, cat collection.PersonCollectCategory, targetID uint32) error { +// RemovePersonCollection provides a mock function with given fields: ctx, userID, cat, targetID +func (_m *CollectionRepo) RemovePersonCollection(ctx context.Context, userID uint32, cat collection.PersonCollectCategory, targetID uint32) error { ret := _m.Called(ctx, userID, cat, targetID) if len(ret) == 0 { - panic("no return value specified for RemovePersonCollect") + panic("no return value specified for RemovePersonCollection") } var r0 error @@ -398,33 +518,33 @@ func (_m *CollectionRepo) RemovePersonCollect(ctx context.Context, userID uint32 return r0 } -// CollectionRepo_RemovePersonCollect_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemovePersonCollect' -type CollectionRepo_RemovePersonCollect_Call struct { +// CollectionRepo_RemovePersonCollection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemovePersonCollection' +type CollectionRepo_RemovePersonCollection_Call struct { *mock.Call } -// RemovePersonCollect is a helper method to define mock.On call +// RemovePersonCollection is a helper method to define mock.On call // - ctx context.Context // - userID uint32 // - cat collection.PersonCollectCategory // - targetID uint32 -func (_e *CollectionRepo_Expecter) RemovePersonCollect(ctx interface{}, userID interface{}, cat interface{}, targetID interface{}) *CollectionRepo_RemovePersonCollect_Call { - return &CollectionRepo_RemovePersonCollect_Call{Call: _e.mock.On("RemovePersonCollect", ctx, userID, cat, targetID)} +func (_e *CollectionRepo_Expecter) RemovePersonCollection(ctx interface{}, userID interface{}, cat interface{}, targetID interface{}) *CollectionRepo_RemovePersonCollection_Call { + return &CollectionRepo_RemovePersonCollection_Call{Call: _e.mock.On("RemovePersonCollection", ctx, userID, cat, targetID)} } -func (_c *CollectionRepo_RemovePersonCollect_Call) Run(run func(ctx context.Context, userID uint32, cat collection.PersonCollectCategory, targetID uint32)) *CollectionRepo_RemovePersonCollect_Call { +func (_c *CollectionRepo_RemovePersonCollection_Call) Run(run func(ctx context.Context, userID uint32, cat collection.PersonCollectCategory, targetID uint32)) *CollectionRepo_RemovePersonCollection_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(context.Context), args[1].(uint32), args[2].(collection.PersonCollectCategory), args[3].(uint32)) }) return _c } -func (_c *CollectionRepo_RemovePersonCollect_Call) Return(_a0 error) *CollectionRepo_RemovePersonCollect_Call { +func (_c *CollectionRepo_RemovePersonCollection_Call) Return(_a0 error) *CollectionRepo_RemovePersonCollection_Call { _c.Call.Return(_a0) return _c } -func (_c *CollectionRepo_RemovePersonCollect_Call) RunAndReturn(run func(context.Context, uint32, collection.PersonCollectCategory, uint32) error) *CollectionRepo_RemovePersonCollect_Call { +func (_c *CollectionRepo_RemovePersonCollection_Call) RunAndReturn(run func(context.Context, uint32, collection.PersonCollectCategory, uint32) error) *CollectionRepo_RemovePersonCollection_Call { _c.Call.Return(run) return _c } diff --git a/web/handler/character/collect.go b/web/handler/character/collect.go index fefab8933..f8d6c60b6 100644 --- a/web/handler/character/collect.go +++ b/web/handler/character/collect.go @@ -54,13 +54,13 @@ func (h Character) collectCharacter(c echo.Context, cid uint32, uid uint32) erro return res.InternalError(c, err, "get character error") } // check if the user has collected the character - if _, err := h.collect.GetPersonCollect(ctx, uid, collection.PersonCollectCategoryCharacter, cid); err == nil { + if _, err := h.collect.GetPersonCollection(ctx, uid, collection.PersonCollectCategoryCharacter, cid); err == nil { return nil // already collected } else if !errors.Is(err, gerr.ErrNotFound) { return res.InternalError(c, err, "get character collect error") } // add the collect - if err := h.collect.AddPersonCollect(ctx, uid, collection.PersonCollectCategoryCharacter, cid); err != nil { + if err := h.collect.AddPersonCollection(ctx, uid, collection.PersonCollectCategoryCharacter, cid); err != nil { return res.InternalError(c, err, "add character collect failed") } return nil @@ -76,14 +76,14 @@ func (h Character) uncollectCharacter(c echo.Context, cid uint32, uid uint32) er return res.InternalError(c, err, "get character error") } // check if the user has collected the character - if _, err := h.collect.GetPersonCollect(ctx, uid, collection.PersonCollectCategoryCharacter, cid); err != nil { + if _, err := h.collect.GetPersonCollection(ctx, uid, collection.PersonCollectCategoryCharacter, cid); err != nil { if errors.Is(err, gerr.ErrNotFound) { return res.NotFound("character not collected") } return res.InternalError(c, err, "get character collect error") } // remove the collect - if err := h.collect.RemovePersonCollect(ctx, uid, collection.PersonCollectCategoryCharacter, cid); err != nil { + if err := h.collect.RemovePersonCollection(ctx, uid, collection.PersonCollectCategoryCharacter, cid); err != nil { return res.InternalError(c, err, "remove character collect failed") } return nil diff --git a/web/handler/person/collect.go b/web/handler/person/collect.go index e3c6d0c17..d1ec9fa62 100644 --- a/web/handler/person/collect.go +++ b/web/handler/person/collect.go @@ -56,13 +56,13 @@ func (h Person) collectPerson(c echo.Context, pid uint32, uid uint32) error { return res.InternalError(c, err, "get person error") } // check if the user has collected the person - if _, err := h.collect.GetPersonCollect(ctx, uid, collection.PersonCollectCategoryPerson, pid); err == nil { + if _, err := h.collect.GetPersonCollection(ctx, uid, collection.PersonCollectCategoryPerson, pid); err == nil { return nil // already collected } else if !errors.Is(err, gerr.ErrNotFound) { return res.InternalError(c, err, "get person collect error") } // add the collect - if err := h.collect.AddPersonCollect(ctx, uid, collection.PersonCollectCategoryPerson, pid); err != nil { + if err := h.collect.AddPersonCollection(ctx, uid, collection.PersonCollectCategoryPerson, pid); err != nil { return res.InternalError(c, err, "add person collect failed") } return nil @@ -78,14 +78,14 @@ func (h Person) uncollectPerson(c echo.Context, pid uint32, uid uint32) error { return res.InternalError(c, err, "get person error") } // check if the user has collected the person - if _, err := h.collect.GetPersonCollect(ctx, uid, collection.PersonCollectCategoryPerson, pid); err != nil { + if _, err := h.collect.GetPersonCollection(ctx, uid, collection.PersonCollectCategoryPerson, pid); err != nil { if errors.Is(err, gerr.ErrNotFound) { return res.NotFound("person not collected") } return res.InternalError(c, err, "get person collect error") } // remove the collect - if err := h.collect.RemovePersonCollect(ctx, uid, collection.PersonCollectCategoryPerson, pid); err != nil { + if err := h.collect.RemovePersonCollection(ctx, uid, collection.PersonCollectCategoryPerson, pid); err != nil { return res.InternalError(c, err, "remove person collect failed") } return nil diff --git a/web/handler/user/get_character_collection.go b/web/handler/user/get_character_collection.go new file mode 100644 index 000000000..42657e601 --- /dev/null +++ b/web/handler/user/get_character_collection.go @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see + +package user + +import ( + "errors" + "net/http" + + "github.com/labstack/echo/v4" + "github.com/trim21/errgo" + + "github.com/bangumi/server/domain/gerr" + "github.com/bangumi/server/internal/collections/domain/collection" + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/web/req" + "github.com/bangumi/server/web/res" +) + +func (h User) GetCharacterCollection(c echo.Context) error { + username := c.Param("username") + if username == "" { + return res.BadRequest("missing require parameters `username`") + } + + characterID, err := req.ParseID(c.Param("character_id")) + if err != nil { + return err + } + + return h.getCharacterCollection(c, username, characterID) +} + +func (h User) getCharacterCollection(c echo.Context, username string, characterID model.CharacterID) error { + const notFoundMessage = "character is not collected by user" + + character, err := h.character.Get(c.Request().Context(), characterID) + if err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return res.ErrNotFound + } + + return errgo.Wrap(err, "failed to get character") + } + + u, err := h.user.GetByName(c.Request().Context(), username) + if err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return res.NotFound("user doesn't exist or has been removed") + } + + return errgo.Wrap(err, "failed to get user by name") + } + + collect, err := h.collect.GetPersonCollection( + c.Request().Context(), + u.ID, + collection.PersonCollectCategoryCharacter, + characterID, + ) + if err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return res.NotFound(notFoundMessage) + } + + return errgo.Wrap(err, "failed to get user's character collection") + } + + return c.JSON(http.StatusOK, res.ConvertModelCharacterCollection(collect, character)) +} diff --git a/web/handler/user/get_person_collection.go b/web/handler/user/get_person_collection.go new file mode 100644 index 000000000..1240cb8a4 --- /dev/null +++ b/web/handler/user/get_person_collection.go @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see + +package user + +import ( + "errors" + "net/http" + + "github.com/labstack/echo/v4" + "github.com/trim21/errgo" + + "github.com/bangumi/server/domain/gerr" + "github.com/bangumi/server/internal/collections/domain/collection" + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/web/req" + "github.com/bangumi/server/web/res" +) + +func (h User) GetPersonCollection(c echo.Context) error { + username := c.Param("username") + if username == "" { + return res.BadRequest("missing require parameters `username`") + } + + personID, err := req.ParseID(c.Param("person_id")) + if err != nil { + return err + } + + return h.getPersonCollection(c, username, personID) +} + +func (h User) getPersonCollection(c echo.Context, username string, personID model.PersonID) error { + const notFoundMessage = "person is not collected by user" + + person, err := h.person.Get(c.Request().Context(), personID) + if err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return res.ErrNotFound + } + + return errgo.Wrap(err, "failed to get person") + } + + u, err := h.user.GetByName(c.Request().Context(), username) + if err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return res.NotFound("user doesn't exist or has been removed") + } + + return errgo.Wrap(err, "failed to get user by name") + } + + collect, err := h.collect.GetPersonCollection( + c.Request().Context(), u.ID, collection.PersonCollectCategoryPerson, personID) + if err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return res.NotFound(notFoundMessage) + } + + return errgo.Wrap(err, "failed to get person collect") + } + + return c.JSON(http.StatusOK, res.ConvertModelPersonCollection(collect, person)) +} diff --git a/web/handler/user/list_character_collections.go b/web/handler/user/list_character_collections.go new file mode 100644 index 000000000..70fa7a534 --- /dev/null +++ b/web/handler/user/list_character_collections.go @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see + +package user + +import ( + "errors" + "net/http" + + "github.com/labstack/echo/v4" + "github.com/trim21/errgo" + + "github.com/bangumi/server/domain/gerr" + "github.com/bangumi/server/internal/collections/domain/collection" + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/pkg/generic/slice" + "github.com/bangumi/server/internal/user" + "github.com/bangumi/server/web/req" + "github.com/bangumi/server/web/res" +) + +func (h User) ListCharacterCollection(c echo.Context) error { + page, err := req.GetPageQuery(c, req.DefaultPageLimit, req.DefaultMaxPageLimit) + if err != nil { + return err + } + + username := c.Param("username") + if username == "" { + return res.BadRequest("missing require parameters `username`") + } + + u, err := h.user.GetByName(c.Request().Context(), username) + if err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return res.NotFound("user doesn't exist or has been removed") + } + + return errgo.Wrap(err, "user.GetByName") + } + + return h.listCharacterCollection(c, u, page) +} + +func (h User) listCharacterCollection(c echo.Context, u user.User, page req.PageQuery) error { + count, err := h.collect.CountPersonCollections(c.Request().Context(), u.ID, collection.PersonCollectCategoryCharacter) + if err != nil { + return errgo.Wrap(err, "failed to count user's character collections") + } + + if count == 0 { + if count == 0 { + return c.JSON(http.StatusOK, res.Paged{Data: []int{}, Total: count, Limit: page.Limit, Offset: page.Offset}) + } + } + + if err = page.Check(count); err != nil { + return err + } + + cols, err := h.collect.ListPersonCollection( + c.Request().Context(), + u.ID, collection.PersonCollectCategoryCharacter, + page.Limit, page.Offset) + if err != nil { + return errgo.Wrap(err, "failed to list user's person collections") + } + + characterIDs := slice.Map(cols, func(item collection.UserPersonCollection) model.PersonID { + return item.TargetID + }) + + characterMap, err := h.character.GetByIDs(c.Request().Context(), characterIDs) + if err != nil { + return errgo.Wrap(err, "failed to get persons") + } + + var data = make([]res.PersonCollection, 0, len(cols)) + + for _, col := range cols { + character, ok := characterMap[col.TargetID] + if !ok { + continue + } + data = append(data, res.ConvertModelCharacterCollection(col, character)) + } + + return c.JSON(http.StatusOK, res.Paged{ + Data: data, + Total: count, + Limit: page.Limit, + Offset: page.Offset, + }) +} diff --git a/web/handler/user/list_person_collections.go b/web/handler/user/list_person_collections.go new file mode 100644 index 000000000..9289fe07d --- /dev/null +++ b/web/handler/user/list_person_collections.go @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see + +package user + +import ( + "errors" + "net/http" + + "github.com/labstack/echo/v4" + "github.com/trim21/errgo" + + "github.com/bangumi/server/domain/gerr" + "github.com/bangumi/server/internal/collections/domain/collection" + "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/pkg/generic/slice" + "github.com/bangumi/server/internal/user" + "github.com/bangumi/server/web/req" + "github.com/bangumi/server/web/res" +) + +func (h User) ListPersonCollection(c echo.Context) error { + page, err := req.GetPageQuery(c, req.DefaultPageLimit, req.DefaultMaxPageLimit) + if err != nil { + return err + } + + username := c.Param("username") + if username == "" { + return res.BadRequest("missing require parameters `username`") + } + + u, err := h.user.GetByName(c.Request().Context(), username) + if err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return res.NotFound("user doesn't exist or has been removed") + } + + return errgo.Wrap(err, "user.GetByName") + } + + return h.listPersonCollection(c, u, page) +} + +func (h User) listPersonCollection(c echo.Context, u user.User, page req.PageQuery) error { + count, err := h.collect.CountPersonCollections(c.Request().Context(), u.ID, collection.PersonCollectCategoryPerson) + if err != nil { + return errgo.Wrap(err, "failed to count user's person collections") + } + + if count == 0 { + if count == 0 { + return c.JSON(http.StatusOK, res.Paged{Data: []int{}, Total: count, Limit: page.Limit, Offset: page.Offset}) + } + } + + if err = page.Check(count); err != nil { + return err + } + + cols, err := h.collect.ListPersonCollection( + c.Request().Context(), u.ID, collection.PersonCollectCategoryPerson, + page.Limit, page.Offset) + if err != nil { + return errgo.Wrap(err, "failed to list user's person collections") + } + + personIDs := slice.Map(cols, func(item collection.UserPersonCollection) model.PersonID { + return item.TargetID + }) + + personMap, err := h.person.GetByIDs(c.Request().Context(), personIDs) + if err != nil { + return errgo.Wrap(err, "failed to get persons") + } + + var data = make([]res.PersonCollection, 0, len(cols)) + + for _, col := range cols { + person, ok := personMap[col.TargetID] + if !ok { + continue + } + data = append(data, res.ConvertModelPersonCollection(col, person)) + } + + return c.JSON(http.StatusOK, res.Paged{ + Data: data, + Total: count, + Limit: page.Limit, + Offset: page.Offset, + }) +} diff --git a/web/handler/user/user.go b/web/handler/user/user.go index 59236045f..a85733224 100644 --- a/web/handler/user/user.go +++ b/web/handler/user/user.go @@ -19,6 +19,7 @@ import ( "github.com/bangumi/server/config" "github.com/bangumi/server/ctrl" + "github.com/bangumi/server/internal/character" "github.com/bangumi/server/internal/collections" "github.com/bangumi/server/internal/episode" "github.com/bangumi/server/internal/person" @@ -27,18 +28,20 @@ import ( ) type User struct { - ctrl ctrl.Ctrl - episode episode.Repo - person person.Service - collect collections.Repo - subject subject.CachedRepo - log *zap.Logger - user user.Repo - cfg config.AppConfig + ctrl ctrl.Ctrl + episode episode.Repo + character character.Repo + person person.Repo + collect collections.Repo + subject subject.CachedRepo + log *zap.Logger + user user.Repo + cfg config.AppConfig } func New( - p person.Service, + person person.Repo, + character character.Repo, user user.Repo, ctrl ctrl.Ctrl, subject subject.Repo, @@ -47,13 +50,14 @@ func New( log *zap.Logger, ) (User, error) { return User{ - ctrl: ctrl, - episode: episode, - collect: collect, - subject: subject, - user: user, - person: p, - log: log.Named("handler.User"), - cfg: config.AppConfig{}, + ctrl: ctrl, + episode: episode, + collect: collect, + subject: subject, + user: user, + person: person, + character: character, + log: log.Named("handler.User"), + cfg: config.AppConfig{}, }, nil } diff --git a/web/res/collection.go b/web/res/collection.go index 17eecb208..84534d55b 100644 --- a/web/res/collection.go +++ b/web/res/collection.go @@ -60,22 +60,24 @@ type PersonCollection struct { CreatedAt time.Time `json:"created_at"` } -// func ConvertModelPersonCollection(c collection.UserPersonCollection, person PersonV0) PersonCollection { -// return PersonCollection{ -// ID: person.ID, -// Type: person.Type, -// Name: person.Name, -// Images: person.Images, -// CreatedAt: c.CreatedAt, -// } -// } +func ConvertModelPersonCollection(c collection.UserPersonCollection, person model.Person) PersonCollection { + img := PersonImage(person.Image) + return PersonCollection{ + ID: person.ID, + Type: person.Type, + Name: person.Name, + Images: img, + CreatedAt: c.CreatedAt, + } +} -// func convertModelCharacterCollection(c collection.UserPersonCollection, character CharacterV0) PersonCollection { -// return PersonCollection{ -// ID: character.ID, -// Type: character.Type, -// Name: character.Name, -// Images: character.Images, -// CreatedAt: c.CreatedAt, -// } -// } +func ConvertModelCharacterCollection(c collection.UserPersonCollection, character model.Character) PersonCollection { + img := PersonImage(character.Image) + return PersonCollection{ + ID: character.ID, + Type: character.Type, + Name: character.Name, + Images: img, + CreatedAt: c.CreatedAt, + } +} diff --git a/web/routes.go b/web/routes.go index ba82adf93..b796c2df7 100644 --- a/web/routes.go +++ b/web/routes.go @@ -94,10 +94,10 @@ func AddRouters( v0.PATCH("/users/-/collections/:subject_id/episodes", userHandler.PatchEpisodeCollectionBatch, req.JSON, mw.NeedLogin) - // v0.GET("/users/:username/collections/-/characters", userHandler.ListCharacterCollection) - // v0.GET("/users/:username/collections/-/characters/:character_id", userHandler.GetCharacterCollection) - // v0.GET("/users/:username/collections/-/persons", userHandler.ListPersonCollection) - // v0.GET("/users/:username/collections/-/persons/:person_id", userHandler.GetPersonCollection) + v0.GET("/users/:username/collections/-/characters", userHandler.ListCharacterCollection) + v0.GET("/users/:username/collections/-/characters/:character_id", userHandler.GetCharacterCollection) + v0.GET("/users/:username/collections/-/persons", userHandler.ListPersonCollection) + v0.GET("/users/:username/collections/-/persons/:person_id", userHandler.GetPersonCollection) { i := indexHandler