diff --git a/internal/collections/domain.go b/internal/collections/domain.go index a3ccbb5cd..e5f7a27f8 100644 --- a/internal/collections/domain.go +++ b/internal/collections/domain.go @@ -70,6 +70,21 @@ type Repo interface { episodeIDs []model.EpisodeID, collection collection.EpisodeCollection, at time.Time, ) (collection.UserSubjectEpisodesCollection, error) + + GetPersonCollect( + ctx context.Context, userID model.UserID, + cat string, targetID model.PersonID, + ) (collection.UserPersonCollection, error) + + AddPersonCollect( + ctx context.Context, userID model.UserID, + cat string, targetID model.PersonID, + ) error + + RemovePersonCollect( + ctx context.Context, userID model.UserID, + cat string, targetID model.PersonID, + ) error } type Update struct { diff --git a/internal/collections/domain/collection/model.go b/internal/collections/domain/collection/model.go index f2f229ba4..c94a2feb1 100644 --- a/internal/collections/domain/collection/model.go +++ b/internal/collections/domain/collection/model.go @@ -51,3 +51,11 @@ type UserEpisodeCollection struct { } type UserSubjectEpisodesCollection map[model.EpisodeID]UserEpisodeCollection + +type UserPersonCollection struct { + ID uint32 + Category string + TargetID model.PersonID + UserID model.UserID + CreatedAt time.Time +} diff --git a/internal/collections/infra/mysql_repo.go b/internal/collections/infra/mysql_repo.go index a42e46dec..1fe2a1ffd 100644 --- a/internal/collections/infra/mysql_repo.go +++ b/internal/collections/infra/mysql_repo.go @@ -461,6 +461,63 @@ func (r mysqlRepo) updateCollectionTime(obj *dao.SubjectCollection, return nil } +func (r mysqlRepo) GetPersonCollect( + ctx context.Context, userID model.UserID, + cat string, targetID model.PersonID, +) (collection.UserPersonCollection, error) { + c, err := r.q.PersonCollect.WithContext(ctx). + Where(r.q.PersonCollect.UserID.Eq(userID), r.q.PersonCollect.Category.Eq(cat), + r.q.PersonCollect.TargetID.Eq(targetID)).Take() + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return collection.UserPersonCollection{}, gerr.ErrNotFound + } + return collection.UserPersonCollection{}, errgo.Wrap(err, "dal") + } + + return collection.UserPersonCollection{ + ID: c.ID, + Category: c.Category, + TargetID: c.TargetID, + UserID: c.UserID, + CreatedAt: time.Unix(int64(c.CreatedTime), 0), + }, nil +} + +func (r mysqlRepo) AddPersonCollect( + ctx context.Context, userID model.UserID, + cat string, targetID model.PersonID, +) error { + var table = r.q.PersonCollect + err := table.WithContext(ctx).Create(&dao.PersonCollect{ + UserID: userID, + Category: cat, + TargetID: targetID, + CreatedTime: uint32(time.Now().Unix()), + }) + if err != nil { + r.log.Error("failed to create person collection record", zap.Error(err)) + return errgo.Wrap(err, "dal") + } + + return nil +} + +func (r mysqlRepo) RemovePersonCollect( + ctx context.Context, userID model.UserID, + cat string, targetID model.PersonID, +) error { + _, err := r.q.PersonCollect.WithContext(ctx). + Where(r.q.PersonCollect.UserID.Eq(userID), r.q.PersonCollect.Category.Eq(cat), + r.q.PersonCollect.TargetID.Eq(targetID)).Delete() + if err != nil { + r.log.Error("failed to delete person collection record", zap.Error(err)) + return errgo.Wrap(err, "dal") + } + + return 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 c38945548..3da2a5559 100644 --- a/internal/collections/infra/mysql_repo_test.go +++ b/internal/collections/infra/mysql_repo_test.go @@ -415,6 +415,93 @@ func TestMysqlRepo_UpdateSubjectCollectionType(t *testing.T) { require.Zero(t, r.DoneTime) require.Zero(t, r.OnHoldTime) } + +func TestMysqlRepo_GetPersonCollect(t *testing.T) { + test.RequireEnv(t, test.EnvMysql) + t.Parallel() + + const uid model.UserID = 35000 + const cat = "prsn" + const mid model.PersonID = 12000 + + repo, q := getRepo(t) + table := q.PersonCollect + test.RunAndCleanup(t, func() { + _, err := table.WithContext(context.TODO()).Where(table.UserID.Eq(uid)).Delete() + require.NoError(t, err) + }) + + err := table.WithContext(context.Background()).Create(&dao.PersonCollect{ + UserID: uid, + Category: cat, + TargetID: mid, + CreatedTime: uint32(time.Now().Unix()), + }) + require.NoError(t, err) + + r, err := repo.GetPersonCollect(context.Background(), uid, cat, mid) + require.NoError(t, err) + require.Len(t, r, 1) +} + +func TestMysqlRepo_AddPersonCollect(t *testing.T) { + test.RequireEnv(t, test.EnvMysql) + t.Parallel() + + const uid model.UserID = 36000 + const cat = "prsn" + const mid model.PersonID = 13000 + + repo, q := getRepo(t) + table := q.PersonCollect + test.RunAndCleanup(t, func() { + _, err := table.WithContext(context.TODO()).Where(table.UserID.Eq(uid)).Delete() + require.NoError(t, err) + }) + + err := repo.AddPersonCollect(context.Background(), uid, cat, mid) + require.NoError(t, err) + + r, err := table.WithContext(context.TODO()).Where(table.UserID.Eq(uid)).Take() + require.NoError(t, err) + require.NotZero(t, r.ID) +} + +func TestMysqlRepo_RemovePersonCollect(t *testing.T) { + test.RequireEnv(t, test.EnvMysql) + t.Parallel() + + const uid model.UserID = 37000 + const cat = "prsn" + const mid model.PersonID = 14000 + + repo, q := getRepo(t) + table := q.PersonCollect + test.RunAndCleanup(t, func() { + _, err := table.WithContext(context.TODO()).Where(table.UserID.Eq(uid)).Delete() + require.NoError(t, err) + }) + + err := table.WithContext(context.Background()).Create(&dao.PersonCollect{ + UserID: uid, + Category: cat, + TargetID: mid, + CreatedTime: uint32(time.Now().Unix()), + }) + require.NoError(t, err) + + r, err := table.WithContext(context.TODO()).Where(table.UserID.Eq(uid)).Take() + require.NoError(t, err) + require.NotZero(t, r.ID) + + err = repo.RemovePersonCollect(context.Background(), uid, cat, mid) + require.NoError(t, err) + + r, err = table.WithContext(context.TODO()).Where(table.UserID.Eq(uid)).Take() + require.NoError(t, err) + require.Zero(t, r.ID) +} + func TestMysqlRepo_UpdateEpisodeCollection(t *testing.T) { test.RequireEnv(t, test.EnvMysql) t.Parallel() diff --git a/internal/mocks/CollectionRepo.go b/internal/mocks/CollectionRepo.go index 811b211b2..a4970f1f4 100644 --- a/internal/mocks/CollectionRepo.go +++ b/internal/mocks/CollectionRepo.go @@ -30,6 +30,55 @@ 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 string, targetID uint32) error { + ret := _m.Called(ctx, userID, cat, targetID) + + if len(ret) == 0 { + panic("no return value specified for AddPersonCollect") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, uint32, string, uint32) error); ok { + r0 = rf(ctx, userID, cat, targetID) + } else { + r0 = ret.Error(0) + } + + 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 { + *mock.Call +} + +// AddPersonCollect is a helper method to define mock.On call +// - ctx context.Context +// - userID uint32 +// - cat string +// - 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 (_c *CollectionRepo_AddPersonCollect_Call) Run(run func(ctx context.Context, userID uint32, cat string, targetID uint32)) *CollectionRepo_AddPersonCollect_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint32), args[2].(string), args[3].(uint32)) + }) + return _c +} + +func (_c *CollectionRepo_AddPersonCollect_Call) Return(_a0 error) *CollectionRepo_AddPersonCollect_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *CollectionRepo_AddPersonCollect_Call) RunAndReturn(run func(context.Context, uint32, string, uint32) error) *CollectionRepo_AddPersonCollect_Call { + _c.Call.Return(run) + return _c +} + // CountSubjectCollections provides a mock function with given fields: ctx, userID, subjectType, collectionType, showPrivate func (_m *CollectionRepo) CountSubjectCollections(ctx context.Context, userID uint32, subjectType uint8, collectionType collection.SubjectCollection, showPrivate bool) (int64, error) { ret := _m.Called(ctx, userID, subjectType, collectionType, showPrivate) @@ -90,6 +139,65 @@ 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 string, targetID uint32) (collection.UserPersonCollection, error) { + ret := _m.Called(ctx, userID, cat, targetID) + + if len(ret) == 0 { + panic("no return value specified for GetPersonCollect") + } + + var r0 collection.UserPersonCollection + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint32, string, uint32) (collection.UserPersonCollection, error)); ok { + return rf(ctx, userID, cat, targetID) + } + if rf, ok := ret.Get(0).(func(context.Context, uint32, string, uint32) collection.UserPersonCollection); ok { + r0 = rf(ctx, userID, cat, targetID) + } else { + r0 = ret.Get(0).(collection.UserPersonCollection) + } + + if rf, ok := ret.Get(1).(func(context.Context, uint32, string, uint32) error); ok { + r1 = rf(ctx, userID, cat, targetID) + } else { + r1 = ret.Error(1) + } + + 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 { + *mock.Call +} + +// GetPersonCollect is a helper method to define mock.On call +// - ctx context.Context +// - userID uint32 +// - cat string +// - 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 (_c *CollectionRepo_GetPersonCollect_Call) Run(run func(ctx context.Context, userID uint32, cat string, targetID uint32)) *CollectionRepo_GetPersonCollect_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint32), args[2].(string), args[3].(uint32)) + }) + return _c +} + +func (_c *CollectionRepo_GetPersonCollect_Call) Return(_a0 collection.UserPersonCollection, _a1 error) *CollectionRepo_GetPersonCollect_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *CollectionRepo_GetPersonCollect_Call) RunAndReturn(run func(context.Context, uint32, string, uint32) (collection.UserPersonCollection, error)) *CollectionRepo_GetPersonCollect_Call { + _c.Call.Return(run) + return _c +} + // GetSubjectCollection provides a mock function with given fields: ctx, userID, subjectID func (_m *CollectionRepo) GetSubjectCollection(ctx context.Context, userID uint32, subjectID uint32) (collection.UserSubjectCollection, error) { ret := _m.Called(ctx, userID, subjectID) @@ -272,6 +380,55 @@ 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 string, targetID uint32) error { + ret := _m.Called(ctx, userID, cat, targetID) + + if len(ret) == 0 { + panic("no return value specified for RemovePersonCollect") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, uint32, string, uint32) error); ok { + r0 = rf(ctx, userID, cat, targetID) + } else { + r0 = ret.Error(0) + } + + 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 { + *mock.Call +} + +// RemovePersonCollect is a helper method to define mock.On call +// - ctx context.Context +// - userID uint32 +// - cat string +// - 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 (_c *CollectionRepo_RemovePersonCollect_Call) Run(run func(ctx context.Context, userID uint32, cat string, targetID uint32)) *CollectionRepo_RemovePersonCollect_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint32), args[2].(string), args[3].(uint32)) + }) + return _c +} + +func (_c *CollectionRepo_RemovePersonCollect_Call) Return(_a0 error) *CollectionRepo_RemovePersonCollect_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *CollectionRepo_RemovePersonCollect_Call) RunAndReturn(run func(context.Context, uint32, string, uint32) error) *CollectionRepo_RemovePersonCollect_Call { + _c.Call.Return(run) + return _c +} + // UpdateEpisodeCollection provides a mock function with given fields: ctx, userID, subjectID, episodeIDs, _a4, at func (_m *CollectionRepo) UpdateEpisodeCollection(ctx context.Context, userID uint32, subjectID uint32, episodeIDs []uint32, _a4 collection.EpisodeCollection, at time.Time) (collection.UserSubjectEpisodesCollection, error) { ret := _m.Called(ctx, userID, subjectID, episodeIDs, _a4, at) diff --git a/web/handler/character/character.go b/web/handler/character/character.go index 2d42cfa2f..8578489f9 100644 --- a/web/handler/character/character.go +++ b/web/handler/character/character.go @@ -20,6 +20,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/model" "github.com/bangumi/server/internal/person" "github.com/bangumi/server/internal/pkg/compat" @@ -30,28 +31,31 @@ import ( ) type Character struct { - ctrl ctrl.Ctrl - person person.Service - c character.Repo - subject subject.Repo - log *zap.Logger - cfg config.AppConfig + ctrl ctrl.Ctrl + person person.Service + character character.Repo + subject subject.Repo + collect collections.Repo + log *zap.Logger + cfg config.AppConfig } func New( - p person.Service, + person person.Service, ctrl ctrl.Ctrl, - c character.Repo, + character character.Repo, subject subject.Repo, + collect collections.Repo, log *zap.Logger, ) (Character, error) { return Character{ - ctrl: ctrl, - c: c, - subject: subject, - person: p, - log: log.Named("handler.Character"), - cfg: config.AppConfig{}, + ctrl: ctrl, + character: character, + subject: subject, + person: person, + collect: collect, + log: log.Named("handler.Character"), + cfg: config.AppConfig{}, }, nil } diff --git a/web/handler/character/collect.go b/web/handler/character/collect.go new file mode 100644 index 000000000..50f31500e --- /dev/null +++ b/web/handler/character/collect.go @@ -0,0 +1,91 @@ +// 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 character + +import ( + "errors" + + "github.com/labstack/echo/v4" + + "github.com/bangumi/server/domain/gerr" + "github.com/bangumi/server/web/accessor" + "github.com/bangumi/server/web/req" + "github.com/bangumi/server/web/res" +) + +const collectionTypeCharacter = "crt" + +func (h Character) CollectCharacter(c echo.Context) error { + cid, err := req.ParseID(c.Param("id")) + if err != nil { + return err + } + uid := accessor.GetFromCtx(c).ID + return h.collectCharacter(c, cid, uid) +} + +func (h Character) UncollectCharacter(c echo.Context) error { + cid, err := req.ParseID(c.Param("id")) + if err != nil { + return err + } + uid := accessor.GetFromCtx(c).ID + return h.uncollectCharacter(c, cid, uid) +} + +func (h Character) collectCharacter(c echo.Context, cid uint32, uid uint32) error { + ctx := c.Request().Context() + // check if the character exists + if _, err := h.character.Get(ctx, cid); err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return res.ErrNotFound + } + return res.InternalError(c, err, "get character error") + } + // check if the user has collected the character + if _, err := h.collect.GetPersonCollect(ctx, uid, collectionTypeCharacter, 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, collectionTypeCharacter, cid); err != nil { + return res.InternalError(c, err, "add character collect failed") + } + return nil +} + +func (h Character) uncollectCharacter(c echo.Context, cid uint32, uid uint32) error { + ctx := c.Request().Context() + // check if the character exists + if _, err := h.character.Get(ctx, cid); err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return res.ErrNotFound + } + return res.InternalError(c, err, "get character error") + } + // check if the user has collected the character + if _, err := h.collect.GetPersonCollect(ctx, uid, collectionTypeCharacter, 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, collectionTypeCharacter, cid); err != nil { + return res.InternalError(c, err, "remove character collect failed") + } + return nil +} diff --git a/web/handler/character/get.go b/web/handler/character/get.go index 5def90ca7..08547ddca 100644 --- a/web/handler/character/get.go +++ b/web/handler/character/get.go @@ -36,7 +36,7 @@ func (h Character) Get(c echo.Context) error { return err } - r, err := h.c.Get(c.Request().Context(), id) + r, err := h.character.Get(c.Request().Context(), id) if err != nil { if errors.Is(err, gerr.ErrNotFound) { return res.ErrNotFound @@ -62,7 +62,7 @@ func (h Character) GetImage(c echo.Context) error { return err } - p, err := h.c.Get(c.Request().Context(), id) + p, err := h.character.Get(c.Request().Context(), id) if err != nil { if errors.Is(err, gerr.ErrNotFound) { return res.ErrNotFound diff --git a/web/handler/character/get_related_persons.go b/web/handler/character/get_related_persons.go index 421a237f9..d82bc8c18 100644 --- a/web/handler/character/get_related_persons.go +++ b/web/handler/character/get_related_persons.go @@ -33,7 +33,7 @@ func (h Character) GetRelatedPersons(c echo.Context) error { return err } - _, err = h.c.Get(c.Request().Context(), id) + _, err = h.character.Get(c.Request().Context(), id) if err != nil { if errors.Is(err, gerr.ErrNotFound) { return res.ErrNotFound diff --git a/web/handler/character/get_related_subjects.go b/web/handler/character/get_related_subjects.go index 38ca03a30..ce228d015 100644 --- a/web/handler/character/get_related_subjects.go +++ b/web/handler/character/get_related_subjects.go @@ -65,7 +65,7 @@ func (h Character) getCharacterRelatedSubjects( ctx context.Context, characterID model.CharacterID, ) (model.Character, []model.SubjectCharacterRelation, error) { - character, err := h.c.Get(ctx, characterID) + character, err := h.character.Get(ctx, characterID) if err != nil { return model.Character{}, nil, errgo.Wrap(err, "get character") } diff --git a/web/handler/person/collect.go b/web/handler/person/collect.go new file mode 100644 index 000000000..c996d15ad --- /dev/null +++ b/web/handler/person/collect.go @@ -0,0 +1,93 @@ +// 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 person + +import ( + "errors" + + "github.com/labstack/echo/v4" + + "github.com/bangumi/server/domain/gerr" + "github.com/bangumi/server/web/accessor" + "github.com/bangumi/server/web/req" + "github.com/bangumi/server/web/res" +) + +const collectCategoryPerson = "prsn" + +func (h Person) CollectPerson(c echo.Context) error { + pid, err := req.ParseID(c.Param("id")) + if err != nil { + return err + } + + uid := accessor.GetFromCtx(c).ID + return h.collectPerson(c, pid, uid) +} + +func (h Person) UncollectPerson(c echo.Context) error { + pid, err := req.ParseID(c.Param("id")) + if err != nil { + return err + } + + uid := accessor.GetFromCtx(c).ID + return h.uncollectPerson(c, pid, uid) +} + +func (h Person) collectPerson(c echo.Context, pid uint32, uid uint32) error { + ctx := c.Request().Context() + // check if the person exists + if _, err := h.person.Get(ctx, pid); err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return res.ErrNotFound + } + return res.InternalError(c, err, "get person error") + } + // check if the user has collected the person + if _, err := h.collect.GetPersonCollect(ctx, uid, collectCategoryPerson, 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, collectCategoryPerson, pid); err != nil { + return res.InternalError(c, err, "add person collect failed") + } + return nil +} + +func (h Person) uncollectPerson(c echo.Context, pid uint32, uid uint32) error { + ctx := c.Request().Context() + // check if the person exists + if _, err := h.person.Get(ctx, pid); err != nil { + if errors.Is(err, gerr.ErrNotFound) { + return res.ErrNotFound + } + return res.InternalError(c, err, "get person error") + } + // check if the user has collected the person + if _, err := h.collect.GetPersonCollect(ctx, uid, collectCategoryPerson, 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, collectCategoryPerson, pid); err != nil { + return res.InternalError(c, err, "remove person collect failed") + } + return nil +} diff --git a/web/handler/person/get_related_characters.go b/web/handler/person/get_related_characters.go index 4207cd4d4..60a530fb6 100644 --- a/web/handler/person/get_related_characters.go +++ b/web/handler/person/get_related_characters.go @@ -59,7 +59,7 @@ func (h Person) GetRelatedCharacters(c echo.Context) error { SubjectID: relation.Subject.ID, } } - subjectRelations, err := h.c.GetSubjectRelationByIDs(c.Request().Context(), compositeIDs) + subjectRelations, err := h.character.GetSubjectRelationByIDs(c.Request().Context(), compositeIDs) if err != nil { return errgo.Wrap(err, "CharacterRepo.GetRelations") } @@ -96,7 +96,7 @@ func (h Person) GetRelatedCharacters(c echo.Context) error { func (h Person) getPersonRelatedCharacters( ctx context.Context, personID model.PersonID, ) ([]model.PersonCharacterRelation, error) { - relations, err := h.c.GetPersonRelated(ctx, personID) + relations, err := h.character.GetPersonRelated(ctx, personID) if err != nil { return nil, errgo.Wrap(err, "CharacterRepo.GetPersonRelated") } @@ -112,7 +112,7 @@ func (h Person) getPersonRelatedCharacters( subjectIDs[i] = relation.SubjectID } - characters, err := h.c.GetByIDs(ctx, characterIDs) + characters, err := h.character.GetByIDs(ctx, characterIDs) if err != nil { return nil, errgo.Wrap(err, "CharacterRepo.GetByIDs") } diff --git a/web/handler/person/person.go b/web/handler/person/person.go index 3146859ae..a087c9cef 100644 --- a/web/handler/person/person.go +++ b/web/handler/person/person.go @@ -17,27 +17,31 @@ package person import ( "github.com/bangumi/server/ctrl" "github.com/bangumi/server/internal/character" + "github.com/bangumi/server/internal/collections" "github.com/bangumi/server/internal/person" "github.com/bangumi/server/internal/subject" ) type Person struct { - person person.Repo - c character.Repo - ctrl ctrl.Ctrl - subject subject.Repo + ctrl ctrl.Ctrl + person person.Repo + character character.Repo + subject subject.Repo + collect collections.Repo } func New( ctrl ctrl.Ctrl, person person.Repo, subject subject.Repo, - c character.Repo, + character character.Repo, + collect collections.Repo, ) (Person, error) { return Person{ - person: person, - ctrl: ctrl, - c: c, - subject: subject, + ctrl: ctrl, + person: person, + character: character, + subject: subject, + collect: collect, }, nil } diff --git a/web/res/collection.go b/web/res/collection.go index 7cb639ec9..61a0b38d8 100644 --- a/web/res/collection.go +++ b/web/res/collection.go @@ -51,3 +51,31 @@ func ConvertModelSubjectCollection(c collection.UserSubjectCollection, subject S Subject: subject, } } + +type PersonCollection struct { + ID uint32 `json:"id"` + Type uint8 `json:"type"` + Name string `json:"name"` + Images PersonImages `json:"images"` + 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 convertModelCharacterCollection(c collection.UserPersonCollection, character CharacterV0) PersonCollection { + return PersonCollection{ + ID: character.ID, + Type: character.Type, + Name: character.Name, + Images: character.Images, + CreatedAt: c.CreatedAt, + } +} diff --git a/web/routes.go b/web/routes.go index 8d46167cd..ba82adf93 100644 --- a/web/routes.go +++ b/web/routes.go @@ -66,15 +66,15 @@ func AddRouters( v0.GET("/persons/:id/image", personHandler.GetImage) v0.GET("/persons/:id/subjects", personHandler.GetRelatedSubjects) v0.GET("/persons/:id/characters", personHandler.GetRelatedCharacters) - // v0.POST("/persons/:id/collect", personHandler.CollectCharacter, mw.NeedLogin) - // v0.DELETE("/persons/:id/collect", personHandler.UnCollectCharacter, mw.NeedLogin) + v0.POST("/persons/:id/collect", personHandler.CollectPerson, mw.NeedLogin) + v0.DELETE("/persons/:id/collect", personHandler.UncollectPerson, mw.NeedLogin) v0.GET("/characters/:id", characterHandler.Get) v0.GET("/characters/:id/image", characterHandler.GetImage) v0.GET("/characters/:id/subjects", characterHandler.GetRelatedSubjects) v0.GET("/characters/:id/persons", characterHandler.GetRelatedPersons) - // v0.POST("/characters/:id/collect", characterHandler.CollectCharacter, mw.NeedLogin) - // v0.DELETE("/characters/:id/collect", characterHandler.UnCollectCharacter, mw.NeedLogin) + v0.POST("/characters/:id/collect", characterHandler.CollectCharacter, mw.NeedLogin) + v0.DELETE("/characters/:id/collect", characterHandler.UncollectCharacter, mw.NeedLogin) v0.GET("/episodes/:id", h.GetEpisode) v0.GET("/episodes", h.ListEpisode)