package db import ( "database/sql" "fmt" "time" "github.com/juju/errors" "github.com/localhots/cmdui/backend/commands" ) type JobState string const ( JobStateNew JobState = "new" JobStateCreated JobState = "created" JobStateStarted JobState = "started" JobStateAborted JobState = "aborted" JobStateFailed JobState = "failed" JobStateFinished JobState = "finished" ) type Job struct { ID string `json:"id" db:"id"` Command string `json:"command" db:"command"` Args string `json:"args" db:"args"` Flags string `json:"flags" db:"flags"` UserID string `json:"user_id" db:"user_id"` User *User `json:"user" db:"-"` State string `json:"state" db:"state"` CreatedAt *time.Time `json:"created_at" db:"created_at"` StartedAt *time.Time `json:"started_at" db:"started_at"` FinishedAt *time.Time `json:"finished_at" db:"finished_at"` } func NewJob(c commands.Command, u User) Job { return Job{ ID: newID(), Command: c.Name, Args: c.Args, Flags: c.FlagsString(), UserID: u.ID, User: &u, State: string(JobStateNew), } } func FindAllJobs(p Page) ([]Job, error) { return findJobsWhere(jobsIndexQuery("", p)) } func FindCommandJobs(name string, p Page) ([]Job, error) { return findJobsWhere(jobsIndexQuery("WHERE command = ?", p), name) } func FindUserJobs(id string, p Page) ([]Job, error) { return findJobsWhere(jobsIndexQuery("WHERE user_id = ?", p), id) } func jobsIndexQuery(where string, p Page) string { p = p.normalize() return fmt.Sprintf("SELECT * FROM jobs %s ORDER BY created_at DESC LIMIT %d, %d", where, p.Offset, p.Limit) } func findJobsWhere(query string, args ...interface{}) ([]Job, error) { var jobs []Job err := db.Select(&jobs, query, args...) if err != nil && err != sql.ErrNoRows { return nil, errors.Annotate(err, "Failed to load Jobs list") } userIDs := stringSet{} for _, r := range jobs { userIDs.add(r.UserID) } users, err := FindUsers(userIDs.items()...) if err != nil { return nil, errors.Annotate(err, "Failed to find users to embed into jobs") } for i, r := range jobs { if u, ok := users[r.UserID]; ok { jobs[i].User = &u } } return jobs, nil } func FindJob(id string) (*Job, error) { var r Job err := db.Get(&r, "SELECT * FROM jobs WHERE id = ?", id) if err != nil { if err != sql.ErrNoRows { return nil, errors.Annotate(err, "Failed to load Job details") } return nil, nil } if r.UserID != "" { r.User, err = FindUser(r.UserID) if err != nil { return nil, errors.Annotate(err, "Failed to find a user to embed into a job") } } return &r, nil } func (r *Job) UpdateState(s JobState) error { r.State = string(s) ts := time.Now().UTC() switch s { case JobStateStarted: r.StartedAt = &ts case JobStateFinished, JobStateAborted, JobStateFailed: r.FinishedAt = &ts } return r.Update() } func (r *Job) Create() error { ts := time.Now().UTC() r.CreatedAt = &ts r.State = string(JobStateCreated) _, err := db.NamedExec(` INSERT INTO jobs SET id = :id, command = :command, args = :args, flags = :flags, user_id = :user_id, state = :state, created_at = :created_at `, r) if err != nil { return errors.Annotate(err, "Failed to create a job") } return nil } func (r Job) Update() error { _, err := db.NamedExec(` UPDATE jobs SET state = :state, started_at = :started_at, finished_at = :finished_at WHERE id = :id `, r) if err != nil { return errors.Annotate(err, "Failed to update a job") } return nil } func jobsSliceToMap(s []Job) map[string]Job { m := make(map[string]Job, len(s)) for _, r := range s { m[r.ID] = r } return m }