cleanup: refactor api architecture & add user roles

This commit is contained in:
Jordan Knott 2020-07-04 18:02:57 -05:00
parent a3958595cd
commit eaffaa70df
141 changed files with 12487 additions and 3792 deletions

View File

@ -1,3 +1,7 @@
generate:
rm graph/schema.graphqls
for f in graph/schema/*.gql; do cat $f; echo; done > graph/schema.graphqls
start:
docker container start test-db
go run cmd/citadel/main.go

View File

@ -3,20 +3,50 @@ package main
import (
"fmt"
_ "github.com/lib/pq"
"io/ioutil"
"net/http"
"time"
"github.com/BurntSushi/toml"
"github.com/jmoiron/sqlx"
"github.com/jordanknott/project-citadel/api/router"
"github.com/jordanknott/project-citadel/api/internal/route"
log "github.com/sirupsen/logrus"
)
type Database struct {
Host string
Name string
User string
Password string
}
type AppConfig struct {
Database Database
}
func main() {
dat, err := ioutil.ReadFile("conf/app.toml")
if err != nil {
panic(err)
}
var appConfig AppConfig
_, err = toml.Decode(string(dat), &appConfig)
if err != nil {
panic(err)
}
Formatter := new(log.TextFormatter)
Formatter.TimestampFormat = "02-01-2006 15:04:05"
Formatter.FullTimestamp = true
log.SetFormatter(Formatter)
db, err := sqlx.Connect("postgres", "user=postgres password=test host=0.0.0.0 dbname=citadel sslmode=disable")
log.SetLevel(log.InfoLevel)
connection := fmt.Sprintf("user=%s password=%s host=%s dbname=%s sslmode=disable",
appConfig.Database.User,
appConfig.Database.Password,
appConfig.Database.Host,
appConfig.Database.Name,
)
db, err := sqlx.Connect("postgres", connection)
if err != nil {
log.Panic(err)
}
@ -27,6 +57,6 @@ func main() {
defer db.Close()
fmt.Println("starting graphql server on http://localhost:3333")
fmt.Println("starting graphql playground on http://localhost:3333/__graphql")
r, _ := router.NewRouter(db)
r, _ := route.NewRouter(db)
http.ListenAndServe(":3333", r)
}

View File

@ -1,20 +1,43 @@
package main
import (
// "context"
// "fmt"
// "io/ioutil"
"bytes"
"context"
"fmt"
"github.com/BurntSushi/toml"
"github.com/jmoiron/sqlx"
"github.com/jordan-wright/email"
"github.com/jordanknott/project-citadel/api/pg"
"github.com/jordanknott/project-citadel/api/router"
_ "github.com/lib/pq"
"golang.org/x/crypto/bcrypt"
"io/ioutil"
"net/smtp"
// "github.com/jmoiron/sqlx"
// "github.com/jordanknott/project-citadel/api/pg"
// "github.com/BurntSushi/toml"
// "github.com/jordanknott/project-citadel/api/router"
// "time"
"text/template"
"time"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database/postgres"
_ "github.com/golang-migrate/migrate/v4/source/file"
)
type Database struct {
Host string
Name string
User string
Password string
}
type AppConfig struct {
Database Database
}
type UserRegistration struct {
Username string
Year string
AppName string
AppURL string
}
type color struct {
Name string
Color string
@ -25,39 +48,136 @@ type colors struct {
Color []color
}
func main() {
func SendEmail() {
emailTmpl, err := ioutil.ReadFile("templates/mail/user/registered.tmpl")
if err != nil {
panic(err)
}
user := UserRegistration{Username: "jordanthedev", AppName: "Citadel", AppURL: "http://localhost:3000/", Year: "2020"}
tmpl, err := template.New("registered").Parse(string(emailTmpl))
if err != nil {
panic(err)
}
var tpl bytes.Buffer
if err := tmpl.Execute(&tpl, &user); err != nil {
panic(err)
}
result := tpl.String()
e := email.NewEmail()
e.From = "Jordan Knott <no-reply@citadel.com>"
e.To = []string{"jordan@jordanthedev.com"}
e.Subject = "Jordan Knott (@jordanthedev) invited you to join the team \"Paradox\" on Citadel"
e.Text = []byte("Text Body is, of course, supported!")
e.HTML = []byte("<h1>Fancy HTML is supported, too!</h1>")
e.HTML = []byte(result)
e.Send("localhost:1025", smtp.PlainAuth("", "test@gmail.com", "password123", "localhost"))
// dur := time.Hour * 24 * 7 * 30
// token, err := router.NewAccessTokenCustomExpiration("21345076-6423-4a00-a6bd-cd9f830e2764", dur)
// if err != nil {
// panic(err)
// }
// fmt.Println(token)
// fmt.Println("seeding database...")
// dat, err := ioutil.ReadFile("data/colors.toml")
// if err != nil {
// panic(err)
// }
// var labelColors colors
// _, err = toml.Decode(string(dat), &labelColors)
// if err != nil {
// panic(err)
// }
// db, err := sqlx.Connect("postgres", "user=postgres password=test host=0.0.0.0 dbname=citadel sslmode=disable")
// repository := pg.NewRepository(db)
// for _, color := range labelColors.Color {
// fmt.Printf("%v\n", color)
// repository.CreateLabelColor(context.Background(), pg.CreateLabelColorParams{color.Name, color.Color, float64(color.Position)})
// }
}
func Seed() {
dur := time.Hour * 24 * 7 * 30
token, err := router.NewAccessTokenCustomExpiration("21345076-6423-4a00-a6bd-cd9f830e2764", dur)
if err != nil {
panic(err)
}
fmt.Println(token)
fmt.Println("seeding database...")
dat, err := ioutil.ReadFile("data/dark_colors.toml")
if err != nil {
panic(err)
}
var labelColors colors
_, err = toml.Decode(string(dat), &labelColors)
if err != nil {
panic(err)
}
db, err := sqlx.Connect("postgres", "user=postgres password=test host=0.0.0.0 dbname=citadel sslmode=disable")
repository := pg.NewRepository(db)
for _, color := range labelColors.Color {
fmt.Printf("%v\n", color)
repository.CreateLabelColor(context.Background(), pg.CreateLabelColorParams{color.Name, color.Color, float64(color.Position)})
}
}
func Migrate() {
dat, err := ioutil.ReadFile("conf/app.toml")
if err != nil {
panic(err)
}
var appConfig AppConfig
_, err = toml.Decode(string(dat), &appConfig)
if err != nil {
panic(err)
}
connection := fmt.Sprintf("user=%s password=%s host=%s dbname=%s sslmode=disable",
appConfig.Database.User,
appConfig.Database.Password,
appConfig.Database.Host,
appConfig.Database.Name,
)
fmt.Println(connection)
db, err := sqlx.Connect("postgres", connection)
if err != nil {
panic(err)
}
defer db.Close()
driver, err := postgres.WithInstance(db.DB, &postgres.Config{})
if err != nil {
panic(err)
}
m, err := migrate.NewWithDatabaseInstance(
"file://migrations",
"postgres", driver)
if err != nil {
panic(err)
}
err = m.Up()
if err != nil {
panic(err)
}
}
func main() {
dat, err := ioutil.ReadFile("conf/app.toml")
if err != nil {
panic(err)
}
var appConfig AppConfig
_, err = toml.Decode(string(dat), &appConfig)
if err != nil {
panic(err)
}
connection := fmt.Sprintf("user=%s password=%s host=%s dbname=%s sslmode=disable",
appConfig.Database.User,
appConfig.Database.Password,
appConfig.Database.Host,
appConfig.Database.Name,
)
fmt.Println(connection)
db, err := sqlx.Connect("postgres", connection)
if err != nil {
panic(err)
}
createdAt := time.Now().UTC()
hashedPwd, err := bcrypt.GenerateFromPassword([]byte("test"), 14)
repo := pg.NewRepository(db)
if err != nil {
panic(err)
}
_, err = repo.CreateUserAccount(context.Background(), pg.CreateUserAccountParams{
Username: "jordan",
Initials: "JK",
Email: "jordan@jordanthedev.com",
PasswordHash: string(hashedPwd),
CreatedAt: createdAt,
RoleCode: "admin",
})
if err != nil {
panic(err)
}
}

56
api/cmd/send/main.go Normal file
View File

@ -0,0 +1,56 @@
package main
import (
"fmt"
"github.com/RichardKnop/machinery/v1"
"github.com/RichardKnop/machinery/v1/config"
"github.com/RichardKnop/machinery/v1/tasks"
)
func Add(args ...int64) (int64, error) {
sum := int64(0)
for _, arg := range args {
sum += arg
}
return sum, nil
}
func main() {
var cnf = &config.Config{
Broker: "amqp://guest:guest@localhost:5672/",
DefaultQueue: "machinery_tasks",
ResultBackend: "memcache://localhost:11211",
AMQP: &config.AMQPConfig{
Exchange: "machinery_exchange",
ExchangeType: "direct",
BindingKey: "machinery_task",
},
}
fmt.Println("starting server")
server, err := machinery.NewServer(cnf)
if err != nil {
// do something with the error
}
addTask0 := tasks.Signature{
Name: "userRegistration",
Args: []tasks.Arg{
{
Type: "string",
Value: "21345076-6423-4a00-a6bd-cd9f830e2764",
},
},
}
asyncResult, err := server.SendTask(&addTask0)
if err != nil {
fmt.Errorf("Could not send task: %s", err.Error())
}
fmt.Println(asyncResult.GetState())
// results, err := asyncResult.Get(time.Duration(time.Millisecond * 5))
// fmt.Printf("split([\"foo\"]) = %v\n", tasks.HumanReadableResults(results))
}

66
api/cmd/worker/main.go Normal file
View File

@ -0,0 +1,66 @@
package main
import (
"context"
"fmt"
"github.com/RichardKnop/machinery/v1"
"github.com/RichardKnop/machinery/v1/config"
"github.com/google/uuid"
"github.com/jmoiron/sqlx"
"github.com/jordanknott/project-citadel/api/pg"
_ "github.com/lib/pq"
)
type MachineTasks struct {
Repository pg.Repository
}
func (m *MachineTasks) UserRegistration(userID string) (bool, error) {
ctx := context.Background()
uid, err := uuid.Parse(userID)
if err != nil {
return false, err
}
user, err := m.Repository.GetUserAccountByID(ctx, uid)
if err != nil {
return false, err
}
if user.Username == "jordan" {
return true, nil
}
return false, nil
}
func main() {
var cnf = &config.Config{
Broker: "amqp://guest:guest@localhost:5672/",
DefaultQueue: "machinery_tasks",
ResultBackend: "memcache://localhost:11211",
AMQP: &config.AMQPConfig{
Exchange: "machinery_exchange",
ExchangeType: "direct",
BindingKey: "machinery_task",
},
}
fmt.Println("starting server")
server, err := machinery.NewServer(cnf)
if err != nil {
// do something with the error
}
db, err := sqlx.Connect("postgres", "user=postgres password=test host=0.0.0.0 dbname=citadel sslmode=disable")
repo := pg.NewRepository(db)
tasks := MachineTasks{repo}
server.RegisterTasks(map[string]interface{}{
"userRegistration": tasks.UserRegistration,
})
worker := server.NewWorker("citadel_worker", 10)
fmt.Println("launching worker")
err = worker.Launch()
if err != nil {
// do something with the error
}
}

5
api/conf/app.toml Normal file
View File

@ -0,0 +1,5 @@
[database]
host = '0.0.0.0'
name = 'citadel'
user = 'postgres'
password = 'test'

74
api/data/dark_colors.toml Normal file
View File

@ -0,0 +1,74 @@
[[color]]
name = "red"
color = '#e8384f'
position = 1
[[color]]
name = "orange"
color = '#fd612c'
position = 2
[[color]]
name = "yellow_orange"
color = '#fd9a00'
position = 3
[[color]]
name = "yellow"
color = '#eec300'
position = 4
[[color]]
name = "yellow_green"
color = '#a4cf30'
position = 5
[[color]]
name = "green"
color = '#62d26f'
position = 6
[[color]]
name = "blue_green"
color = '#37c5ab'
position = 7
[[color]]
name = "aqua"
color = '#20aaea'
position = 8
[[color]]
name = "blue"
color = '#4186e0'
position = 9
[[color]]
name = "indigo"
color = '#7a6ff0'
position = 10
[[color]]
name = "purple"
color = '#aa62e3'
position = 11
[[color]]
name = "magenta"
color = '#e362e3'
position = 12
[[color]]
name = "hot_pink"
color = '#ea4e9d'
position = 13
[[color]]
name = "pink"
color = '#fc91ad'
position = 14
[[color]]
name = "cool_gray"
color = '#8da3a6'
position = 15

16
api/data/dark_colors.yml Normal file
View File

@ -0,0 +1,16 @@
colors:
red: '#e8384f'
orange: '#fd612c'
yellow_orange: '#fd9a00'
yellow: '#eec300'
yellow_green: '#a4cf30'
green: '#62d26f'
blue_green: '#37c5ab'
aqua: '#20aaea'
blue: '#4186e0'
indigo: '#7a6ff0'
purple: '#aa62e3'
magenta: '#e362e3'
hot_pink: '#ea4e9d'
pink: '#fc91ad'
cool_gray: '#8da3a6'

View File

@ -6,3 +6,14 @@ services:
ports:
- 1025:1025
- 8025:8025
broker:
image: rabbitmq:3-management
restart: always
ports:
- 8060:15672
- 5672:5672
result_store:
image: memcached:1.6-alpine
restart: always
ports:
- 11211:11211

View File

@ -5,25 +5,28 @@ go 1.13
require (
github.com/99designs/gqlgen v0.11.3
github.com/BurntSushi/toml v0.3.1
github.com/RichardKnop/machinery v1.8.6
github.com/boyter/scc v2.12.0+incompatible // indirect
github.com/dbaggerman/cuba v0.3.2 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/go-chi/chi v3.3.2+incompatible
github.com/go-chi/cors v1.0.0
github.com/golang-migrate/migrate/v4 v4.11.0
github.com/google/martian v2.1.0+incompatible
github.com/google/uuid v1.1.1
github.com/jmoiron/sqlx v1.2.0
github.com/jordan-wright/email v0.0.0-20200602115436-fd8a7622303e
github.com/lib/pq v1.0.0
github.com/lib/pq v1.3.0
github.com/magefile/mage v1.9.0
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200525100937-58356a36e03f // indirect
github.com/pelletier/go-toml v1.8.0
github.com/pkg/errors v0.8.1
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.4.2
github.com/spf13/cobra v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/urfave/cli v1.20.0 // indirect
github.com/streadway/amqp v1.0.0
github.com/vektah/gqlparser/v2 v2.0.1
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073
golang.org/x/text v0.3.3 // indirect
)

View File

@ -1,70 +1,217 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0 h1:MZQCQQaRwOrAcuKjiHWHrgKykt4fZyuwF2dtiG3fGW8=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0 h1:Lpy6hKgdcl7a3WGSfJIFmxmcdjSpP6OmBEfcOv1Y680=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/spanner v1.2.0/go.mod h1:LfwGAsK42Yz8IeLsd/oagGFBqTXt3xVWtm8/KD2vrEI=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/99designs/gqlgen v0.11.1 h1:QoSL8/AAJ2T3UOeQbdnBR32JcG4pO08+P/g5jdbFkUg=
github.com/99designs/gqlgen v0.11.1/go.mod h1:vjFOyBZ7NwDl+GdSD4PFn7BQn5Fy7ohJwXn7Vk8zz+c=
github.com/99designs/gqlgen v0.11.3 h1:oFSxl1DFS9X///uHV3y6CEfpcXWrDUxVblR4Xib2bs4=
github.com/99designs/gqlgen v0.11.3/go.mod h1:RgX5GRRdDWNkh4pBrdzNpNPFVsdoUFY2+adM6nb1N+4=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/ClickHouse/clickhouse-go v1.3.12/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/RichardKnop/logging v0.0.0-20190827224416-1a693bdd4fae h1:DcFpTQBYQ9Ct2d6sC7ol0/ynxc2pO1cpGUM+f4t5adg=
github.com/RichardKnop/logging v0.0.0-20190827224416-1a693bdd4fae/go.mod h1:rJJ84PyA/Wlmw1hO+xTzV2wsSUon6J5ktg0g8BF2PuU=
github.com/RichardKnop/machinery v1.8.6 h1:IPdPeO/yVE32isMJME2hiCgJgNibCVLjhshyjrQIDFY=
github.com/RichardKnop/machinery v1.8.6/go.mod h1:W87mnh7t91WdrwGbdnAjvDzqD/bqBV+0+GF276gv/bU=
github.com/RichardKnop/redsync v1.2.0 h1:gK35hR3zZkQigHKm8wOGb9MpJ9BsrW6MzxezwjTcHP0=
github.com/RichardKnop/redsync v1.2.0/go.mod h1:9b8nBGAX3bE2uCfJGSnsDvF23mKyHTZzmvmj5FH3Tp0=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0=
github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.29.15 h1:0ms/213murpsujhsnxnNKNeVouW60aJqSd992Ks3mxs=
github.com/aws/aws-sdk-go v1.29.15/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/boyter/scc v2.12.0+incompatible h1:Sxhi1Ry3gdreoaF0xEITQSdjR+pJT0cXHXhlDw5FHT0=
github.com/boyter/scc v2.12.0+incompatible/go.mod h1:VB5w4e0dahmIiKnpZ7LRh/sjauoY0BmCWjIzZcShNY0=
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/cockroach-go v0.0.0-20190925194419-606b3d062051/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk=
github.com/containerd/containerd v1.3.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dbaggerman/cuba v0.3.2 h1:6ZbQX3FNvkocR222YyoAIZ8wi4avrb7JcJkPvVI2AS4=
github.com/dbaggerman/cuba v0.3.2/go.mod h1:t9Oo05XRZGcjaVqsA/gFeNAOm7DYZYNhI17unI5FlwY=
github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/dhui/dktest v0.3.2/go.mod h1:l1/ib23a/CmxAe7yixtrYPc8Iy90Zy2udyaHINM5p58=
github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v1.4.2-0.20200213202729-31a86c4ab209/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-chi/chi v3.3.2+incompatible h1:uQNcQN3NsV1j4ANsPh42P4ew4t6rnRbJb8frvpp31qQ=
github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/cors v1.0.0 h1:e6x8k7uWbUwYs+aXDoiUzeQFT6l0cygBYyNhD7/1Tg0=
github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-redis/redis v6.15.7+incompatible h1:3skhDh95XQMpnqeqNftPkQD9jL9e5e36z/1SUm6dy1U=
github.com/go-redis/redis v6.15.7+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs=
github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28=
github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo=
github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk=
github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw=
github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg=
github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE=
github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8=
github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0=
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang-migrate/migrate v3.5.4+incompatible h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA=
github.com/golang-migrate/migrate/v4 v4.11.0 h1:uqtd0ysK5WyBQ/T1K2uDIooJV0o2Obt6uPwP062DupQ=
github.com/golang-migrate/migrate/v4 v4.11.0/go.mod h1:nqbpDbckcYjsCD5I8q5+NI9Tkk7SVcmaF40Ax1eAWhg=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ=
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
@ -72,39 +219,102 @@ github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoA
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.3.2/go.mod h1:LvCquS3HbBKwgl7KbX9KyqEIumJAbm1UMcTvGaIf3bM=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jordan-wright/email v0.0.0-20200602115436-fd8a7622303e h1:OGunVjqY7y4U4laftpEHv+mvZBlr7UGimJXKEGQtg48=
github.com/jordan-wright/email v0.0.0-20200602115436-fd8a7622303e/go.mod h1:Fy2gCFfZhay8jplf/Csj6cyH/oshQTkLQYZbKkcV+SY=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.10.2 h1:Znfn6hXZAHaLPNnlqUYRrBSReFHYybslgv4PTiyz6P0=
github.com/klauspost/compress v1.10.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/magefile/mage v1.9.0 h1:t3AU2wNwehMCW97vuqQLtw6puppWXHO+O2MHo5a50XE=
github.com/magefile/mage v1.9.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
github.com/markbates/pkger v0.15.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI=
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007 h1:reVOUXwnhsYv/8UqjvhrMOu5CNT9UapHFLbQ2JcXsmg=
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g=
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
@ -115,44 +325,81 @@ github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQz
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/monochromegane/go-gitignore v0.0.0-20200525100937-58356a36e03f h1:0HN0GKijN4mr9SmYoW/Ni3ozuNeHiSxo2s7drhv7obY=
github.com/monochromegane/go-gitignore v0.0.0-20200525100937-58356a36e03f/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA=
github.com/neo4j-drivers/gobolt v1.7.4/go.mod h1:O9AUbip4Dgre+CD3p40dnMD4a4r52QBIfblg5k7CTbE=
github.com/neo4j/neo4j-go-driver v1.7.4/go.mod h1:aPO0vVr+WnhEJne+FgFjfsjzAnssPFLucHgGZ76Zb/U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw=
github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.3.0/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
@ -161,90 +408,319 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/streadway/amqp v0.0.0-20200108173154-1c71cc93ed71/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v1.0.0 h1:kuuDrUJFZL1QYL9hUNuCxNObNzB0bV/ZG5jV3RWAQgo=
github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8=
github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k=
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e h1:+w0Zm/9gaWpEAyDlU1eKOuk5twTjAjuevXqcJJw8hrg=
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
github.com/vektah/gqlparser/v2 v2.0.1 h1:xgl5abVnsd4hkN9rk65OJID9bfcLSMuTaTcZj777q1o=
github.com/vektah/gqlparser/v2 v2.0.1/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms=
github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0=
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.3.0 h1:ew6uUIeJOo+qdUUv7LxFCUhtWmVv7ZV/Xuy4FAUsw2E=
go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200213203834-85f925bdd4d0/go.mod h1:IX6Eufr4L0ErOUlzqX/aFlHqsiKZRbV42Kb69e9VsTE=
golang.org/x/exp v0.0.0-20200228211341-fcea875c7e85/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589 h1:rjUrONFu4kLchcZTfp3/96bR8bW8dIa8uz3cR5n0cgM=
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200128002243-345141a36859/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200213224642-88e652f7a869/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200303165918-5bcca83a7881/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0 h1:GwFK8+l5/gdsOYKz5p6M4UK+QT8OvmHWZPJCnf+5DjA=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200303153909-beee998c1893 h1:OTjq5CN+5TpMIvzqxSFCjbBX3jNKjX0XOPi4SdBxQU8=
google.golang.org/genproto v0.0.0-20200303153909-beee998c1893/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
modernc.org/b v1.0.0/go.mod h1:uZWcZfRj1BpYzfN9JTerzlNUnnPsV9O2ZA8JsRcubNg=
modernc.org/db v1.0.0/go.mod h1:kYD/cO29L/29RM0hXYl4i3+Q5VojL31kTUVpVJDw0s8=
modernc.org/file v1.0.0/go.mod h1:uqEokAEn1u6e+J45e54dsEA/pw4o7zLrA2GwyntZzjw=
modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8=
modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM=
modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8=
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY=
modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k=
modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k=

View File

@ -1,10 +1,10 @@
# Where are all the schema files located? globs are supported eg src/**/*.graphqls
schema:
- graph/*.graphqls
- internal/graph/schema.graphqls
# Where should the generated server code go?
exec:
filename: graph/generated.go
filename: internal/graph/generated.go
package: graph
# Uncomment to enable federation
@ -14,7 +14,7 @@ exec:
# Where should any generated models go?
model:
filename: graph/models_gen.go
filename: internal/graph/models_gen.go
package: graph
# Where should the resolver implementations go?
@ -29,7 +29,7 @@ omit_slice_element_pointers: true
# gqlgen will search for any type names in the schema in these go packages
# if they match it will use them, otherwise it will generate them.
autobind:
- "github.com/jordanknott/project-citadel/api/pg"
- "github.com/jordanknott/project-citadel/api/internal/db"
# This section declares type mapping between the GraphQL and go type systems
#
@ -38,10 +38,10 @@ autobind:
# your liking
models:
ID:
model: github.com/jordanknott/project-citadel/api/graph.UUID
model: github.com/jordanknott/project-citadel/api/internal/graph.UUID
Int:
model:
- github.com/99designs/gqlgen/graphql.Int
UUID:
model: github.com/jordanknott/project-citadel/api/graph.UUID
model: github.com/jordanknott/project-citadel/api/internal/graph.UUID

View File

@ -1,4 +1,4 @@
package router
package auth
import (
"time"
@ -7,6 +7,30 @@ import (
log "github.com/sirupsen/logrus"
)
var jwtKey = []byte("citadel_test_key")
type AccessTokenClaims struct {
UserID string `json:"userId"`
jwt.StandardClaims
}
type RefreshTokenClaims struct {
UserID string `json:"userId"`
jwt.StandardClaims
}
type ErrExpiredToken struct{}
func (r *ErrExpiredToken) Error() string {
return "token is expired"
}
type ErrMalformedToken struct{}
func (r *ErrMalformedToken) Error() string {
return "token is malformed"
}
func NewAccessToken(userID string) (string, error) {
accessExpirationTime := time.Now().Add(5 * time.Second)
accessClaims := &AccessTokenClaims{
@ -51,7 +75,7 @@ func ValidateAccessToken(accessTokenString string) (AccessTokenClaims, error) {
log.WithFields(log.Fields{
"token": accessTokenString,
"timeToExpire": time.Unix(accessClaims.ExpiresAt, 0),
}).Info("token is valid")
}).Debug("token is valid")
return *accessClaims, nil
}

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
package pg
package db
import (
"context"

View File

@ -1,7 +1,7 @@
// Code generated by sqlc. DO NOT EDIT.
// source: label_color.sql
package pg
package db
import (
"context"

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
package pg
package db
import (
"database/sql"
@ -38,6 +38,14 @@ type ProjectLabel struct {
Name sql.NullString `json:"name"`
}
type ProjectMember struct {
ProjectMemberID uuid.UUID `json:"project_member_id"`
ProjectID uuid.UUID `json:"project_id"`
UserID uuid.UUID `json:"user_id"`
AddedAt time.Time `json:"added_at"`
RoleCode string `json:"role_code"`
}
type RefreshToken struct {
TokenID uuid.UUID `json:"token_id"`
UserID uuid.UUID `json:"user_id"`
@ -45,6 +53,11 @@ type RefreshToken struct {
ExpiresAt time.Time `json:"expires_at"`
}
type Role struct {
Code string `json:"code"`
Name string `json:"name"`
}
type Task struct {
TaskID uuid.UUID `json:"task_id"`
TaskGroupID uuid.UUID `json:"task_group_id"`
@ -101,6 +114,7 @@ type Team struct {
CreatedAt time.Time `json:"created_at"`
Name string `json:"name"`
OrganizationID uuid.UUID `json:"organization_id"`
Owner uuid.UUID `json:"owner"`
}
type TeamMember struct {
@ -108,6 +122,7 @@ type TeamMember struct {
TeamID uuid.UUID `json:"team_id"`
UserID uuid.UUID `json:"user_id"`
Addeddate time.Time `json:"addeddate"`
RoleCode string `json:"role_code"`
}
type UserAccount struct {
@ -120,4 +135,5 @@ type UserAccount struct {
FullName string `json:"full_name"`
Initials string `json:"initials"`
ProfileAvatarUrl sql.NullString `json:"profile_avatar_url"`
RoleCode string `json:"role_code"`
}

View File

@ -1,7 +1,7 @@
// Code generated by sqlc. DO NOT EDIT.
// source: organization.sql
package pg
package db
import (
"context"

View File

@ -0,0 +1,294 @@
// Code generated by sqlc. DO NOT EDIT.
// source: project.sql
package db
import (
"context"
"time"
"github.com/google/uuid"
)
const createProject = `-- name: CreateProject :one
INSERT INTO project(owner, team_id, created_at, name) VALUES ($1, $2, $3, $4) RETURNING project_id, team_id, created_at, name, owner
`
type CreateProjectParams struct {
Owner uuid.UUID `json:"owner"`
TeamID uuid.UUID `json:"team_id"`
CreatedAt time.Time `json:"created_at"`
Name string `json:"name"`
}
func (q *Queries) CreateProject(ctx context.Context, arg CreateProjectParams) (Project, error) {
row := q.db.QueryRowContext(ctx, createProject,
arg.Owner,
arg.TeamID,
arg.CreatedAt,
arg.Name,
)
var i Project
err := row.Scan(
&i.ProjectID,
&i.TeamID,
&i.CreatedAt,
&i.Name,
&i.Owner,
)
return i, err
}
const createProjectMember = `-- name: CreateProjectMember :one
INSERT INTO project_member (project_id, user_id, role_code, added_at) VALUES ($1, $2, $3, $4)
RETURNING project_member_id, project_id, user_id, added_at, role_code
`
type CreateProjectMemberParams struct {
ProjectID uuid.UUID `json:"project_id"`
UserID uuid.UUID `json:"user_id"`
RoleCode string `json:"role_code"`
AddedAt time.Time `json:"added_at"`
}
func (q *Queries) CreateProjectMember(ctx context.Context, arg CreateProjectMemberParams) (ProjectMember, error) {
row := q.db.QueryRowContext(ctx, createProjectMember,
arg.ProjectID,
arg.UserID,
arg.RoleCode,
arg.AddedAt,
)
var i ProjectMember
err := row.Scan(
&i.ProjectMemberID,
&i.ProjectID,
&i.UserID,
&i.AddedAt,
&i.RoleCode,
)
return i, err
}
const deleteProjectByID = `-- name: DeleteProjectByID :exec
DELETE FROM project WHERE project_id = $1
`
func (q *Queries) DeleteProjectByID(ctx context.Context, projectID uuid.UUID) error {
_, err := q.db.ExecContext(ctx, deleteProjectByID, projectID)
return err
}
const deleteProjectMember = `-- name: DeleteProjectMember :exec
DELETE FROM project_member WHERE user_id = $1 AND project_id = $2
`
type DeleteProjectMemberParams struct {
UserID uuid.UUID `json:"user_id"`
ProjectID uuid.UUID `json:"project_id"`
}
func (q *Queries) DeleteProjectMember(ctx context.Context, arg DeleteProjectMemberParams) error {
_, err := q.db.ExecContext(ctx, deleteProjectMember, arg.UserID, arg.ProjectID)
return err
}
const getAllProjects = `-- name: GetAllProjects :many
SELECT project_id, team_id, created_at, name, owner FROM project
`
func (q *Queries) GetAllProjects(ctx context.Context) ([]Project, error) {
rows, err := q.db.QueryContext(ctx, getAllProjects)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Project
for rows.Next() {
var i Project
if err := rows.Scan(
&i.ProjectID,
&i.TeamID,
&i.CreatedAt,
&i.Name,
&i.Owner,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getAllProjectsForTeam = `-- name: GetAllProjectsForTeam :many
SELECT project_id, team_id, created_at, name, owner FROM project WHERE team_id = $1
`
func (q *Queries) GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) ([]Project, error) {
rows, err := q.db.QueryContext(ctx, getAllProjectsForTeam, teamID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Project
for rows.Next() {
var i Project
if err := rows.Scan(
&i.ProjectID,
&i.TeamID,
&i.CreatedAt,
&i.Name,
&i.Owner,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getProjectByID = `-- name: GetProjectByID :one
SELECT project_id, team_id, created_at, name, owner FROM project WHERE project_id = $1
`
func (q *Queries) GetProjectByID(ctx context.Context, projectID uuid.UUID) (Project, error) {
row := q.db.QueryRowContext(ctx, getProjectByID, projectID)
var i Project
err := row.Scan(
&i.ProjectID,
&i.TeamID,
&i.CreatedAt,
&i.Name,
&i.Owner,
)
return i, err
}
const getProjectMembersForProjectID = `-- name: GetProjectMembersForProjectID :many
SELECT project_member_id, project_id, user_id, added_at, role_code FROM project_member WHERE project_id = $1
`
func (q *Queries) GetProjectMembersForProjectID(ctx context.Context, projectID uuid.UUID) ([]ProjectMember, error) {
rows, err := q.db.QueryContext(ctx, getProjectMembersForProjectID, projectID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ProjectMember
for rows.Next() {
var i ProjectMember
if err := rows.Scan(
&i.ProjectMemberID,
&i.ProjectID,
&i.UserID,
&i.AddedAt,
&i.RoleCode,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getRoleForProjectMemberByUserID = `-- name: GetRoleForProjectMemberByUserID :one
SELECT code, role.name FROM project_member INNER JOIN role ON role.code = project_member.role_code
WHERE user_id = $1 AND project_id = $2
`
type GetRoleForProjectMemberByUserIDParams struct {
UserID uuid.UUID `json:"user_id"`
ProjectID uuid.UUID `json:"project_id"`
}
func (q *Queries) GetRoleForProjectMemberByUserID(ctx context.Context, arg GetRoleForProjectMemberByUserIDParams) (Role, error) {
row := q.db.QueryRowContext(ctx, getRoleForProjectMemberByUserID, arg.UserID, arg.ProjectID)
var i Role
err := row.Scan(&i.Code, &i.Name)
return i, err
}
const setProjectOwner = `-- name: SetProjectOwner :one
UPDATE project SET owner = $2 WHERE project_id = $1 RETURNING project_id, team_id, created_at, name, owner
`
type SetProjectOwnerParams struct {
ProjectID uuid.UUID `json:"project_id"`
Owner uuid.UUID `json:"owner"`
}
func (q *Queries) SetProjectOwner(ctx context.Context, arg SetProjectOwnerParams) (Project, error) {
row := q.db.QueryRowContext(ctx, setProjectOwner, arg.ProjectID, arg.Owner)
var i Project
err := row.Scan(
&i.ProjectID,
&i.TeamID,
&i.CreatedAt,
&i.Name,
&i.Owner,
)
return i, err
}
const updateProjectMemberRole = `-- name: UpdateProjectMemberRole :one
UPDATE project_member SET role_code = $3 WHERE project_id = $1 AND user_id = $2
RETURNING project_member_id, project_id, user_id, added_at, role_code
`
type UpdateProjectMemberRoleParams struct {
ProjectID uuid.UUID `json:"project_id"`
UserID uuid.UUID `json:"user_id"`
RoleCode string `json:"role_code"`
}
func (q *Queries) UpdateProjectMemberRole(ctx context.Context, arg UpdateProjectMemberRoleParams) (ProjectMember, error) {
row := q.db.QueryRowContext(ctx, updateProjectMemberRole, arg.ProjectID, arg.UserID, arg.RoleCode)
var i ProjectMember
err := row.Scan(
&i.ProjectMemberID,
&i.ProjectID,
&i.UserID,
&i.AddedAt,
&i.RoleCode,
)
return i, err
}
const updateProjectNameByID = `-- name: UpdateProjectNameByID :one
UPDATE project SET name = $2 WHERE project_id = $1 RETURNING project_id, team_id, created_at, name, owner
`
type UpdateProjectNameByIDParams struct {
ProjectID uuid.UUID `json:"project_id"`
Name string `json:"name"`
}
func (q *Queries) UpdateProjectNameByID(ctx context.Context, arg UpdateProjectNameByIDParams) (Project, error) {
row := q.db.QueryRowContext(ctx, updateProjectNameByID, arg.ProjectID, arg.Name)
var i Project
err := row.Scan(
&i.ProjectID,
&i.TeamID,
&i.CreatedAt,
&i.Name,
&i.Owner,
)
return i, err
}

View File

@ -1,7 +1,7 @@
// Code generated by sqlc. DO NOT EDIT.
// source: project_label.sql
package pg
package db
import (
"context"

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
package pg
package db
import (
"context"
@ -13,6 +13,7 @@ type Querier interface {
CreateOrganization(ctx context.Context, arg CreateOrganizationParams) (Organization, error)
CreateProject(ctx context.Context, arg CreateProjectParams) (Project, error)
CreateProjectLabel(ctx context.Context, arg CreateProjectLabelParams) (ProjectLabel, error)
CreateProjectMember(ctx context.Context, arg CreateProjectMemberParams) (ProjectMember, error)
CreateRefreshToken(ctx context.Context, arg CreateRefreshTokenParams) (RefreshToken, error)
CreateTask(ctx context.Context, arg CreateTaskParams) (Task, error)
CreateTaskAssigned(ctx context.Context, arg CreateTaskAssignedParams) (TaskAssigned, error)
@ -26,6 +27,7 @@ type Querier interface {
DeleteExpiredTokens(ctx context.Context) error
DeleteProjectByID(ctx context.Context, projectID uuid.UUID) error
DeleteProjectLabelByID(ctx context.Context, projectLabelID uuid.UUID) error
DeleteProjectMember(ctx context.Context, arg DeleteProjectMemberParams) error
DeleteRefreshTokenByID(ctx context.Context, tokenID uuid.UUID) error
DeleteRefreshTokenByUserID(ctx context.Context, userID uuid.UUID) error
DeleteTaskAssignedByID(ctx context.Context, arg DeleteTaskAssignedByIDParams) (TaskAssigned, error)
@ -37,7 +39,7 @@ type Querier interface {
DeleteTaskLabelForTaskByProjectLabelID(ctx context.Context, arg DeleteTaskLabelForTaskByProjectLabelIDParams) error
DeleteTasksByTaskGroupID(ctx context.Context, taskGroupID uuid.UUID) (int64, error)
DeleteTeamByID(ctx context.Context, teamID uuid.UUID) error
DeleteTeamMemberByUserID(ctx context.Context, userID uuid.UUID) error
DeleteTeamMember(ctx context.Context, arg DeleteTeamMemberParams) error
DeleteUserAccountByID(ctx context.Context, userID uuid.UUID) error
GetAllOrganizations(ctx context.Context) ([]Organization, error)
GetAllProjects(ctx context.Context) ([]Project, error)
@ -50,9 +52,14 @@ type Querier interface {
GetLabelColorByID(ctx context.Context, labelColorID uuid.UUID) (LabelColor, error)
GetLabelColors(ctx context.Context) ([]LabelColor, error)
GetProjectByID(ctx context.Context, projectID uuid.UUID) (Project, error)
GetProjectIDForTask(ctx context.Context, taskID uuid.UUID) (uuid.UUID, error)
GetProjectLabelByID(ctx context.Context, projectLabelID uuid.UUID) (ProjectLabel, error)
GetProjectLabelsForProject(ctx context.Context, projectID uuid.UUID) ([]ProjectLabel, error)
GetProjectMembersForProjectID(ctx context.Context, projectID uuid.UUID) ([]ProjectMember, error)
GetRefreshTokenByID(ctx context.Context, tokenID uuid.UUID) (RefreshToken, error)
GetRoleForProjectMemberByUserID(ctx context.Context, arg GetRoleForProjectMemberByUserIDParams) (Role, error)
GetRoleForTeamMember(ctx context.Context, arg GetRoleForTeamMemberParams) (Role, error)
GetRoleForUserID(ctx context.Context, userID uuid.UUID) (GetRoleForUserIDRow, error)
GetTaskByID(ctx context.Context, taskID uuid.UUID) (Task, error)
GetTaskChecklistByID(ctx context.Context, taskChecklistID uuid.UUID) (TaskChecklist, error)
GetTaskChecklistItemByID(ctx context.Context, taskChecklistItemID uuid.UUID) (TaskChecklistItem, error)
@ -65,16 +72,20 @@ type Querier interface {
GetTaskLabelsForTaskID(ctx context.Context, taskID uuid.UUID) ([]TaskLabel, error)
GetTasksForTaskGroupID(ctx context.Context, taskGroupID uuid.UUID) ([]Task, error)
GetTeamByID(ctx context.Context, teamID uuid.UUID) (Team, error)
GetTeamMemberByID(ctx context.Context, arg GetTeamMemberByIDParams) (TeamMember, error)
GetTeamMembersForTeamID(ctx context.Context, teamID uuid.UUID) ([]TeamMember, error)
GetTeamsForOrganization(ctx context.Context, organizationID uuid.UUID) ([]Team, error)
GetUserAccountByID(ctx context.Context, userID uuid.UUID) (UserAccount, error)
GetUserAccountByUsername(ctx context.Context, username string) (UserAccount, error)
SetProjectOwner(ctx context.Context, arg SetProjectOwnerParams) (Project, error)
SetTaskChecklistItemComplete(ctx context.Context, arg SetTaskChecklistItemCompleteParams) (TaskChecklistItem, error)
SetTaskComplete(ctx context.Context, arg SetTaskCompleteParams) (Task, error)
SetTaskGroupName(ctx context.Context, arg SetTaskGroupNameParams) (TaskGroup, error)
SetTeamOwner(ctx context.Context, arg SetTeamOwnerParams) (Team, error)
UpdateProjectLabel(ctx context.Context, arg UpdateProjectLabelParams) (ProjectLabel, error)
UpdateProjectLabelColor(ctx context.Context, arg UpdateProjectLabelColorParams) (ProjectLabel, error)
UpdateProjectLabelName(ctx context.Context, arg UpdateProjectLabelNameParams) (ProjectLabel, error)
UpdateProjectMemberRole(ctx context.Context, arg UpdateProjectMemberRoleParams) (ProjectMember, error)
UpdateProjectNameByID(ctx context.Context, arg UpdateProjectNameByIDParams) (Project, error)
UpdateTaskChecklistItemName(ctx context.Context, arg UpdateTaskChecklistItemNameParams) (TaskChecklistItem, error)
UpdateTaskChecklistName(ctx context.Context, arg UpdateTaskChecklistNameParams) (TaskChecklist, error)
@ -83,6 +94,7 @@ type Querier interface {
UpdateTaskGroupLocation(ctx context.Context, arg UpdateTaskGroupLocationParams) (TaskGroup, error)
UpdateTaskLocation(ctx context.Context, arg UpdateTaskLocationParams) (Task, error)
UpdateTaskName(ctx context.Context, arg UpdateTaskNameParams) (Task, error)
UpdateTeamMemberRole(ctx context.Context, arg UpdateTeamMemberRoleParams) (TeamMember, error)
UpdateUserAccountProfileAvatarURL(ctx context.Context, arg UpdateUserAccountProfileAvatarURLParams) (UserAccount, error)
}

View File

@ -0,0 +1,41 @@
-- name: GetAllProjects :many
SELECT * FROM project;
-- name: GetAllProjectsForTeam :many
SELECT * FROM project WHERE team_id = $1;
-- name: GetProjectByID :one
SELECT * FROM project WHERE project_id = $1;
-- name: CreateProject :one
INSERT INTO project(owner, team_id, created_at, name) VALUES ($1, $2, $3, $4) RETURNING *;
-- name: SetProjectOwner :one
UPDATE project SET owner = $2 WHERE project_id = $1 RETURNING *;
-- name: UpdateProjectNameByID :one
UPDATE project SET name = $2 WHERE project_id = $1 RETURNING *;
-- name: DeleteProjectByID :exec
DELETE FROM project WHERE project_id = $1;
-- name: GetProjectMembersForProjectID :many
SELECT * FROM project_member WHERE project_id = $1;
-- name: GetRoleForProjectMemberByUserID :one
SELECT code, role.name FROM project_member INNER JOIN role ON role.code = project_member.role_code
WHERE user_id = $1 AND project_id = $2;
-- name: CreateProjectMember :one
INSERT INTO project_member (project_id, user_id, role_code, added_at) VALUES ($1, $2, $3, $4)
RETURNING *;
-- name: DeleteProjectMember :exec
DELETE FROM project_member WHERE user_id = $1 AND project_id = $2;
-- name: UpdateProjectMemberRole :one
UPDATE project_member SET role_code = $3 WHERE project_id = $1 AND user_id = $2
RETURNING *;

View File

@ -31,3 +31,8 @@ UPDATE task SET due_date = $2 WHERE task_id = $1 RETURNING *;
-- name: SetTaskComplete :one
UPDATE task SET complete = $2 WHERE task_id = $1 RETURNING *;
-- name: GetProjectIDForTask :one
SELECT project_id FROM task
INNER JOIN task_group ON task_group.task_group_id = task.task_group_id
WHERE task_id = $1;

View File

@ -5,10 +5,13 @@ SELECT * FROM team;
SELECT * FROM team WHERE team_id = $1;
-- name: CreateTeam :one
INSERT INTO team (organization_id, created_at, name) VALUES ($1, $2, $3) RETURNING *;
INSERT INTO team (organization_id, created_at, name, owner) VALUES ($1, $2, $3, $4) RETURNING *;
-- name: DeleteTeamByID :exec
DELETE FROM team WHERE team_id = $1;
-- name: GetTeamsForOrganization :many
SELECT * FROM team WHERE organization_id = $1;
-- name: SetTeamOwner :one
UPDATE team SET owner = $2 WHERE team_id = $1 RETURNING *;

View File

@ -0,0 +1,21 @@
-- name: CreateTeamMember :one
INSERT INTO team_member (team_id, user_id, addedDate, role_code) VALUES ($1, $2, $3, $4)
RETURNING *;
-- name: GetTeamMembersForTeamID :many
SELECT * FROM team_member WHERE team_id = $1;
-- name: DeleteTeamMember :exec
DELETE FROM team_member WHERE user_id = $1 AND team_id = $2;
-- name: GetRoleForTeamMember :one
SELECT code, role.name FROM team_member
INNER JOIN role ON role.code = team_member.role_code
WHERE user_id = $1 AND team_id = $2;
-- name: UpdateTeamMemberRole :one
UPDATE team_member SET role_code = $3 WHERE user_id = $2 AND team_id = $1
RETURNING *;
-- name: GetTeamMemberByID :one
SELECT * FROM team_member WHERE team_id = $1 AND user_id = $2;

View File

@ -8,8 +8,8 @@ SELECT * FROM user_account;
SELECT * FROM user_account WHERE username = $1;
-- name: CreateUserAccount :one
INSERT INTO user_account(full_name, initials, email, username, created_at, password_hash)
VALUES ($1, $2, $3, $4, $5, $6) RETURNING *;
INSERT INTO user_account(full_name, initials, email, username, created_at, password_hash, role_code)
VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *;
-- name: UpdateUserAccountProfileAvatarURL :one
UPDATE user_account SET profile_avatar_url = $2 WHERE user_id = $1
@ -17,3 +17,8 @@ UPDATE user_account SET profile_avatar_url = $2 WHERE user_id = $1
-- name: DeleteUserAccountByID :exec
DELETE FROM user_account WHERE user_id = $1;
-- name: GetRoleForUserID :one
SELECT username, role.code, role.name FROM user_account
INNER JOIN role ON role.code = user_account.role_code
WHERE user_id = $1;

View File

@ -0,0 +1,18 @@
package db
import (
"github.com/jmoiron/sqlx"
)
type Repository struct {
*Queries
db *sqlx.DB
}
// NewRepository returns an implementation of the Repository interface.
func NewRepository(db *sqlx.DB) *Repository {
return &Repository{
Queries: New(db.DB),
db: db,
}
}

View File

@ -1,7 +1,7 @@
// Code generated by sqlc. DO NOT EDIT.
// source: task.sql
package pg
package db
import (
"context"
@ -101,6 +101,19 @@ func (q *Queries) GetAllTasks(ctx context.Context) ([]Task, error) {
return items, nil
}
const getProjectIDForTask = `-- name: GetProjectIDForTask :one
SELECT project_id FROM task
INNER JOIN task_group ON task_group.task_group_id = task.task_group_id
WHERE task_id = $1
`
func (q *Queries) GetProjectIDForTask(ctx context.Context, taskID uuid.UUID) (uuid.UUID, error) {
row := q.db.QueryRowContext(ctx, getProjectIDForTask, taskID)
var project_id uuid.UUID
err := row.Scan(&project_id)
return project_id, err
}
const getTaskByID = `-- name: GetTaskByID :one
SELECT task_id, task_group_id, created_at, name, position, description, due_date, complete FROM task WHERE task_id = $1
`

View File

@ -1,7 +1,7 @@
// Code generated by sqlc. DO NOT EDIT.
// source: task_assigned.sql
package pg
package db
import (
"context"

View File

@ -1,7 +1,7 @@
// Code generated by sqlc. DO NOT EDIT.
// source: task_checklist.sql
package pg
package db
import (
"context"

View File

@ -1,7 +1,7 @@
// Code generated by sqlc. DO NOT EDIT.
// source: task_group.sql
package pg
package db
import (
"context"

View File

@ -1,7 +1,7 @@
// Code generated by sqlc. DO NOT EDIT.
// source: task_label.sql
package pg
package db
import (
"context"

View File

@ -1,7 +1,7 @@
// Code generated by sqlc. DO NOT EDIT.
// source: team.sql
package pg
package db
import (
"context"
@ -11,23 +11,30 @@ import (
)
const createTeam = `-- name: CreateTeam :one
INSERT INTO team (organization_id, created_at, name) VALUES ($1, $2, $3) RETURNING team_id, created_at, name, organization_id
INSERT INTO team (organization_id, created_at, name, owner) VALUES ($1, $2, $3, $4) RETURNING team_id, created_at, name, organization_id, owner
`
type CreateTeamParams struct {
OrganizationID uuid.UUID `json:"organization_id"`
CreatedAt time.Time `json:"created_at"`
Name string `json:"name"`
Owner uuid.UUID `json:"owner"`
}
func (q *Queries) CreateTeam(ctx context.Context, arg CreateTeamParams) (Team, error) {
row := q.db.QueryRowContext(ctx, createTeam, arg.OrganizationID, arg.CreatedAt, arg.Name)
row := q.db.QueryRowContext(ctx, createTeam,
arg.OrganizationID,
arg.CreatedAt,
arg.Name,
arg.Owner,
)
var i Team
err := row.Scan(
&i.TeamID,
&i.CreatedAt,
&i.Name,
&i.OrganizationID,
&i.Owner,
)
return i, err
}
@ -42,7 +49,7 @@ func (q *Queries) DeleteTeamByID(ctx context.Context, teamID uuid.UUID) error {
}
const getAllTeams = `-- name: GetAllTeams :many
SELECT team_id, created_at, name, organization_id FROM team
SELECT team_id, created_at, name, organization_id, owner FROM team
`
func (q *Queries) GetAllTeams(ctx context.Context) ([]Team, error) {
@ -59,6 +66,7 @@ func (q *Queries) GetAllTeams(ctx context.Context) ([]Team, error) {
&i.CreatedAt,
&i.Name,
&i.OrganizationID,
&i.Owner,
); err != nil {
return nil, err
}
@ -74,7 +82,7 @@ func (q *Queries) GetAllTeams(ctx context.Context) ([]Team, error) {
}
const getTeamByID = `-- name: GetTeamByID :one
SELECT team_id, created_at, name, organization_id FROM team WHERE team_id = $1
SELECT team_id, created_at, name, organization_id, owner FROM team WHERE team_id = $1
`
func (q *Queries) GetTeamByID(ctx context.Context, teamID uuid.UUID) (Team, error) {
@ -85,12 +93,13 @@ func (q *Queries) GetTeamByID(ctx context.Context, teamID uuid.UUID) (Team, erro
&i.CreatedAt,
&i.Name,
&i.OrganizationID,
&i.Owner,
)
return i, err
}
const getTeamsForOrganization = `-- name: GetTeamsForOrganization :many
SELECT team_id, created_at, name, organization_id FROM team WHERE organization_id = $1
SELECT team_id, created_at, name, organization_id, owner FROM team WHERE organization_id = $1
`
func (q *Queries) GetTeamsForOrganization(ctx context.Context, organizationID uuid.UUID) ([]Team, error) {
@ -107,6 +116,7 @@ func (q *Queries) GetTeamsForOrganization(ctx context.Context, organizationID uu
&i.CreatedAt,
&i.Name,
&i.OrganizationID,
&i.Owner,
); err != nil {
return nil, err
}
@ -120,3 +130,25 @@ func (q *Queries) GetTeamsForOrganization(ctx context.Context, organizationID uu
}
return items, nil
}
const setTeamOwner = `-- name: SetTeamOwner :one
UPDATE team SET owner = $2 WHERE team_id = $1 RETURNING team_id, created_at, name, organization_id, owner
`
type SetTeamOwnerParams struct {
TeamID uuid.UUID `json:"team_id"`
Owner uuid.UUID `json:"owner"`
}
func (q *Queries) SetTeamOwner(ctx context.Context, arg SetTeamOwnerParams) (Team, error) {
row := q.db.QueryRowContext(ctx, setTeamOwner, arg.TeamID, arg.Owner)
var i Team
err := row.Scan(
&i.TeamID,
&i.CreatedAt,
&i.Name,
&i.OrganizationID,
&i.Owner,
)
return i, err
}

View File

@ -0,0 +1,152 @@
// Code generated by sqlc. DO NOT EDIT.
// source: team_member.sql
package db
import (
"context"
"time"
"github.com/google/uuid"
)
const createTeamMember = `-- name: CreateTeamMember :one
INSERT INTO team_member (team_id, user_id, addedDate, role_code) VALUES ($1, $2, $3, $4)
RETURNING team_member_id, team_id, user_id, addeddate, role_code
`
type CreateTeamMemberParams struct {
TeamID uuid.UUID `json:"team_id"`
UserID uuid.UUID `json:"user_id"`
Addeddate time.Time `json:"addeddate"`
RoleCode string `json:"role_code"`
}
func (q *Queries) CreateTeamMember(ctx context.Context, arg CreateTeamMemberParams) (TeamMember, error) {
row := q.db.QueryRowContext(ctx, createTeamMember,
arg.TeamID,
arg.UserID,
arg.Addeddate,
arg.RoleCode,
)
var i TeamMember
err := row.Scan(
&i.TeamMemberID,
&i.TeamID,
&i.UserID,
&i.Addeddate,
&i.RoleCode,
)
return i, err
}
const deleteTeamMember = `-- name: DeleteTeamMember :exec
DELETE FROM team_member WHERE user_id = $1 AND team_id = $2
`
type DeleteTeamMemberParams struct {
UserID uuid.UUID `json:"user_id"`
TeamID uuid.UUID `json:"team_id"`
}
func (q *Queries) DeleteTeamMember(ctx context.Context, arg DeleteTeamMemberParams) error {
_, err := q.db.ExecContext(ctx, deleteTeamMember, arg.UserID, arg.TeamID)
return err
}
const getRoleForTeamMember = `-- name: GetRoleForTeamMember :one
SELECT code, role.name FROM team_member
INNER JOIN role ON role.code = team_member.role_code
WHERE user_id = $1 AND team_id = $2
`
type GetRoleForTeamMemberParams struct {
UserID uuid.UUID `json:"user_id"`
TeamID uuid.UUID `json:"team_id"`
}
func (q *Queries) GetRoleForTeamMember(ctx context.Context, arg GetRoleForTeamMemberParams) (Role, error) {
row := q.db.QueryRowContext(ctx, getRoleForTeamMember, arg.UserID, arg.TeamID)
var i Role
err := row.Scan(&i.Code, &i.Name)
return i, err
}
const getTeamMemberByID = `-- name: GetTeamMemberByID :one
SELECT team_member_id, team_id, user_id, addeddate, role_code FROM team_member WHERE team_id = $1 AND user_id = $2
`
type GetTeamMemberByIDParams struct {
TeamID uuid.UUID `json:"team_id"`
UserID uuid.UUID `json:"user_id"`
}
func (q *Queries) GetTeamMemberByID(ctx context.Context, arg GetTeamMemberByIDParams) (TeamMember, error) {
row := q.db.QueryRowContext(ctx, getTeamMemberByID, arg.TeamID, arg.UserID)
var i TeamMember
err := row.Scan(
&i.TeamMemberID,
&i.TeamID,
&i.UserID,
&i.Addeddate,
&i.RoleCode,
)
return i, err
}
const getTeamMembersForTeamID = `-- name: GetTeamMembersForTeamID :many
SELECT team_member_id, team_id, user_id, addeddate, role_code FROM team_member WHERE team_id = $1
`
func (q *Queries) GetTeamMembersForTeamID(ctx context.Context, teamID uuid.UUID) ([]TeamMember, error) {
rows, err := q.db.QueryContext(ctx, getTeamMembersForTeamID, teamID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []TeamMember
for rows.Next() {
var i TeamMember
if err := rows.Scan(
&i.TeamMemberID,
&i.TeamID,
&i.UserID,
&i.Addeddate,
&i.RoleCode,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updateTeamMemberRole = `-- name: UpdateTeamMemberRole :one
UPDATE team_member SET role_code = $3 WHERE user_id = $2 AND team_id = $1
RETURNING team_member_id, team_id, user_id, addeddate, role_code
`
type UpdateTeamMemberRoleParams struct {
TeamID uuid.UUID `json:"team_id"`
UserID uuid.UUID `json:"user_id"`
RoleCode string `json:"role_code"`
}
func (q *Queries) UpdateTeamMemberRole(ctx context.Context, arg UpdateTeamMemberRoleParams) (TeamMember, error) {
row := q.db.QueryRowContext(ctx, updateTeamMemberRole, arg.TeamID, arg.UserID, arg.RoleCode)
var i TeamMember
err := row.Scan(
&i.TeamMemberID,
&i.TeamID,
&i.UserID,
&i.Addeddate,
&i.RoleCode,
)
return i, err
}

View File

@ -1,7 +1,7 @@
// Code generated by sqlc. DO NOT EDIT.
// source: token.sql
package pg
package db
import (
"context"

View File

@ -1,7 +1,7 @@
// Code generated by sqlc. DO NOT EDIT.
// source: user_accounts.sql
package pg
package db
import (
"context"
@ -12,8 +12,8 @@ import (
)
const createUserAccount = `-- name: CreateUserAccount :one
INSERT INTO user_account(full_name, initials, email, username, created_at, password_hash)
VALUES ($1, $2, $3, $4, $5, $6) RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url
INSERT INTO user_account(full_name, initials, email, username, created_at, password_hash, role_code)
VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code
`
type CreateUserAccountParams struct {
@ -23,6 +23,7 @@ type CreateUserAccountParams struct {
Username string `json:"username"`
CreatedAt time.Time `json:"created_at"`
PasswordHash string `json:"password_hash"`
RoleCode string `json:"role_code"`
}
func (q *Queries) CreateUserAccount(ctx context.Context, arg CreateUserAccountParams) (UserAccount, error) {
@ -33,6 +34,7 @@ func (q *Queries) CreateUserAccount(ctx context.Context, arg CreateUserAccountPa
arg.Username,
arg.CreatedAt,
arg.PasswordHash,
arg.RoleCode,
)
var i UserAccount
err := row.Scan(
@ -45,6 +47,7 @@ func (q *Queries) CreateUserAccount(ctx context.Context, arg CreateUserAccountPa
&i.FullName,
&i.Initials,
&i.ProfileAvatarUrl,
&i.RoleCode,
)
return i, err
}
@ -59,7 +62,7 @@ func (q *Queries) DeleteUserAccountByID(ctx context.Context, userID uuid.UUID) e
}
const getAllUserAccounts = `-- name: GetAllUserAccounts :many
SELECT user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url FROM user_account
SELECT user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code FROM user_account
`
func (q *Queries) GetAllUserAccounts(ctx context.Context) ([]UserAccount, error) {
@ -81,6 +84,7 @@ func (q *Queries) GetAllUserAccounts(ctx context.Context) ([]UserAccount, error)
&i.FullName,
&i.Initials,
&i.ProfileAvatarUrl,
&i.RoleCode,
); err != nil {
return nil, err
}
@ -95,8 +99,27 @@ func (q *Queries) GetAllUserAccounts(ctx context.Context) ([]UserAccount, error)
return items, nil
}
const getRoleForUserID = `-- name: GetRoleForUserID :one
SELECT username, role.code, role.name FROM user_account
INNER JOIN role ON role.code = user_account.role_code
WHERE user_id = $1
`
type GetRoleForUserIDRow struct {
Username string `json:"username"`
Code string `json:"code"`
Name string `json:"name"`
}
func (q *Queries) GetRoleForUserID(ctx context.Context, userID uuid.UUID) (GetRoleForUserIDRow, error) {
row := q.db.QueryRowContext(ctx, getRoleForUserID, userID)
var i GetRoleForUserIDRow
err := row.Scan(&i.Username, &i.Code, &i.Name)
return i, err
}
const getUserAccountByID = `-- name: GetUserAccountByID :one
SELECT user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url FROM user_account WHERE user_id = $1
SELECT user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code FROM user_account WHERE user_id = $1
`
func (q *Queries) GetUserAccountByID(ctx context.Context, userID uuid.UUID) (UserAccount, error) {
@ -112,12 +135,13 @@ func (q *Queries) GetUserAccountByID(ctx context.Context, userID uuid.UUID) (Use
&i.FullName,
&i.Initials,
&i.ProfileAvatarUrl,
&i.RoleCode,
)
return i, err
}
const getUserAccountByUsername = `-- name: GetUserAccountByUsername :one
SELECT user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url FROM user_account WHERE username = $1
SELECT user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code FROM user_account WHERE username = $1
`
func (q *Queries) GetUserAccountByUsername(ctx context.Context, username string) (UserAccount, error) {
@ -133,13 +157,14 @@ func (q *Queries) GetUserAccountByUsername(ctx context.Context, username string)
&i.FullName,
&i.Initials,
&i.ProfileAvatarUrl,
&i.RoleCode,
)
return i, err
}
const updateUserAccountProfileAvatarURL = `-- name: UpdateUserAccountProfileAvatarURL :one
UPDATE user_account SET profile_avatar_url = $2 WHERE user_id = $1
RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url
RETURNING user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code
`
type UpdateUserAccountProfileAvatarURLParams struct {
@ -160,6 +185,7 @@ func (q *Queries) UpdateUserAccountProfileAvatarURL(ctx context.Context, arg Upd
&i.FullName,
&i.Initials,
&i.ProfileAvatarUrl,
&i.RoleCode,
)
return i, err
}

File diff suppressed because it is too large Load Diff

View File

@ -12,11 +12,11 @@ import (
"github.com/99designs/gqlgen/graphql/handler/transport"
"github.com/99designs/gqlgen/graphql/playground"
"github.com/google/uuid"
"github.com/jordanknott/project-citadel/api/pg"
"github.com/jordanknott/project-citadel/api/internal/db"
)
// NewHandler returns a new graphql endpoint handler.
func NewHandler(repo pg.Repository) http.Handler {
func NewHandler(repo db.Repository) http.Handler {
srv := handler.New(NewExecutableSchema(Config{
Resolvers: &Resolver{
Repository: repo,

View File

@ -3,10 +3,13 @@
package graph
import (
"fmt"
"io"
"strconv"
"time"
"github.com/google/uuid"
"github.com/jordanknott/project-citadel/api/pg"
"github.com/jordanknott/project-citadel/api/internal/db"
)
type AddTaskLabelInput struct {
@ -24,6 +27,16 @@ type ChecklistBadge struct {
Total int `json:"total"`
}
type CreateProjectMember struct {
ProjectID uuid.UUID `json:"projectID"`
UserID uuid.UUID `json:"userID"`
}
type CreateProjectMemberPayload struct {
Ok bool `json:"ok"`
Member *Member `json:"member"`
}
type CreateTaskChecklist struct {
TaskID uuid.UUID `json:"taskID"`
Name string `json:"name"`
@ -42,8 +55,8 @@ type CreateTeamMember struct {
}
type CreateTeamMemberPayload struct {
Team *pg.Team `json:"team"`
TeamMember *ProjectMember `json:"teamMember"`
Team *db.Team `json:"team"`
TeamMember *Member `json:"teamMember"`
}
type DeleteProject struct {
@ -54,9 +67,20 @@ type DeleteProjectLabel struct {
ProjectLabelID uuid.UUID `json:"projectLabelID"`
}
type DeleteProjectMember struct {
ProjectID uuid.UUID `json:"projectID"`
UserID uuid.UUID `json:"userID"`
}
type DeleteProjectMemberPayload struct {
Ok bool `json:"ok"`
Member *Member `json:"member"`
ProjectID uuid.UUID `json:"projectID"`
}
type DeleteProjectPayload struct {
Ok bool `json:"ok"`
Project *pg.Project `json:"project"`
Project *db.Project `json:"project"`
}
type DeleteTaskChecklist struct {
@ -69,12 +93,12 @@ type DeleteTaskChecklistItem struct {
type DeleteTaskChecklistItemPayload struct {
Ok bool `json:"ok"`
TaskChecklistItem *pg.TaskChecklistItem `json:"taskChecklistItem"`
TaskChecklistItem *db.TaskChecklistItem `json:"taskChecklistItem"`
}
type DeleteTaskChecklistPayload struct {
Ok bool `json:"ok"`
TaskChecklist *pg.TaskChecklist `json:"taskChecklist"`
TaskChecklist *db.TaskChecklist `json:"taskChecklist"`
}
type DeleteTaskGroupInput struct {
@ -84,7 +108,7 @@ type DeleteTaskGroupInput struct {
type DeleteTaskGroupPayload struct {
Ok bool `json:"ok"`
AffectedRows int `json:"affectedRows"`
TaskGroup *pg.TaskGroup `json:"taskGroup"`
TaskGroup *db.TaskGroup `json:"taskGroup"`
}
type DeleteTaskInput struct {
@ -99,10 +123,20 @@ type DeleteTeam struct {
TeamID uuid.UUID `json:"teamID"`
}
type DeleteTeamMember struct {
TeamID uuid.UUID `json:"teamID"`
UserID uuid.UUID `json:"userID"`
}
type DeleteTeamMemberPayload struct {
TeamID uuid.UUID `json:"teamID"`
UserID uuid.UUID `json:"userID"`
}
type DeleteTeamPayload struct {
Ok bool `json:"ok"`
Team *pg.Team `json:"team"`
Projects []pg.Project `json:"projects"`
Team *db.Team `json:"team"`
Projects []db.Project `json:"projects"`
}
type DeleteUserAccount struct {
@ -111,7 +145,7 @@ type DeleteUserAccount struct {
type DeleteUserAccountPayload struct {
Ok bool `json:"ok"`
UserAccount *pg.UserAccount `json:"userAccount"`
UserAccount *db.UserAccount `json:"userAccount"`
}
type FindProject struct {
@ -134,6 +168,14 @@ type LogoutUser struct {
UserID string `json:"userID"`
}
type Member struct {
ID uuid.UUID `json:"id"`
Role *db.Role `json:"role"`
FullName string `json:"fullName"`
Username string `json:"username"`
ProfileIcon *ProfileIcon `json:"profileIcon"`
}
type NewProject struct {
UserID uuid.UUID `json:"userID"`
TeamID uuid.UUID `json:"teamID"`
@ -184,6 +226,7 @@ type NewUserAccount struct {
FullName string `json:"fullName"`
Initials string `json:"initials"`
Password string `json:"password"`
RoleCode string `json:"roleCode"`
}
type ProfileIcon struct {
@ -192,12 +235,6 @@ type ProfileIcon struct {
BgColor *string `json:"bgColor"`
}
type ProjectMember struct {
ID uuid.UUID `json:"id"`
FullName string `json:"fullName"`
ProfileIcon *ProfileIcon `json:"profileIcon"`
}
type ProjectsFilter struct {
TeamID *uuid.UUID `json:"teamID"`
}
@ -206,6 +243,17 @@ type RemoveTaskLabelInput struct {
TaskLabelID uuid.UUID `json:"taskLabelID"`
}
type SetProjectOwner struct {
ProjectID uuid.UUID `json:"projectID"`
OwnerID uuid.UUID `json:"ownerID"`
}
type SetProjectOwnerPayload struct {
Ok bool `json:"ok"`
PrevOwner *Member `json:"prevOwner"`
NewOwner *Member `json:"newOwner"`
}
type SetTaskChecklistItemComplete struct {
TaskChecklistItemID uuid.UUID `json:"taskChecklistItemID"`
Complete bool `json:"complete"`
@ -216,6 +264,17 @@ type SetTaskComplete struct {
Complete bool `json:"complete"`
}
type SetTeamOwner struct {
TeamID uuid.UUID `json:"teamID"`
UserID uuid.UUID `json:"userID"`
}
type SetTeamOwnerPayload struct {
Ok bool `json:"ok"`
PrevOwner *Member `json:"prevOwner"`
NewOwner *Member `json:"newOwner"`
}
type TaskBadges struct {
Checklist *ChecklistBadge `json:"checklist"`
}
@ -227,7 +286,7 @@ type ToggleTaskLabelInput struct {
type ToggleTaskLabelPayload struct {
Active bool `json:"active"`
Task *pg.Task `json:"task"`
Task *db.Task `json:"task"`
}
type UnassignTaskInput struct {
@ -251,6 +310,17 @@ type UpdateProjectLabelName struct {
Name string `json:"name"`
}
type UpdateProjectMemberRole struct {
ProjectID uuid.UUID `json:"projectID"`
UserID uuid.UUID `json:"userID"`
RoleCode RoleCode `json:"roleCode"`
}
type UpdateProjectMemberRolePayload struct {
Ok bool `json:"ok"`
Member *Member `json:"member"`
}
type UpdateProjectName struct {
ProjectID uuid.UUID `json:"projectID"`
Name string `json:"name"`
@ -283,10 +353,66 @@ type UpdateTaskGroupName struct {
type UpdateTaskLocationPayload struct {
PreviousTaskGroupID uuid.UUID `json:"previousTaskGroupID"`
Task *pg.Task `json:"task"`
Task *db.Task `json:"task"`
}
type UpdateTaskName struct {
TaskID string `json:"taskID"`
Name string `json:"name"`
}
type UpdateTeamMemberRole struct {
TeamID uuid.UUID `json:"teamID"`
UserID uuid.UUID `json:"userID"`
RoleCode RoleCode `json:"roleCode"`
}
type UpdateTeamMemberRolePayload struct {
Ok bool `json:"ok"`
Member *Member `json:"member"`
}
type RoleCode string
const (
RoleCodeOwner RoleCode = "owner"
RoleCodeAdmin RoleCode = "admin"
RoleCodeMember RoleCode = "member"
RoleCodeObserver RoleCode = "observer"
)
var AllRoleCode = []RoleCode{
RoleCodeOwner,
RoleCodeAdmin,
RoleCodeMember,
RoleCodeObserver,
}
func (e RoleCode) IsValid() bool {
switch e {
case RoleCodeOwner, RoleCodeAdmin, RoleCodeMember, RoleCodeObserver:
return true
}
return false
}
func (e RoleCode) String() string {
return string(e)
}
func (e *RoleCode) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = RoleCode(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid RoleCode", str)
}
return nil
}
func (e RoleCode) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}

View File

@ -1,13 +1,14 @@
//go:generate sh ../scripts/genSchema.sh
//go:generate go run github.com/99designs/gqlgen
package graph
import (
"sync"
"github.com/jordanknott/project-citadel/api/pg"
"github.com/jordanknott/project-citadel/api/internal/db"
)
type Resolver struct {
Repository pg.Repository
Repository db.Repository
mu sync.Mutex
}

View File

@ -2,6 +2,13 @@ scalar Time
scalar UUID
scalar Upload
enum RoleCode {
owner
admin
member
observer
}
type ProjectLabel {
id: ID!
createdDate: Time!
@ -28,9 +35,11 @@ type ProfileIcon {
bgColor: String
}
type ProjectMember {
type Member {
id: ID!
role: Role!
fullName: String!
username: String!
profileIcon: ProfileIcon!
}
@ -41,12 +50,18 @@ type RefreshToken {
createdAt: Time!
}
type Role {
code: String!
name: String!
}
type UserAccount {
id: ID!
email: String!
createdAt: Time!
fullName: String!
initials: String!
role: Role!
username: String!
profileIcon: ProfileIcon!
}
@ -55,7 +70,7 @@ type Team {
id: ID!
createdAt: Time!
name: String!
members: [ProjectMember!]!
members: [Member!]!
}
type Project {
@ -63,9 +78,9 @@ type Project {
createdAt: Time!
name: String!
team: Team!
owner: ProjectMember!
owner: Member!
taskGroups: [TaskGroup!]!
members: [ProjectMember!]!
members: [Member!]!
labels: [ProjectLabel!]!
}
@ -96,127 +111,17 @@ type Task {
description: String
dueDate: Time
complete: Boolean!
assigned: [ProjectMember!]!
assigned: [Member!]!
labels: [TaskLabel!]!
checklists: [TaskChecklist!]!
badges: TaskBadges!
}
input ProjectsFilter {
teamID: UUID
}
input FindUser {
userId: String!
}
input FindProject {
projectId: String!
}
input FindTask {
taskID: UUID!
}
type Organization {
id: ID!
name: String!
}
input FindTeam {
teamID: UUID!
}
type Query {
organizations: [Organization!]!
users: [UserAccount!]!
findUser(input: FindUser!): UserAccount!
findProject(input: FindProject!): Project!
findTask(input: FindTask!): Task!
projects(input: ProjectsFilter): [Project!]!
findTeam(input: FindTeam!): Team!
teams: [Team!]!
labelColors: [LabelColor!]!
taskGroups: [TaskGroup!]!
me: UserAccount!
}
input NewRefreshToken {
userId: String!
}
input NewUserAccount {
username: String!
email: String!
fullName: String!
initials: String!
password: String!
}
input NewTeam {
name: String!
organizationID: UUID!
}
input NewProject {
userID: UUID!
teamID: UUID!
name: String!
}
input NewTaskGroup {
projectID: String!
name: String!
position: Float!
}
input LogoutUser {
userID: String!
}
input NewTask {
taskGroupID: String!
name: String!
position: Float!
}
input NewTaskLocation {
taskID: UUID!
taskGroupID: UUID!
position: Float!
}
input DeleteTaskInput {
taskID: String!
}
type DeleteTaskPayload {
taskID: String!
}
input UpdateTaskName {
taskID: String!
name: String!
}
input NewTaskGroupLocation {
taskGroupID: UUID!
position: Float!
}
input DeleteTaskGroupInput {
taskGroupID: UUID!
}
type DeleteTaskGroupPayload {
ok: Boolean!
affectedRows: Int!
taskGroup: TaskGroup!
}
type DeleteTaskChecklistItemPayload {
ok: Boolean!
taskChecklistItem: TaskChecklistItem!
}
type TaskChecklistItem {
id: ID!
name: String!
@ -233,27 +138,75 @@ type TaskChecklist {
items: [TaskChecklistItem!]!
}
input AssignTaskInput {
type Query {
organizations: [Organization!]!
users: [UserAccount!]!
findUser(input: FindUser!): UserAccount!
findProject(input: FindProject!): Project!
findTask(input: FindTask!): Task!
projects(input: ProjectsFilter): [Project!]!
findTeam(input: FindTeam!): Team!
teams: [Team!]!
labelColors: [LabelColor!]!
taskGroups: [TaskGroup!]!
me: UserAccount!
}
type Mutation
input ProjectsFilter {
teamID: UUID
}
input FindUser {
userId: String!
}
input FindProject {
projectId: String!
}
input FindTask {
taskID: UUID!
}
input FindTeam {
teamID: UUID!
}
extend type Mutation {
createProject(input: NewProject!): Project!
deleteProject(input: DeleteProject!): DeleteProjectPayload!
updateProjectName(input: UpdateProjectName): Project!
}
input NewProject {
userID: UUID!
teamID: UUID!
name: String!
}
input UnassignTaskInput {
taskID: UUID!
userID: UUID!
}
input UpdateTaskDescriptionInput {
taskID: UUID!
description: String!
input UpdateProjectName {
projectID: UUID!
name: String!
}
input AddTaskLabelInput {
taskID: UUID!
projectLabelID: UUID!
input DeleteProject {
projectID: UUID!
}
input RemoveTaskLabelInput {
taskLabelID: UUID!
type DeleteProjectPayload {
ok: Boolean!
project: Project!
}
extend type Mutation {
createProjectLabel(input: NewProjectLabel!): ProjectLabel!
deleteProjectLabel(input: DeleteProjectLabel!): ProjectLabel!
updateProjectLabel(input: UpdateProjectLabel!): ProjectLabel!
updateProjectLabelName(input: UpdateProjectLabelName!): ProjectLabel!
updateProjectLabelColor(input: UpdateProjectLabelColor!): ProjectLabel!
}
input NewProjectLabel {
@ -282,19 +235,88 @@ input UpdateProjectLabelColor {
labelColorID: UUID!
}
input ToggleTaskLabelInput {
taskID: UUID!
projectLabelID: UUID!
extend type Mutation {
createProjectMember(input: CreateProjectMember!): CreateProjectMemberPayload!
deleteProjectMember(input: DeleteProjectMember!): DeleteProjectMemberPayload!
updateProjectMemberRole(input: UpdateProjectMemberRole!): UpdateProjectMemberRolePayload!
setProjectOwner(input: SetProjectOwner!): SetProjectOwnerPayload!
}
type ToggleTaskLabelPayload {
active: Boolean!
task: Task!
}
input UpdateProjectName {
input CreateProjectMember {
projectID: UUID!
userID: UUID!
}
type CreateProjectMemberPayload {
ok: Boolean!
member: Member!
}
input DeleteProjectMember {
projectID: UUID!
userID: UUID!
}
type DeleteProjectMemberPayload {
ok: Boolean!
member: Member!
projectID: UUID!
}
input UpdateProjectMemberRole {
projectID: UUID!
userID: UUID!
roleCode: RoleCode!
}
type UpdateProjectMemberRolePayload {
ok: Boolean!
member: Member!
}
input SetProjectOwner {
projectID: UUID!
ownerID: UUID!
}
type SetProjectOwnerPayload {
ok: Boolean!
prevOwner: Member!
newOwner: Member!
}
extend type Mutation {
createTask(input: NewTask!): Task!
deleteTask(input: DeleteTaskInput!): DeleteTaskPayload!
updateTaskDescription(input: UpdateTaskDescriptionInput!): Task!
updateTaskLocation(input: NewTaskLocation!): UpdateTaskLocationPayload!
updateTaskName(input: UpdateTaskName!): Task!
setTaskComplete(input: SetTaskComplete!): Task!
updateTaskDueDate(input: UpdateTaskDueDate!): Task!
assignTask(input: AssignTaskInput): Task!
unassignTask(input: UnassignTaskInput): Task!
}
input NewTask {
taskGroupID: String!
name: String!
position: Float!
}
input AssignTaskInput {
taskID: UUID!
userID: UUID!
}
input UnassignTaskInput {
taskID: UUID!
userID: UUID!
}
input UpdateTaskDescriptionInput {
taskID: UUID!
description: String!
}
type UpdateTaskLocationPayload {
@ -302,11 +324,6 @@ type UpdateTaskLocationPayload {
task: Task!
}
input UpdateTaskGroupName {
taskGroupID: UUID!
name: String!
}
input UpdateTaskDueDate {
taskID: UUID!
dueDate: Time
@ -317,12 +334,46 @@ input SetTaskComplete {
complete: Boolean!
}
input NewTaskLocation {
taskID: UUID!
taskGroupID: UUID!
position: Float!
}
input DeleteTaskInput {
taskID: String!
}
type DeleteTaskPayload {
taskID: String!
}
input UpdateTaskName {
taskID: String!
name: String!
}
extend type Mutation {
createTaskChecklist(input: CreateTaskChecklist!): TaskChecklist!
deleteTaskChecklist(input: DeleteTaskChecklist!): DeleteTaskChecklistPayload!
updateTaskChecklistName(input: UpdateTaskChecklistName!): TaskChecklist!
createTaskChecklistItem(input: CreateTaskChecklistItem!): TaskChecklistItem!
updateTaskChecklistItemName(input: UpdateTaskChecklistItemName!): TaskChecklistItem!
setTaskChecklistItemComplete(input: SetTaskChecklistItemComplete!): TaskChecklistItem!
deleteTaskChecklistItem(input: DeleteTaskChecklistItem!): DeleteTaskChecklistItemPayload!
}
input CreateTaskChecklist {
taskID: UUID!
name: String!
position: Float!
}
type DeleteTaskChecklistItemPayload {
ok: Boolean!
taskChecklistItem: TaskChecklistItem!
}
input CreateTaskChecklistItem {
taskChecklistID: UUID!
name: String!
@ -333,6 +384,7 @@ input SetTaskChecklistItemComplete {
taskChecklistItemID: UUID!
complete: Boolean!
}
input DeleteTaskChecklistItem {
taskChecklistItemID: UUID!
}
@ -342,44 +394,6 @@ input UpdateTaskChecklistItemName {
name: String!
}
input CreateTeamMember {
userID: UUID!
teamID: UUID!
}
type CreateTeamMemberPayload {
team: Team!
teamMember: ProjectMember!
}
input DeleteProject {
projectID: UUID!
}
type DeleteProjectPayload {
ok: Boolean!
project: Project!
}
input DeleteTeam {
teamID: UUID!
}
type DeleteTeamPayload {
ok: Boolean!
team: Team!
projects: [Project!]!
}
input DeleteUserAccount {
userID: UUID!
}
type DeleteUserAccountPayload {
ok: Boolean!
userAccount: UserAccount!
}
input UpdateTaskChecklistName {
taskChecklistID: UUID!
name: String!
@ -391,54 +405,163 @@ type DeleteTaskChecklistPayload {
ok: Boolean!
taskChecklist: TaskChecklist!
}
type Mutation {
createRefreshToken(input: NewRefreshToken!): RefreshToken!
createUserAccount(input: NewUserAccount!): UserAccount!
deleteUserAccount(input: DeleteUserAccount!): DeleteUserAccountPayload!
deleteTeam(input: DeleteTeam!): DeleteTeamPayload!
createTeam(input: NewTeam!): Team!
clearProfileAvatar: UserAccount!
createTeamMember(input: CreateTeamMember!): CreateTeamMemberPayload!
createProject(input: NewProject!): Project!
deleteProject(input: DeleteProject!): DeleteProjectPayload!
updateProjectName(input: UpdateProjectName): Project!
createProjectLabel(input: NewProjectLabel!): ProjectLabel!
deleteProjectLabel(input: DeleteProjectLabel!): ProjectLabel!
updateProjectLabel(input: UpdateProjectLabel!): ProjectLabel!
updateProjectLabelName(input: UpdateProjectLabelName!): ProjectLabel!
updateProjectLabelColor(input: UpdateProjectLabelColor!): ProjectLabel!
extend type Mutation {
createTaskGroup(input: NewTaskGroup!): TaskGroup!
updateTaskGroupLocation(input: NewTaskGroupLocation!): TaskGroup!
updateTaskGroupName(input: UpdateTaskGroupName!): TaskGroup!
deleteTaskGroup(input: DeleteTaskGroupInput!): DeleteTaskGroupPayload!
}
input NewTaskGroupLocation {
taskGroupID: UUID!
position: Float!
}
input UpdateTaskGroupName {
taskGroupID: UUID!
name: String!
}
input DeleteTaskGroupInput {
taskGroupID: UUID!
}
type DeleteTaskGroupPayload {
ok: Boolean!
affectedRows: Int!
taskGroup: TaskGroup!
}
input NewTaskGroup {
projectID: String!
name: String!
position: Float!
}
input AddTaskLabelInput {
taskID: UUID!
projectLabelID: UUID!
}
input RemoveTaskLabelInput {
taskLabelID: UUID!
}
input ToggleTaskLabelInput {
taskID: UUID!
projectLabelID: UUID!
}
type ToggleTaskLabelPayload {
active: Boolean!
task: Task!
}
extend type Mutation {
addTaskLabel(input: AddTaskLabelInput): Task!
removeTaskLabel(input: RemoveTaskLabelInput): Task!
toggleTaskLabel(input: ToggleTaskLabelInput!): ToggleTaskLabelPayload!
createTaskChecklist(input: CreateTaskChecklist!): TaskChecklist!
deleteTaskChecklist(input: DeleteTaskChecklist!): DeleteTaskChecklistPayload!
updateTaskChecklistName(input: UpdateTaskChecklistName!): TaskChecklist!
createTaskChecklistItem(input: CreateTaskChecklistItem!): TaskChecklistItem!
updateTaskChecklistItemName(input: UpdateTaskChecklistItemName!): TaskChecklistItem!
setTaskChecklistItemComplete(input: SetTaskChecklistItemComplete!): TaskChecklistItem!
deleteTaskChecklistItem(input: DeleteTaskChecklistItem!): DeleteTaskChecklistItemPayload!
createTask(input: NewTask!): Task!
updateTaskDescription(input: UpdateTaskDescriptionInput!): Task!
updateTaskLocation(input: NewTaskLocation!): UpdateTaskLocationPayload!
updateTaskName(input: UpdateTaskName!): Task!
setTaskComplete(input: SetTaskComplete!): Task!
updateTaskDueDate(input: UpdateTaskDueDate!): Task!
deleteTask(input: DeleteTaskInput!): DeleteTaskPayload!
assignTask(input: AssignTaskInput): Task!
unassignTask(input: UnassignTaskInput): Task!
logoutUser(input: LogoutUser!): Boolean!
}
extend type Mutation {
deleteTeam(input: DeleteTeam!): DeleteTeamPayload!
createTeam(input: NewTeam!): Team!
}
input NewTeam {
name: String!
organizationID: UUID!
}
input DeleteTeam {
teamID: UUID!
}
type DeleteTeamPayload {
ok: Boolean!
team: Team!
projects: [Project!]!
}
extend type Mutation {
setTeamOwner(input: SetTeamOwner!): SetTeamOwnerPayload!
createTeamMember(input: CreateTeamMember!): CreateTeamMemberPayload!
updateTeamMemberRole(input: UpdateTeamMemberRole!): UpdateTeamMemberRolePayload!
deleteTeamMember(input: DeleteTeamMember!): DeleteTeamMemberPayload!
}
input DeleteTeamMember {
teamID: UUID!
userID: UUID!
}
type DeleteTeamMemberPayload {
teamID: UUID!
userID: UUID!
}
input CreateTeamMember {
userID: UUID!
teamID: UUID!
}
type CreateTeamMemberPayload {
team: Team!
teamMember: Member!
}
input UpdateTeamMemberRole {
teamID: UUID!
userID: UUID!
roleCode: RoleCode!
}
type UpdateTeamMemberRolePayload {
ok: Boolean!
member: Member!
}
input SetTeamOwner {
teamID: UUID!
userID: UUID!
}
type SetTeamOwnerPayload {
ok: Boolean!
prevOwner: Member!
newOwner: Member!
}
extend type Mutation {
createRefreshToken(input: NewRefreshToken!): RefreshToken!
createUserAccount(input: NewUserAccount!): UserAccount!
deleteUserAccount(input: DeleteUserAccount!): DeleteUserAccountPayload!
logoutUser(input: LogoutUser!): Boolean!
clearProfileAvatar: UserAccount!
}
input NewRefreshToken {
userId: String!
}
input NewUserAccount {
username: String!
email: String!
fullName: String!
initials: String!
password: String!
roleCode: String!
}
input LogoutUser {
userID: String!
}
input DeleteUserAccount {
userID: UUID!
}
type DeleteUserAccountPayload {
ok: Boolean!
userAccount: UserAccount!
}

View File

@ -0,0 +1,560 @@
scalar Time
scalar UUID
scalar Upload
enum RoleCode {
owner
admin
member
observer
}
type ProjectLabel {
id: ID!
createdDate: Time!
labelColor: LabelColor!
name: String
}
type LabelColor {
id: ID!
name: String!
position: Float!
colorHex: String!
}
type TaskLabel {
id: ID!
projectLabel: ProjectLabel!
assignedDate: Time!
}
type ProfileIcon {
url: String
initials: String
bgColor: String
}
type Member {
id: ID!
role: Role!
fullName: String!
username: String!
profileIcon: ProfileIcon!
}
type RefreshToken {
id: ID!
userId: UUID!
expiresAt: Time!
createdAt: Time!
}
type Role {
code: String!
name: String!
}
type UserAccount {
id: ID!
email: String!
createdAt: Time!
fullName: String!
initials: String!
role: Role!
username: String!
profileIcon: ProfileIcon!
}
type Team {
id: ID!
createdAt: Time!
name: String!
members: [Member!]!
}
type Project {
id: ID!
createdAt: Time!
name: String!
team: Team!
owner: Member!
taskGroups: [TaskGroup!]!
members: [Member!]!
labels: [ProjectLabel!]!
}
type TaskGroup {
id: ID!
projectID: String!
createdAt: Time!
name: String!
position: Float!
tasks: [Task!]!
}
type ChecklistBadge {
complete: Int!
total: Int!
}
type TaskBadges {
checklist: ChecklistBadge
}
type Task {
id: ID!
taskGroup: TaskGroup!
createdAt: Time!
name: String!
position: Float!
description: String
dueDate: Time
complete: Boolean!
assigned: [Member!]!
labels: [TaskLabel!]!
checklists: [TaskChecklist!]!
badges: TaskBadges!
}
input ProjectsFilter {
teamID: UUID
}
input FindUser {
userId: String!
}
input FindProject {
projectId: String!
}
input FindTask {
taskID: UUID!
}
type Organization {
id: ID!
name: String!
}
input ProjectsFilter {
teamID: UUID
}
input FindUser {
userId: String!
}
input FindProject {
projectId: String!
}
input FindTask {
taskID: UUID!
}
type Organization {
id: ID!
name: String!
}
input FindTeam {
teamID: UUID!
}
type Query {
organizations: [Organization!]!
users: [UserAccount!]!
findUser(input: FindUser!): UserAccount!
findProject(input: FindProject!): Project!
findTask(input: FindTask!): Task!
projects(input: ProjectsFilter): [Project!]!
findTeam(input: FindTeam!): Team!
teams: [Team!]!
labelColors: [LabelColor!]!
taskGroups: [TaskGroup!]!
me: UserAccount!
}
type Mutation {}
extend type Mutation {
createTask(input: NewTask!): Task!
deleteTask(input: DeleteTaskInput!): DeleteTaskPayload!
updateTaskDescription(input: UpdateTaskDescriptionInput!): Task!
updateTaskLocation(input: NewTaskLocation!): UpdateTaskLocationPayload!
updateTaskName(input: UpdateTaskName!): Task!
setTaskComplete(input: SetTaskComplete!): Task!
updateTaskDueDate(input: UpdateTaskDueDate!): Task!
assignTask(input: AssignTaskInput): Task!
unassignTask(input: UnassignTaskInput): Task!
}
input NewRefreshToken {
userId: String!
}
input NewUserAccount {
username: String!
email: String!
fullName: String!
initials: String!
password: String!
roleCode: String!
}
input NewTeam {
name: String!
organizationID: UUID!
}
input NewProject {
userID: UUID!
teamID: UUID!
name: String!
}
input NewTaskGroup {
projectID: String!
name: String!
position: Float!
}
input LogoutUser {
userID: String!
}
input NewTask {
taskGroupID: String!
name: String!
position: Float!
}
input NewTaskLocation {
taskID: UUID!
taskGroupID: UUID!
position: Float!
}
input DeleteTaskInput {
taskID: String!
}
type DeleteTaskPayload {
taskID: String!
}
input UpdateTaskName {
taskID: String!
name: String!
}
input NewTaskGroupLocation {
taskGroupID: UUID!
position: Float!
}
input DeleteTaskGroupInput {
taskGroupID: UUID!
}
type DeleteTaskGroupPayload {
ok: Boolean!
affectedRows: Int!
taskGroup: TaskGroup!
}
type DeleteTaskChecklistItemPayload {
ok: Boolean!
taskChecklistItem: TaskChecklistItem!
}
type TaskChecklistItem {
id: ID!
name: String!
taskChecklistID: UUID!
complete: Boolean!
position: Float!
dueDate: Time!
}
type TaskChecklist {
id: ID!
name: String!
position: Float!
items: [TaskChecklistItem!]!
}
input AssignTaskInput {
taskID: UUID!
userID: UUID!
}
input UnassignTaskInput {
taskID: UUID!
userID: UUID!
}
input UpdateTaskDescriptionInput {
taskID: UUID!
description: String!
}
input AddTaskLabelInput {
taskID: UUID!
projectLabelID: UUID!
}
input RemoveTaskLabelInput {
taskLabelID: UUID!
}
input NewProjectLabel {
projectID: UUID!
labelColorID: UUID!
name: String
}
input DeleteProjectLabel {
projectLabelID: UUID!
}
input UpdateProjectLabelName {
projectLabelID: UUID!
name: String!
}
input UpdateProjectLabel {
projectLabelID: UUID!
labelColorID: UUID!
name: String!
}
input UpdateProjectLabelColor {
projectLabelID: UUID!
labelColorID: UUID!
}
input ToggleTaskLabelInput {
taskID: UUID!
projectLabelID: UUID!
}
type ToggleTaskLabelPayload {
active: Boolean!
task: Task!
}
input UpdateProjectName {
projectID: UUID!
name: String!
}
type UpdateTaskLocationPayload {
previousTaskGroupID: UUID!
task: Task!
}
input UpdateTaskGroupName {
taskGroupID: UUID!
name: String!
}
input UpdateTaskDueDate {
taskID: UUID!
dueDate: Time
}
input SetTaskComplete {
taskID: UUID!
complete: Boolean!
}
input CreateTaskChecklist {
taskID: UUID!
name: String!
position: Float!
}
input CreateTaskChecklistItem {
taskChecklistID: UUID!
name: String!
position: Float!
}
input SetTaskChecklistItemComplete {
taskChecklistItemID: UUID!
complete: Boolean!
}
input DeleteTaskChecklistItem {
taskChecklistItemID: UUID!
}
input UpdateTaskChecklistItemName {
taskChecklistItemID: UUID!
name: String!
}
input CreateTeamMember {
userID: UUID!
teamID: UUID!
}
type CreateTeamMemberPayload {
team: Team!
teamMember: Member!
}
input DeleteProject {
projectID: UUID!
}
type DeleteProjectPayload {
ok: Boolean!
project: Project!
}
input DeleteTeam {
teamID: UUID!
}
type DeleteTeamPayload {
ok: Boolean!
team: Team!
projects: [Project!]!
}
input DeleteUserAccount {
userID: UUID!
}
type DeleteUserAccountPayload {
ok: Boolean!
userAccount: UserAccount!
}
input UpdateTaskChecklistName {
taskChecklistID: UUID!
name: String!
}
input DeleteTaskChecklist {
taskChecklistID: UUID!
}
type DeleteTaskChecklistPayload {
ok: Boolean!
taskChecklist: TaskChecklist!
}
input CreateProjectMember {
projectID: UUID!
userID: UUID!
}
type CreateProjectMemberPayload {
ok: Boolean!
member: Member!
}
input DeleteProjectMember {
projectID: UUID!
userID: UUID!
}
type DeleteProjectMemberPayload {
ok: Boolean!
member: Member!
projectID: UUID!
}
input UpdateProjectMemberRole {
projectID: UUID!
userID: UUID!
roleCode: RoleCode!
}
type UpdateProjectMemberRolePayload {
ok: Boolean!
member: Member!
}
input SetProjectOwner {
projectID: UUID!
ownerID: UUID!
}
type SetProjectOwnerPayload {
ok: Boolean!
prevOwner: Member!
newOwner: Member!
}
input UpdateTeamMemberRole {
teamID: UUID!
userID: UUID!
roleCode: RoleCode!
}
type UpdateTeamMemberRolePayload {
ok: Boolean!
member: Member!
}
input SetTeamOwner {
teamID: UUID!
userID: UUID!
}
type SetTeamOwnerPayload {
ok: Boolean!
prevOwner: Member!
newOwner: Member!
}
type Mutation {
createRefreshToken(input: NewRefreshToken!): RefreshToken!
createUserAccount(input: NewUserAccount!): UserAccount!
deleteUserAccount(input: DeleteUserAccount!): DeleteUserAccountPayload!
deleteTeam(input: DeleteTeam!): DeleteTeamPayload!
createTeam(input: NewTeam!): Team!
clearProfileAvatar: UserAccount!
createTeamMember(input: CreateTeamMember!): CreateTeamMemberPayload!
updateTeamMemberRole(input: UpdateTeamMemberRole!): UpdateTeamMemberRolePayload!
setTeamOwner(input: SetTeamOwner!): SetTeamOwnerPayload!
createProject(input: NewProject!): Project!
deleteProject(input: DeleteProject!): DeleteProjectPayload!
updateProjectName(input: UpdateProjectName): Project!
createProjectMember(input: CreateProjectMember!): CreateProjectMemberPayload!
deleteProjectMember(input: DeleteProjectMember!): DeleteProjectMemberPayload!
updateProjectMemberRole(input: UpdateProjectMemberRole!): UpdateProjectMemberRolePayload!
setProjectOwner(input: SetProjectOwner!): SetProjectOwnerPayload!
createProjectLabel(input: NewProjectLabel!): ProjectLabel!
deleteProjectLabel(input: DeleteProjectLabel!): ProjectLabel!
updateProjectLabel(input: UpdateProjectLabel!): ProjectLabel!
updateProjectLabelName(input: UpdateProjectLabelName!): ProjectLabel!
updateProjectLabelColor(input: UpdateProjectLabelColor!): ProjectLabel!
createTaskGroup(input: NewTaskGroup!): TaskGroup!
updateTaskGroupLocation(input: NewTaskGroupLocation!): TaskGroup!
updateTaskGroupName(input: UpdateTaskGroupName!): TaskGroup!
deleteTaskGroup(input: DeleteTaskGroupInput!): DeleteTaskGroupPayload!
addTaskLabel(input: AddTaskLabelInput): Task!
removeTaskLabel(input: RemoveTaskLabelInput): Task!
toggleTaskLabel(input: ToggleTaskLabelInput!): ToggleTaskLabelPayload!
createTaskChecklist(input: CreateTaskChecklist!): TaskChecklist!
deleteTaskChecklist(input: DeleteTaskChecklist!): DeleteTaskChecklistPayload!
updateTaskChecklistName(input: UpdateTaskChecklistName!): TaskChecklist!
createTaskChecklistItem(input: CreateTaskChecklistItem!): TaskChecklistItem!
updateTaskChecklistItemName(input: UpdateTaskChecklistItemName!): TaskChecklistItem!
setTaskChecklistItemComplete(input: SetTaskChecklistItemComplete!): TaskChecklistItem!
deleteTaskChecklistItem(input: DeleteTaskChecklistItem!): DeleteTaskChecklistItemPayload!
logoutUser(input: LogoutUser!): Boolean!
}

View File

@ -0,0 +1,139 @@
scalar Time
scalar UUID
scalar Upload
enum RoleCode {
owner
admin
member
observer
}
type ProjectLabel {
id: ID!
createdDate: Time!
labelColor: LabelColor!
name: String
}
type LabelColor {
id: ID!
name: String!
position: Float!
colorHex: String!
}
type TaskLabel {
id: ID!
projectLabel: ProjectLabel!
assignedDate: Time!
}
type ProfileIcon {
url: String
initials: String
bgColor: String
}
type Member {
id: ID!
role: Role!
fullName: String!
username: String!
profileIcon: ProfileIcon!
}
type RefreshToken {
id: ID!
userId: UUID!
expiresAt: Time!
createdAt: Time!
}
type Role {
code: String!
name: String!
}
type UserAccount {
id: ID!
email: String!
createdAt: Time!
fullName: String!
initials: String!
role: Role!
username: String!
profileIcon: ProfileIcon!
}
type Team {
id: ID!
createdAt: Time!
name: String!
members: [Member!]!
}
type Project {
id: ID!
createdAt: Time!
name: String!
team: Team!
owner: Member!
taskGroups: [TaskGroup!]!
members: [Member!]!
labels: [ProjectLabel!]!
}
type TaskGroup {
id: ID!
projectID: String!
createdAt: Time!
name: String!
position: Float!
tasks: [Task!]!
}
type ChecklistBadge {
complete: Int!
total: Int!
}
type TaskBadges {
checklist: ChecklistBadge
}
type Task {
id: ID!
taskGroup: TaskGroup!
createdAt: Time!
name: String!
position: Float!
description: String
dueDate: Time
complete: Boolean!
assigned: [Member!]!
labels: [TaskLabel!]!
checklists: [TaskChecklist!]!
badges: TaskBadges!
}
type Organization {
id: ID!
name: String!
}
type TaskChecklistItem {
id: ID!
name: String!
taskChecklistID: UUID!
complete: Boolean!
position: Float!
dueDate: Time!
}
type TaskChecklist {
id: ID!
name: String!
position: Float!
items: [TaskChecklistItem!]!
}

View File

@ -0,0 +1,35 @@
type Query {
organizations: [Organization!]!
users: [UserAccount!]!
findUser(input: FindUser!): UserAccount!
findProject(input: FindProject!): Project!
findTask(input: FindTask!): Task!
projects(input: ProjectsFilter): [Project!]!
findTeam(input: FindTeam!): Team!
teams: [Team!]!
labelColors: [LabelColor!]!
taskGroups: [TaskGroup!]!
me: UserAccount!
}
type Mutation
input ProjectsFilter {
teamID: UUID
}
input FindUser {
userId: String!
}
input FindProject {
projectId: String!
}
input FindTask {
taskID: UUID!
}
input FindTeam {
teamID: UUID!
}

View File

@ -0,0 +1,26 @@
extend type Mutation {
createProject(input: NewProject!): Project!
deleteProject(input: DeleteProject!): DeleteProjectPayload!
updateProjectName(input: UpdateProjectName): Project!
}
input NewProject {
userID: UUID!
teamID: UUID!
name: String!
}
input UpdateProjectName {
projectID: UUID!
name: String!
}
input DeleteProject {
projectID: UUID!
}
type DeleteProjectPayload {
ok: Boolean!
project: Project!
}

View File

@ -0,0 +1,33 @@
extend type Mutation {
createProjectLabel(input: NewProjectLabel!): ProjectLabel!
deleteProjectLabel(input: DeleteProjectLabel!): ProjectLabel!
updateProjectLabel(input: UpdateProjectLabel!): ProjectLabel!
updateProjectLabelName(input: UpdateProjectLabelName!): ProjectLabel!
updateProjectLabelColor(input: UpdateProjectLabelColor!): ProjectLabel!
}
input NewProjectLabel {
projectID: UUID!
labelColorID: UUID!
name: String
}
input DeleteProjectLabel {
projectLabelID: UUID!
}
input UpdateProjectLabelName {
projectLabelID: UUID!
name: String!
}
input UpdateProjectLabel {
projectLabelID: UUID!
labelColorID: UUID!
name: String!
}
input UpdateProjectLabelColor {
projectLabelID: UUID!
labelColorID: UUID!
}

View File

@ -0,0 +1,48 @@
extend type Mutation {
createProjectMember(input: CreateProjectMember!): CreateProjectMemberPayload!
deleteProjectMember(input: DeleteProjectMember!): DeleteProjectMemberPayload!
updateProjectMemberRole(input: UpdateProjectMemberRole!): UpdateProjectMemberRolePayload!
setProjectOwner(input: SetProjectOwner!): SetProjectOwnerPayload!
}
input CreateProjectMember {
projectID: UUID!
userID: UUID!
}
type CreateProjectMemberPayload {
ok: Boolean!
member: Member!
}
input DeleteProjectMember {
projectID: UUID!
userID: UUID!
}
type DeleteProjectMemberPayload {
ok: Boolean!
member: Member!
projectID: UUID!
}
input UpdateProjectMemberRole {
projectID: UUID!
userID: UUID!
roleCode: RoleCode!
}
type UpdateProjectMemberRolePayload {
ok: Boolean!
member: Member!
}
input SetProjectOwner {
projectID: UUID!
ownerID: UUID!
}
type SetProjectOwnerPayload {
ok: Boolean!
prevOwner: Member!
newOwner: Member!
}

View File

@ -0,0 +1,68 @@
extend type Mutation {
createTask(input: NewTask!): Task!
deleteTask(input: DeleteTaskInput!): DeleteTaskPayload!
updateTaskDescription(input: UpdateTaskDescriptionInput!): Task!
updateTaskLocation(input: NewTaskLocation!): UpdateTaskLocationPayload!
updateTaskName(input: UpdateTaskName!): Task!
setTaskComplete(input: SetTaskComplete!): Task!
updateTaskDueDate(input: UpdateTaskDueDate!): Task!
assignTask(input: AssignTaskInput): Task!
unassignTask(input: UnassignTaskInput): Task!
}
input NewTask {
taskGroupID: String!
name: String!
position: Float!
}
input AssignTaskInput {
taskID: UUID!
userID: UUID!
}
input UnassignTaskInput {
taskID: UUID!
userID: UUID!
}
input UpdateTaskDescriptionInput {
taskID: UUID!
description: String!
}
type UpdateTaskLocationPayload {
previousTaskGroupID: UUID!
task: Task!
}
input UpdateTaskDueDate {
taskID: UUID!
dueDate: Time
}
input SetTaskComplete {
taskID: UUID!
complete: Boolean!
}
input NewTaskLocation {
taskID: UUID!
taskGroupID: UUID!
position: Float!
}
input DeleteTaskInput {
taskID: String!
}
type DeleteTaskPayload {
taskID: String!
}
input UpdateTaskName {
taskID: String!
name: String!
}

View File

@ -0,0 +1,52 @@
extend type Mutation {
createTaskChecklist(input: CreateTaskChecklist!): TaskChecklist!
deleteTaskChecklist(input: DeleteTaskChecklist!): DeleteTaskChecklistPayload!
updateTaskChecklistName(input: UpdateTaskChecklistName!): TaskChecklist!
createTaskChecklistItem(input: CreateTaskChecklistItem!): TaskChecklistItem!
updateTaskChecklistItemName(input: UpdateTaskChecklistItemName!): TaskChecklistItem!
setTaskChecklistItemComplete(input: SetTaskChecklistItemComplete!): TaskChecklistItem!
deleteTaskChecklistItem(input: DeleteTaskChecklistItem!): DeleteTaskChecklistItemPayload!
}
input CreateTaskChecklist {
taskID: UUID!
name: String!
position: Float!
}
type DeleteTaskChecklistItemPayload {
ok: Boolean!
taskChecklistItem: TaskChecklistItem!
}
input CreateTaskChecklistItem {
taskChecklistID: UUID!
name: String!
position: Float!
}
input SetTaskChecklistItemComplete {
taskChecklistItemID: UUID!
complete: Boolean!
}
input DeleteTaskChecklistItem {
taskChecklistItemID: UUID!
}
input UpdateTaskChecklistItemName {
taskChecklistItemID: UUID!
name: String!
}
input UpdateTaskChecklistName {
taskChecklistID: UUID!
name: String!
}
input DeleteTaskChecklist {
taskChecklistID: UUID!
}
type DeleteTaskChecklistPayload {
ok: Boolean!
taskChecklist: TaskChecklist!
}

View File

@ -0,0 +1,32 @@
extend type Mutation {
createTaskGroup(input: NewTaskGroup!): TaskGroup!
updateTaskGroupLocation(input: NewTaskGroupLocation!): TaskGroup!
updateTaskGroupName(input: UpdateTaskGroupName!): TaskGroup!
deleteTaskGroup(input: DeleteTaskGroupInput!): DeleteTaskGroupPayload!
}
input NewTaskGroupLocation {
taskGroupID: UUID!
position: Float!
}
input UpdateTaskGroupName {
taskGroupID: UUID!
name: String!
}
input DeleteTaskGroupInput {
taskGroupID: UUID!
}
type DeleteTaskGroupPayload {
ok: Boolean!
affectedRows: Int!
taskGroup: TaskGroup!
}
input NewTaskGroup {
projectID: String!
name: String!
position: Float!
}

View File

@ -0,0 +1,22 @@
input AddTaskLabelInput {
taskID: UUID!
projectLabelID: UUID!
}
input RemoveTaskLabelInput {
taskLabelID: UUID!
}
input ToggleTaskLabelInput {
taskID: UUID!
projectLabelID: UUID!
}
type ToggleTaskLabelPayload {
active: Boolean!
task: Task!
}
extend type Mutation {
addTaskLabel(input: AddTaskLabelInput): Task!
removeTaskLabel(input: RemoveTaskLabelInput): Task!
toggleTaskLabel(input: ToggleTaskLabelInput!): ToggleTaskLabelPayload!
}

View File

@ -0,0 +1,19 @@
extend type Mutation {
deleteTeam(input: DeleteTeam!): DeleteTeamPayload!
createTeam(input: NewTeam!): Team!
}
input NewTeam {
name: String!
organizationID: UUID!
}
input DeleteTeam {
teamID: UUID!
}
type DeleteTeamPayload {
ok: Boolean!
team: Team!
projects: [Project!]!
}

View File

@ -0,0 +1,48 @@
extend type Mutation {
setTeamOwner(input: SetTeamOwner!): SetTeamOwnerPayload!
createTeamMember(input: CreateTeamMember!): CreateTeamMemberPayload!
updateTeamMemberRole(input: UpdateTeamMemberRole!): UpdateTeamMemberRolePayload!
deleteTeamMember(input: DeleteTeamMember!): DeleteTeamMemberPayload!
}
input DeleteTeamMember {
teamID: UUID!
userID: UUID!
}
type DeleteTeamMemberPayload {
teamID: UUID!
userID: UUID!
}
input CreateTeamMember {
userID: UUID!
teamID: UUID!
}
type CreateTeamMemberPayload {
team: Team!
teamMember: Member!
}
input UpdateTeamMemberRole {
teamID: UUID!
userID: UUID!
roleCode: RoleCode!
}
type UpdateTeamMemberRolePayload {
ok: Boolean!
member: Member!
}
input SetTeamOwner {
teamID: UUID!
userID: UUID!
}
type SetTeamOwnerPayload {
ok: Boolean!
prevOwner: Member!
newOwner: Member!
}

View File

@ -0,0 +1,33 @@
extend type Mutation {
createRefreshToken(input: NewRefreshToken!): RefreshToken!
createUserAccount(input: NewUserAccount!): UserAccount!
deleteUserAccount(input: DeleteUserAccount!): DeleteUserAccountPayload!
logoutUser(input: LogoutUser!): Boolean!
clearProfileAvatar: UserAccount!
}
input NewRefreshToken {
userId: String!
}
input NewUserAccount {
username: String!
email: String!
fullName: String!
initials: String!
password: String!
roleCode: String!
}
input LogoutUser {
userID: String!
}
input DeleteUserAccount {
userID: UUID!
}
type DeleteUserAccountPayload {
ok: Boolean!
userAccount: UserAccount!
}

View File

@ -1,4 +1,4 @@
package router
package logger
import (
"fmt"
@ -57,8 +57,7 @@ func (l *StructuredLoggerEntry) Write(status, bytes int, elapsed time.Duration)
"resp_status": status, "resp_bytes_length": bytes,
"resp_elapsed_ms": float64(elapsed.Nanoseconds()) / 1000000.0,
})
l.Logger.Infoln("request complete")
l.Logger.Debugln("request complete")
}
func (l *StructuredLoggerEntry) Panic(v interface{}, stack []byte) {

View File

@ -1,13 +1,15 @@
package router
package route
import (
"encoding/json"
"net/http"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/go-chi/chi"
"github.com/google/uuid"
"github.com/jordanknott/project-citadel/api/pg"
"github.com/jordanknott/project-citadel/api/internal/auth"
"github.com/jordanknott/project-citadel/api/internal/db"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/bcrypt"
)
@ -16,6 +18,38 @@ var jwtKey = []byte("citadel_test_key")
type authResource struct{}
type AccessTokenClaims struct {
UserID string `json:"userId"`
jwt.StandardClaims
}
type RefreshTokenClaims struct {
UserID string `json:"userId"`
jwt.StandardClaims
}
type LoginRequestData struct {
Username string
Password string
}
type LoginResponseData struct {
AccessToken string `json:"accessToken"`
}
type LogoutResponseData struct {
Status string `json:"status"`
}
type RefreshTokenResponseData struct {
AccessToken string `json:"accessToken"`
}
type AvatarUploadResponseData struct {
UserID string `json:"userID"`
URL string `json:"url"`
}
func (h *CitadelHandler) RefreshTokenHandler(w http.ResponseWriter, r *http.Request) {
c, err := r.Cookie("refreshToken")
if err != nil {
@ -35,14 +69,14 @@ func (h *CitadelHandler) RefreshTokenHandler(w http.ResponseWriter, r *http.Requ
refreshCreatedAt := time.Now().UTC()
refreshExpiresAt := refreshCreatedAt.AddDate(0, 0, 1)
refreshTokenString, err := h.repo.CreateRefreshToken(r.Context(), pg.CreateRefreshTokenParams{token.UserID, refreshCreatedAt, refreshExpiresAt})
refreshTokenString, err := h.repo.CreateRefreshToken(r.Context(), db.CreateRefreshTokenParams{token.UserID, refreshCreatedAt, refreshExpiresAt})
err = h.repo.DeleteRefreshTokenByID(r.Context(), token.TokenID)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
}
accessTokenString, err := NewAccessToken(token.UserID.String())
accessTokenString, err := auth.NewAccessToken(token.UserID.String())
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
}
@ -106,9 +140,9 @@ func (h *CitadelHandler) LoginHandler(w http.ResponseWriter, r *http.Request) {
refreshCreatedAt := time.Now().UTC()
refreshExpiresAt := refreshCreatedAt.AddDate(0, 0, 1)
refreshTokenString, err := h.repo.CreateRefreshToken(r.Context(), pg.CreateRefreshTokenParams{user.UserID, refreshCreatedAt, refreshExpiresAt})
refreshTokenString, err := h.repo.CreateRefreshToken(r.Context(), db.CreateRefreshTokenParams{user.UserID, refreshCreatedAt, refreshExpiresAt})
accessTokenString, err := NewAccessToken(user.UserID.String())
accessTokenString, err := auth.NewAccessToken(user.UserID.String())
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
}

View File

@ -0,0 +1,52 @@
package route
import (
"database/sql"
"encoding/json"
"io/ioutil"
"net/http"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
"github.com/jordanknott/project-citadel/api/internal/db"
)
func (h *CitadelHandler) ProfileImageUpload(w http.ResponseWriter, r *http.Request) {
log.Info("preparing to upload file")
userID, ok := r.Context().Value("userID").(uuid.UUID)
if !ok {
log.Error("not a valid uuid")
w.WriteHeader(http.StatusInternalServerError)
return
}
// Parse our multipart form, 10 << 20 specifies a maximum
// upload of 10 MB files.
r.ParseMultipartForm(10 << 20)
file, handler, err := r.FormFile("file")
if err != nil {
log.WithError(err).Error("issue while uploading file")
return
}
defer file.Close()
log.WithFields(log.Fields{"filename": handler.Filename, "size": handler.Size, "header": handler.Header}).Info("file metadata")
fileBytes, err := ioutil.ReadAll(file)
if err != nil {
log.WithError(err).Error("while reading file")
return
}
err = ioutil.WriteFile("uploads/"+handler.Filename, fileBytes, 0644)
if err != nil {
log.WithError(err).Error("while reading file")
return
}
h.repo.UpdateUserAccountProfileAvatarURL(r.Context(), db.UpdateUserAccountProfileAvatarURLParams{UserID: userID, ProfileAvatarUrl: sql.NullString{String: "http://localhost:3333/uploads/" + handler.Filename, Valid: true}})
// return that we have successfully uploaded our file!
log.Info("file uploaded")
json.NewEncoder(w).Encode(AvatarUploadResponseData{URL: "http://localhost:3333/uploads/" + handler.Filename, UserID: userID.String()})
}

View File

@ -1,4 +1,4 @@
package router
package route
import (
"context"
@ -6,6 +6,7 @@ import (
"strings"
"github.com/google/uuid"
"github.com/jordanknott/project-citadel/api/internal/auth"
log "github.com/sirupsen/logrus"
)
@ -13,17 +14,14 @@ func AuthenticationMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
bearerTokenRaw := r.Header.Get("Authorization")
splitToken := strings.Split(bearerTokenRaw, "Bearer")
log.WithFields(log.Fields{
"bearerToken": bearerTokenRaw,
}).Warning("loading bearer token")
if len(splitToken) != 2 {
w.WriteHeader(http.StatusBadRequest)
return
}
accessTokenString := strings.TrimSpace(splitToken[1])
accessClaims, err := ValidateAccessToken(accessTokenString)
accessClaims, err := auth.ValidateAccessToken(accessTokenString)
if err != nil {
if _, ok := err.(*ErrExpiredToken); ok {
if _, ok := err.(*auth.ErrExpiredToken); ok {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{
"data": {},

View File

@ -0,0 +1,66 @@
package route
import (
"net/http"
"time"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/go-chi/cors"
"github.com/jmoiron/sqlx"
log "github.com/sirupsen/logrus"
"github.com/jordanknott/project-citadel/api/internal/db"
"github.com/jordanknott/project-citadel/api/internal/graph"
"github.com/jordanknott/project-citadel/api/internal/logger"
)
type CitadelHandler struct {
repo db.Repository
}
func NewRouter(dbConnection *sqlx.DB) (chi.Router, error) {
formatter := new(log.TextFormatter)
formatter.TimestampFormat = "02-01-2006 15:04:05"
formatter.FullTimestamp = true
routerLogger := log.New()
routerLogger.SetLevel(log.InfoLevel)
routerLogger.Formatter = formatter
r := chi.NewRouter()
cors := cors.New(cors.Options{
// AllowedOrigins: []string{"https://foo.com"}, // Use this to allow specific origin hosts
AllowedOrigins: []string{"*"},
// AllowOriginFunc: func(r *http.Request, origin string) bool { return true },
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token", "Cookie"},
ExposedHeaders: []string{"Link"},
AllowCredentials: true,
MaxAge: 300, // Maximum value not ignored by any of major browsers
})
r.Use(cors.Handler)
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(logger.NewStructuredLogger(routerLogger))
r.Use(middleware.Recoverer)
r.Use(middleware.Timeout(60 * time.Second))
repository := db.NewRepository(dbConnection)
citadelHandler := CitadelHandler{*repository}
var imgServer = http.FileServer(http.Dir("./uploads/"))
r.Group(func(mux chi.Router) {
mux.Mount("/auth", authResource{}.Routes(citadelHandler))
mux.Handle("/__graphql", graph.NewPlaygroundHandler("/graphql"))
mux.Mount("/uploads/", http.StripPrefix("/uploads/", imgServer))
})
r.Group(func(mux chi.Router) {
mux.Use(AuthenticationMiddleware)
mux.Post("/users/me/avatar", citadelHandler.ProfileImageUpload)
mux.Handle("/graphql", graph.NewHandler(*repository))
})
return r, nil
}

45
api/magefile.go Normal file
View File

@ -0,0 +1,45 @@
//+build mage
package main
import (
"fmt"
"github.com/magefile/mage/sh"
"io/ioutil"
"os"
"strings"
)
var Aliases = map[string]interface{}{
"g": Generate,
}
// Runs go mod download and then installs the binary.
func Generate() error {
files, err := ioutil.ReadDir("graph/schema/")
if err != nil {
panic(err)
}
var schema strings.Builder
for _, file := range files {
filename := "graph/schema/" + file.Name()
fmt.Println(filename)
f, err := os.Open(filename)
if err != nil {
panic(err)
}
content, err := ioutil.ReadAll(f)
if err != nil {
panic(err)
}
fmt.Fprintln(&schema, string(content))
}
// return sh.Run("go", "install", "./...")
// fmt.Println(schema.String())
err = ioutil.WriteFile("graph/schema.graphqls", []byte(schema.String()), os.FileMode(0755))
if err != nil {
panic(err)
}
return sh.Run("gqlgen")
}

View File

@ -1,3 +1,5 @@
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE TABLE refresh_token (
token_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id uuid NOT NULL,

View File

@ -3,3 +3,5 @@ CREATE TABLE organization (
created_at timestamptz NOT NULL,
name text NOT NULL
);
INSERT INTO organization (created_at, name) VALUES (NOW(), 'sys_default_organization');

View File

@ -0,0 +1,6 @@
CREATE TABLE project_member (
project_member_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
project_id uuid NOT NULL REFERENCES project(project_id) ON DELETE CASCADE,
user_id uuid NOT NULL REFERENCES user_account(user_id) ON DELETE CASCADE,
added_at timestamptz NOT NULL
);

View File

@ -0,0 +1,9 @@
CREATE TABLE role (
code TEXT PRIMARY KEY,
name TEXT NOT NULL
);
INSERT INTO role VALUES ('owner', 'Owner');
INSERT INTO role VALUES ('admin', 'Admin');
INSERT INTO role VALUES ('member', 'Member');
INSERT INTO role VALUES ('observer', 'Observer');

View File

@ -0,0 +1,2 @@
ALTER TABLE user_account ADD COLUMN role_code text
NOT NULL REFERENCES role(code) ON DELETE CASCADE DEFAULT 'member';

View File

@ -0,0 +1 @@
ALTER TABLE project_member ADD UNIQUE (project_id, user_id);

View File

@ -0,0 +1,2 @@
ALTER TABLE project_member ADD COLUMN role_code TEXT
NOT NULL REFERENCES role(code) ON DELETE CASCADE DEFAULT 'member';

View File

@ -0,0 +1 @@
ALTER TABLE user_account ALTER COLUMN profile_avatar_url SET DEFAULT null;

View File

@ -0,0 +1 @@
ALTER TABLE team_member ADD COLUMN role_code TEXT NOT NULL REFERENCES role(code) ON DELETE CASCADE;

View File

@ -0,0 +1,4 @@
ALTER TABLE team ADD COLUMN owner uuid REFERENCES user_account(user_id) ON DELETE
CASCADE;
UPDATE team SET owner = (SELECT user_id FROM user_account WHERE role_code = 'admin' LIMIT 1);
ALTER TABLE team ALTER COLUMN owner SET NOT NULL;

View File

@ -1,114 +0,0 @@
package pg
import (
"context"
"github.com/google/uuid"
"github.com/jmoiron/sqlx"
)
type Repository interface {
CreateTeamMember(ctx context.Context, arg CreateTeamMemberParams) (TeamMember, error)
DeleteTeamMemberByUserID(ctx context.Context, userID uuid.UUID) error
GetTeamMembersForTeamID(ctx context.Context, teamID uuid.UUID) ([]TeamMember, error)
CreateTeam(ctx context.Context, arg CreateTeamParams) (Team, error)
DeleteTeamByID(ctx context.Context, teamID uuid.UUID) error
GetTeamByID(ctx context.Context, teamID uuid.UUID) (Team, error)
GetAllTeams(ctx context.Context) ([]Team, error)
DeleteProjectByID(ctx context.Context, projectID uuid.UUID) error
DeleteUserAccountByID(ctx context.Context, userID uuid.UUID) error
GetTaskChecklistByID(ctx context.Context, taskChecklistID uuid.UUID) (TaskChecklist, error)
DeleteTaskChecklistByID(ctx context.Context, taskChecklistID uuid.UUID) error
UpdateTaskChecklistName(ctx context.Context, arg UpdateTaskChecklistNameParams) (TaskChecklist, error)
CreateProject(ctx context.Context, arg CreateProjectParams) (Project, error)
GetAllProjects(ctx context.Context) ([]Project, error)
GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) ([]Project, error)
GetProjectByID(ctx context.Context, projectID uuid.UUID) (Project, error)
UpdateTaskChecklistItemName(ctx context.Context, arg UpdateTaskChecklistItemNameParams) (TaskChecklistItem, error)
GetTaskChecklistItemByID(ctx context.Context, taskChecklistItemID uuid.UUID) (TaskChecklistItem, error)
CreateTaskChecklist(ctx context.Context, arg CreateTaskChecklistParams) (TaskChecklist, error)
CreateTaskChecklistItem(ctx context.Context, arg CreateTaskChecklistItemParams) (TaskChecklistItem, error)
GetTaskChecklistItemsForTaskChecklist(ctx context.Context, taskChecklistID uuid.UUID) ([]TaskChecklistItem, error)
GetTaskChecklistsForTask(ctx context.Context, taskID uuid.UUID) ([]TaskChecklist, error)
SetTaskChecklistItemComplete(ctx context.Context, arg SetTaskChecklistItemCompleteParams) (TaskChecklistItem, error)
DeleteTaskChecklistItem(ctx context.Context, taskChecklistItemID uuid.UUID) error
UpdateUserAccountProfileAvatarURL(ctx context.Context, arg UpdateUserAccountProfileAvatarURLParams) (UserAccount, error)
CreateUserAccount(ctx context.Context, arg CreateUserAccountParams) (UserAccount, error)
GetUserAccountByID(ctx context.Context, userID uuid.UUID) (UserAccount, error)
GetUserAccountByUsername(ctx context.Context, username string) (UserAccount, error)
GetAllUserAccounts(ctx context.Context) ([]UserAccount, error)
GetTaskLabelByID(ctx context.Context, taskLabelID uuid.UUID) (TaskLabel, error)
SetTaskComplete(ctx context.Context, arg SetTaskCompleteParams) (Task, error)
DeleteTaskLabelForTaskByProjectLabelID(ctx context.Context, arg DeleteTaskLabelForTaskByProjectLabelIDParams) error
GetTaskLabelForTaskByProjectLabelID(ctx context.Context, arg GetTaskLabelForTaskByProjectLabelIDParams) (TaskLabel, error)
UpdateProjectNameByID(ctx context.Context, arg UpdateProjectNameByIDParams) (Project, error)
DeleteTaskLabelByID(ctx context.Context, taskLabelID uuid.UUID) error
UpdateTaskDueDate(ctx context.Context, arg UpdateTaskDueDateParams) (Task, error)
CreateProjectLabel(ctx context.Context, arg CreateProjectLabelParams) (ProjectLabel, error)
GetProjectLabelsForProject(ctx context.Context, projectID uuid.UUID) ([]ProjectLabel, error)
GetProjectLabelByID(ctx context.Context, projectLabelID uuid.UUID) (ProjectLabel, error)
DeleteProjectLabelByID(ctx context.Context, projectLabelID uuid.UUID) error
UpdateProjectLabelColor(ctx context.Context, arg UpdateProjectLabelColorParams) (ProjectLabel, error)
UpdateProjectLabelName(ctx context.Context, arg UpdateProjectLabelNameParams) (ProjectLabel, error)
UpdateProjectLabel(ctx context.Context, arg UpdateProjectLabelParams) (ProjectLabel, error)
GetLabelColors(ctx context.Context) ([]LabelColor, error)
CreateLabelColor(ctx context.Context, arg CreateLabelColorParams) (LabelColor, error)
CreateRefreshToken(ctx context.Context, arg CreateRefreshTokenParams) (RefreshToken, error)
GetRefreshTokenByID(ctx context.Context, tokenID uuid.UUID) (RefreshToken, error)
DeleteRefreshTokenByID(ctx context.Context, tokenID uuid.UUID) error
DeleteRefreshTokenByUserID(ctx context.Context, userID uuid.UUID) error
SetTaskGroupName(ctx context.Context, arg SetTaskGroupNameParams) (TaskGroup, error)
DeleteTaskGroupByID(ctx context.Context, taskGroupID uuid.UUID) (int64, error)
DeleteTasksByTaskGroupID(ctx context.Context, taskGroupID uuid.UUID) (int64, error)
UpdateTaskGroupLocation(ctx context.Context, arg UpdateTaskGroupLocationParams) (TaskGroup, error)
CreateTaskGroup(ctx context.Context, arg CreateTaskGroupParams) (TaskGroup, error)
GetAllTaskGroups(ctx context.Context) ([]TaskGroup, error)
GetTaskGroupsForProject(ctx context.Context, projectID uuid.UUID) ([]TaskGroup, error)
GetTaskGroupByID(ctx context.Context, taskGroupID uuid.UUID) (TaskGroup, error)
GetAllOrganizations(ctx context.Context) ([]Organization, error)
CreateOrganization(ctx context.Context, arg CreateOrganizationParams) (Organization, error)
GetTeamsForOrganization(ctx context.Context, organizationID uuid.UUID) ([]Team, error)
CreateTask(ctx context.Context, arg CreateTaskParams) (Task, error)
GetTaskByID(ctx context.Context, taskID uuid.UUID) (Task, error)
GetAllTasks(ctx context.Context) ([]Task, error)
GetTasksForTaskGroupID(ctx context.Context, taskGroupID uuid.UUID) ([]Task, error)
UpdateTaskLocation(ctx context.Context, arg UpdateTaskLocationParams) (Task, error)
DeleteTaskByID(ctx context.Context, taskID uuid.UUID) error
UpdateTaskName(ctx context.Context, arg UpdateTaskNameParams) (Task, error)
UpdateTaskDescription(ctx context.Context, arg UpdateTaskDescriptionParams) (Task, error)
CreateTaskLabelForTask(ctx context.Context, arg CreateTaskLabelForTaskParams) (TaskLabel, error)
GetTaskLabelsForTaskID(ctx context.Context, taskID uuid.UUID) ([]TaskLabel, error)
GetLabelColorByID(ctx context.Context, labelColorID uuid.UUID) (LabelColor, error)
CreateTaskAssigned(ctx context.Context, arg CreateTaskAssignedParams) (TaskAssigned, error)
GetAssignedMembersForTask(ctx context.Context, taskID uuid.UUID) ([]TaskAssigned, error)
DeleteTaskAssignedByID(ctx context.Context, arg DeleteTaskAssignedByIDParams) (TaskAssigned, error)
}
type repoSvc struct {
*Queries
db *sqlx.DB
}
// NewRepository returns an implementation of the Repository interface.
func NewRepository(db *sqlx.DB) Repository {
return &repoSvc{
Queries: New(db.DB),
db: db,
}
}

View File

@ -1,154 +0,0 @@
// Code generated by sqlc. DO NOT EDIT.
// source: project.sql
package pg
import (
"context"
"time"
"github.com/google/uuid"
)
const createProject = `-- name: CreateProject :one
INSERT INTO project(owner, team_id, created_at, name) VALUES ($1, $2, $3, $4) RETURNING project_id, team_id, created_at, name, owner
`
type CreateProjectParams struct {
Owner uuid.UUID `json:"owner"`
TeamID uuid.UUID `json:"team_id"`
CreatedAt time.Time `json:"created_at"`
Name string `json:"name"`
}
func (q *Queries) CreateProject(ctx context.Context, arg CreateProjectParams) (Project, error) {
row := q.db.QueryRowContext(ctx, createProject,
arg.Owner,
arg.TeamID,
arg.CreatedAt,
arg.Name,
)
var i Project
err := row.Scan(
&i.ProjectID,
&i.TeamID,
&i.CreatedAt,
&i.Name,
&i.Owner,
)
return i, err
}
const deleteProjectByID = `-- name: DeleteProjectByID :exec
DELETE FROM project WHERE project_id = $1
`
func (q *Queries) DeleteProjectByID(ctx context.Context, projectID uuid.UUID) error {
_, err := q.db.ExecContext(ctx, deleteProjectByID, projectID)
return err
}
const getAllProjects = `-- name: GetAllProjects :many
SELECT project_id, team_id, created_at, name, owner FROM project
`
func (q *Queries) GetAllProjects(ctx context.Context) ([]Project, error) {
rows, err := q.db.QueryContext(ctx, getAllProjects)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Project
for rows.Next() {
var i Project
if err := rows.Scan(
&i.ProjectID,
&i.TeamID,
&i.CreatedAt,
&i.Name,
&i.Owner,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getAllProjectsForTeam = `-- name: GetAllProjectsForTeam :many
SELECT project_id, team_id, created_at, name, owner FROM project WHERE team_id = $1
`
func (q *Queries) GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) ([]Project, error) {
rows, err := q.db.QueryContext(ctx, getAllProjectsForTeam, teamID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Project
for rows.Next() {
var i Project
if err := rows.Scan(
&i.ProjectID,
&i.TeamID,
&i.CreatedAt,
&i.Name,
&i.Owner,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getProjectByID = `-- name: GetProjectByID :one
SELECT project_id, team_id, created_at, name, owner FROM project WHERE project_id = $1
`
func (q *Queries) GetProjectByID(ctx context.Context, projectID uuid.UUID) (Project, error) {
row := q.db.QueryRowContext(ctx, getProjectByID, projectID)
var i Project
err := row.Scan(
&i.ProjectID,
&i.TeamID,
&i.CreatedAt,
&i.Name,
&i.Owner,
)
return i, err
}
const updateProjectNameByID = `-- name: UpdateProjectNameByID :one
UPDATE project SET name = $2 WHERE project_id = $1 RETURNING project_id, team_id, created_at, name, owner
`
type UpdateProjectNameByIDParams struct {
ProjectID uuid.UUID `json:"project_id"`
Name string `json:"name"`
}
func (q *Queries) UpdateProjectNameByID(ctx context.Context, arg UpdateProjectNameByIDParams) (Project, error) {
row := q.db.QueryRowContext(ctx, updateProjectNameByID, arg.ProjectID, arg.Name)
var i Project
err := row.Scan(
&i.ProjectID,
&i.TeamID,
&i.CreatedAt,
&i.Name,
&i.Owner,
)
return i, err
}

View File

@ -1,75 +0,0 @@
// Code generated by sqlc. DO NOT EDIT.
// source: team_member.sql
package pg
import (
"context"
"time"
"github.com/google/uuid"
)
const createTeamMember = `-- name: CreateTeamMember :one
INSERT INTO team_member (team_id, user_id, addedDate) VALUES ($1, $2, $3)
RETURNING team_member_id, team_id, user_id, addeddate
`
type CreateTeamMemberParams struct {
TeamID uuid.UUID `json:"team_id"`
UserID uuid.UUID `json:"user_id"`
Addeddate time.Time `json:"addeddate"`
}
func (q *Queries) CreateTeamMember(ctx context.Context, arg CreateTeamMemberParams) (TeamMember, error) {
row := q.db.QueryRowContext(ctx, createTeamMember, arg.TeamID, arg.UserID, arg.Addeddate)
var i TeamMember
err := row.Scan(
&i.TeamMemberID,
&i.TeamID,
&i.UserID,
&i.Addeddate,
)
return i, err
}
const deleteTeamMemberByUserID = `-- name: DeleteTeamMemberByUserID :exec
DELETE FROM team_member WHERE user_id = $1
`
func (q *Queries) DeleteTeamMemberByUserID(ctx context.Context, userID uuid.UUID) error {
_, err := q.db.ExecContext(ctx, deleteTeamMemberByUserID, userID)
return err
}
const getTeamMembersForTeamID = `-- name: GetTeamMembersForTeamID :many
SELECT team_member_id, team_id, user_id, addeddate FROM team_member WHERE team_id = $1
`
func (q *Queries) GetTeamMembersForTeamID(ctx context.Context, teamID uuid.UUID) ([]TeamMember, error) {
rows, err := q.db.QueryContext(ctx, getTeamMembersForTeamID, teamID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []TeamMember
for rows.Next() {
var i TeamMember
if err := rows.Scan(
&i.TeamMemberID,
&i.TeamID,
&i.UserID,
&i.Addeddate,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}

View File

@ -1,17 +0,0 @@
-- name: GetAllProjects :many
SELECT * FROM project;
-- name: GetAllProjectsForTeam :many
SELECT * FROM project WHERE team_id = $1;
-- name: GetProjectByID :one
SELECT * FROM project WHERE project_id = $1;
-- name: CreateProject :one
INSERT INTO project(owner, team_id, created_at, name) VALUES ($1, $2, $3, $4) RETURNING *;
-- name: UpdateProjectNameByID :one
UPDATE project SET name = $2 WHERE project_id = $1 RETURNING *;
-- name: DeleteProjectByID :exec
DELETE FROM project WHERE project_id = $1;

View File

@ -1,9 +0,0 @@
-- name: CreateTeamMember :one
INSERT INTO team_member (team_id, user_id, addedDate) VALUES ($1, $2, $3)
RETURNING *;
-- name: GetTeamMembersForTeamID :many
SELECT * FROM team_member WHERE team_id = $1;
-- name: DeleteTeamMemberByUserID :exec
DELETE FROM team_member WHERE user_id = $1;

View File

@ -1,13 +0,0 @@
package router
type ErrExpiredToken struct{}
func (r *ErrExpiredToken) Error() string {
return "token is expired"
}
type ErrMalformedToken struct{}
func (r *ErrMalformedToken) Error() string {
return "token is malformed"
}

View File

@ -1,7 +0,0 @@
package router
import "github.com/jordanknott/project-citadel/api/pg"
type CitadelHandler struct {
repo pg.Repository
}

View File

@ -1,37 +0,0 @@
package router
import (
"github.com/dgrijalva/jwt-go"
)
type AccessTokenClaims struct {
UserID string `json:"userId"`
jwt.StandardClaims
}
type RefreshTokenClaims struct {
UserID string `json:"userId"`
jwt.StandardClaims
}
type LoginRequestData struct {
Username string
Password string
}
type LoginResponseData struct {
AccessToken string `json:"accessToken"`
}
type LogoutResponseData struct {
Status string `json:"status"`
}
type RefreshTokenResponseData struct {
AccessToken string `json:"accessToken"`
}
type AvatarUploadResponseData struct {
UserID string `json:"userID"`
URL string `json:"url"`
}

View File

@ -1,108 +0,0 @@
package router
import (
"database/sql"
"encoding/json"
"io/ioutil"
"net/http"
"time"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/go-chi/cors"
"github.com/google/uuid"
"github.com/jmoiron/sqlx"
log "github.com/sirupsen/logrus"
"github.com/jordanknott/project-citadel/api/graph"
"github.com/jordanknott/project-citadel/api/pg"
)
func (h *CitadelHandler) PingHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pong"))
}
func (h *CitadelHandler) ProfileImageUpload(w http.ResponseWriter, r *http.Request) {
log.Info("preparing to upload file")
userID, ok := r.Context().Value("userID").(uuid.UUID)
if !ok {
log.Error("not a valid uuid")
w.WriteHeader(http.StatusInternalServerError)
return
}
// Parse our multipart form, 10 << 20 specifies a maximum
// upload of 10 MB files.
r.ParseMultipartForm(10 << 20)
file, handler, err := r.FormFile("file")
if err != nil {
log.WithError(err).Error("issue while uploading file")
return
}
defer file.Close()
log.WithFields(log.Fields{"filename": handler.Filename, "size": handler.Size, "header": handler.Header}).Info("file metadata")
fileBytes, err := ioutil.ReadAll(file)
if err != nil {
log.WithError(err).Error("while reading file")
return
}
err = ioutil.WriteFile("uploads/"+handler.Filename, fileBytes, 0644)
if err != nil {
log.WithError(err).Error("while reading file")
return
}
h.repo.UpdateUserAccountProfileAvatarURL(r.Context(), pg.UpdateUserAccountProfileAvatarURLParams{UserID: userID, ProfileAvatarUrl: sql.NullString{String: "http://localhost:3333/uploads/" + handler.Filename, Valid: true}})
// return that we have successfully uploaded our file!
log.Info("file uploaded")
json.NewEncoder(w).Encode(AvatarUploadResponseData{URL: "http://localhost:3333/uploads/" + handler.Filename, UserID: userID.String()})
}
func NewRouter(db *sqlx.DB) (chi.Router, error) {
formatter := new(log.TextFormatter)
formatter.TimestampFormat = "02-01-2006 15:04:05"
formatter.FullTimestamp = true
routerLogger := log.New()
routerLogger.SetLevel(log.DebugLevel)
routerLogger.Formatter = formatter
r := chi.NewRouter()
cors := cors.New(cors.Options{
// AllowedOrigins: []string{"https://foo.com"}, // Use this to allow specific origin hosts
AllowedOrigins: []string{"*"},
// AllowOriginFunc: func(r *http.Request, origin string) bool { return true },
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token", "Cookie"},
ExposedHeaders: []string{"Link"},
AllowCredentials: true,
MaxAge: 300, // Maximum value not ignored by any of major browsers
})
r.Use(cors.Handler)
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(NewStructuredLogger(routerLogger))
r.Use(middleware.Recoverer)
r.Use(middleware.Timeout(60 * time.Second))
repository := pg.NewRepository(db)
citadelHandler := CitadelHandler{repository}
r.Group(func(mux chi.Router) {
mux.Mount("/auth", authResource{}.Routes(citadelHandler))
mux.Handle("/__graphql", graph.NewPlaygroundHandler("/graphql"))
var imgServer = http.FileServer(http.Dir("./uploads/"))
mux.Mount("/uploads/", http.StripPrefix("/uploads/", imgServer))
})
r.Group(func(mux chi.Router) {
mux.Use(AuthenticationMiddleware)
mux.Post("/users/me/avatar", citadelHandler.ProfileImageUpload)
mux.Get("/ping", citadelHandler.PingHandler)
mux.Handle("/graphql", graph.NewHandler(repository))
})
return r, nil
}

View File

@ -1,10 +1,10 @@
version: "1"
packages:
- name: "pg"
- name: "db"
emit_json_tags: true
emit_prepared_queries: false
emit_interface: true
path: "pg"
queries: "./query/"
path: "internal/db"
queries: "./internal/db/query/"
schema: "./migrations/"

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>{{.Username}} has been registered</title>
</head>
<body>
<p>Hi <b>{{.Username}}</b>, thanks for registering at {{.AppName}}!</p>
<p>© {{.Year}} <a target="_blank" rel="noopener noreferrer" href="{{.AppURL}}">{{.AppName}}</a></p>
</body>
</html>

View File

@ -0,0 +1,501 @@
{
"header": {
"reportVersion": 1,
"event": "Allocation failed - JavaScript heap out of memory",
"trigger": "FatalError",
"filename": "report.20200630.191315.2921.0.001.json",
"dumpEventTime": "2020-06-30T19:13:15Z",
"dumpEventTimeStamp": "1593562395582",
"processId": 2921,
"cwd": "/home/jordan/Projects/project-citadel/web",
"commandLine": [
"/usr/bin/node",
"/home/jordan/.config/coc/extensions/node_modules/coc-tsserver/bin/tsserverForkStart",
"/home/jordan/Projects/project-citadel/web/node_modules/typescript/lib/tsserver.js",
"--allowLocalPluginLoads",
"--useInferredProjectPerProjectRoot",
"--cancellationPipeName",
"/tmp/coc-nvim-tscancellation-de3655a46bee8a6c5461.sock*",
"--npmLocation",
"\"/usr/bin/npm\"",
"--noGetErrOnBackgroundUpdate",
"--validateDefaultNpmLocation"
],
"nodejsVersion": "v13.8.0",
"glibcVersionRuntime": "2.30",
"glibcVersionCompiler": "2.30",
"wordSize": 64,
"arch": "x64",
"platform": "linux",
"componentVersions": {
"node": "13.8.0",
"v8": "7.9.317.25-node.28",
"uv": "1.34.1",
"zlib": "1.2.11",
"brotli": "1.0.7",
"ares": "1.15.0",
"modules": "79",
"nghttp2": "1.39.2",
"napi": "5",
"llhttp": "2.0.4",
"openssl": "1.1.1d",
"cldr": "36.0",
"icu": "65.1",
"tz": "2019c",
"unicode": "12.1"
},
"release": {
"name": "node",
"headersUrl": "https://nodejs.org/download/release/v13.8.0/node-v13.8.0-headers.tar.gz",
"sourceUrl": "https://nodejs.org/download/release/v13.8.0/node-v13.8.0.tar.gz"
},
"osName": "Linux",
"osRelease": "4.19.101-1-lts",
"osVersion": "#1 SMP Sat, 01 Feb 2020 16:35:36 +0000",
"osMachine": "x86_64",
"cpus": [
{
"model": "Intel(R) Core(TM) i7-6500U CPU @ 2.50GHz",
"speed": 928,
"user": 17517600,
"nice": 5000,
"sys": 2658800,
"idle": 54442200,
"irq": 146200
},
{
"model": "Intel(R) Core(TM) i7-6500U CPU @ 2.50GHz",
"speed": 1192,
"user": 16529500,
"nice": 3900,
"sys": 2713400,
"idle": 55020300,
"irq": 289200
},
{
"model": "Intel(R) Core(TM) i7-6500U CPU @ 2.50GHz",
"speed": 1197,
"user": 16764400,
"nice": 3600,
"sys": 2584400,
"idle": 55071500,
"irq": 157600
},
{
"model": "Intel(R) Core(TM) i7-6500U CPU @ 2.50GHz",
"speed": 1196,
"user": 16700100,
"nice": 2500,
"sys": 2588500,
"idle": 55230400,
"irq": 152300
}
],
"networkInterfaces": [
{
"name": "lo",
"internal": true,
"mac": "00:00:00:00:00:00",
"address": "127.0.0.1",
"netmask": "255.0.0.0",
"family": "IPv4"
},
{
"name": "wlp3s0",
"internal": false,
"mac": "7c:b0:c2:fe:93:86",
"address": "192.168.43.5",
"netmask": "255.255.255.0",
"family": "IPv4"
},
{
"name": "docker0",
"internal": false,
"mac": "02:42:64:b5:eb:c5",
"address": "172.17.0.1",
"netmask": "255.255.0.0",
"family": "IPv4"
},
{
"name": "br-e929893879ec",
"internal": false,
"mac": "02:42:77:2b:e8:70",
"address": "172.19.0.1",
"netmask": "255.255.0.0",
"family": "IPv4"
},
{
"name": "lo",
"internal": true,
"mac": "00:00:00:00:00:00",
"address": "::1",
"netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
"family": "IPv6",
"scopeid": 0
},
{
"name": "wlp3s0",
"internal": false,
"mac": "7c:b0:c2:fe:93:86",
"address": "2600:100b:b011:583b:7eb0:c2ff:fefe:9386",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 0
},
{
"name": "wlp3s0",
"internal": false,
"mac": "7c:b0:c2:fe:93:86",
"address": "fe80::7eb0:c2ff:fefe:9386",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 3
},
{
"name": "docker0",
"internal": false,
"mac": "02:42:64:b5:eb:c5",
"address": "fe80::42:64ff:feb5:ebc5",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 5
},
{
"name": "br-e929893879ec",
"internal": false,
"mac": "02:42:77:2b:e8:70",
"address": "fe80::42:77ff:fe2b:e870",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 6
},
{
"name": "veth9f2842e",
"internal": false,
"mac": "02:97:03:9d:e0:38",
"address": "fe80::97:3ff:fe9d:e038",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 8
},
{
"name": "veth009c2b9",
"internal": false,
"mac": "e2:7b:8b:06:cb:6a",
"address": "fe80::e07b:8bff:fe06:cb6a",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 10
},
{
"name": "vethcfe1d35",
"internal": false,
"mac": "de:ee:14:2d:7e:e7",
"address": "fe80::dcee:14ff:fe2d:7ee7",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 12
},
{
"name": "vethff03ba3",
"internal": false,
"mac": "0e:99:63:87:38:fc",
"address": "fe80::c99:63ff:fe87:38fc",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 14
}
],
"host": "archlinux"
},
"javascriptStack": {
"message": "No stack.",
"stack": [
"Unavailable."
]
},
"nativeStack": [
{
"pc": "0x000055daf3226e8a",
"symbol": "report::TriggerNodeReport(v8::Isolate*, node::Environment*, char const*, char const*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, v8::Local<v8::String>) [/usr/bin/node]"
},
{
"pc": "0x000055daf30e7d48",
"symbol": "node::OnFatalError(char const*, char const*) [/usr/bin/node]"
},
{
"pc": "0x000055daf325b382",
"symbol": "v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [/usr/bin/node]"
},
{
"pc": "0x000055daf325b5e8",
"symbol": "v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/usr/bin/node]"
},
{
"pc": "0x000055daf33e5906",
"symbol": " [/usr/bin/node]"
},
{
"pc": "0x000055daf33e5a49",
"symbol": " [/usr/bin/node]"
},
{
"pc": "0x000055daf33f822d",
"symbol": "v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [/usr/bin/node]"
},
{
"pc": "0x000055daf33f8f58",
"symbol": "v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/usr/bin/node]"
},
{
"pc": "0x000055daf33fb48c",
"symbol": "v8::internal::Heap::AllocateRawWithLightRetrySlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/usr/bin/node]"
},
{
"pc": "0x000055daf33fb4f4",
"symbol": "v8::internal::Heap::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/usr/bin/node]"
},
{
"pc": "0x000055daf33c0396",
"symbol": "v8::internal::Factory::AllocateRawWithImmortalMap(int, v8::internal::AllocationType, v8::internal::Map, v8::internal::AllocationAlignment) [/usr/bin/node]"
},
{
"pc": "0x000055daf33c8fd0",
"symbol": "v8::internal::Factory::NewRawTwoByteString(int, v8::internal::AllocationType) [/usr/bin/node]"
},
{
"pc": "0x000055daf360c29e",
"symbol": "v8::internal::String::SlowFlatten(v8::internal::Isolate*, v8::internal::Handle<v8::internal::ConsString>, v8::internal::AllocationType) [/usr/bin/node]"
},
{
"pc": "0x000055daf360d86c",
"symbol": "v8::internal::String::SlowEquals(v8::internal::Isolate*, v8::internal::Handle<v8::internal::String>, v8::internal::Handle<v8::internal::String>) [/usr/bin/node]"
},
{
"pc": "0x000055daf37375ab",
"symbol": "v8::internal::Runtime_StringEqual(int, unsigned long*, v8::internal::Isolate*) [/usr/bin/node]"
},
{
"pc": "0x000055daf3a4abd9",
"symbol": " [/usr/bin/node]"
}
],
"javascriptHeap": {
"totalMemory": 2152394752,
"totalCommittedMemory": 2150767368,
"usedMemory": 2135044960,
"availableMemory": 47808640,
"memoryLimit": 2197815296,
"heapSpaces": {
"read_only_space": {
"memorySize": 262144,
"committedMemory": 33328,
"capacity": 33040,
"used": 33040,
"available": 0
},
"new_space": {
"memorySize": 2097152,
"committedMemory": 1114192,
"capacity": 1047424,
"used": 66000,
"available": 981424
},
"old_space": {
"memorySize": 2049843200,
"committedMemory": 2049732712,
"capacity": 2037853328,
"used": 2037494080,
"available": 359248
},
"code_space": {
"memorySize": 10129408,
"committedMemory": 9828000,
"capacity": 8619200,
"used": 8619200,
"available": 0
},
"map_space": {
"memorySize": 1052672,
"committedMemory": 1048960,
"capacity": 735040,
"used": 735040,
"available": 0
},
"large_object_space": {
"memorySize": 88633344,
"committedMemory": 88633344,
"capacity": 87782784,
"used": 87782784,
"available": 0
},
"code_large_object_space": {
"memorySize": 376832,
"committedMemory": 376832,
"capacity": 314816,
"used": 314816,
"available": 0
},
"new_large_object_space": {
"memorySize": 0,
"committedMemory": 0,
"capacity": 1047424,
"used": 0,
"available": 1047424
}
}
},
"resourceUsage": {
"userCpuSeconds": 2203.37,
"kernelCpuSeconds": 38.1817,
"cpuConsumptionPercent": 34.1127,
"maxRss": 2532282368,
"pageFaults": {
"IORequired": 22,
"IONotRequired": 7984554
},
"fsActivity": {
"reads": 194080,
"writes": 24
}
},
"uvthreadResourceUsage": {
"userCpuSeconds": 1428.9,
"kernelCpuSeconds": 15.9159,
"cpuConsumptionPercent": 21.9878,
"fsActivity": {
"reads": 194080,
"writes": 24
}
},
"libuv": [
],
"environmentVariables": {
"COLORTERM": "truecolor",
"DBUS_SESSION_BUS_ADDRESS": "unix:path=/run/user/1000/bus",
"DESKTOP_SESSION": "awesome",
"DISPLAY": ":0.0",
"EDITOR": "nano",
"FZF_DEFAULT_COMMAND": "ag --hidden --ignore .git -g \"\"",
"GDMSESSION": "awesome",
"GO111MODULE": "on",
"GOBIN": "/home/jordan/go/bin",
"GREP_COLOR": "37;45",
"GREP_COLORS": "mt=37;45",
"GTK_MODULES": "canberra-gtk-module",
"HOME": "/home/jordan",
"LANG": "C",
"LESS": "-F -g -i -M -R -S -w -X -z-4",
"LESS_TERMCAP_mb": "\u001b[01;31m",
"LESS_TERMCAP_md": "\u001b[01;31m",
"LESS_TERMCAP_me": "\u001b[0m",
"LESS_TERMCAP_se": "\u001b[0m",
"LESS_TERMCAP_so": "\u001b[00;47;30m",
"LESS_TERMCAP_ue": "\u001b[0m",
"LESS_TERMCAP_us": "\u001b[01;32m",
"LOGNAME": "jordan",
"LS_COLORS": "rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:",
"MAIL": "/var/spool/mail/jordan",
"OLDPWD": "/home/jordan/Projects/project-citadel/web",
"PAGER": "less",
"PATH": "/home/jordan/.local/bin:/usr/local/bin:/usr/local/sbin:/home/nightwolf/Programs/cmake/bin:/home/nightwolf/Programs/idea-IU-163.13906.18/bin:/home/nightwolf/Programs/wpcli:/home/nightwolf/neovim/bin:/home/nightwolf/Programs/Postman:/home/nightwolf/Programs/Android_SDK/tools/bin:/home/nightwolf/Development/PhantomJS/bin:/home/nightwolf/Programs/node/bin:/home/nightwolf/pyenv/bin:/home/nightwolf/Programs/vv:/usr/bin:/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/usr/local/go/bin:/home/jordan/go/bin:/home/jordan/.garden/bin:~/Programs/node/bin:~/.utilities:/home/jordan/.fzf/bin:/home/jordan/.gem/ruby/2.6.0/bin:/home/jordan/.garden/bin:~/Programs/node/bin:~/.utilities:/home/jordan/.gem/ruby/2.6.0/bin",
"PWD": "/home/jordan/Projects/project-citadel/web",
"SHELL": "/usr/bin/zsh",
"SHLVL": "2",
"SSH_AGENT_PID": "2150",
"SSH_AUTH_SOCK": "/tmp/ssh-agent.sock.1000",
"TERM": "xterm-256color",
"TMUX": "/tmp//tmux-1000/default,2216,1",
"TMUX_PANE": "%2",
"USER": "jordan",
"VIRTUALENVWRAPPER_PYTHON": "/usr/bin/python3",
"VIRTUAL_ENV_DISABLE_PROMPT": "12",
"VISUAL": "nano",
"VTE_VERSION": "5602",
"WINDOWID": "6291459",
"WORKON_HOME": "/home/jordan/.virtualenvs",
"XAUTHORITY": "/home/jordan/.Xauthority",
"XDG_GREETER_DATA_DIR": "/var/lib/lightdm-data/jordan",
"XDG_RUNTIME_DIR": "/run/user/1000",
"XDG_SEAT": "seat0",
"XDG_SEAT_PATH": "/org/freedesktop/DisplayManager/Seat0",
"XDG_SESSION_CLASS": "user",
"XDG_SESSION_DESKTOP": "awesome",
"XDG_SESSION_ID": "1",
"XDG_SESSION_PATH": "/org/freedesktop/DisplayManager/Session0",
"XDG_SESSION_TYPE": "x11",
"XDG_VTNR": "7",
"_": "/usr/bin/nvim",
"is_vim": "ps -o state= -o comm= -t '#{pane_tty}' | grep -iqE '^[^TXZ ]+ +(\\S+\\/)?g?(view|n?vim?x?)(diff)?$'",
"tmux_version": "$(tmux -V | sed -En \"s/^tmux ([0-9]+(.[0-9]+)?).*/\\1/p\")",
"LC_MESSAGES": "",
"VIMRUNTIME": "/usr/share/nvim/runtime",
"NVIM_LISTEN_ADDRESS": "/tmp/nvimGEyAE0/0",
"MYVIMRC": "/home/jordan/.config/nvim/init.vim",
"COC_VIMCONFIG": "/home/jordan/.config/nvim",
"COC_DATA_HOME": "/home/jordan/.config/coc",
"TSS_LOG": "-level verbose -file /tmp/coc-nvim-tsc.log",
"NODE_PATH": "/home/jordan/Projects/project-citadel/web/node_modules"
},
"userLimits": {
"core_file_size_blocks": {
"soft": "unlimited",
"hard": "unlimited"
},
"data_seg_size_kbytes": {
"soft": "unlimited",
"hard": "unlimited"
},
"file_size_blocks": {
"soft": "unlimited",
"hard": "unlimited"
},
"max_locked_memory_bytes": {
"soft": 65536,
"hard": 65536
},
"max_memory_size_kbytes": {
"soft": "unlimited",
"hard": "unlimited"
},
"open_files": {
"soft": 524288,
"hard": 524288
},
"stack_size_bytes": {
"soft": 8388608,
"hard": "unlimited"
},
"cpu_time_seconds": {
"soft": "unlimited",
"hard": "unlimited"
},
"max_user_processes": {
"soft": 31138,
"hard": 31138
},
"virtual_memory_kbytes": {
"soft": "unlimited",
"hard": "unlimited"
}
},
"sharedObjects": [
"linux-vdso.so.1",
"/usr/lib/libz.so.1",
"/usr/lib/libcares.so.2",
"/usr/lib/libnghttp2.so.14",
"/usr/lib/libcrypto.so.1.1",
"/usr/lib/libssl.so.1.1",
"/usr/lib/libicui18n.so.65",
"/usr/lib/libicuuc.so.65",
"/usr/lib/libdl.so.2",
"/usr/lib/libstdc++.so.6",
"/usr/lib/libm.so.6",
"/usr/lib/libgcc_s.so.1",
"/usr/lib/libpthread.so.0",
"/usr/lib/libc.so.6",
"/usr/lib/libicudata.so.65",
"/lib64/ld-linux-x86-64.so.2"
]
}

View File

@ -0,0 +1,498 @@
{
"header": {
"reportVersion": 1,
"event": "Allocation failed - JavaScript heap out of memory",
"trigger": "FatalError",
"filename": "report.20200703.192901.99868.0.001.json",
"dumpEventTime": "2020-07-03T19:29:01Z",
"dumpEventTimeStamp": "1593822541435",
"processId": 99868,
"cwd": "/home/jordan/Projects/project-citadel/web",
"commandLine": [
"/usr/bin/node",
"/home/jordan/.config/coc/extensions/node_modules/coc-tsserver/bin/tsserverForkStart",
"/home/jordan/Projects/project-citadel/web/node_modules/typescript/lib/tsserver.js",
"--allowLocalPluginLoads",
"--useInferredProjectPerProjectRoot",
"--cancellationPipeName",
"/tmp/coc-nvim-tscancellation-c8c6c50b271560f15471.sock*",
"--npmLocation",
"\"/usr/bin/npm\"",
"--noGetErrOnBackgroundUpdate",
"--validateDefaultNpmLocation"
],
"nodejsVersion": "v13.8.0",
"glibcVersionRuntime": "2.30",
"glibcVersionCompiler": "2.30",
"wordSize": 64,
"arch": "x64",
"platform": "linux",
"componentVersions": {
"node": "13.8.0",
"v8": "7.9.317.25-node.28",
"uv": "1.34.1",
"zlib": "1.2.11",
"brotli": "1.0.7",
"ares": "1.15.0",
"modules": "79",
"nghttp2": "1.39.2",
"napi": "5",
"llhttp": "2.0.4",
"openssl": "1.1.1d",
"cldr": "36.0",
"icu": "65.1",
"tz": "2019c",
"unicode": "12.1"
},
"release": {
"name": "node",
"headersUrl": "https://nodejs.org/download/release/v13.8.0/node-v13.8.0-headers.tar.gz",
"sourceUrl": "https://nodejs.org/download/release/v13.8.0/node-v13.8.0.tar.gz"
},
"osName": "Linux",
"osRelease": "4.19.101-1-lts",
"osVersion": "#1 SMP Sat, 01 Feb 2020 16:35:36 +0000",
"osMachine": "x86_64",
"cpus": [
{
"model": "Intel(R) Core(TM) i7-6500U CPU @ 2.50GHz",
"speed": 2800,
"user": 49033300,
"nice": 14200,
"sys": 10239800,
"idle": 199777400,
"irq": 1327100
},
{
"model": "Intel(R) Core(TM) i7-6500U CPU @ 2.50GHz",
"speed": 2800,
"user": 46034200,
"nice": 20700,
"sys": 9673500,
"idle": 173927400,
"irq": 721000
},
{
"model": "Intel(R) Core(TM) i7-6500U CPU @ 2.50GHz",
"speed": 2800,
"user": 46463200,
"nice": 15400,
"sys": 9416800,
"idle": 174061400,
"irq": 597100
},
{
"model": "Intel(R) Core(TM) i7-6500U CPU @ 2.50GHz",
"speed": 2800,
"user": 46337200,
"nice": 14900,
"sys": 9474300,
"idle": 174305900,
"irq": 487800
}
],
"networkInterfaces": [
{
"name": "lo",
"internal": true,
"mac": "00:00:00:00:00:00",
"address": "127.0.0.1",
"netmask": "255.0.0.0",
"family": "IPv4"
},
{
"name": "wlp3s0",
"internal": false,
"mac": "7c:b0:c2:fe:93:86",
"address": "192.168.43.5",
"netmask": "255.255.255.0",
"family": "IPv4"
},
{
"name": "docker0",
"internal": false,
"mac": "02:42:84:c7:8c:b4",
"address": "172.17.0.1",
"netmask": "255.255.0.0",
"family": "IPv4"
},
{
"name": "br-e929893879ec",
"internal": false,
"mac": "02:42:c6:97:95:8c",
"address": "172.19.0.1",
"netmask": "255.255.0.0",
"family": "IPv4"
},
{
"name": "lo",
"internal": true,
"mac": "00:00:00:00:00:00",
"address": "::1",
"netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
"family": "IPv6",
"scopeid": 0
},
{
"name": "wlp3s0",
"internal": false,
"mac": "7c:b0:c2:fe:93:86",
"address": "2600:100b:b018:172f:7eb0:c2ff:fefe:9386",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 0
},
{
"name": "wlp3s0",
"internal": false,
"mac": "7c:b0:c2:fe:93:86",
"address": "2600:100b:b009:7105:7eb0:c2ff:fefe:9386",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 0
},
{
"name": "wlp3s0",
"internal": false,
"mac": "7c:b0:c2:fe:93:86",
"address": "fe80::7eb0:c2ff:fefe:9386",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 3
},
{
"name": "docker0",
"internal": false,
"mac": "02:42:84:c7:8c:b4",
"address": "fe80::42:84ff:fec7:8cb4",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 5
},
{
"name": "br-e929893879ec",
"internal": false,
"mac": "02:42:c6:97:95:8c",
"address": "fe80::42:c6ff:fe97:958c",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 6
},
{
"name": "veth861c75f",
"internal": false,
"mac": "de:7f:bb:fc:33:a0",
"address": "fe80::dc7f:bbff:fefc:33a0",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 8
},
{
"name": "veth40b666f",
"internal": false,
"mac": "5e:46:25:1a:79:73",
"address": "fe80::5c46:25ff:fe1a:7973",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 10
},
{
"name": "vetha03af75",
"internal": false,
"mac": "7e:23:c2:9d:e8:db",
"address": "fe80::7c23:c2ff:fe9d:e8db",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 12
},
{
"name": "vethb492e80",
"internal": false,
"mac": "de:af:a3:79:1e:9a",
"address": "fe80::dcaf:a3ff:fe79:1e9a",
"netmask": "ffff:ffff:ffff:ffff::",
"family": "IPv6",
"scopeid": 14
}
],
"host": "archlinux"
},
"javascriptStack": {
"message": "No stack.",
"stack": [
"Unavailable."
]
},
"nativeStack": [
{
"pc": "0x000055c2d7ceae8a",
"symbol": "report::TriggerNodeReport(v8::Isolate*, node::Environment*, char const*, char const*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, v8::Local<v8::String>) [/usr/bin/node]"
},
{
"pc": "0x000055c2d7babd48",
"symbol": "node::OnFatalError(char const*, char const*) [/usr/bin/node]"
},
{
"pc": "0x000055c2d7d1f382",
"symbol": "v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [/usr/bin/node]"
},
{
"pc": "0x000055c2d7d1f5e8",
"symbol": "v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/usr/bin/node]"
},
{
"pc": "0x000055c2d7ea9906",
"symbol": " [/usr/bin/node]"
},
{
"pc": "0x000055c2d7ea9a49",
"symbol": " [/usr/bin/node]"
},
{
"pc": "0x000055c2d7ebc22d",
"symbol": "v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [/usr/bin/node]"
},
{
"pc": "0x000055c2d7ebcf58",
"symbol": "v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/usr/bin/node]"
},
{
"pc": "0x000055c2d7ebf48c",
"symbol": "v8::internal::Heap::AllocateRawWithLightRetrySlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/usr/bin/node]"
},
{
"pc": "0x000055c2d7ebf4f4",
"symbol": "v8::internal::Heap::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/usr/bin/node]"
},
{
"pc": "0x000055c2d7e8499b",
"symbol": "v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationType, v8::internal::AllocationOrigin) [/usr/bin/node]"
},
{
"pc": "0x000055c2d81b5540",
"symbol": "v8::internal::Runtime_AllocateInYoungGeneration(int, unsigned long*, v8::internal::Isolate*) [/usr/bin/node]"
},
{
"pc": "0x000055c2d850ebd9",
"symbol": " [/usr/bin/node]"
}
],
"javascriptHeap": {
"totalMemory": 2152599552,
"totalCommittedMemory": 2151011168,
"usedMemory": 2136228312,
"availableMemory": 47169120,
"memoryLimit": 2197815296,
"heapSpaces": {
"read_only_space": {
"memorySize": 262144,
"committedMemory": 33328,
"capacity": 33040,
"used": 33040,
"available": 0
},
"new_space": {
"memorySize": 2097152,
"committedMemory": 1353624,
"capacity": 1047424,
"used": 336544,
"available": 710880
},
"old_space": {
"memorySize": 2037784576,
"committedMemory": 2037430360,
"capacity": 2026077792,
"used": 2025882720,
"available": 195072
},
"code_space": {
"memorySize": 6721536,
"committedMemory": 6463424,
"capacity": 5718144,
"used": 5718144,
"available": 0
},
"map_space": {
"memorySize": 1052672,
"committedMemory": 1048960,
"capacity": 737680,
"used": 737680,
"available": 0
},
"large_object_space": {
"memorySize": 104304640,
"committedMemory": 104304640,
"capacity": 103205528,
"used": 103205528,
"available": 0
},
"code_large_object_space": {
"memorySize": 376832,
"committedMemory": 376832,
"capacity": 314656,
"used": 314656,
"available": 0
},
"new_large_object_space": {
"memorySize": 0,
"committedMemory": 0,
"capacity": 1047424,
"used": 0,
"available": 1047424
}
}
},
"resourceUsage": {
"userCpuSeconds": 2108.75,
"kernelCpuSeconds": 34.8424,
"cpuConsumptionPercent": 31.044,
"maxRss": 2328186880,
"pageFaults": {
"IORequired": 32,
"IONotRequired": 6292470
},
"fsActivity": {
"reads": 250312,
"writes": 24
}
},
"uvthreadResourceUsage": {
"userCpuSeconds": 1298.13,
"kernelCpuSeconds": 16.4946,
"cpuConsumptionPercent": 19.0388,
"fsActivity": {
"reads": 250312,
"writes": 24
}
},
"libuv": [
],
"environmentVariables": {
"COLORTERM": "truecolor",
"DBUS_SESSION_BUS_ADDRESS": "unix:path=/run/user/1000/bus",
"DESKTOP_SESSION": "awesome",
"DISPLAY": ":0.0",
"EDITOR": "nano",
"FZF_DEFAULT_COMMAND": "ag --hidden --ignore .git -g \"\"",
"GDMSESSION": "awesome",
"GO111MODULE": "on",
"GOBIN": "/home/jordan/go/bin",
"GREP_COLOR": "37;45",
"GREP_COLORS": "mt=37;45",
"GTK_MODULES": "canberra-gtk-module",
"HOME": "/home/jordan",
"LANG": "C",
"LESS": "-F -g -i -M -R -S -w -X -z-4",
"LESS_TERMCAP_mb": "\u001b[01;31m",
"LESS_TERMCAP_md": "\u001b[01;31m",
"LESS_TERMCAP_me": "\u001b[0m",
"LESS_TERMCAP_se": "\u001b[0m",
"LESS_TERMCAP_so": "\u001b[00;47;30m",
"LESS_TERMCAP_ue": "\u001b[0m",
"LESS_TERMCAP_us": "\u001b[01;32m",
"LOGNAME": "jordan",
"LS_COLORS": "rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:",
"MAIL": "/var/spool/mail/jordan",
"OLDPWD": "/home/jordan/Projects/project-citadel/web",
"PAGER": "less",
"PATH": "/home/jordan/.local/bin:/usr/local/bin:/usr/local/sbin:/home/nightwolf/Programs/cmake/bin:/home/nightwolf/Programs/idea-IU-163.13906.18/bin:/home/nightwolf/Programs/wpcli:/home/nightwolf/neovim/bin:/home/nightwolf/Programs/Postman:/home/nightwolf/Programs/Android_SDK/tools/bin:/home/nightwolf/Development/PhantomJS/bin:/home/nightwolf/Programs/node/bin:/home/nightwolf/pyenv/bin:/home/nightwolf/Programs/vv:/usr/bin:/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/usr/local/go/bin:/home/jordan/go/bin:/home/jordan/.garden/bin:~/Programs/node/bin:~/.utilities:/home/jordan/.fzf/bin:/home/jordan/.gem/ruby/2.6.0/bin:/home/jordan/.garden/bin:~/Programs/node/bin:~/.utilities:/home/jordan/.gem/ruby/2.6.0/bin",
"PWD": "/home/jordan/Projects/project-citadel/web",
"SHELL": "/usr/bin/zsh",
"SHLVL": "2",
"SSH_AGENT_PID": "1475",
"SSH_AUTH_SOCK": "/tmp/ssh-agent.sock.1000",
"TERM": "xterm-256color",
"TMUX": "/tmp//tmux-1000/default,1930,1",
"TMUX_PANE": "%3",
"USER": "jordan",
"VIRTUALENVWRAPPER_PYTHON": "/usr/bin/python3",
"VIRTUAL_ENV_DISABLE_PROMPT": "12",
"VISUAL": "nano",
"VTE_VERSION": "5602",
"WINDOWID": "6291459",
"WORKON_HOME": "/home/jordan/.virtualenvs",
"XAUTHORITY": "/home/jordan/.Xauthority",
"XDG_GREETER_DATA_DIR": "/var/lib/lightdm-data/jordan",
"XDG_RUNTIME_DIR": "/run/user/1000",
"XDG_SEAT": "seat0",
"XDG_SEAT_PATH": "/org/freedesktop/DisplayManager/Seat0",
"XDG_SESSION_CLASS": "user",
"XDG_SESSION_DESKTOP": "awesome",
"XDG_SESSION_ID": "1",
"XDG_SESSION_PATH": "/org/freedesktop/DisplayManager/Session0",
"XDG_SESSION_TYPE": "x11",
"XDG_VTNR": "7",
"_": "/usr/bin/nvim",
"is_vim": "ps -o state= -o comm= -t '#{pane_tty}' | grep -iqE '^[^TXZ ]+ +(\\S+\\/)?g?(view|n?vim?x?)(diff)?$'",
"tmux_version": "$(tmux -V | sed -En \"s/^tmux ([0-9]+(.[0-9]+)?).*/\\1/p\")",
"LC_MESSAGES": "",
"VIMRUNTIME": "/usr/share/nvim/runtime",
"NVIM_LISTEN_ADDRESS": "/tmp/nvimerL06q/0",
"MYVIMRC": "/home/jordan/.config/nvim/init.vim",
"COC_VIMCONFIG": "/home/jordan/.config/nvim",
"COC_DATA_HOME": "/home/jordan/.config/coc",
"TSS_LOG": "-level verbose -file /tmp/coc-nvim-tsc.log",
"NODE_PATH": "/home/jordan/Projects/project-citadel/web/node_modules"
},
"userLimits": {
"core_file_size_blocks": {
"soft": "unlimited",
"hard": "unlimited"
},
"data_seg_size_kbytes": {
"soft": "unlimited",
"hard": "unlimited"
},
"file_size_blocks": {
"soft": "unlimited",
"hard": "unlimited"
},
"max_locked_memory_bytes": {
"soft": 65536,
"hard": 65536
},
"max_memory_size_kbytes": {
"soft": "unlimited",
"hard": "unlimited"
},
"open_files": {
"soft": 524288,
"hard": 524288
},
"stack_size_bytes": {
"soft": 8388608,
"hard": "unlimited"
},
"cpu_time_seconds": {
"soft": "unlimited",
"hard": "unlimited"
},
"max_user_processes": {
"soft": 31137,
"hard": 31137
},
"virtual_memory_kbytes": {
"soft": "unlimited",
"hard": "unlimited"
}
},
"sharedObjects": [
"linux-vdso.so.1",
"/usr/lib/libz.so.1",
"/usr/lib/libcares.so.2",
"/usr/lib/libnghttp2.so.14",
"/usr/lib/libcrypto.so.1.1",
"/usr/lib/libssl.so.1.1",
"/usr/lib/libicui18n.so.65",
"/usr/lib/libicuuc.so.65",
"/usr/lib/libdl.so.2",
"/usr/lib/libstdc++.so.6",
"/usr/lib/libm.so.6",
"/usr/lib/libgcc_s.so.1",
"/usr/lib/libpthread.so.0",
"/usr/lib/libc.so.6",
"/usr/lib/libicudata.so.65",
"/lib64/ld-linux-x86-64.so.2"
]
}

View File

@ -1,20 +1,57 @@
import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import Admin from 'shared/components/Admin';
import Select from 'shared/components/Select';
import GlobalTopNavbar from 'App/TopNavbar';
import { useUsersQuery, useCreateUserAccountMutation, UsersDocument } from 'shared/generated/graphql';
import {
useUsersQuery,
useDeleteUserAccountMutation,
useCreateUserAccountMutation,
UsersDocument,
UsersQuery,
} from 'shared/generated/graphql';
import Input from 'shared/components/Input';
import styled from 'styled-components';
import Button from 'shared/components/Button';
import { useForm } from 'react-hook-form';
import { usePopup, Popup } from 'shared/components/PopupMenu';
import produce from 'immer';
import updateApolloCache from 'shared/utils/cache';
const DeleteUserWrapper = styled.div`
display: flex;
flex-direction: column;
`;
const DeleteUserDescription = styled.p`
font-size: 14px;
`;
const DeleteUserButton = styled(Button)`
margin-top: 6px;
padding: 6px 12px;
width: 100%;
`;
type DeleteUserPopupProps = {
onDeleteUser: () => void;
};
const DeleteUserPopup: React.FC<DeleteUserPopupProps> = ({ onDeleteUser }) => {
return (
<DeleteUserWrapper>
<DeleteUserDescription>Deleting this user will remove all user related data.</DeleteUserDescription>
<DeleteUserButton onClick={() => onDeleteUser()} color="danger">
Delete user
</DeleteUserButton>
</DeleteUserWrapper>
);
};
type CreateUserData = {
email: string;
username: string;
fullName: string;
initials: string;
password: string;
roleCode: string;
};
const CreateUserForm = styled.form`
display: flex;
@ -34,11 +71,16 @@ const InputError = styled.span`
color: rgba(${props => props.theme.colors.danger});
font-size: 12px;
`;
type AddUserPopupProps = {
onAddUser: (user: CreateUserData) => void;
};
const AddUserPopup: React.FC<AddUserPopupProps> = ({ onAddUser }) => {
const { register, handleSubmit, errors } = useForm<CreateUserData>();
const { register, handleSubmit, errors, setValue } = useForm<CreateUserData>();
const [role, setRole] = useState<string | null>(null);
register({ name: 'roleCode' }, { required: true });
const createUser = (data: CreateUserData) => {
onAddUser(data);
};
@ -63,6 +105,18 @@ const AddUserPopup: React.FC<AddUserPopupProps> = ({ onAddUser }) => {
variant="alternate"
ref={register({ required: 'Email is required' })}
/>
<Select
label="Role"
value={role}
options={[
{ label: 'Admin', value: 'admin' },
{ label: 'Member', value: 'member' },
]}
onChange={newRole => {
setRole(newRole);
setValue('roleCode', newRole.value);
}}
/>
{errors.email && <InputError>{errors.email.message}</InputError>}
<AddUserInput
floatingLabel
@ -105,6 +159,15 @@ const AdminRoute = () => {
}, []);
const { loading, data } = useUsersQuery();
const { showPopup, hidePopup } = usePopup();
const [deleteUser] = useDeleteUserAccountMutation({
update: (client, response) => {
updateApolloCache<UsersQuery>(client, UsersDocument, cache =>
produce(cache, draftCache => {
draftCache.users = cache.users.filter(u => u.id !== response.data.deleteUserAccount.userAccount.id);
}),
);
},
});
const [createUser] = useCreateUserAccountMutation({
update: (client, createData) => {
const cacheData: any = client.readQuery({
@ -133,8 +196,21 @@ const AdminRoute = () => {
<GlobalTopNavbar projectID={null} onSaveProjectName={() => {}} name={null} />
<Admin
initialTab={1}
users={data.users.map((user: any) => ({ ...user, role: 'TBD' }))}
users={data.users}
onInviteUser={() => {}}
onDeleteUser={($target, userID) => {
showPopup(
$target,
<Popup tab={0} title="Delete user?" onClose={() => hidePopup()}>
<DeleteUserPopup
onDeleteUser={() => {
deleteUser({ variables: { userID } });
hidePopup();
}}
/>
</Popup>,
);
}}
onAddUser={$target => {
showPopup(
$target,

View File

@ -1,11 +1,12 @@
import React, { useState, useContext, useEffect } from 'react';
import TopNavbar from 'shared/components/TopNavbar';
import TopNavbar, { MenuItem } from 'shared/components/TopNavbar';
import styled from 'styled-components/macro';
import DropdownMenu, { ProfileMenu } from 'shared/components/DropdownMenu';
import ProjectSettings, { DeleteConfirm, DELETE_INFO } from 'shared/components/ProjectSettings';
import { useHistory } from 'react-router';
import UserIDContext from 'App/context';
import {
RoleCode,
useMeQuery,
useDeleteProjectMutation,
useGetProjectsQuery,
@ -219,32 +220,38 @@ export const ProjectPopup: React.FC<ProjectPopupProps> = ({ history, name, proje
type GlobalTopNavbarProps = {
nameOnly?: boolean;
projectID: string | null;
onChangeProjectOwner?: (userID: string) => void;
name: string | null;
initialTab?: number;
currentTab?: number;
popupContent?: JSX.Element;
menuType?: Array<string>;
menuType?: Array<MenuItem>;
onChangeRole?: (userID: string, roleCode: RoleCode) => void;
projectMembers?: null | Array<TaskUser>;
onSaveProjectName?: (projectName: string) => void;
onInviteUser?: ($target: React.RefObject<HTMLElement>) => void;
onSetTab?: (tab: number) => void;
onRemoveFromBoard?: (userID: string) => void;
};
const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
initialTab,
currentTab,
onSetTab,
menuType,
projectID,
onChangeProjectOwner,
onChangeRole,
name,
popupContent,
projectMembers,
onInviteUser,
onSaveProjectName,
onRemoveFromBoard,
nameOnly,
}) => {
console.log(popupContent);
const { loading, data } = useMeQuery();
const { showPopup, hidePopup, setTab } = usePopup();
const history = useHistory();
const [currentTab, setCurrentTab] = useState(initialTab);
useEffect(() => {
setCurrentTab(initialTab);
}, [initialTab]);
const { userID, setUserID } = useContext(UserIDContext);
const onLogout = () => {
fetch('http://localhost:3333/auth/logout', {
@ -305,7 +312,12 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
}}
currentTab={currentTab}
user={data ? data.me : null}
onInviteUser={onInviteUser}
onChangeRole={onChangeRole}
onChangeProjectOwner={onChangeProjectOwner}
onNotificationClick={() => {}}
onSetTab={onSetTab}
onRemoveFromBoard={onRemoveFromBoard}
onDashboardClick={() => {
history.push('/');
}}

View File

@ -53,7 +53,7 @@ const Projects = () => {
<GlobalTopNavbar projectID={null} onSaveProjectName={() => {}} name={null} />
{!loading && data && (
<Settings
profile={data.me.profileIcon}
profile={data.me}
onProfileAvatarChange={() => {
if ($fileUpload && $fileUpload.current) {
$fileUpload.current.click();

View File

@ -305,21 +305,20 @@ const Details: React.FC<DetailsProps> = ({
}}
onMemberProfile={($targetRef, memberID) => {
const member = data.findTask.assigned.find(m => m.id === memberID);
const profileIcon = member ? member.profileIcon : null;
showPopup(
$targetRef,
<Popup title={null} onClose={() => {}} tab={0}>
<MiniProfile
profileIcon={profileIcon}
displayName="Jordan Knott"
username="@jordanthedev"
bio="None"
onRemoveFromTask={() => {
unassignTask({ variables: { taskID: data.findTask.id, userID: userID ?? '' } });
}}
/>
</Popup>,
);
if (member) {
showPopup(
$targetRef,
<Popup title={null} onClose={() => {}} tab={0}>
<MiniProfile
user={member}
bio="None"
onRemoveFromTask={() => {
unassignTask({ variables: { taskID: data.findTask.id, userID: userID ?? '' } });
}}
/>
</Popup>,
);
}
}}
onOpenAddMemberPopup={(task, $targetRef) => {
showPopup(

View File

@ -6,8 +6,12 @@ import GlobalTopNavbar, { ProjectPopup } from 'App/TopNavbar';
import styled, { css } from 'styled-components/macro';
import { Bolt, ToggleOn, Tags, CheckCircle, Sort, Filter } from 'shared/icons';
import { usePopup, Popup } from 'shared/components/PopupMenu';
import { useParams, Route, useRouteMatch, useHistory, RouteComponentProps } from 'react-router-dom';
import { useParams, Route, useRouteMatch, useHistory, RouteComponentProps, useLocation } from 'react-router-dom';
import {
useSetProjectOwnerMutation,
useUpdateProjectMemberRoleMutation,
useCreateProjectMemberMutation,
useDeleteProjectMemberMutation,
useSetTaskCompleteMutation,
useToggleTaskLabelMutation,
useUpdateProjectNameMutation,
@ -30,6 +34,7 @@ import {
useUnassignTaskMutation,
useUpdateTaskDueDateMutation,
FindProjectQuery,
useUsersQuery,
} from 'shared/generated/graphql';
import TaskAssignee from 'shared/components/TaskAssignee';
@ -48,25 +53,51 @@ import Details from './Details';
import { useApolloClient } from '@apollo/react-hooks';
import UserIDContext from 'App/context';
import DueDateManager from 'shared/components/DueDateManager';
import Input from 'shared/components/Input';
import Member from 'shared/components/Member';
const getCacheData = (client: any, projectID: string) => {
const cacheData: FindProjectQuery = client.readQuery({
query: FindProjectDocument,
variables: {
projectId: projectID,
},
});
return cacheData;
const SearchInput = styled(Input)`
margin: 0;
`;
const UserMember = styled(Member)`
padding: 4px 0;
cursor: pointer;
&:hover {
background: rgba(${props => props.theme.colors.bg.primary}, 0.4);
}
border-radius: 6px;
`;
const MemberList = styled.div`
margin: 8px 0;
`;
type UserManagementPopupProps = {
users: Array<User>;
projectMembers: Array<TaskUser>;
onAddProjectMember: (userID: string) => void;
};
const writeCacheData = (client: any, projectID: string, cacheData: any, newData: any) => {
client.writeQuery({
query: FindProjectDocument,
variables: {
projectId: projectID,
},
data: { ...cacheData, findProject: newData },
});
const UserManagementPopup: React.FC<UserManagementPopupProps> = ({ users, projectMembers, onAddProjectMember }) => {
return (
<Popup tab={0} title="Invite a user">
<SearchInput width="100%" variant="alternate" placeholder="Email address or name" name="search" />
<MemberList>
{users
.filter(u => u.id !== projectMembers.find(p => p.id === u.id)?.id)
.map(user => (
<UserMember
key={user.id}
onCardMemberClick={() => onAddProjectMember(user.id)}
showName
member={user}
taskID=""
/>
))}
</MemberList>
</Popup>
);
};
type TaskRouteProps = {
@ -300,6 +331,7 @@ const Project = () => {
},
});
const [updateTaskGroupLocation] = useUpdateTaskGroupLocationMutation({});
const [updateProjectMemberRole] = useUpdateProjectMemberRoleMutation();
const [deleteTaskGroup] = useDeleteTaskGroupMutation({
onCompleted: deletedTaskGroupData => {},
@ -492,9 +524,39 @@ const Project = () => {
});
const [setTaskComplete] = useSetTaskCompleteMutation();
const [createProjectMember] = useCreateProjectMemberMutation({
update: (client, response) => {
updateApolloCache<FindProjectQuery>(
client,
FindProjectDocument,
cache =>
produce(cache, draftCache => {
draftCache.findProject.members.push({ ...response.data.createProjectMember.member });
}),
{ projectId: projectID },
);
},
});
const [setProjectOwner] = useSetProjectOwnerMutation();
const [deleteProjectMember] = useDeleteProjectMemberMutation({
update: (client, response) => {
updateApolloCache<FindProjectQuery>(
client,
FindProjectDocument,
cache =>
produce(cache, draftCache => {
draftCache.findProject.members = cache.findProject.members.filter(
m => m.id !== response.data.deleteProjectMember.member.id,
);
}),
{ projectId: projectID },
);
},
});
const client = useApolloClient();
const { userID } = useContext(UserIDContext);
const location = useLocation();
const { showPopup, hidePopup } = usePopup();
const $labelsRef = useRef<HTMLDivElement>(null);
@ -546,12 +608,35 @@ const Project = () => {
return (
<>
<GlobalTopNavbar
onChangeRole={(userID, roleCode) => {
updateProjectMemberRole({ variables: { userID, roleCode, projectID } });
}}
onChangeProjectOwner={uid => {
setProjectOwner({ variables: { ownerID: uid, projectID } });
hidePopup();
}}
onRemoveFromBoard={userID => {
deleteProjectMember({ variables: { userID, projectID } });
hidePopup();
}}
onSaveProjectName={projectName => {
updateProjectName({ variables: { projectID, name: projectName } });
}}
onInviteUser={$target => {
showPopup(
$target,
<UserManagementPopup
onAddProjectMember={userID => {
createProjectMember({ variables: { userID, projectID } });
}}
users={data.users}
projectMembers={data.findProject.members}
/>,
);
}}
popupContent={<ProjectPopup history={history} name={data.findProject.name} projectID={projectID} />}
menuType={MENU_TYPES.PROJECT_MENU}
initialTab={0}
menuType={[{ name: 'Board', link: location.pathname }]}
currentTab={0}
projectMembers={data.findProject.members}
projectID={projectID}
name={data.findProject.name}
@ -647,27 +732,18 @@ const Project = () => {
onCreateTaskGroup={onCreateList}
onCardMemberClick={($targetRef, taskID, memberID) => {
const member = data.findProject.members.find(m => m.id === memberID);
const profileIcon = member ? member.profileIcon : null;
showPopup(
$targetRef,
<Popup
title={null}
onClose={() => {
hidePopup();
}}
tab={0}
>
if (member) {
showPopup(
$targetRef,
<MiniProfile
profileIcon={profileIcon}
displayName="Jordan Knott"
username="@jordanthedev"
user={member}
bio="None"
onRemoveFromTask={() => {
/* unassignTask({ variables: { taskID: data.findTask.id, userID: userID ?? '' } }); */
}}
/>
</Popup>,
);
/>,
);
}
}}
onChangeTaskGroupName={(taskGroupID, name) => {
updateTaskGroupName({ variables: { taskGroupID, name } });
@ -715,21 +791,18 @@ const Project = () => {
}}
onCardMemberClick={($targetRef, taskID, memberID) => {
const member = data.findProject.members.find(m => m.id === memberID);
const profileIcon = member ? member.profileIcon : null;
showPopup(
$targetRef,
<Popup title={null} onClose={() => hidePopup()} tab={0}>
if (member) {
showPopup(
$targetRef,
<MiniProfile
profileIcon={profileIcon}
displayName="Jordan Knott"
username="@jordanthedev"
bio="None"
user={member}
onRemoveFromTask={() => {
/* unassignTask({ variables: { taskID: data.findTask.id, userID: userID ?? '' } }); */
}}
/>
</Popup>,
);
/>,
);
}
}}
onOpenLabelsPopup={($targetRef, task) => {
taskLabelsRef.current = task.labels;

View File

@ -1,6 +1,7 @@
import React, { useState, useContext, useEffect } from 'react';
import styled from 'styled-components/macro';
import GlobalTopNavbar from 'App/TopNavbar';
import Empty from 'shared/undraw/Empty';
import {
useCreateTeamMutation,
useGetProjectsQuery,
@ -20,7 +21,27 @@ import { useForm } from 'react-hook-form';
import Input from 'shared/components/Input';
import updateApolloCache from 'shared/utils/cache';
import produce from 'immer';
const EmptyStateContent = styled.div`
display: flex;
justy-content: center;
align-items: center;
flex-direction: column;
`;
const EmptyStateTitle = styled.h3`
color: #fff;
font-size: 18px;
`;
const EmptyStatePrompt = styled.span`
color: rgba(${props => props.theme.colors.text.primary});
font-size: 16px;
margin-top: 8px;
`;
const EmptyState = styled(Empty)`
display: block;
margin: 0 auto;
`;
const CreateTeamButton = styled(Button)`
width: 100%;
`;
@ -193,6 +214,10 @@ const AddTeamButton = styled(Button)`
top: 6px;
right: 12px;
`;
const CreateFirstTeam = styled(Button)`
margin-top: 8px;
`;
type ShowNewProject = {
open: boolean;
initialTeamID: null | string;
@ -277,6 +302,39 @@ const Projects = () => {
>
Add Team
</AddTeamButton>
{projectTeams.length === 0 && (
<EmptyStateContent>
<EmptyState width={425} height={425} />
<EmptyStateTitle>No teams exist</EmptyStateTitle>
<EmptyStatePrompt>Create a new team to get started</EmptyStatePrompt>
<CreateFirstTeam
variant="outline"
onClick={$target => {
showPopup(
$target,
<Popup
title="Create team"
tab={0}
onClose={() => {
hidePopup();
}}
>
<CreateTeamForm
onCreateTeam={teamName => {
if (organizationID) {
createTeam({ variables: { name: teamName, organizationID } });
hidePopup();
}
}}
/>
</Popup>,
);
}}
>
Create new team
</CreateFirstTeam>
</EmptyStateContent>
)}
{projectTeams.map(team => {
return (
<div key={team.id}>
@ -286,10 +344,10 @@ const Projects = () => {
<SectionActionLink to={`/teams/${team.id}`}>
<SectionAction variant="outline">Projects</SectionAction>
</SectionActionLink>
<SectionActionLink to="/">
<SectionActionLink to={`/teams/${team.id}/members`}>
<SectionAction variant="outline">Members</SectionAction>
</SectionActionLink>
<SectionActionLink to="/">
<SectionActionLink to={`/teams/${team.id}/settings`}>
<SectionAction variant="outline">Settings</SectionAction>
</SectionActionLink>
</SectionActions>

Some files were not shown because too many files have changed in this diff Show More