package instances import ( "context" "errors" "path/filepath" "github.com/FAU-CDI/wisski-distillery/internal/dis/component" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances/malt" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/sql" "github.com/FAU-CDI/wisski-distillery/internal/models" "github.com/FAU-CDI/wisski-distillery/internal/wisski" "github.com/tkw1536/goprogram/exit" "gorm.io/gorm" "gorm.io/gorm/clause" ) // Instances manages multiple WissKI Instances. type Instances struct { component.Base Dependencies struct { Malt *malt.Malt SQL *sql.SQL InstanceTable *sql.InstanceTable } } func (instances *Instances) Path() string { return filepath.Join(instances.Still.Config.DeployRoot, "instances") } // ErrWissKINotFound is returned when a WissKI is not found var ErrWissKINotFound = errors.New("WissKI not found") var errSQL = exit.Error{ Message: "unknown SQL error %s", ExitCode: exit.ExitGeneric, } // use uses the non-nil wisski instance with this instances func (instances *Instances) use(wisski *wisski.WissKI) { wisski.Liquid.Malt = instances.Dependencies.Malt } // WissKI returns the WissKI with the provided slug, if it exists. // It the WissKI does not exist, returns ErrWissKINotFound. func (instances *Instances) WissKI(ctx context.Context, slug string) (wissKI *wisski.WissKI, err error) { if slug == "" { return nil, ErrWissKINotFound } sql := instances.Dependencies.SQL if err := sql.WaitQueryTable(ctx); err != nil { return nil, err } table, err := sql.QueryTable(ctx, instances.Dependencies.InstanceTable) if err != nil { return nil, err } // create a struct wissKI = new(wisski.WissKI) // find the instance by slug query := table.Find(&wissKI.Liquid.Instance, &models.Instance{Slug: slug}) switch { case query.Error != nil: return nil, errSQL.WithMessageF(query.Error) case query.RowsAffected == 0: return nil, ErrWissKINotFound } // use the wissKI instance instances.use(wissKI) return wissKI, nil } // Instance is a convenience function to return an instance based on a model slug. // When the instance does not exist, returns nil. func (instances *Instances) Instance(ctx context.Context, instance models.Instance) *wisski.WissKI { wissKI, err := instances.WissKI(ctx, instance.Slug) if err != nil { return nil } return wissKI } // Has checks if a WissKI with the provided slug exists inside the database. // It does not perform any checks on the WissKI itself. func (instances *Instances) Has(ctx context.Context, slug string) (ok bool, err error) { sql := instances.Dependencies.SQL if err := sql.WaitQueryTable(ctx); err != nil { return false, err } table, err := sql.QueryTable(ctx, instances.Dependencies.InstanceTable) if err != nil { return false, err } query := table.Select("count(*) > 0").Where("slug = ?", slug).Find(&ok) if query.Error != nil { return false, errSQL.WithMessageF(query.Error) } return } // All returns all instances of the WissKI Distillery in consistent order. // // There is no guarantee that this order remains identical between different api releases; however subsequent invocations are guaranteed to return the same order. func (instances *Instances) All(ctx context.Context) ([]*wisski.WissKI, error) { return instances.find(ctx, true, func(table *gorm.DB) *gorm.DB { return table }) } // WissKIs returns the WissKI instances with the provides slugs. // If a slug does not exist, it is omitted from the result. func (instances *Instances) WissKIs(ctx context.Context, slugs ...string) ([]*wisski.WissKI, error) { return instances.find(ctx, true, func(table *gorm.DB) *gorm.DB { return table.Where("slug IN ?", slugs) }) } // Load is like All, except that when no slugs are provided, it calls All. func (instances *Instances) Load(ctx context.Context, slugs ...string) ([]*wisski.WissKI, error) { if len(slugs) == 0 { return instances.All(ctx) } return instances.WissKIs(ctx, slugs...) } // find finds instances based on the provided query func (instances *Instances) find(ctx context.Context, order bool, query func(table *gorm.DB) *gorm.DB) (results []*wisski.WissKI, err error) { sql := instances.Dependencies.SQL if err := sql.WaitQueryTable(ctx); err != nil { return nil, err } // open the bookkeeping table table, err := sql.QueryTable(ctx, instances.Dependencies.InstanceTable) if err != nil { return nil, err } // prepare a query find := table if order { find = find.Order(clause.OrderByColumn{Column: clause.Column{Name: "slug"}, Desc: false}) } if query != nil { find = query(find) } // fetch bookkeeping instances var bks []models.Instance find = find.Find(&bks) if find.Error != nil { return nil, errSQL.WithMessageF(find.Error) } // make proper instances results = make([]*wisski.WissKI, len(bks)) for i, bk := range bks { results[i] = new(wisski.WissKI) results[i].Liquid.Instance = bk instances.use(results[i]) } return results, nil }