diff --git a/internal/search/client.go b/internal/search/client.go index d92627b9..b3b3b50a 100644 --- a/internal/search/client.go +++ b/internal/search/client.go @@ -39,6 +39,7 @@ import ( "github.com/bangumi/server/domain/gerr" "github.com/bangumi/server/internal/model" "github.com/bangumi/server/internal/subject" + "github.com/bangumi/server/internal/tag" ) // New provide a search app is AppConfig.MeiliSearchURL is empty string, return nope search client. @@ -47,6 +48,7 @@ import ( func New( cfg config.AppConfig, subjectRepo subject.Repo, + tagRepo tag.CachedRepo, log *zap.Logger, query *query.Query, ) (Client, error) { @@ -78,6 +80,7 @@ func New( subjectIndex: meili.Index("subjects"), log: log.Named("search"), subjectRepo: subjectRepo, + tagRepo: tagRepo, } if cfg.AppType != config.AppTypeCanal { @@ -136,6 +139,7 @@ func (c *client) canalInit(cfg config.AppConfig) error { type client struct { subjectRepo subject.Repo + tagRepo tag.CachedRepo meili meilisearch.ServiceManager q *query.Query subjectIndex meilisearch.IndexManager diff --git a/internal/search/handle.go b/internal/search/handle.go index f3ef51f3..2c407bc8 100644 --- a/internal/search/handle.go +++ b/internal/search/handle.go @@ -25,12 +25,16 @@ import ( "github.com/labstack/echo/v4" "github.com/meilisearch/meilisearch-go" + "github.com/samber/lo" "github.com/trim21/errgo" "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/pkg/compat" "github.com/bangumi/server/internal/pkg/generic/slice" "github.com/bangumi/server/internal/pkg/null" "github.com/bangumi/server/internal/subject" + "github.com/bangumi/server/internal/tag" + "github.com/bangumi/server/pkg/wiki" "github.com/bangumi/server/web/accessor" "github.com/bangumi/server/web/req" "github.com/bangumi/server/web/res" @@ -76,17 +80,26 @@ type hit struct { } type ReponseSubject struct { - Date string `json:"date"` - Image string `json:"image"` - Type uint8 `json:"type"` - Summary string `json:"summary"` - Name string `json:"name"` - NameCN string `json:"name_cn"` - Tags []res.SubjectTag `json:"tags"` - Score float64 `json:"score"` - ID model.SubjectID `json:"id"` - Rank uint32 `json:"rank"` - NSFW bool `json:"nsfw"` + Date *string `json:"date"` + Platform *string `json:"platform"` + Images res.SubjectImages `json:"images"` + Image string `json:"image"` + Summary string `json:"summary"` + Name string `json:"name"` + NameCN string `json:"name_cn"` + Tags []res.SubjectTag `json:"tags"` + Infobox res.V0wiki `json:"infobox"` + Rating res.Rating `json:"rating"` + Collection res.SubjectCollectionStat `json:"collection"` + ID model.SubjectID `json:"id"` + Eps uint32 `json:"eps"` + MetaTags []string `json:"meta_tags"` + Volumes uint32 `json:"volumes"` + Series bool `json:"series"` + Locked bool `json:"locked"` + NSFW bool `json:"nsfw"` + TypeID model.SubjectType `json:"type"` + Redirect model.SubjectID `json:"-"` } //nolint:funlen @@ -128,28 +141,20 @@ func (c *client) Handle(ctx echo.Context) error { return errgo.Wrap(err, "subjectRepo.GetByIDs") } + tags, err := c.tagRepo.GetByIDs(ctx.Request().Context(), ids) + if err != nil { + return errgo.Wrap(err, "tagRepo.GetByIDs") + } + var data = make([]ReponseSubject, 0, len(subjects)) for _, id := range ids { s, ok := subjects[id] if !ok { continue } - - data = append(data, ReponseSubject{ - Date: s.Date, - Image: res.SubjectImage(s.Image).Large, - Type: s.TypeID, - Summary: s.Summary, - Name: s.Name, - NameCN: s.NameCN, - Tags: slice.Map(s.Tags, func(item model.Tag) res.SubjectTag { - return res.SubjectTag{Name: item.Name, Count: item.Count} - }), - Score: s.Rating.Score, - ID: s.ID, - Rank: s.Rating.Rank, - NSFW: s.NSFW, - }) + metaTags := tags[id] + subject := toResponseSubject(s, metaTags) + data = append(data, subject) } return ctx.JSON(http.StatusOK, res.Paged{ @@ -323,3 +328,58 @@ func isDigitsOnly(s string) bool { } return true } + +func toResponseSubject(s model.Subject, metaTags []tag.Tag) ReponseSubject { + images := res.SubjectImage(s.Image) + return ReponseSubject{ + ID: s.ID, + Image: images.Large, + Images: images, + Summary: s.Summary, + Name: s.Name, + Platform: res.PlatformString(s), + NameCN: s.NameCN, + Date: null.NilString(s.Date), + Infobox: compat.V0Wiki(wiki.ParseOmitError(s.Infobox).NonZero()), + Volumes: s.Volumes, + Redirect: s.Redirect, + Eps: s.Eps, + MetaTags: lo.Map(metaTags, func(item tag.Tag, index int) string { + return item.Name + }), + Tags: slice.Map(s.Tags, func(tag model.Tag) res.SubjectTag { + return res.SubjectTag{ + Name: tag.Name, + Count: tag.Count, + } + }), + Collection: res.SubjectCollectionStat{ + OnHold: s.OnHold, + Wish: s.Wish, + Dropped: s.Dropped, + Collect: s.Collect, + Doing: s.Doing, + }, + TypeID: s.TypeID, + Series: s.Series, + Locked: s.Locked(), + NSFW: s.NSFW, + Rating: res.Rating{ + Rank: s.Rating.Rank, + Total: s.Rating.Total, + Count: res.Count{ + Field1: s.Rating.Count.Field1, + Field2: s.Rating.Count.Field2, + Field3: s.Rating.Count.Field3, + Field4: s.Rating.Count.Field4, + Field5: s.Rating.Count.Field5, + Field6: s.Rating.Count.Field6, + Field7: s.Rating.Count.Field7, + Field8: s.Rating.Count.Field8, + Field9: s.Rating.Count.Field9, + Field10: s.Rating.Count.Field10, + }, + Score: s.Rating.Score, + }, + } +} diff --git a/openapi/v0.yaml b/openapi/v0.yaml index 53cc4980..c7ea620d 100644 --- a/openapi/v0.yaml +++ b/openapi/v0.yaml @@ -143,66 +143,7 @@ paths: content: application/json: schema: - description: 用户信息 - type: object - properties: - total: - description: 搜索结果数量 - type: integer - example: 100 - limit: - description: 当前分页参数 - type: integer - example: 100 - offset: - description: 当前分页参数 - type: integer - example: 100 - data: - type: array - items: - type: object - required: - - score - - id - - rank - - tags - - name - - name_cn - - image - - date - - summary - properties: - id: - description: 条目ID - type: integer - example: 8 - type: - $ref: "#/components/schemas/SubjectType" - "date": - "type": "string" - description: 上映/开播/连载开始日期,可能为空字符串 - "image": - "type": "string" - format: url - description: 封面 - summary: - type: string - description: 条目描述 - "name": - "type": "string" - description: 条目原名 - "name_cn": - "type": "string" - description: 条目中文名 - "tags": - $ref: "#/components/schemas/SubjectTags" - "score": - description: 评分 - "type": "number" - "rank": - description: 排名 - "type": "integer" + "$ref": "#/components/schemas/Paged_Subject" "/v0/subjects": get: diff --git a/web/handler/subject/browse.go b/web/handler/subject/browse.go index 2968a425..1e08f763 100644 --- a/web/handler/subject/browse.go +++ b/web/handler/subject/browse.go @@ -58,9 +58,19 @@ func (h Subject) Browse(c echo.Context) error { if err != nil { return errgo.Wrap(err, "failed to browse subjects") } + ids := make([]model.SubjectID, 0, len(subjects)) + for _, s := range subjects { + ids = append(ids, s.ID) + } + tags, err := h.tag.GetByIDs(c.Request().Context(), ids) + if err != nil { + return errgo.Wrap(err, "failed to get tags") + } + data := make([]res.SubjectV0, 0, len(subjects)) for _, s := range subjects { - data = append(data, convertModelSubject(s, 0, nil)) + metaTags := tags[s.ID] + data = append(data, res.ToSubjectV0(s, 0, metaTags)) } return c.JSON(http.StatusOK, res.Paged{Data: data, Total: count, Limit: page.Limit, Offset: page.Offset}) diff --git a/web/handler/subject/get.go b/web/handler/subject/get.go index 17a3568a..4aa9179b 100644 --- a/web/handler/subject/get.go +++ b/web/handler/subject/get.go @@ -20,21 +20,14 @@ import ( "net/http" "github.com/labstack/echo/v4" - "github.com/samber/lo" "github.com/trim21/errgo" - "go.uber.org/zap" "github.com/bangumi/server/domain/gerr" "github.com/bangumi/server/internal/episode" "github.com/bangumi/server/internal/model" - "github.com/bangumi/server/internal/pkg/compat" - "github.com/bangumi/server/internal/pkg/generic/slice" - "github.com/bangumi/server/internal/pkg/logger" "github.com/bangumi/server/internal/pkg/null" "github.com/bangumi/server/internal/subject" - "github.com/bangumi/server/internal/tag" "github.com/bangumi/server/pkg/vars" - "github.com/bangumi/server/pkg/wiki" "github.com/bangumi/server/web/accessor" "github.com/bangumi/server/web/req" "github.com/bangumi/server/web/res" @@ -73,24 +66,7 @@ func (h Subject) Get(c echo.Context) error { return err } - return c.JSON(http.StatusOK, convertModelSubject(s, totalEpisode, metaTags)) -} - -func platformString(s model.Subject) *string { - platform, ok := vars.PlatformMap[s.TypeID][s.PlatformID] - if !ok && s.TypeID != 0 { - logger.Warn("unknown platform", - zap.Uint32("subject", s.ID), - zap.Uint8("type", s.TypeID), - zap.Uint16("platform", s.PlatformID), - ) - - return nil - } - - v := platform.String() - - return &v + return c.JSON(http.StatusOK, res.ToSubjectV0(s, totalEpisode, metaTags)) } func (h Subject) GetImage(c echo.Context) error { @@ -121,60 +97,6 @@ func (h Subject) GetImage(c echo.Context) error { return c.Redirect(http.StatusFound, l) } -func convertModelSubject(s model.Subject, totalEpisode int64, metaTags []tag.Tag) res.SubjectV0 { - return res.SubjectV0{ - TotalEpisodes: totalEpisode, - ID: s.ID, - Image: res.SubjectImage(s.Image), - Summary: s.Summary, - Name: s.Name, - Platform: platformString(s), - NameCN: s.NameCN, - Date: null.NilString(s.Date), - Infobox: compat.V0Wiki(wiki.ParseOmitError(s.Infobox).NonZero()), - Volumes: s.Volumes, - Redirect: s.Redirect, - Eps: s.Eps, - MetaTags: lo.Map(metaTags, func(item tag.Tag, index int) string { - return item.Name - }), - Tags: slice.Map(s.Tags, func(tag model.Tag) res.SubjectTag { - return res.SubjectTag{ - Name: tag.Name, - Count: tag.Count, - } - }), - Collection: res.SubjectCollectionStat{ - OnHold: s.OnHold, - Wish: s.Wish, - Dropped: s.Dropped, - Collect: s.Collect, - Doing: s.Doing, - }, - TypeID: s.TypeID, - Series: s.Series, - Locked: s.Locked(), - NSFW: s.NSFW, - Rating: res.Rating{ - Rank: s.Rating.Rank, - Total: s.Rating.Total, - Count: res.Count{ - Field1: s.Rating.Count.Field1, - Field2: s.Rating.Count.Field2, - Field3: s.Rating.Count.Field3, - Field4: s.Rating.Count.Field4, - Field5: s.Rating.Count.Field5, - Field6: s.Rating.Count.Field6, - Field7: s.Rating.Count.Field7, - Field8: s.Rating.Count.Field8, - Field9: s.Rating.Count.Field9, - Field10: s.Rating.Count.Field10, - }, - Score: s.Rating.Score, - }, - } -} - func readableRelation(destSubjectType model.SubjectType, relation uint16) string { var r, ok = vars.RelationMap[destSubjectType][relation] if !ok || relation == 1 { diff --git a/web/res/character.go b/web/res/character.go index 703b55d3..2f6ac467 100644 --- a/web/res/character.go +++ b/web/res/character.go @@ -25,7 +25,7 @@ type CharacterV0 struct { Images PersonImages `json:"images"` Summary string `json:"summary"` Name string `json:"name"` - Infobox v0wiki `json:"infobox"` + Infobox V0wiki `json:"infobox"` Stat Stat `json:"stat"` ID model.CharacterID `json:"id"` Redirect model.CharacterID `json:"-"` diff --git a/web/res/person.go b/web/res/person.go index f08c8f10..8c5410e3 100644 --- a/web/res/person.go +++ b/web/res/person.go @@ -34,7 +34,7 @@ type PersonV0 struct { Summary string `json:"summary"` Name string `json:"name"` Img string `json:"img"` - Infobox v0wiki `json:"infobox"` + Infobox V0wiki `json:"infobox"` Career []string `json:"career"` Stat Stat `json:"stat"` Redirect model.PersonID `json:"-"` diff --git a/web/res/subject.go b/web/res/subject.go index 66300e7b..c7bf3ab0 100644 --- a/web/res/subject.go +++ b/web/res/subject.go @@ -18,15 +18,22 @@ import ( "time" "github.com/samber/lo" + "go.uber.org/zap" "github.com/bangumi/server/internal/model" + "github.com/bangumi/server/internal/pkg/compat" "github.com/bangumi/server/internal/pkg/generic/slice" "github.com/bangumi/server/internal/pkg/gstr" + "github.com/bangumi/server/internal/pkg/logger" + "github.com/bangumi/server/internal/pkg/null" + "github.com/bangumi/server/internal/tag" + "github.com/bangumi/server/pkg/vars" + "github.com/bangumi/server/pkg/wiki" ) const defaultShortSummaryLength = 120 -type v0wiki = []any +type V0wiki = []any type SubjectTag struct { Name string `json:"name"` @@ -37,12 +44,12 @@ type SubjectTag struct { type SubjectV0 struct { Date *string `json:"date"` Platform *string `json:"platform"` - Image SubjectImages `json:"images"` + Images SubjectImages `json:"images"` Summary string `json:"summary"` Name string `json:"name"` NameCN string `json:"name_cn"` Tags []SubjectTag `json:"tags"` - Infobox v0wiki `json:"infobox"` + Infobox V0wiki `json:"infobox"` Rating Rating `json:"rating"` TotalEpisodes int64 `json:"total_episodes" doc:"episodes count in database"` Collection SubjectCollectionStat `json:"collection"` @@ -101,6 +108,75 @@ func ToSlimSubjectV0(s model.Subject) SlimSubjectV0 { } } +func PlatformString(s model.Subject) *string { + platform, ok := vars.PlatformMap[s.TypeID][s.PlatformID] + if !ok && s.TypeID != 0 { + logger.Warn("unknown platform", + zap.Uint32("subject", s.ID), + zap.Uint8("type", s.TypeID), + zap.Uint16("platform", s.PlatformID), + ) + + return nil + } + v := platform.String() + return &v +} + +func ToSubjectV0(s model.Subject, totalEpisode int64, metaTags []tag.Tag) SubjectV0 { + return SubjectV0{ + TotalEpisodes: totalEpisode, + ID: s.ID, + Images: SubjectImage(s.Image), + Summary: s.Summary, + Name: s.Name, + Platform: PlatformString(s), + NameCN: s.NameCN, + Date: null.NilString(s.Date), + Infobox: compat.V0Wiki(wiki.ParseOmitError(s.Infobox).NonZero()), + Volumes: s.Volumes, + Redirect: s.Redirect, + Eps: s.Eps, + MetaTags: lo.Map(metaTags, func(item tag.Tag, index int) string { + return item.Name + }), + Tags: slice.Map(s.Tags, func(tag model.Tag) SubjectTag { + return SubjectTag{ + Name: tag.Name, + Count: tag.Count, + } + }), + Collection: SubjectCollectionStat{ + OnHold: s.OnHold, + Wish: s.Wish, + Dropped: s.Dropped, + Collect: s.Collect, + Doing: s.Doing, + }, + TypeID: s.TypeID, + Series: s.Series, + Locked: s.Locked(), + NSFW: s.NSFW, + Rating: Rating{ + Rank: s.Rating.Rank, + Total: s.Rating.Total, + Count: Count{ + Field1: s.Rating.Count.Field1, + Field2: s.Rating.Count.Field2, + Field3: s.Rating.Count.Field3, + Field4: s.Rating.Count.Field4, + Field5: s.Rating.Count.Field5, + Field6: s.Rating.Count.Field6, + Field7: s.Rating.Count.Field7, + Field8: s.Rating.Count.Field8, + Field9: s.Rating.Count.Field9, + Field10: s.Rating.Count.Field10, + }, + Score: s.Rating.Score, + }, + } +} + type SubjectCollectionStat struct { OnHold uint32 `json:"on_hold"` Dropped uint32 `json:"dropped"` @@ -220,7 +296,7 @@ type IndexSubjectV0 struct { Name string `json:"name"` NameCN string `json:"name_cn"` Comment string `json:"comment"` - Infobox v0wiki `json:"infobox"` + Infobox V0wiki `json:"infobox"` ID model.SubjectID `json:"id"` TypeID model.SubjectType `json:"type"` }