Move wisski instance code to separate package

This commit is contained in:
Tom Wiesing 2022-10-17 14:20:15 +02:00
parent 7c3c84e116
commit 063f3f9b7d
No known key found for this signature in database
67 changed files with 533 additions and 409 deletions

View 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]
}

View 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()
}

View 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)
}