Recently added, shop sorting, and outfit cards with T5 products
CI / build (push) Successful in 34s

- The inventory's Recently Added puts T5 products added to the library in the last 30 days first.
- The shop's T5 listing follows its sort menu: Best Match ranks by name, Newest by upload date
  (oldest first ascending); ratings and prices (T5 products have neither) sort by newest.
- Outfit cards: outfit_products.php gets the outfit's community products added (not for sale),
  find_bundle.php is asked about IMVU's products only, and removing one in the card (remove_pids)
  keeps it from IMVU and forgets it in the outfit.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
dhkatzandClaude Opus 5.5 committed 2026-10-30 02:45:02 -07:00
1 parent af9158513b
commit 171e8173af
11 files changed
+463 -7

No files matched your search

+84
View File
@@ -0,0 +1,84 @@
package products
import (
"encoding/json"
"fmt"
"testing"
"time"
"git.t5de.dev/t5de/t5de-proxy/internal/hub"
)
func TestRecentlyAdded(t *testing.T) {
h, _ := newHarness(t)
hubDir := t.TempDir()
for _, id := range []string{"coatcoat22", "sofasofa22", "oldoldold2", "hairhair22"} {
writePack(t, hubDir, id, 1)
}
h.p.dirs = append(h.p.dirs, hubDir)
h.p.HubDir = hubDir
h.p.Owned = func(string) bool { return true }
h.p.refresh(true)
now := time.Now()
clothes := []hub.Category{{ID: 106}, {ID: 40}, {ID: 716}}
h.p.Library = func() []hub.LibraryEntry {
return []hub.LibraryEntry{
{Product: hub.Product{ID: "coatcoat22", Categories: clothes}, AddedAt: now.Add(-2 * time.Hour)},
{Product: hub.Product{ID: "sofasofa22", Categories: []hub.Category{{ID: 1027}, {ID: 1056}}}, AddedAt: now.Add(-time.Hour)},
{Product: hub.Product{ID: "oldoldold2", Categories: clothes}, AddedAt: now.Add(-40 * 24 * time.Hour)},
{Product: hub.Product{ID: "hairhair22"}, AddedAt: now.Add(-time.Hour)}, // no categories known: worn
}
}
local := func(id string) int64 { return h.p.byProductID(id).local }
get := func(kind string) []int64 {
_, b := h.do("GET", "http://client-dynamic.imvu.com/api/recently_added_inventory.php?type="+kind, nil, false, false)
var ids []int64
if json.Unmarshal(b, &ids) != nil {
t.Fatalf("%s: %s", kind, b)
}
return ids
}
if got, want := fmt.Sprint(get("clothing")), fmt.Sprint([]int64{local("hairhair22"), local("coatcoat22"), 31162856, 108363}); got != want {
t.Errorf("clothing %s, want %s", got, want)
}
if got, want := fmt.Sprint(get("furni")), fmt.Sprint([]int64{local("sofasofa22"), 31162856, 108363}); got != want {
t.Errorf("furni %s, want %s", got, want)
}
if got, want := fmt.Sprint(get("scenes")), fmt.Sprint([]int64{31162856, 108363}); got != want {
t.Errorf("scenes %s, want %s", got, want)
}
}
func TestShopSorting(t *testing.T) {
day := func(n int) time.Time { return time.Date(2026, 10, n, 0, 0, 0, 0, time.UTC) }
list := func() []hub.Product {
return []hub.Product{
{ID: "a", Name: "Long Red Coat", CreatedAt: day(5)},
{ID: "b", Name: "Coat", CreatedAt: day(1)},
{ID: "c", Name: "Coats & Jackets Pack", CreatedAt: day(2)},
{ID: "d", Name: "Raincoat", CreatedAt: day(4)},
{ID: "e", Name: "Hat", Creator: "coat_maker", CreatedAt: day(3)},
}
}
order := func(l []hub.Product) string {
s := ""
for _, p := range l {
s += p.ID
}
return s
}
for _, c := range []struct{ query, field, dir, want string }{
{"coat", "conversion_score_b", "desc", "bcade"}, // same name, starts with, word, inside a word, creator
{"", "conversion_score_b", "desc", "adecb"}, // no search: newest
{"coat", "id", "desc", "adecb"}, // Newest Products
{"coat", "id", "asc", "bceda"},
{"coat", "price", "asc", "adecb"}, // all free: newest
{"coat", "user_rating", "desc", "adecb"},
} {
l := list()
sortShop(l, c.query, c.field, c.dir)
if got := order(l); got != c.want {
t.Errorf("%q %s %s: %s, want %s", c.query, c.field, c.dir, got, c.want)
}
}
}