feat: redesign project invite popup
This commit is contained in:
		@@ -16,7 +16,7 @@ import {
 | 
			
		||||
} from 'react-router-dom';
 | 
			
		||||
import {
 | 
			
		||||
  useUpdateProjectMemberRoleMutation,
 | 
			
		||||
  useInviteProjectMemberMutation,
 | 
			
		||||
  useInviteProjectMembersMutation,
 | 
			
		||||
  useDeleteProjectMemberMutation,
 | 
			
		||||
  useToggleTaskLabelMutation,
 | 
			
		||||
  useUpdateProjectNameMutation,
 | 
			
		||||
@@ -84,9 +84,10 @@ type InviteUserData = {
 | 
			
		||||
  suerID?: string;
 | 
			
		||||
};
 | 
			
		||||
type UserManagementPopupProps = {
 | 
			
		||||
  projectID: string;
 | 
			
		||||
  users: Array<User>;
 | 
			
		||||
  projectMembers: Array<TaskUser>;
 | 
			
		||||
  onInviteProjectMember: (data: InviteUserData) => void;
 | 
			
		||||
  onInviteProjectMembers: (data: Array<InviteUserData>) => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const VisibiltyPrivateIcon = styled(Lock)`
 | 
			
		||||
@@ -131,14 +132,14 @@ type MemberFilterOptions = {
 | 
			
		||||
  organization?: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const fetchMembers = async (client: any, options: MemberFilterOptions, input: string, cb: any) => {
 | 
			
		||||
const fetchMembers = async (client: any, projectID: string, 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}"}) {
 | 
			
		||||
      searchMembers(input: {SearchFilter:"${input}", projectID:"${projectID}"}) {
 | 
			
		||||
        similarity
 | 
			
		||||
        confirmed
 | 
			
		||||
        joined
 | 
			
		||||
@@ -165,7 +166,7 @@ const fetchMembers = async (client: any, options: MemberFilterOptions, input: st
 | 
			
		||||
        emails.push(m.user.email);
 | 
			
		||||
        return {
 | 
			
		||||
          label: m.user.fullName,
 | 
			
		||||
          value: { id: m.id, type: 0, profileIcon: m.user.profileIcon },
 | 
			
		||||
          value: { id: m.user.id, type: 0, profileIcon: m.user.profileIcon },
 | 
			
		||||
        };
 | 
			
		||||
      }),
 | 
			
		||||
    ];
 | 
			
		||||
@@ -214,6 +215,7 @@ const OptionContent = styled.div`
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const UserOption: React.FC<UserOptionProps> = ({ isDisabled, isFocused, innerProps, label, data }) => {
 | 
			
		||||
  console.log(data);
 | 
			
		||||
  return !isDisabled ? (
 | 
			
		||||
    <OptionWrapper {...innerProps} isFocused={isFocused}>
 | 
			
		||||
      <TaskAssignee
 | 
			
		||||
@@ -280,16 +282,24 @@ const InviteContainer = styled.div`
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const UserManagementPopup: React.FC<UserManagementPopupProps> = ({ users, projectMembers, onInviteProjectMember }) => {
 | 
			
		||||
const UserManagementPopup: React.FC<UserManagementPopupProps> = ({
 | 
			
		||||
  projectID,
 | 
			
		||||
  users,
 | 
			
		||||
  projectMembers,
 | 
			
		||||
  onInviteProjectMembers,
 | 
			
		||||
}) => {
 | 
			
		||||
  const client = useApolloClient();
 | 
			
		||||
  const [invitedUsers, setInvitedUsers] = useState<Array<any> | null>(null);
 | 
			
		||||
  return (
 | 
			
		||||
    <Popup tab={0} title="Invite a user">
 | 
			
		||||
      <InviteContainer>
 | 
			
		||||
        <AsyncSelect
 | 
			
		||||
          getOptionValue={option => option.value.id}
 | 
			
		||||
          placeholder="Email address or username"
 | 
			
		||||
          noOptionsMessage={() => null}
 | 
			
		||||
          onChange={(e: any) => setInvitedUsers(e ? e.value : null)}
 | 
			
		||||
          onChange={(e: any) => {
 | 
			
		||||
            setInvitedUsers(e);
 | 
			
		||||
          }}
 | 
			
		||||
          isMulti
 | 
			
		||||
          autoFocus
 | 
			
		||||
          cacheOptions
 | 
			
		||||
@@ -301,13 +311,25 @@ const UserManagementPopup: React.FC<UserManagementPopupProps> = ({ users, projec
 | 
			
		||||
            IndicatorSeparator: null,
 | 
			
		||||
            DropdownIndicator: null,
 | 
			
		||||
          }}
 | 
			
		||||
          loadOptions={(i, cb) => fetchMembers(client, {}, i, cb)}
 | 
			
		||||
          loadOptions={(i, cb) => fetchMembers(client, projectID, {}, i, cb)}
 | 
			
		||||
        />
 | 
			
		||||
      </InviteContainer>
 | 
			
		||||
      <InviteButton
 | 
			
		||||
        onClick={() => {
 | 
			
		||||
          // FUCK, gotta rewrite invite member to be MULTIPLE. SHIT!
 | 
			
		||||
          // onInviteProjectMember();
 | 
			
		||||
          if (invitedUsers) {
 | 
			
		||||
            onInviteProjectMembers(
 | 
			
		||||
              invitedUsers.map(user => {
 | 
			
		||||
                if (user.value.type === 0) {
 | 
			
		||||
                  return {
 | 
			
		||||
                    userID: user.value.id,
 | 
			
		||||
                  };
 | 
			
		||||
                }
 | 
			
		||||
                return {
 | 
			
		||||
                  email: user.value.id,
 | 
			
		||||
                };
 | 
			
		||||
              }),
 | 
			
		||||
            );
 | 
			
		||||
          }
 | 
			
		||||
        }}
 | 
			
		||||
        disabled={invitedUsers === null}
 | 
			
		||||
        hoverVariant="none"
 | 
			
		||||
@@ -398,14 +420,17 @@ const Project = () => {
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const [inviteProjectMember] = useInviteProjectMemberMutation({
 | 
			
		||||
  const [inviteProjectMembers] = useInviteProjectMembersMutation({
 | 
			
		||||
    update: (client, response) => {
 | 
			
		||||
      updateApolloCache<FindProjectQuery>(
 | 
			
		||||
        client,
 | 
			
		||||
        FindProjectDocument,
 | 
			
		||||
        cache =>
 | 
			
		||||
          produce(cache, draftCache => {
 | 
			
		||||
            draftCache.findProject.members.push({ ...response.data.inviteProjectMember.member });
 | 
			
		||||
            draftCache.findProject.members = [
 | 
			
		||||
              ...cache.findProject.members,
 | 
			
		||||
              ...response.data.inviteProjectMembers.members,
 | 
			
		||||
            ];
 | 
			
		||||
          }),
 | 
			
		||||
        { projectID },
 | 
			
		||||
      );
 | 
			
		||||
@@ -472,8 +497,10 @@ const Project = () => {
 | 
			
		||||
            showPopup(
 | 
			
		||||
              $target,
 | 
			
		||||
              <UserManagementPopup
 | 
			
		||||
                onInviteProjectMember={userID => {
 | 
			
		||||
                  // /inviteProjectMember({ variables: { userID, projectID } });
 | 
			
		||||
                projectID={projectID}
 | 
			
		||||
                onInviteProjectMembers={members => {
 | 
			
		||||
                  inviteProjectMembers({ variables: { projectID, members } });
 | 
			
		||||
                  hidePopup();
 | 
			
		||||
                }}
 | 
			
		||||
                users={data.users}
 | 
			
		||||
                projectMembers={data.findProject.members}
 | 
			
		||||
 
 | 
			
		||||
@@ -289,7 +289,7 @@ export type Mutation = {
 | 
			
		||||
  deleteTeamMember: DeleteTeamMemberPayload;
 | 
			
		||||
  deleteUserAccount: DeleteUserAccountPayload;
 | 
			
		||||
  duplicateTaskGroup: DuplicateTaskGroupPayload;
 | 
			
		||||
  inviteProjectMember: InviteProjectMemberPayload;
 | 
			
		||||
  inviteProjectMembers: InviteProjectMembersPayload;
 | 
			
		||||
  logoutUser: Scalars['Boolean'];
 | 
			
		||||
  removeTaskLabel: Task;
 | 
			
		||||
  setTaskChecklistItemComplete: TaskChecklistItem;
 | 
			
		||||
@@ -439,8 +439,8 @@ export type MutationDuplicateTaskGroupArgs = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export type MutationInviteProjectMemberArgs = {
 | 
			
		||||
  input: InviteProjectMember;
 | 
			
		||||
export type MutationInviteProjectMembersArgs = {
 | 
			
		||||
  input: InviteProjectMembers;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -694,16 +694,21 @@ export type UpdateProjectLabelColor = {
 | 
			
		||||
  labelColorID: Scalars['UUID'];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type InviteProjectMember = {
 | 
			
		||||
  projectID: Scalars['UUID'];
 | 
			
		||||
export type MemberInvite = {
 | 
			
		||||
  userID?: Maybe<Scalars['UUID']>;
 | 
			
		||||
  email?: Maybe<Scalars['String']>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type InviteProjectMemberPayload = {
 | 
			
		||||
   __typename?: 'InviteProjectMemberPayload';
 | 
			
		||||
export type InviteProjectMembers = {
 | 
			
		||||
  projectID: Scalars['UUID'];
 | 
			
		||||
  members: Array<MemberInvite>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type InviteProjectMembersPayload = {
 | 
			
		||||
   __typename?: 'InviteProjectMembersPayload';
 | 
			
		||||
  ok: Scalars['Boolean'];
 | 
			
		||||
  member: Member;
 | 
			
		||||
  projectID: Scalars['UUID'];
 | 
			
		||||
  members: Array<Member>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type DeleteProjectMember = {
 | 
			
		||||
@@ -1446,19 +1451,18 @@ export type DeleteProjectMemberMutation = (
 | 
			
		||||
  ) }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export type InviteProjectMemberMutationVariables = {
 | 
			
		||||
export type InviteProjectMembersMutationVariables = {
 | 
			
		||||
  projectID: Scalars['UUID'];
 | 
			
		||||
  userID?: Maybe<Scalars['UUID']>;
 | 
			
		||||
  email?: Maybe<Scalars['String']>;
 | 
			
		||||
  members: Array<MemberInvite>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export type InviteProjectMemberMutation = (
 | 
			
		||||
export type InviteProjectMembersMutation = (
 | 
			
		||||
  { __typename?: 'Mutation' }
 | 
			
		||||
  & { inviteProjectMember: (
 | 
			
		||||
    { __typename?: 'InviteProjectMemberPayload' }
 | 
			
		||||
    & Pick<InviteProjectMemberPayload, 'ok'>
 | 
			
		||||
    & { member: (
 | 
			
		||||
  & { inviteProjectMembers: (
 | 
			
		||||
    { __typename?: 'InviteProjectMembersPayload' }
 | 
			
		||||
    & Pick<InviteProjectMembersPayload, 'ok'>
 | 
			
		||||
    & { members: Array<(
 | 
			
		||||
      { __typename?: 'Member' }
 | 
			
		||||
      & Pick<Member, 'id' | 'fullName' | 'username'>
 | 
			
		||||
      & { profileIcon: (
 | 
			
		||||
@@ -1468,7 +1472,7 @@ export type InviteProjectMemberMutation = (
 | 
			
		||||
        { __typename?: 'Role' }
 | 
			
		||||
        & Pick<Role, 'code' | 'name'>
 | 
			
		||||
      ) }
 | 
			
		||||
    ) }
 | 
			
		||||
    )> }
 | 
			
		||||
  ) }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
@@ -2978,11 +2982,11 @@ 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}) {
 | 
			
		||||
export const InviteProjectMembersDocument = gql`
 | 
			
		||||
    mutation inviteProjectMembers($projectID: UUID!, $members: [MemberInvite!]!) {
 | 
			
		||||
  inviteProjectMembers(input: {projectID: $projectID, members: $members}) {
 | 
			
		||||
    ok
 | 
			
		||||
    member {
 | 
			
		||||
    members {
 | 
			
		||||
      id
 | 
			
		||||
      fullName
 | 
			
		||||
      profileIcon {
 | 
			
		||||
@@ -2999,33 +3003,32 @@ export const InviteProjectMemberDocument = gql`
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
    `;
 | 
			
		||||
export type InviteProjectMemberMutationFn = ApolloReactCommon.MutationFunction<InviteProjectMemberMutation, InviteProjectMemberMutationVariables>;
 | 
			
		||||
export type InviteProjectMembersMutationFn = ApolloReactCommon.MutationFunction<InviteProjectMembersMutation, InviteProjectMembersMutationVariables>;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * __useInviteProjectMemberMutation__
 | 
			
		||||
 * __useInviteProjectMembersMutation__
 | 
			
		||||
 *
 | 
			
		||||
 * 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:
 | 
			
		||||
 * To run a mutation, you first call `useInviteProjectMembersMutation` within a React component and pass it any options that fit your needs.
 | 
			
		||||
 * When your component renders, `useInviteProjectMembersMutation` 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({
 | 
			
		||||
 * const [inviteProjectMembersMutation, { data, loading, error }] = useInviteProjectMembersMutation({
 | 
			
		||||
 *   variables: {
 | 
			
		||||
 *      projectID: // value for 'projectID'
 | 
			
		||||
 *      userID: // value for 'userID'
 | 
			
		||||
 *      email: // value for 'email'
 | 
			
		||||
 *      members: // value for 'members'
 | 
			
		||||
 *   },
 | 
			
		||||
 * });
 | 
			
		||||
 */
 | 
			
		||||
export function useInviteProjectMemberMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<InviteProjectMemberMutation, InviteProjectMemberMutationVariables>) {
 | 
			
		||||
        return ApolloReactHooks.useMutation<InviteProjectMemberMutation, InviteProjectMemberMutationVariables>(InviteProjectMemberDocument, baseOptions);
 | 
			
		||||
export function useInviteProjectMembersMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<InviteProjectMembersMutation, InviteProjectMembersMutationVariables>) {
 | 
			
		||||
        return ApolloReactHooks.useMutation<InviteProjectMembersMutation, InviteProjectMembersMutationVariables>(InviteProjectMembersDocument, baseOptions);
 | 
			
		||||
      }
 | 
			
		||||
export type InviteProjectMemberMutationHookResult = ReturnType<typeof useInviteProjectMemberMutation>;
 | 
			
		||||
export type InviteProjectMemberMutationResult = ApolloReactCommon.MutationResult<InviteProjectMemberMutation>;
 | 
			
		||||
export type InviteProjectMemberMutationOptions = ApolloReactCommon.BaseMutationOptions<InviteProjectMemberMutation, InviteProjectMemberMutationVariables>;
 | 
			
		||||
export type InviteProjectMembersMutationHookResult = ReturnType<typeof useInviteProjectMembersMutation>;
 | 
			
		||||
export type InviteProjectMembersMutationResult = ApolloReactCommon.MutationResult<InviteProjectMembersMutation>;
 | 
			
		||||
export type InviteProjectMembersMutationOptions = ApolloReactCommon.BaseMutationOptions<InviteProjectMembersMutation, InviteProjectMembersMutationVariables>;
 | 
			
		||||
export const UpdateProjectMemberRoleDocument = gql`
 | 
			
		||||
    mutation updateProjectMemberRole($projectID: UUID!, $userID: UUID!, $roleCode: RoleCode!) {
 | 
			
		||||
  updateProjectMemberRole(input: {projectID: $projectID, userID: $userID, roleCode: $roleCode}) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +0,0 @@
 | 
			
		||||
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;
 | 
			
		||||
							
								
								
									
										25
									
								
								frontend/src/shared/graphql/project/inviteProjectMembers.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								frontend/src/shared/graphql/project/inviteProjectMembers.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
import gql from 'graphql-tag';
 | 
			
		||||
 | 
			
		||||
export const INVITE_PROJECT_MEMBERS_MUTATION = gql`
 | 
			
		||||
  mutation inviteProjectMembers($projectID: UUID!, $members: [MemberInvite!]!) {
 | 
			
		||||
    inviteProjectMembers(input: { projectID: $projectID, members: $members }) {
 | 
			
		||||
      ok
 | 
			
		||||
      members {
 | 
			
		||||
        id
 | 
			
		||||
        fullName
 | 
			
		||||
        profileIcon {
 | 
			
		||||
          url
 | 
			
		||||
          initials
 | 
			
		||||
          bgColor
 | 
			
		||||
        }
 | 
			
		||||
        username
 | 
			
		||||
        role {
 | 
			
		||||
          code
 | 
			
		||||
          name
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export default INVITE_PROJECT_MEMBERS_MUTATION;
 | 
			
		||||
@@ -78,8 +78,11 @@ func newWebCmd() *cobra.Command {
 | 
			
		||||
			return http.ListenAndServe(viper.GetString("server.hostname"), r)
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cc.Flags().Bool("migrate", false, "if true, auto run's schema migrations before starting the web server")
 | 
			
		||||
 | 
			
		||||
	viper.BindPFlag("migrate", cc.Flags().Lookup("migrate"))
 | 
			
		||||
 | 
			
		||||
	viper.SetDefault("migrate", false)
 | 
			
		||||
	return cc
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -61,7 +61,7 @@ type Querier interface {
 | 
			
		||||
	GetEntityIDForNotificationID(ctx context.Context, notificationID uuid.UUID) (uuid.UUID, error)
 | 
			
		||||
	GetLabelColorByID(ctx context.Context, labelColorID uuid.UUID) (LabelColor, error)
 | 
			
		||||
	GetLabelColors(ctx context.Context) ([]LabelColor, error)
 | 
			
		||||
	GetMemberData(ctx context.Context) ([]GetMemberDataRow, error)
 | 
			
		||||
	GetMemberData(ctx context.Context, projectID uuid.UUID) ([]UserAccount, error)
 | 
			
		||||
	GetMemberProjectIDsForUserID(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)
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,9 @@ UPDATE user_account SET profile_avatar_url = $2 WHERE user_id = $1
 | 
			
		||||
  RETURNING *;
 | 
			
		||||
 | 
			
		||||
-- name: GetMemberData :many
 | 
			
		||||
SELECT username, full_name, email, user_id FROM user_account;
 | 
			
		||||
SELECT * FROM user_account
 | 
			
		||||
  WHERE username != 'system'
 | 
			
		||||
  AND user_id NOT IN (SELECT user_id FROM project_member WHERE project_id = $1);
 | 
			
		||||
 | 
			
		||||
-- name: UpdateUserAccountInfo :one
 | 
			
		||||
UPDATE user_account SET bio = $2, full_name = $3, initials = $4, email = $5
 | 
			
		||||
 
 | 
			
		||||
@@ -102,30 +102,32 @@ func (q *Queries) GetAllUserAccounts(ctx context.Context) ([]UserAccount, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getMemberData = `-- name: GetMemberData :many
 | 
			
		||||
SELECT username, full_name, email, user_id FROM user_account
 | 
			
		||||
SELECT user_id, created_at, email, username, password_hash, profile_bg_color, full_name, initials, profile_avatar_url, role_code, bio FROM user_account
 | 
			
		||||
  WHERE username != 'system'
 | 
			
		||||
  AND user_id NOT IN (SELECT user_id FROM project_member WHERE project_id = $1)
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
type GetMemberDataRow struct {
 | 
			
		||||
	Username string    `json:"username"`
 | 
			
		||||
	FullName string    `json:"full_name"`
 | 
			
		||||
	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)
 | 
			
		||||
func (q *Queries) GetMemberData(ctx context.Context, projectID uuid.UUID) ([]UserAccount, error) {
 | 
			
		||||
	rows, err := q.db.QueryContext(ctx, getMemberData, projectID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	defer rows.Close()
 | 
			
		||||
	var items []GetMemberDataRow
 | 
			
		||||
	var items []UserAccount
 | 
			
		||||
	for rows.Next() {
 | 
			
		||||
		var i GetMemberDataRow
 | 
			
		||||
		var i UserAccount
 | 
			
		||||
		if err := rows.Scan(
 | 
			
		||||
			&i.Username,
 | 
			
		||||
			&i.FullName,
 | 
			
		||||
			&i.Email,
 | 
			
		||||
			&i.UserID,
 | 
			
		||||
			&i.CreatedAt,
 | 
			
		||||
			&i.Email,
 | 
			
		||||
			&i.Username,
 | 
			
		||||
			&i.PasswordHash,
 | 
			
		||||
			&i.ProfileBgColor,
 | 
			
		||||
			&i.FullName,
 | 
			
		||||
			&i.Initials,
 | 
			
		||||
			&i.ProfileAvatarUrl,
 | 
			
		||||
			&i.RoleCode,
 | 
			
		||||
			&i.Bio,
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -127,9 +127,10 @@ type ComplexityRoot struct {
 | 
			
		||||
		TaskGroup func(childComplexity int) int
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	InviteProjectMemberPayload struct {
 | 
			
		||||
		Member func(childComplexity int) int
 | 
			
		||||
		Ok     func(childComplexity int) int
 | 
			
		||||
	InviteProjectMembersPayload struct {
 | 
			
		||||
		Members   func(childComplexity int) int
 | 
			
		||||
		Ok        func(childComplexity int) int
 | 
			
		||||
		ProjectID func(childComplexity int) int
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	LabelColor struct {
 | 
			
		||||
@@ -194,7 +195,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
 | 
			
		||||
		InviteProjectMembers            func(childComplexity int, input InviteProjectMembers) int
 | 
			
		||||
		LogoutUser                      func(childComplexity int, input LogoutUser) int
 | 
			
		||||
		RemoveTaskLabel                 func(childComplexity int, input *RemoveTaskLabelInput) int
 | 
			
		||||
		SetTaskChecklistItemComplete    func(childComplexity int, input SetTaskChecklistItemComplete) int
 | 
			
		||||
@@ -454,7 +455,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)
 | 
			
		||||
	InviteProjectMember(ctx context.Context, input InviteProjectMember) (*InviteProjectMemberPayload, error)
 | 
			
		||||
	InviteProjectMembers(ctx context.Context, input InviteProjectMembers) (*InviteProjectMembersPayload, 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)
 | 
			
		||||
@@ -801,19 +802,26 @@ 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 {
 | 
			
		||||
	case "InviteProjectMembersPayload.members":
 | 
			
		||||
		if e.complexity.InviteProjectMembersPayload.Members == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return e.complexity.InviteProjectMemberPayload.Member(childComplexity), true
 | 
			
		||||
		return e.complexity.InviteProjectMembersPayload.Members(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "InviteProjectMemberPayload.ok":
 | 
			
		||||
		if e.complexity.InviteProjectMemberPayload.Ok == nil {
 | 
			
		||||
	case "InviteProjectMembersPayload.ok":
 | 
			
		||||
		if e.complexity.InviteProjectMembersPayload.Ok == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return e.complexity.InviteProjectMemberPayload.Ok(childComplexity), true
 | 
			
		||||
		return e.complexity.InviteProjectMembersPayload.Ok(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "InviteProjectMembersPayload.projectID":
 | 
			
		||||
		if e.complexity.InviteProjectMembersPayload.ProjectID == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return e.complexity.InviteProjectMembersPayload.ProjectID(childComplexity), true
 | 
			
		||||
 | 
			
		||||
	case "LabelColor.colorHex":
 | 
			
		||||
		if e.complexity.LabelColor.ColorHex == nil {
 | 
			
		||||
@@ -1257,17 +1265,17 @@ 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 {
 | 
			
		||||
	case "Mutation.inviteProjectMembers":
 | 
			
		||||
		if e.complexity.Mutation.InviteProjectMembers == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		args, err := ec.field_Mutation_inviteProjectMember_args(context.TODO(), rawArgs)
 | 
			
		||||
		args, err := ec.field_Mutation_inviteProjectMembers_args(context.TODO(), rawArgs)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return e.complexity.Mutation.InviteProjectMember(childComplexity, args["input"].(InviteProjectMember)), true
 | 
			
		||||
		return e.complexity.Mutation.InviteProjectMembers(childComplexity, args["input"].(InviteProjectMembers)), true
 | 
			
		||||
 | 
			
		||||
	case "Mutation.logoutUser":
 | 
			
		||||
		if e.complexity.Mutation.LogoutUser == nil {
 | 
			
		||||
@@ -2869,24 +2877,28 @@ input UpdateProjectLabelColor {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extend type Mutation {
 | 
			
		||||
  # TODO: rename to inviteProjectMember
 | 
			
		||||
  inviteProjectMember(input: InviteProjectMember!):
 | 
			
		||||
    InviteProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  inviteProjectMembers(input: InviteProjectMembers!):
 | 
			
		||||
    InviteProjectMembersPayload! @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 InviteProjectMember {
 | 
			
		||||
  projectID: UUID!
 | 
			
		||||
input MemberInvite {
 | 
			
		||||
  userID: UUID
 | 
			
		||||
  email: String
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type InviteProjectMemberPayload {
 | 
			
		||||
input InviteProjectMembers {
 | 
			
		||||
  projectID: UUID!
 | 
			
		||||
  members: [MemberInvite!]!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type InviteProjectMembersPayload {
 | 
			
		||||
  ok: Boolean!
 | 
			
		||||
  member: Member!
 | 
			
		||||
  projectID: UUID!
 | 
			
		||||
  members: [Member!]!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input DeleteProjectMember {
 | 
			
		||||
@@ -3715,12 +3727,12 @@ 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) {
 | 
			
		||||
func (ec *executionContext) field_Mutation_inviteProjectMembers_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
	args := map[string]interface{}{}
 | 
			
		||||
	var arg0 InviteProjectMember
 | 
			
		||||
	var arg0 InviteProjectMembers
 | 
			
		||||
	if tmp, ok := rawArgs["input"]; ok {
 | 
			
		||||
		arg0, err = ec.unmarshalNInviteProjectMember2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐInviteProjectMember(ctx, tmp)
 | 
			
		||||
		arg0, err = ec.unmarshalNInviteProjectMembers2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐInviteProjectMembers(ctx, tmp)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
@@ -5179,7 +5191,7 @@ 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) {
 | 
			
		||||
func (ec *executionContext) _InviteProjectMembersPayload_ok(ctx context.Context, field graphql.CollectedField, obj *InviteProjectMembersPayload) (ret graphql.Marshaler) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			ec.Error(ctx, ec.Recover(ctx, r))
 | 
			
		||||
@@ -5187,7 +5199,7 @@ func (ec *executionContext) _InviteProjectMemberPayload_ok(ctx context.Context,
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	fc := &graphql.FieldContext{
 | 
			
		||||
		Object:   "InviteProjectMemberPayload",
 | 
			
		||||
		Object:   "InviteProjectMembersPayload",
 | 
			
		||||
		Field:    field,
 | 
			
		||||
		Args:     nil,
 | 
			
		||||
		IsMethod: false,
 | 
			
		||||
@@ -5213,7 +5225,7 @@ func (ec *executionContext) _InviteProjectMemberPayload_ok(ctx context.Context,
 | 
			
		||||
	return ec.marshalNBoolean2bool(ctx, field.Selections, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _InviteProjectMemberPayload_member(ctx context.Context, field graphql.CollectedField, obj *InviteProjectMemberPayload) (ret graphql.Marshaler) {
 | 
			
		||||
func (ec *executionContext) _InviteProjectMembersPayload_projectID(ctx context.Context, field graphql.CollectedField, obj *InviteProjectMembersPayload) (ret graphql.Marshaler) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			ec.Error(ctx, ec.Recover(ctx, r))
 | 
			
		||||
@@ -5221,7 +5233,7 @@ func (ec *executionContext) _InviteProjectMemberPayload_member(ctx context.Conte
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	fc := &graphql.FieldContext{
 | 
			
		||||
		Object:   "InviteProjectMemberPayload",
 | 
			
		||||
		Object:   "InviteProjectMembersPayload",
 | 
			
		||||
		Field:    field,
 | 
			
		||||
		Args:     nil,
 | 
			
		||||
		IsMethod: false,
 | 
			
		||||
@@ -5230,7 +5242,7 @@ func (ec *executionContext) _InviteProjectMemberPayload_member(ctx context.Conte
 | 
			
		||||
	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
 | 
			
		||||
		return obj.ProjectID, nil
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ec.Error(ctx, err)
 | 
			
		||||
@@ -5242,9 +5254,43 @@ func (ec *executionContext) _InviteProjectMemberPayload_member(ctx context.Conte
 | 
			
		||||
		}
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	res := resTmp.(*Member)
 | 
			
		||||
	res := resTmp.(uuid.UUID)
 | 
			
		||||
	fc.Result = res
 | 
			
		||||
	return ec.marshalNMember2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐMember(ctx, field.Selections, res)
 | 
			
		||||
	return ec.marshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, field.Selections, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _InviteProjectMembersPayload_members(ctx context.Context, field graphql.CollectedField, obj *InviteProjectMembersPayload) (ret graphql.Marshaler) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			ec.Error(ctx, ec.Recover(ctx, r))
 | 
			
		||||
			ret = graphql.Null
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	fc := &graphql.FieldContext{
 | 
			
		||||
		Object:   "InviteProjectMembersPayload",
 | 
			
		||||
		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.Members, 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) {
 | 
			
		||||
@@ -6545,7 +6591,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_inviteProjectMember(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
 | 
			
		||||
func (ec *executionContext) _Mutation_inviteProjectMembers(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			ec.Error(ctx, ec.Recover(ctx, r))
 | 
			
		||||
@@ -6561,7 +6607,7 @@ func (ec *executionContext) _Mutation_inviteProjectMember(ctx context.Context, f
 | 
			
		||||
 | 
			
		||||
	ctx = graphql.WithFieldContext(ctx, fc)
 | 
			
		||||
	rawArgs := field.ArgumentMap(ec.Variables)
 | 
			
		||||
	args, err := ec.field_Mutation_inviteProjectMember_args(ctx, rawArgs)
 | 
			
		||||
	args, err := ec.field_Mutation_inviteProjectMembers_args(ctx, rawArgs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ec.Error(ctx, err)
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
@@ -6570,7 +6616,7 @@ func (ec *executionContext) _Mutation_inviteProjectMember(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().InviteProjectMember(rctx, args["input"].(InviteProjectMember))
 | 
			
		||||
			return ec.resolvers.Mutation().InviteProjectMembers(rctx, args["input"].(InviteProjectMembers))
 | 
			
		||||
		}
 | 
			
		||||
		directive1 := func(ctx context.Context) (interface{}, error) {
 | 
			
		||||
			roles, err := ec.unmarshalNRoleLevel2ᚕgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐRoleLevelᚄ(ctx, []interface{}{"ADMIN"})
 | 
			
		||||
@@ -6598,10 +6644,10 @@ func (ec *executionContext) _Mutation_inviteProjectMember(ctx context.Context, f
 | 
			
		||||
		if tmp == nil {
 | 
			
		||||
			return nil, nil
 | 
			
		||||
		}
 | 
			
		||||
		if data, ok := tmp.(*InviteProjectMemberPayload); ok {
 | 
			
		||||
		if data, ok := tmp.(*InviteProjectMembersPayload); ok {
 | 
			
		||||
			return data, nil
 | 
			
		||||
		}
 | 
			
		||||
		return nil, fmt.Errorf(`unexpected type %T from directive, should be *github.com/jordanknott/taskcafe/internal/graph.InviteProjectMemberPayload`, tmp)
 | 
			
		||||
		return nil, fmt.Errorf(`unexpected type %T from directive, should be *github.com/jordanknott/taskcafe/internal/graph.InviteProjectMembersPayload`, tmp)
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ec.Error(ctx, err)
 | 
			
		||||
@@ -6613,9 +6659,9 @@ func (ec *executionContext) _Mutation_inviteProjectMember(ctx context.Context, f
 | 
			
		||||
		}
 | 
			
		||||
		return graphql.Null
 | 
			
		||||
	}
 | 
			
		||||
	res := resTmp.(*InviteProjectMemberPayload)
 | 
			
		||||
	res := resTmp.(*InviteProjectMembersPayload)
 | 
			
		||||
	fc.Result = res
 | 
			
		||||
	return ec.marshalNInviteProjectMemberPayload2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐInviteProjectMemberPayload(ctx, field.Selections, res)
 | 
			
		||||
	return ec.marshalNInviteProjectMembersPayload2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐInviteProjectMembersPayload(ctx, field.Selections, res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _Mutation_deleteProjectMember(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
 | 
			
		||||
@@ -15403,8 +15449,8 @@ 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
 | 
			
		||||
func (ec *executionContext) unmarshalInputInviteProjectMembers(ctx context.Context, obj interface{}) (InviteProjectMembers, error) {
 | 
			
		||||
	var it InviteProjectMembers
 | 
			
		||||
	var asMap = obj.(map[string]interface{})
 | 
			
		||||
 | 
			
		||||
	for k, v := range asMap {
 | 
			
		||||
@@ -15415,15 +15461,9 @@ func (ec *executionContext) unmarshalInputInviteProjectMember(ctx context.Contex
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return it, err
 | 
			
		||||
			}
 | 
			
		||||
		case "userID":
 | 
			
		||||
		case "members":
 | 
			
		||||
			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)
 | 
			
		||||
			it.Members, err = ec.unmarshalNMemberInvite2ᚕgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐMemberInviteᚄ(ctx, v)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return it, err
 | 
			
		||||
			}
 | 
			
		||||
@@ -15451,6 +15491,30 @@ func (ec *executionContext) unmarshalInputLogoutUser(ctx context.Context, obj in
 | 
			
		||||
	return it, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) unmarshalInputMemberInvite(ctx context.Context, obj interface{}) (MemberInvite, error) {
 | 
			
		||||
	var it MemberInvite
 | 
			
		||||
	var asMap = obj.(map[string]interface{})
 | 
			
		||||
 | 
			
		||||
	for k, v := range asMap {
 | 
			
		||||
		switch k {
 | 
			
		||||
		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) unmarshalInputMemberSearchFilter(ctx context.Context, obj interface{}) (MemberSearchFilter, error) {
 | 
			
		||||
	var it MemberSearchFilter
 | 
			
		||||
	var asMap = obj.(map[string]interface{})
 | 
			
		||||
@@ -16797,24 +16861,29 @@ func (ec *executionContext) _DuplicateTaskGroupPayload(ctx context.Context, sel
 | 
			
		||||
	return out
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var inviteProjectMemberPayloadImplementors = []string{"InviteProjectMemberPayload"}
 | 
			
		||||
var inviteProjectMembersPayloadImplementors = []string{"InviteProjectMembersPayload"}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) _InviteProjectMemberPayload(ctx context.Context, sel ast.SelectionSet, obj *InviteProjectMemberPayload) graphql.Marshaler {
 | 
			
		||||
	fields := graphql.CollectFields(ec.OperationContext, sel, inviteProjectMemberPayloadImplementors)
 | 
			
		||||
func (ec *executionContext) _InviteProjectMembersPayload(ctx context.Context, sel ast.SelectionSet, obj *InviteProjectMembersPayload) graphql.Marshaler {
 | 
			
		||||
	fields := graphql.CollectFields(ec.OperationContext, sel, inviteProjectMembersPayloadImplementors)
 | 
			
		||||
 | 
			
		||||
	out := graphql.NewFieldSet(fields)
 | 
			
		||||
	var invalids uint32
 | 
			
		||||
	for i, field := range fields {
 | 
			
		||||
		switch field.Name {
 | 
			
		||||
		case "__typename":
 | 
			
		||||
			out.Values[i] = graphql.MarshalString("InviteProjectMemberPayload")
 | 
			
		||||
			out.Values[i] = graphql.MarshalString("InviteProjectMembersPayload")
 | 
			
		||||
		case "ok":
 | 
			
		||||
			out.Values[i] = ec._InviteProjectMemberPayload_ok(ctx, field, obj)
 | 
			
		||||
			out.Values[i] = ec._InviteProjectMembersPayload_ok(ctx, field, obj)
 | 
			
		||||
			if out.Values[i] == graphql.Null {
 | 
			
		||||
				invalids++
 | 
			
		||||
			}
 | 
			
		||||
		case "member":
 | 
			
		||||
			out.Values[i] = ec._InviteProjectMemberPayload_member(ctx, field, obj)
 | 
			
		||||
		case "projectID":
 | 
			
		||||
			out.Values[i] = ec._InviteProjectMembersPayload_projectID(ctx, field, obj)
 | 
			
		||||
			if out.Values[i] == graphql.Null {
 | 
			
		||||
				invalids++
 | 
			
		||||
			}
 | 
			
		||||
		case "members":
 | 
			
		||||
			out.Values[i] = ec._InviteProjectMembersPayload_members(ctx, field, obj)
 | 
			
		||||
			if out.Values[i] == graphql.Null {
 | 
			
		||||
				invalids++
 | 
			
		||||
			}
 | 
			
		||||
@@ -17108,8 +17177,8 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
 | 
			
		||||
			if out.Values[i] == graphql.Null {
 | 
			
		||||
				invalids++
 | 
			
		||||
			}
 | 
			
		||||
		case "inviteProjectMember":
 | 
			
		||||
			out.Values[i] = ec._Mutation_inviteProjectMember(ctx, field)
 | 
			
		||||
		case "inviteProjectMembers":
 | 
			
		||||
			out.Values[i] = ec._Mutation_inviteProjectMembers(ctx, field)
 | 
			
		||||
			if out.Values[i] == graphql.Null {
 | 
			
		||||
				invalids++
 | 
			
		||||
			}
 | 
			
		||||
@@ -19692,22 +19761,22 @@ 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) unmarshalNInviteProjectMembers2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐInviteProjectMembers(ctx context.Context, v interface{}) (InviteProjectMembers, error) {
 | 
			
		||||
	return ec.unmarshalInputInviteProjectMembers(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) marshalNInviteProjectMembersPayload2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐInviteProjectMembersPayload(ctx context.Context, sel ast.SelectionSet, v InviteProjectMembersPayload) graphql.Marshaler {
 | 
			
		||||
	return ec._InviteProjectMembersPayload(ctx, sel, &v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) marshalNInviteProjectMemberPayload2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐInviteProjectMemberPayload(ctx context.Context, sel ast.SelectionSet, v *InviteProjectMemberPayload) graphql.Marshaler {
 | 
			
		||||
func (ec *executionContext) marshalNInviteProjectMembersPayload2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐInviteProjectMembersPayload(ctx context.Context, sel ast.SelectionSet, v *InviteProjectMembersPayload) 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)
 | 
			
		||||
	return ec._InviteProjectMembersPayload(ctx, sel, v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) marshalNLabelColor2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋdbᚐLabelColor(ctx context.Context, sel ast.SelectionSet, v db.LabelColor) graphql.Marshaler {
 | 
			
		||||
@@ -19830,6 +19899,30 @@ func (ec *executionContext) marshalNMember2ᚖgithubᚗcomᚋjordanknottᚋtaskc
 | 
			
		||||
	return ec._Member(ctx, sel, v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) unmarshalNMemberInvite2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐMemberInvite(ctx context.Context, v interface{}) (MemberInvite, error) {
 | 
			
		||||
	return ec.unmarshalInputMemberInvite(ctx, v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) unmarshalNMemberInvite2ᚕgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐMemberInviteᚄ(ctx context.Context, v interface{}) ([]MemberInvite, error) {
 | 
			
		||||
	var vSlice []interface{}
 | 
			
		||||
	if v != nil {
 | 
			
		||||
		if tmp1, ok := v.([]interface{}); ok {
 | 
			
		||||
			vSlice = tmp1
 | 
			
		||||
		} else {
 | 
			
		||||
			vSlice = []interface{}{v}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	var err error
 | 
			
		||||
	res := make([]MemberInvite, len(vSlice))
 | 
			
		||||
	for i := range vSlice {
 | 
			
		||||
		res[i], err = ec.unmarshalNMemberInvite2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐMemberInvite(ctx, vSlice[i])
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return res, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ec *executionContext) marshalNMemberList2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐMemberList(ctx context.Context, sel ast.SelectionSet, v MemberList) graphql.Marshaler {
 | 
			
		||||
	return ec._MemberList(ctx, sel, &v)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ import (
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
	"github.com/jordanknott/taskcafe/internal/auth"
 | 
			
		||||
	"github.com/jordanknott/taskcafe/internal/db"
 | 
			
		||||
	"github.com/jordanknott/taskcafe/internal/logger"
 | 
			
		||||
	"github.com/jordanknott/taskcafe/internal/utils"
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/vektah/gqlparser/v2/gqlerror"
 | 
			
		||||
@@ -63,10 +64,10 @@ func NewHandler(repo db.Repository) http.Handler {
 | 
			
		||||
		default:
 | 
			
		||||
			fieldName = "ProjectID"
 | 
			
		||||
		}
 | 
			
		||||
		log.WithFields(log.Fields{"typeArg": typeArg, "fieldName": fieldName}).Info("getting field by name")
 | 
			
		||||
		logger.New(ctx).WithFields(log.Fields{"typeArg": typeArg, "fieldName": fieldName}).Info("getting field by name")
 | 
			
		||||
		subjectField := val.FieldByName(fieldName)
 | 
			
		||||
		if !subjectField.IsValid() {
 | 
			
		||||
			log.Error("subject field name does not exist on input type")
 | 
			
		||||
			logger.New(ctx).Error("subject field name does not exist on input type")
 | 
			
		||||
			return nil, errors.New("subject field name does not exist on input type")
 | 
			
		||||
		}
 | 
			
		||||
		if fieldName == "TeamID" && subjectField.IsNil() {
 | 
			
		||||
@@ -76,13 +77,13 @@ func NewHandler(repo db.Repository) http.Handler {
 | 
			
		||||
		}
 | 
			
		||||
		subjectID, ok = subjectField.Interface().(uuid.UUID)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			log.Error("error while casting subject UUID")
 | 
			
		||||
			logger.New(ctx).Error("error while casting subject UUID")
 | 
			
		||||
			return nil, errors.New("error while casting subject uuid")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var err error
 | 
			
		||||
		if level == ActionLevelProject {
 | 
			
		||||
			log.WithFields(log.Fields{"subjectID": subjectID}).Info("fetching subject ID by typeArg")
 | 
			
		||||
			logger.New(ctx).WithFields(log.Fields{"subjectID": subjectID}).Info("fetching subject ID by typeArg")
 | 
			
		||||
			if typeArg == ObjectTypeTask {
 | 
			
		||||
				subjectID, err = repo.GetProjectIDForTask(ctx, subjectID)
 | 
			
		||||
			}
 | 
			
		||||
@@ -96,7 +97,7 @@ func NewHandler(repo db.Repository) http.Handler {
 | 
			
		||||
				subjectID, err = repo.GetProjectIDForTaskChecklistItem(ctx, subjectID)
 | 
			
		||||
			}
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.WithError(err).Error("error while getting subject ID")
 | 
			
		||||
				logger.New(ctx).WithError(err).Error("error while getting subject ID")
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			projectRoles, err := GetProjectRoles(ctx, repo, subjectID)
 | 
			
		||||
@@ -109,13 +110,13 @@ func NewHandler(repo db.Repository) http.Handler {
 | 
			
		||||
						},
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				log.WithError(err).Error("error while getting project roles")
 | 
			
		||||
				logger.New(ctx).WithError(err).Error("error while getting project roles")
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			for _, validRole := range roles {
 | 
			
		||||
				log.WithFields(log.Fields{"validRole": validRole}).Info("checking role")
 | 
			
		||||
				logger.New(ctx).WithFields(log.Fields{"validRole": validRole}).Info("checking role")
 | 
			
		||||
				if CompareRoleLevel(projectRoles.TeamRole, validRole) || CompareRoleLevel(projectRoles.ProjectRole, validRole) {
 | 
			
		||||
					log.WithFields(log.Fields{"teamRole": projectRoles.TeamRole, "projectRole": projectRoles.ProjectRole}).Info("is team or project role")
 | 
			
		||||
					logger.New(ctx).WithFields(log.Fields{"teamRole": projectRoles.TeamRole, "projectRole": projectRoles.ProjectRole}).Info("is team or project role")
 | 
			
		||||
					return next(ctx)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
@@ -132,7 +133,7 @@ func NewHandler(repo db.Repository) http.Handler {
 | 
			
		||||
			}
 | 
			
		||||
			role, err := repo.GetTeamRoleForUserID(ctx, db.GetTeamRoleForUserIDParams{UserID: userID, TeamID: subjectID})
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.WithError(err).Error("error while getting team roles for user ID")
 | 
			
		||||
				logger.New(ctx).WithError(err).Error("error while getting team roles for user ID")
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			for _, validRole := range roles {
 | 
			
		||||
 
 | 
			
		||||
@@ -177,15 +177,15 @@ 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 InviteProjectMembers struct {
 | 
			
		||||
	ProjectID uuid.UUID      `json:"projectID"`
 | 
			
		||||
	Members   []MemberInvite `json:"members"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type InviteProjectMemberPayload struct {
 | 
			
		||||
	Ok     bool    `json:"ok"`
 | 
			
		||||
	Member *Member `json:"member"`
 | 
			
		||||
type InviteProjectMembersPayload struct {
 | 
			
		||||
	Ok        bool      `json:"ok"`
 | 
			
		||||
	ProjectID uuid.UUID `json:"projectID"`
 | 
			
		||||
	Members   []Member  `json:"members"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type LogoutUser struct {
 | 
			
		||||
@@ -208,6 +208,11 @@ type Member struct {
 | 
			
		||||
	Member      *MemberList  `json:"member"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MemberInvite struct {
 | 
			
		||||
	UserID *uuid.UUID `json:"userID"`
 | 
			
		||||
	Email  *string    `json:"email"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MemberList struct {
 | 
			
		||||
	Teams    []db.Team    `json:"teams"`
 | 
			
		||||
	Projects []db.Project `json:"projects"`
 | 
			
		||||
 
 | 
			
		||||
@@ -338,24 +338,28 @@ input UpdateProjectLabelColor {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extend type Mutation {
 | 
			
		||||
  # TODO: rename to inviteProjectMember
 | 
			
		||||
  inviteProjectMember(input: InviteProjectMember!):
 | 
			
		||||
    InviteProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  inviteProjectMembers(input: InviteProjectMembers!):
 | 
			
		||||
    InviteProjectMembersPayload! @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 InviteProjectMember {
 | 
			
		||||
  projectID: UUID!
 | 
			
		||||
input MemberInvite {
 | 
			
		||||
  userID: UUID
 | 
			
		||||
  email: String
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type InviteProjectMemberPayload {
 | 
			
		||||
input InviteProjectMembers {
 | 
			
		||||
  projectID: UUID!
 | 
			
		||||
  members: [MemberInvite!]!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type InviteProjectMembersPayload {
 | 
			
		||||
  ok: Boolean!
 | 
			
		||||
  member: Member!
 | 
			
		||||
  projectID: UUID!
 | 
			
		||||
  members: [Member!]!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input DeleteProjectMember {
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ import (
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
	"github.com/jordanknott/taskcafe/internal/auth"
 | 
			
		||||
	"github.com/jordanknott/taskcafe/internal/db"
 | 
			
		||||
	"github.com/jordanknott/taskcafe/internal/logger"
 | 
			
		||||
	"github.com/lithammer/fuzzysearch/fuzzy"
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/vektah/gqlparser/v2/gqlerror"
 | 
			
		||||
@@ -29,7 +30,7 @@ func (r *mutationResolver) CreateProject(ctx context.Context, input NewProject)
 | 
			
		||||
		return &db.Project{}, errors.New("user id is missing")
 | 
			
		||||
	}
 | 
			
		||||
	createdAt := time.Now().UTC()
 | 
			
		||||
	log.WithFields(log.Fields{"name": input.Name, "teamID": input.TeamID}).Info("creating new project")
 | 
			
		||||
	logger.New(ctx).WithFields(log.Fields{"name": input.Name, "teamID": input.TeamID}).Info("creating new project")
 | 
			
		||||
	var project db.Project
 | 
			
		||||
	var err error
 | 
			
		||||
	if input.TeamID == nil {
 | 
			
		||||
@@ -38,10 +39,10 @@ func (r *mutationResolver) CreateProject(ctx context.Context, input NewProject)
 | 
			
		||||
			Name:      input.Name,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.WithError(err).Error("error while creating project")
 | 
			
		||||
			logger.New(ctx).WithError(err).Error("error while creating project")
 | 
			
		||||
			return &db.Project{}, err
 | 
			
		||||
		}
 | 
			
		||||
		log.WithFields(log.Fields{"userID": userID, "projectID": project.ProjectID}).Info("creating personal project link")
 | 
			
		||||
		logger.New(ctx).WithField("projectID", project.ProjectID).Info("creating personal project link")
 | 
			
		||||
	} else {
 | 
			
		||||
		project, err = r.Repository.CreateTeamProject(ctx, db.CreateTeamProjectParams{
 | 
			
		||||
			CreatedAt: createdAt,
 | 
			
		||||
@@ -49,13 +50,13 @@ func (r *mutationResolver) CreateProject(ctx context.Context, input NewProject)
 | 
			
		||||
			TeamID:    *input.TeamID,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.WithError(err).Error("error while creating project")
 | 
			
		||||
			logger.New(ctx).WithError(err).Error("error while creating project")
 | 
			
		||||
			return &db.Project{}, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	_, err = r.Repository.CreateProjectMember(ctx, db.CreateProjectMemberParams{ProjectID: project.ProjectID, UserID: userID, AddedAt: createdAt, RoleCode: "admin"})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.WithError(err).Error("error while creating initial project member")
 | 
			
		||||
		logger.New(ctx).WithError(err).Error("error while creating initial project member")
 | 
			
		||||
		return &db.Project{}, err
 | 
			
		||||
	}
 | 
			
		||||
	return &project, nil
 | 
			
		||||
@@ -124,53 +125,55 @@ func (r *mutationResolver) UpdateProjectLabelColor(ctx context.Context, input Up
 | 
			
		||||
	return &label, 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",
 | 
			
		||||
			},
 | 
			
		||||
func (r *mutationResolver) InviteProjectMembers(ctx context.Context, input InviteProjectMembers) (*InviteProjectMembersPayload, error) {
 | 
			
		||||
	members := []Member{}
 | 
			
		||||
	for _, invitedMember := range input.Members {
 | 
			
		||||
		if invitedMember.Email != nil && invitedMember.UserID != nil {
 | 
			
		||||
			return &InviteProjectMembersPayload{Ok: false}, &gqlerror.Error{
 | 
			
		||||
				Message: "Both email and userID can not be used to invite a project member",
 | 
			
		||||
				Extensions: map[string]interface{}{
 | 
			
		||||
					"code": "403",
 | 
			
		||||
				},
 | 
			
		||||
			}
 | 
			
		||||
		} else if invitedMember.Email == nil && invitedMember.UserID == nil {
 | 
			
		||||
			return &InviteProjectMembersPayload{Ok: false}, &gqlerror.Error{
 | 
			
		||||
				Message: "Either email or userID must be set to invite a project member",
 | 
			
		||||
				Extensions: map[string]interface{}{
 | 
			
		||||
					"code": "403",
 | 
			
		||||
				},
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} 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 invitedMember.UserID != nil {
 | 
			
		||||
			addedAt := time.Now().UTC()
 | 
			
		||||
			_, err := r.Repository.CreateProjectMember(ctx, db.CreateProjectMemberParams{ProjectID: input.ProjectID, UserID: *invitedMember.UserID, AddedAt: addedAt, RoleCode: "member"})
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return &InviteProjectMembersPayload{Ok: false}, err
 | 
			
		||||
			}
 | 
			
		||||
			user, err := r.Repository.GetUserAccountByID(ctx, *invitedMember.UserID)
 | 
			
		||||
			if err != nil && err != sql.ErrNoRows {
 | 
			
		||||
				return &InviteProjectMembersPayload{Ok: false}, err
 | 
			
		||||
 | 
			
		||||
			}
 | 
			
		||||
			var url *string
 | 
			
		||||
			if user.ProfileAvatarUrl.Valid {
 | 
			
		||||
				url = &user.ProfileAvatarUrl.String
 | 
			
		||||
			}
 | 
			
		||||
			profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor}
 | 
			
		||||
 | 
			
		||||
			role, err := r.Repository.GetRoleForProjectMemberByUserID(ctx, db.GetRoleForProjectMemberByUserIDParams{UserID: *invitedMember.UserID, ProjectID: input.ProjectID})
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return &InviteProjectMembersPayload{Ok: false}, err
 | 
			
		||||
			}
 | 
			
		||||
			members = append(members, Member{
 | 
			
		||||
				ID:          *invitedMember.UserID,
 | 
			
		||||
				FullName:    user.FullName,
 | 
			
		||||
				Username:    user.Username,
 | 
			
		||||
				ProfileIcon: profileIcon,
 | 
			
		||||
				Role:        &db.Role{Code: role.Code, Name: role.Name},
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	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 &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 {
 | 
			
		||||
			url = &user.ProfileAvatarUrl.String
 | 
			
		||||
		}
 | 
			
		||||
		profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor}
 | 
			
		||||
 | 
			
		||||
		role, err := r.Repository.GetRoleForProjectMemberByUserID(ctx, db.GetRoleForProjectMemberByUserIDParams{UserID: *input.UserID, ProjectID: input.ProjectID})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return &InviteProjectMemberPayload{Ok: false}, err
 | 
			
		||||
		}
 | 
			
		||||
		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")
 | 
			
		||||
	return &InviteProjectMembersPayload{Ok: false, ProjectID: input.ProjectID, Members: members}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *mutationResolver) DeleteProjectMember(ctx context.Context, input DeleteProjectMember) (*DeleteProjectMemberPayload, error) {
 | 
			
		||||
@@ -202,18 +205,18 @@ func (r *mutationResolver) DeleteProjectMember(ctx context.Context, input Delete
 | 
			
		||||
func (r *mutationResolver) UpdateProjectMemberRole(ctx context.Context, input UpdateProjectMemberRole) (*UpdateProjectMemberRolePayload, error) {
 | 
			
		||||
	user, err := r.Repository.GetUserAccountByID(ctx, input.UserID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.WithError(err).Error("get user account")
 | 
			
		||||
		logger.New(ctx).WithError(err).Error("get user account")
 | 
			
		||||
		return &UpdateProjectMemberRolePayload{Ok: false}, err
 | 
			
		||||
	}
 | 
			
		||||
	_, err = r.Repository.UpdateProjectMemberRole(ctx, db.UpdateProjectMemberRoleParams{ProjectID: input.ProjectID,
 | 
			
		||||
		UserID: input.UserID, RoleCode: input.RoleCode.String()})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.WithError(err).Error("update project member role")
 | 
			
		||||
		logger.New(ctx).WithError(err).Error("update project member role")
 | 
			
		||||
		return &UpdateProjectMemberRolePayload{Ok: false}, err
 | 
			
		||||
	}
 | 
			
		||||
	role, err := r.Repository.GetRoleForProjectMemberByUserID(ctx, db.GetRoleForProjectMemberByUserIDParams{UserID: user.UserID, ProjectID: input.ProjectID})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.WithError(err).Error("get role for project member")
 | 
			
		||||
		logger.New(ctx).WithError(err).Error("get role for project member")
 | 
			
		||||
		return &UpdateProjectMemberRolePayload{Ok: false}, err
 | 
			
		||||
	}
 | 
			
		||||
	var url *string
 | 
			
		||||
@@ -232,17 +235,17 @@ func (r *mutationResolver) UpdateProjectMemberRole(ctx context.Context, input Up
 | 
			
		||||
 | 
			
		||||
func (r *mutationResolver) CreateTask(ctx context.Context, input NewTask) (*db.Task, error) {
 | 
			
		||||
	createdAt := time.Now().UTC()
 | 
			
		||||
	log.WithFields(log.Fields{"positon": input.Position, "taskGroupID": input.TaskGroupID}).Info("creating task")
 | 
			
		||||
	logger.New(ctx).WithFields(log.Fields{"positon": input.Position, "taskGroupID": input.TaskGroupID}).Info("creating task")
 | 
			
		||||
	task, err := r.Repository.CreateTask(ctx, db.CreateTaskParams{input.TaskGroupID, createdAt, input.Name, input.Position})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.WithError(err).Error("issue while creating task")
 | 
			
		||||
		logger.New(ctx).WithError(err).Error("issue while creating task")
 | 
			
		||||
		return &db.Task{}, err
 | 
			
		||||
	}
 | 
			
		||||
	return &task, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *mutationResolver) DeleteTask(ctx context.Context, input DeleteTaskInput) (*DeleteTaskPayload, error) {
 | 
			
		||||
	log.WithFields(log.Fields{
 | 
			
		||||
	logger.New(ctx).WithFields(log.Fields{
 | 
			
		||||
		"taskID": input.TaskID,
 | 
			
		||||
	}).Info("deleting task")
 | 
			
		||||
	err := r.Repository.DeleteTaskByID(ctx, input.TaskID)
 | 
			
		||||
@@ -299,8 +302,8 @@ func (r *mutationResolver) UpdateTaskDueDate(ctx context.Context, input UpdateTa
 | 
			
		||||
func (r *mutationResolver) AssignTask(ctx context.Context, input *AssignTaskInput) (*db.Task, error) {
 | 
			
		||||
	assignedDate := time.Now().UTC()
 | 
			
		||||
	assignedTask, err := r.Repository.CreateTaskAssigned(ctx, db.CreateTaskAssignedParams{input.TaskID, input.UserID, assignedDate})
 | 
			
		||||
	log.WithFields(log.Fields{
 | 
			
		||||
		"userID":         assignedTask.UserID,
 | 
			
		||||
	logger.New(ctx).WithFields(log.Fields{
 | 
			
		||||
		"assignedUserID": assignedTask.UserID,
 | 
			
		||||
		"taskID":         assignedTask.TaskID,
 | 
			
		||||
		"assignedTaskID": assignedTask.TaskAssignedID,
 | 
			
		||||
	}).Info("assigned task")
 | 
			
		||||
@@ -610,7 +613,7 @@ func (r *mutationResolver) ToggleTaskLabel(ctx context.Context, input ToggleTask
 | 
			
		||||
	createdAt := time.Now().UTC()
 | 
			
		||||
 | 
			
		||||
	if err == sql.ErrNoRows {
 | 
			
		||||
		log.WithFields(log.Fields{"err": err}).Warning("no rows")
 | 
			
		||||
		logger.New(ctx).WithFields(log.Fields{"err": err}).Warning("no rows")
 | 
			
		||||
		_, err := r.Repository.CreateTaskLabelForTask(ctx, db.CreateTaskLabelForTaskParams{
 | 
			
		||||
			TaskID:         input.TaskID,
 | 
			
		||||
			ProjectLabelID: input.ProjectLabelID,
 | 
			
		||||
@@ -643,17 +646,17 @@ func (r *mutationResolver) ToggleTaskLabel(ctx context.Context, input ToggleTask
 | 
			
		||||
func (r *mutationResolver) DeleteTeam(ctx context.Context, input DeleteTeam) (*DeleteTeamPayload, error) {
 | 
			
		||||
	team, err := r.Repository.GetTeamByID(ctx, input.TeamID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error(err)
 | 
			
		||||
		logger.New(ctx).Error(err)
 | 
			
		||||
		return &DeleteTeamPayload{Ok: false}, err
 | 
			
		||||
	}
 | 
			
		||||
	projects, err := r.Repository.GetAllProjectsForTeam(ctx, input.TeamID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error(err)
 | 
			
		||||
		logger.New(ctx).Error(err)
 | 
			
		||||
		return &DeleteTeamPayload{Ok: false}, err
 | 
			
		||||
	}
 | 
			
		||||
	err = r.Repository.DeleteTeamByID(ctx, input.TeamID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error(err)
 | 
			
		||||
		logger.New(ctx).Error(err)
 | 
			
		||||
		return &DeleteTeamPayload{Ok: false}, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -708,18 +711,18 @@ func (r *mutationResolver) CreateTeamMember(ctx context.Context, input CreateTea
 | 
			
		||||
func (r *mutationResolver) UpdateTeamMemberRole(ctx context.Context, input UpdateTeamMemberRole) (*UpdateTeamMemberRolePayload, error) {
 | 
			
		||||
	user, err := r.Repository.GetUserAccountByID(ctx, input.UserID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.WithError(err).Error("get user account")
 | 
			
		||||
		logger.New(ctx).WithError(err).Error("get user account")
 | 
			
		||||
		return &UpdateTeamMemberRolePayload{Ok: false}, err
 | 
			
		||||
	}
 | 
			
		||||
	_, err = r.Repository.UpdateTeamMemberRole(ctx, db.UpdateTeamMemberRoleParams{TeamID: input.TeamID,
 | 
			
		||||
		UserID: input.UserID, RoleCode: input.RoleCode.String()})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.WithError(err).Error("update project member role")
 | 
			
		||||
		logger.New(ctx).WithError(err).Error("update project member role")
 | 
			
		||||
		return &UpdateTeamMemberRolePayload{Ok: false}, err
 | 
			
		||||
	}
 | 
			
		||||
	role, err := r.Repository.GetRoleForTeamMember(ctx, db.GetRoleForTeamMemberParams{UserID: user.UserID, TeamID: input.TeamID})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.WithError(err).Error("get role for project member")
 | 
			
		||||
		logger.New(ctx).WithError(err).Error("get role for project member")
 | 
			
		||||
		return &UpdateTeamMemberRolePayload{Ok: false}, err
 | 
			
		||||
	}
 | 
			
		||||
	var url *string
 | 
			
		||||
@@ -871,9 +874,9 @@ func (r *notificationResolver) ID(ctx context.Context, obj *db.Notification) (uu
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *notificationResolver) Entity(ctx context.Context, obj *db.Notification) (*NotificationEntity, error) {
 | 
			
		||||
	log.WithFields(log.Fields{"notificationID": obj.NotificationID}).Info("fetching entity for notification")
 | 
			
		||||
	logger.New(ctx).WithFields(log.Fields{"notificationID": obj.NotificationID}).Info("fetching entity for notification")
 | 
			
		||||
	entity, err := r.Repository.GetEntityForNotificationID(ctx, obj.NotificationID)
 | 
			
		||||
	log.WithFields(log.Fields{"entityID": entity.EntityID}).Info("fetched entity")
 | 
			
		||||
	logger.New(ctx).WithFields(log.Fields{"entityID": entity.EntityID}).Info("fetched entity")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return &NotificationEntity{}, err
 | 
			
		||||
	}
 | 
			
		||||
@@ -905,7 +908,7 @@ func (r *notificationResolver) Actor(ctx context.Context, obj *db.Notification)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return &NotificationActor{}, err
 | 
			
		||||
	}
 | 
			
		||||
	log.WithFields(log.Fields{"entityID": entity.ActorID}).Info("fetching actor")
 | 
			
		||||
	logger.New(ctx).WithFields(log.Fields{"entityID": entity.ActorID}).Info("fetching actor")
 | 
			
		||||
	user, err := r.Repository.GetUserAccountByID(ctx, entity.ActorID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return &NotificationActor{}, err
 | 
			
		||||
@@ -935,7 +938,7 @@ func (r *projectResolver) Team(ctx context.Context, obj *db.Project) (*db.Team,
 | 
			
		||||
		if err == sql.ErrNoRows {
 | 
			
		||||
			return nil, nil
 | 
			
		||||
		}
 | 
			
		||||
		log.WithFields(log.Fields{"teamID": obj.TeamID, "projectID": obj.ProjectID}).WithError(err).Error("issue while getting team for project")
 | 
			
		||||
		logger.New(ctx).WithFields(log.Fields{"teamID": obj.TeamID, "projectID": obj.ProjectID}).WithError(err).Error("issue while getting team for project")
 | 
			
		||||
		return &team, err
 | 
			
		||||
	}
 | 
			
		||||
	return &team, nil
 | 
			
		||||
@@ -949,14 +952,14 @@ func (r *projectResolver) Members(ctx context.Context, obj *db.Project) ([]Membe
 | 
			
		||||
	members := []Member{}
 | 
			
		||||
	projectMembers, err := r.Repository.GetProjectMembersForProjectID(ctx, obj.ProjectID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.WithError(err).Error("get project members for project id")
 | 
			
		||||
		logger.New(ctx).WithError(err).Error("get project members for project id")
 | 
			
		||||
		return members, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, projectMember := range projectMembers {
 | 
			
		||||
		user, err := r.Repository.GetUserAccountByID(ctx, projectMember.UserID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.WithError(err).Error("get user account by ID")
 | 
			
		||||
			logger.New(ctx).WithError(err).Error("get user account by ID")
 | 
			
		||||
			return members, err
 | 
			
		||||
		}
 | 
			
		||||
		var url *string
 | 
			
		||||
@@ -965,7 +968,7 @@ func (r *projectResolver) Members(ctx context.Context, obj *db.Project) ([]Membe
 | 
			
		||||
		}
 | 
			
		||||
		role, err := r.Repository.GetRoleForProjectMemberByUserID(ctx, db.GetRoleForProjectMemberByUserIDParams{UserID: user.UserID, ProjectID: obj.ProjectID})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.WithError(err).Error("get role for projet member by user ID")
 | 
			
		||||
			logger.New(ctx).WithError(err).Error("get role for projet member by user ID")
 | 
			
		||||
			return members, err
 | 
			
		||||
		}
 | 
			
		||||
		profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor}
 | 
			
		||||
@@ -1023,11 +1026,7 @@ func (r *queryResolver) FindUser(ctx context.Context, input FindUser) (*db.UserA
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *queryResolver) FindProject(ctx context.Context, input FindProject) (*db.Project, error) {
 | 
			
		||||
	userID, role, ok := GetUser(ctx)
 | 
			
		||||
	log.WithFields(log.Fields{"userID": userID, "role": role}).Info("find project user")
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return &db.Project{}, nil
 | 
			
		||||
	}
 | 
			
		||||
	logger.New(ctx).Info("finding project user")
 | 
			
		||||
	project, err := r.Repository.GetProjectByID(ctx, input.ProjectID)
 | 
			
		||||
	if err == sql.ErrNoRows {
 | 
			
		||||
		return &db.Project{}, &gqlerror.Error{
 | 
			
		||||
@@ -1048,10 +1047,10 @@ func (r *queryResolver) FindTask(ctx context.Context, input FindTask) (*db.Task,
 | 
			
		||||
func (r *queryResolver) Projects(ctx context.Context, input *ProjectsFilter) ([]db.Project, error) {
 | 
			
		||||
	userID, orgRole, ok := GetUser(ctx)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		log.Info("user id was not found from middleware")
 | 
			
		||||
		logger.New(ctx).Info("user id was not found from middleware")
 | 
			
		||||
		return []db.Project{}, nil
 | 
			
		||||
	}
 | 
			
		||||
	log.WithFields(log.Fields{"userID": userID}).Info("fetching projects")
 | 
			
		||||
	logger.New(ctx).Info("fetching projects")
 | 
			
		||||
 | 
			
		||||
	if input != nil {
 | 
			
		||||
		return r.Repository.GetAllProjectsForTeam(ctx, *input.TeamID)
 | 
			
		||||
@@ -1067,37 +1066,36 @@ func (r *queryResolver) Projects(ctx context.Context, input *ProjectsFilter) ([]
 | 
			
		||||
 | 
			
		||||
	projects := make(map[string]db.Project)
 | 
			
		||||
	for _, team := range teams {
 | 
			
		||||
		log.WithFields(log.Fields{"teamID": team.TeamID}).Info("found team")
 | 
			
		||||
		logger.New(ctx).WithField("teamID", team.TeamID).Info("found team")
 | 
			
		||||
		teamProjects, err := r.Repository.GetAllProjectsForTeam(ctx, team.TeamID)
 | 
			
		||||
		if err != sql.ErrNoRows && err != nil {
 | 
			
		||||
			log.Info("issue getting team projects")
 | 
			
		||||
			return []db.Project{}, nil
 | 
			
		||||
		}
 | 
			
		||||
		for _, project := range teamProjects {
 | 
			
		||||
			log.WithFields(log.Fields{"projectID": project.ProjectID.String()}).Info("adding team project")
 | 
			
		||||
			logger.New(ctx).WithField("projectID", project.ProjectID).Info("adding team project")
 | 
			
		||||
			projects[project.ProjectID.String()] = project
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	visibleProjects, err := r.Repository.GetAllVisibleProjectsForUserID(ctx, userID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.WithField("userID", userID).Info("error getting visible projects for user")
 | 
			
		||||
		logger.New(ctx).Info("error getting visible projects for user")
 | 
			
		||||
		return []db.Project{}, nil
 | 
			
		||||
	}
 | 
			
		||||
	for _, project := range visibleProjects {
 | 
			
		||||
		log.WithFields(log.Fields{"projectID": project.ProjectID.String()}).Info("found visible project")
 | 
			
		||||
		logger.New(ctx).WithField("projectID", project.ProjectID).Info("found visible project")
 | 
			
		||||
		if _, ok := projects[project.ProjectID.String()]; !ok {
 | 
			
		||||
			log.WithFields(log.Fields{"projectID": project.ProjectID.String()}).Info("adding visible project")
 | 
			
		||||
			logger.New(ctx).WithField("projectID", project.ProjectID).Info("adding visible project")
 | 
			
		||||
			projects[project.ProjectID.String()] = project
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	log.WithFields(log.Fields{"projectLength": len(projects)}).Info("making projects")
 | 
			
		||||
	logger.New(ctx).WithField("projectLength", len(projects)).Info("making projects")
 | 
			
		||||
	allProjects := make([]db.Project, 0, len(projects))
 | 
			
		||||
	for _, project := range projects {
 | 
			
		||||
		log.WithFields(log.Fields{"projectID": project.ProjectID.String()}).Info("add project to final list")
 | 
			
		||||
		logger.New(ctx).WithField("projectID", project.ProjectID).Info("adding project to final list")
 | 
			
		||||
		allProjects = append(allProjects, project)
 | 
			
		||||
	}
 | 
			
		||||
	log.Info(allProjects)
 | 
			
		||||
	return allProjects, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1112,7 +1110,7 @@ func (r *queryResolver) FindTeam(ctx context.Context, input FindTeam) (*db.Team,
 | 
			
		||||
func (r *queryResolver) Teams(ctx context.Context) ([]db.Team, error) {
 | 
			
		||||
	userID, orgRole, ok := GetUser(ctx)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		log.Error("userID or orgRole does not exist!")
 | 
			
		||||
		logger.New(ctx).Error("userID or org role does not exist")
 | 
			
		||||
		return []db.Team{}, errors.New("internal error")
 | 
			
		||||
	}
 | 
			
		||||
	if orgRole == "admin" {
 | 
			
		||||
@@ -1123,7 +1121,7 @@ func (r *queryResolver) Teams(ctx context.Context) ([]db.Team, error) {
 | 
			
		||||
	teams := make(map[string]db.Team)
 | 
			
		||||
	adminTeams, err := r.Repository.GetTeamsForUserIDWhereAdmin(ctx, userID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.WithError(err).Error("error while getting teams for user ID")
 | 
			
		||||
		logger.New(ctx).WithError(err).Error("error while getting teams for user ID")
 | 
			
		||||
		return []db.Team{}, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -1133,19 +1131,19 @@ func (r *queryResolver) Teams(ctx context.Context) ([]db.Team, error) {
 | 
			
		||||
 | 
			
		||||
	visibleProjects, err := r.Repository.GetAllVisibleProjectsForUserID(ctx, userID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.WithField("userID", userID).WithError(err).Error("error while getting visible projects for user ID")
 | 
			
		||||
		logger.New(ctx).WithError(err).Error("error while getting visible projects for user ID")
 | 
			
		||||
		return []db.Team{}, err
 | 
			
		||||
	}
 | 
			
		||||
	for _, project := range visibleProjects {
 | 
			
		||||
		log.WithFields(log.Fields{"projectID": project.ProjectID.String()}).Info("found visible project")
 | 
			
		||||
		logger.New(ctx).WithField("projectID", project.ProjectID).Info("found visible project")
 | 
			
		||||
		if _, ok := teams[project.ProjectID.String()]; !ok {
 | 
			
		||||
			log.WithFields(log.Fields{"projectID": project.ProjectID.String()}).Info("adding visible project")
 | 
			
		||||
			logger.New(ctx).WithField("projectID", project.ProjectID).Info("adding visible project")
 | 
			
		||||
			team, err := r.Repository.GetTeamByID(ctx, project.TeamID)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				if err == sql.ErrNoRows {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				log.WithField("teamID", project.TeamID).WithError(err).Error("error getting team by id")
 | 
			
		||||
				logger.New(ctx).WithField("teamID", project.TeamID).WithError(err).Error("error getting team by id")
 | 
			
		||||
				return []db.Team{}, err
 | 
			
		||||
			}
 | 
			
		||||
			teams[project.TeamID.String()] = team
 | 
			
		||||
@@ -1173,7 +1171,7 @@ func (r *queryResolver) Me(ctx context.Context) (*MePayload, error) {
 | 
			
		||||
	}
 | 
			
		||||
	user, err := r.Repository.GetUserAccountByID(ctx, userID)
 | 
			
		||||
	if err == sql.ErrNoRows {
 | 
			
		||||
		log.WithFields(log.Fields{"userID": userID}).Warning("can not find user for me query")
 | 
			
		||||
		logger.New(ctx).Warning("can not find user for me query")
 | 
			
		||||
		return &MePayload{}, nil
 | 
			
		||||
	} else if err != nil {
 | 
			
		||||
		return &MePayload{}, err
 | 
			
		||||
@@ -1201,7 +1199,7 @@ func (r *queryResolver) Me(ctx context.Context) (*MePayload, error) {
 | 
			
		||||
 | 
			
		||||
func (r *queryResolver) Notifications(ctx context.Context) ([]db.Notification, error) {
 | 
			
		||||
	userID, ok := GetUserID(ctx)
 | 
			
		||||
	log.WithFields(log.Fields{"userID": userID}).Info("fetching notifications")
 | 
			
		||||
	logger.New(ctx).Info("fetching notifications")
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return []db.Notification{}, errors.New("user id is missing")
 | 
			
		||||
	}
 | 
			
		||||
@@ -1215,7 +1213,7 @@ func (r *queryResolver) Notifications(ctx context.Context) ([]db.Notification, e
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *queryResolver) SearchMembers(ctx context.Context, input MemberSearchFilter) ([]MemberSearchResult, error) {
 | 
			
		||||
	availableMembers, err := r.Repository.GetMemberData(ctx)
 | 
			
		||||
	availableMembers, err := r.Repository.GetMemberData(ctx, *input.ProjectID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []MemberSearchResult{}, err
 | 
			
		||||
	}
 | 
			
		||||
@@ -1233,7 +1231,7 @@ func (r *queryResolver) SearchMembers(ctx context.Context, input MemberSearchFil
 | 
			
		||||
	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")
 | 
			
		||||
			logger.New(ctx).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 {
 | 
			
		||||
@@ -1313,7 +1311,7 @@ func (r *taskResolver) Assigned(ctx context.Context, obj *db.Task) ([]Member, er
 | 
			
		||||
			if err == sql.ErrNoRows {
 | 
			
		||||
				role = db.Role{Code: "owner", Name: "Owner"}
 | 
			
		||||
			} else {
 | 
			
		||||
				log.WithFields(log.Fields{"userID": user.UserID}).WithError(err).Error("get role for project member")
 | 
			
		||||
				logger.New(ctx).WithError(err).Error("get role for project member")
 | 
			
		||||
				return taskMembers, err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@@ -1407,14 +1405,14 @@ func (r *teamResolver) Members(ctx context.Context, obj *db.Team) ([]Member, err
 | 
			
		||||
 | 
			
		||||
	teamMembers, err := r.Repository.GetTeamMembersForTeamID(ctx, obj.TeamID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.WithError(err).Error("get project members for project id")
 | 
			
		||||
		logger.New(ctx).Error("get project members for project id")
 | 
			
		||||
		return members, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, teamMember := range teamMembers {
 | 
			
		||||
		user, err := r.Repository.GetUserAccountByID(ctx, teamMember.UserID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.WithError(err).Error("get user account by ID")
 | 
			
		||||
			logger.New(ctx).WithError(err).Error("get user account by ID")
 | 
			
		||||
			return members, err
 | 
			
		||||
		}
 | 
			
		||||
		var url *string
 | 
			
		||||
@@ -1423,7 +1421,7 @@ func (r *teamResolver) Members(ctx context.Context, obj *db.Team) ([]Member, err
 | 
			
		||||
		}
 | 
			
		||||
		role, err := r.Repository.GetRoleForTeamMember(ctx, db.GetRoleForTeamMemberParams{UserID: user.UserID, TeamID: obj.TeamID})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.WithError(err).Error("get role for projet member by user ID")
 | 
			
		||||
			logger.New(ctx).WithError(err).Error("get role for projet member by user ID")
 | 
			
		||||
			return members, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -1451,8 +1449,7 @@ func (r *userAccountResolver) ID(ctx context.Context, obj *db.UserAccount) (uuid
 | 
			
		||||
func (r *userAccountResolver) Role(ctx context.Context, obj *db.UserAccount) (*db.Role, error) {
 | 
			
		||||
	role, err := r.Repository.GetRoleForUserID(ctx, obj.UserID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Info("beep!")
 | 
			
		||||
		log.WithError(err).Error("get role for user id")
 | 
			
		||||
		logger.New(ctx).WithError(err).Error("get role for user id")
 | 
			
		||||
		return &db.Role{}, err
 | 
			
		||||
	}
 | 
			
		||||
	return &db.Role{Code: role.Code, Name: role.Name}, nil
 | 
			
		||||
 
 | 
			
		||||
@@ -1,22 +1,26 @@
 | 
			
		||||
extend type Mutation {
 | 
			
		||||
  # TODO: rename to inviteProjectMember
 | 
			
		||||
  inviteProjectMember(input: InviteProjectMember!):
 | 
			
		||||
    InviteProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  inviteProjectMembers(input: InviteProjectMembers!):
 | 
			
		||||
    InviteProjectMembersPayload! @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 InviteProjectMember {
 | 
			
		||||
  projectID: UUID!
 | 
			
		||||
input MemberInvite {
 | 
			
		||||
  userID: UUID
 | 
			
		||||
  email: String
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type InviteProjectMemberPayload {
 | 
			
		||||
input InviteProjectMembers {
 | 
			
		||||
  projectID: UUID!
 | 
			
		||||
  members: [MemberInvite!]!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type InviteProjectMembersPayload {
 | 
			
		||||
  ok: Boolean!
 | 
			
		||||
  member: Member!
 | 
			
		||||
  projectID: UUID!
 | 
			
		||||
  members: [Member!]!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input DeleteProjectMember {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,89 +1,21 @@
 | 
			
		||||
package logger
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"github.com/go-chi/chi/middleware"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
	"github.com/jordanknott/taskcafe/internal/utils"
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// NewStructuredLogger creates a new logger for chi router
 | 
			
		||||
func NewStructuredLogger(logger *logrus.Logger) func(next http.Handler) http.Handler {
 | 
			
		||||
	return middleware.RequestLogger(&StructuredLogger{logger})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StructuredLogger is a logger for chi router
 | 
			
		||||
type StructuredLogger struct {
 | 
			
		||||
	Logger *logrus.Logger
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewLogEntry creates a new log entry for the given HTTP request
 | 
			
		||||
func (l *StructuredLogger) NewLogEntry(r *http.Request) middleware.LogEntry {
 | 
			
		||||
	entry := &StructuredLoggerEntry{Logger: logrus.NewEntry(l.Logger)}
 | 
			
		||||
	logFields := logrus.Fields{}
 | 
			
		||||
 | 
			
		||||
	if reqID := middleware.GetReqID(r.Context()); reqID != "" {
 | 
			
		||||
		logFields["req_id"] = reqID
 | 
			
		||||
// New returns a log entry with the reqID and userID fields populated if they exist
 | 
			
		||||
func New(ctx context.Context) *log.Entry {
 | 
			
		||||
	entry := log.NewEntry(log.StandardLogger())
 | 
			
		||||
	if reqID, ok := ctx.Value(utils.ReqIDKey).(uuid.UUID); ok {
 | 
			
		||||
		entry = entry.WithField("reqID", reqID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	scheme := "http"
 | 
			
		||||
	if r.TLS != nil {
 | 
			
		||||
		scheme = "https"
 | 
			
		||||
	if userID, ok := ctx.Value(utils.UserIDKey).(uuid.UUID); ok {
 | 
			
		||||
		entry = entry.WithField("userID", userID)
 | 
			
		||||
	}
 | 
			
		||||
	logFields["http_scheme"] = scheme
 | 
			
		||||
	logFields["http_proto"] = r.Proto
 | 
			
		||||
	logFields["http_method"] = r.Method
 | 
			
		||||
 | 
			
		||||
	logFields["remote_addr"] = r.RemoteAddr
 | 
			
		||||
	logFields["user_agent"] = r.UserAgent()
 | 
			
		||||
 | 
			
		||||
	logFields["uri"] = fmt.Sprintf("%s://%s%s", scheme, r.Host, r.RequestURI)
 | 
			
		||||
 | 
			
		||||
	entry.Logger = entry.Logger.WithFields(logFields)
 | 
			
		||||
 | 
			
		||||
	return entry
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StructuredLoggerEntry is a log entry will all relevant information about a specific http request
 | 
			
		||||
type StructuredLoggerEntry struct {
 | 
			
		||||
	Logger logrus.FieldLogger
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Write logs information about http request response body
 | 
			
		||||
func (l *StructuredLoggerEntry) Write(status, bytes int, elapsed time.Duration) {
 | 
			
		||||
	l.Logger = l.Logger.WithFields(logrus.Fields{
 | 
			
		||||
		"resp_status": status, "resp_bytes_length": bytes,
 | 
			
		||||
		"resp_elapsed_ms": float64(elapsed.Nanoseconds()) / 1000000.0,
 | 
			
		||||
	})
 | 
			
		||||
	l.Logger.Debugln("request complete")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Panic logs if the request panics
 | 
			
		||||
func (l *StructuredLoggerEntry) Panic(v interface{}, stack []byte) {
 | 
			
		||||
	l.Logger = l.Logger.WithFields(logrus.Fields{
 | 
			
		||||
		"stack": string(stack),
 | 
			
		||||
		"panic": fmt.Sprintf("%+v", v),
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetLogEntry helper function for getting log entry for request
 | 
			
		||||
func GetLogEntry(r *http.Request) logrus.FieldLogger {
 | 
			
		||||
	entry := middleware.GetLogEntry(r).(*StructuredLoggerEntry)
 | 
			
		||||
	return entry.Logger
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LogEntrySetField sets a key's value
 | 
			
		||||
func LogEntrySetField(r *http.Request, key string, value interface{}) {
 | 
			
		||||
	if entry, ok := r.Context().Value(middleware.LogEntryCtxKey).(*StructuredLoggerEntry); ok {
 | 
			
		||||
		entry.Logger = entry.Logger.WithField(key, value)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LogEntrySetFields sets the log entry's fields
 | 
			
		||||
func LogEntrySetFields(r *http.Request, fields map[string]interface{}) {
 | 
			
		||||
	if entry, ok := r.Context().Value(middleware.LogEntryCtxKey).(*StructuredLoggerEntry); ok {
 | 
			
		||||
		entry.Logger = entry.Logger.WithFields(fields)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										89
									
								
								internal/logger/route_logger.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								internal/logger/route_logger.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
			
		||||
package logger
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/go-chi/chi/middleware"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// NewStructuredLogger creates a new logger for chi router
 | 
			
		||||
func NewStructuredLogger(logger *logrus.Logger) func(next http.Handler) http.Handler {
 | 
			
		||||
	return middleware.RequestLogger(&StructuredLogger{logger})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StructuredLogger is a logger for chi router
 | 
			
		||||
type StructuredLogger struct {
 | 
			
		||||
	Logger *logrus.Logger
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewLogEntry creates a new log entry for the given HTTP request
 | 
			
		||||
func (l *StructuredLogger) NewLogEntry(r *http.Request) middleware.LogEntry {
 | 
			
		||||
	entry := &StructuredLoggerEntry{Logger: logrus.NewEntry(l.Logger)}
 | 
			
		||||
	logFields := logrus.Fields{}
 | 
			
		||||
 | 
			
		||||
	if reqID := middleware.GetReqID(r.Context()); reqID != "" {
 | 
			
		||||
		logFields["req_id"] = reqID
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	scheme := "http"
 | 
			
		||||
	if r.TLS != nil {
 | 
			
		||||
		scheme = "https"
 | 
			
		||||
	}
 | 
			
		||||
	logFields["http_scheme"] = scheme
 | 
			
		||||
	logFields["http_proto"] = r.Proto
 | 
			
		||||
	logFields["http_method"] = r.Method
 | 
			
		||||
 | 
			
		||||
	logFields["remote_addr"] = r.RemoteAddr
 | 
			
		||||
	logFields["user_agent"] = r.UserAgent()
 | 
			
		||||
 | 
			
		||||
	logFields["uri"] = fmt.Sprintf("%s://%s%s", scheme, r.Host, r.RequestURI)
 | 
			
		||||
 | 
			
		||||
	entry.Logger = entry.Logger.WithFields(logFields)
 | 
			
		||||
 | 
			
		||||
	return entry
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StructuredLoggerEntry is a log entry will all relevant information about a specific http request
 | 
			
		||||
type StructuredLoggerEntry struct {
 | 
			
		||||
	Logger logrus.FieldLogger
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Write logs information about http request response body
 | 
			
		||||
func (l *StructuredLoggerEntry) Write(status, bytes int, elapsed time.Duration) {
 | 
			
		||||
	l.Logger = l.Logger.WithFields(logrus.Fields{
 | 
			
		||||
		"resp_status": status, "resp_bytes_length": bytes,
 | 
			
		||||
		"resp_elapsed_ms": float64(elapsed.Nanoseconds()) / 1000000.0,
 | 
			
		||||
	})
 | 
			
		||||
	l.Logger.Debugln("request complete")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Panic logs if the request panics
 | 
			
		||||
func (l *StructuredLoggerEntry) Panic(v interface{}, stack []byte) {
 | 
			
		||||
	l.Logger = l.Logger.WithFields(logrus.Fields{
 | 
			
		||||
		"stack": string(stack),
 | 
			
		||||
		"panic": fmt.Sprintf("%+v", v),
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetLogEntry helper function for getting log entry for request
 | 
			
		||||
func GetLogEntry(r *http.Request) logrus.FieldLogger {
 | 
			
		||||
	entry := middleware.GetLogEntry(r).(*StructuredLoggerEntry)
 | 
			
		||||
	return entry.Logger
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LogEntrySetField sets a key's value
 | 
			
		||||
func LogEntrySetField(r *http.Request, key string, value interface{}) {
 | 
			
		||||
	if entry, ok := r.Context().Value(middleware.LogEntryCtxKey).(*StructuredLoggerEntry); ok {
 | 
			
		||||
		entry.Logger = entry.Logger.WithField(key, value)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LogEntrySetFields sets the log entry's fields
 | 
			
		||||
func LogEntrySetFields(r *http.Request, fields map[string]interface{}) {
 | 
			
		||||
	if entry, ok := r.Context().Value(middleware.LogEntryCtxKey).(*StructuredLoggerEntry); ok {
 | 
			
		||||
		entry.Logger = entry.Logger.WithFields(fields)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -19,6 +19,7 @@ type AuthenticationMiddleware struct {
 | 
			
		||||
// Middleware returns the middleware handler
 | 
			
		||||
func (m *AuthenticationMiddleware) Middleware(next http.Handler) http.Handler {
 | 
			
		||||
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		requestID := uuid.New()
 | 
			
		||||
		bearerTokenRaw := r.Header.Get("Authorization")
 | 
			
		||||
		splitToken := strings.Split(bearerTokenRaw, "Bearer")
 | 
			
		||||
		if len(splitToken) != 2 {
 | 
			
		||||
@@ -61,6 +62,7 @@ func (m *AuthenticationMiddleware) Middleware(next http.Handler) http.Handler {
 | 
			
		||||
		ctx := context.WithValue(r.Context(), utils.UserIDKey, userID)
 | 
			
		||||
		ctx = context.WithValue(ctx, utils.RestrictedModeKey, accessClaims.Restricted)
 | 
			
		||||
		ctx = context.WithValue(ctx, utils.OrgRoleKey, accessClaims.OrgRole)
 | 
			
		||||
		ctx = context.WithValue(ctx, utils.ReqIDKey, requestID)
 | 
			
		||||
 | 
			
		||||
		next.ServeHTTP(w, r.WithContext(ctx))
 | 
			
		||||
	})
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,8 @@ type ContextKey string
 | 
			
		||||
const (
 | 
			
		||||
	// UserIDKey is the key for the user id of the authenticated user
 | 
			
		||||
	UserIDKey ContextKey = "userID"
 | 
			
		||||
	// ReqIDKey is the unique ID key for current request
 | 
			
		||||
	ReqIDKey ContextKey = "reqID"
 | 
			
		||||
	//RestrictedModeKey is the key for whether the authenticated user only has access to install route
 | 
			
		||||
	RestrictedModeKey ContextKey = "restricted_mode"
 | 
			
		||||
	// OrgRoleKey is the key for the organization role code of the authenticated user
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user