Puberty commit

This commit is contained in:
2017-10-29 23:06:41 +01:00
commit 34034b5223
63 changed files with 16011 additions and 0 deletions
+80
View File
@@ -0,0 +1,80 @@
package db
import (
"reflect"
"strings"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
uuid "github.com/satori/go.uuid"
"github.com/localhots/cmdui/backend/config"
)
var (
db *sqlx.DB
)
func Connect() error {
var err error
cfg := config.Get().Database
db, err = sqlx.Connect(cfg.Driver, cfg.Spec)
return err
}
type Page struct {
Offset uint
Limit uint
}
func (p Page) normalize() Page {
const defaultPerPage = 50
if p.Limit == 0 {
p.Limit = defaultPerPage
}
return p
}
//
// Helpers
//
func newID() string {
return uuid.NewV4().String()
}
func placeholders(val interface{}) string {
v := reflect.ValueOf(val)
if v.Kind() == reflect.Slice {
s := strings.Repeat("?, ", v.Len())
return s[0 : len(s)-2]
}
return "?"
}
func iargs(args []string) []interface{} {
iargs := make([]interface{}, len(args))
for i, arg := range args {
iargs[i] = arg
}
return iargs
}
type stringSet map[string]struct{}
func (s stringSet) add(items ...string) {
for _, item := range items {
s[item] = struct{}{}
}
}
func (s stringSet) items() []string {
l := make([]string, len(s))
i := 0
for item := range s {
l[i] = item
i++
}
return l
}
+168
View File
@@ -0,0 +1,168 @@
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
}
+60
View File
@@ -0,0 +1,60 @@
package db
import (
"database/sql"
"time"
"github.com/juju/errors"
)
type Session struct {
ID string `db:"id"`
UserID string `db:"user_id"`
CreatedAt time.Time `db:"created_at"`
ExpiresAt time.Time `db:"expires_at"`
}
func NewSession(userID string) Session {
const ttl = 6 * 30 * 24 * time.Hour // 6 months
now := time.Now().UTC()
exp := now.Add(ttl)
return Session{
ID: newID(),
UserID: userID,
CreatedAt: now,
ExpiresAt: exp,
}
}
func FindSession(id string) (*Session, error) {
var s Session
err := db.Get(&s, "SELECT * FROM sessions WHERE id = ?", id)
if err != nil {
if err != sql.ErrNoRows {
return nil, errors.Annotate(err, "Failed to load session details")
}
return nil, nil
}
return &s, nil
}
func (s Session) Create() error {
_, err := db.NamedExec(`
INSERT INTO sessions
SET
id = :id,
user_id = :user_id,
created_at = :created_at,
expires_at = :expires_at
`, s)
if err != nil {
return errors.Annotate(err, "Failed to create a session")
}
return nil
}
func (s Session) User() (*User, error) {
return FindUser(s.UserID)
}
+110
View File
@@ -0,0 +1,110 @@
package db
import (
"database/sql"
"fmt"
"github.com/juju/errors"
)
type User struct {
ID string `json:"id" db:"id"`
Name string `json:"name" db:"github_name"`
Picture string `json:"picture" db:"github_picture"`
GithubID uint `json:"-" db:"github_id"`
GithubLogin string `json:"-" db:"github_login"`
Authorized bool `json:"authorized" db:"-"`
}
func NewUser() User {
return User{ID: newID()}
}
func FindAllUsers() (map[string]User, error) {
return findUsers("SELECT * FROM users ORDER BY id ASC")
}
func FindUsers(ids ...string) (map[string]User, error) {
if len(ids) == 0 {
return map[string]User{}, nil
}
query := fmt.Sprintf("SELECT * FROM users WHERE id IN (%s)", placeholders(ids))
return findUsers(query, iargs(ids)...)
}
func findUsers(query string, args ...interface{}) (map[string]User, error) {
var users []User
err := db.Select(&users, query, args...)
if err != nil && err != sql.ErrNoRows {
return nil, errors.Annotate(err, "Failed to load users list")
}
return usersSliceToMap(users), nil
}
func FindUser(id string) (*User, error) {
return findUser("SELECT * FROM users WHERE id = ?", id)
}
func FindUserByGithubID(id uint) (*User, error) {
return findUser("SELECT * FROM users WHERE github_id = ?", id)
}
func FindUserByLogin(login string) (*User, error) {
return findUser("SELECT * FROM users WHERE github_login = ?", login)
}
func findUser(query string, args ...interface{}) (*User, error) {
var u User
err := db.Get(&u, query, args...)
if err != nil {
if err != sql.ErrNoRows {
return nil, errors.Annotate(err, "Failed to load user details")
}
return nil, nil
}
return &u, nil
}
func (u User) Create() error {
_, err := db.NamedExec(`
INSERT INTO users
SET
id = :id,
github_id = :github_id,
github_login = :github_login,
github_name = :github_name,
github_picture = :github_picture
`, u)
if err != nil {
return errors.Annotate(err, "Failed to create a user")
}
return nil
}
func (u User) Update() error {
_, err := db.NamedExec(`
UPDATE users
SET
github_login = :github_login,
github_name = :github_name,
github_picture = :github_picture
WHERE
github_id = :github_id
`, u)
if err != nil {
return errors.Annotate(err, "Failed to update a user")
}
return nil
}
func usersSliceToMap(s []User) map[string]User {
m := make(map[string]User, len(s))
for _, u := range s {
m[u.ID] = u
}
return m
}