diff --git a/api/cmd/citadelctl/main.go b/api/cmd/citadelctl/main.go index b54e8b8..2c88681 100644 --- a/api/cmd/citadelctl/main.go +++ b/api/cmd/citadelctl/main.go @@ -1,16 +1,55 @@ package main import ( + "context" "fmt" - "github.com/jordanknott/project-citadel/api/router" - "time" + "io/ioutil" + + _ "github.com/lib/pq" + + "github.com/jmoiron/sqlx" + "github.com/jordanknott/project-citadel/api/pg" + + "github.com/BurntSushi/toml" + // "github.com/jordanknott/project-citadel/api/router" + // "time" ) +type color struct { + Name string + Color string + Position int +} + +type colors struct { + Color []color +} + func main() { - dur := time.Hour * 24 * 7 * 30 - token, err := router.NewAccessTokenCustomExpiration("21345076-6423-4a00-a6bd-cd9f830e2764", dur) + // 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) } - fmt.Println(token) + + 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)}) + } + } diff --git a/api/data/colors.toml b/api/data/colors.toml new file mode 100644 index 0000000..1b7dc1d --- /dev/null +++ b/api/data/colors.toml @@ -0,0 +1,49 @@ +[[color]] +name = 'green' +color = '#61bd4f' +position = 1 + +[[color]] +name = 'yellow' +color = '#f2d600' +position = 2 + +[[color]] +name = 'orange' +color = '#ff9f1a' +position = 3 + +[[color]] +name = 'red' +color = '#eb5a46' +position = 4 + +[[color]] +name = 'purple' +position = 5 +color = '#c377e0' + +[[color]] +name = 'blue' +position = 6 +color = '#0079bf' + +[[color]] +name = 'sky' +position = 7 +color = '#00c2e0' + +[[color]] +name = 'lime' +position = 8 +color = '#51e898' + +[[color]] +name = 'pink' +position = 9 +color = '#ff78cb' + +[[color]] +name = 'black' +position = 10 +color = '#344563' diff --git a/api/go.mod b/api/go.mod index 5efc2ee..d54cc3c 100644 --- a/api/go.mod +++ b/api/go.mod @@ -4,6 +4,7 @@ go 1.13 require ( github.com/99designs/gqlgen v0.11.3 + github.com/BurntSushi/toml v0.3.1 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 @@ -11,6 +12,7 @@ require ( github.com/google/uuid v1.1.1 github.com/jmoiron/sqlx v1.2.0 github.com/lib/pq v1.0.0 + github.com/pelletier/go-toml v1.8.0 github.com/pkg/errors v0.8.1 github.com/sirupsen/logrus v1.4.2 github.com/urfave/cli v1.20.0 // indirect diff --git a/api/go.sum b/api/go.sum index e5f16ce..b250ca3 100644 --- a/api/go.sum +++ b/api/go.sum @@ -2,6 +2,7 @@ github.com/99designs/gqlgen v0.11.1 h1:QoSL8/AAJ2T3UOeQbdnBR32JcG4pO08+P/g5jdbFk 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/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0= @@ -56,6 +57,8 @@ github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047 h1:zCoDWFD5 github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 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/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/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -112,5 +115,7 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 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.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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= diff --git a/api/graph/generated.go b/api/graph/generated.go index cfac87a..28d4cf0 100644 --- a/api/graph/generated.go +++ b/api/graph/generated.go @@ -37,6 +37,7 @@ type Config struct { } type ResolverRoot interface { + LabelColor() LabelColorResolver Mutation() MutationResolver Project() ProjectResolver ProjectLabel() ProjectLabelResolver @@ -63,6 +64,13 @@ type ComplexityRoot struct { TaskID func(childComplexity int) int } + LabelColor struct { + ColorHex func(childComplexity int) int + ID func(childComplexity int) int + Name func(childComplexity int) int + Position func(childComplexity int) int + } + Mutation struct { AddTaskLabel func(childComplexity int, input *AddTaskLabelInput) int AssignTask func(childComplexity int, input *AssignTaskInput) int @@ -102,9 +110,9 @@ type ComplexityRoot struct { } ProjectLabel struct { - ColorHex func(childComplexity int) int CreatedDate func(childComplexity int) int ID func(childComplexity int) int + LabelColor func(childComplexity int) int Name func(childComplexity int) int } @@ -119,6 +127,7 @@ type ComplexityRoot struct { FindProject func(childComplexity int, input FindProject) int FindTask func(childComplexity int, input FindTask) int FindUser func(childComplexity int, input FindUser) int + LabelColors func(childComplexity int) int Me func(childComplexity int) int Projects func(childComplexity int, input *ProjectsFilter) int TaskGroups func(childComplexity int) int @@ -177,6 +186,9 @@ type ComplexityRoot struct { } } +type LabelColorResolver interface { + ID(ctx context.Context, obj *pg.LabelColor) (uuid.UUID, error) +} type MutationResolver interface { CreateRefreshToken(ctx context.Context, input NewRefreshToken) (*pg.RefreshToken, error) CreateUserAccount(ctx context.Context, input NewUserAccount) (*pg.UserAccount, error) @@ -209,7 +221,7 @@ type ProjectResolver interface { type ProjectLabelResolver interface { ID(ctx context.Context, obj *pg.ProjectLabel) (uuid.UUID, error) - ColorHex(ctx context.Context, obj *pg.ProjectLabel) (string, error) + LabelColor(ctx context.Context, obj *pg.ProjectLabel) (*pg.LabelColor, error) Name(ctx context.Context, obj *pg.ProjectLabel) (*string, error) } type QueryResolver interface { @@ -218,6 +230,7 @@ type QueryResolver interface { FindProject(ctx context.Context, input FindProject) (*pg.Project, error) FindTask(ctx context.Context, input FindTask) (*pg.Task, error) Projects(ctx context.Context, input *ProjectsFilter) ([]pg.Project, error) + LabelColors(ctx context.Context) ([]pg.LabelColor, error) TaskGroups(ctx context.Context) ([]pg.TaskGroup, error) Me(ctx context.Context) (*pg.UserAccount, error) } @@ -296,6 +309,34 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.DeleteTaskPayload.TaskID(childComplexity), true + case "LabelColor.colorHex": + if e.complexity.LabelColor.ColorHex == nil { + break + } + + return e.complexity.LabelColor.ColorHex(childComplexity), true + + case "LabelColor.id": + if e.complexity.LabelColor.ID == nil { + break + } + + return e.complexity.LabelColor.ID(childComplexity), true + + case "LabelColor.name": + if e.complexity.LabelColor.Name == nil { + break + } + + return e.complexity.LabelColor.Name(childComplexity), true + + case "LabelColor.position": + if e.complexity.LabelColor.Position == nil { + break + } + + return e.complexity.LabelColor.Position(childComplexity), true + case "Mutation.addTaskLabel": if e.complexity.Mutation.AddTaskLabel == nil { break @@ -589,13 +630,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Project.Team(childComplexity), true - case "ProjectLabel.colorHex": - if e.complexity.ProjectLabel.ColorHex == nil { - break - } - - return e.complexity.ProjectLabel.ColorHex(childComplexity), true - case "ProjectLabel.createdDate": if e.complexity.ProjectLabel.CreatedDate == nil { break @@ -610,6 +644,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.ProjectLabel.ID(childComplexity), true + case "ProjectLabel.labelColor": + if e.complexity.ProjectLabel.LabelColor == nil { + break + } + + return e.complexity.ProjectLabel.LabelColor(childComplexity), true + case "ProjectLabel.name": if e.complexity.ProjectLabel.Name == nil { break @@ -681,6 +722,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.FindUser(childComplexity, args["input"].(FindUser)), true + case "Query.labelColors": + if e.complexity.Query.LabelColors == nil { + break + } + + return e.complexity.Query.LabelColors(childComplexity), true + case "Query.me": if e.complexity.Query.Me == nil { break @@ -1015,10 +1063,17 @@ scalar UUID type ProjectLabel { id: ID! createdDate: Time! - colorHex: String! + labelColor: LabelColor! name: String } +type LabelColor { + id: ID! + name: String! + position: Float! + colorHex: String! +} + type TaskLabel { id: ID! projectLabelID: UUID! @@ -1116,6 +1171,7 @@ type Query { findProject(input: FindProject!): Project! findTask(input: FindTask!): Task! projects(input: ProjectsFilter): [Project!]! + labelColors: [LabelColor!]! taskGroups: [TaskGroup!]! me: UserAccount! } @@ -1750,6 +1806,142 @@ func (ec *executionContext) _DeleteTaskPayload_taskID(ctx context.Context, field return ec.marshalNString2string(ctx, field.Selections, res) } +func (ec *executionContext) _LabelColor_id(ctx context.Context, field graphql.CollectedField, obj *pg.LabelColor) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "LabelColor", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.LabelColor().ID(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(uuid.UUID) + fc.Result = res + return ec.marshalNID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, field.Selections, res) +} + +func (ec *executionContext) _LabelColor_name(ctx context.Context, field graphql.CollectedField, obj *pg.LabelColor) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "LabelColor", + Field: field, + Args: nil, + IsMethod: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Name, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _LabelColor_position(ctx context.Context, field graphql.CollectedField, obj *pg.LabelColor) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "LabelColor", + Field: field, + Args: nil, + IsMethod: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Position, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(float64) + fc.Result = res + return ec.marshalNFloat2float64(ctx, field.Selections, res) +} + +func (ec *executionContext) _LabelColor_colorHex(ctx context.Context, field graphql.CollectedField, obj *pg.LabelColor) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "LabelColor", + Field: field, + Args: nil, + IsMethod: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.ColorHex, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + func (ec *executionContext) _Mutation_createRefreshToken(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -2921,7 +3113,7 @@ func (ec *executionContext) _ProjectLabel_createdDate(ctx context.Context, field return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) } -func (ec *executionContext) _ProjectLabel_colorHex(ctx context.Context, field graphql.CollectedField, obj *pg.ProjectLabel) (ret graphql.Marshaler) { +func (ec *executionContext) _ProjectLabel_labelColor(ctx context.Context, field graphql.CollectedField, obj *pg.ProjectLabel) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -2938,7 +3130,7 @@ func (ec *executionContext) _ProjectLabel_colorHex(ctx context.Context, field gr ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.ProjectLabel().ColorHex(rctx, obj) + return ec.resolvers.ProjectLabel().LabelColor(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -2950,9 +3142,9 @@ func (ec *executionContext) _ProjectLabel_colorHex(ctx context.Context, field gr } return graphql.Null } - res := resTmp.(string) + res := resTmp.(*pg.LabelColor) fc.Result = res - return ec.marshalNString2string(ctx, field.Selections, res) + return ec.marshalNLabelColor2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐLabelColor(ctx, field.Selections, res) } func (ec *executionContext) _ProjectLabel_name(ctx context.Context, field graphql.CollectedField, obj *pg.ProjectLabel) (ret graphql.Marshaler) { @@ -3320,6 +3512,40 @@ func (ec *executionContext) _Query_projects(ctx context.Context, field graphql.C return ec.marshalNProject2ᚕgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐProjectᚄ(ctx, field.Selections, res) } +func (ec *executionContext) _Query_labelColors(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().LabelColors(rctx) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]pg.LabelColor) + fc.Result = res + return ec.marshalNLabelColor2ᚕgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐLabelColorᚄ(ctx, field.Selections, res) +} + func (ec *executionContext) _Query_taskGroups(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -6228,6 +6454,57 @@ func (ec *executionContext) _DeleteTaskPayload(ctx context.Context, sel ast.Sele return out } +var labelColorImplementors = []string{"LabelColor"} + +func (ec *executionContext) _LabelColor(ctx context.Context, sel ast.SelectionSet, obj *pg.LabelColor) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, labelColorImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("LabelColor") + case "id": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._LabelColor_id(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) + case "name": + out.Values[i] = ec._LabelColor_name(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + case "position": + out.Values[i] = ec._LabelColor_position(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + case "colorHex": + out.Values[i] = ec._LabelColor_colorHex(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var mutationImplementors = []string{"Mutation"} func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler { @@ -6518,7 +6795,7 @@ func (ec *executionContext) _ProjectLabel(ctx context.Context, sel ast.Selection if out.Values[i] == graphql.Null { atomic.AddUint32(&invalids, 1) } - case "colorHex": + case "labelColor": field := field out.Concurrently(i, func() (res graphql.Marshaler) { defer func() { @@ -6526,7 +6803,7 @@ func (ec *executionContext) _ProjectLabel(ctx context.Context, sel ast.Selection ec.Error(ctx, ec.Recover(ctx, r)) } }() - res = ec._ProjectLabel_colorHex(ctx, field, obj) + res = ec._ProjectLabel_labelColor(ctx, field, obj) if res == graphql.Null { atomic.AddUint32(&invalids, 1) } @@ -6681,6 +6958,20 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } return res }) + case "labelColors": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_labelColors(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "taskGroups": field := field out.Concurrently(i, func() (res graphql.Marshaler) { @@ -7499,6 +7790,57 @@ func (ec *executionContext) marshalNInt2int(ctx context.Context, sel ast.Selecti return res } +func (ec *executionContext) marshalNLabelColor2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐLabelColor(ctx context.Context, sel ast.SelectionSet, v pg.LabelColor) graphql.Marshaler { + return ec._LabelColor(ctx, sel, &v) +} + +func (ec *executionContext) marshalNLabelColor2ᚕgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐLabelColorᚄ(ctx context.Context, sel ast.SelectionSet, v []pg.LabelColor) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNLabelColor2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐLabelColor(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + return ret +} + +func (ec *executionContext) marshalNLabelColor2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐLabelColor(ctx context.Context, sel ast.SelectionSet, v *pg.LabelColor) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._LabelColor(ctx, sel, v) +} + func (ec *executionContext) unmarshalNLogoutUser2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐLogoutUser(ctx context.Context, v interface{}) (LogoutUser, error) { return ec.unmarshalInputLogoutUser(ctx, v) } diff --git a/api/graph/schema.graphqls b/api/graph/schema.graphqls index 42ca110..71b57b8 100644 --- a/api/graph/schema.graphqls +++ b/api/graph/schema.graphqls @@ -4,10 +4,17 @@ scalar UUID type ProjectLabel { id: ID! createdDate: Time! - colorHex: String! + labelColor: LabelColor! name: String } +type LabelColor { + id: ID! + name: String! + position: Float! + colorHex: String! +} + type TaskLabel { id: ID! projectLabelID: UUID! @@ -105,6 +112,7 @@ type Query { findProject(input: FindProject!): Project! findTask(input: FindTask!): Task! projects(input: ProjectsFilter): [Project!]! + labelColors: [LabelColor!]! taskGroups: [TaskGroup!]! me: UserAccount! } diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index e857b79..c943788 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -15,6 +15,10 @@ import ( "github.com/vektah/gqlparser/v2/gqlerror" ) +func (r *labelColorResolver) ID(ctx context.Context, obj *pg.LabelColor) (uuid.UUID, error) { + return obj.LabelColorID, nil +} + func (r *mutationResolver) CreateRefreshToken(ctx context.Context, input NewRefreshToken) (*pg.RefreshToken, error) { userID := uuid.MustParse("0183d9ab-d0ed-4c9b-a3df-77a0cdd93dca") refreshCreatedAt := time.Now().UTC() @@ -46,7 +50,23 @@ func (r *mutationResolver) CreateProject(ctx context.Context, input NewProject) } func (r *mutationResolver) CreateProjectLabel(ctx context.Context, input NewProjectLabel) (*pg.ProjectLabel, error) { - panic(fmt.Errorf("not implemented")) + createdAt := time.Now().UTC() + + var name sql.NullString + if input.Name != nil { + name = sql.NullString{ + *input.Name, + true, + } + } else { + name = sql.NullString{ + "", + false, + } + } + projectLabel, err := r.Repository.CreateProjectLabel(ctx, pg.CreateProjectLabelParams{input.ProjectID, + input.LabelColorID, createdAt, name}) + return &projectLabel, err } func (r *mutationResolver) CreateTaskGroup(ctx context.Context, input NewTaskGroup) (*pg.TaskGroup, error) { @@ -234,12 +254,12 @@ func (r *projectLabelResolver) ID(ctx context.Context, obj *pg.ProjectLabel) (uu return obj.ProjectLabelID, nil } -func (r *projectLabelResolver) ColorHex(ctx context.Context, obj *pg.ProjectLabel) (string, error) { +func (r *projectLabelResolver) LabelColor(ctx context.Context, obj *pg.ProjectLabel) (*pg.LabelColor, error) { labelColor, err := r.Repository.GetLabelColorByID(ctx, obj.LabelColorID) if err != nil { - return "", err + return &pg.LabelColor{}, err } - return labelColor.ColorHex, nil + return &labelColor, nil } func (r *projectLabelResolver) Name(ctx context.Context, obj *pg.ProjectLabel) (*string, error) { @@ -304,6 +324,10 @@ func (r *queryResolver) Projects(ctx context.Context, input *ProjectsFilter) ([] return r.Repository.GetAllProjects(ctx) } +func (r *queryResolver) LabelColors(ctx context.Context) ([]pg.LabelColor, error) { + return r.Repository.GetLabelColors(ctx) +} + func (r *queryResolver) TaskGroups(ctx context.Context) ([]pg.TaskGroup, error) { return r.Repository.GetAllTaskGroups(ctx) } @@ -424,6 +448,9 @@ func (r *userAccountResolver) ProfileIcon(ctx context.Context, obj *pg.UserAccou return profileIcon, nil } +// LabelColor returns LabelColorResolver implementation. +func (r *Resolver) LabelColor() LabelColorResolver { return &labelColorResolver{r} } + // Mutation returns MutationResolver implementation. func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} } @@ -454,6 +481,7 @@ func (r *Resolver) Team() TeamResolver { return &teamResolver{r} } // UserAccount returns UserAccountResolver implementation. func (r *Resolver) UserAccount() UserAccountResolver { return &userAccountResolver{r} } +type labelColorResolver struct{ *Resolver } type mutationResolver struct{ *Resolver } type projectResolver struct{ *Resolver } type projectLabelResolver struct{ *Resolver } @@ -464,3 +492,17 @@ type taskGroupResolver struct{ *Resolver } type taskLabelResolver struct{ *Resolver } type teamResolver struct{ *Resolver } type userAccountResolver struct{ *Resolver } + +// !!! WARNING !!! +// The code below was going to be deleted when updating resolvers. It has been copied here so you have +// one last chance to move it out of harms way if you want. There are two reasons this happens: +// - When renaming or deleting a resolver the old code will be put in here. You can safely delete +// it when you're done. +// - You have helper methods in this file. Move them out to keep these resolver files clean. +func (r *projectLabelResolver) ColorHex(ctx context.Context, obj *pg.ProjectLabel) (string, error) { + labelColor, err := r.Repository.GetLabelColorByID(ctx, obj.LabelColorID) + if err != nil { + return "", err + } + return labelColor.ColorHex, nil +} diff --git a/api/migrations/0018_add-name-column-to-label-color.up.sql b/api/migrations/0018_add-name-column-to-label-color.up.sql new file mode 100644 index 0000000..c4b4ff1 --- /dev/null +++ b/api/migrations/0018_add-name-column-to-label-color.up.sql @@ -0,0 +1 @@ +ALTER TABLE label_color ADD COLUMN name TEXT NOT NULL DEFAULT 'needs name'; diff --git a/api/pg/label_color.sql.go b/api/pg/label_color.sql.go index 8aaae7c..5986c7a 100644 --- a/api/pg/label_color.sql.go +++ b/api/pg/label_color.sql.go @@ -9,13 +9,73 @@ import ( "github.com/google/uuid" ) +const createLabelColor = `-- name: CreateLabelColor :one +INSERT INTO label_color (name, color_hex, position) VALUES ($1, $2, $3) + RETURNING label_color_id, color_hex, position, name +` + +type CreateLabelColorParams struct { + Name string `json:"name"` + ColorHex string `json:"color_hex"` + Position float64 `json:"position"` +} + +func (q *Queries) CreateLabelColor(ctx context.Context, arg CreateLabelColorParams) (LabelColor, error) { + row := q.db.QueryRowContext(ctx, createLabelColor, arg.Name, arg.ColorHex, arg.Position) + var i LabelColor + err := row.Scan( + &i.LabelColorID, + &i.ColorHex, + &i.Position, + &i.Name, + ) + return i, err +} + const getLabelColorByID = `-- name: GetLabelColorByID :one -SELECT label_color_id, color_hex, position FROM label_color WHERE label_color_id = $1 +SELECT label_color_id, color_hex, position, name FROM label_color WHERE label_color_id = $1 ` func (q *Queries) GetLabelColorByID(ctx context.Context, labelColorID uuid.UUID) (LabelColor, error) { row := q.db.QueryRowContext(ctx, getLabelColorByID, labelColorID) var i LabelColor - err := row.Scan(&i.LabelColorID, &i.ColorHex, &i.Position) + err := row.Scan( + &i.LabelColorID, + &i.ColorHex, + &i.Position, + &i.Name, + ) return i, err } + +const getLabelColors = `-- name: GetLabelColors :many +SELECT label_color_id, color_hex, position, name FROM label_color +` + +func (q *Queries) GetLabelColors(ctx context.Context) ([]LabelColor, error) { + rows, err := q.db.QueryContext(ctx, getLabelColors) + if err != nil { + return nil, err + } + defer rows.Close() + var items []LabelColor + for rows.Next() { + var i LabelColor + if err := rows.Scan( + &i.LabelColorID, + &i.ColorHex, + &i.Position, + &i.Name, + ); 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 +} diff --git a/api/pg/models.go b/api/pg/models.go index 8081bbd..8de99af 100644 --- a/api/pg/models.go +++ b/api/pg/models.go @@ -13,6 +13,7 @@ type LabelColor struct { LabelColorID uuid.UUID `json:"label_color_id"` ColorHex string `json:"color_hex"` Position float64 `json:"position"` + Name string `json:"name"` } type Organization struct { diff --git a/api/pg/pg.go b/api/pg/pg.go index e5c108e..a5bc575 100644 --- a/api/pg/pg.go +++ b/api/pg/pg.go @@ -26,6 +26,9 @@ type Repository interface { GetProjectLabelsForProject(ctx context.Context, projectID uuid.UUID) ([]ProjectLabel, error) GetProjectLabelByID(ctx context.Context, projectLabelID uuid.UUID) (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 diff --git a/api/pg/querier.go b/api/pg/querier.go index 4265666..62d566a 100644 --- a/api/pg/querier.go +++ b/api/pg/querier.go @@ -9,6 +9,7 @@ import ( ) type Querier interface { + CreateLabelColor(ctx context.Context, arg CreateLabelColorParams) (LabelColor, error) CreateOrganization(ctx context.Context, arg CreateOrganizationParams) (Organization, error) CreateProject(ctx context.Context, arg CreateProjectParams) (Project, error) CreateProjectLabel(ctx context.Context, arg CreateProjectLabelParams) (ProjectLabel, error) @@ -36,6 +37,7 @@ type Querier interface { GetAllUserAccounts(ctx context.Context) ([]UserAccount, error) GetAssignedMembersForTask(ctx context.Context, taskID uuid.UUID) ([]TaskAssigned, error) GetLabelColorByID(ctx context.Context, labelColorID uuid.UUID) (LabelColor, error) + GetLabelColors(ctx context.Context) ([]LabelColor, error) GetProjectByID(ctx context.Context, projectID uuid.UUID) (Project, error) GetProjectLabelByID(ctx context.Context, projectLabelID uuid.UUID) (ProjectLabel, error) GetProjectLabelsForProject(ctx context.Context, projectID uuid.UUID) ([]ProjectLabel, error) diff --git a/api/query/label_color.sql b/api/query/label_color.sql index f041ce8..b2ec454 100644 --- a/api/query/label_color.sql +++ b/api/query/label_color.sql @@ -1,2 +1,9 @@ -- name: GetLabelColorByID :one SELECT * FROM label_color WHERE label_color_id = $1; + +-- name: GetLabelColors :many +SELECT * FROM label_color; + +-- name: CreateLabelColor :one +INSERT INTO label_color (name, color_hex, position) VALUES ($1, $2, $3) + RETURNING *; diff --git a/web/src/App/TopNavbar.tsx b/web/src/App/TopNavbar.tsx index ab34bd9..d35bae2 100644 --- a/web/src/App/TopNavbar.tsx +++ b/web/src/App/TopNavbar.tsx @@ -7,8 +7,9 @@ import { useMeQuery } from 'shared/generated/graphql'; type GlobalTopNavbarProps = { name: string; + projectMembers?: null | Array; }; -const GlobalTopNavbar: React.FC = ({ name }) => { +const GlobalTopNavbar: React.FC = ({ name, projectMembers }) => { const { loading, data } = useMeQuery(); const history = useHistory(); const { userID, setUserID } = useContext(UserIDContext); @@ -50,6 +51,7 @@ const GlobalTopNavbar: React.FC = ({ name }) => { lastName={data ? data.me.lastName : ''} initials={!data ? '' : data.me.profileIcon.initials ?? ''} onNotificationClick={() => console.log('beep')} + projectMembers={projectMembers} onProfileClick={onProfileClick} /> {menu.isOpen && ( diff --git a/web/src/Projects/Project/KanbanBoard/index.tsx b/web/src/Projects/Project/KanbanBoard/index.tsx index 10660ee..17252e1 100644 --- a/web/src/Projects/Project/KanbanBoard/index.tsx +++ b/web/src/Projects/Project/KanbanBoard/index.tsx @@ -12,6 +12,7 @@ type KanbanBoardProps = { onCardCreate: (taskGroupID: string, name: string) => void; onQuickEditorOpen: (e: ContextMenuEvent) => void; onCreateList: (listName: string) => void; + onCardMemberClick: OnCardMemberClick; }; const KanbanBoard: React.FC = ({ @@ -22,6 +23,7 @@ const KanbanBoard: React.FC = ({ onCardDrop, onListDrop, onCreateList, + onCardMemberClick, }) => { const match = useRouteMatch(); const history = useHistory(); @@ -40,6 +42,7 @@ const KanbanBoard: React.FC = ({ onListDrop={onListDrop} {...listsData} onCreateList={onCreateList} + onCardMemberClick={onCardMemberClick} /> ); diff --git a/web/src/Projects/Project/index.tsx b/web/src/Projects/Project/index.tsx index 48a27d7..13f2c1c 100644 --- a/web/src/Projects/Project/index.tsx +++ b/web/src/Projects/Project/index.tsx @@ -18,8 +18,10 @@ import { useAssignTaskMutation, DeleteTaskDocument, FindProjectDocument, + useCreateProjectLabelMutation, } from 'shared/generated/graphql'; +import TaskAssignee from 'shared/components/TaskAssignee'; import QuickCardEditor from 'shared/components/QuickCardEditor'; import ListActions from 'shared/components/ListActions'; import MemberManager from 'shared/components/MemberManager'; @@ -30,6 +32,7 @@ import LabelManager from 'shared/components/PopupMenu/LabelManager'; import LabelEditor from 'shared/components/PopupMenu/LabelEditor'; import produce from 'immer'; import Details from './Details'; +import MiniProfile from 'shared/components/MiniProfile'; type TaskRouteProps = { taskID: string; @@ -52,14 +55,23 @@ const Title = styled.span` font-size: 24px; color: #fff; `; +const ProjectMembers = styled.div` + display: flex; + padding-left: 4px; + padding-top: 4px; + align-items: center; +`; type LabelManagerEditorProps = { labels: Array