mirror of
https://codeberg.org/VnPower/PixivFE
synced 2024-12-06 19:16:23 +01:00
core/user: use goroutines to avoid blocking on network requests
This commit is contained in:
+135
-103
@@ -13,6 +13,7 @@ import (
|
||||
"codeberg.org/vnpower/pixivfe/v2/server/session"
|
||||
"github.com/goccy/go-json"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// UserWorkCategory represents a unified structure for handling different work categories.
|
||||
@@ -143,8 +144,9 @@ type User struct {
|
||||
}
|
||||
|
||||
// GetUserProfile retrieves the user profile, including counts, artworks/bookmarks, and social data.
|
||||
//
|
||||
// Goroutines are used to avoid blocking on network requests.
|
||||
func GetUserProfile(auditor *audit.Auditor, r *http.Request, id string, currentCategory *UserWorkCategory, page int, getTags bool, mode string) (User, error) {
|
||||
var user User
|
||||
auditor.Logger.Debug("GetUserProfile called",
|
||||
zap.String("userID", id),
|
||||
zap.String("currentCategory", currentCategory.Value),
|
||||
@@ -152,67 +154,82 @@ func GetUserProfile(auditor *audit.Auditor, r *http.Request, id string, currentC
|
||||
zap.Bool("getTags", getTags),
|
||||
)
|
||||
|
||||
// Fetch user information
|
||||
userInfoURL := GetUserInformationURL(id)
|
||||
auditor.Logger.Debug("Fetching user information", zap.String("URL", userInfoURL))
|
||||
token := session.GetUserToken(r)
|
||||
resp, err := API_GET_UnwrapJson(r.Context(), auditor, userInfoURL, token, r.Header)
|
||||
if err != nil {
|
||||
auditor.SugaredLogger.Error("Failed to fetch user information", zap.Error(err))
|
||||
return user, err
|
||||
}
|
||||
var userInfo User
|
||||
var categories map[string]*UserWorkCategory
|
||||
var g errgroup.Group
|
||||
|
||||
resp, err = session.ProxyImageUrl(r, resp)
|
||||
if err != nil {
|
||||
auditor.SugaredLogger.Error("Failed to proxy image URL", zap.Error(err))
|
||||
return user, err
|
||||
}
|
||||
// Goroutine to fetch user information
|
||||
g.Go(func() error {
|
||||
// Fetch user information
|
||||
userInfoURL := GetUserInformationURL(id)
|
||||
auditor.Logger.Debug("Fetching user information", zap.String("URL", userInfoURL))
|
||||
token := session.GetUserToken(r)
|
||||
resp, err := API_GET_UnwrapJson(r.Context(), auditor, userInfoURL, token, r.Header)
|
||||
if err != nil {
|
||||
auditor.SugaredLogger.Error("Failed to fetch user information", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(resp), &user); err != nil {
|
||||
auditor.SugaredLogger.Error("Failed to unmarshal user information", zap.Error(err))
|
||||
return user, err
|
||||
}
|
||||
resp, err = session.ProxyImageUrl(r, resp)
|
||||
if err != nil {
|
||||
auditor.SugaredLogger.Error("Failed to proxy image URL", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize the user categories
|
||||
user.Categories = map[string]*UserWorkCategory{
|
||||
"illustrations": {Value: "illustrations"},
|
||||
"manga": {Value: "manga"},
|
||||
"novels": {Value: "novels"},
|
||||
"bookmarks": {Value: "bookmarks"},
|
||||
}
|
||||
if err := json.Unmarshal([]byte(resp), &userInfo); err != nil {
|
||||
auditor.SugaredLogger.Error("Failed to unmarshal user information", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// Get populated works or bookmarks based on the currentCategory and mode
|
||||
auditor.Logger.Debug("Getting populated works")
|
||||
if err := getPopulatedWorks(auditor, r, &user, id, currentCategory, page, getTags, mode); err != nil {
|
||||
auditor.SugaredLogger.Error("Failed to get populated works", zap.Error(err))
|
||||
return user, err
|
||||
}
|
||||
// Parse social data
|
||||
auditor.Logger.Debug("Parsing social data")
|
||||
if err := userInfo.parseSocial(); err != nil {
|
||||
auditor.SugaredLogger.Error("Failed to parse social data", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
auditor.Logger.Debug("Social data parsed", zap.Any("social", userInfo.Social))
|
||||
|
||||
// Parse social data
|
||||
auditor.Logger.Debug("Parsing social data")
|
||||
if err := user.parseSocial(); err != nil {
|
||||
auditor.SugaredLogger.Error("Failed to parse social data", zap.Error(err))
|
||||
// Set background image if available
|
||||
if userInfo.Background != nil {
|
||||
if url, ok := userInfo.Background["url"].(string); ok {
|
||||
userInfo.BackgroundImage = url
|
||||
auditor.Logger.Debug("Background image set", zap.String("BackgroundImage", url))
|
||||
} else {
|
||||
auditor.SugaredLogger.Warn("Background URL not found or invalid")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// Goroutine to fetch works and populate categories
|
||||
g.Go(func() error {
|
||||
var err error
|
||||
auditor.Logger.Debug("Getting populated works")
|
||||
categories, err = getPopulatedWorks(auditor, r, id, currentCategory, page, getTags, mode)
|
||||
if err != nil {
|
||||
auditor.SugaredLogger.Error("Failed to get populated works", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// Wait for both goroutines to finish
|
||||
if err := g.Wait(); err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
auditor.Logger.Debug("Social data parsed", zap.Any("social", user.Social))
|
||||
|
||||
// Set background image if available
|
||||
if user.Background != nil {
|
||||
if url, ok := user.Background["url"].(string); ok {
|
||||
user.BackgroundImage = url
|
||||
auditor.Logger.Debug("Background image set", zap.String("BackgroundImage", url))
|
||||
} else {
|
||||
auditor.SugaredLogger.Warn("Background URL not found or invalid")
|
||||
}
|
||||
}
|
||||
// Merge userInfo and categories into the final user struct
|
||||
user := userInfo
|
||||
user.Categories = categories
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// getPopulatedWorks fetches then populates information for a user's works or bookmarks, based on the currentCategory.
|
||||
// getPopulatedWorks fetches then populates information for a user's works, as well as handling their
|
||||
// frequently used tags and series data.
|
||||
//
|
||||
// It also handles a user's frequently used tags and series data.
|
||||
func getPopulatedWorks(auditor *audit.Auditor, r *http.Request, user *User, id string, currentCategory *UserWorkCategory, page int, getTags bool, mode string) error {
|
||||
// Goroutines are used to avoid blocking on network requests.
|
||||
func getPopulatedWorks(auditor *audit.Auditor, r *http.Request, id string, currentCategory *UserWorkCategory, page int, getTags bool, mode string) (map[string]*UserWorkCategory, error) {
|
||||
auditor.Logger.Debug("getPopulatedWorks called",
|
||||
zap.String("userID", id),
|
||||
zap.String("currentCategory", currentCategory.Value),
|
||||
@@ -225,80 +242,95 @@ func getPopulatedWorks(auditor *audit.Auditor, r *http.Request, user *User, id s
|
||||
categoriesData, err := fetchWorkIDsAndSeriesData(auditor, r, id, currentCategory, page)
|
||||
if err != nil {
|
||||
auditor.SugaredLogger.Error("Failed to fetch work IDs and series data", zap.Error(err))
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update user.Categories with the fetched IDs and series data
|
||||
for name, catData := range categoriesData {
|
||||
user.Categories[name] = catData
|
||||
}
|
||||
// Create an errgroup
|
||||
var g errgroup.Group
|
||||
|
||||
// Fetch and populate works for each category
|
||||
for _, cat := range user.Categories {
|
||||
// Fetch and populate works for each category in parallel
|
||||
for _, cat := range categoriesData {
|
||||
// Capture cat in local variable
|
||||
cat := cat
|
||||
|
||||
// Skip categories with no work IDs (except bookmarks)
|
||||
if cat.WorkIDs == "" && cat.Value != "bookmarks" {
|
||||
continue
|
||||
}
|
||||
|
||||
switch cat.Value {
|
||||
case "illustrations", "manga":
|
||||
auditor.Logger.Debug("Fetching and populating ArtworkBriefs", zap.String("category", cat.Value))
|
||||
artworks, safeArtworks, err := populateArtworkIDs(auditor, r, id, cat.WorkIDs)
|
||||
if err != nil {
|
||||
auditor.SugaredLogger.Error("Failed to populate artwork IDs", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
if mode == "all" {
|
||||
cat.IllustWorks = artworks
|
||||
} else if mode == "safe" {
|
||||
cat.IllustWorks = safeArtworks
|
||||
g.Go(func() error {
|
||||
switch cat.Value {
|
||||
case "illustrations", "manga":
|
||||
auditor.Logger.Debug("Fetching and populating ArtworkBriefs", zap.String("category", cat.Value))
|
||||
artworks, safeArtworks, err := populateArtworkIDs(auditor, r, id, cat.WorkIDs)
|
||||
if err != nil {
|
||||
auditor.SugaredLogger.Error("Failed to populate artwork IDs", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
if mode == "all" {
|
||||
cat.IllustWorks = artworks
|
||||
} else if mode == "safe" {
|
||||
cat.IllustWorks = safeArtworks
|
||||
}
|
||||
|
||||
case "novels":
|
||||
auditor.Logger.Debug("Fetching and populating NovelBriefs", zap.String("category", cat.Value))
|
||||
novels, safeNovels, err := populateNovelIDs(auditor, r, id, cat.WorkIDs)
|
||||
if err != nil {
|
||||
auditor.SugaredLogger.Error("Failed to populate novel IDs", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
if mode == "all" {
|
||||
cat.NovelWorks = novels
|
||||
} else if mode == "safe" {
|
||||
cat.NovelWorks = safeNovels
|
||||
}
|
||||
|
||||
case "bookmarks":
|
||||
auditor.Logger.Debug("Fetching and populating Bookmarks", zap.String("category", cat.Value))
|
||||
bookmarks, count, err := populateBookmarks(auditor, r, id, "show", page)
|
||||
if err != nil {
|
||||
auditor.SugaredLogger.Error("Failed to populate bookmarked work IDs", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
cat.IllustWorks = bookmarks
|
||||
cat.WorkCount = count
|
||||
worksPerPage := 48.0
|
||||
cat.PageLimit = int(math.Ceil(float64(count) / worksPerPage))
|
||||
}
|
||||
|
||||
case "novels":
|
||||
auditor.Logger.Debug("Fetching and populating NovelBriefs", zap.String("category", cat.Value))
|
||||
novels, safeNovels, err := populateNovelIDs(auditor, r, id, cat.WorkIDs)
|
||||
if err != nil {
|
||||
auditor.SugaredLogger.Error("Failed to populate novel IDs", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
if mode == "all" {
|
||||
cat.NovelWorks = novels
|
||||
} else if mode == "safe" {
|
||||
cat.NovelWorks = safeNovels
|
||||
return nil
|
||||
})
|
||||
|
||||
g.Go(func() error {
|
||||
// Fetch frequent tags if requested
|
||||
if getTags && cat.Value != "bookmarks" {
|
||||
auditor.Logger.Debug("Fetching frequent tags for category", zap.String("category", cat.Value))
|
||||
tags, err := fetchFrequentTags(auditor, r, cat.WorkIDs, cat)
|
||||
if err != nil {
|
||||
auditor.SugaredLogger.Error("Failed to fetch frequent tags", zap.String("category", cat.Value), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
cat.FrequentTags = tags
|
||||
}
|
||||
|
||||
case "bookmarks":
|
||||
auditor.Logger.Debug("Fetching and populating Bookmarks", zap.String("category", cat.Value))
|
||||
bookmarks, count, err := populateBookmarks(auditor, r, id, "show", page)
|
||||
if err != nil {
|
||||
auditor.SugaredLogger.Error("Failed to populate bookmarked work IDs", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
cat.IllustWorks = bookmarks
|
||||
cat.WorkCount = count
|
||||
worksPerPage := 48.0
|
||||
cat.PageLimit = int(math.Ceil(float64(count) / worksPerPage))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Fetch frequent tags if requested
|
||||
if getTags && cat.Value != "bookmarks" {
|
||||
auditor.Logger.Debug("Fetching frequent tags for category", zap.String("category", cat.Value))
|
||||
tags, err := fetchFrequentTags(auditor, r, cat.WorkIDs, cat)
|
||||
if err != nil {
|
||||
auditor.SugaredLogger.Error("Failed to fetch frequent tags", zap.String("category", cat.Value), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
cat.FrequentTags = tags
|
||||
}
|
||||
// Wait for all goroutines to finish
|
||||
if err := g.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set PageLimit for the current category
|
||||
if currentCategory != nil {
|
||||
if cat, ok := user.Categories[currentCategory.Value]; ok {
|
||||
if cat, ok := categoriesData[currentCategory.Value]; ok {
|
||||
currentCategory.PageLimit = cat.PageLimit
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return categoriesData, nil
|
||||
}
|
||||
|
||||
// fetchWorkIDsAndSeriesData retrieves artwork IDs and series information for a user.
|
||||
|
||||
Reference in New Issue
Block a user