github.com/leg100/ots@v0.0.7-0.20210919080622-034055ced4bd/sqlite/workspace.go (about) 1 package sqlite 2 3 import ( 4 "fmt" 5 6 "github.com/leg100/ots" 7 "gorm.io/gorm" 8 "gorm.io/gorm/clause" 9 ) 10 11 var _ ots.WorkspaceStore = (*WorkspaceDB)(nil) 12 13 type WorkspaceDB struct { 14 *gorm.DB 15 } 16 17 func NewWorkspaceDB(db *gorm.DB) *WorkspaceDB { 18 return &WorkspaceDB{ 19 DB: db, 20 } 21 } 22 23 // Create persists a Workspace to the DB. The returned Workspace is adorned with 24 // additional metadata, i.e. CreatedAt, UpdatedAt, etc. 25 func (db WorkspaceDB) Create(domain *ots.Workspace) (*ots.Workspace, error) { 26 model := &Workspace{} 27 model.FromDomain(domain) 28 29 if result := db.Omit("Organization").Create(model); result.Error != nil { 30 return nil, result.Error 31 } 32 33 return model.ToDomain(), nil 34 } 35 36 // Update persists an updated Workspace to the DB. The existing run is fetched 37 // from the DB, the supplied func is invoked on the run, and the updated run is 38 // persisted back to the DB. The returned Workspace includes any changes, 39 // including a new UpdatedAt value. 40 func (db WorkspaceDB) Update(spec ots.WorkspaceSpecifier, fn func(*ots.Workspace) error) (*ots.Workspace, error) { 41 var model *Workspace 42 43 err := db.Transaction(func(tx *gorm.DB) (err error) { 44 // Get existing model obj from DB 45 model, err = getWorkspace(tx, spec) 46 if err != nil { 47 return err 48 } 49 50 // Update domain obj using client-supplied fn 51 if err := model.Update(fn); err != nil { 52 return err 53 } 54 55 if result := tx.Save(model); result.Error != nil { 56 return err 57 } 58 59 return nil 60 }) 61 if err != nil { 62 return nil, err 63 } 64 65 // Convert back to domain obj 66 return model.ToDomain(), nil 67 } 68 69 func (db WorkspaceDB) List(opts ots.WorkspaceListOptions) (*ots.WorkspaceList, error) { 70 var models WorkspaceList 71 var count int64 72 73 err := db.Transaction(func(tx *gorm.DB) error { 74 query := tx 75 76 if opts.OrganizationName != nil { 77 org, err := getOrganizationByName(tx, *opts.OrganizationName) 78 if err != nil { 79 return err 80 } 81 82 query = query.Where("organization_id = ?", org.Model.ID) 83 } 84 85 if opts.Prefix != nil { 86 query = query.Where("name LIKE ?", fmt.Sprintf("%s%%", *opts.Prefix)) 87 } 88 89 if result := query.Model(&models).Count(&count); result.Error != nil { 90 return result.Error 91 } 92 93 if result := query.Preload(clause.Associations).Scopes(paginate(opts.ListOptions)).Find(&models); result.Error != nil { 94 return result.Error 95 } 96 97 return nil 98 }) 99 if err != nil { 100 return nil, err 101 } 102 103 return &ots.WorkspaceList{ 104 Items: models.ToDomain(), 105 Pagination: ots.NewPagination(opts.ListOptions, int(count)), 106 }, nil 107 } 108 109 func (db WorkspaceDB) Get(spec ots.WorkspaceSpecifier) (*ots.Workspace, error) { 110 ws, err := getWorkspace(db.DB, spec) 111 if err != nil { 112 return nil, err 113 } 114 return ws.ToDomain(), nil 115 } 116 117 // Delete deletes a specific workspace, along with its associated records (runs 118 // etc). 119 func (db WorkspaceDB) Delete(spec ots.WorkspaceSpecifier) error { 120 err := db.Transaction(func(tx *gorm.DB) error { 121 var ws Workspace 122 var query *gorm.DB 123 124 switch { 125 case spec.ID != nil: 126 // Get workspace by ID 127 query = tx.Where("external_id = ?", *spec.ID) 128 case spec.Name != nil && spec.OrganizationName != nil: 129 // Get workspace by name and organization name 130 org, err := getOrganizationByName(tx, *spec.OrganizationName) 131 if err != nil { 132 return err 133 } 134 135 query = tx.Where("organization_id = ? AND name = ?", org.ID, spec.Name) 136 default: 137 return ots.ErrInvalidWorkspaceSpecifier 138 } 139 140 // Retrieve workspace 141 if result := query.First(&ws); result.Error != nil { 142 return result.Error 143 } 144 145 // Delete workspace 146 if result := query.Delete(&ws); result.Error != nil { 147 return result.Error 148 } 149 150 // Delete associated runs if they exist 151 result := tx.Delete(&Run{}, "workspace_id = ?", ws.ID) 152 if result.Error != nil && !ots.IsNotFound(result.Error) { 153 return result.Error 154 } 155 156 // Delete associated state versions if they exist 157 result = tx.Delete(&StateVersion{}, "workspace_id = ?", ws.ID) 158 if result.Error != nil && !ots.IsNotFound(result.Error) { 159 return result.Error 160 } 161 162 // Delete associated configuration versions if they exist 163 result = tx.Delete(&ConfigurationVersion{}, "workspace_id = ?", ws.ID) 164 if result.Error != nil && !ots.IsNotFound(result.Error) { 165 return result.Error 166 } 167 168 return nil 169 }) 170 if err != nil { 171 return err 172 } 173 174 return nil 175 } 176 177 func getWorkspace(db *gorm.DB, spec ots.WorkspaceSpecifier) (*Workspace, error) { 178 var model Workspace 179 180 query := db.Preload(clause.Associations) 181 182 switch { 183 case spec.ID != nil: 184 // Get workspace by ID 185 query = query.Where("external_id = ?", *spec.ID) 186 case spec.Name != nil && spec.OrganizationName != nil: 187 // Get workspace by name and organization name 188 query = query.Joins("JOIN organizations ON organizations.id = workspaces.organization_id"). 189 Where("workspaces.name = ? AND organizations.name = ?", spec.Name, spec.OrganizationName) 190 default: 191 return nil, ots.ErrInvalidWorkspaceSpecifier 192 } 193 194 if result := query.First(&model); result.Error != nil { 195 return nil, result.Error 196 } 197 198 return &model, nil 199 }