feat: add public projects

This commit is contained in:
Jordan Knott
2020-09-21 21:43:56 -05:00
parent 696a9aeee7
commit 36f25391b4
14 changed files with 714 additions and 23 deletions

View File

@ -160,6 +160,15 @@ type ComplexityRoot struct {
Teams func(childComplexity int) int
}
MemberSearchResult struct {
Confirmed func(childComplexity int) int
FullName func(childComplexity int) int
ID func(childComplexity int) int
Joined func(childComplexity int) int
Similarity func(childComplexity int) int
Username func(childComplexity int) int
}
Mutation struct {
AddTaskLabel func(childComplexity int, input *AddTaskLabelInput) int
AssignTask func(childComplexity int, input *AssignTaskInput) int
@ -289,6 +298,7 @@ type ComplexityRoot struct {
Notifications func(childComplexity int) int
Organizations func(childComplexity int) int
Projects func(childComplexity int, input *ProjectsFilter) int
SearchMembers func(childComplexity int, input MemberSearchFilter) int
TaskGroups func(childComplexity int) int
Teams func(childComplexity int) int
Users func(childComplexity int) int
@ -528,6 +538,7 @@ type QueryResolver interface {
TaskGroups(ctx context.Context) ([]db.TaskGroup, error)
Me(ctx context.Context) (*MePayload, error)
Notifications(ctx context.Context) ([]db.Notification, error)
SearchMembers(ctx context.Context, input MemberSearchFilter) ([]MemberSearchResult, error)
}
type RefreshTokenResolver interface {
ID(ctx context.Context, obj *db.RefreshToken) (uuid.UUID, error)
@ -917,6 +928,48 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.MemberList.Teams(childComplexity), true
case "MemberSearchResult.confirmed":
if e.complexity.MemberSearchResult.Confirmed == nil {
break
}
return e.complexity.MemberSearchResult.Confirmed(childComplexity), true
case "MemberSearchResult.fullName":
if e.complexity.MemberSearchResult.FullName == nil {
break
}
return e.complexity.MemberSearchResult.FullName(childComplexity), true
case "MemberSearchResult.id":
if e.complexity.MemberSearchResult.ID == nil {
break
}
return e.complexity.MemberSearchResult.ID(childComplexity), true
case "MemberSearchResult.joined":
if e.complexity.MemberSearchResult.Joined == nil {
break
}
return e.complexity.MemberSearchResult.Joined(childComplexity), true
case "MemberSearchResult.similarity":
if e.complexity.MemberSearchResult.Similarity == nil {
break
}
return e.complexity.MemberSearchResult.Similarity(childComplexity), true
case "MemberSearchResult.username":
if e.complexity.MemberSearchResult.Username == nil {
break
}
return e.complexity.MemberSearchResult.Username(childComplexity), true
case "Mutation.addTaskLabel":
if e.complexity.Mutation.AddTaskLabel == nil {
break
@ -1862,6 +1915,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Query.Projects(childComplexity, args["input"].(*ProjectsFilter)), true
case "Query.searchMembers":
if e.complexity.Query.SearchMembers == nil {
break
}
args, err := ec.field_Query_searchMembers_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Query.SearchMembers(childComplexity, args["input"].(MemberSearchFilter)), true
case "Query.taskGroups":
if e.complexity.Query.TaskGroups == nil {
break
@ -3208,6 +3273,24 @@ extend type Mutation {
UpdateUserInfoPayload! @hasRole(roles: [ADMIN], level: ORG, type: ORG)
}
extend type Query {
searchMembers(input: MemberSearchFilter!): [MemberSearchResult!]!
}
input MemberSearchFilter {
SearchFilter: String!
projectID: UUID
}
type MemberSearchResult {
id: UUID!
similarity: Int!
username: String!
fullName: String!
confirmed: Boolean!
joined: Boolean!
}
type UpdateUserInfoPayload {
user: UserAccount!
}
@ -4101,6 +4184,20 @@ func (ec *executionContext) field_Query_projects_args(ctx context.Context, rawAr
return args, nil
}
func (ec *executionContext) field_Query_searchMembers_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 MemberSearchFilter
if tmp, ok := rawArgs["input"]; ok {
arg0, err = ec.unmarshalNMemberSearchFilter2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐMemberSearchFilter(ctx, tmp)
if err != nil {
return nil, err
}
}
args["input"] = arg0
return args, nil
}
func (ec *executionContext) field___Type_enumValues_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
@ -5701,6 +5798,210 @@ func (ec *executionContext) _MemberList_projects(ctx context.Context, field grap
return ec.marshalNProject2ᚕgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋdbᚐProjectᚄ(ctx, field.Selections, res)
}
func (ec *executionContext) _MemberSearchResult_id(ctx context.Context, field graphql.CollectedField, obj *MemberSearchResult) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "MemberSearchResult",
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.ID, 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.(uuid.UUID)
fc.Result = res
return ec.marshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, field.Selections, res)
}
func (ec *executionContext) _MemberSearchResult_similarity(ctx context.Context, field graphql.CollectedField, obj *MemberSearchResult) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "MemberSearchResult",
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.Similarity, 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.(int)
fc.Result = res
return ec.marshalNInt2int(ctx, field.Selections, res)
}
func (ec *executionContext) _MemberSearchResult_username(ctx context.Context, field graphql.CollectedField, obj *MemberSearchResult) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "MemberSearchResult",
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.Username, 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) _MemberSearchResult_fullName(ctx context.Context, field graphql.CollectedField, obj *MemberSearchResult) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "MemberSearchResult",
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.FullName, 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) _MemberSearchResult_confirmed(ctx context.Context, field graphql.CollectedField, obj *MemberSearchResult) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "MemberSearchResult",
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.Confirmed, 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) _MemberSearchResult_joined(ctx context.Context, field graphql.CollectedField, obj *MemberSearchResult) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "MemberSearchResult",
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.Joined, 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) _Mutation_createProject(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@ -10978,6 +11279,47 @@ func (ec *executionContext) _Query_notifications(ctx context.Context, field grap
return ec.marshalNNotification2ᚕgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋdbᚐNotificationᚄ(ctx, field.Selections, res)
}
func (ec *executionContext) _Query_searchMembers(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)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Query_searchMembers_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.Query().SearchMembers(rctx, args["input"].(MemberSearchFilter))
})
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.([]MemberSearchResult)
fc.Result = res
return ec.marshalNMemberSearchResult2ᚕgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐMemberSearchResultᚄ(ctx, field.Selections, res)
}
func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@ -15144,6 +15486,30 @@ func (ec *executionContext) unmarshalInputLogoutUser(ctx context.Context, obj in
return it, nil
}
func (ec *executionContext) unmarshalInputMemberSearchFilter(ctx context.Context, obj interface{}) (MemberSearchFilter, error) {
var it MemberSearchFilter
var asMap = obj.(map[string]interface{})
for k, v := range asMap {
switch k {
case "SearchFilter":
var err error
it.SearchFilter, err = ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
case "projectID":
var err error
it.ProjectID, err = ec.unmarshalOUUID2ᚖgithubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v)
if err != nil {
return it, err
}
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputNewProject(ctx context.Context, obj interface{}) (NewProject, error) {
var it NewProject
var asMap = obj.(map[string]interface{})
@ -16675,6 +17041,58 @@ func (ec *executionContext) _MemberList(ctx context.Context, sel ast.SelectionSe
return out
}
var memberSearchResultImplementors = []string{"MemberSearchResult"}
func (ec *executionContext) _MemberSearchResult(ctx context.Context, sel ast.SelectionSet, obj *MemberSearchResult) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, memberSearchResultImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("MemberSearchResult")
case "id":
out.Values[i] = ec._MemberSearchResult_id(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "similarity":
out.Values[i] = ec._MemberSearchResult_similarity(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "username":
out.Values[i] = ec._MemberSearchResult_username(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "fullName":
out.Values[i] = ec._MemberSearchResult_fullName(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "confirmed":
out.Values[i] = ec._MemberSearchResult_confirmed(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "joined":
out.Values[i] = ec._MemberSearchResult_joined(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 mutationImplementors = []string{"Mutation"}
func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler {
@ -17645,6 +18063,20 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
}
return res
})
case "searchMembers":
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_searchMembers(ctx, field)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "__type":
out.Values[i] = ec._Query___type(ctx, field)
case "__schema":
@ -19452,6 +19884,51 @@ func (ec *executionContext) marshalNMemberList2ᚖgithubᚗcomᚋjordanknottᚋt
return ec._MemberList(ctx, sel, v)
}
func (ec *executionContext) unmarshalNMemberSearchFilter2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐMemberSearchFilter(ctx context.Context, v interface{}) (MemberSearchFilter, error) {
return ec.unmarshalInputMemberSearchFilter(ctx, v)
}
func (ec *executionContext) marshalNMemberSearchResult2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐMemberSearchResult(ctx context.Context, sel ast.SelectionSet, v MemberSearchResult) graphql.Marshaler {
return ec._MemberSearchResult(ctx, sel, &v)
}
func (ec *executionContext) marshalNMemberSearchResult2ᚕgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐMemberSearchResultᚄ(ctx context.Context, sel ast.SelectionSet, v []MemberSearchResult) 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.marshalNMemberSearchResult2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐMemberSearchResult(ctx, sel, v[i])
}
if isLen1 {
f(i)
} else {
go f(i)
}
}
wg.Wait()
return ret
}
func (ec *executionContext) unmarshalNNewProject2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐNewProject(ctx context.Context, v interface{}) (NewProject, error) {
return ec.unmarshalInputNewProject(ctx, v)
}

View File

@ -212,6 +212,20 @@ type MemberList struct {
Projects []db.Project `json:"projects"`
}
type MemberSearchFilter struct {
SearchFilter string `json:"SearchFilter"`
ProjectID *uuid.UUID `json:"projectID"`
}
type MemberSearchResult struct {
ID uuid.UUID `json:"id"`
Similarity int `json:"similarity"`
Username string `json:"username"`
FullName string `json:"fullName"`
Confirmed bool `json:"confirmed"`
Joined bool `json:"joined"`
}
type NewProject struct {
TeamID *uuid.UUID `json:"teamID"`
Name string `json:"name"`

View File

@ -734,6 +734,24 @@ extend type Mutation {
UpdateUserInfoPayload! @hasRole(roles: [ADMIN], level: ORG, type: ORG)
}
extend type Query {
searchMembers(input: MemberSearchFilter!): [MemberSearchResult!]!
}
input MemberSearchFilter {
SearchFilter: String!
projectID: UUID
}
type MemberSearchResult {
id: UUID!
similarity: Int!
username: String!
fullName: String!
confirmed: Boolean!
joined: Boolean!
}
type UpdateUserInfoPayload {
user: UserAccount!
}

View File

@ -13,6 +13,7 @@ import (
"github.com/google/uuid"
"github.com/jordanknott/taskcafe/internal/auth"
"github.com/jordanknott/taskcafe/internal/db"
"github.com/lithammer/fuzzysearch/fuzzy"
log "github.com/sirupsen/logrus"
"github.com/vektah/gqlparser/v2/gqlerror"
"golang.org/x/crypto/bcrypt"
@ -1193,6 +1194,41 @@ func (r *queryResolver) Notifications(ctx context.Context) ([]db.Notification, e
return notifications, nil
}
func (r *queryResolver) SearchMembers(ctx context.Context, input MemberSearchFilter) ([]MemberSearchResult, error) {
availableMembers, err := r.Repository.GetMemberData(ctx)
if err != nil {
return []MemberSearchResult{}, err
}
sortList := []string{}
masterList := map[string]uuid.UUID{}
for _, member := range availableMembers {
sortList = append(sortList, member.Username)
sortList = append(sortList, member.Email)
masterList[member.Username] = member.UserID
masterList[member.Email] = member.UserID
}
rankedList := fuzzy.RankFind(input.SearchFilter, sortList)
results := []MemberSearchResult{}
memberList := map[uuid.UUID]bool{}
for _, rank := range rankedList {
if _, ok := memberList[masterList[rank.Target]]; !ok {
log.WithFields(log.Fields{"source": rank.Source, "target": rank.Target}).Info("searching")
userID := masterList[rank.Target]
user, err := r.Repository.GetUserAccountByID(ctx, userID)
if err != nil {
if err == sql.ErrNoRows {
continue
}
return []MemberSearchResult{}, err
}
results = append(results, MemberSearchResult{FullName: user.FullName, Username: user.Username, Joined: false, Confirmed: false, Similarity: rank.Distance, ID: user.UserID})
memberList[masterList[rank.Target]] = true
}
}
return results, nil
}
func (r *refreshTokenResolver) ID(ctx context.Context, obj *db.RefreshToken) (uuid.UUID, error) {
return obj.TokenID, nil
}

View File

@ -15,6 +15,24 @@ extend type Mutation {
UpdateUserInfoPayload! @hasRole(roles: [ADMIN], level: ORG, type: ORG)
}
extend type Query {
searchMembers(input: MemberSearchFilter!): [MemberSearchResult!]!
}
input MemberSearchFilter {
SearchFilter: String!
projectID: UUID
}
type MemberSearchResult {
id: UUID!
similarity: Int!
username: String!
fullName: String!
confirmed: Boolean!
joined: Boolean!
}
type UpdateUserInfoPayload {
user: UserAccount!
}