package storage import ( "errors" "fmt" "github.com/glebarez/sqlite" "gorm.io/driver/postgres" "gorm.io/gorm" ) // Storage is responsible for all database, cache and media related actions // and serves as the lowest layer of the cake type Storage struct { db *gorm.DB } var ErrAccountNotFound = errors.New("account not found") // Build a new storage using sqlite as database backend func NewStorageSqlite(filePath string) (*Storage, error) { db, err := gorm.Open(sqlite.Open(filePath)) if err != nil { return nil, err } return storageFromEmptyDb(db) } func NewStoragePostgres(dbUrl string) (*Storage, error) { db, err := gorm.Open(postgres.Open(dbUrl)) if err != nil { return nil, err } return storageFromEmptyDb(db) } func storageFromEmptyDb(db *gorm.DB) (*Storage, error) { // AutoMigrate ensures the db is in a state where all the structs given here // have their own tables and relations setup. It also updates tables if necessary if err := db.AutoMigrate( placeholderMediaFile, placeholderUser, placeholderNote, placeholderServer, ); err != nil { return nil, fmt.Errorf("problem while auto migrating: %w", err) } // Afterwards add the placeholder entries for each table. // FirstOrCreate either creates a new entry or retrieves the first matching one // We only care about the creation if there is none yet, so no need to carry the result over if res := db.FirstOrCreate(placeholderMediaFile); res.Error != nil { return nil, fmt.Errorf("failed to add placeholder media file: %w", res.Error) } if res := db.FirstOrCreate(placeholderUser()); res.Error != nil { return nil, fmt.Errorf("failed to add placeholder media file: %w", res.Error) } if res := db.FirstOrCreate(placeholderNote); res.Error != nil { return nil, fmt.Errorf("failed to add placeholder media file: %w", res.Error) } if res := db.FirstOrCreate(placeholderServer); res.Error != nil { return nil, fmt.Errorf("failed to add placeholder media file: %w", res.Error) } // And finally, build the actual storage struct return &Storage{ db: db, }, nil } func (s *Storage) FindLocalAccount(handle string) (*User, error) { acc := User{} res := s.db.Where("handle = ?", handle).First(&acc) if res.Error != nil { return nil, fmt.Errorf("failed to query db: %w", res.Error) } if res.RowsAffected == 0 { return nil, ErrAccountNotFound } return nil, errors.New("unimplemented") }