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  }