diff --git a/api/graph/generated.go b/api/graph/generated.go index 95fe15f..c6ea037 100644 --- a/api/graph/generated.go +++ b/api/graph/generated.go @@ -39,6 +39,7 @@ type Config struct { type ResolverRoot interface { LabelColor() LabelColorResolver Mutation() MutationResolver + Organization() OrganizationResolver Project() ProjectResolver ProjectLabel() ProjectLabelResolver Query() QueryResolver @@ -61,6 +62,16 @@ type ComplexityRoot struct { Total func(childComplexity int) int } + CreateTeamMemberPayload struct { + Team func(childComplexity int) int + TeamMember func(childComplexity int) int + } + + DeleteProjectPayload struct { + Ok func(childComplexity int) int + Project func(childComplexity int) int + } + DeleteTaskChecklistItemPayload struct { Ok func(childComplexity int) int TaskChecklistItem func(childComplexity int) int @@ -95,7 +106,9 @@ type ComplexityRoot struct { CreateTaskChecklistItem func(childComplexity int, input CreateTaskChecklistItem) int CreateTaskGroup func(childComplexity int, input NewTaskGroup) int CreateTeam func(childComplexity int, input NewTeam) int + CreateTeamMember func(childComplexity int, input CreateTeamMember) int CreateUserAccount func(childComplexity int, input NewUserAccount) int + DeleteProject func(childComplexity int, input DeleteProject) int DeleteProjectLabel func(childComplexity int, input DeleteProjectLabel) int DeleteTask func(childComplexity int, input DeleteTaskInput) int DeleteTaskChecklistItem func(childComplexity int, input DeleteTaskChecklistItem) int @@ -119,6 +132,11 @@ type ComplexityRoot struct { UpdateTaskName func(childComplexity int, input UpdateTaskName) int } + Organization struct { + ID func(childComplexity int) int + Name func(childComplexity int) int + } + ProfileIcon struct { BgColor func(childComplexity int) int Initials func(childComplexity int) int @@ -150,15 +168,16 @@ type ComplexityRoot struct { } Query 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 - Teams func(childComplexity int) int - Users func(childComplexity int) int + 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 + Organizations func(childComplexity int) int + Projects func(childComplexity int, input *ProjectsFilter) int + TaskGroups func(childComplexity int) int + Teams func(childComplexity int) int + Users func(childComplexity int) int } RefreshToken struct { @@ -221,6 +240,7 @@ type ComplexityRoot struct { Team struct { CreatedAt func(childComplexity int) int ID func(childComplexity int) int + Members func(childComplexity int) int Name func(childComplexity int) int } @@ -253,7 +273,9 @@ type MutationResolver interface { CreateUserAccount(ctx context.Context, input NewUserAccount) (*pg.UserAccount, error) CreateTeam(ctx context.Context, input NewTeam) (*pg.Team, error) ClearProfileAvatar(ctx context.Context) (*pg.UserAccount, error) + CreateTeamMember(ctx context.Context, input CreateTeamMember) (*CreateTeamMemberPayload, error) CreateProject(ctx context.Context, input NewProject) (*pg.Project, error) + DeleteProject(ctx context.Context, input DeleteProject) (*DeleteProjectPayload, error) UpdateProjectName(ctx context.Context, input *UpdateProjectName) (*pg.Project, error) CreateProjectLabel(ctx context.Context, input NewProjectLabel) (*pg.ProjectLabel, error) DeleteProjectLabel(ctx context.Context, input DeleteProjectLabel) (*pg.ProjectLabel, error) @@ -283,6 +305,9 @@ type MutationResolver interface { UnassignTask(ctx context.Context, input *UnassignTaskInput) (*pg.Task, error) LogoutUser(ctx context.Context, input LogoutUser) (bool, error) } +type OrganizationResolver interface { + ID(ctx context.Context, obj *pg.Organization) (uuid.UUID, error) +} type ProjectResolver interface { ID(ctx context.Context, obj *pg.Project) (uuid.UUID, error) @@ -299,6 +324,7 @@ type ProjectLabelResolver interface { Name(ctx context.Context, obj *pg.ProjectLabel) (*string, error) } type QueryResolver interface { + Organizations(ctx context.Context) ([]pg.Organization, error) Users(ctx context.Context) ([]pg.UserAccount, error) FindUser(ctx context.Context, input FindUser) (*pg.UserAccount, error) FindProject(ctx context.Context, input FindProject) (*pg.Project, error) @@ -346,6 +372,8 @@ type TaskLabelResolver interface { } type TeamResolver interface { ID(ctx context.Context, obj *pg.Team) (uuid.UUID, error) + + Members(ctx context.Context, obj *pg.Team) ([]ProjectMember, error) } type UserAccountResolver interface { ID(ctx context.Context, obj *pg.UserAccount) (uuid.UUID, error) @@ -382,6 +410,34 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.ChecklistBadge.Total(childComplexity), true + case "CreateTeamMemberPayload.team": + if e.complexity.CreateTeamMemberPayload.Team == nil { + break + } + + return e.complexity.CreateTeamMemberPayload.Team(childComplexity), true + + case "CreateTeamMemberPayload.teamMember": + if e.complexity.CreateTeamMemberPayload.TeamMember == nil { + break + } + + return e.complexity.CreateTeamMemberPayload.TeamMember(childComplexity), true + + case "DeleteProjectPayload.ok": + if e.complexity.DeleteProjectPayload.Ok == nil { + break + } + + return e.complexity.DeleteProjectPayload.Ok(childComplexity), true + + case "DeleteProjectPayload.project": + if e.complexity.DeleteProjectPayload.Project == nil { + break + } + + return e.complexity.DeleteProjectPayload.Project(childComplexity), true + case "DeleteTaskChecklistItemPayload.ok": if e.complexity.DeleteTaskChecklistItemPayload.Ok == nil { break @@ -579,6 +635,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.CreateTeam(childComplexity, args["input"].(NewTeam)), true + case "Mutation.createTeamMember": + if e.complexity.Mutation.CreateTeamMember == nil { + break + } + + args, err := ec.field_Mutation_createTeamMember_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.CreateTeamMember(childComplexity, args["input"].(CreateTeamMember)), true + case "Mutation.createUserAccount": if e.complexity.Mutation.CreateUserAccount == nil { break @@ -591,6 +659,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.CreateUserAccount(childComplexity, args["input"].(NewUserAccount)), true + case "Mutation.deleteProject": + if e.complexity.Mutation.DeleteProject == nil { + break + } + + args, err := ec.field_Mutation_deleteProject_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.DeleteProject(childComplexity, args["input"].(DeleteProject)), true + case "Mutation.deleteProjectLabel": if e.complexity.Mutation.DeleteProjectLabel == nil { break @@ -843,6 +923,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.UpdateTaskName(childComplexity, args["input"].(UpdateTaskName)), true + case "Organization.id": + if e.complexity.Organization.ID == nil { + break + } + + return e.complexity.Organization.ID(childComplexity), true + + case "Organization.name": + if e.complexity.Organization.Name == nil { + break + } + + return e.complexity.Organization.Name(childComplexity), true + case "ProfileIcon.bgColor": if e.complexity.ProfileIcon.BgColor == nil { break @@ -1019,6 +1113,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.Me(childComplexity), true + case "Query.organizations": + if e.complexity.Query.Organizations == nil { + break + } + + return e.complexity.Query.Organizations(childComplexity), true + case "Query.projects": if e.complexity.Query.Projects == nil { break @@ -1318,6 +1419,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Team.ID(childComplexity), true + case "Team.members": + if e.complexity.Team.Members == nil { + break + } + + return e.complexity.Team.Members(childComplexity), true + case "Team.name": if e.complexity.Team.Name == nil { break @@ -1523,6 +1631,7 @@ type Team { id: ID! createdAt: Time! name: String! + members: [ProjectMember!]! } type Project { @@ -1585,7 +1694,13 @@ input FindTask { taskID: UUID! } +type Organization { + id: ID! + name: String! +} + type Query { + organizations: [Organization!]! users: [UserAccount!]! findUser(input: FindUser!): UserAccount! findProject(input: FindProject!): Project! @@ -1611,7 +1726,7 @@ input NewUserAccount { input NewTeam { name: String! - organizationID: String! + organizationID: UUID! } input NewProject { @@ -1798,6 +1913,25 @@ 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! +} + type Mutation { createRefreshToken(input: NewRefreshToken!): RefreshToken! @@ -1806,7 +1940,10 @@ type Mutation { 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! @@ -1976,6 +2113,20 @@ func (ec *executionContext) field_Mutation_createTask_args(ctx context.Context, return args, nil } +func (ec *executionContext) field_Mutation_createTeamMember_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 CreateTeamMember + if tmp, ok := rawArgs["input"]; ok { + arg0, err = ec.unmarshalNCreateTeamMember2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐCreateTeamMember(ctx, tmp) + if err != nil { + return nil, err + } + } + args["input"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation_createTeam_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -2018,6 +2169,20 @@ func (ec *executionContext) field_Mutation_deleteProjectLabel_args(ctx context.C return args, nil } +func (ec *executionContext) field_Mutation_deleteProject_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 DeleteProject + if tmp, ok := rawArgs["input"]; ok { + arg0, err = ec.unmarshalNDeleteProject2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐDeleteProject(ctx, tmp) + if err != nil { + return nil, err + } + } + args["input"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation_deleteTaskChecklistItem_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -2472,6 +2637,142 @@ func (ec *executionContext) _ChecklistBadge_total(ctx context.Context, field gra return ec.marshalNInt2int(ctx, field.Selections, res) } +func (ec *executionContext) _CreateTeamMemberPayload_team(ctx context.Context, field graphql.CollectedField, obj *CreateTeamMemberPayload) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "CreateTeamMemberPayload", + 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.Team, 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.(*pg.Team) + fc.Result = res + return ec.marshalNTeam2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐTeam(ctx, field.Selections, res) +} + +func (ec *executionContext) _CreateTeamMemberPayload_teamMember(ctx context.Context, field graphql.CollectedField, obj *CreateTeamMemberPayload) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "CreateTeamMemberPayload", + 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.TeamMember, 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.(*ProjectMember) + fc.Result = res + return ec.marshalNProjectMember2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐProjectMember(ctx, field.Selections, res) +} + +func (ec *executionContext) _DeleteProjectPayload_ok(ctx context.Context, field graphql.CollectedField, obj *DeleteProjectPayload) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "DeleteProjectPayload", + 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.Ok, 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.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + +func (ec *executionContext) _DeleteProjectPayload_project(ctx context.Context, field graphql.CollectedField, obj *DeleteProjectPayload) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "DeleteProjectPayload", + 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.Project, 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.(*pg.Project) + fc.Result = res + return ec.marshalNProject2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐProject(ctx, field.Selections, res) +} + func (ec *executionContext) _DeleteTaskChecklistItemPayload_ok(ctx context.Context, field graphql.CollectedField, obj *DeleteTaskChecklistItemPayload) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -2969,6 +3270,47 @@ func (ec *executionContext) _Mutation_clearProfileAvatar(ctx context.Context, fi return ec.marshalNUserAccount2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐUserAccount(ctx, field.Selections, res) } +func (ec *executionContext) _Mutation_createTeamMember(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: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation_createTeamMember_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().CreateTeamMember(rctx, args["input"].(CreateTeamMember)) + }) + 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.(*CreateTeamMemberPayload) + fc.Result = res + return ec.marshalNCreateTeamMemberPayload2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐCreateTeamMemberPayload(ctx, field.Selections, res) +} + func (ec *executionContext) _Mutation_createProject(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -3010,6 +3352,47 @@ func (ec *executionContext) _Mutation_createProject(ctx context.Context, field g return ec.marshalNProject2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐProject(ctx, field.Selections, res) } +func (ec *executionContext) _Mutation_deleteProject(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: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation_deleteProject_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().DeleteProject(rctx, args["input"].(DeleteProject)) + }) + 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.(*DeleteProjectPayload) + fc.Result = res + return ec.marshalNDeleteProjectPayload2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐDeleteProjectPayload(ctx, field.Selections, res) +} + func (ec *executionContext) _Mutation_updateProjectName(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -4158,6 +4541,74 @@ func (ec *executionContext) _Mutation_logoutUser(ctx context.Context, field grap return ec.marshalNBoolean2bool(ctx, field.Selections, res) } +func (ec *executionContext) _Organization_id(ctx context.Context, field graphql.CollectedField, obj *pg.Organization) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Organization", + 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.Organization().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) _Organization_name(ctx context.Context, field graphql.CollectedField, obj *pg.Organization) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Organization", + 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) _ProfileIcon_url(ctx context.Context, field graphql.CollectedField, obj *ProfileIcon) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -4758,6 +5209,40 @@ func (ec *executionContext) _ProjectMember_profileIcon(ctx context.Context, fiel return ec.marshalNProfileIcon2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐProfileIcon(ctx, field.Selections, res) } +func (ec *executionContext) _Query_organizations(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().Organizations(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.Organization) + fc.Result = res + return ec.marshalNOrganization2ᚕgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐOrganizationᚄ(ctx, field.Selections, res) +} + func (ec *executionContext) _Query_users(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -6478,6 +6963,40 @@ func (ec *executionContext) _Team_name(ctx context.Context, field graphql.Collec return ec.marshalNString2string(ctx, field.Selections, res) } +func (ec *executionContext) _Team_members(ctx context.Context, field graphql.CollectedField, obj *pg.Team) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Team", + 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.Team().Members(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.([]ProjectMember) + fc.Result = res + return ec.marshalNProjectMember2ᚕgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐProjectMemberᚄ(ctx, field.Selections, res) +} + func (ec *executionContext) _ToggleTaskLabelPayload_active(ctx context.Context, field graphql.CollectedField, obj *ToggleTaskLabelPayload) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -8015,6 +8534,48 @@ func (ec *executionContext) unmarshalInputCreateTaskChecklistItem(ctx context.Co return it, nil } +func (ec *executionContext) unmarshalInputCreateTeamMember(ctx context.Context, obj interface{}) (CreateTeamMember, error) { + var it CreateTeamMember + var asMap = obj.(map[string]interface{}) + + for k, v := range asMap { + switch k { + case "userID": + var err error + it.UserID, err = ec.unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v) + if err != nil { + return it, err + } + case "teamID": + var err error + it.TeamID, err = ec.unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v) + if err != nil { + return it, err + } + } + } + + return it, nil +} + +func (ec *executionContext) unmarshalInputDeleteProject(ctx context.Context, obj interface{}) (DeleteProject, error) { + var it DeleteProject + var asMap = obj.(map[string]interface{}) + + for k, v := range asMap { + switch k { + case "projectID": + var err error + it.ProjectID, err = ec.unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v) + if err != nil { + return it, err + } + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputDeleteProjectLabel(ctx context.Context, obj interface{}) (DeleteProjectLabel, error) { var it DeleteProjectLabel var asMap = obj.(map[string]interface{}) @@ -8365,7 +8926,7 @@ func (ec *executionContext) unmarshalInputNewTeam(ctx context.Context, obj inter } case "organizationID": var err error - it.OrganizationID, err = ec.unmarshalNString2string(ctx, v) + it.OrganizationID, err = ec.unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v) if err != nil { return it, err } @@ -8811,6 +9372,70 @@ func (ec *executionContext) _ChecklistBadge(ctx context.Context, sel ast.Selecti return out } +var createTeamMemberPayloadImplementors = []string{"CreateTeamMemberPayload"} + +func (ec *executionContext) _CreateTeamMemberPayload(ctx context.Context, sel ast.SelectionSet, obj *CreateTeamMemberPayload) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, createTeamMemberPayloadImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("CreateTeamMemberPayload") + case "team": + out.Values[i] = ec._CreateTeamMemberPayload_team(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "teamMember": + out.Values[i] = ec._CreateTeamMemberPayload_teamMember(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + +var deleteProjectPayloadImplementors = []string{"DeleteProjectPayload"} + +func (ec *executionContext) _DeleteProjectPayload(ctx context.Context, sel ast.SelectionSet, obj *DeleteProjectPayload) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, deleteProjectPayloadImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("DeleteProjectPayload") + case "ok": + out.Values[i] = ec._DeleteProjectPayload_ok(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "project": + out.Values[i] = ec._DeleteProjectPayload_project(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var deleteTaskChecklistItemPayloadImplementors = []string{"DeleteTaskChecklistItemPayload"} func (ec *executionContext) _DeleteTaskChecklistItemPayload(ctx context.Context, sel ast.SelectionSet, obj *DeleteTaskChecklistItemPayload) graphql.Marshaler { @@ -8993,11 +9618,21 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { invalids++ } + case "createTeamMember": + out.Values[i] = ec._Mutation_createTeamMember(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } case "createProject": out.Values[i] = ec._Mutation_createProject(ctx, field) if out.Values[i] == graphql.Null { invalids++ } + case "deleteProject": + out.Values[i] = ec._Mutation_deleteProject(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } case "updateProjectName": out.Values[i] = ec._Mutation_updateProjectName(ctx, field) if out.Values[i] == graphql.Null { @@ -9149,6 +9784,47 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) return out } +var organizationImplementors = []string{"Organization"} + +func (ec *executionContext) _Organization(ctx context.Context, sel ast.SelectionSet, obj *pg.Organization) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, organizationImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Organization") + 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._Organization_id(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) + case "name": + out.Values[i] = ec._Organization_name(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 profileIconImplementors = []string{"ProfileIcon"} func (ec *executionContext) _ProfileIcon(ctx context.Context, sel ast.SelectionSet, obj *ProfileIcon) graphql.Marshaler { @@ -9411,6 +10087,20 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("Query") + case "organizations": + 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_organizations(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "users": field := field out.Concurrently(i, func() (res graphql.Marshaler) { @@ -10074,6 +10764,20 @@ func (ec *executionContext) _Team(ctx context.Context, sel ast.SelectionSet, obj if out.Values[i] == graphql.Null { atomic.AddUint32(&invalids, 1) } + case "members": + 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._Team_members(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -10491,10 +11195,46 @@ func (ec *executionContext) unmarshalNCreateTaskChecklistItem2githubᚗcomᚋjor return ec.unmarshalInputCreateTaskChecklistItem(ctx, v) } +func (ec *executionContext) unmarshalNCreateTeamMember2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐCreateTeamMember(ctx context.Context, v interface{}) (CreateTeamMember, error) { + return ec.unmarshalInputCreateTeamMember(ctx, v) +} + +func (ec *executionContext) marshalNCreateTeamMemberPayload2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐCreateTeamMemberPayload(ctx context.Context, sel ast.SelectionSet, v CreateTeamMemberPayload) graphql.Marshaler { + return ec._CreateTeamMemberPayload(ctx, sel, &v) +} + +func (ec *executionContext) marshalNCreateTeamMemberPayload2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐCreateTeamMemberPayload(ctx context.Context, sel ast.SelectionSet, v *CreateTeamMemberPayload) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._CreateTeamMemberPayload(ctx, sel, v) +} + +func (ec *executionContext) unmarshalNDeleteProject2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐDeleteProject(ctx context.Context, v interface{}) (DeleteProject, error) { + return ec.unmarshalInputDeleteProject(ctx, v) +} + func (ec *executionContext) unmarshalNDeleteProjectLabel2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐDeleteProjectLabel(ctx context.Context, v interface{}) (DeleteProjectLabel, error) { return ec.unmarshalInputDeleteProjectLabel(ctx, v) } +func (ec *executionContext) marshalNDeleteProjectPayload2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐDeleteProjectPayload(ctx context.Context, sel ast.SelectionSet, v DeleteProjectPayload) graphql.Marshaler { + return ec._DeleteProjectPayload(ctx, sel, &v) +} + +func (ec *executionContext) marshalNDeleteProjectPayload2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐDeleteProjectPayload(ctx context.Context, sel ast.SelectionSet, v *DeleteProjectPayload) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._DeleteProjectPayload(ctx, sel, v) +} + func (ec *executionContext) unmarshalNDeleteTaskChecklistItem2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐDeleteTaskChecklistItem(ctx context.Context, v interface{}) (DeleteTaskChecklistItem, error) { return ec.unmarshalInputDeleteTaskChecklistItem(ctx, v) } @@ -10694,6 +11434,47 @@ func (ec *executionContext) unmarshalNNewUserAccount2githubᚗcomᚋjordanknott return ec.unmarshalInputNewUserAccount(ctx, v) } +func (ec *executionContext) marshalNOrganization2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐOrganization(ctx context.Context, sel ast.SelectionSet, v pg.Organization) graphql.Marshaler { + return ec._Organization(ctx, sel, &v) +} + +func (ec *executionContext) marshalNOrganization2ᚕgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐOrganizationᚄ(ctx context.Context, sel ast.SelectionSet, v []pg.Organization) 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.marshalNOrganization2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐOrganization(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + return ret +} + func (ec *executionContext) marshalNProfileIcon2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐProfileIcon(ctx context.Context, sel ast.SelectionSet, v ProfileIcon) graphql.Marshaler { return ec._ProfileIcon(ctx, sel, &v) } diff --git a/api/graph/models_gen.go b/api/graph/models_gen.go index 37e3e29..7ce4699 100644 --- a/api/graph/models_gen.go +++ b/api/graph/models_gen.go @@ -36,10 +36,29 @@ type CreateTaskChecklistItem struct { Position float64 `json:"position"` } +type CreateTeamMember struct { + UserID uuid.UUID `json:"userID"` + TeamID uuid.UUID `json:"teamID"` +} + +type CreateTeamMemberPayload struct { + Team *pg.Team `json:"team"` + TeamMember *ProjectMember `json:"teamMember"` +} + +type DeleteProject struct { + ProjectID uuid.UUID `json:"projectID"` +} + type DeleteProjectLabel struct { ProjectLabelID uuid.UUID `json:"projectLabelID"` } +type DeleteProjectPayload struct { + Ok bool `json:"ok"` + Project *pg.Project `json:"project"` +} + type DeleteTaskChecklistItem struct { TaskChecklistItemID uuid.UUID `json:"taskChecklistItemID"` } @@ -123,8 +142,8 @@ type NewTaskLocation struct { } type NewTeam struct { - Name string `json:"name"` - OrganizationID string `json:"organizationID"` + Name string `json:"name"` + OrganizationID uuid.UUID `json:"organizationID"` } type NewUserAccount struct { diff --git a/api/graph/schema.graphqls b/api/graph/schema.graphqls index 5e33d69..d7e1977 100644 --- a/api/graph/schema.graphqls +++ b/api/graph/schema.graphqls @@ -55,6 +55,7 @@ type Team { id: ID! createdAt: Time! name: String! + members: [ProjectMember!]! } type Project { @@ -117,7 +118,13 @@ input FindTask { taskID: UUID! } +type Organization { + id: ID! + name: String! +} + type Query { + organizations: [Organization!]! users: [UserAccount!]! findUser(input: FindUser!): UserAccount! findProject(input: FindProject!): Project! @@ -143,7 +150,7 @@ input NewUserAccount { input NewTeam { name: String! - organizationID: String! + organizationID: UUID! } input NewProject { @@ -330,6 +337,25 @@ 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! +} + type Mutation { createRefreshToken(input: NewRefreshToken!): RefreshToken! @@ -338,7 +364,10 @@ type Mutation { 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! diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index a2fbcd7..a2e095c 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -41,12 +41,8 @@ func (r *mutationResolver) CreateUserAccount(ctx context.Context, input NewUserA } func (r *mutationResolver) CreateTeam(ctx context.Context, input NewTeam) (*pg.Team, error) { - organizationID, err := uuid.Parse(input.OrganizationID) - if err != nil { - return &pg.Team{}, err - } createdAt := time.Now().UTC() - team, err := r.Repository.CreateTeam(ctx, pg.CreateTeamParams{organizationID, createdAt, input.Name}) + team, err := r.Repository.CreateTeam(ctx, pg.CreateTeamParams{input.OrganizationID, createdAt, input.Name}) return &team, err } @@ -65,12 +61,49 @@ func (r *mutationResolver) ClearProfileAvatar(ctx context.Context) (*pg.UserAcco return &user, nil } +func (r *mutationResolver) CreateTeamMember(ctx context.Context, input CreateTeamMember) (*CreateTeamMemberPayload, error) { + addedDate := time.Now().UTC() + team, err := r.Repository.GetTeamByID(ctx, input.TeamID) + if err != nil { + return &CreateTeamMemberPayload{}, err + } + _, err = r.Repository.CreateTeamMember(ctx, pg.CreateTeamMemberParams{TeamID: input.TeamID, UserID: input.UserID, Addeddate: addedDate}) + user, err := r.Repository.GetUserAccountByID(ctx, input.UserID) + if err != nil { + return &CreateTeamMemberPayload{}, err + } + var url *string + if user.ProfileAvatarUrl.Valid { + url = &user.ProfileAvatarUrl.String + } + profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor} + return &CreateTeamMemberPayload{ + Team: &team, + TeamMember: &ProjectMember{ + ID: user.UserID, + FullName: user.FullName, + ProfileIcon: profileIcon, + }}, nil +} + func (r *mutationResolver) CreateProject(ctx context.Context, input NewProject) (*pg.Project, error) { createdAt := time.Now().UTC() project, err := r.Repository.CreateProject(ctx, pg.CreateProjectParams{input.UserID, input.TeamID, createdAt, input.Name}) return &project, err } +func (r *mutationResolver) DeleteProject(ctx context.Context, input DeleteProject) (*DeleteProjectPayload, error) { + project, err := r.Repository.GetProjectByID(ctx, input.ProjectID) + if err != nil { + return &DeleteProjectPayload{Ok: false}, err + } + err = r.Repository.DeleteProjectByID(ctx, input.ProjectID) + if err != nil { + return &DeleteProjectPayload{Ok: false}, err + } + return &DeleteProjectPayload{Project: &project, Ok: true}, err +} + func (r *mutationResolver) UpdateProjectName(ctx context.Context, input *UpdateProjectName) (*pg.Project, error) { project, err := r.Repository.UpdateProjectNameByID(ctx, pg.UpdateProjectNameByIDParams{ProjectID: input.ProjectID, Name: input.Name}) if err != nil { @@ -409,6 +442,10 @@ func (r *mutationResolver) LogoutUser(ctx context.Context, input LogoutUser) (bo return true, err } +func (r *organizationResolver) ID(ctx context.Context, obj *pg.Organization) (uuid.UUID, error) { + return obj.OrganizationID, nil +} + func (r *projectResolver) ID(ctx context.Context, obj *pg.Project) (uuid.UUID, error) { return obj.ProjectID, nil } @@ -475,6 +512,10 @@ func (r *projectLabelResolver) Name(ctx context.Context, obj *pg.ProjectLabel) ( return name, nil } +func (r *queryResolver) Organizations(ctx context.Context) ([]pg.Organization, error) { + return r.Repository.GetAllOrganizations(ctx) +} + func (r *queryResolver) Users(ctx context.Context) ([]pg.UserAccount, error) { return r.Repository.GetAllUserAccounts(ctx) } @@ -683,6 +724,31 @@ func (r *teamResolver) ID(ctx context.Context, obj *pg.Team) (uuid.UUID, error) return obj.TeamID, nil } +func (r *teamResolver) Members(ctx context.Context, obj *pg.Team) ([]ProjectMember, error) { + teamMembers, err := r.Repository.GetTeamMembersForTeamID(ctx, obj.TeamID) + var projectMembers []ProjectMember + if err != nil { + return projectMembers, err + } + for _, teamMember := range teamMembers { + user, err := r.Repository.GetUserAccountByID(ctx, teamMember.UserID) + if err != nil { + return projectMembers, err + } + var url *string + if user.ProfileAvatarUrl.Valid { + url = &user.ProfileAvatarUrl.String + } + profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor} + projectMembers = append(projectMembers, ProjectMember{ + ID: user.UserID, + FullName: user.FullName, + ProfileIcon: profileIcon, + }) + } + return projectMembers, nil +} + func (r *userAccountResolver) ID(ctx context.Context, obj *pg.UserAccount) (uuid.UUID, error) { return obj.UserID, nil } @@ -702,6 +768,9 @@ func (r *Resolver) LabelColor() LabelColorResolver { return &labelColorResolver{ // Mutation returns MutationResolver implementation. func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} } +// Organization returns OrganizationResolver implementation. +func (r *Resolver) Organization() OrganizationResolver { return &organizationResolver{r} } + // Project returns ProjectResolver implementation. func (r *Resolver) Project() ProjectResolver { return &projectResolver{r} } @@ -737,6 +806,7 @@ func (r *Resolver) UserAccount() UserAccountResolver { return &userAccountResolv type labelColorResolver struct{ *Resolver } type mutationResolver struct{ *Resolver } +type organizationResolver struct{ *Resolver } type projectResolver struct{ *Resolver } type projectLabelResolver struct{ *Resolver } type queryResolver struct{ *Resolver } diff --git a/api/migrations/0031_add-team-member-table.up.sql b/api/migrations/0031_add-team-member-table.up.sql new file mode 100644 index 0000000..93c964e --- /dev/null +++ b/api/migrations/0031_add-team-member-table.up.sql @@ -0,0 +1,8 @@ +CREATE TABLE team_member ( + team_member_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + team_id uuid NOT NULL REFERENCES team(team_id) ON DELETE CASCADE, + user_id uuid NOT NULL REFERENCES user_account(user_id) ON DELETE CASCADE, + UNIQUE(team_id, user_id), + addedDate timestamptz NOT NULL +); + diff --git a/api/migrations/0032_add-cascade-delete-to-project_label.up.sql b/api/migrations/0032_add-cascade-delete-to-project_label.up.sql new file mode 100644 index 0000000..ab6e087 --- /dev/null +++ b/api/migrations/0032_add-cascade-delete-to-project_label.up.sql @@ -0,0 +1,6 @@ +ALTER TABLE project_label DROP CONSTRAINT project_label_project_id_fkey; +ALTER TABLE project_label + ADD CONSTRAINT project_label_project_id_fkey + FOREIGN KEY (project_id) + REFERENCES project(project_id) + ON DELETE CASCADE; diff --git a/api/migrations/0033_add-cascade-delete-to_task_label_project_label_id_fkey.up.sql b/api/migrations/0033_add-cascade-delete-to_task_label_project_label_id_fkey.up.sql new file mode 100644 index 0000000..797ccc6 --- /dev/null +++ b/api/migrations/0033_add-cascade-delete-to_task_label_project_label_id_fkey.up.sql @@ -0,0 +1,6 @@ +ALTER TABLE task_label DROP CONSTRAINT task_label_project_label_id_fkey; +ALTER TABLE task_label + ADD CONSTRAINT task_label_project_label_id_fkey + FOREIGN KEY (project_label_id) + REFERENCES project_label(project_label_id) + ON DELETE CASCADE; diff --git a/api/pg/models.go b/api/pg/models.go index 789a0ee..2f18099 100644 --- a/api/pg/models.go +++ b/api/pg/models.go @@ -103,6 +103,13 @@ type Team struct { OrganizationID uuid.UUID `json:"organization_id"` } +type TeamMember struct { + TeamMemberID uuid.UUID `json:"team_member_id"` + TeamID uuid.UUID `json:"team_id"` + UserID uuid.UUID `json:"user_id"` + Addeddate time.Time `json:"addeddate"` +} + type UserAccount struct { UserID uuid.UUID `json:"user_id"` CreatedAt time.Time `json:"created_at"` diff --git a/api/pg/pg.go b/api/pg/pg.go index c1ed94b..d546775 100644 --- a/api/pg/pg.go +++ b/api/pg/pg.go @@ -7,11 +7,17 @@ import ( ) 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 + CreateProject(ctx context.Context, arg CreateProjectParams) (Project, error) GetAllProjects(ctx context.Context) ([]Project, error) GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) ([]Project, error) diff --git a/api/pg/project.sql.go b/api/pg/project.sql.go index daa6061..32b449d 100644 --- a/api/pg/project.sql.go +++ b/api/pg/project.sql.go @@ -39,6 +39,15 @@ func (q *Queries) CreateProject(ctx context.Context, arg CreateProjectParams) (P 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 ` diff --git a/api/pg/querier.go b/api/pg/querier.go index c55e6d5..ba421c1 100644 --- a/api/pg/querier.go +++ b/api/pg/querier.go @@ -21,8 +21,10 @@ type Querier interface { CreateTaskGroup(ctx context.Context, arg CreateTaskGroupParams) (TaskGroup, error) CreateTaskLabelForTask(ctx context.Context, arg CreateTaskLabelForTaskParams) (TaskLabel, error) CreateTeam(ctx context.Context, arg CreateTeamParams) (Team, error) + CreateTeamMember(ctx context.Context, arg CreateTeamMemberParams) (TeamMember, error) CreateUserAccount(ctx context.Context, arg CreateUserAccountParams) (UserAccount, error) DeleteExpiredTokens(ctx context.Context) error + DeleteProjectByID(ctx context.Context, projectID uuid.UUID) error DeleteProjectLabelByID(ctx context.Context, projectLabelID uuid.UUID) error DeleteRefreshTokenByID(ctx context.Context, tokenID uuid.UUID) error DeleteRefreshTokenByUserID(ctx context.Context, userID uuid.UUID) error @@ -34,6 +36,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 GetAllOrganizations(ctx context.Context) ([]Organization, error) GetAllProjects(ctx context.Context) ([]Project, error) GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) ([]Project, error) @@ -59,6 +62,7 @@ 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) + 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) diff --git a/api/pg/team_member.sql.go b/api/pg/team_member.sql.go new file mode 100644 index 0000000..223e3e9 --- /dev/null +++ b/api/pg/team_member.sql.go @@ -0,0 +1,75 @@ +// 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 +} diff --git a/api/query/project.sql b/api/query/project.sql index 53a6df3..12a1514 100644 --- a/api/query/project.sql +++ b/api/query/project.sql @@ -12,3 +12,6 @@ INSERT INTO project(owner, team_id, created_at, name) VALUES ($1, $2, $3, $4) RE -- name: UpdateProjectNameByID :one UPDATE project SET name = $2 WHERE project_id = $1 RETURNING *; + +-- name: DeleteProjectByID :exec +DELETE FROM project WHERE project_id = $1; diff --git a/api/query/team_member.sql b/api/query/team_member.sql new file mode 100644 index 0000000..41a1ddf --- /dev/null +++ b/api/query/team_member.sql @@ -0,0 +1,9 @@ +-- 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; diff --git a/web/src/App/TopNavbar.tsx b/web/src/App/TopNavbar.tsx index c6f4db1..86684c0 100644 --- a/web/src/App/TopNavbar.tsx +++ b/web/src/App/TopNavbar.tsx @@ -1,22 +1,47 @@ import React, { useState, useContext } from 'react'; import TopNavbar from 'shared/components/TopNavbar'; import DropdownMenu, { ProfileMenu } from 'shared/components/DropdownMenu'; -import ProjectSettings from 'shared/components/ProjectSettings'; +import ProjectSettings, { DeleteProject } from 'shared/components/ProjectSettings'; import { useHistory } from 'react-router'; import UserIDContext from 'App/context'; -import { useMeQuery } from 'shared/generated/graphql'; +import { useMeQuery, useDeleteProjectMutation, GetProjectsDocument } from 'shared/generated/graphql'; import { usePopup, Popup } from 'shared/components/PopupMenu'; +import produce from 'immer'; type GlobalTopNavbarProps = { + projectID: string | null; name: string | null; projectMembers?: null | Array; onSaveProjectName?: (projectName: string) => void; }; -const GlobalTopNavbar: React.FC = ({ name, projectMembers, onSaveProjectName }) => { +const GlobalTopNavbar: React.FC = ({ projectID, name, projectMembers, onSaveProjectName }) => { const { loading, data } = useMeQuery(); - const { showPopup, hidePopup } = usePopup(); + const { showPopup, hidePopup, setTab } = usePopup(); const history = useHistory(); const { userID, setUserID } = useContext(UserIDContext); + const [deleteProject] = useDeleteProjectMutation({ + update: (client, deleteData) => { + const cacheData: any = client.readQuery({ + query: GetProjectsDocument, + }); + + console.log(cacheData); + console.log(deleteData); + + const newData = produce(cacheData, (draftState: any) => { + draftState.projects = draftState.projects.filter( + (project: any) => project.id !== deleteData.data.deleteProject.project.id, + ); + }); + + client.writeQuery({ + query: GetProjectsDocument, + data: { + ...newData, + }, + }); + }, + }); const onLogout = () => { fetch('http://localhost:3333/auth/logout', { method: 'POST', @@ -49,9 +74,27 @@ const GlobalTopNavbar: React.FC = ({ name, projectMembers, const onOpenSettings = ($target: React.RefObject) => { showPopup( $target, - - - , + <> + + { + setTab(1, 325); + }} + /> + + + { + if (projectID) { + deleteProject({ variables: { projectID } }); + hidePopup(); + history.push('/projects'); + } + }} + /> + + , 185, ); }; diff --git a/web/src/Profile/index.tsx b/web/src/Profile/index.tsx index 26c4312..5ac96a7 100644 --- a/web/src/Profile/index.tsx +++ b/web/src/Profile/index.tsx @@ -50,7 +50,7 @@ const Projects = () => { } }} /> - {}} name={null} /> + {}} name={null} /> {!loading && data && ( { if (loading) { return ( <> - {}} name="" /> + {}} name="" projectID={null} /> ); } @@ -510,6 +510,7 @@ const Project = () => { updateProjectName({ variables: { projectID, name: projectName } }); }} projectMembers={data.findProject.members} + projectID={projectID} name={data.findProject.name} /> diff --git a/web/src/Projects/index.tsx b/web/src/Projects/index.tsx index cc7044a..7af54ca 100644 --- a/web/src/Projects/index.tsx +++ b/web/src/Projects/index.tsx @@ -1,32 +1,183 @@ import React, { useState, useContext, useEffect } from 'react'; import styled from 'styled-components/macro'; import GlobalTopNavbar from 'App/TopNavbar'; -import { useGetProjectsQuery, useCreateProjectMutation, GetProjectsDocument } from 'shared/generated/graphql'; +import { + useCreateTeamMutation, + useGetProjectsQuery, + useCreateProjectMutation, + GetProjectsDocument, +} from 'shared/generated/graphql'; import ProjectGridItem, { AddProjectItem } from 'shared/components/ProjectGridItem'; import { Link } from 'react-router-dom'; import Navbar from 'App/Navbar'; import NewProject from 'shared/components/NewProject'; import UserIDContext from 'App/context'; +import Button from 'shared/components/Button'; +import { usePopup, Popup } from 'shared/components/PopupMenu'; +import { useForm } from 'react-hook-form'; +import Input from 'shared/components/Input'; -const MainContent = styled.div` - padding: 0 0 50px 80px; - height: 100%; - background: #262c49; +const CreateTeamButton = styled(Button)` + width: 100%; +`; +type CreateTeamData = { teamName: string }; +type CreateTeamFormProps = { + onCreateTeam: (teamName: string) => void; +}; +const CreateTeamFormContainer = styled.form``; + +const CreateTeamForm: React.FC = ({ onCreateTeam }) => { + const { register, handleSubmit, errors } = useForm(); + const createTeam = (data: CreateTeamData) => { + onCreateTeam(data.teamName); + }; + return ( + + + Create + + ); +}; + +const ProjectAddTile = styled.div` + background-color: rgba(${props => props.theme.colors.bg.primary}, 0.4); + background-size: cover; + background-position: 50%; + color: #fff; + line-height: 20px; + padding: 8px; + position: relative; + text-decoration: none; + + border-radius: 3px; + display: block; `; +const ProjectTile = styled(Link)<{ color: string }>` + background-color: ${props => props.color}; + background-size: cover; + background-position: 50%; + color: #fff; + line-height: 20px; + padding: 8px; + position: relative; + text-decoration: none; + + border-radius: 3px; + display: block; +`; + +const ProjectTileFade = styled.div` + background-color: rgba(0, 0, 0, 0.15); + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; +`; + +const ProjectListItem = styled.li` + width: 23.5%; + padding: 0; + margin: 0 2% 2% 0; + + box-sizing: border-box; + position: relative; + cursor: pointer; + + &:hover ${ProjectTileFade} { + background-color: rgba(0, 0, 0, 0.25); + } +`; + +const ProjectList = styled.ul` + display: flex; + flex-wrap: wrap; + + & ${ProjectListItem}:nth-of-type(4n) { + margin-right: 0; + } +`; + +const ProjectTileDetails = styled.div` + display: flex; + height: 80px; + position: relative; + flex-direction: column; + justify-content: space-between; +`; + +const ProjectAddTileDetails = styled.div` + display: flex; + height: 80px; + position: relative; + flex-direction: column; + align-items: center; + justify-content: center; +`; + +const ProjectTileName = styled.div<{ centered?: boolean }>` + flex: 0 0 auto; + font-size: 16px; + font-weight: 700; + display: inline-block; + overflow: hidden; + max-height: 40px; + width: 100%; + word-wrap: break-word; + ${props => props.centered && 'text-align: center;'} +`; + +const Wrapper = styled.div` + display: flex; + flex-direction: row; + align-items: flex-start; + justify-content: center; +`; + +const ProjectSectionTitleWrapper = styled.div` + align-items: center; + display: flex; + height: 32px; + margin-bottom: 24px; + padding: 8px 0; + position: relative; +`; + +const ProjectSectionTitle = styled.h3` + font-size: 16px; + color: rgba(${props => props.theme.colors.text.primary}); +`; + +const ProjectsContainer = styled.div` + margin: 40px 16px 0; + width: 100%; + max-width: 825px; + min-width: 288px; +`; const ProjectGrid = styled.div` - width: 60%; max-width: 780px; - margin: 25px auto; display: grid; grid-template-columns: 240px 240px 240px; gap: 20px 10px; `; +const AddTeamButton = styled(Button)` + padding: 6px 12px; + float: right; +`; const ProjectLink = styled(Link)``; const Projects = () => { + const { showPopup } = usePopup(); const { loading, data } = useGetProjectsQuery(); useEffect(() => { document.title = 'Citadel'; @@ -53,6 +204,7 @@ const Projects = () => { }); const [showNewProject, setShowNewProject] = useState(false); const { userID, setUserID } = useContext(UserIDContext); + const [createTeam] = useCreateTeamMutation(); if (loading) { return ( <> @@ -60,39 +212,91 @@ const Projects = () => { ); } + + const colors = ['#e362e3', '#7a6ff0', '#37c5ab', '#aa62e3', '#e8384f']; if (data) { - const { projects, teams } = data; + const { projects, teams, organizations } = data; + const organizationID = organizations[0].id ?? null; + const projectTeams = teams.map(team => { + return { + id: team.id, + name: team.name, + projects: projects.filter(project => project.team.id === team.id), + }; + }); return ( <> - {}} name={null} /> - - {projects.map(project => ( - - {}} projectID={null} name={null} /> + + + { + showPopup( + $target, + + { + if (organizationID) { + createTeam({ variables: { name: teamName, organizationID } }); + } + }} + /> + , + ); + }} + > + Add Team + + {projectTeams.map(team => { + return ( +
+ + {team.name} + + + {team.projects.map((project, idx) => ( + + + + + {project.name} + + + + ))} + + { + setShowNewProject(true); + }} + > + + + Create new project + + + + +
+ ); + })} + {showNewProject && ( + { + if (userID) { + createProject({ variables: { teamID, name, userID } }); + setShowNewProject(false); + } + }} + onClose={() => { + setShowNewProject(false); + }} + teams={teams} /> -
- ))} - { - setShowNewProject(true); - }} - /> -
- {showNewProject && ( - { - if (userID) { - createProject({ variables: { teamID, name, userID } }); - setShowNewProject(false); - } - }} - onClose={() => { - setShowNewProject(false); - }} - teams={teams} - /> - )} + )} + + ); } diff --git a/web/src/shared/components/Input/index.tsx b/web/src/shared/components/Input/index.tsx index 669a765..4d654c3 100644 --- a/web/src/shared/components/Input/index.tsx +++ b/web/src/shared/components/Input/index.tsx @@ -11,7 +11,7 @@ const InputWrapper = styled.div<{ width: string }>` justify-content: center; margin-bottom: 2.2rem; - margin-top: 17px; + margin-top: 24px; `; const InputLabel = styled.span<{ width: string }>` diff --git a/web/src/shared/components/Login/Styles.ts b/web/src/shared/components/Login/Styles.ts index c6f61c4..b74ec6b 100644 --- a/web/src/shared/components/Login/Styles.ts +++ b/web/src/shared/components/Login/Styles.ts @@ -1,4 +1,5 @@ import styled from 'styled-components'; +import Button from 'shared/components/Button'; export const Wrapper = styled.div` background: #eff2f7; @@ -70,21 +71,7 @@ export const FormError = styled.span` color: rgb(234, 84, 85); `; -export const LoginButton = styled.input` - padding: 0.75rem 2rem; - font-size: 1rem; - border-radius: 6px; - background: var(--color-button-background); - outline: none; - border: none; - cursor: pointer; - color: var(--color-button-text-hover); - &:disabled { - opacity: 0.5; - cursor: default; - pointer-events: none; - } -`; +export const LoginButton = styled(Button)``; export const ActionButtons = styled.div` margin-top: 17.5px; @@ -92,15 +79,7 @@ export const ActionButtons = styled.div` justify-content: space-between; `; -export const RegisterButton = styled.button` - padding: 0.679rem 2rem; - border-radius: 6px; - border: 1px solid rgb(115, 103, 240); - background: transparent; - font-size: 1rem; - color: var(--color-primary); - cursor: pointer; -`; +export const RegisterButton = styled(Button)``; export const LogoTitle = styled.div` font-size: 24px; diff --git a/web/src/shared/components/Login/index.tsx b/web/src/shared/components/Login/index.tsx index 2ad0213..d0a9dfd 100644 --- a/web/src/shared/components/Login/index.tsx +++ b/web/src/shared/components/Login/index.tsx @@ -72,8 +72,10 @@ const Login = ({ onSubmit }: LoginProps) => { {errors.password && {errors.password.message}} - Register - + Register + + Login + diff --git a/web/src/shared/components/PopupMenu/index.tsx b/web/src/shared/components/PopupMenu/index.tsx index ae3076a..5793a91 100644 --- a/web/src/shared/components/PopupMenu/index.tsx +++ b/web/src/shared/components/PopupMenu/index.tsx @@ -16,7 +16,7 @@ import { type PopupContextState = { show: (target: RefObject, content: JSX.Element, width?: string | number) => void; - setTab: (newTab: number) => void; + setTab: (newTab: number, width?: number | string) => void; getCurrentTab: () => number; hide: () => void; }; @@ -139,12 +139,14 @@ export const PopupProvider: React.FC = ({ children }) => { }; const portalTarget = canUseDOM ? document.body : null; // appease flow - const setTab = (newTab: number) => { + const setTab = (newTab: number, width?: number | string) => { + let newWidth = width ?? currentState.width; setState((prevState: PopupState) => { return { ...prevState, previousTab: currentState.currentTab, currentTab: newTab, + width: newWidth, }; }); }; diff --git a/web/src/shared/components/ProjectSettings/index.tsx b/web/src/shared/components/ProjectSettings/index.tsx index 2af9438..9c2435a 100644 --- a/web/src/shared/components/ProjectSettings/index.tsx +++ b/web/src/shared/components/ProjectSettings/index.tsx @@ -1,6 +1,7 @@ import React from 'react'; import styled from 'styled-components'; +import Button from 'shared/components/Button'; export const ListActionsWrapper = styled.ul` list-style-type: none; @@ -36,16 +37,64 @@ export const ListSeparator = styled.hr` width: 100%; `; -type Props = {}; -const ProjectSettings: React.FC = () => { +type Props = { + onDeleteProject: () => void; +}; +const ProjectSettings: React.FC = ({ onDeleteProject }) => { return ( <> - {}}> + onDeleteProject()}> Delete Project ); }; + +const ConfirmWrapper = styled.div``; + +const ConfirmSubTitle = styled.h3` + font-size: 14px; +`; + +const ConfirmDescription = styled.div` + font-size: 14px; +`; + +const DeleteList = styled.ul` + margin-bottom: 12px; +`; +const DeleteListItem = styled.li` + padding: 6px 0; + list-style: disc; + margin-left: 12px; +`; + +const ConfirmDeleteButton = styled(Button)` + width: 100%; + padding: 6px 12px; +`; + +type DeleteProjectProps = { + name: string; + onDeleteProject: () => void; +}; +const DeleteProject: React.FC = ({ name, onDeleteProject }) => { + return ( + + + Deleting the project will also delete the following: + + Task groups and tasks + + + onDeleteProject()} color="danger"> + Delete + + + ); +}; + +export { DeleteProject }; export default ProjectSettings; diff --git a/web/src/shared/generated/graphql.tsx b/web/src/shared/generated/graphql.tsx index 21d35b4..421f871 100644 --- a/web/src/shared/generated/graphql.tsx +++ b/web/src/shared/generated/graphql.tsx @@ -78,6 +78,7 @@ export type Team = { id: Scalars['ID']; createdAt: Scalars['Time']; name: Scalars['String']; + members: Array; }; export type Project = { @@ -145,8 +146,15 @@ export type FindTask = { taskID: Scalars['UUID']; }; +export type Organization = { + __typename?: 'Organization'; + id: Scalars['ID']; + name: Scalars['String']; +}; + export type Query = { __typename?: 'Query'; + organizations: Array; users: Array; findUser: UserAccount; findProject: Project; @@ -192,7 +200,7 @@ export type NewUserAccount = { export type NewTeam = { name: Scalars['String']; - organizationID: Scalars['String']; + organizationID: Scalars['UUID']; }; export type NewProject = { @@ -390,13 +398,36 @@ export type UpdateTaskChecklistItemName = { name: Scalars['String']; }; +export type CreateTeamMember = { + userID: Scalars['UUID']; + teamID: Scalars['UUID']; +}; + +export type CreateTeamMemberPayload = { + __typename?: 'CreateTeamMemberPayload'; + team: Team; + teamMember: ProjectMember; +}; + +export type DeleteProject = { + projectID: Scalars['UUID']; +}; + +export type DeleteProjectPayload = { + __typename?: 'DeleteProjectPayload'; + ok: Scalars['Boolean']; + project: Project; +}; + export type Mutation = { __typename?: 'Mutation'; createRefreshToken: RefreshToken; createUserAccount: UserAccount; createTeam: Team; clearProfileAvatar: UserAccount; + createTeamMember: CreateTeamMemberPayload; createProject: Project; + deleteProject: DeleteProjectPayload; updateProjectName: Project; createProjectLabel: ProjectLabel; deleteProjectLabel: ProjectLabel; @@ -443,11 +474,21 @@ export type MutationCreateTeamArgs = { }; +export type MutationCreateTeamMemberArgs = { + input: CreateTeamMember; +}; + + export type MutationCreateProjectArgs = { input: NewProject; }; +export type MutationDeleteProjectArgs = { + input: DeleteProject; +}; + + export type MutationUpdateProjectNameArgs = { input?: Maybe; }; @@ -869,7 +910,10 @@ export type GetProjectsQueryVariables = {}; export type GetProjectsQuery = ( { __typename?: 'Query' } - & { teams: Array<( + & { organizations: Array<( + { __typename?: 'Organization' } + & Pick + )>, teams: Array<( { __typename?: 'Team' } & Pick )>, projects: Array<( @@ -897,6 +941,23 @@ export type MeQuery = ( ) } ); +export type DeleteProjectMutationVariables = { + projectID: Scalars['UUID']; +}; + + +export type DeleteProjectMutation = ( + { __typename?: 'Mutation' } + & { deleteProject: ( + { __typename?: 'DeleteProjectPayload' } + & Pick + & { project: ( + { __typename?: 'Project' } + & Pick + ) } + ) } +); + export type CreateTaskChecklistItemMutationVariables = { taskChecklistID: Scalars['UUID']; name: Scalars['String']; @@ -985,6 +1046,20 @@ export type UpdateTaskGroupNameMutation = ( ) } ); +export type CreateTeamMutationVariables = { + name: Scalars['String']; + organizationID: Scalars['UUID']; +}; + + +export type CreateTeamMutation = ( + { __typename?: 'Mutation' } + & { createTeam: ( + { __typename?: 'Team' } + & Pick + ) } +); + export type ToggleTaskLabelMutationVariables = { taskID: Scalars['UUID']; projectLabelID: Scalars['UUID']; @@ -1690,6 +1765,10 @@ export type FindTaskLazyQueryHookResult = ReturnType; export const GetProjectsDocument = gql` query getProjects { + organizations { + id + name + } teams { id name @@ -1768,6 +1847,41 @@ export function useMeLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptio export type MeQueryHookResult = ReturnType; export type MeLazyQueryHookResult = ReturnType; export type MeQueryResult = ApolloReactCommon.QueryResult; +export const DeleteProjectDocument = gql` + mutation deleteProject($projectID: UUID!) { + deleteProject(input: {projectID: $projectID}) { + ok + project { + id + } + } +} + `; +export type DeleteProjectMutationFn = ApolloReactCommon.MutationFunction; + +/** + * __useDeleteProjectMutation__ + * + * To run a mutation, you first call `useDeleteProjectMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useDeleteProjectMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [deleteProjectMutation, { data, loading, error }] = useDeleteProjectMutation({ + * variables: { + * projectID: // value for 'projectID' + * }, + * }); + */ +export function useDeleteProjectMutation(baseOptions?: ApolloReactHooks.MutationHookOptions) { + return ApolloReactHooks.useMutation(DeleteProjectDocument, baseOptions); + } +export type DeleteProjectMutationHookResult = ReturnType; +export type DeleteProjectMutationResult = ApolloReactCommon.MutationResult; +export type DeleteProjectMutationOptions = ApolloReactCommon.BaseMutationOptions; export const CreateTaskChecklistItemDocument = gql` mutation createTaskChecklistItem($taskChecklistID: UUID!, $name: String!, $position: Float!) { createTaskChecklistItem(input: {taskChecklistID: $taskChecklistID, name: $name, position: $position}) { @@ -1980,6 +2094,41 @@ export function useUpdateTaskGroupNameMutation(baseOptions?: ApolloReactHooks.Mu export type UpdateTaskGroupNameMutationHookResult = ReturnType; export type UpdateTaskGroupNameMutationResult = ApolloReactCommon.MutationResult; export type UpdateTaskGroupNameMutationOptions = ApolloReactCommon.BaseMutationOptions; +export const CreateTeamDocument = gql` + mutation createTeam($name: String!, $organizationID: UUID!) { + createTeam(input: {name: $name, organizationID: $organizationID}) { + id + createdAt + name + } +} + `; +export type CreateTeamMutationFn = ApolloReactCommon.MutationFunction; + +/** + * __useCreateTeamMutation__ + * + * To run a mutation, you first call `useCreateTeamMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useCreateTeamMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [createTeamMutation, { data, loading, error }] = useCreateTeamMutation({ + * variables: { + * name: // value for 'name' + * organizationID: // value for 'organizationID' + * }, + * }); + */ +export function useCreateTeamMutation(baseOptions?: ApolloReactHooks.MutationHookOptions) { + return ApolloReactHooks.useMutation(CreateTeamDocument, baseOptions); + } +export type CreateTeamMutationHookResult = ReturnType; +export type CreateTeamMutationResult = ApolloReactCommon.MutationResult; +export type CreateTeamMutationOptions = ApolloReactCommon.BaseMutationOptions; export const ToggleTaskLabelDocument = gql` mutation toggleTaskLabel($taskID: UUID!, $projectLabelID: UUID!) { toggleTaskLabel(input: {taskID: $taskID, projectLabelID: $projectLabelID}) { diff --git a/web/src/shared/graphql/getProjects.graphqls b/web/src/shared/graphql/getProjects.graphqls index 942542f..b52e053 100644 --- a/web/src/shared/graphql/getProjects.graphqls +++ b/web/src/shared/graphql/getProjects.graphqls @@ -1,4 +1,8 @@ query getProjects { + organizations { + id + name + } teams { id name diff --git a/web/src/shared/graphql/project/deleteProject.ts b/web/src/shared/graphql/project/deleteProject.ts new file mode 100644 index 0000000..167604d --- /dev/null +++ b/web/src/shared/graphql/project/deleteProject.ts @@ -0,0 +1,14 @@ +import gql from 'graphql-tag'; + +export const DELETE_PROJECT_MUTATION = gql` + mutation deleteProject($projectID: UUID!) { + deleteProject(input: { projectID: $projectID }) { + ok + project { + id + } + } + } +`; + +export default DELETE_PROJECT_MUTATION; diff --git a/web/src/shared/graphql/team/createTeam.ts b/web/src/shared/graphql/team/createTeam.ts new file mode 100644 index 0000000..e4e63b8 --- /dev/null +++ b/web/src/shared/graphql/team/createTeam.ts @@ -0,0 +1,13 @@ +import gql from 'graphql-tag'; + +export const CREATE_TEAM_MUTATION = gql` + mutation createTeam($name: String!, $organizationID: UUID!) { + createTeam(input: { name: $name, organizationID: $organizationID }) { + id + createdAt + name + } + } +`; + +export default CREATE_TEAM_MUTATION;