mirror of
https://github.com/yusing/godoxy.git
synced 2025-07-15 01:54:03 +02:00
fix: json marshal/unmarshal
This commit is contained in:
parent
080bbc18eb
commit
8363dfe257
5 changed files with 117 additions and 43 deletions
|
@ -34,7 +34,7 @@ type sessionClaims struct {
|
||||||
|
|
||||||
type sessionID string
|
type sessionID string
|
||||||
|
|
||||||
var oauthRefreshTokens jsonstore.Typed[oauthRefreshToken]
|
var oauthRefreshTokens jsonstore.MapStore[oauthRefreshToken]
|
||||||
|
|
||||||
var (
|
var (
|
||||||
defaultRefreshTokenExpiry = 30 * 24 * time.Hour // 1 month
|
defaultRefreshTokenExpiry = 30 * 24 * time.Hour // 1 month
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package homepage
|
package homepage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"maps"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
|
@ -15,12 +16,19 @@ type OverrideConfig struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
var overrideConfigInstance = jsonstore.Object[OverrideConfig](common.NamespaceHomepageOverrides)
|
var overrideConfigInstance = jsonstore.Object[*OverrideConfig](common.NamespaceHomepageOverrides)
|
||||||
|
|
||||||
func GetOverrideConfig() *OverrideConfig {
|
func GetOverrideConfig() *OverrideConfig {
|
||||||
return overrideConfigInstance
|
return overrideConfigInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *OverrideConfig) Initialize() {
|
||||||
|
c.ItemOverrides = make(map[string]*ItemConfig)
|
||||||
|
c.DisplayOrder = make(map[string]int)
|
||||||
|
c.CategoryOrder = make(map[string]int)
|
||||||
|
c.ItemVisibility = make(map[string]bool)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *OverrideConfig) OverrideItem(alias string, override *ItemConfig) {
|
func (c *OverrideConfig) OverrideItem(alias string, override *ItemConfig) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
@ -30,9 +38,7 @@ func (c *OverrideConfig) OverrideItem(alias string, override *ItemConfig) {
|
||||||
func (c *OverrideConfig) OverrideItems(items map[string]*ItemConfig) {
|
func (c *OverrideConfig) OverrideItems(items map[string]*ItemConfig) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
for key, value := range items {
|
maps.Copy(c.ItemOverrides, items)
|
||||||
c.ItemOverrides[key] = value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *OverrideConfig) GetOverride(alias string, item *ItemConfig) *ItemConfig {
|
func (c *OverrideConfig) GetOverride(alias string, item *ItemConfig) *ItemConfig {
|
||||||
|
|
|
@ -4,8 +4,11 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"maps"
|
||||||
|
|
||||||
"github.com/puzpuzpuz/xsync/v3"
|
"github.com/puzpuzpuz/xsync/v3"
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
"github.com/yusing/go-proxy/internal/gperr"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
|
@ -16,16 +19,29 @@ import (
|
||||||
|
|
||||||
type namespace string
|
type namespace string
|
||||||
|
|
||||||
type Typed[VT any] struct {
|
type MapStore[VT any] struct {
|
||||||
*xsync.MapOf[string, VT]
|
*xsync.MapOf[string, VT]
|
||||||
}
|
}
|
||||||
|
|
||||||
type storesMap struct {
|
type ObjectStore[Pointer Initializer] struct {
|
||||||
sync.RWMutex
|
ptr Pointer
|
||||||
m map[namespace]any
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var stores = storesMap{m: make(map[namespace]any)}
|
type Initializer interface {
|
||||||
|
Initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
type storeByNamespace struct {
|
||||||
|
sync.RWMutex
|
||||||
|
m map[namespace]store
|
||||||
|
}
|
||||||
|
|
||||||
|
type store interface {
|
||||||
|
json.Marshaler
|
||||||
|
json.Unmarshaler
|
||||||
|
}
|
||||||
|
|
||||||
|
var stores = storeByNamespace{m: make(map[namespace]store)}
|
||||||
var storesPath = common.DataDir
|
var storesPath = common.DataDir
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -44,12 +60,16 @@ func load() error {
|
||||||
stores.Lock()
|
stores.Lock()
|
||||||
defer stores.Unlock()
|
defer stores.Unlock()
|
||||||
errs := gperr.NewBuilder("failed to load data stores")
|
errs := gperr.NewBuilder("failed to load data stores")
|
||||||
for ns, store := range stores.m {
|
for ns, obj := range stores.m {
|
||||||
if err := utils.LoadJSONIfExist(filepath.Join(storesPath, string(ns)+".json"), &store); err != nil {
|
if init, ok := obj.(Initializer); ok {
|
||||||
|
init.Initialize()
|
||||||
|
}
|
||||||
|
if err := utils.LoadJSONIfExist(filepath.Join(storesPath, string(ns)+".json"), &obj); err != nil {
|
||||||
errs.Add(err)
|
errs.Add(err)
|
||||||
} else {
|
} else {
|
||||||
logging.Info().Str("name", string(ns)).Msg("store loaded")
|
logging.Info().Str("name", string(ns)).Msg("store loaded")
|
||||||
}
|
}
|
||||||
|
stores.m[ns] = obj
|
||||||
}
|
}
|
||||||
return errs.Error()
|
return errs.Error()
|
||||||
}
|
}
|
||||||
|
@ -66,41 +86,42 @@ func save() error {
|
||||||
return errs.Error()
|
return errs.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
func Store[VT any](namespace namespace) Typed[VT] {
|
func Store[VT any](namespace namespace) MapStore[VT] {
|
||||||
stores.Lock()
|
stores.Lock()
|
||||||
defer stores.Unlock()
|
defer stores.Unlock()
|
||||||
if s, ok := stores.m[namespace]; ok {
|
if s, ok := stores.m[namespace]; ok {
|
||||||
return s.(Typed[VT])
|
v, ok := s.(*MapStore[VT])
|
||||||
}
|
if !ok {
|
||||||
m := Typed[VT]{MapOf: xsync.NewMapOf[string, VT]()}
|
|
||||||
stores.m[namespace] = m
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func Object[VT any](namespace namespace) *VT {
|
|
||||||
stores.Lock()
|
|
||||||
defer stores.Unlock()
|
|
||||||
if s, ok := stores.m[namespace]; ok {
|
|
||||||
v, ok := s.(*VT)
|
|
||||||
if ok {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
panic(fmt.Errorf("type mismatch: %T != %T", s, v))
|
panic(fmt.Errorf("type mismatch: %T != %T", s, v))
|
||||||
}
|
}
|
||||||
v := new(VT)
|
return *v
|
||||||
stores.m[namespace] = v
|
}
|
||||||
return v
|
m := &MapStore[VT]{MapOf: xsync.NewMapOf[string, VT]()}
|
||||||
|
stores.m[namespace] = m
|
||||||
|
return *m
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Typed[VT]) MarshalJSON() ([]byte, error) {
|
func Object[Ptr Initializer](namespace namespace) Ptr {
|
||||||
tmp := make(map[string]VT, s.Size())
|
stores.Lock()
|
||||||
for k, v := range s.Range {
|
defer stores.Unlock()
|
||||||
tmp[k] = v
|
if s, ok := stores.m[namespace]; ok {
|
||||||
|
v, ok := s.(*ObjectStore[Ptr])
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Errorf("type mismatch: %T != %T", s, v))
|
||||||
}
|
}
|
||||||
return json.Marshal(tmp)
|
return v.ptr
|
||||||
|
}
|
||||||
|
obj := &ObjectStore[Ptr]{}
|
||||||
|
obj.init()
|
||||||
|
stores.m[namespace] = obj
|
||||||
|
return obj.ptr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Typed[VT]) UnmarshalJSON(data []byte) error {
|
func (s MapStore[VT]) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(maps.Collect(s.Range))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MapStore[VT]) UnmarshalJSON(data []byte) error {
|
||||||
tmp := make(map[string]VT)
|
tmp := make(map[string]VT)
|
||||||
if err := json.Unmarshal(data, &tmp); err != nil {
|
if err := json.Unmarshal(data, &tmp); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -111,3 +132,17 @@ func (s Typed[VT]) UnmarshalJSON(data []byte) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (obj *ObjectStore[Ptr]) init() {
|
||||||
|
obj.ptr = reflect.New(reflect.TypeFor[Ptr]().Elem()).Interface().(Ptr)
|
||||||
|
obj.ptr.Initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj ObjectStore[Ptr]) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(obj.ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj *ObjectStore[Ptr]) UnmarshalJSON(data []byte) error {
|
||||||
|
obj.init()
|
||||||
|
return json.Unmarshal(data, obj.ptr)
|
||||||
|
}
|
||||||
|
|
|
@ -12,19 +12,52 @@ func TestNewJSON(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSaveLoad(t *testing.T) {
|
func TestSaveLoadStore(t *testing.T) {
|
||||||
storesPath = t.TempDir()
|
storesPath = t.TempDir()
|
||||||
store := Store[string]("test")
|
store := Store[string]("test")
|
||||||
store.Store("a", "1")
|
store.Store("a", "1")
|
||||||
if err := save(); err != nil {
|
if err := save(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
stores.m = nil
|
|
||||||
if err := load(); err != nil {
|
if err := load(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
store = Store[string]("test")
|
loaded := Store[string]("test")
|
||||||
if v, _ := store.Load("a"); v != "1" {
|
v, ok := loaded.Load("a")
|
||||||
t.Fatal("expected 1, got", v)
|
if !ok {
|
||||||
|
t.Fatal("expected key exists")
|
||||||
|
}
|
||||||
|
if v != "1" {
|
||||||
|
t.Fatalf("expected 1, got %q", v)
|
||||||
|
}
|
||||||
|
if loaded.MapOf == store.MapOf {
|
||||||
|
t.Fatal("expected different objects")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testObject struct {
|
||||||
|
I int `json:"i"`
|
||||||
|
S string `json:"s"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*testObject) Initialize() {}
|
||||||
|
|
||||||
|
func TestSaveLoadObject(t *testing.T) {
|
||||||
|
storesPath = t.TempDir()
|
||||||
|
obj := Object[*testObject]("test")
|
||||||
|
obj.I = 1
|
||||||
|
obj.S = "1"
|
||||||
|
if err := save(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := load(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
loaded := Object[*testObject]("test")
|
||||||
|
if loaded.I != 1 || loaded.S != "1" {
|
||||||
|
t.Fatalf("expected 1, got %d, %s", loaded.I, loaded.S)
|
||||||
|
}
|
||||||
|
if loaded == obj {
|
||||||
|
t.Fatal("expected different objects")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ func getHealthInfo(r Route) map[string]string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type HealthInfoRaw struct {
|
type HealthInfoRaw struct {
|
||||||
Status health.Status `json:"status,string"`
|
Status health.Status `json:"status"`
|
||||||
Latency time.Duration `json:"latency"`
|
Latency time.Duration `json:"latency"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue