From 6add2f220edc984249cbac7392206e630f881d8c Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Fri, 9 Jan 2026 17:16:46 +0700 Subject: [PATCH] Refactor storage layer to use key-value template pattern Extract common key-value storage operations into internal/storage/kvtpl/ template directory. This consolidates duplicated logic from both leveldb and inmemory implementations into reusable generic functions. Key changes: - Create kvtpl/ package with generic KV storage operations - Consolidate iterator implementation using generics - Update migration functions to use new template structure - Refactor tests to work with new storage interface This prepares the codebase for easier addition of new storage backends by providing a consistent template layer. --- internal/storage/inmemory/auth_user.go | 129 ------------ internal/storage/inmemory/config.go | 8 +- internal/storage/inmemory/database.go | 100 ++++++++++ internal/storage/inmemory/domain.go | 183 ------------------ internal/storage/inmemory/iterator.go | 100 ++++------ internal/storage/inmemory/provider.go | 118 ----------- internal/storage/inmemory/session.go | 101 ---------- internal/storage/inmemory/user.go | 105 ---------- internal/storage/inmemory/zone.go | 116 ----------- internal/storage/interface.go | 20 ++ internal/storage/{leveldb => kvtpl}/auth.go | 56 +++--- internal/storage/kvtpl/domain-log.go | 122 ++++++++++++ internal/storage/{leveldb => kvtpl}/domain.go | 60 +++--- .../storage/{leveldb => kvtpl}/insights.go | 19 +- internal/storage/kvtpl/iterator.go | 120 ++++++++++++ internal/storage/kvtpl/provider.go | 112 +++++++++++ internal/storage/kvtpl/session.go | 107 ++++++++++ .../insight.go => kvtpl/template.go} | 39 ++-- .../{leveldb => kvtpl}/updates-from-0.go | 39 ++-- .../{leveldb => kvtpl}/updates-from-1.go | 66 ++++--- .../{leveldb => kvtpl}/updates-from-2.go | 133 ++++++------- .../{leveldb => kvtpl}/updates-from-3.go | 34 ++-- .../{leveldb => kvtpl}/updates-from-4.go | 2 +- .../{leveldb => kvtpl}/updates-from-5.go | 2 +- .../{leveldb => kvtpl}/updates-from-6.go | 2 +- .../{leveldb => kvtpl}/updates-from-7.go | 2 +- .../storage/{leveldb => kvtpl}/updates.go | 36 ++-- internal/storage/{leveldb => kvtpl}/user.go | 52 ++--- internal/storage/kvtpl/zone.go | 96 +++++++++ internal/storage/leveldb/config.go | 8 +- internal/storage/leveldb/database.go | 30 ++- internal/storage/leveldb/domain-log.go | 107 ---------- internal/storage/leveldb/iterator.go | 95 ++------- internal/storage/leveldb/provider.go | 138 ------------- internal/storage/leveldb/session.go | 122 ------------ internal/storage/leveldb/zone.go | 108 ----------- internal/usecase/authentication_test.go | 48 +++-- internal/usecase/authuser/authuser_test.go | 13 +- internal/usecase/domain/domain_test.go | 108 ++++++----- internal/usecase/domain_log/domainlog_test.go | 59 +++--- internal/usecase/provider/provider_test.go | 86 ++++---- internal/usecase/session/session_test.go | 96 +++++---- internal/usecase/user/user_test.go | 15 +- model/errors.go | 1 + model/iterators.go | 6 +- 45 files changed, 1284 insertions(+), 1835 deletions(-) delete mode 100644 internal/storage/inmemory/auth_user.go delete mode 100644 internal/storage/inmemory/domain.go delete mode 100644 internal/storage/inmemory/provider.go delete mode 100644 internal/storage/inmemory/session.go delete mode 100644 internal/storage/inmemory/user.go delete mode 100644 internal/storage/inmemory/zone.go rename internal/storage/{leveldb => kvtpl}/auth.go (56%) create mode 100644 internal/storage/kvtpl/domain-log.go rename internal/storage/{leveldb => kvtpl}/domain.go (55%) rename internal/storage/{leveldb => kvtpl}/insights.go (76%) create mode 100644 internal/storage/kvtpl/iterator.go create mode 100644 internal/storage/kvtpl/provider.go create mode 100644 internal/storage/kvtpl/session.go rename internal/storage/{inmemory/insight.go => kvtpl/template.go} (60%) rename internal/storage/{leveldb => kvtpl}/updates-from-0.go (77%) rename internal/storage/{leveldb => kvtpl}/updates-from-1.go (72%) rename internal/storage/{leveldb => kvtpl}/updates-from-2.go (72%) rename internal/storage/{leveldb => kvtpl}/updates-from-3.go (74%) rename internal/storage/{leveldb => kvtpl}/updates-from-4.go (95%) rename internal/storage/{leveldb => kvtpl}/updates-from-5.go (95%) rename internal/storage/{leveldb => kvtpl}/updates-from-6.go (96%) rename internal/storage/{leveldb => kvtpl}/updates-from-7.go (99%) rename internal/storage/{leveldb => kvtpl}/updates.go (63%) rename internal/storage/{leveldb => kvtpl}/user.go (59%) create mode 100644 internal/storage/kvtpl/zone.go delete mode 100644 internal/storage/leveldb/domain-log.go delete mode 100644 internal/storage/leveldb/provider.go delete mode 100644 internal/storage/leveldb/session.go delete mode 100644 internal/storage/leveldb/zone.go diff --git a/internal/storage/inmemory/auth_user.go b/internal/storage/inmemory/auth_user.go deleted file mode 100644 index 4be41264..00000000 --- a/internal/storage/inmemory/auth_user.go +++ /dev/null @@ -1,129 +0,0 @@ -// This file is part of the happyDomain (R) project. -// Copyright (c) 2020-2025 happyDomain -// Authors: Pierre-Olivier Mercier, et al. -// -// This program is offered under a commercial and under the AGPL license. -// For commercial licensing, contact us at . -// -// For AGPL licensing: -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package inmemory - -import ( - "git.happydns.org/happyDomain/model" -) - -func (s *InMemoryStorage) ListAllAuthUsers() (happydns.Iterator[happydns.UserAuth], error) { - s.mu.Lock() - defer s.mu.Unlock() - - return NewInMemoryIterator[happydns.UserAuth](&s.authUsers), nil -} - -// ListAuthUsers retrieves the list of known Users. -func (s *InMemoryStorage) ListAuthUsers() (happydns.UserAuths, error) { - s.mu.Lock() - defer s.mu.Unlock() - - var users happydns.UserAuths - for _, user := range s.authUsers { - users = append(users, user) - } - - return users, nil -} - -// GetAuthUser retrieves the User with the given identifier. -func (s *InMemoryStorage) GetAuthUser(id happydns.Identifier) (*happydns.UserAuth, error) { - s.mu.Lock() - defer s.mu.Unlock() - - user, exists := s.authUsers[id.String()] - if !exists { - return nil, happydns.ErrAuthUserNotFound - } - - return user, nil -} - -// GetAuthUserByEmail retrieves the User with the given email address. -func (s *InMemoryStorage) GetAuthUserByEmail(email string) (*happydns.UserAuth, error) { - s.mu.Lock() - defer s.mu.Unlock() - - userid, exists := s.authUsersByEmail[email] - if !exists { - return nil, happydns.ErrAuthUserNotFound - } - - user, exists := s.authUsers[userid.String()] - if !exists { - return nil, happydns.ErrAuthUserNotFound - } - - return user, nil -} - -// AuthUserExists checks if the given email address is already associated to an User. -func (s *InMemoryStorage) AuthUserExists(email string) (bool, error) { - s.mu.Lock() - defer s.mu.Unlock() - - _, exists := s.authUsersByEmail[email] - return exists, nil -} - -// CreateAuthUser creates a record in the database for the given User. -func (s *InMemoryStorage) CreateAuthUser(user *happydns.UserAuth) (err error) { - s.mu.Lock() - defer s.mu.Unlock() - - user.Id, err = happydns.NewRandomIdentifier() - s.authUsers[user.Id.String()] = user - s.authUsersByEmail[user.Email] = user.Id - return -} - -// UpdateAuthUser updates the fields of the given User. -func (s *InMemoryStorage) UpdateAuthUser(user *happydns.UserAuth) error { - s.mu.Lock() - defer s.mu.Unlock() - - s.authUsers[user.Id.String()] = user - s.authUsersByEmail[user.Email] = user.Id - return nil -} - -// DeleteAuthUser removes the given User from the database. -func (s *InMemoryStorage) DeleteAuthUser(user *happydns.UserAuth) error { - s.mu.Lock() - defer s.mu.Unlock() - - delete(s.authUsers, user.Id.String()) - delete(s.authUsersByEmail, user.Email) - - return nil -} - -// ClearAuthUsers deletes all AuthUsers present in the database. -func (s *InMemoryStorage) ClearAuthUsers() error { - s.mu.Lock() - defer s.mu.Unlock() - - s.authUsers = make(map[string]*happydns.UserAuth) - s.authUsersByEmail = make(map[string]happydns.Identifier) - - return nil -} diff --git a/internal/storage/inmemory/config.go b/internal/storage/inmemory/config.go index 7a5e24ed..8d24e4f0 100644 --- a/internal/storage/inmemory/config.go +++ b/internal/storage/inmemory/config.go @@ -23,6 +23,7 @@ package inmemory import ( "git.happydns.org/happyDomain/internal/storage" + kv "git.happydns.org/happyDomain/internal/storage/kvtpl" ) func init() { @@ -30,5 +31,10 @@ func init() { } func Instantiate() (storage.Storage, error) { - return NewInMemoryStorage() + db, err := NewInMemoryStorage() + if err != nil { + return nil, err + } + + return kv.NewKVDatabase(db) } diff --git a/internal/storage/inmemory/database.go b/internal/storage/inmemory/database.go index aaac70c4..824d0cc9 100644 --- a/internal/storage/inmemory/database.go +++ b/internal/storage/inmemory/database.go @@ -22,16 +22,22 @@ package inmemory import ( + "encoding/json" + "fmt" "log" + "sort" + "strings" "sync" "time" + "git.happydns.org/happyDomain/internal/storage" "git.happydns.org/happyDomain/model" ) // InMemoryStorage implements the Storage interface using in-memory data structures. type InMemoryStorage struct { mu sync.Mutex + data map[string][]byte // Generic key-value store for KVStorage interface authUsers map[string]*happydns.UserAuth authUsersByEmail map[string]happydns.Identifier domains map[string]*happydns.Domain @@ -49,6 +55,7 @@ type InMemoryStorage struct { // NewInMemoryStorage creates a new instance of InMemoryStorage. func NewInMemoryStorage() (*InMemoryStorage, error) { return &InMemoryStorage{ + data: make(map[string][]byte), authUsers: make(map[string]*happydns.UserAuth), authUsersByEmail: make(map[string]happydns.Identifier), domains: make(map[string]*happydns.Domain), @@ -85,3 +92,96 @@ func (s *InMemoryStorage) Close() error { // No connection to close for in-memory storage. return nil } + +// DecodeData decodes data from the interface (expected to be []byte) into v. +func (s *InMemoryStorage) DecodeData(data interface{}, v interface{}) error { + b, ok := data.([]byte) + if !ok { + return fmt.Errorf("data to decode are not in []byte format (%T)", data) + } + return json.Unmarshal(b, v) +} + +// Has checks if a key exists in the storage. +func (s *InMemoryStorage) Has(key string) (bool, error) { + s.mu.Lock() + defer s.mu.Unlock() + _, exists := s.data[key] + return exists, nil +} + +// Get retrieves a value by key and decodes it into v. +func (s *InMemoryStorage) Get(key string, v interface{}) error { + s.mu.Lock() + defer s.mu.Unlock() + data, exists := s.data[key] + if !exists { + return happydns.ErrNotFound + } + return json.Unmarshal(data, v) +} + +// Put stores a value with the given key. +func (s *InMemoryStorage) Put(key string, v interface{}) error { + data, err := json.Marshal(v) + if err != nil { + return err + } + s.mu.Lock() + defer s.mu.Unlock() + s.data[key] = data + return nil +} + +// FindIdentifierKey finds a unique key with the given prefix. +func (s *InMemoryStorage) FindIdentifierKey(prefix string) (key string, id happydns.Identifier, err error) { + s.mu.Lock() + defer s.mu.Unlock() + + for { + id, err = happydns.NewRandomIdentifier() + if err != nil { + return + } + key = fmt.Sprintf("%s%s", prefix, id.String()) + + if _, exists := s.data[key]; !exists { + return + } + } +} + +// Delete removes a key from the storage. +func (s *InMemoryStorage) Delete(key string) error { + s.mu.Lock() + defer s.mu.Unlock() + delete(s.data, key) + return nil +} + +// Search returns an iterator for all keys with the given prefix. +func (s *InMemoryStorage) Search(prefix string) storage.Iterator { + s.mu.Lock() + defer s.mu.Unlock() + + // Collect all matching keys + var keys []string + for k := range s.data { + if strings.HasPrefix(k, prefix) { + keys = append(keys, k) + } + } + + // Sort keys for consistent iteration order + sort.Strings(keys) + + // Copy data for the iterator to avoid concurrent access issues + data := make(map[string][]byte, len(keys)) + for _, k := range keys { + dataCopy := make([]byte, len(s.data[k])) + copy(dataCopy, s.data[k]) + data[k] = dataCopy + } + + return NewKVIterator(keys, data) +} diff --git a/internal/storage/inmemory/domain.go b/internal/storage/inmemory/domain.go deleted file mode 100644 index aa3972f7..00000000 --- a/internal/storage/inmemory/domain.go +++ /dev/null @@ -1,183 +0,0 @@ -// This file is part of the happyDomain (R) project. -// Copyright (c) 2020-2025 happyDomain -// Authors: Pierre-Olivier Mercier, et al. -// -// This program is offered under a commercial and under the AGPL license. -// For commercial licensing, contact us at . -// -// For AGPL licensing: -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package inmemory - -import ( - "slices" - - "git.happydns.org/happyDomain/model" -) - -func (s *InMemoryStorage) ListAllDomains() (happydns.Iterator[happydns.Domain], error) { - s.mu.Lock() - defer s.mu.Unlock() - - return NewInMemoryIterator[happydns.Domain](&s.domains), nil -} - -// ListDomains retrieves all Domains associated to the given User. -func (s *InMemoryStorage) ListDomains(u *happydns.User) ([]*happydns.Domain, error) { - s.mu.Lock() - defer s.mu.Unlock() - - var domains []*happydns.Domain - for _, domain := range s.domains { - if domain.Owner.Equals(u.Id) { - domains = append(domains, domain) - } - } - - return domains, nil -} - -// GetDomain retrieves the Domain with the given id and owned by the given User. -func (s *InMemoryStorage) GetDomain(id happydns.Identifier) (*happydns.Domain, error) { - s.mu.Lock() - defer s.mu.Unlock() - - domain, exists := s.domains[id.String()] - if !exists { - return nil, happydns.ErrDomainNotFound - } - - return domain, nil -} - -// GetDomainByDN is like GetDomain but look for the domain name instead of identifier. -func (s *InMemoryStorage) GetDomainByDN(u *happydns.User, dn string) (ret []*happydns.Domain, err error) { - s.mu.Lock() - defer s.mu.Unlock() - - for _, domain := range s.domains { - if domain.DomainName == dn { - ret = append(ret, domain) - } - } - - if len(ret) == 0 { - return nil, happydns.ErrDomainNotFound - } - - return -} - -// CreateDomain creates a record in the database for the given Domain. -func (s *InMemoryStorage) CreateDomain(domain *happydns.Domain) (err error) { - s.mu.Lock() - defer s.mu.Unlock() - - domain.Id, err = happydns.NewRandomIdentifier() - s.domains[domain.Id.String()] = domain - - return -} - -// UpdateDomain updates the fields of the given Domain. -func (s *InMemoryStorage) UpdateDomain(domain *happydns.Domain) error { - s.mu.Lock() - defer s.mu.Unlock() - - s.domains[domain.Id.String()] = domain - - return nil -} - -// DeleteDomain removes the given Domain from the database. -func (s *InMemoryStorage) DeleteDomain(domainid happydns.Identifier) error { - s.mu.Lock() - defer s.mu.Unlock() - - delete(s.domains, domainid.String()) - return nil -} - -// ClearDomains deletes all Domains present in the database. -func (s *InMemoryStorage) ClearDomains() error { - s.mu.Lock() - defer s.mu.Unlock() - - s.domains = make(map[string]*happydns.Domain) - return nil -} - -// DOMAIN LOGS -------------------------------------------------- - -func (s *InMemoryStorage) ListAllDomainLogs() (happydns.Iterator[happydns.DomainLogWithDomainId], error) { - s.mu.Lock() - defer s.mu.Unlock() - - return NewInMemoryIterator[happydns.DomainLogWithDomainId](&s.domainLogs), nil -} - -// ListDomainLogs retrieves the logs for the given Domain. -func (s *InMemoryStorage) ListDomainLogs(domain *happydns.Domain) (dlogs []*happydns.DomainLog, err error) { - s.mu.Lock() - defer s.mu.Unlock() - - for _, logid := range s.domainLogsByDomains[domain.Id.String()] { - dlogs = append(dlogs, &s.domainLogs[logid.String()].DomainLog) - } - - return -} - -// CreateDomainLog creates a log entry for the given Domain. -func (s *InMemoryStorage) CreateDomainLog(domain *happydns.Domain, log *happydns.DomainLog) (err error) { - s.mu.Lock() - defer s.mu.Unlock() - - log.Id, err = happydns.NewRandomIdentifier() - if err != nil { - return - } - - s.domainLogs[log.Id.String()] = &happydns.DomainLogWithDomainId{DomainLog: *log, DomainId: domain.Id} - s.domainLogsByDomains[domain.Id.String()] = append(s.domainLogsByDomains[domain.Id.String()], &log.Id) - return -} - -// UpdateDomainLog updates a log entry for the given Domain. -func (s *InMemoryStorage) UpdateDomainLog(domain *happydns.Domain, log *happydns.DomainLog) error { - s.mu.Lock() - defer s.mu.Unlock() - - s.domainLogs[log.Id.String()] = &happydns.DomainLogWithDomainId{DomainLog: *log, DomainId: domain.Id} - return nil -} - -// DeleteDomainLog deletes a log entry for the given Domain. -func (s *InMemoryStorage) DeleteDomainLog(domain *happydns.Domain, log *happydns.DomainLog) error { - s.mu.Lock() - defer s.mu.Unlock() - - delete(s.domainLogs, log.Id.String()) - - i := slices.IndexFunc(s.domainLogsByDomains[domain.Id.String()], func(e *happydns.Identifier) bool { - return e.Equals(log.Id) - }) - if i == -1 { - return happydns.ErrDomainLogNotFound - } - s.domainLogsByDomains[domain.Id.String()] = append(s.domainLogsByDomains[domain.Id.String()][:i], s.domainLogsByDomains[domain.Id.String()][i+1:]...) - - return nil -} diff --git a/internal/storage/inmemory/iterator.go b/internal/storage/inmemory/iterator.go index dae4966a..4b1176fe 100644 --- a/internal/storage/inmemory/iterator.go +++ b/internal/storage/inmemory/iterator.go @@ -21,81 +21,55 @@ package inmemory -import ( - "encoding/json" - "iter" - "maps" -) - -// InMemoryIterator provides an iterator over a map[string]*T. -type InMemoryIterator[T any] struct { - origin *map[string]*T - next func() (string, *T, bool) - stop func() - key string - item *T - err error - decode func([]byte, interface{}) error +// KVIterator implements the storage.Iterator interface for in-memory KVStorage. +type KVIterator struct { + keys []string + data map[string][]byte + index int + current string } -// NewInMemoryIterator constructs an iterator over a map[string]*T. -// You can pass any map like map[string]*UserAuth, map[string]*Domain, etc. -func NewInMemoryIterator[T any](m *map[string]*T) *InMemoryIterator[T] { - next, stop := iter.Pull2(maps.All[map[string]*T](*m)) - return &InMemoryIterator[T]{ - origin: m, - next: next, - stop: stop, +// NewKVIterator creates a new iterator for the given keys and data. +func NewKVIterator(keys []string, data map[string][]byte) *KVIterator { + return &KVIterator{ + keys: keys, + data: data, + index: -1, } } -// NewInMemoryIteratorCustomDecode creates a new LevelDBIterator instance for the given LevelDB iterator and decode function. -func NewInMemoryIteratorCustomDecode[T any](m *map[string]*T, decodeFunc func([]byte, interface{}) error) *InMemoryIterator[T] { - next, stop := iter.Pull2(maps.All[map[string]*T](*m)) - return &InMemoryIterator[T]{ - origin: m, - next: next, - stop: stop, - decode: decodeFunc, +// Next moves the iterator to the next item. +func (it *KVIterator) Next() bool { + it.index++ + if it.index >= len(it.keys) { + return false } + it.current = it.keys[it.index] + return true } -// Next advances the iterator to the next item. -func (it *InMemoryIterator[T]) Next() (valid bool) { - it.key, it.item, valid = it.next() - return +// Key returns the current key. +func (it *KVIterator) Key() string { + if it.index < 0 || it.index >= len(it.keys) { + return "" + } + return it.current } -// NextWithError advances the iterator to the next item. -func (it *InMemoryIterator[T]) NextWithError() (valid bool) { - it.key, it.item, valid = it.next() - return +// Value returns the current value. +func (it *KVIterator) Value() interface{} { + if it.index < 0 || it.index >= len(it.keys) { + return []byte{} + } + return it.data[it.current] } -// Item returns the current item pointed to by the iterator. -func (it *InMemoryIterator[T]) Item() *T { - return it.item +// Valid returns whether the iterator is at a valid position. +func (it *KVIterator) Valid() bool { + return it.index >= 0 && it.index < len(it.keys) } -// DropItem deletes the key currently pointed to by the iterator. -func (it *InMemoryIterator[T]) DropItem() error { - delete(*it.origin, it.key) - return nil -} - -// Raw returns the raw (non-decoded) value at the current iterator position. -// Should only be called after a successful call to Next(). -func (it *InMemoryIterator[T]) Raw() []byte { - j, _ := json.Marshal(it.item) - return j -} - -// Err returns any error encountered during iteration (always nil here). -func (it *InMemoryIterator[T]) Err() error { - return it.err -} - -// Close is a no-op for in-memory iterators, present to satisfy common interfaces. -func (it *InMemoryIterator[T]) Close() { - return +// Release releases the iterator resources. +func (it *KVIterator) Release() { + // No resources to release for in-memory iterator } diff --git a/internal/storage/inmemory/provider.go b/internal/storage/inmemory/provider.go deleted file mode 100644 index 97955842..00000000 --- a/internal/storage/inmemory/provider.go +++ /dev/null @@ -1,118 +0,0 @@ -// This file is part of the happyDomain (R) project. -// Copyright (c) 2020-2025 happyDomain -// Authors: Pierre-Olivier Mercier, et al. -// -// This program is offered under a commercial and under the AGPL license. -// For commercial licensing, contact us at . -// -// For AGPL licensing: -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package inmemory - -import ( - "encoding/json" - - "git.happydns.org/happyDomain/model" -) - -func (s *InMemoryStorage) ListAllProviders() (happydns.Iterator[happydns.ProviderMessage], error) { - s.mu.Lock() - defer s.mu.Unlock() - - return NewInMemoryIterator[happydns.ProviderMessage](&s.providers), nil -} - -// ListProviders retrieves all providers owned by the given User. -func (s *InMemoryStorage) ListProviders(u *happydns.User) (happydns.ProviderMessages, error) { - s.mu.Lock() - defer s.mu.Unlock() - - var providers happydns.ProviderMessages - for _, provider := range s.providers { - if provider.Owner.Equals(u.Id) { - providers = append(providers, provider) - } - } - - return providers, nil -} - -// GetProvider retrieves the full Provider with the given identifier and owner. -func (s *InMemoryStorage) GetProvider(id happydns.Identifier) (*happydns.ProviderMessage, error) { - s.mu.Lock() - defer s.mu.Unlock() - - provider, exists := s.providers[id.String()] - if !exists { - return nil, happydns.ErrProviderNotFound - } - - return provider, nil -} - -// CreateProvider creates a record in the database for the given Provider. -func (s *InMemoryStorage) CreateProvider(p *happydns.Provider) error { - s.mu.Lock() - defer s.mu.Unlock() - - message, err := json.Marshal(p.Provider) - if err != nil { - return err - } - - p.ProviderMeta.Id, err = happydns.NewRandomIdentifier() - s.providers[p.ProviderMeta.Id.String()] = &happydns.ProviderMessage{ - ProviderMeta: p.ProviderMeta, - Provider: message, - } - - return nil -} - -// UpdateProvider updates the fields of the given Provider. -func (s *InMemoryStorage) UpdateProvider(p *happydns.Provider) error { - s.mu.Lock() - defer s.mu.Unlock() - - message, err := json.Marshal(p.Provider) - if err != nil { - return err - } - - s.providers[p.ProviderMeta.Id.String()] = &happydns.ProviderMessage{ - ProviderMeta: p.ProviderMeta, - Provider: message, - } - - return nil -} - -// DeleteProvider removes the given Provider from the database. -func (s *InMemoryStorage) DeleteProvider(id happydns.Identifier) error { - s.mu.Lock() - defer s.mu.Unlock() - - delete(s.providers, id.String()) - return nil -} - -// ClearProviders deletes all Providers present in the database. -func (s *InMemoryStorage) ClearProviders() error { - s.mu.Lock() - defer s.mu.Unlock() - - s.providers = make(map[string]*happydns.ProviderMessage) - return nil -} diff --git a/internal/storage/inmemory/session.go b/internal/storage/inmemory/session.go deleted file mode 100644 index 7a00f647..00000000 --- a/internal/storage/inmemory/session.go +++ /dev/null @@ -1,101 +0,0 @@ -// This file is part of the happyDomain (R) project. -// Copyright (c) 2020-2025 happyDomain -// Authors: Pierre-Olivier Mercier, et al. -// -// This program is offered under a commercial and under the AGPL license. -// For commercial licensing, contact us at . -// -// For AGPL licensing: -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package inmemory - -import ( - "git.happydns.org/happyDomain/model" -) - -func (s *InMemoryStorage) ListAllSessions() (happydns.Iterator[happydns.Session], error) { - s.mu.Lock() - defer s.mu.Unlock() - - return NewInMemoryIterator[happydns.Session](&s.sessions), nil -} - -// GetSession retrieves the Session with the given identifier. -func (s *InMemoryStorage) GetSession(id string) (*happydns.Session, error) { - s.mu.Lock() - defer s.mu.Unlock() - - session, exists := s.sessions[id] - if !exists { - return nil, happydns.ErrSessionNotFound - } - - return session, nil -} - -// ListAuthUserSessions retrieves all Session for the given AuthUser. -func (s *InMemoryStorage) ListAuthUserSessions(user *happydns.UserAuth) (sessions []*happydns.Session, ess error) { - s.mu.Lock() - defer s.mu.Unlock() - - for _, session := range s.sessions { - if user.Id.Equals(session.IdUser) { - sessions = append(sessions, session) - } - } - - return sessions, nil -} - -// ListUserSessions retrieves all Session for the given User. -func (s *InMemoryStorage) ListUserSessions(userid happydns.Identifier) (sessions []*happydns.Session, ess error) { - s.mu.Lock() - defer s.mu.Unlock() - - for _, session := range s.sessions { - if userid.Equals(session.IdUser) { - sessions = append(sessions, session) - } - } - - return sessions, nil -} - -// UpdateSession updates the fields of the given Session. -func (s *InMemoryStorage) UpdateSession(session *happydns.Session) error { - s.mu.Lock() - defer s.mu.Unlock() - - s.sessions[session.Id] = session - return nil -} - -// DeleteSession removes the given Session from the database. -func (s *InMemoryStorage) DeleteSession(id string) error { - s.mu.Lock() - defer s.mu.Unlock() - - delete(s.sessions, id) - return nil -} - -// ClearSessions deletes all Sessions present in the database. -func (s *InMemoryStorage) ClearSessions() error { - s.mu.Lock() - defer s.mu.Unlock() - - s.sessions = make(map[string]*happydns.Session) - return nil -} diff --git a/internal/storage/inmemory/user.go b/internal/storage/inmemory/user.go deleted file mode 100644 index 0e29a964..00000000 --- a/internal/storage/inmemory/user.go +++ /dev/null @@ -1,105 +0,0 @@ -// This file is part of the happyDomain (R) project. -// Copyright (c) 2020-2025 happyDomain -// Authors: Pierre-Olivier Mercier, et al. -// -// This program is offered under a commercial and under the AGPL license. -// For commercial licensing, contact us at . -// -// For AGPL licensing: -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package inmemory - -import ( - "git.happydns.org/happyDomain/model" -) - -func (s *InMemoryStorage) ListAllUsers() (happydns.Iterator[happydns.User], error) { - s.mu.Lock() - defer s.mu.Unlock() - - return NewInMemoryIterator[happydns.User](&s.users), nil -} - -// ListUsers retrieves the list of known Users. -func (s *InMemoryStorage) ListUsers() (users []*happydns.User, err error) { - s.mu.Lock() - defer s.mu.Unlock() - - for _, user := range s.users { - users = append(users, user) - } - - return users, nil -} - -// GetUser retrieves the User with the given identifier. -func (s *InMemoryStorage) GetUser(id happydns.Identifier) (*happydns.User, error) { - s.mu.Lock() - defer s.mu.Unlock() - - user, exists := s.users[id.String()] - if !exists { - return nil, happydns.ErrUserNotFound - } - - return user, nil -} - -// GetUserByEmail retrieves the User with the given email address. -func (s *InMemoryStorage) GetUserByEmail(email string) (*happydns.User, error) { - s.mu.Lock() - defer s.mu.Unlock() - - user, exists := s.usersByEmail[email] - if !exists { - return nil, happydns.ErrUserNotFound - } - - return user, nil -} - -// CreateOrUpdateUser updates the fields of the given User. -func (s *InMemoryStorage) CreateOrUpdateUser(user *happydns.User) error { - s.mu.Lock() - defer s.mu.Unlock() - - s.users[user.Id.String()] = user - s.usersByEmail[user.Email] = user - - return nil -} - -// DeleteUser removes the given User from the database. -func (s *InMemoryStorage) DeleteUser(userid happydns.Identifier) error { - s.mu.Lock() - defer s.mu.Unlock() - - if user, exists := s.users[userid.String()]; exists { - delete(s.users, userid.String()) - delete(s.usersByEmail, user.Email) - } - - return nil -} - -// ClearUsers deletes all Users present in the database. -func (s *InMemoryStorage) ClearUsers() error { - s.mu.Lock() - defer s.mu.Unlock() - - s.users = make(map[string]*happydns.User) - s.usersByEmail = make(map[string]*happydns.User) - return nil -} diff --git a/internal/storage/inmemory/zone.go b/internal/storage/inmemory/zone.go deleted file mode 100644 index b5264f10..00000000 --- a/internal/storage/inmemory/zone.go +++ /dev/null @@ -1,116 +0,0 @@ -// This file is part of the happyDomain (R) project. -// Copyright (c) 2020-2025 happyDomain -// Authors: Pierre-Olivier Mercier, et al. -// -// This program is offered under a commercial and under the AGPL license. -// For commercial licensing, contact us at . -// -// For AGPL licensing: -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package inmemory - -import ( - "encoding/json" - - "git.happydns.org/happyDomain/model" -) - -func (s *InMemoryStorage) ListAllZones() (happydns.Iterator[happydns.ZoneMessage], error) { - s.mu.Lock() - defer s.mu.Unlock() - - return NewInMemoryIterator[happydns.ZoneMessage](&s.zones), nil -} - -// GetZoneMeta retrieves metadata of the Zone with the given identifier. -func (s *InMemoryStorage) GetZoneMeta(id happydns.Identifier) (*happydns.ZoneMeta, error) { - s.mu.Lock() - defer s.mu.Unlock() - zone, exists := s.zones[id.String()] - if !exists { - return nil, happydns.ErrZoneNotFound - } - return &zone.ZoneMeta, nil -} - -// GetZone retrieves the full Zone (including Services and metadata) which have the given identifier. -func (s *InMemoryStorage) GetZone(id happydns.Identifier) (*happydns.ZoneMessage, error) { - s.mu.Lock() - defer s.mu.Unlock() - - zone, exists := s.zones[id.String()] - if !exists { - return nil, happydns.ErrZoneNotFound - } - - return zone, nil -} - -// CreateZone creates a record in the database for the given Zone. -func (s *InMemoryStorage) CreateZone(zone *happydns.Zone) (err error) { - zone.ZoneMeta.Id, err = happydns.NewRandomIdentifier() - if err != nil { - return - } - - return s.UpdateZone(zone) -} - -// UpdateZone updates the fields of the given Zone. -func (s *InMemoryStorage) UpdateZone(zone *happydns.Zone) error { - s.mu.Lock() - defer s.mu.Unlock() - - zmsg := &happydns.ZoneMessage{ - ZoneMeta: zone.ZoneMeta, - Services: map[happydns.Subdomain][]*happydns.ServiceMessage{}, - } - - for subdn, services := range zone.Services { - for _, service := range services { - message, err := json.Marshal(service.Service) - if err != nil { - return err - } - - zmsg.Services[subdn] = append(zmsg.Services[subdn], &happydns.ServiceMessage{ - ServiceMeta: service.ServiceMeta, - Service: message, - }) - } - } - - s.zones[zone.Id.String()] = zmsg - - return nil -} - -// DeleteZone removes the given Zone from the database. -func (s *InMemoryStorage) DeleteZone(id happydns.Identifier) error { - s.mu.Lock() - defer s.mu.Unlock() - - delete(s.zones, id.String()) - return nil -} - -// ClearZones deletes all Zones present in the database. -func (s *InMemoryStorage) ClearZones() error { - s.mu.Lock() - defer s.mu.Unlock() - - s.zones = make(map[string]*happydns.ZoneMessage) - return nil -} diff --git a/internal/storage/interface.go b/internal/storage/interface.go index 9d8e410f..9b654e62 100644 --- a/internal/storage/interface.go +++ b/internal/storage/interface.go @@ -30,6 +30,7 @@ import ( "git.happydns.org/happyDomain/internal/usecase/session" "git.happydns.org/happyDomain/internal/usecase/user" "git.happydns.org/happyDomain/internal/usecase/zone" + "git.happydns.org/happyDomain/model" ) type ProviderAndDomainStorage interface { @@ -56,3 +57,22 @@ type Storage interface { // Close shutdown the connection with the database and releases all structure. Close() error } + +type Iterator interface { + Release() + Next() bool + Valid() bool + Key() string + Value() interface{} +} + +type KVStorage interface { + Close() error + DecodeData(i interface{}, v interface{}) error + Has(key string) (bool, error) + Get(key string, v interface{}) error + Put(key string, v interface{}) error + FindIdentifierKey(prefix string) (key string, id happydns.Identifier, err error) + Delete(key string) error + Search(prefix string) Iterator +} diff --git a/internal/storage/leveldb/auth.go b/internal/storage/kvtpl/auth.go similarity index 56% rename from internal/storage/leveldb/auth.go rename to internal/storage/kvtpl/auth.go index 3cba8789..31a84ab6 100644 --- a/internal/storage/leveldb/auth.go +++ b/internal/storage/kvtpl/auth.go @@ -1,5 +1,5 @@ // This file is part of the happyDomain (R) project. -// Copyright (c) 2020-2024 happyDomain +// Copyright (c) 2020-2025 happyDomain // Authors: Pierre-Olivier Mercier, et al. // // This program is offered under a commercial and under the AGPL license. @@ -25,35 +25,33 @@ import ( "errors" "fmt" - "github.com/syndtr/goleveldb/leveldb" - "github.com/syndtr/goleveldb/leveldb/util" - "git.happydns.org/happyDomain/model" ) -func (s *LevelDBStorage) ListAllAuthUsers() (happydns.Iterator[happydns.UserAuth], error) { - iter := s.search("auth-") - return NewLevelDBIterator[happydns.UserAuth](s.db, iter), nil +func (s *KVStorage) ListAllAuthUsers() (happydns.Iterator[happydns.UserAuth], error) { + iter := s.db.Search("auth-") + return NewKVIterator[happydns.UserAuth](s.db, iter), nil } -func (s *LevelDBStorage) getAuthUser(key string) (*happydns.UserAuth, error) { +func (s *KVStorage) getAuthUser(key string) (*happydns.UserAuth, error) { u := &happydns.UserAuth{} - err := s.get(key, &u) - if errors.Is(err, leveldb.ErrNotFound) { + err := s.db.Get(key, &u) + if errors.Is(err, happydns.ErrNotFound) { return nil, happydns.ErrAuthUserNotFound } return u, err } -func (s *LevelDBStorage) GetAuthUser(id happydns.Identifier) (u *happydns.UserAuth, err error) { +func (s *KVStorage) GetAuthUser(id happydns.Identifier) (u *happydns.UserAuth, err error) { return s.getAuthUser(fmt.Sprintf("auth-%s", id.String())) } -func (s *LevelDBStorage) GetAuthUserByEmail(email string) (*happydns.UserAuth, error) { +func (s *KVStorage) GetAuthUserByEmail(email string) (*happydns.UserAuth, error) { users, err := s.ListAllAuthUsers() if err != nil { return nil, err } + defer users.Close() for users.Next() { user := users.Item() @@ -65,11 +63,12 @@ func (s *LevelDBStorage) GetAuthUserByEmail(email string) (*happydns.UserAuth, e return nil, fmt.Errorf("Unable to find user with email address '%s'.", email) } -func (s *LevelDBStorage) AuthUserExists(email string) (bool, error) { +func (s *KVStorage) AuthUserExists(email string) (bool, error) { users, err := s.ListAllAuthUsers() if err != nil { return false, err } + defer users.Close() for users.Next() { user := users.Item() @@ -81,46 +80,37 @@ func (s *LevelDBStorage) AuthUserExists(email string) (bool, error) { return false, nil } -func (s *LevelDBStorage) CreateAuthUser(u *happydns.UserAuth) error { - key, id, err := s.findIdentifierKey("auth-") +func (s *KVStorage) CreateAuthUser(u *happydns.UserAuth) error { + key, id, err := s.db.FindIdentifierKey("auth-") if err != nil { return err } u.Id = id - return s.put(key, u) + return s.db.Put(key, u) } -func (s *LevelDBStorage) UpdateAuthUser(u *happydns.UserAuth) error { - return s.put(fmt.Sprintf("auth-%s", u.Id.String()), u) +func (s *KVStorage) UpdateAuthUser(u *happydns.UserAuth) error { + return s.db.Put(fmt.Sprintf("auth-%s", u.Id.String()), u) } -func (s *LevelDBStorage) DeleteAuthUser(u *happydns.UserAuth) error { - return s.delete(fmt.Sprintf("auth-%s", u.Id.String())) +func (s *KVStorage) DeleteAuthUser(u *happydns.UserAuth) error { + return s.db.Delete(fmt.Sprintf("auth-%s", u.Id.String())) } -func (s *LevelDBStorage) ClearAuthUsers() error { - tx, err := s.db.OpenTransaction() +func (s *KVStorage) ClearAuthUsers() error { + iter, err := s.ListAllAuthUsers() if err != nil { return err } - - iter := tx.NewIterator(util.BytesPrefix([]byte("auth-")), nil) - defer iter.Release() + defer iter.Close() for iter.Next() { - err = tx.Delete(iter.Key(), nil) + err = s.db.Delete(iter.Key()) if err != nil { - tx.Discard() return err } } - err = tx.Commit() - if err != nil { - tx.Discard() - return err - } - return nil } diff --git a/internal/storage/kvtpl/domain-log.go b/internal/storage/kvtpl/domain-log.go new file mode 100644 index 00000000..0604bc7b --- /dev/null +++ b/internal/storage/kvtpl/domain-log.go @@ -0,0 +1,122 @@ +// This file is part of the happyDomain (R) project. +// Copyright (c) 2020-2025 happyDomain +// Authors: Pierre-Olivier Mercier, et al. +// +// This program is offered under a commercial and under the AGPL license. +// For commercial licensing, contact us at . +// +// For AGPL licensing: +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package database + +import ( + "errors" + "fmt" + "strings" + + "git.happydns.org/happyDomain/internal/storage" + "git.happydns.org/happyDomain/model" +) + +// DomainLogIterator wraps KVIterator to populate DomainId from the key +type DomainLogIterator struct { + *KVIterator[happydns.DomainLogWithDomainId] +} + +// NewDomainLogIterator creates a new DomainLogIterator +func NewDomainLogIterator(db storage.KVStorage, iter storage.Iterator) *DomainLogIterator { + return &DomainLogIterator{ + KVIterator: NewKVIterator[happydns.DomainLogWithDomainId](db, iter), + } +} + +// Next advances the iterator and extracts the DomainId from the key +func (it *DomainLogIterator) Next() bool { + if it.KVIterator.Next() { + // Extract domain ID from key + key := it.Key() + st := strings.Split(key, "|") + if len(st) >= 3 && it.item != nil { + domainId, err := happydns.NewIdentifierFromString(st[1]) + if err == nil { + it.item.DomainId = domainId + } + } + return true + } + return false +} + +func (s *KVStorage) ListAllDomainLogs() (happydns.Iterator[happydns.DomainLogWithDomainId], error) { + iter := s.db.Search("domain.log|") + return NewDomainLogIterator(s.db, iter), nil +} + +func (s *KVStorage) ListDomainLogs(domain *happydns.Domain) (logs []*happydns.DomainLog, err error) { + iter := s.db.Search(fmt.Sprintf("domain.log|%s|", domain.Id.String())) + defer iter.Release() + + for iter.Next() { + var z happydns.DomainLog + + err = s.db.DecodeData(iter.Value(), &z) + if err != nil { + return + } + + logs = append(logs, &z) + } + + return +} + +func (s *KVStorage) getDomainLog(id string) (l *happydns.DomainLog, d *happydns.Domain, err error) { + l = &happydns.DomainLog{} + err = s.db.Get(id, l) + if errors.Is(err, happydns.ErrNotFound) { + return nil, nil, happydns.ErrDomainLogNotFound + } + + st := strings.Split(id, "|") + if len(st) < 3 { + return + } + + d = &happydns.Domain{} + err = s.db.Get(fmt.Sprintf("domain-%s", st[1]), d) + if errors.Is(err, happydns.ErrNotFound) { + return nil, nil, happydns.ErrDomainNotFound + } + + return +} + +func (s *KVStorage) CreateDomainLog(d *happydns.Domain, l *happydns.DomainLog) error { + key, id, err := s.db.FindIdentifierKey(fmt.Sprintf("domain.log|%s|", d.Id.String())) + if err != nil { + return err + } + + l.Id = id + return s.db.Put(key, l) +} + +func (s *KVStorage) UpdateDomainLog(d *happydns.Domain, l *happydns.DomainLog) error { + return s.db.Put(fmt.Sprintf("domain.log|%s|%s", d.Id.String(), l.Id.String()), l) +} + +func (s *KVStorage) DeleteDomainLog(d *happydns.Domain, l *happydns.DomainLog) error { + return s.db.Delete(fmt.Sprintf("domain.log|%s|%s", d.Id.String(), l.Id.String())) +} diff --git a/internal/storage/leveldb/domain.go b/internal/storage/kvtpl/domain.go similarity index 55% rename from internal/storage/leveldb/domain.go rename to internal/storage/kvtpl/domain.go index 45e43208..85605fd2 100644 --- a/internal/storage/leveldb/domain.go +++ b/internal/storage/kvtpl/domain.go @@ -1,5 +1,5 @@ // This file is part of the happyDomain (R) project. -// Copyright (c) 2020-2024 happyDomain +// Copyright (c) 2020-2025 happyDomain // Authors: Pierre-Olivier Mercier, et al. // // This program is offered under a commercial and under the AGPL license. @@ -26,25 +26,22 @@ import ( "errors" "fmt" - "github.com/syndtr/goleveldb/leveldb" - "github.com/syndtr/goleveldb/leveldb/util" - "git.happydns.org/happyDomain/model" ) -func (s *LevelDBStorage) ListAllDomains() (happydns.Iterator[happydns.Domain], error) { - iter := s.search("domain-") - return NewLevelDBIterator[happydns.Domain](s.db, iter), nil +func (s *KVStorage) ListAllDomains() (happydns.Iterator[happydns.Domain], error) { + iter := s.db.Search("domain-") + return NewKVIterator[happydns.Domain](s.db, iter), nil } -func (s *LevelDBStorage) ListDomains(u *happydns.User) (domains []*happydns.Domain, err error) { - iter := s.search("domain-") +func (s *KVStorage) ListDomains(u *happydns.User) (domains []*happydns.Domain, err error) { + iter := s.db.Search("domain-") defer iter.Release() for iter.Next() { var z happydns.Domain - err = decodeData(iter.Value(), &z) + err = s.db.DecodeData(iter.Value(), &z) if err != nil { return } @@ -57,20 +54,20 @@ func (s *LevelDBStorage) ListDomains(u *happydns.User) (domains []*happydns.Doma return } -func (s *LevelDBStorage) getDomain(id string) (*happydns.Domain, error) { +func (s *KVStorage) getDomain(id string) (*happydns.Domain, error) { domain := &happydns.Domain{} - err := s.get(id, domain) - if errors.Is(err, leveldb.ErrNotFound) { + err := s.db.Get(id, domain) + if errors.Is(err, happydns.ErrNotFound) { return nil, happydns.ErrDomainNotFound } return domain, err } -func (s *LevelDBStorage) GetDomain(id happydns.Identifier) (*happydns.Domain, error) { +func (s *KVStorage) GetDomain(id happydns.Identifier) (*happydns.Domain, error) { return s.getDomain(fmt.Sprintf("domain-%s", id.String())) } -func (s *LevelDBStorage) GetDomainByDN(u *happydns.User, dn string) ([]*happydns.Domain, error) { +func (s *KVStorage) GetDomainByDN(u *happydns.User, dn string) ([]*happydns.Domain, error) { domains, err := s.ListDomains(u) if err != nil { return nil, err @@ -84,57 +81,48 @@ func (s *LevelDBStorage) GetDomainByDN(u *happydns.User, dn string) ([]*happydns } if len(ret) == 0 { - return nil, leveldb.ErrNotFound + return nil, happydns.ErrNotFound } return ret, nil } -func (s *LevelDBStorage) CreateDomain(z *happydns.Domain) error { - key, id, err := s.findIdentifierKey("domain-") +func (s *KVStorage) CreateDomain(z *happydns.Domain) error { + key, id, err := s.db.FindIdentifierKey("domain-") if err != nil { return err } z.Id = id - return s.put(key, z) + return s.db.Put(key, z) } -func (s *LevelDBStorage) UpdateDomain(z *happydns.Domain) error { - return s.put(fmt.Sprintf("domain-%s", z.Id.String()), z) +func (s *KVStorage) UpdateDomain(z *happydns.Domain) error { + return s.db.Put(fmt.Sprintf("domain-%s", z.Id.String()), z) } -func (s *LevelDBStorage) DeleteDomain(zId happydns.Identifier) error { - return s.delete(fmt.Sprintf("domain-%s", zId.String())) +func (s *KVStorage) DeleteDomain(zId happydns.Identifier) error { + return s.db.Delete(fmt.Sprintf("domain-%s", zId.String())) } -func (s *LevelDBStorage) ClearDomains() error { +func (s *KVStorage) ClearDomains() error { err := s.ClearZones() if err != nil { return err } - tx, err := s.db.OpenTransaction() + iter, err := s.ListAllDomains() if err != nil { return err } - - iter := tx.NewIterator(util.BytesPrefix([]byte("domain-")), nil) - defer iter.Release() + defer iter.Close() for iter.Next() { - err = tx.Delete(iter.Key(), nil) + err = s.db.Delete(iter.Key()) if err != nil { - tx.Discard() return err } } - err = tx.Commit() - if err != nil { - tx.Discard() - return err - } - return nil } diff --git a/internal/storage/leveldb/insights.go b/internal/storage/kvtpl/insights.go similarity index 76% rename from internal/storage/leveldb/insights.go rename to internal/storage/kvtpl/insights.go index 4bfbabb5..bee64f37 100644 --- a/internal/storage/leveldb/insights.go +++ b/internal/storage/kvtpl/insights.go @@ -22,27 +22,26 @@ package database import ( + "errors" "time" - "github.com/syndtr/goleveldb/leveldb" - "git.happydns.org/happyDomain/model" ) -func (s *LevelDBStorage) InsightsRun() error { - return s.put("insights", time.Now()) +func (s *KVStorage) InsightsRun() error { + return s.db.Put("insights", time.Now()) } -func (s *LevelDBStorage) LastInsightsRun() (t *time.Time, instance happydns.Identifier, err error) { - err = s.get("insights.instance-id", &instance) - if err == leveldb.ErrNotFound { +func (s *KVStorage) LastInsightsRun() (t *time.Time, instance happydns.Identifier, err error) { + err = s.db.Get("insights.instance-id", &instance) + if errors.Is(err, happydns.ErrNotFound) { // No instance ID defined, set one instance, err = happydns.NewRandomIdentifier() if err != nil { return } - err = s.put("insights.instance-id", instance) + err = s.db.Put("insights.instance-id", instance) if err != nil { return } @@ -51,8 +50,8 @@ func (s *LevelDBStorage) LastInsightsRun() (t *time.Time, instance happydns.Iden } t = new(time.Time) - err = s.get("insights", &t) - if err == leveldb.ErrNotFound { + err = s.db.Get("insights", &t) + if errors.Is(err, happydns.ErrNotFound) { t = nil err = nil } else if err != nil { diff --git a/internal/storage/kvtpl/iterator.go b/internal/storage/kvtpl/iterator.go new file mode 100644 index 00000000..18b67b6c --- /dev/null +++ b/internal/storage/kvtpl/iterator.go @@ -0,0 +1,120 @@ +// This file is part of the happyDomain (R) project. +// Copyright (c) 2020-2025 happyDomain +// Authors: Pierre-Olivier Mercier, et al. +// +// This program is offered under a commercial and under the AGPL license. +// For commercial licensing, contact us at . +// +// For AGPL licensing: +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package database + +import ( + "fmt" + "log" + + "git.happydns.org/happyDomain/internal/storage" +) + +// KVIterator is a generic implementation of Iterator for LevelDB. +type KVIterator[T any] struct { + db storage.KVStorage + iter storage.Iterator + err error + item *T +} + +// NewKVIterator creates a new KVIterator instance for the given iterator. +func NewKVIterator[T any](db storage.KVStorage, iter storage.Iterator) *KVIterator[T] { + return &KVIterator[T]{ + db: db, + iter: iter, + } +} + +// Next moves the iterator to the next valid item. +// Skips items that fail to decode and logs the error. +func (it *KVIterator[T]) Next() bool { + for it.iter.Next() { + var value T + err := it.db.DecodeData(it.iter.Value(), &value) + if err != nil { + log.Printf("KVIterator: error decoding item at key %q: %s", it.iter.Key(), err) + it.err = err + continue + } + it.item = &value + return true + } + return false +} + +// NextWithError advances the iterator to the next item, on decode error it doesn't continue to the next item. +// Returns true if there is a next item, false otherwise. +func (it *KVIterator[T]) NextWithError() bool { + if it.iter.Next() { + var value T + err := it.db.DecodeData(it.iter.Value(), &value) + if err != nil { + it.err = err + it.item = nil + } else { + it.err = nil + it.item = &value + } + return true + } + return false +} + +// Item returns the current item from the iterator. +// Only valid after a successful call to Next(). +func (it *KVIterator[T]) Item() *T { + return it.item +} + +// DropItem deletes the key currently pointed to by the iterator. +func (it *KVIterator[T]) DropItem() error { + if it.iter == nil || !it.iter.Valid() { + return fmt.Errorf("DropItem: iterator is not valid") + } + return it.db.Delete(it.iter.Key()) +} + +// Raw returns the raw (non-decoded) value at the current iterator position. +// Should only be called after a successful call to Next(). +func (it *KVIterator[T]) Raw() interface{} { + if it.iter == nil || !it.iter.Valid() { + return []byte{} + } + return it.iter.Value() +} + +func (it *KVIterator[T]) Key() string { + if it.iter == nil || !it.iter.Valid() { + return "" + } + return it.iter.Key() +} + +// Err returns the first error encountered during iteration, if any. +func (it *KVIterator[T]) Err() error { + return it.err +} + +// Close releases resources held by the underlying LevelDB iterator. +func (it *KVIterator[T]) Close() { + it.iter.Release() +} diff --git a/internal/storage/kvtpl/provider.go b/internal/storage/kvtpl/provider.go new file mode 100644 index 00000000..bbee96a1 --- /dev/null +++ b/internal/storage/kvtpl/provider.go @@ -0,0 +1,112 @@ +// This file is part of the happyDomain (R) project. +// Copyright (c) 2020-2025 happyDomain +// Authors: Pierre-Olivier Mercier, et al. +// +// This program is offered under a commercial and under the AGPL license. +// For commercial licensing, contact us at . +// +// For AGPL licensing: +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package database + +import ( + "bytes" + "errors" + "fmt" + + "git.happydns.org/happyDomain/model" +) + +func (s *KVStorage) ListAllProviders() (happydns.Iterator[happydns.ProviderMessage], error) { + iter := s.db.Search("provider-") + return NewKVIterator[happydns.ProviderMessage](s.db, iter), nil +} + +func (s *KVStorage) getProviderMeta(id happydns.Identifier) (*happydns.ProviderMessage, error) { + srcMsg := &happydns.ProviderMessage{} + err := s.db.Get(id.String(), srcMsg) + if errors.Is(err, happydns.ErrNotFound) { + return nil, happydns.ErrProviderNotFound + } + return srcMsg, err +} + +func (s *KVStorage) ListProviders(u *happydns.User) (srcs happydns.ProviderMessages, err error) { + iter, err := s.ListAllProviders() + if err != nil { + return nil, err + } + defer iter.Close() + + for iter.Next() { + srcMsg := iter.Item() + if !bytes.Equal(srcMsg.Owner, u.Id) { + continue + } + + srcs = append(srcs, srcMsg) + } + + return +} + +func (s *KVStorage) GetProvider(id happydns.Identifier) (*happydns.ProviderMessage, error) { + var prvdMsg happydns.ProviderMessage + err := s.db.Get(fmt.Sprintf("provider-%s", id.String()), &prvdMsg) + if errors.Is(err, happydns.ErrNotFound) { + return nil, happydns.ErrProviderNotFound + } + if err != nil { + return nil, err + } + + return &prvdMsg, nil +} + +func (s *KVStorage) CreateProvider(prvd *happydns.Provider) error { + key, id, err := s.db.FindIdentifierKey("provider-") + if err != nil { + return err + } + + prvd.Id = id + + return s.db.Put(key, prvd) +} + +func (s *KVStorage) UpdateProvider(prvd *happydns.Provider) error { + return s.db.Put(fmt.Sprintf("provider-%s", prvd.Id.String()), prvd) +} + +func (s *KVStorage) DeleteProvider(prvdId happydns.Identifier) error { + return s.db.Delete(fmt.Sprintf("provider-%s", prvdId.String())) +} + +func (s *KVStorage) ClearProviders() error { + iter, err := s.ListAllProviders() + if err != nil { + return err + } + defer iter.Close() + + for iter.Next() { + err = s.db.Delete(iter.Key()) + if err != nil { + return err + } + } + + return nil +} diff --git a/internal/storage/kvtpl/session.go b/internal/storage/kvtpl/session.go new file mode 100644 index 00000000..fbc8b2a8 --- /dev/null +++ b/internal/storage/kvtpl/session.go @@ -0,0 +1,107 @@ +// This file is part of the happyDomain (R) project. +// Copyright (c) 2020-2025 happyDomain +// Authors: Pierre-Olivier Mercier, et al. +// +// This program is offered under a commercial and under the AGPL license. +// For commercial licensing, contact us at . +// +// For AGPL licensing: +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package database + +import ( + "errors" + "fmt" + + "git.happydns.org/happyDomain/model" +) + +func (s *KVStorage) ListAllSessions() (happydns.Iterator[happydns.Session], error) { + iter := s.db.Search("user.session-") + return NewKVIterator[happydns.Session](s.db, iter), nil +} + +func (s *KVStorage) getSession(id string) (*happydns.Session, error) { + session := &happydns.Session{} + err := s.db.Get(id, &session) + if errors.Is(err, happydns.ErrNotFound) { + return nil, happydns.ErrSessionNotFound + } + return session, err +} + +func (s *KVStorage) GetSession(id string) (session *happydns.Session, err error) { + return s.getSession(fmt.Sprintf("user.session-%s", id)) +} + +func (s *KVStorage) ListAuthUserSessions(user *happydns.UserAuth) (sessions []*happydns.Session, err error) { + iter := s.db.Search("user.session-") + defer iter.Release() + + for iter.Next() { + var session happydns.Session + + err = s.db.DecodeData(iter.Value(), &session) + if err != nil { + return + } + if session.IdUser.Equals(user.Id) { + sessions = append(sessions, &session) + } + } + + return +} + +func (s *KVStorage) ListUserSessions(userid happydns.Identifier) (sessions []*happydns.Session, err error) { + iter := s.db.Search("user.session-") + defer iter.Release() + + for iter.Next() { + var session happydns.Session + + err = s.db.DecodeData(iter.Value(), &session) + if err != nil { + return + } + if session.IdUser.Equals(userid) { + sessions = append(sessions, &session) + } + } + + return +} + +func (s *KVStorage) UpdateSession(session *happydns.Session) error { + return s.db.Put(fmt.Sprintf("user.session-%s", session.Id), session) +} + +func (s *KVStorage) DeleteSession(id string) error { + return s.db.Delete(fmt.Sprintf("user.session-%s", id)) +} + +func (s *KVStorage) ClearSessions() error { + iter := s.db.Search("user.session-") + defer iter.Release() + + for iter.Next() { + err := s.db.Delete(iter.Key()) + if err != nil { + return err + } + } + + return nil +} diff --git a/internal/storage/inmemory/insight.go b/internal/storage/kvtpl/template.go similarity index 60% rename from internal/storage/inmemory/insight.go rename to internal/storage/kvtpl/template.go index d2a31671..9522312b 100644 --- a/internal/storage/inmemory/insight.go +++ b/internal/storage/kvtpl/template.go @@ -19,37 +19,22 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -package inmemory +package database import ( - "time" - - "git.happydns.org/happyDomain/model" + "git.happydns.org/happyDomain/internal/storage" ) -// InsightsRun registers a insights process run just now. -func (s *InMemoryStorage) InsightsRun() error { - s.mu.Lock() - defer s.mu.Unlock() - - now := time.Now() - s.lastInsightsRun = &now - - return nil +type KVStorage struct { + db storage.KVStorage } -// LastInsightsRun gets the last time insights process run. -func (s *InMemoryStorage) LastInsightsRun() (*time.Time, happydns.Identifier, error) { - s.mu.Lock() - defer s.mu.Unlock() - - if s.lastInsightsID == nil { - instance, err := happydns.NewRandomIdentifier() - if err != nil { - return nil, nil, err - } - s.lastInsightsID = instance - } - - return s.lastInsightsRun, s.lastInsightsID, nil +func NewKVDatabase(impl storage.KVStorage) (storage.Storage, error) { + return &KVStorage{ + impl, + }, nil +} + +func (s *KVStorage) Close() error { + return s.db.Close() } diff --git a/internal/storage/leveldb/updates-from-0.go b/internal/storage/kvtpl/updates-from-0.go similarity index 77% rename from internal/storage/leveldb/updates-from-0.go rename to internal/storage/kvtpl/updates-from-0.go index ba8a7e0c..d8d00ca2 100644 --- a/internal/storage/leveldb/updates-from-0.go +++ b/internal/storage/kvtpl/updates-from-0.go @@ -26,7 +26,7 @@ import ( "log" ) -func migrateFrom0(s *LevelDBStorage) (err error) { +func migrateFrom0(s *KVStorage) (err error) { err = migrateFrom0_sourcesProvider(s) if err != nil { return @@ -47,14 +47,20 @@ type sourceMeta struct { Comment string `json:"_comment,omitempty"` } -func migrateFrom0_sourcesProvider(s *LevelDBStorage) (err error) { - iter := s.search("source-") +func migrateFrom0_sourcesProvider(s *KVStorage) (err error) { + iter := s.db.Search("source-") defer iter.Release() for iter.Next() { - src := iter.Value() - for src[0] == '"' { - err = decodeData(src, &src) + srcRaw := iter.Value() + src, ok := srcRaw.([]byte) + if !ok { + log.Printf("Migrating v0 -> v1: skip %s (not bytes)...", iter.Key()) + continue + } + + for len(src) > 0 && src[0] == '"' { + err = s.db.DecodeData(src, &src) if err != nil { return } @@ -63,7 +69,7 @@ func migrateFrom0_sourcesProvider(s *LevelDBStorage) (err error) { src = bytes.Replace(src, []byte("\"Source\":"), []byte("\"Provider\":"), 1) var srcMeta sourceMeta - err = decodeData(src, &srcMeta) + err = s.db.DecodeData(src, &srcMeta) if err != nil { return } @@ -101,16 +107,16 @@ func migrateFrom0_sourcesProvider(s *LevelDBStorage) (err error) { } } - newKey := bytes.Replace(iter.Key(), []byte("source-"), []byte("provider-"), 1) + newKey := string(bytes.Replace([]byte(iter.Key()), []byte("source-"), []byte("provider-"), 1)) log.Printf("Migrating v0 -> v1: %s to %s (%s)...", iter.Key(), newKey, newType) - err = s.db.Put(newKey, src, nil) + err = s.db.Put(newKey, src) if err != nil { return } - err = s.delete(string(iter.Key())) + err = s.db.Delete(iter.Key()) if err != nil { return } @@ -119,18 +125,23 @@ func migrateFrom0_sourcesProvider(s *LevelDBStorage) (err error) { return } -func migrateFrom0_reparentDomains(s *LevelDBStorage) (err error) { - iter := s.search("domain-") +func migrateFrom0_reparentDomains(s *KVStorage) (err error) { + iter := s.db.Search("domain-") defer iter.Release() for iter.Next() { - domstr := iter.Value() + domRaw := iter.Value() + domstr, ok := domRaw.([]byte) + if !ok { + log.Printf("Migrating v0 -> v1: skip %s (not bytes)...", iter.Key()) + continue + } domstr = bytes.Replace(domstr, []byte("\"id_source\":"), []byte("\"id_provider\":"), 1) log.Printf("Migrating v0 -> v1: %s...", iter.Key()) - err = s.db.Put(iter.Key(), domstr, nil) + err = s.db.Put(iter.Key(), domstr) if err != nil { return } diff --git a/internal/storage/leveldb/updates-from-1.go b/internal/storage/kvtpl/updates-from-1.go similarity index 72% rename from internal/storage/leveldb/updates-from-1.go rename to internal/storage/kvtpl/updates-from-1.go index c68ebb55..746cf29e 100644 --- a/internal/storage/leveldb/updates-from-1.go +++ b/internal/storage/kvtpl/updates-from-1.go @@ -32,7 +32,7 @@ import ( "git.happydns.org/happyDomain/model" ) -func migrateFrom1(s *LevelDBStorage) (err error) { +func migrateFrom1(s *KVStorage) (err error) { err = migrateFrom1_users_tree(s) if err != nil { return @@ -61,19 +61,19 @@ func genUserIdv2(input int64) (string, []byte, error) { return hex.EncodeToString(decoded), decoded, err } -func migrateFrom1_users_tree(s *LevelDBStorage) (err error) { - iter := s.search("user-") +func migrateFrom1_users_tree(s *KVStorage) (err error) { + iter := s.db.Search("user-") defer iter.Release() for iter.Next() { var user userV1 - err = decodeData(iter.Value(), &user) + err = s.db.DecodeData(iter.Value(), &user) if err != nil { return } newId, idRaw, errr := genUserIdv2(user.Id) - if err != nil { + if errr != nil { log.Printf("Migrating v1 -> v2: %s: unable to calculate new ID: %s", iter.Key(), errr.Error()) continue } else if len(idRaw) == 0 { @@ -106,17 +106,17 @@ func migrateFrom1_users_tree(s *LevelDBStorage) (err error) { log.Printf("Migrating v1 -> v2: %s to user-%x...", iter.Key(), idRaw) - err = s.put(fmt.Sprintf("user-%x", idRaw), newUser) + err = s.db.Put(fmt.Sprintf("user-%x", idRaw), newUser) if err != nil { return } - err = s.put(fmt.Sprintf("auth-%x", idRaw), user4auth) + err = s.db.Put(fmt.Sprintf("auth-%x", idRaw), user4auth) if err != nil { return } - err = s.delete(string(iter.Key())) + err = s.db.Delete(iter.Key()) if err != nil { return } @@ -127,27 +127,35 @@ func migrateFrom1_users_tree(s *LevelDBStorage) (err error) { migrateFrom1_provider(s, user.Id, newId), migrateFrom1_zone(s, user.Id, newId), ) + if err != nil { + return + } } return } -func migrateFrom1_domains(s *LevelDBStorage, oldUserId int64, newUserId string) (err error) { +func migrateFrom1_domains(s *KVStorage, oldUserId int64, newUserId string) (err error) { oldIdStr := []byte(fmt.Sprintf("\"id_owner\":%d", oldUserId)) newIdStr := []byte(fmt.Sprintf("\"id_owner\":\"%s\"", newUserId)) - iter := s.search("domain-") + iter := s.db.Search("domain-") defer iter.Release() for iter.Next() { - domstr := iter.Value() + domRaw := iter.Value() + domstr, ok := domRaw.([]byte) + if !ok { + log.Printf("Migrating v1 -> v2: skip %s (not bytes)...", iter.Key()) + continue + } migstr := bytes.Replace(domstr, oldIdStr, newIdStr, 1) if !bytes.Equal(migstr, domstr) { log.Printf("Migrating v1 -> v2: %s...", iter.Key()) - err = s.db.Put(iter.Key(), migstr, nil) + err = s.db.Put(iter.Key(), migstr) if err != nil { return } @@ -157,22 +165,27 @@ func migrateFrom1_domains(s *LevelDBStorage, oldUserId int64, newUserId string) return } -func migrateFrom1_provider(s *LevelDBStorage, oldUserId int64, newUserId string) (err error) { +func migrateFrom1_provider(s *KVStorage, oldUserId int64, newUserId string) (err error) { oldIdStr := []byte(fmt.Sprintf("\"_ownerid\":%d", oldUserId)) newIdStr := []byte(fmt.Sprintf("\"_ownerid\":\"%s\"", newUserId)) - iter := s.search("provider-") + iter := s.db.Search("provider-") defer iter.Release() for iter.Next() { - domstr := iter.Value() + provRaw := iter.Value() + provstr, ok := provRaw.([]byte) + if !ok { + log.Printf("Migrating v1 -> v2: skip %s (not bytes)...", iter.Key()) + continue + } - migstr := bytes.Replace(domstr, oldIdStr, newIdStr, 1) + migstr := bytes.Replace(provstr, oldIdStr, newIdStr, 1) - if !bytes.Equal(migstr, domstr) { + if !bytes.Equal(migstr, provstr) { log.Printf("Migrating v1 -> v2: %s...", iter.Key()) - err = s.db.Put(iter.Key(), migstr, nil) + err = s.db.Put(iter.Key(), migstr) if err != nil { return } @@ -182,22 +195,27 @@ func migrateFrom1_provider(s *LevelDBStorage, oldUserId int64, newUserId string) return } -func migrateFrom1_zone(s *LevelDBStorage, oldUserId int64, newUserId string) (err error) { +func migrateFrom1_zone(s *KVStorage, oldUserId int64, newUserId string) (err error) { oldIdStr := []byte(fmt.Sprintf("\"id_author\":%d", oldUserId)) newIdStr := []byte(fmt.Sprintf("\"id_author\":\"%s\"", newUserId)) - iter := s.search("domain.zone-") + iter := s.db.Search("domain.zone-") defer iter.Release() for iter.Next() { - domstr := iter.Value() + zoneRaw := iter.Value() + zonestr, ok := zoneRaw.([]byte) + if !ok { + log.Printf("Migrating v1 -> v2: skip %s (not bytes)...", iter.Key()) + continue + } - migstr := bytes.Replace(domstr, oldIdStr, newIdStr, 1) + migstr := bytes.Replace(zonestr, oldIdStr, newIdStr, 1) - if !bytes.Equal(migstr, domstr) { + if !bytes.Equal(migstr, zonestr) { log.Printf("Migrating v1 -> v2: %s...", iter.Key()) - err = s.db.Put(iter.Key(), migstr, nil) + err = s.db.Put(iter.Key(), migstr) if err != nil { return } diff --git a/internal/storage/leveldb/updates-from-2.go b/internal/storage/kvtpl/updates-from-2.go similarity index 72% rename from internal/storage/leveldb/updates-from-2.go rename to internal/storage/kvtpl/updates-from-2.go index 52d396c0..b753a848 100644 --- a/internal/storage/leveldb/updates-from-2.go +++ b/internal/storage/kvtpl/updates-from-2.go @@ -24,16 +24,16 @@ package database import ( "bytes" "encoding/base64" + "encoding/json" + "errors" "fmt" "log" "time" "git.happydns.org/happyDomain/model" - - "github.com/syndtr/goleveldb/leveldb/errors" ) -func migrateFrom2(s *LevelDBStorage) (err error) { +func migrateFrom2(s *KVStorage) (err error) { err = migrateFrom2_users_tree(s) if err != nil { return @@ -50,16 +50,12 @@ type userV2 struct { Settings happydns.UserSettings } -func migrateFrom2_users_tree(s *LevelDBStorage) (err error) { - iter := s.search("user-") - defer iter.Release() +func migrateFrom2_users_tree(s *KVStorage) (err error) { + iter := NewKVIterator[userV2](s.db, s.db.Search("user-")) + defer iter.Close() for iter.Next() { - var user userV2 - err = decodeData(iter.Value(), &user) - if err != nil { - return - } + user := iter.Item() newId := happydns.Identifier(user.Id) if len(newId) < happydns.IDENTIFIER_LEN { @@ -79,18 +75,18 @@ func migrateFrom2_users_tree(s *LevelDBStorage) (err error) { log.Printf("Migrating v2 -> v3: %s to user-%s...", iter.Key(), newId.String()) - err = s.put(fmt.Sprintf("user-%s", newId.String()), newUser) + err = s.db.Put(fmt.Sprintf("user-%s", newId.String()), newUser) if err != nil { return fmt.Errorf("unable to write %s: %w", iter.Key(), err) } - err = s.delete(string(iter.Key())) + err = s.db.Delete(iter.Key()) if err != nil { return fmt.Errorf("unable to delete migrated %s: %w", iter.Key(), err) } // Migrate object of the user - err = migrateFrom2_auth(s, user.Id, newId, user) + err = migrateFrom2_auth(s, user.Id, newId, *user) if err != nil { return fmt.Errorf("unable to migrate auth user for user-%s (%s): %w", newId.String(), user.Email, err) } @@ -109,15 +105,16 @@ func migrateFrom2_users_tree(s *LevelDBStorage) (err error) { return } -func migrateFrom2_auth(s *LevelDBStorage, oldUserId happydns.HexaString, newId happydns.Identifier, user userV2) (err error) { +func migrateFrom2_auth(s *KVStorage, oldUserId happydns.HexaString, newId happydns.Identifier, user userV2) (err error) { oldIdStr := []byte(fmt.Sprintf("\"Id\":\"%s\"", base64.StdEncoding.EncodeToString(oldUserId))) newIdStr := []byte(fmt.Sprintf("\"Id\":\"%s\"", newId.String())) oldAuthKey := fmt.Sprintf("auth-%x", oldUserId) - usrstr, err := s.db.Get([]byte(oldAuthKey), nil) + var usrstr json.RawMessage + err = s.db.Get(oldAuthKey, &usrstr) if err != nil { - if err == errors.ErrNotFound { + if errors.Is(err, happydns.ErrNotFound) { user4auth := &happydns.UserAuth{ Id: newId, Email: user.Email, @@ -129,28 +126,28 @@ func migrateFrom2_auth(s *LevelDBStorage, oldUserId happydns.HexaString, newId h log.Printf("Migrating v2 -> v3: auth-%s: %s not found, creating it", newId.String(), oldAuthKey) - return s.put(fmt.Sprintf("auth-%s", newId.String()), user4auth) + return s.db.Put(fmt.Sprintf("auth-%s", newId.String()), user4auth) } return fmt.Errorf("unable to find/decode %s: %w", oldAuthKey, err) } - migstr := bytes.Replace(usrstr, oldIdStr, newIdStr, 1) + migstr := bytes.Replace([]byte(usrstr), oldIdStr, newIdStr, 1) - if !bytes.Equal(migstr, usrstr) { + if !bytes.Equal(migstr, []byte(usrstr)) { var newauth happydns.UserAuth - err = decodeData(migstr, &newauth) + err = s.db.DecodeData(migstr, &newauth) if err != nil { log.Printf("From %s to %s", usrstr, migstr) return fmt.Errorf("unable to reconstruct a valid auth user: %w", err) } - err = s.db.Put([]byte(fmt.Sprintf("auth-%s", newId.String())), migstr, nil) + err = s.db.Put(fmt.Sprintf("auth-%s", newId.String()), json.RawMessage(migstr)) if err != nil { return fmt.Errorf("unable to write auth-%s (from %s): %w", newId.String(), oldAuthKey, err) } log.Printf("Migrating v2 -> v3: %s to auth-%s...", oldAuthKey, newId.String()) - err = s.delete(oldAuthKey) + err = s.db.Delete(oldAuthKey) if err != nil { return fmt.Errorf("unable to delete migrated %s: %w", oldAuthKey, err) } @@ -163,21 +160,22 @@ type sessionV2 struct { Id []byte `json:"id"` } -func migrateFrom2_session(s *LevelDBStorage, oldUserId happydns.HexaString, newUserId string) (err error) { +func migrateFrom2_session(s *KVStorage, oldUserId happydns.HexaString, newUserId string) (err error) { oldOwnerIdStr := []byte(fmt.Sprintf("\"login\":\"%x\"", oldUserId)) newOwnerIdStr := []byte(fmt.Sprintf("\"login\":\"%s\"", newUserId)) - iter := s.search("user.session-") - defer iter.Release() + iter := s.db.Search("user.session-") + kvIter := NewKVIterator[json.RawMessage](s.db, iter) + defer kvIter.Close() - for iter.Next() { - usrstr := iter.Value() + for kvIter.Next() { + usrstr := []byte(*kvIter.Item()) if bytes.Contains(usrstr, oldOwnerIdStr) { var session sessionV2 - err = decodeData(usrstr, &session) + err = s.db.DecodeData(usrstr, &session) if err != nil { - return fmt.Errorf("unable to decode %s: %w", iter.Key(), err) + return fmt.Errorf("unable to decode %s: %w", kvIter.Key(), err) } newId := happydns.Identifier(session.Id) @@ -189,15 +187,15 @@ func migrateFrom2_session(s *LevelDBStorage, oldUserId happydns.HexaString, newU migstr = bytes.Replace(migstr, oldOwnerIdStr, newOwnerIdStr, 1) if !bytes.Equal(migstr, usrstr) { - err = s.db.Put([]byte(fmt.Sprintf("user.session-%s", newUserId)), migstr, nil) + err = s.db.Put(fmt.Sprintf("user.session-%s", newUserId), json.RawMessage(migstr)) if err != nil { - return fmt.Errorf("unable to write user.session-%s (from %s): %w", newId.String(), iter.Key(), err) + return fmt.Errorf("unable to write user.session-%s (from %s): %w", newId.String(), kvIter.Key(), err) } - log.Printf("Migrating v2 -> v3: %s to user.session-%s...", iter.Key(), newId.String()) + log.Printf("Migrating v2 -> v3: %s to user.session-%s...", kvIter.Key(), newId.String()) - err = s.delete(string(iter.Key())) + err = s.db.Delete(kvIter.Key()) if err != nil { - return fmt.Errorf("unable to delete migrated %s: %w", iter.Key(), err) + return fmt.Errorf("unable to delete migrated %s: %w", kvIter.Key(), err) } } } @@ -211,27 +209,28 @@ type providerV2 struct { OwnerId happydns.HexaString `json:"_ownerid"` } -func migrateFrom2_provider(s *LevelDBStorage, oldUserId happydns.HexaString, newUserId string) (err error) { +func migrateFrom2_provider(s *KVStorage, oldUserId happydns.HexaString, newUserId string) (err error) { oldOwnerIdStr := []byte(fmt.Sprintf("\"_ownerid\":\"%x\"", oldUserId)) newOwnerIdStr := []byte(fmt.Sprintf("\"_ownerid\":\"%s\"", newUserId)) - iter := s.search("provider-") - defer iter.Release() + iter := s.db.Search("provider-") + kvIter := NewKVIterator[json.RawMessage](s.db, iter) + defer kvIter.Close() - for iter.Next() { - domstr := iter.Value() + for kvIter.Next() { + domstr := []byte(*kvIter.Item()) if bytes.Contains(domstr, oldOwnerIdStr) { var provider providerV2 - err = decodeData(domstr, &provider) + err = s.db.DecodeData(domstr, &provider) if err != nil { - return fmt.Errorf("unable to decode %s: %w", iter.Key(), err) + return fmt.Errorf("unable to decode %s: %w", kvIter.Key(), err) } var newId happydns.Identifier newId, err = happydns.NewRandomIdentifier() if err != nil { - return fmt.Errorf("unable to generate a new identifier for %s: %w", iter.Key(), err) + return fmt.Errorf("unable to generate a new identifier for %s: %w", kvIter.Key(), err) } oldIdStr := []byte(fmt.Sprintf("\"_id\":%d", provider.Id)) @@ -242,20 +241,20 @@ func migrateFrom2_provider(s *LevelDBStorage, oldUserId happydns.HexaString, new if !bytes.Equal(migstr, domstr) { var newprv happydns.ProviderMeta - err = decodeData(migstr, &newprv) + err = s.db.DecodeData(migstr, &newprv) if err != nil { log.Printf("From %s to %s", domstr, migstr) return fmt.Errorf("unable to reconstruct a valid provider: %w", err) } - log.Printf("Migrating v2 -> v3: %s...", iter.Key()) + log.Printf("Migrating v2 -> v3: %s...", kvIter.Key()) - err = s.db.Put([]byte(fmt.Sprintf("provider-%s", newId.String())), migstr, nil) + err = s.db.Put(fmt.Sprintf("provider-%s", newId.String()), json.RawMessage(migstr)) if err != nil { return } - err = s.delete(string(iter.Key())) + err = s.db.Delete(kvIter.Key()) if err != nil { return } @@ -277,29 +276,30 @@ type domainV2 struct { ZoneHistory []int64 `json:"zone_history"` } -func migrateFrom2_domains(s *LevelDBStorage, oldUserId happydns.HexaString, newUserId string, oldProviderId int64, newProviderId string) (err error) { +func migrateFrom2_domains(s *KVStorage, oldUserId happydns.HexaString, newUserId string, oldProviderId int64, newProviderId string) (err error) { oldProviderIdStr := []byte(fmt.Sprintf("\"id_provider\":%d", oldProviderId)) newProviderIdStr := []byte(fmt.Sprintf("\"id_provider\":\"%s\"", newProviderId)) oldOwnerIdStr := []byte(fmt.Sprintf("\"id_owner\":\"%x\"", oldUserId)) newOwnerIdStr := []byte(fmt.Sprintf("\"id_owner\":\"%s\"", newUserId)) - iter := s.search("domain-") - defer iter.Release() + iter := s.db.Search("domain-") + kvIter := NewKVIterator[json.RawMessage](s.db, iter) + defer kvIter.Close() - for iter.Next() { - domstr := iter.Value() + for kvIter.Next() { + domstr := []byte(*kvIter.Item()) if bytes.Contains(domstr, oldProviderIdStr) && bytes.Contains(domstr, oldOwnerIdStr) { var domain domainV2 - err = decodeData(domstr, &domain) + err = s.db.DecodeData(domstr, &domain) if err != nil { - return fmt.Errorf("unable to decode %s: %w", iter.Key(), err) + return fmt.Errorf("unable to decode %s: %w", kvIter.Key(), err) } var newId happydns.Identifier newId, err = happydns.NewRandomIdentifier() if err != nil { - return fmt.Errorf("unable to generate a new identifier for %s: %w", iter.Key(), err) + return fmt.Errorf("unable to generate a new identifier for %s: %w", kvIter.Key(), err) } oldIdStr := []byte(fmt.Sprintf("\"id\":%d", domain.Id)) @@ -312,7 +312,7 @@ func migrateFrom2_domains(s *LevelDBStorage, oldUserId happydns.HexaString, newU var newZoneId happydns.Identifier newZoneId, err = happydns.NewRandomIdentifier() if err != nil { - return fmt.Errorf("unable to generate a new identifier for a zone of %s: %w", iter.Key(), err) + return fmt.Errorf("unable to generate a new identifier for a zone of %s: %w", kvIter.Key(), err) } err = migrateFrom2_zone(s, oldUserId, newUserId, zoneid, newZoneId.String()) @@ -333,20 +333,20 @@ func migrateFrom2_domains(s *LevelDBStorage, oldUserId happydns.HexaString, newU if !bytes.Equal(migstr, domstr) { var newdn happydns.Domain - err = decodeData(migstr, &newdn) + err = s.db.DecodeData(migstr, &newdn) if err != nil { log.Printf("From %s to %s", domstr, migstr) return fmt.Errorf("unable to reconstruct a valid domain: %w", err) } - log.Printf("Migrating v2 -> v3: %s...", iter.Key()) + log.Printf("Migrating v2 -> v3: %s...", kvIter.Key()) - err = s.db.Put([]byte(fmt.Sprintf("domain-%s", newId.String())), migstr, nil) + err = s.db.Put(fmt.Sprintf("domain-%s", newId.String()), json.RawMessage(migstr)) if err != nil { return } - err = s.delete(string(iter.Key())) + err = s.db.Delete(kvIter.Key()) if err != nil { return } @@ -357,7 +357,7 @@ func migrateFrom2_domains(s *LevelDBStorage, oldUserId happydns.HexaString, newU return } -func migrateFrom2_zone(s *LevelDBStorage, oldUserId happydns.HexaString, newUserId string, oldZoneId int64, newZoneId string) (err error) { +func migrateFrom2_zone(s *KVStorage, oldUserId happydns.HexaString, newUserId string, oldZoneId int64, newZoneId string) (err error) { oldIdStr := []byte(fmt.Sprintf("\"id\":%d", oldZoneId)) newIdStr := []byte(fmt.Sprintf("\"id\":\"%s\"", newZoneId)) oldIdOwnerStr := []byte(fmt.Sprintf("\"id_author\":%d", oldUserId)) @@ -365,22 +365,23 @@ func migrateFrom2_zone(s *LevelDBStorage, oldUserId happydns.HexaString, newUser oldZoneKey := fmt.Sprintf("domain.zone-%d", oldZoneId) - zonestr, err := s.db.Get([]byte(oldZoneKey), nil) + var zonestr json.RawMessage + err = s.db.Get(oldZoneKey, &zonestr) if err != nil { return fmt.Errorf("unable to find/decode %s: %w", oldZoneKey, err) } - migstr := bytes.Replace(zonestr, oldIdStr, newIdStr, 1) + migstr := bytes.Replace([]byte(zonestr), oldIdStr, newIdStr, 1) migstr = bytes.Replace(migstr, oldIdOwnerStr, newIdOwnerStr, 1) - if !bytes.Equal(migstr, zonestr) { - err = s.db.Put([]byte(fmt.Sprintf("domain.zone-%s", newZoneId)), migstr, nil) + if !bytes.Equal(migstr, []byte(zonestr)) { + err = s.db.Put(fmt.Sprintf("domain.zone-%s", newZoneId), json.RawMessage(migstr)) if err != nil { return fmt.Errorf("unable to write domain.zone-%s (from %s): %w", newZoneId, oldZoneKey, err) } log.Printf("Migrating v2 -> v3: %s to domain.zone-%s...", oldZoneKey, newZoneId) - err = s.delete(oldZoneKey) + err = s.db.Delete(oldZoneKey) if err != nil { return fmt.Errorf("unable to delete migrated %s: %w", oldZoneKey, err) } diff --git a/internal/storage/leveldb/updates-from-3.go b/internal/storage/kvtpl/updates-from-3.go similarity index 74% rename from internal/storage/leveldb/updates-from-3.go rename to internal/storage/kvtpl/updates-from-3.go index f4fccb92..98ade634 100644 --- a/internal/storage/leveldb/updates-from-3.go +++ b/internal/storage/kvtpl/updates-from-3.go @@ -27,41 +27,41 @@ import ( "log" ) -func migrateFrom3(s *LevelDBStorage) (err error) { - err = migrateFrom3_records(s) - if err != nil { - return - } - - return +func migrateFrom3(s *KVStorage) error { + return migrateFrom3_records(s) } -func migrateFrom3_records(s *LevelDBStorage) (err error) { +func migrateFrom3_records(s *KVStorage) error { TypeStr := []byte("\"_svctype\":\"abstract.Origin\"") - iter := s.search("domain.zone-") + iter := NewKVIterator[[]byte](s.db, s.db.Search("domain.zone-")) + defer iter.Close() + for iter.Next() { - zonestr, err := s.db.Get(iter.Key(), nil) - if err != nil { - return fmt.Errorf("unable to find/decode %s: %w", iter.Key(), err) + zonestr, ok := iter.Raw().([]byte) + if !ok { + continue } + key := iter.Key() if bytes.Contains(zonestr, TypeStr) { - migstr := zonestr + migstr := make([]byte, len(zonestr)) + copy(migstr, zonestr) + migstr = bytes.Replace(migstr, []byte("000000000,\"retry\":"), []byte(",\"retry\":"), 1) migstr = bytes.Replace(migstr, []byte("000000000,\"expire\":"), []byte(",\"expire\":"), 1) migstr = bytes.Replace(migstr, []byte("000000000,\"nxttl\":"), []byte(",\"nxttl\":"), 1) migstr = bytes.Replace(migstr, []byte("000000000,\"ns\":"), []byte(",\"ns\":"), 1) if !bytes.Equal(migstr, zonestr) { - err = s.db.Put(iter.Key(), migstr, nil) + err := s.db.Put(key, migstr) if err != nil { - return fmt.Errorf("unable to write %s: %w", iter.Key(), err) + return fmt.Errorf("unable to write %s: %w", key, err) } - log.Printf("Migrating v2 -> v3: %s (contains Origin)...", iter.Key()) + log.Printf("Migrating v3 -> v4: %s (contains Origin)...", key) } } } - return + return nil } diff --git a/internal/storage/leveldb/updates-from-4.go b/internal/storage/kvtpl/updates-from-4.go similarity index 95% rename from internal/storage/leveldb/updates-from-4.go rename to internal/storage/kvtpl/updates-from-4.go index 2b9287e2..b8b97833 100644 --- a/internal/storage/leveldb/updates-from-4.go +++ b/internal/storage/kvtpl/updates-from-4.go @@ -25,6 +25,6 @@ import ( "fmt" ) -func migrateFrom4(s *LevelDBStorage) (err error) { +func migrateFrom4(s *KVStorage) (err error) { return fmt.Errorf("Unable to migrate from DB version 4. Please use a previous happyDomain release to perform this migration") } diff --git a/internal/storage/leveldb/updates-from-5.go b/internal/storage/kvtpl/updates-from-5.go similarity index 95% rename from internal/storage/leveldb/updates-from-5.go rename to internal/storage/kvtpl/updates-from-5.go index 612bc2cc..b84a7193 100644 --- a/internal/storage/leveldb/updates-from-5.go +++ b/internal/storage/kvtpl/updates-from-5.go @@ -25,6 +25,6 @@ import ( "fmt" ) -func migrateFrom5(s *LevelDBStorage) (err error) { +func migrateFrom5(s *KVStorage) (err error) { return fmt.Errorf("Unable to migrate from DB version 4. Please use a previous happyDomain release to perform this migration") } diff --git a/internal/storage/leveldb/updates-from-6.go b/internal/storage/kvtpl/updates-from-6.go similarity index 96% rename from internal/storage/leveldb/updates-from-6.go rename to internal/storage/kvtpl/updates-from-6.go index 230d8e17..c62a570e 100644 --- a/internal/storage/leveldb/updates-from-6.go +++ b/internal/storage/kvtpl/updates-from-6.go @@ -25,7 +25,7 @@ import ( "log" ) -func migrateFrom6(s *LevelDBStorage) error { +func migrateFrom6(s *KVStorage) error { log.Println("Drop all sessions to use new format") return s.ClearSessions() } diff --git a/internal/storage/leveldb/updates-from-7.go b/internal/storage/kvtpl/updates-from-7.go similarity index 99% rename from internal/storage/leveldb/updates-from-7.go rename to internal/storage/kvtpl/updates-from-7.go index 59e22d04..1d1fd4f4 100644 --- a/internal/storage/leveldb/updates-from-7.go +++ b/internal/storage/kvtpl/updates-from-7.go @@ -191,7 +191,7 @@ func explodeAbstractEMail(dn happydns.Subdomain, in *happydns.ServiceMessage) ([ var migrateFrom7SvcType map[string]func(json.RawMessage) (json.RawMessage, error) -func migrateFrom7(s *LevelDBStorage) (err error) { +func migrateFrom7(s *KVStorage) (err error) { migrateFrom7SvcType = make(map[string]func(json.RawMessage) (json.RawMessage, error)) // abstract.ACMEChallenge diff --git a/internal/storage/leveldb/updates.go b/internal/storage/kvtpl/updates.go similarity index 63% rename from internal/storage/leveldb/updates.go rename to internal/storage/kvtpl/updates.go index 50902637..937fce27 100644 --- a/internal/storage/leveldb/updates.go +++ b/internal/storage/kvtpl/updates.go @@ -1,5 +1,5 @@ // This file is part of the happyDomain (R) project. -// Copyright (c) 2020-2024 happyDomain +// Copyright (c) 2020-2025 happyDomain // Authors: Pierre-Olivier Mercier, et al. // // This program is offered under a commercial and under the AGPL license. @@ -26,9 +26,9 @@ import ( "log" ) -type LevelDBMigrationFunc func(s *LevelDBStorage) error +type KVMigrationFunc func(s *KVStorage) error -var migrations []LevelDBMigrationFunc = []LevelDBMigrationFunc{ +var migrations []KVMigrationFunc = []KVMigrationFunc{ migrateFrom0, migrateFrom1, migrateFrom2, @@ -39,49 +39,53 @@ var migrations []LevelDBMigrationFunc = []LevelDBMigrationFunc{ migrateFrom7, } -func (s *LevelDBStorage) SchemaVersion() int { +type Version struct { + Version int `json:"version"` +} + +func (s *KVStorage) SchemaVersion() int { return len(migrations) } -func (s *LevelDBStorage) MigrateSchema() (err error) { +func (s *KVStorage) MigrateSchema() (err error) { found := false - found, err = s.db.Has([]byte("version"), nil) + found, err = s.db.Has("version") if err != nil { return } - var version int + var version Version if !found { - version = len(migrations) - err = s.put("version", version) + version.Version = len(migrations) + err = s.db.Put("version", version) if err != nil { return } } - err = s.get("version", &version) + err = s.db.Get("version", &version.Version) if err != nil { return } - if version > len(migrations) { - return fmt.Errorf("Your database has revision %d, which is newer than the revision this happyDomain version can handle (max DB revision %d). Please update happyDomain", version, len(migrations)) + if version.Version > len(migrations) { + return fmt.Errorf("Your database has revision %d, which is newer than the revision this happyDomain version can handle (max DB revision %d). Please update happyDomain", version.Version, len(migrations)) } - for v, migration := range migrations[version:] { - log.Printf("Doing migration from %d to %d", version+v, version+v+1) + for v, migration := range migrations[version.Version:] { + log.Printf("Doing migration from %d to %d", version.Version+v, version.Version+v+1) // Do the migration if err = migration(s); err != nil { return } // Save the step - if err = s.put("version", version+v+1); err != nil { + if err = s.db.Put("version", version.Version+v+1); err != nil { return } - log.Printf("Migration from %d to %d DONE!", version+v, version+v+1) + log.Printf("Migration from %d to %d DONE!", version.Version+v, version.Version+v+1) } return nil diff --git a/internal/storage/leveldb/user.go b/internal/storage/kvtpl/user.go similarity index 59% rename from internal/storage/leveldb/user.go rename to internal/storage/kvtpl/user.go index b61fd895..48e45fbf 100644 --- a/internal/storage/leveldb/user.go +++ b/internal/storage/kvtpl/user.go @@ -1,5 +1,5 @@ // This file is part of the happyDomain (R) project. -// Copyright (c) 2020-2024 happyDomain +// Copyright (c) 2020-2025 happyDomain // Authors: Pierre-Olivier Mercier, et al. // // This program is offered under a commercial and under the AGPL license. @@ -25,35 +25,33 @@ import ( "errors" "fmt" - "github.com/syndtr/goleveldb/leveldb" - "github.com/syndtr/goleveldb/leveldb/util" - "git.happydns.org/happyDomain/model" ) -func (s *LevelDBStorage) ListAllUsers() (happydns.Iterator[happydns.User], error) { - iter := s.search("user-") - return NewLevelDBIterator[happydns.User](s.db, iter), nil +func (s *KVStorage) ListAllUsers() (happydns.Iterator[happydns.User], error) { + iter := s.db.Search("user-") + return NewKVIterator[happydns.User](s.db, iter), nil } -func (s *LevelDBStorage) getUser(key string) (*happydns.User, error) { +func (s *KVStorage) getUser(key string) (*happydns.User, error) { u := &happydns.User{} - err := s.get(key, &u) - if errors.Is(err, leveldb.ErrNotFound) { + err := s.db.Get(key, &u) + if errors.Is(err, happydns.ErrNotFound) { return nil, happydns.ErrUserNotFound } return u, err } -func (s *LevelDBStorage) GetUser(id happydns.Identifier) (u *happydns.User, err error) { +func (s *KVStorage) GetUser(id happydns.Identifier) (u *happydns.User, err error) { return s.getUser(fmt.Sprintf("user-%s", id.String())) } -func (s *LevelDBStorage) GetUserByEmail(email string) (*happydns.User, error) { +func (s *KVStorage) GetUserByEmail(email string) (*happydns.User, error) { users, err := s.ListAllUsers() if err != nil { return nil, err } + defer users.Close() for users.Next() { user := users.Item() @@ -65,11 +63,12 @@ func (s *LevelDBStorage) GetUserByEmail(email string) (*happydns.User, error) { return nil, happydns.ErrUserNotFound } -func (s *LevelDBStorage) UserExists(email string) bool { +func (s *KVStorage) UserExists(email string) bool { users, err := s.ListAllUsers() if err != nil { return false } + defer users.Close() for users.Next() { if users.Item().Email == email { @@ -80,9 +79,9 @@ func (s *LevelDBStorage) UserExists(email string) bool { return false } -func (s *LevelDBStorage) CreateOrUpdateUser(u *happydns.User) error { +func (s *KVStorage) CreateOrUpdateUser(u *happydns.User) error { if u.Id.IsEmpty() { - _, id, err := s.findIdentifierKey("user-") + _, id, err := s.db.FindIdentifierKey("user-") if err != nil { return err } @@ -90,39 +89,30 @@ func (s *LevelDBStorage) CreateOrUpdateUser(u *happydns.User) error { u.Id = id } - return s.put(fmt.Sprintf("user-%s", u.Id.String()), u) + return s.db.Put(fmt.Sprintf("user-%s", u.Id.String()), u) } -func (s *LevelDBStorage) DeleteUser(uId happydns.Identifier) error { - return s.delete(fmt.Sprintf("user-%s", uId.String())) +func (s *KVStorage) DeleteUser(uId happydns.Identifier) error { + return s.db.Delete(fmt.Sprintf("user-%s", uId.String())) } -func (s *LevelDBStorage) ClearUsers() error { +func (s *KVStorage) ClearUsers() error { if err := s.ClearSessions(); err != nil { return err } - tx, err := s.db.OpenTransaction() + iter, err := s.ListAllUsers() if err != nil { return err } - - iter := tx.NewIterator(util.BytesPrefix([]byte("user-")), nil) - defer iter.Release() + defer iter.Close() for iter.Next() { - err = tx.Delete(iter.Key(), nil) + err = s.db.Delete(iter.Key()) if err != nil { - tx.Discard() return err } } - err = tx.Commit() - if err != nil { - tx.Discard() - return err - } - return nil } diff --git a/internal/storage/kvtpl/zone.go b/internal/storage/kvtpl/zone.go new file mode 100644 index 00000000..0af7a10e --- /dev/null +++ b/internal/storage/kvtpl/zone.go @@ -0,0 +1,96 @@ +// This file is part of the happyDomain (R) project. +// Copyright (c) 2020-2025 happyDomain +// Authors: Pierre-Olivier Mercier, et al. +// +// This program is offered under a commercial and under the AGPL license. +// For commercial licensing, contact us at . +// +// For AGPL licensing: +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package database + +import ( + "errors" + "fmt" + + "git.happydns.org/happyDomain/model" +) + +func (s *KVStorage) ListAllZones() (happydns.Iterator[happydns.ZoneMessage], error) { + iter := s.db.Search("domain.zone-") + return NewKVIterator[happydns.ZoneMessage](s.db, iter), nil +} + +func (s *KVStorage) GetZone(id happydns.Identifier) (*happydns.ZoneMessage, error) { + z := &happydns.ZoneMessage{} + err := s.db.Get(fmt.Sprintf("domain.zone-%s", id.String()), &z) + if errors.Is(err, happydns.ErrNotFound) { + return nil, happydns.ErrZoneNotFound + } + return z, err +} + +func (s *KVStorage) getZoneMeta(id string) (z *happydns.ZoneMeta, err error) { + z = &happydns.ZoneMeta{} + err = s.db.Get(id, z) + if errors.Is(err, happydns.ErrNotFound) { + return nil, happydns.ErrZoneNotFound + } + return +} + +func (s *KVStorage) GetZoneMeta(id happydns.Identifier) (z *happydns.ZoneMeta, err error) { + z, err = s.getZoneMeta(fmt.Sprintf("domain.zone-%s", id.String())) + return +} + +func (s *KVStorage) CreateZone(z *happydns.Zone) error { + key, id, err := s.db.FindIdentifierKey("domain.zone-") + if err != nil { + return err + } + + z.Id = id + return s.db.Put(key, z) +} + +func (s *KVStorage) UpdateZone(z *happydns.Zone) error { + return s.db.Put(fmt.Sprintf("domain.zone-%s", z.Id.String()), z) +} + +func (s *KVStorage) UpdateZoneMessage(z *happydns.ZoneMessage) error { + return s.db.Put(fmt.Sprintf("domain.zone-%s", z.Id.String()), z) +} + +func (s *KVStorage) DeleteZone(id happydns.Identifier) error { + return s.db.Delete(fmt.Sprintf("domain.zone-%s", id.String())) +} + +func (s *KVStorage) ClearZones() error { + iter, err := s.ListAllZones() + if err != nil { + return err + } + defer iter.Close() + + for iter.Next() { + err = s.db.Delete(iter.Key()) + if err != nil { + return err + } + } + + return nil +} diff --git a/internal/storage/leveldb/config.go b/internal/storage/leveldb/config.go index da0c7232..3f3b25de 100644 --- a/internal/storage/leveldb/config.go +++ b/internal/storage/leveldb/config.go @@ -25,6 +25,7 @@ import ( "flag" "git.happydns.org/happyDomain/internal/storage" + kv "git.happydns.org/happyDomain/internal/storage/kvtpl" ) var path string @@ -36,5 +37,10 @@ func init() { } func Instantiate() (storage.Storage, error) { - return NewLevelDBStorage(path) + db, err := NewLevelDBStorage(path) + if err != nil { + return nil, err + } + + return kv.NewKVDatabase(db) } diff --git a/internal/storage/leveldb/database.go b/internal/storage/leveldb/database.go index f1e63886..9c8d55bf 100644 --- a/internal/storage/leveldb/database.go +++ b/internal/storage/leveldb/database.go @@ -23,14 +23,15 @@ package database // import "git.happydns.org/happyDomain/internal/storage/leveld import ( "encoding/json" + goerrors "errors" "fmt" "log" "github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb/errors" - "github.com/syndtr/goleveldb/leveldb/iterator" "github.com/syndtr/goleveldb/leveldb/util" + "git.happydns.org/happyDomain/internal/storage" "git.happydns.org/happyDomain/model" ) @@ -68,16 +69,31 @@ func decodeData(data []byte, v interface{}) error { return json.Unmarshal(data, v) } -func (s *LevelDBStorage) get(key string, v interface{}) error { +func (s *LevelDBStorage) DecodeData(data interface{}, v interface{}) error { + b, ok := data.([]byte) + if !ok { + return fmt.Errorf("data to decode are not in []byte format (%T)", data) + } + return decodeData(b, v) +} + +func (s *LevelDBStorage) Has(key string) (bool, error) { + return s.db.Has([]byte(key), nil) +} + +func (s *LevelDBStorage) Get(key string, v interface{}) error { data, err := s.db.Get([]byte(key), nil) if err != nil { + if goerrors.Is(err, leveldb.ErrNotFound) { + return happydns.ErrNotFound + } return err } return decodeData(data, v) } -func (s *LevelDBStorage) put(key string, v interface{}) error { +func (s *LevelDBStorage) Put(key string, v interface{}) error { data, err := json.Marshal(v) if err != nil { return err @@ -86,7 +102,7 @@ func (s *LevelDBStorage) put(key string, v interface{}) error { return s.db.Put([]byte(key), data, nil) } -func (s *LevelDBStorage) findIdentifierKey(prefix string) (key string, id happydns.Identifier, err error) { +func (s *LevelDBStorage) FindIdentifierKey(prefix string) (key string, id happydns.Identifier, err error) { found := true for found { id, err = happydns.NewRandomIdentifier() @@ -103,10 +119,10 @@ func (s *LevelDBStorage) findIdentifierKey(prefix string) (key string, id happyd return } -func (s *LevelDBStorage) delete(key string) error { +func (s *LevelDBStorage) Delete(key string) error { return s.db.Delete([]byte(key), nil) } -func (s *LevelDBStorage) search(prefix string) iterator.Iterator { - return s.db.NewIterator(util.BytesPrefix([]byte(prefix)), nil) +func (s *LevelDBStorage) Search(prefix string) storage.Iterator { + return NewIterator(s.db.NewIterator(util.BytesPrefix([]byte(prefix)), nil)) } diff --git a/internal/storage/leveldb/domain-log.go b/internal/storage/leveldb/domain-log.go deleted file mode 100644 index 96bbf488..00000000 --- a/internal/storage/leveldb/domain-log.go +++ /dev/null @@ -1,107 +0,0 @@ -// This file is part of the happyDomain (R) project. -// Copyright (c) 2020-2024 happyDomain -// Authors: Pierre-Olivier Mercier, et al. -// -// This program is offered under a commercial and under the AGPL license. -// For commercial licensing, contact us at . -// -// For AGPL licensing: -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package database - -import ( - "errors" - "fmt" - "strings" - - "github.com/syndtr/goleveldb/leveldb" - - "git.happydns.org/happyDomain/model" -) - -func (s *LevelDBStorage) ListAllDomainLogs() (happydns.Iterator[happydns.DomainLogWithDomainId], error) { - iter := s.search("domain.log|") - return NewLevelDBIteratorCustomDecode[happydns.DomainLogWithDomainId](s.db, iter, func(data []byte, v interface{}) error { - err := decodeData(data, v) - if err != nil { - return err - } - - st := strings.Split(string(iter.Key()), "|") - if len(st) < 3 { - return fmt.Errorf("invalid domain log key: %s", string(iter.Key())) - } - - v.(*happydns.DomainLogWithDomainId).DomainId, err = happydns.NewIdentifierFromString(st[1]) - return err - }), nil -} - -func (s *LevelDBStorage) ListDomainLogs(domain *happydns.Domain) (logs []*happydns.DomainLog, err error) { - iter := s.search(fmt.Sprintf("domain.log|%s|", domain.Id.String())) - defer iter.Release() - - for iter.Next() { - var z happydns.DomainLog - - err = decodeData(iter.Value(), &z) - if err != nil { - return - } - - logs = append(logs, &z) - } - - return -} - -func (s *LevelDBStorage) getDomainLog(id string) (l *happydns.DomainLog, d *happydns.Domain, err error) { - l = &happydns.DomainLog{} - err = s.get(id, l) - if errors.Is(err, leveldb.ErrNotFound) { - return nil, nil, happydns.ErrDomainLogNotFound - } - - st := strings.Split(id, "|") - if len(st) < 3 { - return - } - - d = &happydns.Domain{} - err = s.get(id, fmt.Sprintf("domain-%s", st[1])) - if errors.Is(err, leveldb.ErrNotFound) { - return nil, nil, happydns.ErrDomainNotFound - } - - return -} - -func (s *LevelDBStorage) CreateDomainLog(d *happydns.Domain, l *happydns.DomainLog) error { - key, id, err := s.findIdentifierKey(fmt.Sprintf("domain.log|%s|", d.Id.String())) - if err != nil { - return err - } - - l.Id = id - return s.put(key, l) -} - -func (s *LevelDBStorage) UpdateDomainLog(d *happydns.Domain, l *happydns.DomainLog) error { - return s.put(fmt.Sprintf("domain.log|%s|%s", d.Id.String(), l.Id.String()), l) -} - -func (s *LevelDBStorage) DeleteDomainLog(d *happydns.Domain, l *happydns.DomainLog) error { - return s.delete(fmt.Sprintf("domain.log|%s|%s", d.Id.String(), l.Id.String())) -} diff --git a/internal/storage/leveldb/iterator.go b/internal/storage/leveldb/iterator.go index 8026bc68..1c257df7 100644 --- a/internal/storage/leveldb/iterator.go +++ b/internal/storage/leveldb/iterator.go @@ -22,104 +22,35 @@ package database import ( - "fmt" - "log" - - "github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb/iterator" ) -// LevelDBIterator is a generic implementation of Iterator for LevelDB. -type LevelDBIterator[T any] struct { - db *leveldb.DB - iter iterator.Iterator - err error - item *T - decode func([]byte, interface{}) error +type LevelDBIterator struct { + iter iterator.Iterator } -// NewLevelDBIterator creates a new LevelDBIterator instance for the given LevelDB iterator and decode function. -func NewLevelDBIterator[T any](db *leveldb.DB, iter iterator.Iterator) *LevelDBIterator[T] { - return &LevelDBIterator[T]{ - db: db, - iter: iter, - decode: decodeData, +func NewIterator(iter iterator.Iterator) *LevelDBIterator { + return &LevelDBIterator{ + iter: iter, } } -// NewLevelDBIterator creates a new LevelDBIterator instance for the given LevelDB iterator and decode function. -func NewLevelDBIteratorCustomDecode[T any](db *leveldb.DB, iter iterator.Iterator, decodeFunc func([]byte, interface{}) error) *LevelDBIterator[T] { - return &LevelDBIterator[T]{ - db: db, - iter: iter, - decode: decodeFunc, - } +func (it *LevelDBIterator) Next() bool { + return it.iter.Next() } -// Next moves the iterator to the next valid item. -// Skips items that fail to decode and logs the error. -func (it *LevelDBIterator[T]) Next() bool { - for it.iter.Next() { - var value T - err := it.decode(it.iter.Value(), &value) - if err != nil { - log.Printf("LevelDBIterator: error decoding item at key %q: %s", it.iter.Key(), err) - it.err = err - continue - } - it.item = &value - return true - } - return false +func (it *LevelDBIterator) Key() string { + return string(it.iter.Key()) } -// NextWithError advances the iterator to the next item, on decode error it doesn't continue to the next item. -// Returns true if there is a next item, false otherwise. -func (it *LevelDBIterator[T]) NextWithError() bool { - if it.iter.Next() { - var value T - err := it.decode(it.iter.Value(), &value) - if err != nil { - it.err = err - it.item = nil - } else { - it.err = nil - it.item = &value - } - return true - } - return false -} - -// Item returns the current item from the iterator. -// Only valid after a successful call to Next(). -func (it *LevelDBIterator[T]) Item() *T { - return it.item -} - -// DropItem deletes the key currently pointed to by the iterator. -func (it *LevelDBIterator[T]) DropItem() error { - if it.iter == nil || !it.iter.Valid() { - return fmt.Errorf("DropItem: iterator is not valid") - } - return it.db.Delete(it.iter.Key(), nil) -} - -// Raw returns the raw (non-decoded) value at the current iterator position. -// Should only be called after a successful call to Next(). -func (it *LevelDBIterator[T]) Raw() []byte { - if it.iter == nil || !it.iter.Valid() { - return []byte{} - } +func (it *LevelDBIterator) Value() interface{} { return it.iter.Value() } -// Err returns the first error encountered during iteration, if any. -func (it *LevelDBIterator[T]) Err() error { - return it.err +func (it *LevelDBIterator) Valid() bool { + return it.iter.Valid() } -// Close releases resources held by the underlying LevelDB iterator. -func (it *LevelDBIterator[T]) Close() { +func (it *LevelDBIterator) Release() { it.iter.Release() } diff --git a/internal/storage/leveldb/provider.go b/internal/storage/leveldb/provider.go deleted file mode 100644 index b9144716..00000000 --- a/internal/storage/leveldb/provider.go +++ /dev/null @@ -1,138 +0,0 @@ -// This file is part of the happyDomain (R) project. -// Copyright (c) 2020-2024 happyDomain -// Authors: Pierre-Olivier Mercier, et al. -// -// This program is offered under a commercial and under the AGPL license. -// For commercial licensing, contact us at . -// -// For AGPL licensing: -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package database - -import ( - "bytes" - "errors" - "fmt" - - "git.happydns.org/happyDomain/model" - - "github.com/syndtr/goleveldb/leveldb" - "github.com/syndtr/goleveldb/leveldb/util" -) - -func (s *LevelDBStorage) ListAllProviders() (happydns.Iterator[happydns.ProviderMessage], error) { - iter := s.search("provider-") - return NewLevelDBIterator[happydns.ProviderMessage](s.db, iter), nil -} - -func (s *LevelDBStorage) getProviderMeta(id happydns.Identifier) (*happydns.ProviderMessage, error) { - v, err := s.db.Get(id, nil) - if err != nil { - if errors.Is(err, leveldb.ErrNotFound) { - return nil, happydns.ErrProviderNotFound - } else { - return nil, err - } - } - - srcMsg := &happydns.ProviderMessage{} - err = decodeData(v, srcMsg) - return srcMsg, err -} - -func (s *LevelDBStorage) ListProviders(u *happydns.User) (srcs happydns.ProviderMessages, err error) { - iter := s.search("provider-") - defer iter.Release() - - for iter.Next() { - var srcMsg happydns.ProviderMessage - err = decodeData(iter.Value(), &srcMsg) - if err != nil { - return - } - - if !bytes.Equal(srcMsg.Owner, u.Id) { - continue - } - - srcs = append(srcs, &srcMsg) - } - - return -} - -func (s *LevelDBStorage) GetProvider(id happydns.Identifier) (*happydns.ProviderMessage, error) { - v, err := s.db.Get([]byte(fmt.Sprintf("provider-%s", id.String())), nil) - if err != nil { - if errors.Is(err, leveldb.ErrNotFound) { - return nil, happydns.ErrProviderNotFound - } else { - return nil, err - } - } - - var prvdMsg happydns.ProviderMessage - err = decodeData(v, &prvdMsg) - if err != nil { - return nil, err - } - - return &prvdMsg, err -} - -func (s *LevelDBStorage) CreateProvider(prvd *happydns.Provider) error { - key, id, err := s.findIdentifierKey("provider-") - if err != nil { - return err - } - - prvd.Id = id - - return s.put(key, prvd) -} - -func (s *LevelDBStorage) UpdateProvider(prvd *happydns.Provider) error { - return s.put(fmt.Sprintf("provider-%s", prvd.Id.String()), prvd) -} - -func (s *LevelDBStorage) DeleteProvider(prvdId happydns.Identifier) error { - return s.delete(fmt.Sprintf("provider-%s", prvdId.String())) -} - -func (s *LevelDBStorage) ClearProviders() error { - tx, err := s.db.OpenTransaction() - if err != nil { - return err - } - - iter := tx.NewIterator(util.BytesPrefix([]byte("provider-")), nil) - defer iter.Release() - - for iter.Next() { - err = tx.Delete(iter.Key(), nil) - if err != nil { - tx.Discard() - return err - } - } - - err = tx.Commit() - if err != nil { - tx.Discard() - return err - } - - return nil -} diff --git a/internal/storage/leveldb/session.go b/internal/storage/leveldb/session.go deleted file mode 100644 index 74fcad7e..00000000 --- a/internal/storage/leveldb/session.go +++ /dev/null @@ -1,122 +0,0 @@ -// This file is part of the happyDomain (R) project. -// Copyright (c) 2020-2024 happyDomain -// Authors: Pierre-Olivier Mercier, et al. -// -// This program is offered under a commercial and under the AGPL license. -// For commercial licensing, contact us at . -// -// For AGPL licensing: -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package database - -import ( - "errors" - "fmt" - - "git.happydns.org/happyDomain/model" - - "github.com/syndtr/goleveldb/leveldb" - "github.com/syndtr/goleveldb/leveldb/util" -) - -func (s *LevelDBStorage) ListAllSessions() (happydns.Iterator[happydns.Session], error) { - iter := s.search("user.session-") - return NewLevelDBIterator[happydns.Session](s.db, iter), nil -} - -func (s *LevelDBStorage) getSession(id string) (*happydns.Session, error) { - session := &happydns.Session{} - err := s.get(id, &session) - if errors.Is(err, leveldb.ErrNotFound) { - return nil, happydns.ErrSessionNotFound - } - return session, err -} - -func (s *LevelDBStorage) GetSession(id string) (session *happydns.Session, err error) { - return s.getSession(fmt.Sprintf("user.session-%s", id)) -} - -func (s *LevelDBStorage) ListAuthUserSessions(user *happydns.UserAuth) (sessions []*happydns.Session, err error) { - iter := s.search("user.session-") - defer iter.Release() - - for iter.Next() { - var s happydns.Session - - err = decodeData(iter.Value(), &s) - if err != nil { - return - } - if s.IdUser.Equals(user.Id) { - sessions = append(sessions, &s) - } - } - - return -} - -func (s *LevelDBStorage) ListUserSessions(userid happydns.Identifier) (sessions []*happydns.Session, err error) { - iter := s.search("user.session-") - defer iter.Release() - - for iter.Next() { - var s happydns.Session - - err = decodeData(iter.Value(), &s) - if err != nil { - return - } - if s.IdUser.Equals(userid) { - sessions = append(sessions, &s) - } - } - - return -} - -func (s *LevelDBStorage) UpdateSession(session *happydns.Session) error { - return s.put(fmt.Sprintf("user.session-%s", session.Id), session) -} - -func (s *LevelDBStorage) DeleteSession(id string) error { - return s.delete(fmt.Sprintf("user.session-%s", id)) -} - -func (s *LevelDBStorage) ClearSessions() error { - tx, err := s.db.OpenTransaction() - if err != nil { - return err - } - - iter := tx.NewIterator(util.BytesPrefix([]byte("user.session-")), nil) - defer iter.Release() - - for iter.Next() { - err = tx.Delete(iter.Key(), nil) - if err != nil { - tx.Discard() - return err - } - } - - err = tx.Commit() - if err != nil { - tx.Discard() - return err - } - - return nil -} diff --git a/internal/storage/leveldb/zone.go b/internal/storage/leveldb/zone.go deleted file mode 100644 index ed8138f9..00000000 --- a/internal/storage/leveldb/zone.go +++ /dev/null @@ -1,108 +0,0 @@ -// This file is part of the happyDomain (R) project. -// Copyright (c) 2020-2024 happyDomain -// Authors: Pierre-Olivier Mercier, et al. -// -// This program is offered under a commercial and under the AGPL license. -// For commercial licensing, contact us at . -// -// For AGPL licensing: -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package database - -import ( - "errors" - "fmt" - - "github.com/syndtr/goleveldb/leveldb" - "github.com/syndtr/goleveldb/leveldb/util" - - "git.happydns.org/happyDomain/model" -) - -func (s *LevelDBStorage) ListAllZones() (happydns.Iterator[happydns.ZoneMessage], error) { - iter := s.search("domain.zone-") - return NewLevelDBIterator[happydns.ZoneMessage](s.db, iter), nil -} - -func (s *LevelDBStorage) GetZone(id happydns.Identifier) (*happydns.ZoneMessage, error) { - z := &happydns.ZoneMessage{} - err := s.get(fmt.Sprintf("domain.zone-%s", id.String()), &z) - if errors.Is(err, leveldb.ErrNotFound) { - return nil, happydns.ErrZoneNotFound - } - return z, err -} - -func (s *LevelDBStorage) getZoneMeta(id string) (z *happydns.ZoneMeta, err error) { - z = &happydns.ZoneMeta{} - err = s.get(id, z) - if errors.Is(err, leveldb.ErrNotFound) { - return nil, happydns.ErrZoneNotFound - } - return -} - -func (s *LevelDBStorage) GetZoneMeta(id happydns.Identifier) (z *happydns.ZoneMeta, err error) { - z, err = s.getZoneMeta(fmt.Sprintf("domain.zone-%s", id.String())) - return -} - -func (s *LevelDBStorage) CreateZone(z *happydns.Zone) error { - key, id, err := s.findIdentifierKey("domain.zone-") - if err != nil { - return err - } - - z.Id = id - return s.put(key, z) -} - -func (s *LevelDBStorage) UpdateZone(z *happydns.Zone) error { - return s.put(fmt.Sprintf("domain.zone-%s", z.Id.String()), z) -} - -func (s *LevelDBStorage) UpdateZoneMessage(z *happydns.ZoneMessage) error { - return s.put(fmt.Sprintf("domain.zone-%s", z.Id.String()), z) -} - -func (s *LevelDBStorage) DeleteZone(id happydns.Identifier) error { - return s.delete(fmt.Sprintf("domain.zone-%s", id.String())) -} - -func (s *LevelDBStorage) ClearZones() error { - tx, err := s.db.OpenTransaction() - if err != nil { - return err - } - - iter := tx.NewIterator(util.BytesPrefix([]byte("domain.zone-")), nil) - defer iter.Release() - - for iter.Next() { - err = tx.Delete(iter.Key(), nil) - if err != nil { - tx.Discard() - return err - } - } - - err = tx.Commit() - if err != nil { - tx.Discard() - return err - } - - return nil -} diff --git a/internal/usecase/authentication_test.go b/internal/usecase/authentication_test.go index 7cf7eea9..0383906b 100644 --- a/internal/usecase/authentication_test.go +++ b/internal/usecase/authentication_test.go @@ -5,6 +5,7 @@ import ( "time" "git.happydns.org/happyDomain/internal/storage/inmemory" + kv "git.happydns.org/happyDomain/internal/storage/kvtpl" "git.happydns.org/happyDomain/internal/usecase" userUC "git.happydns.org/happyDomain/internal/usecase/user" "git.happydns.org/happyDomain/model" @@ -22,8 +23,9 @@ func (u testUserInfo) JoinNewsletter() bool { return u.newsletter } func Test_CompleteAuthentication(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - userUsecase := userUC.NewUserUsecases(mem, nil, nil, nil) - authenticationUsecase := usecase.NewAuthenticationUsecase(&happydns.Options{}, mem, userUsecase) + db, _ := kv.NewKVDatabase(mem) + userUsecase := userUC.NewUserUsecases(db, nil, nil, nil) + authenticationUsecase := usecase.NewAuthenticationUsecase(&happydns.Options{}, db, userUsecase) uinfo := testUserInfo{ id: happydns.Identifier([]byte("user-123")), @@ -40,7 +42,7 @@ func Test_CompleteAuthentication(t *testing.T) { } // Check the user is correctly stored in db - stored, err := mem.GetUser(happydns.Identifier([]byte("user-123"))) + stored, err := db.GetUser(happydns.Identifier([]byte("user-123"))) if err != nil { t.Fatalf("expected stored user, got error: %v", err) } @@ -61,9 +63,10 @@ func (ds *testNewsletterSubscription) SubscribeToNewsletter(u happydns.UserInfo) func Test_CompleteAuthentication_WithNewsletter(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() + db, _ := kv.NewKVDatabase(mem) mockNewsletterSubscription := &testNewsletterSubscription{} - userUsecase := userUC.NewUserUsecases(mem, mockNewsletterSubscription, nil, nil) - authenticationUsecase := usecase.NewAuthenticationUsecase(&happydns.Options{}, mem, userUsecase) + userUsecase := userUC.NewUserUsecases(db, mockNewsletterSubscription, nil, nil) + authenticationUsecase := usecase.NewAuthenticationUsecase(&happydns.Options{}, db, userUsecase) uinfo := testUserInfo{ id: happydns.Identifier([]byte("user-123")), @@ -97,6 +100,7 @@ func Test_CompleteAuthentication_WithNewsletter(t *testing.T) { func Test_AuthenticateUserWithPassword_WrongPassword(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() + db, _ := kv.NewKVDatabase(mem) authUser := &happydns.UserAuth{ Email: "a@b.c", @@ -106,13 +110,13 @@ func Test_AuthenticateUserWithPassword_WrongPassword(t *testing.T) { t.Fatalf("unexpected error: %v", err) } - err = mem.CreateAuthUser(authUser) + err = db.CreateAuthUser(authUser) if err != nil { t.Fatalf("unexpected error: %v", err) } - userUsecase := userUC.NewUserUsecases(mem, nil, nil, nil) - authenticationUsecase := usecase.NewAuthenticationUsecase(&happydns.Options{}, mem, userUsecase) + userUsecase := userUC.NewUserUsecases(db, nil, nil, nil) + authenticationUsecase := usecase.NewAuthenticationUsecase(&happydns.Options{}, db, userUsecase) _, err = authenticationUsecase.AuthenticateUserWithPassword(happydns.LoginRequest{ Email: "a@b.c", @@ -125,6 +129,7 @@ func Test_AuthenticateUserWithPassword_WrongPassword(t *testing.T) { func Test_AuthenticateUserWithPassword_WeakPassword(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() + db, _ := kv.NewKVDatabase(mem) authUser := &happydns.UserAuth{ Email: "a@b.c", @@ -134,13 +139,13 @@ func Test_AuthenticateUserWithPassword_WeakPassword(t *testing.T) { t.Fatalf("unexpected error: %v", err) } - err = mem.CreateAuthUser(authUser) + err = db.CreateAuthUser(authUser) if err != nil { t.Fatalf("unexpected error: %v", err) } - userUsecase := userUC.NewUserUsecases(mem, nil, nil, nil) - authenticationUsecase := usecase.NewAuthenticationUsecase(&happydns.Options{}, mem, userUsecase) + userUsecase := userUC.NewUserUsecases(db, nil, nil, nil) + authenticationUsecase := usecase.NewAuthenticationUsecase(&happydns.Options{}, db, userUsecase) _, err = authenticationUsecase.AuthenticateUserWithPassword(happydns.LoginRequest{ Email: "a@b.c", @@ -153,6 +158,7 @@ func Test_AuthenticateUserWithPassword_WeakPassword(t *testing.T) { func Test_AuthenticateUserWithPassword_UnverifiedEmail(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() + db, _ := kv.NewKVDatabase(mem) authUser := &happydns.UserAuth{ Email: "a@b.c", @@ -162,13 +168,13 @@ func Test_AuthenticateUserWithPassword_UnverifiedEmail(t *testing.T) { t.Fatalf("unexpected error: %v", err) } - err = mem.CreateAuthUser(authUser) + err = db.CreateAuthUser(authUser) if err != nil { t.Fatalf("unexpected error: %v", err) } - userUsecase := userUC.NewUserUsecases(mem, nil, nil, nil) - authenticationUsecase := usecase.NewAuthenticationUsecase(&happydns.Options{}, mem, userUsecase) + userUsecase := userUC.NewUserUsecases(db, nil, nil, nil) + authenticationUsecase := usecase.NewAuthenticationUsecase(&happydns.Options{}, db, userUsecase) _, err = authenticationUsecase.AuthenticateUserWithPassword(happydns.LoginRequest{ Email: "a@b.c", @@ -181,6 +187,7 @@ func Test_AuthenticateUserWithPassword_UnverifiedEmail(t *testing.T) { func Test_AuthenticateUserWithPassword_NoEmail(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() + db, _ := kv.NewKVDatabase(mem) authUser := &happydns.UserAuth{ Email: "a@b.c", @@ -190,13 +197,13 @@ func Test_AuthenticateUserWithPassword_NoEmail(t *testing.T) { t.Fatalf("unexpected error: %v", err) } - err = mem.CreateAuthUser(authUser) + err = db.CreateAuthUser(authUser) if err != nil { t.Fatalf("unexpected error: %v", err) } - userUsecase := userUC.NewUserUsecases(mem, nil, nil, nil) - authenticationUsecase := usecase.NewAuthenticationUsecase(&happydns.Options{NoMail: true}, mem, userUsecase) + userUsecase := userUC.NewUserUsecases(db, nil, nil, nil) + authenticationUsecase := usecase.NewAuthenticationUsecase(&happydns.Options{NoMail: true}, db, userUsecase) _, err = authenticationUsecase.AuthenticateUserWithPassword(happydns.LoginRequest{ Email: "a@b.c", @@ -209,6 +216,7 @@ func Test_AuthenticateUserWithPassword_NoEmail(t *testing.T) { func Test_AuthenticateUserWithPassword(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() + db, _ := kv.NewKVDatabase(mem) now := time.Now() authUser := &happydns.UserAuth{ @@ -220,13 +228,13 @@ func Test_AuthenticateUserWithPassword(t *testing.T) { t.Fatalf("unexpected error: %v", err) } - err = mem.CreateAuthUser(authUser) + err = db.CreateAuthUser(authUser) if err != nil { t.Fatalf("unexpected error: %v", err) } - userUsecase := userUC.NewUserUsecases(mem, nil, nil, nil) - authenticationUsecase := usecase.NewAuthenticationUsecase(&happydns.Options{}, mem, userUsecase) + userUsecase := userUC.NewUserUsecases(db, nil, nil, nil) + authenticationUsecase := usecase.NewAuthenticationUsecase(&happydns.Options{}, db, userUsecase) _, err = authenticationUsecase.AuthenticateUserWithPassword(happydns.LoginRequest{ Email: "a@b.c", diff --git a/internal/usecase/authuser/authuser_test.go b/internal/usecase/authuser/authuser_test.go index dcd62b5d..89c7207e 100644 --- a/internal/usecase/authuser/authuser_test.go +++ b/internal/usecase/authuser/authuser_test.go @@ -26,7 +26,9 @@ import ( "testing" "time" + "git.happydns.org/happyDomain/internal/storage" "git.happydns.org/happyDomain/internal/storage/inmemory" + kv "git.happydns.org/happyDomain/internal/storage/kvtpl" "git.happydns.org/happyDomain/internal/usecase/authuser" "git.happydns.org/happyDomain/model" ) @@ -47,8 +49,9 @@ func (m *MockCloseUserSessionsUsecase) ByID(userID happydns.Identifier) error { return m.CloseAll(&happydns.UserAuth{Id: userID}) } -func setupTestService() (*authuser.Service, *inmemory.InMemoryStorage) { - store, _ := inmemory.NewInMemoryStorage() +func setupTestService() (*authuser.Service, storage.Storage) { + mem, _ := inmemory.NewInMemoryStorage() + store, _ := kv.NewKVDatabase(mem) cfg := &happydns.Options{ DisableRegistration: false, } @@ -75,7 +78,8 @@ func TestCanRegister_Success(t *testing.T) { } func TestCanRegister_Closed(t *testing.T) { - store, _ := inmemory.NewInMemoryStorage() + mem, _ := inmemory.NewInMemoryStorage() + store, _ := kv.NewKVDatabase(mem) cfg := &happydns.Options{ DisableRegistration: true, // Registration closed } @@ -344,7 +348,8 @@ func TestCheckNewPassword(t *testing.T) { // ========== DeleteAuthUser Tests ========== func TestDeleteAuthUser(t *testing.T) { - store, _ := inmemory.NewInMemoryStorage() + mem, _ := inmemory.NewInMemoryStorage() + store, _ := kv.NewKVDatabase(mem) cfg := &happydns.Options{ DisableRegistration: false, } diff --git a/internal/usecase/domain/domain_test.go b/internal/usecase/domain/domain_test.go index f1a36289..9f17c1ba 100644 --- a/internal/usecase/domain/domain_test.go +++ b/internal/usecase/domain/domain_test.go @@ -25,7 +25,9 @@ import ( "fmt" "testing" + "git.happydns.org/happyDomain/internal/storage" "git.happydns.org/happyDomain/internal/storage/inmemory" + kv "git.happydns.org/happyDomain/internal/storage/kvtpl" "git.happydns.org/happyDomain/internal/usecase/domain" providerUC "git.happydns.org/happyDomain/internal/usecase/provider" zoneUC "git.happydns.org/happyDomain/internal/usecase/zone" @@ -90,7 +92,7 @@ func (m *mockDomainLogAppender) AppendDomainLog(d *happydns.Domain, log *happydn // Helper functions -func createTestUser(t *testing.T, store *inmemory.InMemoryStorage, email string) *happydns.User { +func createTestUser(t *testing.T, store storage.Storage, email string) *happydns.User { user := &happydns.User{ Id: happydns.Identifier([]byte("user-" + email)), Email: email, @@ -101,7 +103,7 @@ func createTestUser(t *testing.T, store *inmemory.InMemoryStorage, email string) return user } -func createTestProvider(t *testing.T, store *inmemory.InMemoryStorage, user *happydns.User, name string) happydns.Identifier { +func createTestProvider(t *testing.T, store storage.Storage, user *happydns.User, name string) happydns.Identifier { provider := &happydns.Provider{ ProviderMeta: happydns.ProviderMeta{ Type: "mockProviderBody", @@ -118,7 +120,7 @@ func createTestProvider(t *testing.T, store *inmemory.InMemoryStorage, user *hap return provider.Id } -func setupTestService(store *inmemory.InMemoryStorage) (*domain.Service, *mockDomainLogAppender) { +func setupTestService(store storage.Storage) (*domain.Service, *mockDomainLogAppender) { // Create the provider service providerService := providerUC.NewService(store) @@ -146,10 +148,11 @@ func setupTestService(store *inmemory.InMemoryStorage) (*domain.Service, *mockDo func Test_CreateDomain(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - service, logAppender := setupTestService(mem) + db, _ := kv.NewKVDatabase(mem) + service, logAppender := setupTestService(db) - user := createTestUser(t, mem, "test@example.com") - providerId := createTestProvider(t, mem, user, "Test Provider") + user := createTestUser(t, db, "test@example.com") + providerId := createTestProvider(t, db, user, "Test Provider") domainToCreate := &happydns.Domain{ DomainName: "example.com", @@ -191,9 +194,10 @@ func Test_CreateDomain(t *testing.T) { func Test_CreateDomain_InvalidProvider(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - service, _ := setupTestService(mem) + db, _ := kv.NewKVDatabase(mem) + service, _ := setupTestService(db) - user := createTestUser(t, mem, "test@example.com") + user := createTestUser(t, db, "test@example.com") invalidProviderId := happydns.Identifier([]byte("invalid-provider")) domainToCreate := &happydns.Domain{ @@ -209,10 +213,11 @@ func Test_CreateDomain_InvalidProvider(t *testing.T) { func Test_GetUserDomain(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - service, _ := setupTestService(mem) + db, _ := kv.NewKVDatabase(mem) + service, _ := setupTestService(db) - user := createTestUser(t, mem, "test@example.com") - providerId := createTestProvider(t, mem, user, "Test Provider") + user := createTestUser(t, db, "test@example.com") + providerId := createTestProvider(t, db, user, "Test Provider") // Create a domain domainToCreate := &happydns.Domain{ @@ -248,11 +253,12 @@ func Test_GetUserDomain(t *testing.T) { func Test_GetUserDomain_WrongUser(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - service, _ := setupTestService(mem) + db, _ := kv.NewKVDatabase(mem) + service, _ := setupTestService(db) - user1 := createTestUser(t, mem, "user1@example.com") - user2 := createTestUser(t, mem, "user2@example.com") - providerId := createTestProvider(t, mem, user1, "Test Provider") + user1 := createTestUser(t, db, "user1@example.com") + user2 := createTestUser(t, db, "user2@example.com") + providerId := createTestProvider(t, db, user1, "Test Provider") // Create a domain for user1 domainToCreate := &happydns.Domain{ @@ -283,9 +289,10 @@ func Test_GetUserDomain_WrongUser(t *testing.T) { func Test_GetUserDomain_NotFound(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - service, _ := setupTestService(mem) + db, _ := kv.NewKVDatabase(mem) + service, _ := setupTestService(db) - user := createTestUser(t, mem, "test@example.com") + user := createTestUser(t, db, "test@example.com") nonexistentId := happydns.Identifier([]byte("nonexistent-domain")) _, err := service.GetUserDomain(user, nonexistentId) @@ -299,10 +306,11 @@ func Test_GetUserDomain_NotFound(t *testing.T) { func Test_GetUserDomainByFQDN(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - service, _ := setupTestService(mem) + db, _ := kv.NewKVDatabase(mem) + service, _ := setupTestService(db) - user := createTestUser(t, mem, "test@example.com") - providerId := createTestProvider(t, mem, user, "Test Provider") + user := createTestUser(t, db, "test@example.com") + providerId := createTestProvider(t, db, user, "Test Provider") // Create a domain domainToCreate := &happydns.Domain{ @@ -331,10 +339,11 @@ func Test_GetUserDomainByFQDN(t *testing.T) { func Test_ListUserDomains(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - service, _ := setupTestService(mem) + db, _ := kv.NewKVDatabase(mem) + service, _ := setupTestService(db) - user := createTestUser(t, mem, "test@example.com") - providerId := createTestProvider(t, mem, user, "Test Provider") + user := createTestUser(t, db, "test@example.com") + providerId := createTestProvider(t, db, user, "Test Provider") // Create multiple domains domainNames := []string{"example1.com", "example2.com", "example3.com"} @@ -362,12 +371,13 @@ func Test_ListUserDomains(t *testing.T) { func Test_ListUserDomains_MultipleUsers(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - service, _ := setupTestService(mem) + db, _ := kv.NewKVDatabase(mem) + service, _ := setupTestService(db) - user1 := createTestUser(t, mem, "user1@example.com") - user2 := createTestUser(t, mem, "user2@example.com") - providerId1 := createTestProvider(t, mem, user1, "Provider 1") - providerId2 := createTestProvider(t, mem, user2, "Provider 2") + user1 := createTestUser(t, db, "user1@example.com") + user2 := createTestUser(t, db, "user2@example.com") + providerId1 := createTestProvider(t, db, user1, "Provider 1") + providerId2 := createTestProvider(t, db, user2, "Provider 2") // Create domains for user1 for i := 1; i <= 2; i++ { @@ -412,9 +422,10 @@ func Test_ListUserDomains_MultipleUsers(t *testing.T) { func Test_ListUserDomains_Empty(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - service, _ := setupTestService(mem) + db, _ := kv.NewKVDatabase(mem) + service, _ := setupTestService(db) - user := createTestUser(t, mem, "test@example.com") + user := createTestUser(t, db, "test@example.com") // List domains (should be empty) domains, err := service.ListUserDomains(user) @@ -429,10 +440,11 @@ func Test_ListUserDomains_Empty(t *testing.T) { func Test_UpdateDomain(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - service, logAppender := setupTestService(mem) + db, _ := kv.NewKVDatabase(mem) + service, logAppender := setupTestService(db) - user := createTestUser(t, mem, "test@example.com") - providerId := createTestProvider(t, mem, user, "Test Provider") + user := createTestUser(t, db, "test@example.com") + providerId := createTestProvider(t, db, user, "Test Provider") // Create a domain domainToCreate := &happydns.Domain{ @@ -480,10 +492,11 @@ func Test_UpdateDomain(t *testing.T) { func Test_UpdateDomain_PreventIdChange(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - service, _ := setupTestService(mem) + db, _ := kv.NewKVDatabase(mem) + service, _ := setupTestService(db) - user := createTestUser(t, mem, "test@example.com") - providerId := createTestProvider(t, mem, user, "Test Provider") + user := createTestUser(t, db, "test@example.com") + providerId := createTestProvider(t, db, user, "Test Provider") // Create a domain domainToCreate := &happydns.Domain{ @@ -521,11 +534,12 @@ func Test_UpdateDomain_PreventIdChange(t *testing.T) { func Test_UpdateDomain_WrongUser(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - service, _ := setupTestService(mem) + db, _ := kv.NewKVDatabase(mem) + service, _ := setupTestService(db) - user1 := createTestUser(t, mem, "user1@example.com") - user2 := createTestUser(t, mem, "user2@example.com") - providerId := createTestProvider(t, mem, user1, "Test Provider") + user1 := createTestUser(t, db, "user1@example.com") + user2 := createTestUser(t, db, "user2@example.com") + providerId := createTestProvider(t, db, user1, "Test Provider") // Create a domain for user1 domainToCreate := &happydns.Domain{ @@ -555,10 +569,11 @@ func Test_UpdateDomain_WrongUser(t *testing.T) { func Test_DeleteDomain(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - service, _ := setupTestService(mem) + db, _ := kv.NewKVDatabase(mem) + service, _ := setupTestService(db) - user := createTestUser(t, mem, "test@example.com") - providerId := createTestProvider(t, mem, user, "Test Provider") + user := createTestUser(t, db, "test@example.com") + providerId := createTestProvider(t, db, user, "Test Provider") // Create a domain domainToCreate := &happydns.Domain{ @@ -595,10 +610,11 @@ func Test_DeleteDomain(t *testing.T) { func Test_UpdateDomain_Alias(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - service, _ := setupTestService(mem) + db, _ := kv.NewKVDatabase(mem) + service, _ := setupTestService(db) - user := createTestUser(t, mem, "test@example.com") - providerId := createTestProvider(t, mem, user, "Test Provider") + user := createTestUser(t, db, "test@example.com") + providerId := createTestProvider(t, db, user, "Test Provider") // Create a domain domainToCreate := &happydns.Domain{ diff --git a/internal/usecase/domain_log/domainlog_test.go b/internal/usecase/domain_log/domainlog_test.go index eee77b5f..f2d41261 100644 --- a/internal/usecase/domain_log/domainlog_test.go +++ b/internal/usecase/domain_log/domainlog_test.go @@ -25,12 +25,14 @@ import ( "testing" "time" + "git.happydns.org/happyDomain/internal/storage" "git.happydns.org/happyDomain/internal/storage/inmemory" + kv "git.happydns.org/happyDomain/internal/storage/kvtpl" "git.happydns.org/happyDomain/internal/usecase/domain_log" "git.happydns.org/happyDomain/model" ) -func createTestUser(t *testing.T, store *inmemory.InMemoryStorage, email string) *happydns.User { +func createTestUser(t *testing.T, store storage.Storage, email string) *happydns.User { user := &happydns.User{ Id: happydns.Identifier([]byte("user-" + email)), Email: email, @@ -41,7 +43,7 @@ func createTestUser(t *testing.T, store *inmemory.InMemoryStorage, email string) return user } -func createTestDomain(t *testing.T, store *inmemory.InMemoryStorage, user *happydns.User, domainName string) *happydns.Domain { +func createTestDomain(t *testing.T, store storage.Storage, user *happydns.User, domainName string) *happydns.Domain { domain := &happydns.Domain{ Owner: user.Id, DomainName: domainName, @@ -54,10 +56,11 @@ func createTestDomain(t *testing.T, store *inmemory.InMemoryStorage, user *happy func Test_AppendDomainLog(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - logService := domainlog.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + logService := domainlog.NewService(db) - user := createTestUser(t, mem, "test@example.com") - domain := createTestDomain(t, mem, user, "example.com") + user := createTestUser(t, db, "test@example.com") + domain := createTestDomain(t, db, user, "example.com") log := happydns.NewDomainLog(user, happydns.LOG_INFO, "Test log entry") @@ -83,7 +86,7 @@ func Test_AppendDomainLog(t *testing.T) { } // Verify log is stored in database - logs, err := mem.ListDomainLogs(domain) + logs, err := db.ListDomainLogs(domain) if err != nil { t.Fatalf("expected stored logs, got error: %v", err) } @@ -97,10 +100,11 @@ func Test_AppendDomainLog(t *testing.T) { func Test_ListDomainLogs(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - logService := domainlog.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + logService := domainlog.NewService(db) - user := createTestUser(t, mem, "test@example.com") - domain := createTestDomain(t, mem, user, "example.com") + user := createTestUser(t, db, "test@example.com") + domain := createTestDomain(t, db, user, "example.com") // Create multiple logs with different timestamps log1 := happydns.NewDomainLog(user, happydns.LOG_INFO, "First log") @@ -150,11 +154,12 @@ func Test_ListDomainLogs(t *testing.T) { func Test_ListDomainLogs_MultipleDomains(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - logService := domainlog.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + logService := domainlog.NewService(db) - user := createTestUser(t, mem, "test@example.com") - domain1 := createTestDomain(t, mem, user, "example.com") - domain2 := createTestDomain(t, mem, user, "test.com") + user := createTestUser(t, db, "test@example.com") + domain1 := createTestDomain(t, db, user, "example.com") + domain2 := createTestDomain(t, db, user, "test.com") // Create logs for domain1 log1 := happydns.NewDomainLog(user, happydns.LOG_INFO, "Domain1 Log 1") @@ -197,10 +202,11 @@ func Test_ListDomainLogs_MultipleDomains(t *testing.T) { func Test_UpdateDomainLog(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - logService := domainlog.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + logService := domainlog.NewService(db) - user := createTestUser(t, mem, "test@example.com") - domain := createTestDomain(t, mem, user, "example.com") + user := createTestUser(t, db, "test@example.com") + domain := createTestDomain(t, db, user, "example.com") // Create a log log := happydns.NewDomainLog(user, happydns.LOG_INFO, "Original content") @@ -235,10 +241,11 @@ func Test_UpdateDomainLog(t *testing.T) { func Test_DeleteDomainLog(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - logService := domainlog.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + logService := domainlog.NewService(db) - user := createTestUser(t, mem, "test@example.com") - domain := createTestDomain(t, mem, user, "example.com") + user := createTestUser(t, db, "test@example.com") + domain := createTestDomain(t, db, user, "example.com") // Create multiple logs log1 := happydns.NewDomainLog(user, happydns.LOG_INFO, "Log 1") @@ -274,10 +281,11 @@ func Test_DeleteDomainLog(t *testing.T) { func Test_AppendDomainLog_DifferentLogLevels(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - logService := domainlog.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + logService := domainlog.NewService(db) - user := createTestUser(t, mem, "test@example.com") - domain := createTestDomain(t, mem, user, "example.com") + user := createTestUser(t, db, "test@example.com") + domain := createTestDomain(t, db, user, "example.com") levels := []int8{ happydns.LOG_CRIT, @@ -311,10 +319,11 @@ func Test_AppendDomainLog_DifferentLogLevels(t *testing.T) { func Test_ListDomainLogs_EmptyDomain(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - logService := domainlog.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + logService := domainlog.NewService(db) - user := createTestUser(t, mem, "test@example.com") - domain := createTestDomain(t, mem, user, "example.com") + user := createTestUser(t, db, "test@example.com") + domain := createTestDomain(t, db, user, "example.com") // List logs for a domain with no logs logs, err := logService.ListDomainLogs(domain) diff --git a/internal/usecase/provider/provider_test.go b/internal/usecase/provider/provider_test.go index 6e2e2dde..5e4dc3ae 100644 --- a/internal/usecase/provider/provider_test.go +++ b/internal/usecase/provider/provider_test.go @@ -25,13 +25,15 @@ import ( "encoding/json" "testing" + "git.happydns.org/happyDomain/internal/storage" "git.happydns.org/happyDomain/internal/storage/inmemory" + kv "git.happydns.org/happyDomain/internal/storage/kvtpl" "git.happydns.org/happyDomain/internal/usecase/provider" "git.happydns.org/happyDomain/model" "git.happydns.org/happyDomain/providers" ) -func createTestUser(t *testing.T, store *inmemory.InMemoryStorage, email string) *happydns.User { +func createTestUser(t *testing.T, store storage.Storage, email string) *happydns.User { user := &happydns.User{ Id: happydns.Identifier([]byte("user-" + email)), Email: email, @@ -74,11 +76,12 @@ func (v *mockValidator) Validate(p *happydns.Provider) error { func Test_CreateProvider(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - providerService := provider.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + providerService := provider.NewService(db) // Replace validator with mock to avoid actual DNS validation providerService.SetValidator(&mockValidator{}) - user := createTestUser(t, mem, "test@example.com") + user := createTestUser(t, db, "test@example.com") msg := createTestProviderMessage(t, "DDNSServer", "Test DDNS Provider") p, err := providerService.CreateProvider(user, msg) @@ -97,7 +100,7 @@ func Test_CreateProvider(t *testing.T) { } // Verify provider is stored in database - stored, err := mem.GetProvider(p.Id) + stored, err := db.GetProvider(p.Id) if err != nil { t.Fatalf("expected stored provider, got error: %v", err) } @@ -108,10 +111,11 @@ func Test_CreateProvider(t *testing.T) { func Test_GetUserProvider(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - providerService := provider.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + providerService := provider.NewService(db) providerService.SetValidator(&mockValidator{}) - user := createTestUser(t, mem, "test@example.com") + user := createTestUser(t, db, "test@example.com") // Create a provider msg := createTestProviderMessage(t, "DDNSServer", "Test Provider") @@ -136,11 +140,12 @@ func Test_GetUserProvider(t *testing.T) { func Test_GetUserProvider_WrongUser(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - providerService := provider.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + providerService := provider.NewService(db) providerService.SetValidator(&mockValidator{}) - user1 := createTestUser(t, mem, "user1@example.com") - user2 := createTestUser(t, mem, "user2@example.com") + user1 := createTestUser(t, db, "user1@example.com") + user2 := createTestUser(t, db, "user2@example.com") // Create a provider for user1 msg := createTestProviderMessage(t, "DDNSServer", "User1 Provider") @@ -161,9 +166,10 @@ func Test_GetUserProvider_WrongUser(t *testing.T) { func Test_GetUserProvider_NotFound(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - providerService := provider.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + providerService := provider.NewService(db) - user := createTestUser(t, mem, "test@example.com") + user := createTestUser(t, db, "test@example.com") nonexistentID := happydns.Identifier([]byte("nonexistent-id")) _, err := providerService.GetUserProvider(user, nonexistentID) @@ -177,10 +183,11 @@ func Test_GetUserProvider_NotFound(t *testing.T) { func Test_GetUserProviderMeta(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - providerService := provider.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + providerService := provider.NewService(db) providerService.SetValidator(&mockValidator{}) - user := createTestUser(t, mem, "test@example.com") + user := createTestUser(t, db, "test@example.com") // Create a provider msg := createTestProviderMessage(t, "DDNSServer", "Test Provider Meta") @@ -205,10 +212,11 @@ func Test_GetUserProviderMeta(t *testing.T) { func Test_ListUserProviders(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - providerService := provider.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + providerService := provider.NewService(db) providerService.SetValidator(&mockValidator{}) - user := createTestUser(t, mem, "test@example.com") + user := createTestUser(t, db, "test@example.com") // Create multiple providers _, err := providerService.CreateProvider(user, createTestProviderMessage(t, "DDNSServer", "Provider 1")) @@ -237,11 +245,12 @@ func Test_ListUserProviders(t *testing.T) { func Test_ListUserProviders_MultipleUsers(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - providerService := provider.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + providerService := provider.NewService(db) providerService.SetValidator(&mockValidator{}) - user1 := createTestUser(t, mem, "user1@example.com") - user2 := createTestUser(t, mem, "user2@example.com") + user1 := createTestUser(t, db, "user1@example.com") + user2 := createTestUser(t, db, "user2@example.com") // Create providers for user1 _, err := providerService.CreateProvider(user1, createTestProviderMessage(t, "DDNSServer", "User1 Provider 1")) @@ -280,10 +289,11 @@ func Test_ListUserProviders_MultipleUsers(t *testing.T) { func Test_UpdateProvider(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - providerService := provider.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + providerService := provider.NewService(db) providerService.SetValidator(&mockValidator{}) - user := createTestUser(t, mem, "test@example.com") + user := createTestUser(t, db, "test@example.com") // Create a provider msg := createTestProviderMessage(t, "DDNSServer", "Original comment") @@ -312,10 +322,11 @@ func Test_UpdateProvider(t *testing.T) { func Test_UpdateProvider_PreventIdChange(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - providerService := provider.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + providerService := provider.NewService(db) providerService.SetValidator(&mockValidator{}) - user := createTestUser(t, mem, "test@example.com") + user := createTestUser(t, db, "test@example.com") // Create a provider msg := createTestProviderMessage(t, "DDNSServer", "Test Provider") @@ -339,11 +350,12 @@ func Test_UpdateProvider_PreventIdChange(t *testing.T) { func Test_UpdateProvider_WrongUser(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - providerService := provider.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + providerService := provider.NewService(db) providerService.SetValidator(&mockValidator{}) - user1 := createTestUser(t, mem, "user1@example.com") - user2 := createTestUser(t, mem, "user2@example.com") + user1 := createTestUser(t, db, "user1@example.com") + user2 := createTestUser(t, db, "user2@example.com") // Create a provider for user1 msg := createTestProviderMessage(t, "DDNSServer", "User1 Provider") @@ -363,10 +375,11 @@ func Test_UpdateProvider_WrongUser(t *testing.T) { func Test_DeleteProvider(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - providerService := provider.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + providerService := provider.NewService(db) providerService.SetValidator(&mockValidator{}) - user := createTestUser(t, mem, "test@example.com") + user := createTestUser(t, db, "test@example.com") // Create a provider msg := createTestProviderMessage(t, "DDNSServer", "Test Provider") @@ -426,12 +439,13 @@ func Test_ParseProvider_InvalidType(t *testing.T) { func Test_RestrictedService_CreateProvider_Disabled(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() + db, _ := kv.NewKVDatabase(mem) config := &happydns.Options{ DisableProviders: true, } - providerService := provider.NewRestrictedService(config, mem) + providerService := provider.NewRestrictedService(config, db) - user := createTestUser(t, mem, "test@example.com") + user := createTestUser(t, db, "test@example.com") msg := createTestProviderMessage(t, "DDNSServer", "Test Provider") _, err := providerService.CreateProvider(user, msg) @@ -445,11 +459,12 @@ func Test_RestrictedService_CreateProvider_Disabled(t *testing.T) { func Test_RestrictedService_UpdateProvider_Disabled(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() + db, _ := kv.NewKVDatabase(mem) // First create a provider without restrictions - unrestricted := provider.NewService(mem) + unrestricted := provider.NewService(db) unrestricted.SetValidator(&mockValidator{}) - user := createTestUser(t, mem, "test@example.com") + user := createTestUser(t, db, "test@example.com") msg := createTestProviderMessage(t, "DDNSServer", "Test Provider") createdProvider, err := unrestricted.CreateProvider(user, msg) if err != nil { @@ -460,7 +475,7 @@ func Test_RestrictedService_UpdateProvider_Disabled(t *testing.T) { config := &happydns.Options{ DisableProviders: true, } - restrictedService := provider.NewRestrictedService(config, mem) + restrictedService := provider.NewRestrictedService(config, db) err = restrictedService.UpdateProvider(createdProvider.Id, user, func(p *happydns.Provider) { p.Comment = "Updated" @@ -475,11 +490,12 @@ func Test_RestrictedService_UpdateProvider_Disabled(t *testing.T) { func Test_RestrictedService_DeleteProvider_Disabled(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() + db, _ := kv.NewKVDatabase(mem) // First create a provider without restrictions - unrestricted := provider.NewService(mem) + unrestricted := provider.NewService(db) unrestricted.SetValidator(&mockValidator{}) - user := createTestUser(t, mem, "test@example.com") + user := createTestUser(t, db, "test@example.com") msg := createTestProviderMessage(t, "DDNSServer", "Test Provider") createdProvider, err := unrestricted.CreateProvider(user, msg) if err != nil { @@ -490,7 +506,7 @@ func Test_RestrictedService_DeleteProvider_Disabled(t *testing.T) { config := &happydns.Options{ DisableProviders: true, } - restrictedService := provider.NewRestrictedService(config, mem) + restrictedService := provider.NewRestrictedService(config, db) err = restrictedService.DeleteProvider(user, createdProvider.Id) if err == nil { diff --git a/internal/usecase/session/session_test.go b/internal/usecase/session/session_test.go index 55df7473..b3aa6807 100644 --- a/internal/usecase/session/session_test.go +++ b/internal/usecase/session/session_test.go @@ -25,12 +25,14 @@ import ( "testing" "time" + "git.happydns.org/happyDomain/internal/storage" "git.happydns.org/happyDomain/internal/storage/inmemory" + kv "git.happydns.org/happyDomain/internal/storage/kvtpl" "git.happydns.org/happyDomain/internal/usecase/session" "git.happydns.org/happyDomain/model" ) -func createTestUser(t *testing.T, store *inmemory.InMemoryStorage, email string) *happydns.User { +func createTestUser(t *testing.T, store storage.Storage, email string) *happydns.User { user := &happydns.User{ Id: happydns.Identifier([]byte("user-" + email)), Email: email, @@ -43,9 +45,10 @@ func createTestUser(t *testing.T, store *inmemory.InMemoryStorage, email string) func Test_CreateUserSession(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - sessionService := session.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + sessionService := session.NewService(db) - user := createTestUser(t, mem, "test@example.com") + user := createTestUser(t, db, "test@example.com") sess, err := sessionService.CreateUserSession(user, "Test session") if err != nil { @@ -69,7 +72,7 @@ func Test_CreateUserSession(t *testing.T) { } // Verify session is stored in database - stored, err := mem.GetSession(sess.Id) + stored, err := db.GetSession(sess.Id) if err != nil { t.Fatalf("expected stored session, got error: %v", err) } @@ -80,9 +83,10 @@ func Test_CreateUserSession(t *testing.T) { func Test_GetUserSession(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - sessionService := session.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + sessionService := session.NewService(db) - user := createTestUser(t, mem, "test@example.com") + user := createTestUser(t, db, "test@example.com") // Create a session createdSession, err := sessionService.CreateUserSession(user, "Test session") @@ -106,10 +110,11 @@ func Test_GetUserSession(t *testing.T) { func Test_GetUserSession_WrongUser(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - sessionService := session.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + sessionService := session.NewService(db) - user1 := createTestUser(t, mem, "user1@example.com") - user2 := createTestUser(t, mem, "user2@example.com") + user1 := createTestUser(t, db, "user1@example.com") + user2 := createTestUser(t, db, "user2@example.com") // Create a session for user1 createdSession, err := sessionService.CreateUserSession(user1, "User1 session") @@ -129,9 +134,10 @@ func Test_GetUserSession_WrongUser(t *testing.T) { func Test_GetUserSession_NotFound(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - sessionService := session.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + sessionService := session.NewService(db) - user := createTestUser(t, mem, "test@example.com") + user := createTestUser(t, db, "test@example.com") _, err := sessionService.GetUserSession(user, "nonexistent-session-id") if err == nil { @@ -144,9 +150,10 @@ func Test_GetUserSession_NotFound(t *testing.T) { func Test_ListUserSessions(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - sessionService := session.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + sessionService := session.NewService(db) - user := createTestUser(t, mem, "test@example.com") + user := createTestUser(t, db, "test@example.com") // Create multiple sessions _, err := sessionService.CreateUserSession(user, "Session 1") @@ -175,10 +182,11 @@ func Test_ListUserSessions(t *testing.T) { func Test_ListUserSessions_MultipleUsers(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - sessionService := session.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + sessionService := session.NewService(db) - user1 := createTestUser(t, mem, "user1@example.com") - user2 := createTestUser(t, mem, "user2@example.com") + user1 := createTestUser(t, db, "user1@example.com") + user2 := createTestUser(t, db, "user2@example.com") // Create sessions for user1 _, err := sessionService.CreateUserSession(user1, "User1 Session 1") @@ -217,9 +225,10 @@ func Test_ListUserSessions_MultipleUsers(t *testing.T) { func Test_UpdateUserSession(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - sessionService := session.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + sessionService := session.NewService(db) - user := createTestUser(t, mem, "test@example.com") + user := createTestUser(t, db, "test@example.com") // Create a session createdSession, err := sessionService.CreateUserSession(user, "Original description") @@ -250,9 +259,10 @@ func Test_UpdateUserSession(t *testing.T) { func Test_UpdateUserSession_PreventIdChange(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - sessionService := session.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + sessionService := session.NewService(db) - user := createTestUser(t, mem, "test@example.com") + user := createTestUser(t, db, "test@example.com") // Create a session createdSession, err := sessionService.CreateUserSession(user, "Test session") @@ -274,10 +284,11 @@ func Test_UpdateUserSession_PreventIdChange(t *testing.T) { func Test_UpdateUserSession_WrongUser(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - sessionService := session.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + sessionService := session.NewService(db) - user1 := createTestUser(t, mem, "user1@example.com") - user2 := createTestUser(t, mem, "user2@example.com") + user1 := createTestUser(t, db, "user1@example.com") + user2 := createTestUser(t, db, "user2@example.com") // Create a session for user1 createdSession, err := sessionService.CreateUserSession(user1, "User1 session") @@ -296,9 +307,10 @@ func Test_UpdateUserSession_WrongUser(t *testing.T) { func Test_DeleteUserSession(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - sessionService := session.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + sessionService := session.NewService(db) - user := createTestUser(t, mem, "test@example.com") + user := createTestUser(t, db, "test@example.com") // Create a session createdSession, err := sessionService.CreateUserSession(user, "Test session") @@ -324,10 +336,11 @@ func Test_DeleteUserSession(t *testing.T) { func Test_DeleteUserSession_WrongUser(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - sessionService := session.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + sessionService := session.NewService(db) - user1 := createTestUser(t, mem, "user1@example.com") - user2 := createTestUser(t, mem, "user2@example.com") + user1 := createTestUser(t, db, "user1@example.com") + user2 := createTestUser(t, db, "user2@example.com") // Create a session for user1 createdSession, err := sessionService.CreateUserSession(user1, "User1 session") @@ -350,9 +363,10 @@ func Test_DeleteUserSession_WrongUser(t *testing.T) { func Test_CloseUserSessions(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - sessionService := session.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + sessionService := session.NewService(db) - user := createTestUser(t, mem, "test@example.com") + user := createTestUser(t, db, "test@example.com") // Create multiple sessions _, err := sessionService.CreateUserSession(user, "Session 1") @@ -386,10 +400,11 @@ func Test_CloseUserSessions(t *testing.T) { func Test_CloseUserSessions_MultipleUsers(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - sessionService := session.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + sessionService := session.NewService(db) - user1 := createTestUser(t, mem, "user1@example.com") - user2 := createTestUser(t, mem, "user2@example.com") + user1 := createTestUser(t, db, "user1@example.com") + user2 := createTestUser(t, db, "user2@example.com") // Create sessions for both users _, err := sessionService.CreateUserSession(user1, "User1 Session") @@ -439,14 +454,15 @@ func (u testUserInfo) JoinNewsletter() bool { return false } func Test_CloseAll_UserInfoInterface(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - sessionService := session.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + sessionService := session.NewService(db) userID := happydns.Identifier([]byte("user-123")) user := &happydns.User{ Id: userID, Email: "test@example.com", } - if err := mem.CreateOrUpdateUser(user); err != nil { + if err := db.CreateOrUpdateUser(user); err != nil { t.Fatalf("failed to create test user: %v", err) } @@ -479,14 +495,15 @@ func Test_CloseAll_UserInfoInterface(t *testing.T) { func Test_ByID(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - sessionService := session.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + sessionService := session.NewService(db) userID := happydns.Identifier([]byte("user-123")) user := &happydns.User{ Id: userID, Email: "test@example.com", } - if err := mem.CreateOrUpdateUser(user); err != nil { + if err := db.CreateOrUpdateUser(user); err != nil { t.Fatalf("failed to create test user: %v", err) } @@ -534,9 +551,10 @@ func Test_NewSessionID(t *testing.T) { func Test_SessionExpiration(t *testing.T) { mem, _ := inmemory.NewInMemoryStorage() - sessionService := session.NewService(mem) + db, _ := kv.NewKVDatabase(mem) + sessionService := session.NewService(db) - user := createTestUser(t, mem, "test@example.com") + user := createTestUser(t, db, "test@example.com") // Create a session sess, err := sessionService.CreateUserSession(user, "Test session") diff --git a/internal/usecase/user/user_test.go b/internal/usecase/user/user_test.go index a05e150d..9b7d23f1 100644 --- a/internal/usecase/user/user_test.go +++ b/internal/usecase/user/user_test.go @@ -25,7 +25,9 @@ import ( "bytes" "testing" + "git.happydns.org/happyDomain/internal/storage" "git.happydns.org/happyDomain/internal/storage/inmemory" + kv "git.happydns.org/happyDomain/internal/storage/kvtpl" authuserUC "git.happydns.org/happyDomain/internal/usecase/authuser" sessionUC "git.happydns.org/happyDomain/internal/usecase/session" "git.happydns.org/happyDomain/internal/usecase/user" @@ -70,8 +72,9 @@ func (u testUserInfo) GetUserId() happydns.Identifier { return u.id } func (u testUserInfo) GetEmail() string { return u.email } func (u testUserInfo) JoinNewsletter() bool { return u.joinNewsletter } -func createTestService(t *testing.T) (*user.Service, *inmemory.InMemoryStorage, *mockNewsletterSubscriptor, *mockSessionCloser) { - mem, err := inmemory.NewInMemoryStorage() +func createTestService(t *testing.T) (*user.Service, storage.Storage, *mockNewsletterSubscriptor, *mockSessionCloser) { + mem, _ := inmemory.NewInMemoryStorage() + db, err := kv.NewKVDatabase(mem) if err != nil { t.Fatalf("failed to create in-memory storage: %v", err) } @@ -79,15 +82,15 @@ func createTestService(t *testing.T) (*user.Service, *inmemory.InMemoryStorage, cfg := &happydns.Options{ DisableRegistration: false, } - sessionService := sessionUC.NewService(mem) - authUserService := authuserUC.NewAuthUserUsecases(cfg, nil, mem, sessionService) + sessionService := sessionUC.NewService(db) + authUserService := authuserUC.NewAuthUserUsecases(cfg, nil, db, sessionService) newsletter := &mockNewsletterSubscriptor{} sessionCloser := &mockSessionCloser{} - service := user.NewUserUsecases(mem, newsletter, authUserService, sessionCloser) + service := user.NewUserUsecases(db, newsletter, authUserService, sessionCloser) - return service, mem, newsletter, sessionCloser + return service, db, newsletter, sessionCloser } func Test_CreateUser(t *testing.T) { diff --git a/model/errors.go b/model/errors.go index 3986b9b8..d3bb697b 100644 --- a/model/errors.go +++ b/model/errors.go @@ -35,6 +35,7 @@ var ( ErrUserNotFound = errors.New("user not found") ErrUserAlreadyExist = errors.New("user already exists") ErrZoneNotFound = errors.New("zone not found") + ErrNotFound = errors.New("not found") ) const TryAgainErr = "Sorry, we are currently unable to sent email validation link. Please try again later." diff --git a/model/iterators.go b/model/iterators.go index 665ebf24..fa62062a 100644 --- a/model/iterators.go +++ b/model/iterators.go @@ -39,9 +39,13 @@ type Iterator[T any] interface { // Must be called only after a successful call to Next(). DropItem() error + // Key returns the storage key of the current item. + // Should be called only after a successful call to Next(). + Key() string + // Raw returns the raw (non-decoded) value at the current iterator position. // Should only be called after a successful call to Next(). - Raw() []byte + Raw() interface{} // Err returns the first error encountered during iteration, if any. Err() error