feat: add public projects
This commit is contained in:
parent
696a9aeee7
commit
36f25391b4
@ -3,6 +3,7 @@ import React, { useState, useRef, useEffect, useContext } from 'react';
|
|||||||
import updateApolloCache from 'shared/utils/cache';
|
import updateApolloCache from 'shared/utils/cache';
|
||||||
import GlobalTopNavbar, { ProjectPopup } from 'App/TopNavbar';
|
import GlobalTopNavbar, { ProjectPopup } from 'App/TopNavbar';
|
||||||
import styled from 'styled-components/macro';
|
import styled from 'styled-components/macro';
|
||||||
|
import AsyncSelect from 'react-select/async';
|
||||||
import { usePopup, Popup } from 'shared/components/PopupMenu';
|
import { usePopup, Popup } from 'shared/components/PopupMenu';
|
||||||
import {
|
import {
|
||||||
useParams,
|
useParams,
|
||||||
@ -37,12 +38,18 @@ import Input from 'shared/components/Input';
|
|||||||
import Member from 'shared/components/Member';
|
import Member from 'shared/components/Member';
|
||||||
import EmptyBoard from 'shared/components/EmptyBoard';
|
import EmptyBoard from 'shared/components/EmptyBoard';
|
||||||
import NOOP from 'shared/utils/noop';
|
import NOOP from 'shared/utils/noop';
|
||||||
|
import { Lock } from 'shared/icons';
|
||||||
|
import Button from 'shared/components/Button';
|
||||||
|
import { useApolloClient } from '@apollo/react-hooks';
|
||||||
|
import gql from 'graphql-tag';
|
||||||
import Board, { BoardLoading } from './Board';
|
import Board, { BoardLoading } from './Board';
|
||||||
import Details from './Details';
|
import Details from './Details';
|
||||||
import LabelManagerEditor from './LabelManagerEditor';
|
import LabelManagerEditor from './LabelManagerEditor';
|
||||||
|
|
||||||
const CARD_LABEL_VARIANT_STORAGE_KEY = 'card_label_variant';
|
const CARD_LABEL_VARIANT_STORAGE_KEY = 'card_label_variant';
|
||||||
|
|
||||||
|
const RFC2822_EMAIL = /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/;
|
||||||
|
|
||||||
const useStateWithLocalStorage = (localStorageKey: string): [string, React.Dispatch<React.SetStateAction<string>>] => {
|
const useStateWithLocalStorage = (localStorageKey: string): [string, React.Dispatch<React.SetStateAction<string>>] => {
|
||||||
const [value, setValue] = React.useState<string>(localStorage.getItem(localStorageKey) || '');
|
const [value, setValue] = React.useState<string>(localStorage.getItem(localStorageKey) || '');
|
||||||
|
|
||||||
@ -76,23 +83,90 @@ type UserManagementPopupProps = {
|
|||||||
onAddProjectMember: (userID: string) => void;
|
onAddProjectMember: (userID: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const VisibiltyPrivateIcon = styled(Lock)`
|
||||||
|
padding-right: 4px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const VisibiltyButtonText = styled.span`
|
||||||
|
color: rgba(${props => props.theme.colors.text.primary});
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ShareActions = styled.div`
|
||||||
|
border-top: 1px solid #414561;
|
||||||
|
margin-top: 8px;
|
||||||
|
padding-top: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const VisibiltyButton = styled.button`
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 2px 4px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-bottom: 1px solid transparent;
|
||||||
|
&:hover ${VisibiltyButtonText} {
|
||||||
|
color: rgba(${props => props.theme.colors.text.secondary});
|
||||||
|
}
|
||||||
|
&:hover ${VisibiltyPrivateIcon} {
|
||||||
|
fill: rgba(${props => props.theme.colors.text.secondary});
|
||||||
|
stroke: rgba(${props => props.theme.colors.text.secondary});
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
border-bottom: 1px solid rgba(${props => props.theme.colors.primary});
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
type MemberFilterOptions = {
|
||||||
|
projectID?: null | string;
|
||||||
|
teamID?: null | string;
|
||||||
|
organization?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchMembers = async (client: any, options: MemberFilterOptions, input: string, cb: any) => {
|
||||||
|
if (input && input.trim().length < 3) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const res = await client.query({
|
||||||
|
query: gql`
|
||||||
|
query {
|
||||||
|
searchMembers(input: {SearchFilter:"${input}"}) {
|
||||||
|
id
|
||||||
|
similarity
|
||||||
|
username
|
||||||
|
fullName
|
||||||
|
confirmed
|
||||||
|
joined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
let results: any = [];
|
||||||
|
if (res.data && res.data.searchMembers) {
|
||||||
|
results = [...res.data.searchMembers.map((m: any) => ({ label: m.fullName, value: m.id }))];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RFC2822_EMAIL.test(input)) {
|
||||||
|
results = [...results, { label: input, value: input }];
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
};
|
||||||
|
|
||||||
const UserManagementPopup: React.FC<UserManagementPopupProps> = ({ users, projectMembers, onAddProjectMember }) => {
|
const UserManagementPopup: React.FC<UserManagementPopupProps> = ({ users, projectMembers, onAddProjectMember }) => {
|
||||||
|
const client = useApolloClient();
|
||||||
return (
|
return (
|
||||||
<Popup tab={0} title="Invite a user">
|
<Popup tab={0} title="Invite a user">
|
||||||
<SearchInput width="100%" variant="alternate" placeholder="Email address or name" name="search" />
|
<AsyncSelect isMulti cacheOptions defaultOption loadOptions={(i, cb) => fetchMembers(client, {}, i, cb)} />
|
||||||
<MemberList>
|
<ShareActions>
|
||||||
{users
|
<VisibiltyButton>
|
||||||
.filter(u => u.id !== projectMembers.find(p => p.id === u.id)?.id)
|
<VisibiltyPrivateIcon width={12} height={12} />
|
||||||
.map(user => (
|
<VisibiltyButtonText>Private</VisibiltyButtonText>
|
||||||
<UserMember
|
</VisibiltyButton>
|
||||||
key={user.id}
|
</ShareActions>
|
||||||
onCardMemberClick={() => onAddProjectMember(user.id)}
|
|
||||||
showName
|
|
||||||
member={user}
|
|
||||||
taskID=""
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</MemberList>
|
|
||||||
</Popup>
|
</Popup>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
12
frontend/src/shared/icons/EyeSlash.tsx
Normal file
12
frontend/src/shared/icons/EyeSlash.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Icon, { IconProps } from './Icon';
|
||||||
|
|
||||||
|
const EyeSlash: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
|
||||||
|
return (
|
||||||
|
<Icon width={width} height={height} className={className} viewBox="0 0 640 512">
|
||||||
|
<path d="M320 400c-75.85 0-137.25-58.71-142.9-133.11L72.2 185.82c-13.79 17.3-26.48 35.59-36.72 55.59a32.35 32.35 0 0 0 0 29.19C89.71 376.41 197.07 448 320 448c26.91 0 52.87-4 77.89-10.46L346 397.39a144.13 144.13 0 0 1-26 2.61zm313.82 58.1l-110.55-85.44a331.25 331.25 0 0 0 81.25-102.07 32.35 32.35 0 0 0 0-29.19C550.29 135.59 442.93 64 320 64a308.15 308.15 0 0 0-147.32 37.7L45.46 3.37A16 16 0 0 0 23 6.18L3.37 31.45A16 16 0 0 0 6.18 53.9l588.36 454.73a16 16 0 0 0 22.46-2.81l19.64-25.27a16 16 0 0 0-2.82-22.45zm-183.72-142l-39.3-30.38A94.75 94.75 0 0 0 416 256a94.76 94.76 0 0 0-121.31-92.21A47.65 47.65 0 0 1 304 192a46.64 46.64 0 0 1-1.54 10l-73.61-56.89A142.31 142.31 0 0 1 320 112a143.92 143.92 0 0 1 144 144c0 21.63-5.29 41.79-13.9 60.11z" />
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EyeSlash;
|
@ -1,6 +1,7 @@
|
|||||||
import Cross from './Cross';
|
import Cross from './Cross';
|
||||||
import Cog from './Cog';
|
import Cog from './Cog';
|
||||||
import Eye from './Eye';
|
import Eye from './Eye';
|
||||||
|
import EyeSlash from './EyeSlash';
|
||||||
import List from './List';
|
import List from './List';
|
||||||
import At from './At';
|
import At from './At';
|
||||||
import Task from './Task';
|
import Task from './Task';
|
||||||
@ -88,5 +89,6 @@ export {
|
|||||||
Paperclip,
|
Paperclip,
|
||||||
Share,
|
Share,
|
||||||
Eye,
|
Eye,
|
||||||
|
EyeSlash,
|
||||||
List,
|
List,
|
||||||
};
|
};
|
||||||
|
1
go.mod
1
go.mod
@ -11,6 +11,7 @@ require (
|
|||||||
github.com/google/uuid v1.1.1
|
github.com/google/uuid v1.1.1
|
||||||
github.com/jmoiron/sqlx v1.2.0
|
github.com/jmoiron/sqlx v1.2.0
|
||||||
github.com/lib/pq v1.3.0
|
github.com/lib/pq v1.3.0
|
||||||
|
github.com/lithammer/fuzzysearch v1.1.0
|
||||||
github.com/magefile/mage v1.9.0
|
github.com/magefile/mage v1.9.0
|
||||||
github.com/pelletier/go-toml v1.8.0 // indirect
|
github.com/pelletier/go-toml v1.8.0 // indirect
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
|
2
go.sum
2
go.sum
@ -357,6 +357,8 @@ github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
|||||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
|
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
|
||||||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lithammer/fuzzysearch v1.1.0 h1:go9v8tLCrNTTlH42OAaq4eHFe81TDHEnlrMEb6R4f+A=
|
||||||
|
github.com/lithammer/fuzzysearch v1.1.0/go.mod h1:Bqx4wo8lTOFcJr3ckpY6HA9lEIOO0H5HrkJ5CsN56HQ=
|
||||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||||
github.com/magefile/mage v1.9.0 h1:t3AU2wNwehMCW97vuqQLtw6puppWXHO+O2MHo5a50XE=
|
github.com/magefile/mage v1.9.0 h1:t3AU2wNwehMCW97vuqQLtw6puppWXHO+O2MHo5a50XE=
|
||||||
github.com/magefile/mage v1.9.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
github.com/magefile/mage v1.9.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||||
|
@ -122,12 +122,12 @@ func (q *Queries) DeleteProjectMember(ctx context.Context, arg DeleteProjectMemb
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAllProjects = `-- name: GetAllProjects :many
|
const getAllProjectsForTeam = `-- name: GetAllProjectsForTeam :many
|
||||||
SELECT project_id, team_id, created_at, name FROM project
|
SELECT project_id, team_id, created_at, name FROM project WHERE team_id = $1
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetAllProjects(ctx context.Context) ([]Project, error) {
|
func (q *Queries) GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) ([]Project, error) {
|
||||||
rows, err := q.db.QueryContext(ctx, getAllProjects)
|
rows, err := q.db.QueryContext(ctx, getAllProjectsForTeam, teamID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -154,12 +154,12 @@ func (q *Queries) GetAllProjects(ctx context.Context) ([]Project, error) {
|
|||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAllProjectsForTeam = `-- name: GetAllProjectsForTeam :many
|
const getAllTeamProjects = `-- name: GetAllTeamProjects :many
|
||||||
SELECT project_id, team_id, created_at, name FROM project WHERE team_id = $1
|
SELECT project_id, team_id, created_at, name FROM project WHERE team_id IS NOT null
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) ([]Project, error) {
|
func (q *Queries) GetAllTeamProjects(ctx context.Context) ([]Project, error) {
|
||||||
rows, err := q.db.QueryContext(ctx, getAllProjectsForTeam, teamID)
|
rows, err := q.db.QueryContext(ctx, getAllTeamProjects)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -49,10 +49,10 @@ type Querier interface {
|
|||||||
DeleteUserAccountByID(ctx context.Context, userID uuid.UUID) error
|
DeleteUserAccountByID(ctx context.Context, userID uuid.UUID) error
|
||||||
GetAllNotificationsForUserID(ctx context.Context, notifierID uuid.UUID) ([]Notification, error)
|
GetAllNotificationsForUserID(ctx context.Context, notifierID uuid.UUID) ([]Notification, error)
|
||||||
GetAllOrganizations(ctx context.Context) ([]Organization, error)
|
GetAllOrganizations(ctx context.Context) ([]Organization, error)
|
||||||
GetAllProjects(ctx context.Context) ([]Project, error)
|
|
||||||
GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) ([]Project, error)
|
GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) ([]Project, error)
|
||||||
GetAllTaskGroups(ctx context.Context) ([]TaskGroup, error)
|
GetAllTaskGroups(ctx context.Context) ([]TaskGroup, error)
|
||||||
GetAllTasks(ctx context.Context) ([]Task, error)
|
GetAllTasks(ctx context.Context) ([]Task, error)
|
||||||
|
GetAllTeamProjects(ctx context.Context) ([]Project, error)
|
||||||
GetAllTeams(ctx context.Context) ([]Team, error)
|
GetAllTeams(ctx context.Context) ([]Team, error)
|
||||||
GetAllUserAccounts(ctx context.Context) ([]UserAccount, error)
|
GetAllUserAccounts(ctx context.Context) ([]UserAccount, error)
|
||||||
GetAllVisibleProjectsForUserID(ctx context.Context, userID uuid.UUID) ([]Project, error)
|
GetAllVisibleProjectsForUserID(ctx context.Context, userID uuid.UUID) ([]Project, error)
|
||||||
@ -61,6 +61,7 @@ type Querier interface {
|
|||||||
GetEntityIDForNotificationID(ctx context.Context, notificationID uuid.UUID) (uuid.UUID, error)
|
GetEntityIDForNotificationID(ctx context.Context, notificationID uuid.UUID) (uuid.UUID, error)
|
||||||
GetLabelColorByID(ctx context.Context, labelColorID uuid.UUID) (LabelColor, error)
|
GetLabelColorByID(ctx context.Context, labelColorID uuid.UUID) (LabelColor, error)
|
||||||
GetLabelColors(ctx context.Context) ([]LabelColor, error)
|
GetLabelColors(ctx context.Context) ([]LabelColor, error)
|
||||||
|
GetMemberData(ctx context.Context) ([]GetMemberDataRow, error)
|
||||||
GetMemberProjectIDsForUserID(ctx context.Context, userID uuid.UUID) ([]uuid.UUID, error)
|
GetMemberProjectIDsForUserID(ctx context.Context, userID uuid.UUID) ([]uuid.UUID, error)
|
||||||
GetMemberTeamIDsForUserID(ctx context.Context, userID uuid.UUID) ([]uuid.UUID, error)
|
GetMemberTeamIDsForUserID(ctx context.Context, userID uuid.UUID) ([]uuid.UUID, error)
|
||||||
GetNotificationForNotificationID(ctx context.Context, notificationID uuid.UUID) (GetNotificationForNotificationIDRow, error)
|
GetNotificationForNotificationID(ctx context.Context, notificationID uuid.UUID) (GetNotificationForNotificationIDRow, error)
|
||||||
|
@ -15,6 +15,9 @@ INSERT INTO user_account(full_name, initials, email, username, created_at, passw
|
|||||||
UPDATE user_account SET profile_avatar_url = $2 WHERE user_id = $1
|
UPDATE user_account SET profile_avatar_url = $2 WHERE user_id = $1
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: GetMemberData :many
|
||||||
|
SELECT username, email, user_id FROM user_account;
|
||||||
|
|
||||||
-- name: UpdateUserAccountInfo :one
|
-- name: UpdateUserAccountInfo :one
|
||||||
UPDATE user_account SET bio = $2, full_name = $3, initials = $4, email = $5
|
UPDATE user_account SET bio = $2, full_name = $3, initials = $4, email = $5
|
||||||
WHERE user_id = $1 RETURNING *;
|
WHERE user_id = $1 RETURNING *;
|
||||||
|
@ -101,6 +101,39 @@ func (q *Queries) GetAllUserAccounts(ctx context.Context) ([]UserAccount, error)
|
|||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getMemberData = `-- name: GetMemberData :many
|
||||||
|
SELECT username, email, user_id FROM user_account
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetMemberDataRow struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
UserID uuid.UUID `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetMemberData(ctx context.Context) ([]GetMemberDataRow, error) {
|
||||||
|
rows, err := q.db.QueryContext(ctx, getMemberData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetMemberDataRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetMemberDataRow
|
||||||
|
if err := rows.Scan(&i.Username, &i.Email, &i.UserID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
const getRoleForUserID = `-- name: GetRoleForUserID :one
|
const getRoleForUserID = `-- name: GetRoleForUserID :one
|
||||||
SELECT username, role.code, role.name FROM user_account
|
SELECT username, role.code, role.name FROM user_account
|
||||||
INNER JOIN role ON role.code = user_account.role_code
|
INNER JOIN role ON role.code = user_account.role_code
|
||||||
|
@ -160,6 +160,15 @@ type ComplexityRoot struct {
|
|||||||
Teams func(childComplexity int) int
|
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 {
|
Mutation struct {
|
||||||
AddTaskLabel func(childComplexity int, input *AddTaskLabelInput) int
|
AddTaskLabel func(childComplexity int, input *AddTaskLabelInput) int
|
||||||
AssignTask func(childComplexity int, input *AssignTaskInput) int
|
AssignTask func(childComplexity int, input *AssignTaskInput) int
|
||||||
@ -289,6 +298,7 @@ type ComplexityRoot struct {
|
|||||||
Notifications func(childComplexity int) int
|
Notifications func(childComplexity int) int
|
||||||
Organizations func(childComplexity int) int
|
Organizations func(childComplexity int) int
|
||||||
Projects func(childComplexity int, input *ProjectsFilter) int
|
Projects func(childComplexity int, input *ProjectsFilter) int
|
||||||
|
SearchMembers func(childComplexity int, input MemberSearchFilter) int
|
||||||
TaskGroups func(childComplexity int) int
|
TaskGroups func(childComplexity int) int
|
||||||
Teams func(childComplexity int) int
|
Teams func(childComplexity int) int
|
||||||
Users func(childComplexity int) int
|
Users func(childComplexity int) int
|
||||||
@ -528,6 +538,7 @@ type QueryResolver interface {
|
|||||||
TaskGroups(ctx context.Context) ([]db.TaskGroup, error)
|
TaskGroups(ctx context.Context) ([]db.TaskGroup, error)
|
||||||
Me(ctx context.Context) (*MePayload, error)
|
Me(ctx context.Context) (*MePayload, error)
|
||||||
Notifications(ctx context.Context) ([]db.Notification, error)
|
Notifications(ctx context.Context) ([]db.Notification, error)
|
||||||
|
SearchMembers(ctx context.Context, input MemberSearchFilter) ([]MemberSearchResult, error)
|
||||||
}
|
}
|
||||||
type RefreshTokenResolver interface {
|
type RefreshTokenResolver interface {
|
||||||
ID(ctx context.Context, obj *db.RefreshToken) (uuid.UUID, error)
|
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
|
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":
|
case "Mutation.addTaskLabel":
|
||||||
if e.complexity.Mutation.AddTaskLabel == nil {
|
if e.complexity.Mutation.AddTaskLabel == nil {
|
||||||
break
|
break
|
||||||
@ -1862,6 +1915,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||||||
|
|
||||||
return e.complexity.Query.Projects(childComplexity, args["input"].(*ProjectsFilter)), true
|
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":
|
case "Query.taskGroups":
|
||||||
if e.complexity.Query.TaskGroups == nil {
|
if e.complexity.Query.TaskGroups == nil {
|
||||||
break
|
break
|
||||||
@ -3208,6 +3273,24 @@ extend type Mutation {
|
|||||||
UpdateUserInfoPayload! @hasRole(roles: [ADMIN], level: ORG, type: ORG)
|
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 {
|
type UpdateUserInfoPayload {
|
||||||
user: UserAccount!
|
user: UserAccount!
|
||||||
}
|
}
|
||||||
@ -4101,6 +4184,20 @@ func (ec *executionContext) field_Query_projects_args(ctx context.Context, rawAr
|
|||||||
return args, nil
|
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) {
|
func (ec *executionContext) field___Type_enumValues_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||||
var err error
|
var err error
|
||||||
args := map[string]interface{}{}
|
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)
|
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) {
|
func (ec *executionContext) _Mutation_createProject(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
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)
|
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) {
|
func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
@ -15144,6 +15486,30 @@ func (ec *executionContext) unmarshalInputLogoutUser(ctx context.Context, obj in
|
|||||||
return it, nil
|
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) {
|
func (ec *executionContext) unmarshalInputNewProject(ctx context.Context, obj interface{}) (NewProject, error) {
|
||||||
var it NewProject
|
var it NewProject
|
||||||
var asMap = obj.(map[string]interface{})
|
var asMap = obj.(map[string]interface{})
|
||||||
@ -16675,6 +17041,58 @@ func (ec *executionContext) _MemberList(ctx context.Context, sel ast.SelectionSe
|
|||||||
return out
|
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"}
|
var mutationImplementors = []string{"Mutation"}
|
||||||
|
|
||||||
func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler {
|
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
|
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":
|
case "__type":
|
||||||
out.Values[i] = ec._Query___type(ctx, field)
|
out.Values[i] = ec._Query___type(ctx, field)
|
||||||
case "__schema":
|
case "__schema":
|
||||||
@ -19452,6 +19884,51 @@ func (ec *executionContext) marshalNMemberList2ᚖgithubᚗcomᚋjordanknottᚋt
|
|||||||
return ec._MemberList(ctx, sel, v)
|
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) {
|
func (ec *executionContext) unmarshalNNewProject2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐNewProject(ctx context.Context, v interface{}) (NewProject, error) {
|
||||||
return ec.unmarshalInputNewProject(ctx, v)
|
return ec.unmarshalInputNewProject(ctx, v)
|
||||||
}
|
}
|
||||||
|
@ -212,6 +212,20 @@ type MemberList struct {
|
|||||||
Projects []db.Project `json:"projects"`
|
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 {
|
type NewProject struct {
|
||||||
TeamID *uuid.UUID `json:"teamID"`
|
TeamID *uuid.UUID `json:"teamID"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
@ -734,6 +734,24 @@ extend type Mutation {
|
|||||||
UpdateUserInfoPayload! @hasRole(roles: [ADMIN], level: ORG, type: ORG)
|
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 {
|
type UpdateUserInfoPayload {
|
||||||
user: UserAccount!
|
user: UserAccount!
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jordanknott/taskcafe/internal/auth"
|
"github.com/jordanknott/taskcafe/internal/auth"
|
||||||
"github.com/jordanknott/taskcafe/internal/db"
|
"github.com/jordanknott/taskcafe/internal/db"
|
||||||
|
"github.com/lithammer/fuzzysearch/fuzzy"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
@ -1193,6 +1194,41 @@ func (r *queryResolver) Notifications(ctx context.Context) ([]db.Notification, e
|
|||||||
return notifications, nil
|
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) {
|
func (r *refreshTokenResolver) ID(ctx context.Context, obj *db.RefreshToken) (uuid.UUID, error) {
|
||||||
return obj.TokenID, nil
|
return obj.TokenID, nil
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,24 @@ extend type Mutation {
|
|||||||
UpdateUserInfoPayload! @hasRole(roles: [ADMIN], level: ORG, type: ORG)
|
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 {
|
type UpdateUserInfoPayload {
|
||||||
user: UserAccount!
|
user: UserAccount!
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user