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)