feat: redesign project sharing

This commit is contained in:
Jordan Knott 2020-09-29 16:01:52 -05:00
parent 36f25391b4
commit 737d2b640f
17 changed files with 708 additions and 530 deletions

View File

@ -3,32 +3,11 @@ import updateApolloCache from 'shared/utils/cache';
import { usePopup, Popup } from 'shared/components/PopupMenu';
import produce from 'immer';
import {
useUpdateProjectMemberRoleMutation,
useCreateProjectMemberMutation,
useDeleteProjectMemberMutation,
useSetTaskCompleteMutation,
useToggleTaskLabelMutation,
useUpdateProjectNameMutation,
useFindProjectQuery,
useUpdateTaskGroupNameMutation,
useUpdateTaskNameMutation,
useUpdateProjectLabelMutation,
useCreateTaskMutation,
useDeleteProjectLabelMutation,
useDeleteTaskMutation,
useUpdateTaskLocationMutation,
useUpdateTaskGroupLocationMutation,
useCreateTaskGroupMutation,
useDeleteTaskGroupMutation,
useUpdateTaskDescriptionMutation,
useAssignTaskMutation,
DeleteTaskDocument,
FindProjectDocument,
useCreateProjectLabelMutation,
useUnassignTaskMutation,
useUpdateTaskDueDateMutation,
FindProjectQuery,
useUsersQuery,
} from 'shared/generated/graphql';
import LabelManager from 'shared/components/PopupMenu/LabelManager';
import LabelEditor from 'shared/components/PopupMenu/LabelEditor';

View File

@ -16,7 +16,7 @@ import {
} from 'react-router-dom';
import {
useUpdateProjectMemberRoleMutation,
useCreateProjectMemberMutation,
useInviteProjectMemberMutation,
useDeleteProjectMemberMutation,
useToggleTaskLabelMutation,
useUpdateProjectNameMutation,
@ -38,10 +38,12 @@ import Input from 'shared/components/Input';
import Member from 'shared/components/Member';
import EmptyBoard from 'shared/components/EmptyBoard';
import NOOP from 'shared/utils/noop';
import { Lock } from 'shared/icons';
import { Lock, Cross } from 'shared/icons';
import Button from 'shared/components/Button';
import { useApolloClient } from '@apollo/react-hooks';
import TaskAssignee from 'shared/components/TaskAssignee';
import gql from 'graphql-tag';
import { colourStyles } from 'shared/components/Select';
import Board, { BoardLoading } from './Board';
import Details from './Details';
import LabelManagerEditor from './LabelManagerEditor';
@ -77,10 +79,14 @@ const MemberList = styled.div`
margin: 8px 0;
`;
type InviteUserData = {
email?: string;
suerID?: string;
};
type UserManagementPopupProps = {
users: Array<User>;
projectMembers: Array<TaskUser>;
onAddProjectMember: (userID: string) => void;
onInviteProjectMember: (data: InviteUserData) => void;
};
const VisibiltyPrivateIcon = styled(Lock)`
@ -133,40 +139,182 @@ const fetchMembers = async (client: any, options: MemberFilterOptions, input: st
query: gql`
query {
searchMembers(input: {SearchFilter:"${input}"}) {
id
similarity
username
fullName
confirmed
joined
user {
id
fullName
email
profileIcon {
url
initials
bgColor
}
}
}
}
`,
});
let results: any = [];
const emails: Array<string> = [];
if (res.data && res.data.searchMembers) {
results = [...res.data.searchMembers.map((m: any) => ({ label: m.fullName, value: m.id }))];
results = [
...res.data.searchMembers.map((m: any) => {
emails.push(m.user.email);
return {
label: m.user.fullName,
value: { id: m.id, type: 0, profileIcon: m.user.profileIcon },
};
}),
];
}
if (RFC2822_EMAIL.test(input)) {
results = [...results, { label: input, value: input }];
if (RFC2822_EMAIL.test(input) && !emails.find(e => e === input)) {
results = [
...results,
{
label: input,
value: {
id: input,
type: 1,
profileIcon: {
bgColor: '#ccc',
initials: input.charAt(0),
},
},
},
];
}
return results;
};
const UserManagementPopup: React.FC<UserManagementPopupProps> = ({ users, projectMembers, onAddProjectMember }) => {
type UserOptionProps = {
innerProps: any;
isDisabled: boolean;
isFocused: boolean;
label: string;
data: any;
getValue: any;
};
const OptionWrapper = styled.div<{ isFocused: boolean }>`
cursor: pointer;
padding: 4px 8px;
${props => props.isFocused && `background: rgba(${props.theme.colors.primary});`}
display: flex;
align-items: center;
`;
const OptionContent = styled.div`
display: flex;
flex-direction: column;
margin-left: 12px;
`;
const UserOption: React.FC<UserOptionProps> = ({ isDisabled, isFocused, innerProps, label, data }) => {
return !isDisabled ? (
<OptionWrapper {...innerProps} isFocused={isFocused}>
<TaskAssignee
size={32}
member={{
id: '',
fullName: 'Jordan Knott',
profileIcon: data.value.profileIcon,
}}
/>
<OptionContent>{label}</OptionContent>
</OptionWrapper>
) : null;
};
const OptionValueWrapper = styled.div`
background: rgba(${props => props.theme.colors.bg.primary});
border-radius: 4px;
margin: 2px;
padding: 3px 6px 3px 4px;
display: flex;
align-items: center;
`;
const OptionValueLabel = styled.span`
font-size: 12px;
color: rgba(${props => props.theme.colors.text.secondary});
`;
const OptionValueRemove = styled.button`
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
background: none;
border: none;
outline: none;
padding: 0;
margin: 0;
margin-left: 4px;
`;
const OptionValue = ({ data, removeProps }: any) => {
return (
<OptionValueWrapper>
<OptionValueLabel>{data.label}</OptionValueLabel>
<OptionValueRemove {...removeProps}>
<Cross width={14} height={14} />
</OptionValueRemove>
</OptionValueWrapper>
);
};
const InviteButton = styled(Button)`
margin-top: 12px;
height: 32px;
padding: 4px 12px;
width: 100%;
justify-content: center;
`;
const InviteContainer = styled.div`
min-height: 300px;
display: flex;
flex-direction: column;
`;
const UserManagementPopup: React.FC<UserManagementPopupProps> = ({ users, projectMembers, onInviteProjectMember }) => {
const client = useApolloClient();
const [invitedUsers, setInvitedUsers] = useState<Array<any> | null>(null);
return (
<Popup tab={0} title="Invite a user">
<AsyncSelect isMulti cacheOptions defaultOption loadOptions={(i, cb) => fetchMembers(client, {}, i, cb)} />
<ShareActions>
<VisibiltyButton>
<VisibiltyPrivateIcon width={12} height={12} />
<VisibiltyButtonText>Private</VisibiltyButtonText>
</VisibiltyButton>
</ShareActions>
<InviteContainer>
<AsyncSelect
placeholder="Email address or username"
noOptionsMessage={() => null}
onChange={(e: any) => setInvitedUsers(e ? e.value : null)}
isMulti
autoFocus
cacheOptions
styles={colourStyles}
defaultOption
components={{
MultiValue: OptionValue,
Option: UserOption,
IndicatorSeparator: null,
DropdownIndicator: null,
}}
loadOptions={(i, cb) => fetchMembers(client, {}, i, cb)}
/>
</InviteContainer>
<InviteButton
onClick={() => {
// FUCK, gotta rewrite invite member to be MULTIPLE. SHIT!
// onInviteProjectMember();
}}
disabled={invitedUsers === null}
hoverVariant="none"
fontSize="16px"
>
Send Invite
</InviteButton>
</Popup>
);
};
@ -250,14 +398,14 @@ const Project = () => {
},
});
const [createProjectMember] = useCreateProjectMemberMutation({
const [inviteProjectMember] = useInviteProjectMemberMutation({
update: (client, response) => {
updateApolloCache<FindProjectQuery>(
client,
FindProjectDocument,
cache =>
produce(cache, draftCache => {
draftCache.findProject.members.push({ ...response.data.createProjectMember.member });
draftCache.findProject.members.push({ ...response.data.inviteProjectMember.member });
}),
{ projectID },
);
@ -324,8 +472,8 @@ const Project = () => {
showPopup(
$target,
<UserManagementPopup
onAddProjectMember={userID => {
createProjectMember({ variables: { userID, projectID } });
onInviteProjectMember={userID => {
// /inviteProjectMember({ variables: { userID, projectID } });
}}
users={data.users}
projectMembers={data.findProject.members}

View File

@ -35,11 +35,15 @@ const Base = styled.button<{ color: string; disabled: boolean }>`
`}
`;
const Filled = styled(Base)`
const Filled = styled(Base)<{ hoverVariant: HoverVariant }>`
background: rgba(${props => props.theme.colors[props.color]});
${props =>
props.hoverVariant === 'boxShadow' &&
css`
&:hover {
box-shadow: 0 8px 25px -8px rgba(${props => props.theme.colors[props.color]});
box-shadow: 0 8px 25px -8px rgba(${props.theme.colors[props.color]});
}
`}
`;
const Outline = styled(Base)<{ invert: boolean }>`
border: 1px solid rgba(${props => props.theme.colors[props.color]});
@ -123,9 +127,11 @@ const Relief = styled(Base)`
}
`;
type HoverVariant = 'boxShadow' | 'none';
type ButtonProps = {
fontSize?: string;
variant?: 'filled' | 'outline' | 'flat' | 'lineDown' | 'gradient' | 'relief';
hoverVariant?: HoverVariant;
color?: 'primary' | 'danger' | 'success' | 'warning' | 'dark';
disabled?: boolean;
type?: 'button' | 'submit';
@ -142,6 +148,7 @@ const Button: React.FC<ButtonProps> = ({
invert = false,
color = 'primary',
variant = 'filled',
hoverVariant = 'boxShadow',
type = 'button',
justifyTextContent = 'center',
icon,
@ -158,7 +165,15 @@ const Button: React.FC<ButtonProps> = ({
switch (variant) {
case 'filled':
return (
<Filled ref={$button} type={type} onClick={handleClick} className={className} disabled={disabled} color={color}>
<Filled
ref={$button}
hoverVariant={hoverVariant}
type={type}
onClick={handleClick}
className={className}
disabled={disabled}
color={color}
>
{icon && icon}
<Text hasIcon={typeof icon !== 'undefined'} justifyTextContent={justifyTextContent} fontSize={fontSize}>
{children}

View File

@ -16,7 +16,7 @@ function getBackgroundColor(isDisabled: boolean, isSelected: boolean, isFocused:
return null;
}
const colourStyles = {
export const colourStyles = {
control: (styles: any, data: any) => {
return {
...styles,

View File

@ -1,5 +1,5 @@
import React, { useRef } from 'react';
import styled from 'styled-components';
import styled, { css } from 'styled-components';
import { DoubleChevronUp, Crown } from 'shared/icons';
export const AdminIcon = styled(DoubleChevronUp)`
@ -24,7 +24,12 @@ const TaskDetailAssignee = styled.div`
position: relative;
`;
export const Wrapper = styled.div<{ size: number | string; bgColor: string | null; backgroundURL: string | null }>`
export const Wrapper = styled.div<{
size: number | string;
bgColor: string | null;
backgroundURL: string | null;
hasClick: boolean;
}>`
width: ${props => props.size}px;
height: ${props => props.size}px;
border-radius: 9999px;
@ -37,33 +42,52 @@ export const Wrapper = styled.div<{ size: number | string; bgColor: string | nul
background-size: contain;
font-size: 14px;
font-weight: 400;
${props =>
props.hasClick &&
css`
&:hover {
opacity: 0.8;
}
`}
`;
type TaskAssigneeProps = {
size: number | string;
showRoleIcons?: boolean;
member: TaskUser;
onMemberProfile: ($targetRef: React.RefObject<HTMLElement>, memberID: string) => void;
onMemberProfile?: ($targetRef: React.RefObject<HTMLElement>, memberID: string) => void;
className?: string;
};
const TaskAssignee: React.FC<TaskAssigneeProps> = ({ showRoleIcons, member, onMemberProfile, size, className }) => {
const $memberRef = useRef<HTMLDivElement>(null);
let profileIcon: ProfileIcon = {
url: null,
bgColor: null,
initials: null,
};
if (member.profileIcon) {
profileIcon = member.profileIcon;
}
return (
<TaskDetailAssignee
className={className}
ref={$memberRef}
onClick={e => {
e.stopPropagation();
if (onMemberProfile) {
onMemberProfile($memberRef, member.id);
}
}}
key={member.id}
>
<Wrapper backgroundURL={member.profileIcon.url ?? null} bgColor={member.profileIcon.bgColor ?? null} size={size}>
{(!member.profileIcon.url && member.profileIcon.initials) ?? ''}
<Wrapper
hasClick={typeof onMemberProfile !== undefined}
backgroundURL={profileIcon.url ?? null}
bgColor={profileIcon.bgColor ?? null}
size={size}
>
{(!profileIcon.url && profileIcon.initials) ?? ''}
</Wrapper>
{showRoleIcons && member.role && member.role.code === 'admin' && <AdminIcon width={10} height={10} />}
{showRoleIcons && member.role && member.role.code === 'owner' && <OwnerIcon width={10} height={10} />}

View File

@ -226,6 +226,7 @@ export type Query = {
notifications: Array<Notification>;
organizations: Array<Organization>;
projects: Array<Project>;
searchMembers: Array<MemberSearchResult>;
taskGroups: Array<TaskGroup>;
teams: Array<Team>;
users: Array<UserAccount>;
@ -256,6 +257,11 @@ export type QueryProjectsArgs = {
input?: Maybe<ProjectsFilter>;
};
export type QuerySearchMembersArgs = {
input: MemberSearchFilter;
};
export type Mutation = {
__typename?: 'Mutation';
addTaskLabel: Task;
@ -263,7 +269,6 @@ export type Mutation = {
clearProfileAvatar: UserAccount;
createProject: Project;
createProjectLabel: ProjectLabel;
createProjectMember: CreateProjectMemberPayload;
createRefreshToken: RefreshToken;
createTask: Task;
createTaskChecklist: TaskChecklist;
@ -284,6 +289,7 @@ export type Mutation = {
deleteTeamMember: DeleteTeamMemberPayload;
deleteUserAccount: DeleteUserAccountPayload;
duplicateTaskGroup: DuplicateTaskGroupPayload;
inviteProjectMember: InviteProjectMemberPayload;
logoutUser: Scalars['Boolean'];
removeTaskLabel: Task;
setTaskChecklistItemComplete: TaskChecklistItem;
@ -333,11 +339,6 @@ export type MutationCreateProjectLabelArgs = {
};
export type MutationCreateProjectMemberArgs = {
input: CreateProjectMember;
};
export type MutationCreateRefreshTokenArgs = {
input: NewRefreshToken;
};
@ -438,6 +439,11 @@ export type MutationDuplicateTaskGroupArgs = {
};
export type MutationInviteProjectMemberArgs = {
input: InviteProjectMember;
};
export type MutationLogoutUserArgs = {
input: LogoutUser;
};
@ -688,13 +694,14 @@ export type UpdateProjectLabelColor = {
labelColorID: Scalars['UUID'];
};
export type CreateProjectMember = {
export type InviteProjectMember = {
projectID: Scalars['UUID'];
userID: Scalars['UUID'];
userID?: Maybe<Scalars['UUID']>;
email?: Maybe<Scalars['String']>;
};
export type CreateProjectMemberPayload = {
__typename?: 'CreateProjectMemberPayload';
export type InviteProjectMemberPayload = {
__typename?: 'InviteProjectMemberPayload';
ok: Scalars['Boolean'];
member: Member;
};
@ -989,6 +996,20 @@ export type UpdateTeamMemberRolePayload = {
member: Member;
};
export type MemberSearchFilter = {
SearchFilter: Scalars['String'];
projectID?: Maybe<Scalars['UUID']>;
};
export type MemberSearchResult = {
__typename?: 'MemberSearchResult';
similarity: Scalars['Int'];
user: UserAccount;
confirmed: Scalars['Boolean'];
invited: Scalars['Boolean'];
joined: Scalars['Boolean'];
};
export type UpdateUserInfoPayload = {
__typename?: 'UpdateUserInfoPayload';
user: UserAccount;
@ -1390,31 +1411,6 @@ export type MeQuery = (
) }
);
export type CreateProjectMemberMutationVariables = {
projectID: Scalars['UUID'];
userID: Scalars['UUID'];
};
export type CreateProjectMemberMutation = (
{ __typename?: 'Mutation' }
& { createProjectMember: (
{ __typename?: 'CreateProjectMemberPayload' }
& Pick<CreateProjectMemberPayload, 'ok'>
& { member: (
{ __typename?: 'Member' }
& Pick<Member, 'id' | 'fullName' | 'username'>
& { profileIcon: (
{ __typename?: 'ProfileIcon' }
& Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'>
), role: (
{ __typename?: 'Role' }
& Pick<Role, 'code' | 'name'>
) }
) }
) }
);
export type DeleteProjectMutationVariables = {
projectID: Scalars['UUID'];
};
@ -1450,6 +1446,32 @@ export type DeleteProjectMemberMutation = (
) }
);
export type InviteProjectMemberMutationVariables = {
projectID: Scalars['UUID'];
userID?: Maybe<Scalars['UUID']>;
email?: Maybe<Scalars['String']>;
};
export type InviteProjectMemberMutation = (
{ __typename?: 'Mutation' }
& { inviteProjectMember: (
{ __typename?: 'InviteProjectMemberPayload' }
& Pick<InviteProjectMemberPayload, 'ok'>
& { member: (
{ __typename?: 'Member' }
& Pick<Member, 'id' | 'fullName' | 'username'>
& { profileIcon: (
{ __typename?: 'ProfileIcon' }
& Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'>
), role: (
{ __typename?: 'Role' }
& Pick<Role, 'code' | 'name'>
) }
) }
) }
);
export type UpdateProjectMemberRoleMutationVariables = {
projectID: Scalars['UUID'];
userID: Scalars['UUID'];
@ -2884,53 +2906,6 @@ export function useMeLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptio
export type MeQueryHookResult = ReturnType<typeof useMeQuery>;
export type MeLazyQueryHookResult = ReturnType<typeof useMeLazyQuery>;
export type MeQueryResult = ApolloReactCommon.QueryResult<MeQuery, MeQueryVariables>;
export const CreateProjectMemberDocument = gql`
mutation createProjectMember($projectID: UUID!, $userID: UUID!) {
createProjectMember(input: {projectID: $projectID, userID: $userID}) {
ok
member {
id
fullName
profileIcon {
url
initials
bgColor
}
username
role {
code
name
}
}
}
}
`;
export type CreateProjectMemberMutationFn = ApolloReactCommon.MutationFunction<CreateProjectMemberMutation, CreateProjectMemberMutationVariables>;
/**
* __useCreateProjectMemberMutation__
*
* To run a mutation, you first call `useCreateProjectMemberMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateProjectMemberMutation` 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 [createProjectMemberMutation, { data, loading, error }] = useCreateProjectMemberMutation({
* variables: {
* projectID: // value for 'projectID'
* userID: // value for 'userID'
* },
* });
*/
export function useCreateProjectMemberMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<CreateProjectMemberMutation, CreateProjectMemberMutationVariables>) {
return ApolloReactHooks.useMutation<CreateProjectMemberMutation, CreateProjectMemberMutationVariables>(CreateProjectMemberDocument, baseOptions);
}
export type CreateProjectMemberMutationHookResult = ReturnType<typeof useCreateProjectMemberMutation>;
export type CreateProjectMemberMutationResult = ApolloReactCommon.MutationResult<CreateProjectMemberMutation>;
export type CreateProjectMemberMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateProjectMemberMutation, CreateProjectMemberMutationVariables>;
export const DeleteProjectDocument = gql`
mutation deleteProject($projectID: UUID!) {
deleteProject(input: {projectID: $projectID}) {
@ -3003,6 +2978,54 @@ export function useDeleteProjectMemberMutation(baseOptions?: ApolloReactHooks.Mu
export type DeleteProjectMemberMutationHookResult = ReturnType<typeof useDeleteProjectMemberMutation>;
export type DeleteProjectMemberMutationResult = ApolloReactCommon.MutationResult<DeleteProjectMemberMutation>;
export type DeleteProjectMemberMutationOptions = ApolloReactCommon.BaseMutationOptions<DeleteProjectMemberMutation, DeleteProjectMemberMutationVariables>;
export const InviteProjectMemberDocument = gql`
mutation inviteProjectMember($projectID: UUID!, $userID: UUID, $email: String) {
inviteProjectMember(input: {projectID: $projectID, userID: $userID, email: $email}) {
ok
member {
id
fullName
profileIcon {
url
initials
bgColor
}
username
role {
code
name
}
}
}
}
`;
export type InviteProjectMemberMutationFn = ApolloReactCommon.MutationFunction<InviteProjectMemberMutation, InviteProjectMemberMutationVariables>;
/**
* __useInviteProjectMemberMutation__
*
* To run a mutation, you first call `useInviteProjectMemberMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useInviteProjectMemberMutation` 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 [inviteProjectMemberMutation, { data, loading, error }] = useInviteProjectMemberMutation({
* variables: {
* projectID: // value for 'projectID'
* userID: // value for 'userID'
* email: // value for 'email'
* },
* });
*/
export function useInviteProjectMemberMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<InviteProjectMemberMutation, InviteProjectMemberMutationVariables>) {
return ApolloReactHooks.useMutation<InviteProjectMemberMutation, InviteProjectMemberMutationVariables>(InviteProjectMemberDocument, baseOptions);
}
export type InviteProjectMemberMutationHookResult = ReturnType<typeof useInviteProjectMemberMutation>;
export type InviteProjectMemberMutationResult = ApolloReactCommon.MutationResult<InviteProjectMemberMutation>;
export type InviteProjectMemberMutationOptions = ApolloReactCommon.BaseMutationOptions<InviteProjectMemberMutation, InviteProjectMemberMutationVariables>;
export const UpdateProjectMemberRoleDocument = gql`
mutation updateProjectMemberRole($projectID: UUID!, $userID: UUID!, $roleCode: RoleCode!) {
updateProjectMemberRole(input: {projectID: $projectID, userID: $userID, roleCode: $roleCode}) {

View File

@ -1,25 +0,0 @@
import gql from 'graphql-tag';
export const CREATE_PROJECT_MEMBER_MUTATION = gql`
mutation createProjectMember($projectID: UUID!, $userID: UUID!) {
createProjectMember(input: { projectID: $projectID, userID: $userID }) {
ok
member {
id
fullName
profileIcon {
url
initials
bgColor
}
username
role {
code
name
}
}
}
}
`;
export default CREATE_PROJECT_MEMBER_MUTATION;

View File

@ -0,0 +1,25 @@
import gql from 'graphql-tag';
export const INVITE_PROJECT_MEMBER_MUTATION = gql`
mutation inviteProjectMember($projectID: UUID!, $userID: UUID, $email: String) {
inviteProjectMember(input: { projectID: $projectID, userID: $userID, email: $email }) {
ok
member {
id
fullName
profileIcon {
url
initials
bgColor
}
username
role {
code
name
}
}
}
}
`;
export default INVITE_PROJECT_MEMBER_MUTATION;

1
go.sum
View File

@ -109,6 +109,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSY
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369 h1:XNT/Zf5l++1Pyg08/HV04ppB0gKxAqtZQBRYiYrUuYk=
github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=

View File

@ -16,7 +16,7 @@ UPDATE user_account SET profile_avatar_url = $2 WHERE user_id = $1
RETURNING *;
-- name: GetMemberData :many
SELECT username, email, user_id FROM user_account;
SELECT username, full_name, email, user_id FROM user_account;
-- name: UpdateUserAccountInfo :one
UPDATE user_account SET bio = $2, full_name = $3, initials = $4, email = $5

View File

@ -102,11 +102,12 @@ func (q *Queries) GetAllUserAccounts(ctx context.Context) ([]UserAccount, error)
}
const getMemberData = `-- name: GetMemberData :many
SELECT username, email, user_id FROM user_account
SELECT username, full_name, email, user_id FROM user_account
`
type GetMemberDataRow struct {
Username string `json:"username"`
FullName string `json:"full_name"`
Email string `json:"email"`
UserID uuid.UUID `json:"user_id"`
}
@ -120,7 +121,12 @@ func (q *Queries) GetMemberData(ctx context.Context) ([]GetMemberDataRow, error)
var items []GetMemberDataRow
for rows.Next() {
var i GetMemberDataRow
if err := rows.Scan(&i.Username, &i.Email, &i.UserID); err != nil {
if err := rows.Scan(
&i.Username,
&i.FullName,
&i.Email,
&i.UserID,
); err != nil {
return nil, err
}
items = append(items, i)

View File

@ -65,11 +65,6 @@ type ComplexityRoot struct {
Total func(childComplexity int) int
}
CreateProjectMemberPayload struct {
Member func(childComplexity int) int
Ok func(childComplexity int) int
}
CreateTeamMemberPayload struct {
Team func(childComplexity int) int
TeamMember func(childComplexity int) int
@ -132,6 +127,11 @@ type ComplexityRoot struct {
TaskGroup func(childComplexity int) int
}
InviteProjectMemberPayload struct {
Member func(childComplexity int) int
Ok func(childComplexity int) int
}
LabelColor struct {
ColorHex func(childComplexity int) int
ID func(childComplexity int) int
@ -162,11 +162,10 @@ type ComplexityRoot struct {
MemberSearchResult struct {
Confirmed func(childComplexity int) int
FullName func(childComplexity int) int
ID func(childComplexity int) int
Invited func(childComplexity int) int
Joined func(childComplexity int) int
Similarity func(childComplexity int) int
Username func(childComplexity int) int
User func(childComplexity int) int
}
Mutation struct {
@ -175,7 +174,6 @@ type ComplexityRoot struct {
ClearProfileAvatar func(childComplexity int) int
CreateProject func(childComplexity int, input NewProject) int
CreateProjectLabel func(childComplexity int, input NewProjectLabel) int
CreateProjectMember func(childComplexity int, input CreateProjectMember) int
CreateRefreshToken func(childComplexity int, input NewRefreshToken) int
CreateTask func(childComplexity int, input NewTask) int
CreateTaskChecklist func(childComplexity int, input CreateTaskChecklist) int
@ -196,6 +194,7 @@ type ComplexityRoot struct {
DeleteTeamMember func(childComplexity int, input DeleteTeamMember) int
DeleteUserAccount func(childComplexity int, input DeleteUserAccount) int
DuplicateTaskGroup func(childComplexity int, input DuplicateTaskGroup) int
InviteProjectMember func(childComplexity int, input InviteProjectMember) int
LogoutUser func(childComplexity int, input LogoutUser) int
RemoveTaskLabel func(childComplexity int, input *RemoveTaskLabelInput) int
SetTaskChecklistItemComplete func(childComplexity int, input SetTaskChecklistItemComplete) int
@ -455,7 +454,7 @@ type MutationResolver interface {
UpdateProjectLabel(ctx context.Context, input UpdateProjectLabel) (*db.ProjectLabel, error)
UpdateProjectLabelName(ctx context.Context, input UpdateProjectLabelName) (*db.ProjectLabel, error)
UpdateProjectLabelColor(ctx context.Context, input UpdateProjectLabelColor) (*db.ProjectLabel, error)
CreateProjectMember(ctx context.Context, input CreateProjectMember) (*CreateProjectMemberPayload, error)
InviteProjectMember(ctx context.Context, input InviteProjectMember) (*InviteProjectMemberPayload, error)
DeleteProjectMember(ctx context.Context, input DeleteProjectMember) (*DeleteProjectMemberPayload, error)
UpdateProjectMemberRole(ctx context.Context, input UpdateProjectMemberRole) (*UpdateProjectMemberRolePayload, error)
CreateTask(ctx context.Context, input NewTask) (*db.Task, error)
@ -620,20 +619,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.ChecklistBadge.Total(childComplexity), true
case "CreateProjectMemberPayload.member":
if e.complexity.CreateProjectMemberPayload.Member == nil {
break
}
return e.complexity.CreateProjectMemberPayload.Member(childComplexity), true
case "CreateProjectMemberPayload.ok":
if e.complexity.CreateProjectMemberPayload.Ok == nil {
break
}
return e.complexity.CreateProjectMemberPayload.Ok(childComplexity), true
case "CreateTeamMemberPayload.team":
if e.complexity.CreateTeamMemberPayload.Team == nil {
break
@ -816,6 +801,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.DuplicateTaskGroupPayload.TaskGroup(childComplexity), true
case "InviteProjectMemberPayload.member":
if e.complexity.InviteProjectMemberPayload.Member == nil {
break
}
return e.complexity.InviteProjectMemberPayload.Member(childComplexity), true
case "InviteProjectMemberPayload.ok":
if e.complexity.InviteProjectMemberPayload.Ok == nil {
break
}
return e.complexity.InviteProjectMemberPayload.Ok(childComplexity), true
case "LabelColor.colorHex":
if e.complexity.LabelColor.ColorHex == nil {
break
@ -935,19 +934,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.MemberSearchResult.Confirmed(childComplexity), true
case "MemberSearchResult.fullName":
if e.complexity.MemberSearchResult.FullName == nil {
case "MemberSearchResult.invited":
if e.complexity.MemberSearchResult.Invited == 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
return e.complexity.MemberSearchResult.Invited(childComplexity), true
case "MemberSearchResult.joined":
if e.complexity.MemberSearchResult.Joined == nil {
@ -963,12 +955,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.MemberSearchResult.Similarity(childComplexity), true
case "MemberSearchResult.username":
if e.complexity.MemberSearchResult.Username == nil {
case "MemberSearchResult.user":
if e.complexity.MemberSearchResult.User == nil {
break
}
return e.complexity.MemberSearchResult.Username(childComplexity), true
return e.complexity.MemberSearchResult.User(childComplexity), true
case "Mutation.addTaskLabel":
if e.complexity.Mutation.AddTaskLabel == nil {
@ -1025,18 +1017,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.CreateProjectLabel(childComplexity, args["input"].(NewProjectLabel)), true
case "Mutation.createProjectMember":
if e.complexity.Mutation.CreateProjectMember == nil {
break
}
args, err := ec.field_Mutation_createProjectMember_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.CreateProjectMember(childComplexity, args["input"].(CreateProjectMember)), true
case "Mutation.createRefreshToken":
if e.complexity.Mutation.CreateRefreshToken == nil {
break
@ -1277,6 +1257,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.DuplicateTaskGroup(childComplexity, args["input"].(DuplicateTaskGroup)), true
case "Mutation.inviteProjectMember":
if e.complexity.Mutation.InviteProjectMember == nil {
break
}
args, err := ec.field_Mutation_inviteProjectMember_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.InviteProjectMember(childComplexity, args["input"].(InviteProjectMember)), true
case "Mutation.logoutUser":
if e.complexity.Mutation.LogoutUser == nil {
break
@ -2877,20 +2869,22 @@ input UpdateProjectLabelColor {
}
extend type Mutation {
createProjectMember(input: CreateProjectMember!):
CreateProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
# TODO: rename to inviteProjectMember
inviteProjectMember(input: InviteProjectMember!):
InviteProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
deleteProjectMember(input: DeleteProjectMember!):
DeleteProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
updateProjectMemberRole(input: UpdateProjectMemberRole!):
UpdateProjectMemberRolePayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
}
input CreateProjectMember {
input InviteProjectMember {
projectID: UUID!
userID: UUID!
userID: UUID
email: String
}
type CreateProjectMemberPayload {
type InviteProjectMemberPayload {
ok: Boolean!
member: Member!
}
@ -3283,11 +3277,10 @@ input MemberSearchFilter {
}
type MemberSearchResult {
id: UUID!
similarity: Int!
username: String!
fullName: String!
user: UserAccount!
confirmed: Boolean!
invited: Boolean!
joined: Boolean!
}
@ -3428,20 +3421,6 @@ func (ec *executionContext) field_Mutation_createProjectLabel_args(ctx context.C
return args, nil
}
func (ec *executionContext) field_Mutation_createProjectMember_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 CreateProjectMember
if tmp, ok := rawArgs["input"]; ok {
arg0, err = ec.unmarshalNCreateProjectMember2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐCreateProjectMember(ctx, tmp)
if err != nil {
return nil, err
}
}
args["input"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_createProject_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
@ -3736,6 +3715,20 @@ func (ec *executionContext) field_Mutation_duplicateTaskGroup_args(ctx context.C
return args, nil
}
func (ec *executionContext) field_Mutation_inviteProjectMember_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 InviteProjectMember
if tmp, ok := rawArgs["input"]; ok {
arg0, err = ec.unmarshalNInviteProjectMember2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐInviteProjectMember(ctx, tmp)
if err != nil {
return nil, err
}
}
args["input"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_logoutUser_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
@ -4302,74 +4295,6 @@ func (ec *executionContext) _ChecklistBadge_total(ctx context.Context, field gra
return ec.marshalNInt2int(ctx, field.Selections, res)
}
func (ec *executionContext) _CreateProjectMemberPayload_ok(ctx context.Context, field graphql.CollectedField, obj *CreateProjectMemberPayload) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "CreateProjectMemberPayload",
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) _CreateProjectMemberPayload_member(ctx context.Context, field graphql.CollectedField, obj *CreateProjectMemberPayload) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "CreateProjectMemberPayload",
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.Member, 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.(*Member)
fc.Result = res
return ec.marshalNMember2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐMember(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 {
@ -5254,6 +5179,74 @@ func (ec *executionContext) _DuplicateTaskGroupPayload_taskGroup(ctx context.Con
return ec.marshalNTaskGroup2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋdbᚐTaskGroup(ctx, field.Selections, res)
}
func (ec *executionContext) _InviteProjectMemberPayload_ok(ctx context.Context, field graphql.CollectedField, obj *InviteProjectMemberPayload) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "InviteProjectMemberPayload",
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) _InviteProjectMemberPayload_member(ctx context.Context, field graphql.CollectedField, obj *InviteProjectMemberPayload) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "InviteProjectMemberPayload",
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.Member, 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.(*Member)
fc.Result = res
return ec.marshalNMember2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐMember(ctx, field.Selections, res)
}
func (ec *executionContext) _LabelColor_id(ctx context.Context, field graphql.CollectedField, obj *db.LabelColor) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@ -5798,40 +5791,6 @@ 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 {
@ -5866,7 +5825,7 @@ func (ec *executionContext) _MemberSearchResult_similarity(ctx context.Context,
return ec.marshalNInt2int(ctx, field.Selections, res)
}
func (ec *executionContext) _MemberSearchResult_username(ctx context.Context, field graphql.CollectedField, obj *MemberSearchResult) (ret graphql.Marshaler) {
func (ec *executionContext) _MemberSearchResult_user(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))
@ -5883,7 +5842,7 @@ func (ec *executionContext) _MemberSearchResult_username(ctx context.Context, fi
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
return obj.User, nil
})
if err != nil {
ec.Error(ctx, err)
@ -5895,43 +5854,9 @@ func (ec *executionContext) _MemberSearchResult_username(ctx context.Context, fi
}
return graphql.Null
}
res := resTmp.(string)
res := resTmp.(*db.UserAccount)
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)
return ec.marshalNUserAccount2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋdbᚐUserAccount(ctx, field.Selections, res)
}
func (ec *executionContext) _MemberSearchResult_confirmed(ctx context.Context, field graphql.CollectedField, obj *MemberSearchResult) (ret graphql.Marshaler) {
@ -5968,6 +5893,40 @@ func (ec *executionContext) _MemberSearchResult_confirmed(ctx context.Context, f
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
}
func (ec *executionContext) _MemberSearchResult_invited(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.Invited, 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 {
@ -6586,7 +6545,7 @@ func (ec *executionContext) _Mutation_updateProjectLabelColor(ctx context.Contex
return ec.marshalNProjectLabel2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋdbᚐProjectLabel(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_createProjectMember(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
func (ec *executionContext) _Mutation_inviteProjectMember(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
@ -6602,7 +6561,7 @@ func (ec *executionContext) _Mutation_createProjectMember(ctx context.Context, f
ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Mutation_createProjectMember_args(ctx, rawArgs)
args, err := ec.field_Mutation_inviteProjectMember_args(ctx, rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
@ -6611,7 +6570,7 @@ func (ec *executionContext) _Mutation_createProjectMember(ctx context.Context, f
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
directive0 := func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().CreateProjectMember(rctx, args["input"].(CreateProjectMember))
return ec.resolvers.Mutation().InviteProjectMember(rctx, args["input"].(InviteProjectMember))
}
directive1 := func(ctx context.Context) (interface{}, error) {
roles, err := ec.unmarshalNRoleLevel2ᚕgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐRoleLevelᚄ(ctx, []interface{}{"ADMIN"})
@ -6639,10 +6598,10 @@ func (ec *executionContext) _Mutation_createProjectMember(ctx context.Context, f
if tmp == nil {
return nil, nil
}
if data, ok := tmp.(*CreateProjectMemberPayload); ok {
if data, ok := tmp.(*InviteProjectMemberPayload); ok {
return data, nil
}
return nil, fmt.Errorf(`unexpected type %T from directive, should be *github.com/jordanknott/taskcafe/internal/graph.CreateProjectMemberPayload`, tmp)
return nil, fmt.Errorf(`unexpected type %T from directive, should be *github.com/jordanknott/taskcafe/internal/graph.InviteProjectMemberPayload`, tmp)
})
if err != nil {
ec.Error(ctx, err)
@ -6654,9 +6613,9 @@ func (ec *executionContext) _Mutation_createProjectMember(ctx context.Context, f
}
return graphql.Null
}
res := resTmp.(*CreateProjectMemberPayload)
res := resTmp.(*InviteProjectMemberPayload)
fc.Result = res
return ec.marshalNCreateProjectMemberPayload2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐCreateProjectMemberPayload(ctx, field.Selections, res)
return ec.marshalNInviteProjectMemberPayload2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐInviteProjectMemberPayload(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_deleteProjectMember(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
@ -15030,30 +14989,6 @@ func (ec *executionContext) unmarshalInputAssignTaskInput(ctx context.Context, o
return it, nil
}
func (ec *executionContext) unmarshalInputCreateProjectMember(ctx context.Context, obj interface{}) (CreateProjectMember, error) {
var it CreateProjectMember
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
}
case "userID":
var err error
it.UserID, err = ec.unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v)
if err != nil {
return it, err
}
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputCreateTaskChecklist(ctx context.Context, obj interface{}) (CreateTaskChecklist, error) {
var it CreateTaskChecklist
var asMap = obj.(map[string]interface{})
@ -15468,6 +15403,36 @@ func (ec *executionContext) unmarshalInputFindUser(ctx context.Context, obj inte
return it, nil
}
func (ec *executionContext) unmarshalInputInviteProjectMember(ctx context.Context, obj interface{}) (InviteProjectMember, error) {
var it InviteProjectMember
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
}
case "userID":
var err error
it.UserID, err = ec.unmarshalOUUID2ᚖgithubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v)
if err != nil {
return it, err
}
case "email":
var err error
it.Email, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputLogoutUser(ctx context.Context, obj interface{}) (LogoutUser, error) {
var it LogoutUser
var asMap = obj.(map[string]interface{})
@ -16438,38 +16403,6 @@ func (ec *executionContext) _ChecklistBadge(ctx context.Context, sel ast.Selecti
return out
}
var createProjectMemberPayloadImplementors = []string{"CreateProjectMemberPayload"}
func (ec *executionContext) _CreateProjectMemberPayload(ctx context.Context, sel ast.SelectionSet, obj *CreateProjectMemberPayload) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, createProjectMemberPayloadImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("CreateProjectMemberPayload")
case "ok":
out.Values[i] = ec._CreateProjectMemberPayload_ok(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "member":
out.Values[i] = ec._CreateProjectMemberPayload_member(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 createTeamMemberPayloadImplementors = []string{"CreateTeamMemberPayload"}
func (ec *executionContext) _CreateTeamMemberPayload(ctx context.Context, sel ast.SelectionSet, obj *CreateTeamMemberPayload) graphql.Marshaler {
@ -16864,6 +16797,38 @@ func (ec *executionContext) _DuplicateTaskGroupPayload(ctx context.Context, sel
return out
}
var inviteProjectMemberPayloadImplementors = []string{"InviteProjectMemberPayload"}
func (ec *executionContext) _InviteProjectMemberPayload(ctx context.Context, sel ast.SelectionSet, obj *InviteProjectMemberPayload) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, inviteProjectMemberPayloadImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("InviteProjectMemberPayload")
case "ok":
out.Values[i] = ec._InviteProjectMemberPayload_ok(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "member":
out.Values[i] = ec._InviteProjectMemberPayload_member(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 labelColorImplementors = []string{"LabelColor"}
func (ec *executionContext) _LabelColor(ctx context.Context, sel ast.SelectionSet, obj *db.LabelColor) graphql.Marshaler {
@ -17052,23 +17017,13 @@ func (ec *executionContext) _MemberSearchResult(ctx context.Context, sel ast.Sel
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)
case "user":
out.Values[i] = ec._MemberSearchResult_user(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
@ -17077,6 +17032,11 @@ func (ec *executionContext) _MemberSearchResult(ctx context.Context, sel ast.Sel
if out.Values[i] == graphql.Null {
invalids++
}
case "invited":
out.Values[i] = ec._MemberSearchResult_invited(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 {
@ -17148,8 +17108,8 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null {
invalids++
}
case "createProjectMember":
out.Values[i] = ec._Mutation_createProjectMember(ctx, field)
case "inviteProjectMember":
out.Values[i] = ec._Mutation_inviteProjectMember(ctx, field)
if out.Values[i] == graphql.Null {
invalids++
}
@ -19437,24 +19397,6 @@ func (ec *executionContext) marshalNBoolean2bool(ctx context.Context, sel ast.Se
return res
}
func (ec *executionContext) unmarshalNCreateProjectMember2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐCreateProjectMember(ctx context.Context, v interface{}) (CreateProjectMember, error) {
return ec.unmarshalInputCreateProjectMember(ctx, v)
}
func (ec *executionContext) marshalNCreateProjectMemberPayload2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐCreateProjectMemberPayload(ctx context.Context, sel ast.SelectionSet, v CreateProjectMemberPayload) graphql.Marshaler {
return ec._CreateProjectMemberPayload(ctx, sel, &v)
}
func (ec *executionContext) marshalNCreateProjectMemberPayload2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐCreateProjectMemberPayload(ctx context.Context, sel ast.SelectionSet, v *CreateProjectMemberPayload) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
return ec._CreateProjectMemberPayload(ctx, sel, v)
}
func (ec *executionContext) unmarshalNCreateTaskChecklist2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐCreateTaskChecklist(ctx context.Context, v interface{}) (CreateTaskChecklist, error) {
return ec.unmarshalInputCreateTaskChecklist(ctx, v)
}
@ -19750,6 +19692,24 @@ func (ec *executionContext) marshalNInt2int(ctx context.Context, sel ast.Selecti
return res
}
func (ec *executionContext) unmarshalNInviteProjectMember2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐInviteProjectMember(ctx context.Context, v interface{}) (InviteProjectMember, error) {
return ec.unmarshalInputInviteProjectMember(ctx, v)
}
func (ec *executionContext) marshalNInviteProjectMemberPayload2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐInviteProjectMemberPayload(ctx context.Context, sel ast.SelectionSet, v InviteProjectMemberPayload) graphql.Marshaler {
return ec._InviteProjectMemberPayload(ctx, sel, &v)
}
func (ec *executionContext) marshalNInviteProjectMemberPayload2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐInviteProjectMemberPayload(ctx context.Context, sel ast.SelectionSet, v *InviteProjectMemberPayload) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
return ec._InviteProjectMemberPayload(ctx, sel, v)
}
func (ec *executionContext) marshalNLabelColor2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋdbᚐLabelColor(ctx context.Context, sel ast.SelectionSet, v db.LabelColor) graphql.Marshaler {
return ec._LabelColor(ctx, sel, &v)
}

View File

@ -27,16 +27,6 @@ type ChecklistBadge struct {
Total int `json:"total"`
}
type CreateProjectMember struct {
ProjectID uuid.UUID `json:"projectID"`
UserID uuid.UUID `json:"userID"`
}
type CreateProjectMemberPayload struct {
Ok bool `json:"ok"`
Member *Member `json:"member"`
}
type CreateTaskChecklist struct {
TaskID uuid.UUID `json:"taskID"`
Name string `json:"name"`
@ -187,6 +177,17 @@ type FindUser struct {
UserID uuid.UUID `json:"userID"`
}
type InviteProjectMember struct {
ProjectID uuid.UUID `json:"projectID"`
UserID *uuid.UUID `json:"userID"`
Email *string `json:"email"`
}
type InviteProjectMemberPayload struct {
Ok bool `json:"ok"`
Member *Member `json:"member"`
}
type LogoutUser struct {
UserID uuid.UUID `json:"userID"`
}
@ -218,11 +219,10 @@ type MemberSearchFilter struct {
}
type MemberSearchResult struct {
ID uuid.UUID `json:"id"`
Similarity int `json:"similarity"`
Username string `json:"username"`
FullName string `json:"fullName"`
User *db.UserAccount `json:"user"`
Confirmed bool `json:"confirmed"`
Invited bool `json:"invited"`
Joined bool `json:"joined"`
}

View File

@ -338,20 +338,22 @@ input UpdateProjectLabelColor {
}
extend type Mutation {
createProjectMember(input: CreateProjectMember!):
CreateProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
# TODO: rename to inviteProjectMember
inviteProjectMember(input: InviteProjectMember!):
InviteProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
deleteProjectMember(input: DeleteProjectMember!):
DeleteProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
updateProjectMemberRole(input: UpdateProjectMemberRole!):
UpdateProjectMemberRolePayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
}
input CreateProjectMember {
input InviteProjectMember {
projectID: UUID!
userID: UUID!
userID: UUID
email: String
}
type CreateProjectMemberPayload {
type InviteProjectMemberPayload {
ok: Boolean!
member: Member!
}
@ -744,11 +746,10 @@ input MemberSearchFilter {
}
type MemberSearchResult {
id: UUID!
similarity: Int!
username: String!
fullName: String!
user: UserAccount!
confirmed: Boolean!
invited: Boolean!
joined: Boolean!
}

View File

@ -124,15 +124,32 @@ func (r *mutationResolver) UpdateProjectLabelColor(ctx context.Context, input Up
return &label, err
}
func (r *mutationResolver) CreateProjectMember(ctx context.Context, input CreateProjectMember) (*CreateProjectMemberPayload, error) {
addedAt := time.Now().UTC()
_, err := r.Repository.CreateProjectMember(ctx, db.CreateProjectMemberParams{ProjectID: input.ProjectID, UserID: input.UserID, AddedAt: addedAt, RoleCode: "member"})
if err != nil {
return &CreateProjectMemberPayload{Ok: false}, err
func (r *mutationResolver) InviteProjectMember(ctx context.Context, input InviteProjectMember) (*InviteProjectMemberPayload, error) {
if input.Email != nil && input.UserID != nil {
return &InviteProjectMemberPayload{Ok: false}, &gqlerror.Error{
Message: "Both email and userID can not be used to invite a project member",
Extensions: map[string]interface{}{
"code": "403",
},
}
user, err := r.Repository.GetUserAccountByID(ctx, input.UserID)
} else if input.Email == nil && input.UserID == nil {
return &InviteProjectMemberPayload{Ok: false}, &gqlerror.Error{
Message: "Either email or userID must be set to invite a project member",
Extensions: map[string]interface{}{
"code": "403",
},
}
}
if input.UserID != nil {
addedAt := time.Now().UTC()
_, err := r.Repository.CreateProjectMember(ctx, db.CreateProjectMemberParams{ProjectID: input.ProjectID, UserID: *input.UserID, AddedAt: addedAt, RoleCode: "member"})
if err != nil {
return &CreateProjectMemberPayload{Ok: false}, err
return &InviteProjectMemberPayload{Ok: false}, err
}
user, err := r.Repository.GetUserAccountByID(ctx, *input.UserID)
if err != nil && err != sql.ErrNoRows {
return &InviteProjectMemberPayload{Ok: false}, err
}
var url *string
if user.ProfileAvatarUrl.Valid {
@ -140,17 +157,20 @@ func (r *mutationResolver) CreateProjectMember(ctx context.Context, input Create
}
profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor}
role, err := r.Repository.GetRoleForProjectMemberByUserID(ctx, db.GetRoleForProjectMemberByUserIDParams{UserID: input.UserID, ProjectID: input.ProjectID})
role, err := r.Repository.GetRoleForProjectMemberByUserID(ctx, db.GetRoleForProjectMemberByUserIDParams{UserID: *input.UserID, ProjectID: input.ProjectID})
if err != nil {
return &CreateProjectMemberPayload{Ok: false}, err
return &InviteProjectMemberPayload{Ok: false}, err
}
return &CreateProjectMemberPayload{Ok: true, Member: &Member{
ID: input.UserID,
return &InviteProjectMemberPayload{Ok: true, Member: &Member{
ID: *input.UserID,
FullName: user.FullName,
Username: user.Username,
ProfileIcon: profileIcon,
Role: &db.Role{Code: role.Code, Name: role.Name},
}}, nil
}
// invite user
return &InviteProjectMemberPayload{Ok: false}, errors.New("not implemented")
}
func (r *mutationResolver) DeleteProjectMember(ctx context.Context, input DeleteProjectMember) (*DeleteProjectMemberPayload, error) {
@ -1222,7 +1242,7 @@ func (r *queryResolver) SearchMembers(ctx context.Context, input MemberSearchFil
}
return []MemberSearchResult{}, err
}
results = append(results, MemberSearchResult{FullName: user.FullName, Username: user.Username, Joined: false, Confirmed: false, Similarity: rank.Distance, ID: user.UserID})
results = append(results, MemberSearchResult{User: &user, Joined: false, Confirmed: false, Similarity: rank.Distance})
memberList[masterList[rank.Target]] = true
}
}

View File

@ -1,18 +1,20 @@
extend type Mutation {
createProjectMember(input: CreateProjectMember!):
CreateProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
# TODO: rename to inviteProjectMember
inviteProjectMember(input: InviteProjectMember!):
InviteProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
deleteProjectMember(input: DeleteProjectMember!):
DeleteProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
updateProjectMemberRole(input: UpdateProjectMemberRole!):
UpdateProjectMemberRolePayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
}
input CreateProjectMember {
input InviteProjectMember {
projectID: UUID!
userID: UUID!
userID: UUID
email: String
}
type CreateProjectMemberPayload {
type InviteProjectMemberPayload {
ok: Boolean!
member: Member!
}

View File

@ -25,11 +25,10 @@ input MemberSearchFilter {
}
type MemberSearchResult {
id: UUID!
similarity: Int!
username: String!
fullName: String!
user: UserAccount!
confirmed: Boolean!
invited: Boolean!
joined: Boolean!
}