* 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 <>
231 lines
5.6 KiB
Go
231 lines
5.6 KiB
Go
package util
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/pocketbase/pocketbase/core"
|
|
)
|
|
|
|
func ValidateSubcategoryRecord(app core.App, record *core.Record) error {
|
|
parentCategory := record.GetString("category")
|
|
if parentCategory == "" {
|
|
return fmt.Errorf("subcategory category is required")
|
|
}
|
|
|
|
name := record.GetString("name")
|
|
normalizedName := NormalizeCategoryName(name)
|
|
|
|
allSubcategories, err := app.FindAllRecords("subcategories")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, existing := range allSubcategories {
|
|
if existing.Id == record.Id || existing.GetString("category") != parentCategory {
|
|
continue
|
|
}
|
|
|
|
if NormalizeCategoryName(existing.GetString("name")) == normalizedName {
|
|
return fmt.Errorf("subcategory name %q collides with existing subcategory %q in the same category after normalization", name, existing.GetString("name"))
|
|
}
|
|
}
|
|
|
|
if _, err := ParseCategoryTranslations(record.Get("translations")); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func ValidateTrailSubcategoryRecord(app core.App, record *core.Record, subcategoryExplicit bool) error {
|
|
subcategoryID := record.GetString("subcategory")
|
|
if subcategoryID == "" {
|
|
return nil
|
|
}
|
|
|
|
categoryID := record.GetString("category")
|
|
if categoryID == "" {
|
|
if !subcategoryExplicit {
|
|
record.Set("subcategory", "")
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("trail subcategory requires a category")
|
|
}
|
|
|
|
subcategory, err := app.FindRecordById("subcategories", subcategoryID)
|
|
if err != nil {
|
|
if !subcategoryExplicit {
|
|
record.Set("subcategory", "")
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("trail subcategory %q does not exist: %w", subcategoryID, err)
|
|
}
|
|
|
|
parentCategory := subcategory.GetString("category")
|
|
if parentCategory != categoryID {
|
|
if !subcategoryExplicit {
|
|
record.Set("subcategory", "")
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("trail subcategory %q belongs to category %q, not %q", subcategoryID, parentCategory, categoryID)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func FindSubcategoryByNormalizedName(app core.App, categoryID string, name string) (*core.Record, error) {
|
|
normalizedName := NormalizeCategoryName(name)
|
|
if categoryID == "" || normalizedName == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
subcategories, err := app.FindRecordsByFilter(
|
|
"subcategories",
|
|
"category = {:category}",
|
|
"",
|
|
0,
|
|
0,
|
|
map[string]any{"category": categoryID},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, subcategory := range subcategories {
|
|
if NormalizeCategoryName(subcategory.GetString("name")) == normalizedName {
|
|
return subcategory, nil
|
|
}
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func ResolveCategoryAndSubcategoryByNormalizedNames(app core.App, categoryName string, subcategoryName string) (*core.Record, *core.Record, error) {
|
|
category, err := FindCategoryByNormalizedName(app, categoryName)
|
|
if err != nil || category == nil {
|
|
return category, nil, err
|
|
}
|
|
|
|
subcategory, err := FindSubcategoryByNormalizedName(app, category.Id, subcategoryName)
|
|
if err != nil {
|
|
return category, nil, err
|
|
}
|
|
|
|
return category, subcategory, nil
|
|
}
|
|
|
|
func BackfillRemoteTrailCategory(app core.App, category *core.Record) error {
|
|
if category == nil || category.Id == "" {
|
|
return nil
|
|
}
|
|
|
|
subcategoriesByName, err := normalizedSubcategoriesByName(app, category.Id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
trails, err := app.FindRecordsByFilter(
|
|
"trails",
|
|
"federated_category_name != '' && category = ''",
|
|
"",
|
|
0,
|
|
0,
|
|
nil,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
normalizedCategoryName := NormalizeCategoryName(category.GetString("name"))
|
|
for _, trail := range trails {
|
|
if NormalizeCategoryName(trail.GetString("federated_category_name")) != normalizedCategoryName {
|
|
continue
|
|
}
|
|
|
|
trail.Set("category", category.Id)
|
|
if subcategory, ok := subcategoriesByName[NormalizeCategoryName(trail.GetString("federated_subcategory_name"))]; ok {
|
|
trail.Set("subcategory", subcategory.Id)
|
|
}
|
|
|
|
if err := app.Save(trail); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func BackfillRemoteTrailSubcategory(app core.App, subcategory *core.Record) error {
|
|
if subcategory == nil || subcategory.Id == "" {
|
|
return nil
|
|
}
|
|
|
|
category, err := app.FindRecordById("categories", subcategory.GetString("category"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
trails, err := app.FindRecordsByFilter(
|
|
"trails",
|
|
"federated_subcategory_name != '' && subcategory = '' && (category = {:category} || category = '')",
|
|
"",
|
|
0,
|
|
0,
|
|
map[string]any{"category": category.Id},
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
normalizedCategoryName := NormalizeCategoryName(category.GetString("name"))
|
|
normalizedSubcategoryName := NormalizeCategoryName(subcategory.GetString("name"))
|
|
for _, trail := range trails {
|
|
categoryID := trail.GetString("category")
|
|
if categoryID == "" {
|
|
if NormalizeCategoryName(trail.GetString("federated_category_name")) != normalizedCategoryName {
|
|
continue
|
|
}
|
|
trail.Set("category", category.Id)
|
|
}
|
|
|
|
if NormalizeCategoryName(trail.GetString("federated_subcategory_name")) != normalizedSubcategoryName {
|
|
continue
|
|
}
|
|
|
|
trail.Set("subcategory", subcategory.Id)
|
|
if err := app.Save(trail); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func normalizedSubcategoriesByName(app core.App, categoryID string) (map[string]*core.Record, error) {
|
|
subcategories, err := app.FindRecordsByFilter(
|
|
"subcategories",
|
|
"category = {:category}",
|
|
"",
|
|
0,
|
|
0,
|
|
map[string]any{"category": categoryID},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
byName := make(map[string]*core.Record, len(subcategories))
|
|
for _, subcategory := range subcategories {
|
|
normalizedName := NormalizeCategoryName(subcategory.GetString("name"))
|
|
if normalizedName == "" {
|
|
continue
|
|
}
|
|
byName[normalizedName] = subcategory
|
|
}
|
|
|
|
return byName, nil
|
|
}
|