166 lines
4.5 KiB
Go
166 lines
4.5 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"gorm.io/driver/postgres"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/logger"
|
|
)
|
|
|
|
var (
|
|
backendStore Store = unavailableStore{err: errors.New("backend store not initialized")}
|
|
|
|
defaultStoreOnce sync.Once
|
|
defaultStoreErr error
|
|
)
|
|
|
|
func setBackendStore(store Store) {
|
|
if store == nil {
|
|
backendStore = unavailableStore{err: errors.New("backend store not initialized")}
|
|
return
|
|
}
|
|
backendStore = store
|
|
}
|
|
|
|
func bootstrapDefaultStore() (Store, error) {
|
|
defaultStoreOnce.Do(func() {
|
|
db, err := openDBFromEnv()
|
|
if err != nil {
|
|
defaultStoreErr = err
|
|
setBackendStore(unavailableStore{err: err})
|
|
return
|
|
}
|
|
store, err := newGORMStore(db)
|
|
if err != nil {
|
|
defaultStoreErr = err
|
|
setBackendStore(unavailableStore{err: err})
|
|
return
|
|
}
|
|
setBackendStore(store)
|
|
})
|
|
return backendStore, defaultStoreErr
|
|
}
|
|
|
|
func openDBFromEnv() (*gorm.DB, error) {
|
|
dsn := strings.TrimSpace(os.Getenv("DATABASE_URL"))
|
|
if dsn == "" {
|
|
return nil, errors.New("DATABASE_URL is required")
|
|
}
|
|
return openDB(dsn)
|
|
}
|
|
|
|
func openDB(dsn string) (*gorm.DB, error) {
|
|
config := &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)}
|
|
var (
|
|
db *gorm.DB
|
|
err error
|
|
)
|
|
if strings.HasPrefix(dsn, "sqlite:") || strings.HasPrefix(dsn, "file:") || dsn == ":memory:" {
|
|
db, err = gorm.Open(sqlite.Open(dsn), config)
|
|
} else {
|
|
db, err = gorm.Open(postgres.Open(dsn), config)
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open database: %w", err)
|
|
}
|
|
sqlDB, err := db.DB()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("database handle: %w", err)
|
|
}
|
|
sqlDB.SetConnMaxLifetime(5 * time.Minute)
|
|
sqlDB.SetMaxIdleConns(5)
|
|
sqlDB.SetMaxOpenConns(10)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
if err := sqlDB.PingContext(ctx); err != nil {
|
|
return nil, fmt.Errorf("ping database: %w", err)
|
|
}
|
|
return db, nil
|
|
}
|
|
|
|
type unavailableStore struct {
|
|
err error
|
|
}
|
|
|
|
func (s unavailableStore) Ping(context.Context) error { return s.err }
|
|
|
|
func (s unavailableStore) UserByLogin(context.Context, string) (UserProfile, bool, error) {
|
|
return UserProfile{}, false, s.err
|
|
}
|
|
|
|
func (s unavailableStore) UserByID(context.Context, string) (UserProfile, bool, error) {
|
|
return UserProfile{}, false, s.err
|
|
}
|
|
|
|
func (s unavailableStore) AddUser(context.Context, string, string, string) (UserProfile, error) {
|
|
return UserProfile{}, s.err
|
|
}
|
|
|
|
func (s unavailableStore) UpdatePassword(context.Context, string, string) error { return s.err }
|
|
|
|
func (s unavailableStore) ListUsers(context.Context) ([]UserProfile, error) { return nil, s.err }
|
|
|
|
func (s unavailableStore) ListContent(context.Context, ContentFilter) ([]ContentItem, error) {
|
|
return nil, s.err
|
|
}
|
|
|
|
func (s unavailableStore) ContentByID(context.Context, string) (ContentItem, bool, error) {
|
|
return ContentItem{}, false, s.err
|
|
}
|
|
|
|
func (s unavailableStore) AddContent(context.Context, ContentItem) (ContentItem, error) {
|
|
return ContentItem{}, s.err
|
|
}
|
|
|
|
func (s unavailableStore) PatchContent(context.Context, string, ContentItem) (ContentItem, bool, error) {
|
|
return ContentItem{}, false, s.err
|
|
}
|
|
|
|
func (s unavailableStore) DeleteContent(context.Context, string) (bool, error) { return false, s.err }
|
|
|
|
func (s unavailableStore) ListCategories(context.Context) ([]string, error) { return nil, s.err }
|
|
|
|
func (s unavailableStore) ListTags(context.Context) ([]string, error) { return nil, s.err }
|
|
|
|
func (s unavailableStore) ListSpeakers(context.Context) ([]Speaker, error) { return nil, s.err }
|
|
|
|
func (s unavailableStore) AddFile(context.Context, StoredFile) (StoredFile, error) {
|
|
return StoredFile{}, s.err
|
|
}
|
|
|
|
func (s unavailableStore) FileByID(context.Context, string) (StoredFile, bool, error) {
|
|
return StoredFile{}, false, s.err
|
|
}
|
|
|
|
func (s unavailableStore) UpsertSubscription(context.Context, string, string) ([]string, error) {
|
|
return nil, s.err
|
|
}
|
|
|
|
func (s unavailableStore) ListNotifications(context.Context) ([]NotificationItem, error) {
|
|
return nil, s.err
|
|
}
|
|
|
|
func (s unavailableStore) MarkNotificationRead(context.Context, string) (*NotificationItem, error) {
|
|
return nil, s.err
|
|
}
|
|
|
|
func (s unavailableStore) ListComments(context.Context, string) ([]CommentItem, error) {
|
|
return nil, s.err
|
|
}
|
|
|
|
func (s unavailableStore) AddComment(context.Context, string, UserProfile, string) (CommentItem, error) {
|
|
return CommentItem{}, s.err
|
|
}
|
|
|
|
func (s unavailableStore) CountComments(context.Context) (int, error) { return 0, s.err }
|
|
|
|
func (s unavailableStore) ListAudit(context.Context) ([]AuditItem, error) { return nil, s.err }
|