// Package daos handles common PocketBase DB model manipulations. // // Think of daos as DB repository and service layer in one. package daos import ( "errors" "fmt" "github.com/pocketbase/dbx" "github.com/pocketbase/pocketbase/models" ) // New creates a new Dao instance with the provided db builder. func New(db dbx.Builder) *Dao { return &Dao{ db: db, } } // Dao handles various db operations. // Think of Dao as a repository and service layer in one. type Dao struct { db dbx.Builder BeforeCreateFunc func(eventDao *Dao, m models.Model) error AfterCreateFunc func(eventDao *Dao, m models.Model) BeforeUpdateFunc func(eventDao *Dao, m models.Model) error AfterUpdateFunc func(eventDao *Dao, m models.Model) BeforeDeleteFunc func(eventDao *Dao, m models.Model) error AfterDeleteFunc func(eventDao *Dao, m models.Model) } // DB returns the internal db builder (*dbx.DB or *dbx.TX). func (dao *Dao) DB() dbx.Builder { return dao.db } // ModelQuery creates a new query with preset Select and From fields // based on the provided model argument. func (dao *Dao) ModelQuery(m models.Model) *dbx.SelectQuery { tableName := m.TableName() return dao.db.Select(fmt.Sprintf("{{%s}}.*", tableName)).From(tableName) } // FindById finds a single db record with the specified id and // scans the result into m. func (dao *Dao) FindById(m models.Model, id string) error { return dao.ModelQuery(m).Where(dbx.HashExp{"id": id}).Limit(1).One(m) } // RunInTransaction wraps fn into a transaction. // // It is safe to nest RunInTransaction calls. func (dao *Dao) RunInTransaction(fn func(txDao *Dao) error) error { switch txOrDB := dao.db.(type) { case *dbx.Tx: // nested transactions are not supported by default // so execute the function within the current transaction return fn(dao) case *dbx.DB: return txOrDB.Transactional(func(tx *dbx.Tx) error { txDao := New(tx) txDao.BeforeCreateFunc = func(eventDao *Dao, m models.Model) error { if dao.BeforeCreateFunc != nil { return dao.BeforeCreateFunc(eventDao, m) } return nil } txDao.AfterCreateFunc = func(eventDao *Dao, m models.Model) { if dao.AfterCreateFunc != nil { dao.AfterCreateFunc(eventDao, m) } } txDao.BeforeUpdateFunc = func(eventDao *Dao, m models.Model) error { if dao.BeforeUpdateFunc != nil { return dao.BeforeUpdateFunc(eventDao, m) } return nil } txDao.AfterUpdateFunc = func(eventDao *Dao, m models.Model) { if dao.AfterUpdateFunc != nil { dao.AfterUpdateFunc(eventDao, m) } } txDao.BeforeDeleteFunc = func(eventDao *Dao, m models.Model) error { if dao.BeforeDeleteFunc != nil { return dao.BeforeDeleteFunc(eventDao, m) } return nil } txDao.AfterDeleteFunc = func(eventDao *Dao, m models.Model) { if dao.AfterDeleteFunc != nil { dao.AfterDeleteFunc(eventDao, m) } } return fn(txDao) }) } return errors.New("Failed to start transaction (unknown dao.db)") } // Delete deletes the provided model. func (dao *Dao) Delete(m models.Model) error { if !m.HasId() { return errors.New("ID is not set") } if dao.BeforeDeleteFunc != nil { if err := dao.BeforeDeleteFunc(dao, m); err != nil { return err } } if err := dao.db.Model(m).Delete(); err != nil { return err } if dao.AfterDeleteFunc != nil { dao.AfterDeleteFunc(dao, m) } return nil } // Save upserts (update or create if primary key is not set) the provided model. func (dao *Dao) Save(m models.Model) error { if m.HasId() { return dao.update(m) } return dao.create(m) } func (dao *Dao) update(m models.Model) error { if !m.HasId() { return errors.New("ID is not set") } m.RefreshUpdated() if dao.BeforeUpdateFunc != nil { if err := dao.BeforeUpdateFunc(dao, m); err != nil { return err } } if v, ok := any(m).(models.ColumnValueMapper); ok { dataMap := v.ColumnValueMap() _, err := dao.db.Update( m.TableName(), dataMap, dbx.HashExp{"id": m.GetId()}, ).Execute() if err != nil { return err } } else { if err := dao.db.Model(m).Update(); err != nil { return err } } if dao.AfterUpdateFunc != nil { dao.AfterUpdateFunc(dao, m) } return nil } func (dao *Dao) create(m models.Model) error { if !m.HasId() { // auto generate id m.RefreshId() } if m.GetCreated().IsZero() { m.RefreshCreated() } if m.GetUpdated().IsZero() { m.RefreshUpdated() } if dao.BeforeCreateFunc != nil { if err := dao.BeforeCreateFunc(dao, m); err != nil { return err } } if v, ok := any(m).(models.ColumnValueMapper); ok { dataMap := v.ColumnValueMap() _, err := dao.db.Insert(m.TableName(), dataMap).Execute() if err != nil { return err } } else { if err := dao.db.Model(m).Insert(); err != nil { return err } } if dao.AfterCreateFunc != nil { dao.AfterCreateFunc(dao, m) } return nil }