Move wisski instance code to separate package
This commit is contained in:
parent
7c3c84e116
commit
063f3f9b7d
67 changed files with 533 additions and 409 deletions
44
internal/component/meta/meta.go
Normal file
44
internal/component/meta/meta.go
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
package meta
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/component"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/component/sql"
|
||||
)
|
||||
|
||||
// Component meta is responsible for managing metadata per WissKI Instance
|
||||
type Meta struct {
|
||||
component.ComponentBase
|
||||
|
||||
SQL *sql.SQL
|
||||
|
||||
sl sync.Mutex
|
||||
sc map[string]*Storage
|
||||
}
|
||||
|
||||
func (*Meta) Name() string { return "metadata" }
|
||||
|
||||
// Storage returns a Storage for the instance with the given slug.
|
||||
// When slug is nil, returns a global storage.
|
||||
func (meta *Meta) Storage(slug string) *Storage {
|
||||
meta.sl.Lock()
|
||||
defer meta.sl.Unlock()
|
||||
|
||||
// create the cache (unless it already exists)
|
||||
if meta.sc == nil {
|
||||
meta.sc = make(map[string]*Storage)
|
||||
}
|
||||
|
||||
// cache hit
|
||||
if storage, ok := meta.sc[slug]; ok {
|
||||
return storage
|
||||
}
|
||||
|
||||
// create a new storage
|
||||
meta.sc[slug] = &Storage{
|
||||
Slug: slug,
|
||||
sql: meta.SQL,
|
||||
}
|
||||
return meta.sc[slug]
|
||||
}
|
||||
14
internal/component/meta/provision.go
Normal file
14
internal/component/meta/provision.go
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package meta
|
||||
|
||||
import "github.com/FAU-CDI/wisski-distillery/internal/models"
|
||||
|
||||
// Provision provisions new meta storage for this instance.
|
||||
// NOTE(twiesing): This is a no-op, because we implement Purge.
|
||||
func (meta *Meta) Provision(instance models.Instance, domain string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Purge purges the storage for the given instance.
|
||||
func (meta *Meta) Purge(instance models.Instance, domain string) error {
|
||||
return meta.Storage(instance.Slug).Purge()
|
||||
}
|
||||
218
internal/component/meta/storage.go
Normal file
218
internal/component/meta/storage.go
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
package meta
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/component/sql"
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/models"
|
||||
"github.com/tkw1536/goprogram/lib/collection"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Key represents a key for metadata.
|
||||
type Key string
|
||||
|
||||
// ErrMetadatumNotSet is returned by various [MetaStorage] functions when a metadatum is not set
|
||||
var ErrMetadatumNotSet = errors.New("metadatum not set")
|
||||
|
||||
// Storage manages metadata for either the entire distillery, or a single slug
|
||||
type Storage struct {
|
||||
Slug string
|
||||
sql *sql.SQL
|
||||
}
|
||||
|
||||
// Get retrieves metadata with the provided key and deserializes the first one into target.
|
||||
// If no metadatum exists, returns [ErrMetadatumNotSet].
|
||||
func (s Storage) Get(key Key, target any) error {
|
||||
table, err := s.sql.QueryTable(true, models.MetadataTable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// read the datum from the database
|
||||
var datum models.Metadatum
|
||||
status := table.Where(&models.Metadatum{Slug: s.Slug, Key: string(key)}).Order("pk DESC").Find(&datum)
|
||||
|
||||
// check if there was an error
|
||||
if err := status.Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check if e actually found it!
|
||||
if status.RowsAffected == 0 {
|
||||
return ErrMetadatumNotSet
|
||||
}
|
||||
|
||||
// and do the unmarshaling!
|
||||
return json.Unmarshal(datum.Value, target)
|
||||
}
|
||||
|
||||
// GetAll receives all metadata with the provided keys.
|
||||
// For each received value, the targets function is called with the current index, and total number of results.
|
||||
// The function is intended to return a target for deserialization.
|
||||
//
|
||||
// When no metadatum exists, targets is not called, and nil error is returned.
|
||||
func (s Storage) GetAll(key Key, target func(index, total int) any) error {
|
||||
table, err := s.sql.QueryTable(true, models.MetadataTable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// read the datum from the database
|
||||
var data []models.Metadatum
|
||||
status := table.Where(&models.Metadatum{Slug: s.Slug, Key: string(key)}).Find(&data)
|
||||
|
||||
// check if there was an error
|
||||
if err := status.Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// unpack all of them into the destination
|
||||
for index, datum := range data {
|
||||
err := json.Unmarshal(datum.Value, target(index, len(data)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes all metadata with the provided key.
|
||||
func (s Storage) Delete(key Key) error {
|
||||
table, err := s.sql.QueryTable(true, models.MetadataTable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// delete all the values
|
||||
status := table.Where(&models.Metadatum{Slug: s.Slug, Key: string(key)}).Delete(&models.Metadatum{})
|
||||
if err := status.Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set serializes value and stores it with the provided key.
|
||||
// Any other metadata with the same key is deleted.
|
||||
func (s Storage) Set(key Key, value any) error {
|
||||
table, err := s.sql.QueryTable(true, models.MetadataTable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// marshal the value
|
||||
bytes, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return table.Transaction(func(tx *gorm.DB) error {
|
||||
// delete the old values
|
||||
status := tx.Where(&models.Metadatum{Slug: s.Slug, Key: string(key)}).Delete(&models.Metadatum{})
|
||||
if err := status.Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create the new item to insert
|
||||
status = tx.Create(&models.Metadatum{
|
||||
Key: string(key),
|
||||
Slug: s.Slug,
|
||||
Value: bytes,
|
||||
})
|
||||
if err := status.Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Set serializes values and stores them with the provided key.
|
||||
// Any other metadata with the same key is deleted.
|
||||
func (s Storage) SetAll(key Key, values ...any) error {
|
||||
table, err := s.sql.QueryTable(true, models.MetadataTable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return table.Transaction(func(tx *gorm.DB) error {
|
||||
// delete the old values
|
||||
status := tx.Where(&models.Metadatum{Slug: s.Slug, Key: string(key)}).Delete(&models.Metadatum{})
|
||||
if err := status.Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, value := range values {
|
||||
bytes, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create the new item to insert
|
||||
status := tx.Create(&models.Metadatum{
|
||||
Key: string(key),
|
||||
Slug: s.Slug,
|
||||
Value: bytes,
|
||||
})
|
||||
if err := status.Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Purge removes all metadata, regardless of key.
|
||||
func (s Storage) Purge() error {
|
||||
table, err := s.sql.QueryTable(true, models.MetadataTable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
status := table.Where("slug = ?", s.Slug).Delete(&models.Metadatum{})
|
||||
if status.Error != nil {
|
||||
return status.Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StorageFor returns a storage for the given key.
|
||||
func StorageFor[Value any](key Key) func(storage *Storage) SpecifcStorage[Value] {
|
||||
return func(storage *Storage) SpecifcStorage[Value] {
|
||||
return SpecifcStorage[Value]{storage: storage, key: key}
|
||||
}
|
||||
}
|
||||
|
||||
type SpecifcStorage[Value any] struct {
|
||||
storage *Storage
|
||||
key Key
|
||||
}
|
||||
|
||||
func (sf SpecifcStorage[Value]) Get() (value Value, err error) {
|
||||
err = sf.storage.Get(sf.key, &value)
|
||||
return
|
||||
}
|
||||
|
||||
func (sf SpecifcStorage[Value]) GetAll() (values []Value, err error) {
|
||||
err = sf.storage.GetAll(sf.key, func(index, total int) any {
|
||||
if values == nil {
|
||||
values = make([]Value, total)
|
||||
}
|
||||
return &values[index]
|
||||
})
|
||||
return values, err
|
||||
}
|
||||
|
||||
func (sf SpecifcStorage[Value]) Set(value Value) error {
|
||||
return sf.storage.Set(sf.key, value)
|
||||
}
|
||||
|
||||
func (sf SpecifcStorage[Value]) SetAll(values ...Value) error {
|
||||
return sf.storage.SetAll(sf.key, collection.AsAny(values)...)
|
||||
}
|
||||
|
||||
func (sf SpecifcStorage[Value]) Delete() error {
|
||||
return sf.storage.Delete(sf.key)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue