first commit

This commit is contained in:
npc0-hue
2026-07-11 14:56:10 +08:00
commit 7e05d0a4e7
660 changed files with 78119 additions and 0 deletions
+225
View File
@@ -0,0 +1,225 @@
package repo
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"browser.local/platform/domain"
)
type StoreSnapshot struct {
Users []domain.User `json:"users"`
AIProviders []domain.AIProvider `json:"aiProviders"`
GamePlugins []domain.GamePlugin `json:"gamePlugins"`
ServerInstances []domain.ServerInstance `json:"serverInstances"`
RunEndpoints []domain.RunEndpoint `json:"runEndpoints"`
Jobs []domain.Job `json:"jobs"`
Artifacts []domain.Artifact `json:"artifacts"`
LogStreams []domain.LogStream `json:"logStreams"`
AuditEvents []domain.AuditEvent `json:"auditEvents"`
}
type FileStore struct {
*MemoryStore
path string
persistMu sync.Mutex
}
func NewFileStore(path string) (*FileStore, error) {
path = strings.TrimSpace(path)
if path == "" {
return nil, fmt.Errorf("metadata path is required")
}
store := &FileStore{
MemoryStore: NewMemoryStore(),
path: path,
}
if err := store.load(); err != nil {
return nil, err
}
return store, nil
}
func (store *FileStore) MetadataPath() string {
return store.path
}
func (store *FileStore) Users() UserRepository {
return &persistentRepository[domain.User, domain.UserFilter]{repository: store.MemoryStore.users, persist: store.persist}
}
func (store *FileStore) AIProviders() AIProviderRepository {
return &persistentRepository[domain.AIProvider, domain.AIProviderFilter]{repository: store.MemoryStore.aiProviders, persist: store.persist}
}
func (store *FileStore) GamePlugins() GamePluginRepository {
return &persistentRepository[domain.GamePlugin, domain.GamePluginFilter]{repository: store.MemoryStore.gamePlugins, persist: store.persist}
}
func (store *FileStore) ServerInstances() ServerInstanceRepository {
return &persistentRepository[domain.ServerInstance, domain.ServerInstanceFilter]{repository: store.MemoryStore.serverInstances, persist: store.persist}
}
func (store *FileStore) RunEndpoints() RunEndpointRepository {
return &persistentRepository[domain.RunEndpoint, domain.RunEndpointFilter]{repository: store.MemoryStore.runEndpoints, persist: store.persist}
}
func (store *FileStore) Jobs() JobRepository {
return &persistentJobRepository{
persistentRepository: &persistentRepository[domain.Job, domain.JobFilter]{repository: store.MemoryStore.jobs, persist: store.persist},
repository: store.MemoryStore.jobs,
}
}
func (store *FileStore) Artifacts() ArtifactRepository {
return &persistentRepository[domain.Artifact, domain.ArtifactFilter]{repository: store.MemoryStore.artifacts, persist: store.persist}
}
func (store *FileStore) LogStreams() LogStreamRepository {
return &persistentRepository[domain.LogStream, domain.LogStreamFilter]{repository: store.MemoryStore.logStreams, persist: store.persist}
}
func (store *FileStore) AuditEvents() AuditEventRepository {
return &persistentRepository[domain.AuditEvent, domain.AuditEventFilter]{repository: store.MemoryStore.auditEvents, persist: store.persist}
}
func (store *FileStore) load() error {
data, err := os.ReadFile(store.path)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return fmt.Errorf("read metadata snapshot: %w", err)
}
if len(strings.TrimSpace(string(data))) == 0 {
return nil
}
var snapshot StoreSnapshot
if err := json.Unmarshal(data, &snapshot); err != nil {
return fmt.Errorf("decode metadata snapshot: %w", err)
}
store.loadSnapshot(snapshot)
return nil
}
func (store *FileStore) persist() error {
store.persistMu.Lock()
defer store.persistMu.Unlock()
snapshot := store.snapshot()
data, err := json.MarshalIndent(snapshot, "", " ")
if err != nil {
return fmt.Errorf("encode metadata snapshot: %w", err)
}
if err := os.MkdirAll(filepath.Dir(store.path), 0o755); err != nil {
return fmt.Errorf("create metadata directory: %w", err)
}
tmpPath := store.path + ".tmp"
if err := os.WriteFile(tmpPath, data, 0o600); err != nil {
return fmt.Errorf("write metadata snapshot: %w", err)
}
if err := os.Rename(tmpPath, store.path); err != nil {
return fmt.Errorf("replace metadata snapshot: %w", err)
}
return nil
}
func (store *FileStore) snapshot() StoreSnapshot {
return StoreSnapshot{
Users: snapshotRepository(store.MemoryStore.users),
AIProviders: snapshotRepository(store.MemoryStore.aiProviders),
GamePlugins: snapshotRepository(store.MemoryStore.gamePlugins),
ServerInstances: snapshotRepository(store.MemoryStore.serverInstances),
RunEndpoints: snapshotRepository(store.MemoryStore.runEndpoints),
Jobs: snapshotRepository(store.MemoryStore.jobs.memoryRepository),
Artifacts: snapshotRepository(store.MemoryStore.artifacts),
LogStreams: snapshotRepository(store.MemoryStore.logStreams),
AuditEvents: snapshotRepository(store.MemoryStore.auditEvents),
}
}
func (store *FileStore) loadSnapshot(snapshot StoreSnapshot) {
loadRepository(store.MemoryStore.users, snapshot.Users)
loadRepository(store.MemoryStore.aiProviders, snapshot.AIProviders)
loadRepository(store.MemoryStore.gamePlugins, snapshot.GamePlugins)
loadRepository(store.MemoryStore.serverInstances, snapshot.ServerInstances)
loadRepository(store.MemoryStore.runEndpoints, snapshot.RunEndpoints)
loadRepository(store.MemoryStore.jobs.memoryRepository, snapshot.Jobs)
loadRepository(store.MemoryStore.artifacts, snapshot.Artifacts)
loadRepository(store.MemoryStore.logStreams, snapshot.LogStreams)
loadRepository(store.MemoryStore.auditEvents, snapshot.AuditEvents)
}
type mutableRepository[T any, F any] interface {
Create(T) error
Get(string) (T, error)
List(F) ([]T, error)
Update(T) error
}
type persistentRepository[T any, F any] struct {
repository mutableRepository[T, F]
persist func() error
}
func (repository *persistentRepository[T, F]) Create(value T) error {
if err := repository.repository.Create(value); err != nil {
return err
}
return repository.persist()
}
func (repository *persistentRepository[T, F]) Get(id string) (T, error) {
return repository.repository.Get(id)
}
func (repository *persistentRepository[T, F]) List(filter F) ([]T, error) {
return repository.repository.List(filter)
}
func (repository *persistentRepository[T, F]) Update(value T) error {
if err := repository.repository.Update(value); err != nil {
return err
}
return repository.persist()
}
type persistentJobRepository struct {
*persistentRepository[domain.Job, domain.JobFilter]
repository JobRepository
}
func (repository *persistentJobRepository) GetByIdempotency(runEndpointID string, idempotencyKey string) (domain.Job, error) {
return repository.repository.GetByIdempotency(runEndpointID, idempotencyKey)
}
func snapshotRepository[T any, F any](repository *memoryRepository[T, F]) []T {
repository.mu.RLock()
defer repository.mu.RUnlock()
ids := make([]string, 0, len(repository.byID))
for id := range repository.byID {
ids = append(ids, id)
}
sort.Strings(ids)
values := make([]T, 0, len(ids))
for _, id := range ids {
values = append(values, repository.copyOf(repository.byID[id]))
}
return values
}
func loadRepository[T any, F any](repository *memoryRepository[T, F], values []T) {
repository.mu.Lock()
defer repository.mu.Unlock()
repository.byID = map[string]T{}
for _, value := range values {
repository.byID[repository.idOf(value)] = repository.copyOf(value)
}
}