feat: Refine trail category model (#1059)
* feat: advanced trail categories * rename remote_category * fix merge issues * subcategories for plugins mapping * fix refresh * remove vertical trail filter category scrolling * cleanup * add confirm modal for disabling a category * Fix federation issues * fix review findings * redesign category settings page * remove prio badge * fix subcategory badge layout * optimize subcategory settings layout * further settings page layout optimization * fix subcategory icon position * fix * update docs --------- Co-authored-by: Christian Beutel <>
This commit is contained in:
241
db/util/category_preference.go
Normal file
241
db/util/category_preference.go
Normal file
@@ -0,0 +1,241 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
)
|
||||
|
||||
const DefaultPriorityCategoryName = "Hiking"
|
||||
|
||||
func ValidateUserCategoryPreferenceRequest(priorityExplicit bool) error {
|
||||
if priorityExplicit {
|
||||
return fmt.Errorf("category preference priority can only be changed through the reorder endpoint")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateUserSubcategoryPreferenceRequest(priorityExplicit bool) error {
|
||||
if priorityExplicit {
|
||||
return fmt.Errorf("subcategory preference priority can only be changed through the reorder endpoint")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func EnsureUserCategoryPriority(app core.App, userID, categoryID string) error {
|
||||
if userID == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
prioritized, err := app.FindRecordsByFilter(
|
||||
"user_category_preferences",
|
||||
"user = {:user} && priority > 0",
|
||||
"",
|
||||
1,
|
||||
0,
|
||||
map[string]any{"user": userID},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(prioritized) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if categoryID == "" {
|
||||
category, err := FindCategoryByNormalizedName(app, DefaultPriorityCategoryName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if category == nil {
|
||||
return nil
|
||||
}
|
||||
categoryID = category.Id
|
||||
}
|
||||
|
||||
collection, err := app.FindCollectionByNameOrId("user_category_preferences")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
existing, err := app.FindRecordsByFilter(
|
||||
"user_category_preferences",
|
||||
"user = {:user} && category = {:category}",
|
||||
"",
|
||||
1,
|
||||
0,
|
||||
map[string]any{"user": userID, "category": categoryID},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var record *core.Record
|
||||
if len(existing) > 0 {
|
||||
record = existing[0]
|
||||
} else {
|
||||
record = core.NewRecord(collection)
|
||||
record.Set("user", userID)
|
||||
record.Set("category", categoryID)
|
||||
record.Set("visible", true)
|
||||
}
|
||||
|
||||
record.Set("priority", 1)
|
||||
return app.SaveNoValidate(record)
|
||||
}
|
||||
|
||||
func ReorderUserCategoryPreferences(app core.App, userID string, categoryIDs []string) error {
|
||||
if userID == "" {
|
||||
return fmt.Errorf("authentication required")
|
||||
}
|
||||
|
||||
categories, err := app.FindAllRecords("categories")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(categoryIDs) != len(categories) {
|
||||
return fmt.Errorf("reorder request must include all categories")
|
||||
}
|
||||
|
||||
validCategories := make(map[string]struct{}, len(categories))
|
||||
for _, category := range categories {
|
||||
validCategories[category.Id] = struct{}{}
|
||||
}
|
||||
|
||||
seen := make(map[string]struct{}, len(categoryIDs))
|
||||
for _, categoryID := range categoryIDs {
|
||||
if _, ok := validCategories[categoryID]; !ok {
|
||||
return fmt.Errorf("unknown category %q", categoryID)
|
||||
}
|
||||
if _, ok := seen[categoryID]; ok {
|
||||
return fmt.Errorf("duplicate category %q", categoryID)
|
||||
}
|
||||
seen[categoryID] = struct{}{}
|
||||
}
|
||||
|
||||
return app.RunInTransaction(func(txApp core.App) error {
|
||||
collection, err := txApp.FindCollectionByNameOrId("user_category_preferences")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
existing, err := txApp.FindRecordsByFilter(
|
||||
"user_category_preferences",
|
||||
"user = {:user}",
|
||||
"",
|
||||
0,
|
||||
0,
|
||||
map[string]any{"user": userID},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
byCategory := make(map[string]*core.Record, len(existing))
|
||||
for _, record := range existing {
|
||||
byCategory[record.GetString("category")] = record
|
||||
}
|
||||
|
||||
for index, categoryID := range categoryIDs {
|
||||
record := byCategory[categoryID]
|
||||
if record == nil {
|
||||
record = core.NewRecord(collection)
|
||||
record.Set("user", userID)
|
||||
record.Set("category", categoryID)
|
||||
record.Set("visible", true)
|
||||
}
|
||||
|
||||
record.Set("priority", index+1)
|
||||
if err := txApp.SaveNoValidate(record); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func ReorderUserSubcategoryPreferences(app core.App, userID, categoryID string, subcategoryIDs []string) error {
|
||||
if userID == "" {
|
||||
return fmt.Errorf("authentication required")
|
||||
}
|
||||
if categoryID == "" {
|
||||
return fmt.Errorf("category is required")
|
||||
}
|
||||
|
||||
subcategories, err := app.FindRecordsByFilter(
|
||||
"subcategories",
|
||||
"category = {:category}",
|
||||
"",
|
||||
0,
|
||||
0,
|
||||
map[string]any{"category": categoryID},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(subcategoryIDs) != len(subcategories) {
|
||||
return fmt.Errorf("reorder request must include all subcategories for the category")
|
||||
}
|
||||
|
||||
validSubcategories := make(map[string]struct{}, len(subcategories))
|
||||
for _, subcategory := range subcategories {
|
||||
validSubcategories[subcategory.Id] = struct{}{}
|
||||
}
|
||||
|
||||
seen := make(map[string]struct{}, len(subcategoryIDs))
|
||||
for _, subcategoryID := range subcategoryIDs {
|
||||
if _, ok := validSubcategories[subcategoryID]; !ok {
|
||||
return fmt.Errorf("unknown subcategory %q", subcategoryID)
|
||||
}
|
||||
if _, ok := seen[subcategoryID]; ok {
|
||||
return fmt.Errorf("duplicate subcategory %q", subcategoryID)
|
||||
}
|
||||
seen[subcategoryID] = struct{}{}
|
||||
}
|
||||
|
||||
return app.RunInTransaction(func(txApp core.App) error {
|
||||
collection, err := txApp.FindCollectionByNameOrId("user_subcategory_preferences")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
existing, err := txApp.FindRecordsByFilter(
|
||||
"user_subcategory_preferences",
|
||||
"user = {:user}",
|
||||
"",
|
||||
0,
|
||||
0,
|
||||
map[string]any{"user": userID},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bySubcategory := make(map[string]*core.Record, len(existing))
|
||||
for _, record := range existing {
|
||||
bySubcategory[record.GetString("subcategory")] = record
|
||||
}
|
||||
|
||||
for index, subcategoryID := range subcategoryIDs {
|
||||
record := bySubcategory[subcategoryID]
|
||||
if record == nil {
|
||||
record = core.NewRecord(collection)
|
||||
record.Set("user", userID)
|
||||
record.Set("subcategory", subcategoryID)
|
||||
record.Set("visible", true)
|
||||
}
|
||||
|
||||
record.Set("priority", index+1)
|
||||
if err := txApp.SaveNoValidate(record); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user