package repo import ( "errors" "sort" "sync" "browser.local/platform/domain" ) var ( ErrDuplicate = errors.New("resource already exists") ErrNotFound = errors.New("resource not found") ) type UserRepository interface { Create(domain.User) error Get(id string) (domain.User, error) List(domain.UserFilter) ([]domain.User, error) Update(domain.User) error } type AIProviderRepository interface { Create(domain.AIProvider) error Get(id string) (domain.AIProvider, error) List(domain.AIProviderFilter) ([]domain.AIProvider, error) Update(domain.AIProvider) error } type GamePluginRepository interface { Create(domain.GamePlugin) error Get(id string) (domain.GamePlugin, error) List(domain.GamePluginFilter) ([]domain.GamePlugin, error) Update(domain.GamePlugin) error } type ServerInstanceRepository interface { Create(domain.ServerInstance) error Get(id string) (domain.ServerInstance, error) List(domain.ServerInstanceFilter) ([]domain.ServerInstance, error) Update(domain.ServerInstance) error } type RunEndpointRepository interface { Create(domain.RunEndpoint) error Get(id string) (domain.RunEndpoint, error) List(domain.RunEndpointFilter) ([]domain.RunEndpoint, error) Update(domain.RunEndpoint) error } type JobRepository interface { Create(domain.Job) error Get(id string) (domain.Job, error) GetByIdempotency(runEndpointID string, idempotencyKey string) (domain.Job, error) List(domain.JobFilter) ([]domain.Job, error) Update(domain.Job) error } type ArtifactRepository interface { Create(domain.Artifact) error Get(id string) (domain.Artifact, error) List(domain.ArtifactFilter) ([]domain.Artifact, error) Update(domain.Artifact) error } type LogStreamRepository interface { Create(domain.LogStream) error Get(id string) (domain.LogStream, error) List(domain.LogStreamFilter) ([]domain.LogStream, error) Update(domain.LogStream) error } type AuditEventRepository interface { Create(domain.AuditEvent) error Get(id string) (domain.AuditEvent, error) List(domain.AuditEventFilter) ([]domain.AuditEvent, error) Update(domain.AuditEvent) error } type Store interface { Users() UserRepository AIProviders() AIProviderRepository GamePlugins() GamePluginRepository ServerInstances() ServerInstanceRepository RunEndpoints() RunEndpointRepository Jobs() JobRepository Artifacts() ArtifactRepository LogStreams() LogStreamRepository AuditEvents() AuditEventRepository } type MemoryStore struct { users *memoryRepository[domain.User, domain.UserFilter] aiProviders *memoryRepository[domain.AIProvider, domain.AIProviderFilter] gamePlugins *memoryRepository[domain.GamePlugin, domain.GamePluginFilter] serverInstances *memoryRepository[domain.ServerInstance, domain.ServerInstanceFilter] runEndpoints *memoryRepository[domain.RunEndpoint, domain.RunEndpointFilter] jobs *memoryJobRepository artifacts *memoryRepository[domain.Artifact, domain.ArtifactFilter] logStreams *memoryRepository[domain.LogStream, domain.LogStreamFilter] auditEvents *memoryRepository[domain.AuditEvent, domain.AuditEventFilter] } func NewMemoryStore() *MemoryStore { return &MemoryStore{ users: newMemoryRepository( func(user domain.User) string { return user.ID }, domain.CopyUser, matchUser, ), aiProviders: newMemoryRepository( func(provider domain.AIProvider) string { return provider.ID }, domain.CopyAIProvider, matchAIProvider, ), gamePlugins: newMemoryRepository( func(plugin domain.GamePlugin) string { return plugin.ID }, domain.CopyGamePlugin, matchGamePlugin, ), serverInstances: newMemoryRepository( func(instance domain.ServerInstance) string { return instance.ID }, domain.CopyServerInstance, matchServerInstance, ), runEndpoints: newMemoryRepository( func(endpoint domain.RunEndpoint) string { return endpoint.ID }, domain.CopyRunEndpoint, matchRunEndpoint, ), jobs: newMemoryJobRepository(), artifacts: newMemoryRepository( func(artifact domain.Artifact) string { return artifact.ID }, domain.CopyArtifact, matchArtifact, ), logStreams: newMemoryRepository( func(stream domain.LogStream) string { return stream.ID }, domain.CopyLogStream, matchLogStream, ), auditEvents: newMemoryRepository( func(event domain.AuditEvent) string { return event.ID }, domain.CopyAuditEvent, matchAuditEvent, ), } } func (store *MemoryStore) Users() UserRepository { return store.users } func (store *MemoryStore) AIProviders() AIProviderRepository { return store.aiProviders } func (store *MemoryStore) GamePlugins() GamePluginRepository { return store.gamePlugins } func (store *MemoryStore) ServerInstances() ServerInstanceRepository { return store.serverInstances } func (store *MemoryStore) RunEndpoints() RunEndpointRepository { return store.runEndpoints } func (store *MemoryStore) Jobs() JobRepository { return store.jobs } func (store *MemoryStore) Artifacts() ArtifactRepository { return store.artifacts } func (store *MemoryStore) LogStreams() LogStreamRepository { return store.logStreams } func (store *MemoryStore) AuditEvents() AuditEventRepository { return store.auditEvents } type memoryRepository[T any, F any] struct { mu sync.RWMutex byID map[string]T idOf func(T) string copyOf func(T) T match func(T, F) bool } func newMemoryRepository[T any, F any](idOf func(T) string, copyOf func(T) T, match func(T, F) bool) *memoryRepository[T, F] { return &memoryRepository[T, F]{ byID: map[string]T{}, idOf: idOf, copyOf: copyOf, match: match, } } func (repository *memoryRepository[T, F]) Create(value T) error { repository.mu.Lock() defer repository.mu.Unlock() id := repository.idOf(value) if _, exists := repository.byID[id]; exists { return ErrDuplicate } repository.byID[id] = repository.copyOf(value) return nil } func (repository *memoryRepository[T, F]) Get(id string) (T, error) { repository.mu.RLock() defer repository.mu.RUnlock() value, exists := repository.byID[id] if !exists { var zero T return zero, ErrNotFound } return repository.copyOf(value), nil } func (repository *memoryRepository[T, F]) List(filter F) ([]T, error) { 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 { value := repository.byID[id] if repository.match(value, filter) { values = append(values, repository.copyOf(value)) } } return values, nil } func (repository *memoryRepository[T, F]) Update(value T) error { repository.mu.Lock() defer repository.mu.Unlock() id := repository.idOf(value) if _, exists := repository.byID[id]; !exists { return ErrNotFound } repository.byID[id] = repository.copyOf(value) return nil } type memoryJobRepository struct { *memoryRepository[domain.Job, domain.JobFilter] } func newMemoryJobRepository() *memoryJobRepository { return &memoryJobRepository{ memoryRepository: newMemoryRepository( func(job domain.Job) string { return job.ID }, domain.CopyJob, matchJob, ), } } func (repository *memoryJobRepository) GetByIdempotency(runEndpointID string, idempotencyKey string) (domain.Job, error) { repository.mu.RLock() defer repository.mu.RUnlock() for _, job := range repository.byID { if job.RunEndpointID == runEndpointID && job.IdempotencyKey == idempotencyKey { return domain.CopyJob(job), nil } } return domain.Job{}, ErrNotFound } func matchUser(user domain.User, filter domain.UserFilter) bool { return filter.Status == "" || user.Status == filter.Status } func matchAIProvider(provider domain.AIProvider, filter domain.AIProviderFilter) bool { return (filter.Kind == "" || provider.Kind == filter.Kind) && (filter.Status == "" || provider.Status == filter.Status) } func matchGamePlugin(plugin domain.GamePlugin, filter domain.GamePluginFilter) bool { return (filter.ServerType == "" || plugin.ServerType == filter.ServerType) && (filter.Status == "" || plugin.Status == filter.Status) } func matchServerInstance(instance domain.ServerInstance, filter domain.ServerInstanceFilter) bool { return (filter.PluginID == "" || instance.PluginID == filter.PluginID) && (filter.RunEndpointID == "" || instance.RunEndpointID == filter.RunEndpointID) && (filter.State == "" || instance.State == filter.State) && (filter.VisibleToUserID == "" || instance.OwnerUserID == filter.VisibleToUserID || containsString(instance.AdminUserIDs, filter.VisibleToUserID)) } func containsString(values []string, target string) bool { for _, value := range values { if value == target { return true } } return false } func matchRunEndpoint(endpoint domain.RunEndpoint, filter domain.RunEndpointFilter) bool { return filter.Status == "" || endpoint.Status == filter.Status } func matchJob(job domain.Job, filter domain.JobFilter) bool { return (filter.ServerInstanceID == "" || job.ServerInstanceID == filter.ServerInstanceID) && (filter.RunEndpointID == "" || job.RunEndpointID == filter.RunEndpointID) && (filter.State == "" || job.State == filter.State) } func matchArtifact(artifact domain.Artifact, filter domain.ArtifactFilter) bool { return (filter.OwnerKind == "" || artifact.OwnerKind == filter.OwnerKind) && (filter.OwnerID == "" || artifact.OwnerID == filter.OwnerID) && (filter.State == "" || artifact.State == filter.State) } func matchLogStream(stream domain.LogStream, filter domain.LogStreamFilter) bool { return (filter.ServerInstanceID == "" || stream.ServerInstanceID == filter.ServerInstanceID) && (filter.StreamKey == "" || stream.StreamKey == filter.StreamKey) } func matchAuditEvent(event domain.AuditEvent, filter domain.AuditEventFilter) bool { return (filter.ActorID == "" || event.ActorID == filter.ActorID) && (filter.ResourceKind == "" || event.ResourceKind == filter.ResourceKind) && (filter.ResourceID == "" || event.ResourceID == filter.ResourceID) && (filter.Result == "" || event.Result == filter.Result) }