try refactoring core/user again

This commit is contained in:
perennial
2024-10-19 22:03:16 +11:00
parent f770d91a91
commit 958819ea4f
+208 -111
View File
@@ -6,6 +6,7 @@ import (
"net/http"
"sort"
"strconv"
"strings"
"codeberg.org/vnpower/pixivfe/v2/i18n"
"codeberg.org/vnpower/pixivfe/v2/server/session"
@@ -24,6 +25,13 @@ const (
CategoryNovels UserWorkCategory = "novels"
)
// UserWorkIDs holds separate ID strings for illustrations, manga, and novels.
type UserWorkIDs struct {
IllustrationIDs string
MangaIDs string
NovelIDs string
}
func (s UserWorkCategory) Validate() error {
if s != CategoryAny &&
s != CategoryAnyAlt &&
@@ -155,44 +163,134 @@ func fetchUserWorks(r *http.Request, user *User, id string, category UserWorkCat
}
user.Artworks = works
user.NovelSeries = nil
user.MangaSeries = nil
user.CategoryItemCount = count
} else {
// Fetch artworks or novels
ids, countInfo, series, err := getUserArtworksIDAndSeries(r, id, category, page)
return nil
}
// Fetch artworks or novels
userWorkIDs, countInfo, series, err := getUserArtworksIDAndSeries(r, id, category, page)
if err != nil {
return err
}
// Initialize ID strings for different work types
var illustrationIDs, mangaIDs, novelIDs string
switch category {
case CategoryIllustration:
illustrationIDs = userWorkIDs.IllustrationIDs
case CategoryManga:
mangaIDs = userWorkIDs.MangaIDs
case CategoryNovels:
novelIDs = userWorkIDs.NovelIDs
case CategoryAny, CategoryAnyAlt:
// Combine all relevant ID strings
illustrationIDs = userWorkIDs.IllustrationIDs
mangaIDs = userWorkIDs.MangaIDs
novelIDs = userWorkIDs.NovelIDs
default:
return fmt.Errorf("unsupported category: %v", category)
}
// Flags to determine which work types to fetch
fetchArtworks := false
fetchNovels := false
switch category {
case CategoryIllustration, CategoryManga, CategoryAny, CategoryAnyAlt:
fetchArtworks = true
case CategoryNovels:
fetchNovels = true
}
// Early return if there are no works to fetch
if countInfo.All == 0 {
user.Artworks = nil
user.CategoryItemCount = 0
user.NovelSeries = nil
user.MangaSeries = nil
return nil
}
// Fetch and process novels if required
if fetchNovels && novelIDs != "" {
ids := novelIDs
novels, err := fetchAndProcessItems[NovelBrief](r, id, ids, getUserNovelIDs)
if err != nil {
return err
}
user.Novels = novels
}
// Fetch and process artworks if required
if fetchArtworks && (illustrationIDs != "" || mangaIDs != "") {
// Combine Illustration and Manga IDs
ids := illustrationIDs + mangaIDs
artworks, err := fetchAndProcessItems[ArtworkBrief](r, id, ids, getUserArtworkIDs)
if err != nil {
return err
}
if countInfo.All > 0 {
artworks, novels, err := fetchAndProcessArtworks(r, id, ids, category)
if err != nil {
return err
}
if category == CategoryNovels {
user.Novels = novels
} else {
user.Artworks = artworks
user.Illustrations = filterArtworksByType(artworks, 0)
user.Manga = filterArtworksByType(artworks, 1)
}
if getTags {
user.FrequentTags, err = getUserFrequentTags(r, ids, category)
if err != nil {
return err
}
}
}
// Handle series data
user.NovelSeries, user.MangaSeries = handleSeriesData(series, category)
user.CategoryItemCount = countInfo.All
user.Artworks = artworks
user.Illustrations = filterArtworksByType(artworks, 0)
user.Manga = filterArtworksByType(artworks, 1)
}
// Fetch frequent tags if requested
if getTags {
// Combine all IDs when fetching tags for 'Any' categories
tagsIDs := illustrationIDs + mangaIDs + novelIDs
user.FrequentTags, err = getUserFrequentTags(r, tagsIDs, category)
if err != nil {
return err
}
}
// Handle series data
user.NovelSeries, user.MangaSeries = handleSeriesData(series, category)
user.CategoryItemCount = countInfo.All
return nil
}
// The HasID interface allows for generic handling of the ArtworkBrief
// and NovelBrief struct structs, which both contaisn an ID string.
type HasID interface {
GetID() string
}
// GetID implements the Identifiable interface for the ArtworkBrief struct.
func (a ArtworkBrief) GetID() string {
return a.ID
}
// GetID implements the Identifiable interface for the NovelBrief struct.
func (n NovelBrief) GetID() string {
return n.ID
}
// fetchAndProcessItems is a generic function that fetches and processes items of type T,
// where T must implement the HasID interface.
func fetchAndProcessItems[T HasID](
r *http.Request,
id, ids string,
fetchFunc func(*http.Request, string, string) ([]T, error),
) ([]T, error) {
items, err := fetchFunc(r, id, ids)
if err != nil {
return nil, err
}
sort.Slice(items, func(i, j int) bool {
return numberGreaterThan(items[i].GetID(), items[j].GetID())
})
return items, nil
}
// filterArtworksByType filters artworks based on the IllustType.
func filterArtworksByType(artworks []ArtworkBrief, illustType int) []ArtworkBrief {
filtered := make([]ArtworkBrief, 0)
@@ -210,8 +308,8 @@ func filterArtworksByType(artworks []ArtworkBrief, illustType int) []ArtworkBrie
// don't exist for a user.
type IntStringMap map[int]string
// unmarshalJSON implements custom unmarshalling for IntStringMap.
func (m *IntStringMap) unmarshalJSON(data []byte) error {
// UnmarshalJSON implements custom unmarshalling for IntStringMap.
func (m *IntStringMap) UnmarshalJSON(data []byte) error {
// Check if the data is an empty array.
if string(data) == "[]" {
*m = make(map[int]string)
@@ -242,19 +340,18 @@ func (m *IntStringMap) unmarshalJSON(data []byte) error {
}
// getUserArtworksIDAndSeries retrieves artwork IDs and series information for a user.
func getUserArtworksIDAndSeries(r *http.Request, id string, category UserWorkCategory, page int) (idsString string, countInfo CountInfo, series json.RawMessage, err error) {
// It now returns separate ID strings for illustrations, manga, and novels encapsulated in UserWorkIDs.
func getUserArtworksIDAndSeries(r *http.Request, id string, category UserWorkCategory, page int) (userWorkIDs UserWorkIDs, countInfo CountInfo, series json.RawMessage, err error) {
URL := GetUserArtworksURL(id)
resp, err := API_GET_UnwrapJson(r.Context(), URL, "", r.Header)
if err != nil {
return "", CountInfo{}, nil, err
return UserWorkIDs{}, CountInfo{}, nil, err
}
resp = session.ProxyImageUrl(r, resp)
var body struct {
// *json.RawMessage allows us to easily check whether
// a field exists by verifying if pointer != nil
Illusts *json.RawMessage `json:"illusts"`
Manga *json.RawMessage `json:"manga"`
MangaSeries *json.RawMessage `json:"mangaSeries"`
@@ -264,108 +361,108 @@ func getUserArtworksIDAndSeries(r *http.Request, id string, category UserWorkCat
err = json.Unmarshal([]byte(resp), &body)
if err != nil {
return "", CountInfo{}, nil, fmt.Errorf("failed to unmarshal response body: %w", err)
return UserWorkIDs{}, CountInfo{}, nil, fmt.Errorf("failed to unmarshal response body: %w", err)
}
var ids []int
countInfo = CountInfo{}
var illusts IntStringMap
var manga IntStringMap
var novels IntStringMap
if category == CategoryIllustration || category == CategoryAny || category == CategoryAnyAlt {
if body.Illusts != nil && len(*body.Illusts) > 0 {
if err = json.Unmarshal(*body.Illusts, &illusts); err != nil {
fmt.Printf("Error unmarshalling illusts: %v\n", err)
illusts = make(IntStringMap)
}
for k := range illusts {
ids = append(ids, k)
}
countInfo.All += len(illusts)
countInfo.Illustrations = len(illusts)
var illustIDs []int
var mangaIDs []int
var novelIDs []int
// Process Illustrations
if body.Illusts != nil && len(*body.Illusts) > 0 {
if err = json.Unmarshal(*body.Illusts, &illusts); err != nil {
fmt.Printf("Error unmarshalling illusts: %v\n", err)
illusts = make(IntStringMap)
}
for k := range illusts {
illustIDs = append(illustIDs, k)
}
countInfo.All += len(illusts)
countInfo.Illustrations = len(illusts)
}
// Process Manga
if body.Manga != nil && len(*body.Manga) > 0 {
if err = json.Unmarshal(*body.Manga, &manga); err != nil {
fmt.Printf("Error unmarshalling manga: %v\n", err)
manga = make(IntStringMap)
}
for k := range manga {
mangaIDs = append(mangaIDs, k)
}
countInfo.All += len(manga)
countInfo.Manga = len(manga)
if body.MangaSeries != nil {
series = *body.MangaSeries
}
}
if category == CategoryManga || category == CategoryAny {
if body.Manga != nil && len(*body.Manga) > 0 {
if err = json.Unmarshal(*body.Manga, &manga); err != nil {
fmt.Printf("Error unmarshalling manga: %v\n", err)
manga = make(IntStringMap)
}
for k := range manga {
ids = append(ids, k)
}
countInfo.All += len(manga)
countInfo.Manga = len(manga)
if body.MangaSeries != nil {
series = *body.MangaSeries
}
// Process Novels
if body.Novels != nil && len(*body.Novels) > 0 {
if err = json.Unmarshal(*body.Novels, &novels); err != nil {
fmt.Printf("Error unmarshalling novels: %v\n", err)
novels = make(IntStringMap)
}
for k := range novels {
novelIDs = append(novelIDs, k)
}
countInfo.All += len(novels)
countInfo.Novels = len(novels)
if body.NovelSeries != nil {
series = *body.NovelSeries
}
}
if category == CategoryNovels {
if body.Novels != nil && len(*body.Novels) > 0 {
if err = json.Unmarshal(*body.Novels, &novels); err != nil {
fmt.Printf("Error unmarshalling novels: %v\n", err)
novels = make(IntStringMap)
}
for k := range novels {
ids = append(ids, k)
}
countInfo.All += len(novels)
countInfo.Novels = len(novels)
if body.NovelSeries != nil {
series = *body.NovelSeries
}
}
}
sort.Sort(sort.Reverse(sort.IntSlice(ids)))
// Sort IDs in descending order
sort.Sort(sort.Reverse(sort.IntSlice(illustIDs)))
sort.Sort(sort.Reverse(sort.IntSlice(mangaIDs)))
sort.Sort(sort.Reverse(sort.IntSlice(novelIDs)))
worksPerPage := 30.0
start, end, err := computeSliceBounds(page, worksPerPage, len(ids))
// Compute slice bounds for each category
startIllust, endIllust, err := computeSliceBounds(page, worksPerPage, len(illustIDs))
if err != nil {
return "", CountInfo{}, nil, err
}
// Build the IDs string for the API request
for _, k := range ids[start:end] {
idsString += fmt.Sprintf("&ids[]=%d", k)
}
return idsString, countInfo, series, nil
}
func fetchAndProcessArtworks(r *http.Request, id, ids string, category UserWorkCategory) ([]ArtworkBrief, []NovelBrief, error) {
var artworks []ArtworkBrief
var novels []NovelBrief
var err error
if category == CategoryNovels {
novels, err = getUserNovelIDs(r, id, ids)
} else {
artworks, err = getUserArtworkIDs(r, id, ids)
return UserWorkIDs{}, CountInfo{}, nil, err
}
startManga, endManga, err := computeSliceBounds(page, worksPerPage, len(mangaIDs))
if err != nil {
return nil, nil, err
return UserWorkIDs{}, CountInfo{}, nil, err
}
// Sort the works
if category == CategoryNovels {
sort.Slice(novels[:], func(i, j int) bool {
return numberGreaterThan(novels[i].ID, novels[j].ID)
})
} else {
sort.Slice(artworks[:], func(i, j int) bool {
return numberGreaterThan(artworks[i].ID, artworks[j].ID)
})
startNovel, endNovel, err := computeSliceBounds(page, worksPerPage, len(novelIDs))
if err != nil {
return UserWorkIDs{}, CountInfo{}, nil, err
}
return artworks, novels, nil
// Build the ID strings for each category
var idsBuilder strings.Builder
for _, k := range illustIDs[startIllust:endIllust] {
idsBuilder.WriteString(fmt.Sprintf("&ids[]=%d", k))
}
userWorkIDs.IllustrationIDs = idsBuilder.String()
idsBuilder.Reset()
for _, k := range mangaIDs[startManga:endManga] {
idsBuilder.WriteString(fmt.Sprintf("&ids[]=%d", k))
}
userWorkIDs.MangaIDs = idsBuilder.String()
idsBuilder.Reset()
for _, k := range novelIDs[startNovel:endNovel] {
idsBuilder.WriteString(fmt.Sprintf("&ids[]=%d", k))
}
userWorkIDs.NovelIDs = idsBuilder.String()
return userWorkIDs, countInfo, series, nil
}
func handleSeriesData(series json.RawMessage, category UserWorkCategory) ([]NovelSeries, []MangaSeries) {