Puberty commit
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user