90 lines
2.3 KiB
Go
90 lines
2.3 KiB
Go
package filesvc
|
||
|
||
import (
|
||
"context"
|
||
"crypto/rand"
|
||
"errors"
|
||
"fmt"
|
||
"time"
|
||
)
|
||
|
||
/*** Domain ***/
|
||
|
||
type ID = string
|
||
|
||
type File struct {
|
||
ID ID `json:"id"`
|
||
Name string `json:"name"`
|
||
UpdatedAt int64 `json:"updatedAt"` // UnixNano für LWW
|
||
Deleted bool `json:"deleted"` // Tombstone für Mesh-Delete
|
||
Owner string `json:"owner"` //AdvertURL/NodeID des Erzeugers
|
||
}
|
||
|
||
/*** Fehler ***/
|
||
|
||
var (
|
||
ErrNotFound = errors.New("file not found")
|
||
ErrBadInput = errors.New("bad input")
|
||
ErrConflict = errors.New("conflict")
|
||
ErrForbidden = errors.New("forbidden")
|
||
ErrTransient = errors.New("transient")
|
||
)
|
||
|
||
/*** Basis-API (lokal nutzbar) ***/
|
||
|
||
type Store interface {
|
||
// Lesen & Auflisten
|
||
Get(ctx context.Context, id ID) (File, error)
|
||
List(ctx context.Context, next ID, limit int) (items []File, nextOut ID, err error)
|
||
Create(ctx context.Context, name string) (File, error)
|
||
Rename(ctx context.Context, id ID, newName string) (File, error) // nur Owner darf
|
||
Delete(ctx context.Context, id ID) (File, error) // nur Owner darf
|
||
TakeoverOwner(ctx context.Context, id ID, newOwner string) (File, error)
|
||
}
|
||
|
||
/*** Mesh-Replikation ***/
|
||
|
||
type Snapshot struct {
|
||
Items []File `json:"items"`
|
||
}
|
||
|
||
type Replicable interface {
|
||
// Snapshot liefert den vollständigen aktuellen Stand (inkl. Tombstones).
|
||
Snapshot(ctx context.Context) (Snapshot, error)
|
||
// ApplyRemote wendet LWW an. next-ID wird dabei korrekt fortgeschrieben.
|
||
ApplyRemote(ctx context.Context, s Snapshot) error
|
||
}
|
||
|
||
/*** Events (optional) ***/
|
||
|
||
// ChangeEvent kann genutzt werden, um proaktive Mesh-Pushes zu triggern.
|
||
// Bei deiner Pull-basierten Anti-Entropy ist es optional.
|
||
type ChangeEvent struct {
|
||
At time.Time
|
||
Item File
|
||
}
|
||
|
||
// Watch gibt Änderungen aus; close(stop) beendet den Stream.
|
||
// Eine Noop-Implementierung ist erlaubt, wenn Pull-Sync genügt.
|
||
type Watchable interface {
|
||
Watch(stop <-chan struct{}) <-chan ChangeEvent
|
||
}
|
||
|
||
/*** Kombiniertes Interface ***/
|
||
|
||
type MeshStore interface {
|
||
Store
|
||
Replicable
|
||
Watchable // optional – kann Noop sein
|
||
}
|
||
|
||
func NewUUIDv4() (string, error) {
|
||
b := make([]byte, 16)
|
||
if _, err := rand.Read(b); err != nil {
|
||
return "", err
|
||
}
|
||
b[6] = (b[6] & 0x0f) | 0x40 // Version 4
|
||
b[8] = (b[8] & 0x3f) | 0x80 // Variant RFC4122
|
||
return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16]), nil
|
||
}
|