feat: redesign project invite popup

This commit is contained in:
Jordan Knott 2020-10-14 16:52:32 -05:00
parent 737d2b640f
commit 262f9cbdda
18 changed files with 530 additions and 364 deletions

View File

@ -16,7 +16,7 @@ import {
} from 'react-router-dom'; } from 'react-router-dom';
import { import {
useUpdateProjectMemberRoleMutation, useUpdateProjectMemberRoleMutation,
useInviteProjectMemberMutation, useInviteProjectMembersMutation,
useDeleteProjectMemberMutation, useDeleteProjectMemberMutation,
useToggleTaskLabelMutation, useToggleTaskLabelMutation,
useUpdateProjectNameMutation, useUpdateProjectNameMutation,
@ -84,9 +84,10 @@ type InviteUserData = {
suerID?: string; suerID?: string;
}; };
type UserManagementPopupProps = { type UserManagementPopupProps = {
projectID: string;
users: Array<User>; users: Array<User>;
projectMembers: Array<TaskUser>; projectMembers: Array<TaskUser>;
onInviteProjectMember: (data: InviteUserData) => void; onInviteProjectMembers: (data: Array<InviteUserData>) => void;
}; };
const VisibiltyPrivateIcon = styled(Lock)` const VisibiltyPrivateIcon = styled(Lock)`
@ -131,14 +132,14 @@ type MemberFilterOptions = {
organization?: boolean; 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) { if (input && input.trim().length < 3) {
return []; return [];
} }
const res = await client.query({ const res = await client.query({
query: gql` query: gql`
query { query {
searchMembers(input: {SearchFilter:"${input}"}) { searchMembers(input: {SearchFilter:"${input}", projectID:"${projectID}"}) {
similarity similarity
confirmed confirmed
joined joined
@ -165,7 +166,7 @@ const fetchMembers = async (client: any, options: MemberFilterOptions, input: st
emails.push(m.user.email); emails.push(m.user.email);
return { return {
label: m.user.fullName, 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 }) => { const UserOption: React.FC<UserOptionProps> = ({ isDisabled, isFocused, innerProps, label, data }) => {
console.log(data);
return !isDisabled ? ( return !isDisabled ? (
<OptionWrapper {...innerProps} isFocused={isFocused}> <OptionWrapper {...innerProps} isFocused={isFocused}>
<TaskAssignee <TaskAssignee
@ -280,16 +282,24 @@ const InviteContainer = styled.div`
flex-direction: column; flex-direction: column;
`; `;
const UserManagementPopup: React.FC<UserManagementPopupProps> = ({ users, projectMembers, onInviteProjectMember }) => { const UserManagementPopup: React.FC<UserManagementPopupProps> = ({
projectID,
users,
projectMembers,
onInviteProjectMembers,
}) => {
const client = useApolloClient(); const client = useApolloClient();
const [invitedUsers, setInvitedUsers] = useState<Array<any> | null>(null); const [invitedUsers, setInvitedUsers] = useState<Array<any> | null>(null);
return ( return (
<Popup tab={0} title="Invite a user"> <Popup tab={0} title="Invite a user">
<InviteContainer> <InviteContainer>
<AsyncSelect <AsyncSelect
getOptionValue={option => option.value.id}
placeholder="Email address or username" placeholder="Email address or username"
noOptionsMessage={() => null} noOptionsMessage={() => null}
onChange={(e: any) => setInvitedUsers(e ? e.value : null)} onChange={(e: any) => {
setInvitedUsers(e);
}}
isMulti isMulti
autoFocus autoFocus
cacheOptions cacheOptions
@ -301,13 +311,25 @@ const UserManagementPopup: React.FC<UserManagementPopupProps> = ({ users, projec
IndicatorSeparator: null, IndicatorSeparator: null,
DropdownIndicator: null, DropdownIndicator: null,
}} }}
loadOptions={(i, cb) => fetchMembers(client, {}, i, cb)} loadOptions={(i, cb) => fetchMembers(client, projectID, {}, i, cb)}
/> />
</InviteContainer> </InviteContainer>
<InviteButton <InviteButton
onClick={() => { onClick={() => {
// FUCK, gotta rewrite invite member to be MULTIPLE. SHIT! if (invitedUsers) {
// onInviteProjectMember(); onInviteProjectMembers(
invitedUsers.map(user => {
if (user.value.type === 0) {
return {
userID: user.value.id,
};
}
return {
email: user.value.id,
};
}),
);
}
}} }}
disabled={invitedUsers === null} disabled={invitedUsers === null}
hoverVariant="none" hoverVariant="none"
@ -398,14 +420,17 @@ const Project = () => {
}, },
}); });
const [inviteProjectMember] = useInviteProjectMemberMutation({ const [inviteProjectMembers] = useInviteProjectMembersMutation({
update: (client, response) => { update: (client, response) => {
updateApolloCache<FindProjectQuery>( updateApolloCache<FindProjectQuery>(
client, client,
FindProjectDocument, FindProjectDocument,
cache => cache =>
produce(cache, draftCache => { produce(cache, draftCache => {
draftCache.findProject.members.push({ ...response.data.inviteProjectMember.member }); draftCache.findProject.members = [
...cache.findProject.members,
...response.data.inviteProjectMembers.members,
];
}), }),
{ projectID }, { projectID },
); );
@ -472,8 +497,10 @@ const Project = () => {
showPopup( showPopup(
$target, $target,
<UserManagementPopup <UserManagementPopup
onInviteProjectMember={userID => { projectID={projectID}
// /inviteProjectMember({ variables: { userID, projectID } }); onInviteProjectMembers={members => {
inviteProjectMembers({ variables: { projectID, members } });
hidePopup();
}} }}
users={data.users} users={data.users}
projectMembers={data.findProject.members} projectMembers={data.findProject.members}

View File

@ -289,7 +289,7 @@ export type Mutation = {
deleteTeamMember: DeleteTeamMemberPayload; deleteTeamMember: DeleteTeamMemberPayload;
deleteUserAccount: DeleteUserAccountPayload; deleteUserAccount: DeleteUserAccountPayload;
duplicateTaskGroup: DuplicateTaskGroupPayload; duplicateTaskGroup: DuplicateTaskGroupPayload;
inviteProjectMember: InviteProjectMemberPayload; inviteProjectMembers: InviteProjectMembersPayload;
logoutUser: Scalars['Boolean']; logoutUser: Scalars['Boolean'];
removeTaskLabel: Task; removeTaskLabel: Task;
setTaskChecklistItemComplete: TaskChecklistItem; setTaskChecklistItemComplete: TaskChecklistItem;
@ -439,8 +439,8 @@ export type MutationDuplicateTaskGroupArgs = {
}; };
export type MutationInviteProjectMemberArgs = { export type MutationInviteProjectMembersArgs = {
input: InviteProjectMember; input: InviteProjectMembers;
}; };
@ -694,16 +694,21 @@ export type UpdateProjectLabelColor = {
labelColorID: Scalars['UUID']; labelColorID: Scalars['UUID'];
}; };
export type InviteProjectMember = { export type MemberInvite = {
projectID: Scalars['UUID'];
userID?: Maybe<Scalars['UUID']>; userID?: Maybe<Scalars['UUID']>;
email?: Maybe<Scalars['String']>; email?: Maybe<Scalars['String']>;
}; };
export type InviteProjectMemberPayload = { export type InviteProjectMembers = {
__typename?: 'InviteProjectMemberPayload'; projectID: Scalars['UUID'];
members: Array<MemberInvite>;
};
export type InviteProjectMembersPayload = {
__typename?: 'InviteProjectMembersPayload';
ok: Scalars['Boolean']; ok: Scalars['Boolean'];
member: Member; projectID: Scalars['UUID'];
members: Array<Member>;
}; };
export type DeleteProjectMember = { export type DeleteProjectMember = {
@ -1446,19 +1451,18 @@ export type DeleteProjectMemberMutation = (
) } ) }
); );
export type InviteProjectMemberMutationVariables = { export type InviteProjectMembersMutationVariables = {
projectID: Scalars['UUID']; projectID: Scalars['UUID'];
userID?: Maybe<Scalars['UUID']>; members: Array<MemberInvite>;
email?: Maybe<Scalars['String']>;
}; };
export type InviteProjectMemberMutation = ( export type InviteProjectMembersMutation = (
{ __typename?: 'Mutation' } { __typename?: 'Mutation' }
& { inviteProjectMember: ( & { inviteProjectMembers: (
{ __typename?: 'InviteProjectMemberPayload' } { __typename?: 'InviteProjectMembersPayload' }
& Pick<InviteProjectMemberPayload, 'ok'> & Pick<InviteProjectMembersPayload, 'ok'>
& { member: ( & { members: Array<(
{ __typename?: 'Member' } { __typename?: 'Member' }
& Pick<Member, 'id' | 'fullName' | 'username'> & Pick<Member, 'id' | 'fullName' | 'username'>
& { profileIcon: ( & { profileIcon: (
@ -1468,7 +1472,7 @@ export type InviteProjectMemberMutation = (
{ __typename?: 'Role' } { __typename?: 'Role' }
& Pick<Role, 'code' | 'name'> & Pick<Role, 'code' | 'name'>
) } ) }
) } )> }
) } ) }
); );
@ -2978,11 +2982,11 @@ export function useDeleteProjectMemberMutation(baseOptions?: ApolloReactHooks.Mu
export type DeleteProjectMemberMutationHookResult = ReturnType<typeof useDeleteProjectMemberMutation>; export type DeleteProjectMemberMutationHookResult = ReturnType<typeof useDeleteProjectMemberMutation>;
export type DeleteProjectMemberMutationResult = ApolloReactCommon.MutationResult<DeleteProjectMemberMutation>; export type DeleteProjectMemberMutationResult = ApolloReactCommon.MutationResult<DeleteProjectMemberMutation>;
export type DeleteProjectMemberMutationOptions = ApolloReactCommon.BaseMutationOptions<DeleteProjectMemberMutation, DeleteProjectMemberMutationVariables>; export type DeleteProjectMemberMutationOptions = ApolloReactCommon.BaseMutationOptions<DeleteProjectMemberMutation, DeleteProjectMemberMutationVariables>;
export const InviteProjectMemberDocument = gql` export const InviteProjectMembersDocument = gql`
mutation inviteProjectMember($projectID: UUID!, $userID: UUID, $email: String) { mutation inviteProjectMembers($projectID: UUID!, $members: [MemberInvite!]!) {
inviteProjectMember(input: {projectID: $projectID, userID: $userID, email: $email}) { inviteProjectMembers(input: {projectID: $projectID, members: $members}) {
ok ok
member { members {
id id
fullName fullName
profileIcon { 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. * 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, `useInviteProjectMemberMutation` returns a tuple that includes: * When your component renders, `useInviteProjectMembersMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation * - 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 * - 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; * @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 * @example
* const [inviteProjectMemberMutation, { data, loading, error }] = useInviteProjectMemberMutation({ * const [inviteProjectMembersMutation, { data, loading, error }] = useInviteProjectMembersMutation({
* variables: { * variables: {
* projectID: // value for 'projectID' * projectID: // value for 'projectID'
* userID: // value for 'userID' * members: // value for 'members'
* email: // value for 'email'
* }, * },
* }); * });
*/ */
export function useInviteProjectMemberMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<InviteProjectMemberMutation, InviteProjectMemberMutationVariables>) { export function useInviteProjectMembersMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<InviteProjectMembersMutation, InviteProjectMembersMutationVariables>) {
return ApolloReactHooks.useMutation<InviteProjectMemberMutation, InviteProjectMemberMutationVariables>(InviteProjectMemberDocument, baseOptions); return ApolloReactHooks.useMutation<InviteProjectMembersMutation, InviteProjectMembersMutationVariables>(InviteProjectMembersDocument, baseOptions);
} }
export type InviteProjectMemberMutationHookResult = ReturnType<typeof useInviteProjectMemberMutation>; export type InviteProjectMembersMutationHookResult = ReturnType<typeof useInviteProjectMembersMutation>;
export type InviteProjectMemberMutationResult = ApolloReactCommon.MutationResult<InviteProjectMemberMutation>; export type InviteProjectMembersMutationResult = ApolloReactCommon.MutationResult<InviteProjectMembersMutation>;
export type InviteProjectMemberMutationOptions = ApolloReactCommon.BaseMutationOptions<InviteProjectMemberMutation, InviteProjectMemberMutationVariables>; export type InviteProjectMembersMutationOptions = ApolloReactCommon.BaseMutationOptions<InviteProjectMembersMutation, InviteProjectMembersMutationVariables>;
export const UpdateProjectMemberRoleDocument = gql` export const UpdateProjectMemberRoleDocument = gql`
mutation updateProjectMemberRole($projectID: UUID!, $userID: UUID!, $roleCode: RoleCode!) { mutation updateProjectMemberRole($projectID: UUID!, $userID: UUID!, $roleCode: RoleCode!) {
updateProjectMemberRole(input: {projectID: $projectID, userID: $userID, roleCode: $roleCode}) { updateProjectMemberRole(input: {projectID: $projectID, userID: $userID, roleCode: $roleCode}) {

View File

@ -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;

View 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;

View File

@ -78,8 +78,11 @@ func newWebCmd() *cobra.Command {
return http.ListenAndServe(viper.GetString("server.hostname"), r) 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") 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.BindPFlag("migrate", cc.Flags().Lookup("migrate"))
viper.SetDefault("migrate", false) viper.SetDefault("migrate", false)
return cc return cc
} }

View File

@ -61,7 +61,7 @@ type Querier interface {
GetEntityIDForNotificationID(ctx context.Context, notificationID uuid.UUID) (uuid.UUID, error) GetEntityIDForNotificationID(ctx context.Context, notificationID uuid.UUID) (uuid.UUID, error)
GetLabelColorByID(ctx context.Context, labelColorID uuid.UUID) (LabelColor, error) GetLabelColorByID(ctx context.Context, labelColorID uuid.UUID) (LabelColor, error)
GetLabelColors(ctx context.Context) ([]LabelColor, error) GetLabelColors(ctx context.Context) ([]LabelColor, error)
GetMemberData(ctx context.Context) ([]GetMemberDataRow, error) GetMemberData(ctx context.Context, projectID uuid.UUID) ([]UserAccount, error)
GetMemberProjectIDsForUserID(ctx context.Context, userID uuid.UUID) ([]uuid.UUID, error) GetMemberProjectIDsForUserID(ctx context.Context, userID uuid.UUID) ([]uuid.UUID, error)
GetMemberTeamIDsForUserID(ctx context.Context, userID uuid.UUID) ([]uuid.UUID, error) GetMemberTeamIDsForUserID(ctx context.Context, userID uuid.UUID) ([]uuid.UUID, error)
GetNotificationForNotificationID(ctx context.Context, notificationID uuid.UUID) (GetNotificationForNotificationIDRow, error) GetNotificationForNotificationID(ctx context.Context, notificationID uuid.UUID) (GetNotificationForNotificationIDRow, error)

View File

@ -16,7 +16,9 @@ UPDATE user_account SET profile_avatar_url = $2 WHERE user_id = $1
RETURNING *; RETURNING *;
-- name: GetMemberData :many -- 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 -- name: UpdateUserAccountInfo :one
UPDATE user_account SET bio = $2, full_name = $3, initials = $4, email = $5 UPDATE user_account SET bio = $2, full_name = $3, initials = $4, email = $5

View File

@ -102,30 +102,32 @@ func (q *Queries) GetAllUserAccounts(ctx context.Context) ([]UserAccount, error)
} }
const getMemberData = `-- name: GetMemberData :many 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 { func (q *Queries) GetMemberData(ctx context.Context, projectID uuid.UUID) ([]UserAccount, error) {
Username string `json:"username"` rows, err := q.db.QueryContext(ctx, getMemberData, projectID)
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)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer rows.Close() defer rows.Close()
var items []GetMemberDataRow var items []UserAccount
for rows.Next() { for rows.Next() {
var i GetMemberDataRow var i UserAccount
if err := rows.Scan( if err := rows.Scan(
&i.Username,
&i.FullName,
&i.Email,
&i.UserID, &i.UserID,
&i.CreatedAt,
&i.Email,
&i.Username,
&i.PasswordHash,
&i.ProfileBgColor,
&i.FullName,
&i.Initials,
&i.ProfileAvatarUrl,
&i.RoleCode,
&i.Bio,
); err != nil { ); err != nil {
return nil, err return nil, err
} }

View File

@ -127,9 +127,10 @@ type ComplexityRoot struct {
TaskGroup func(childComplexity int) int TaskGroup func(childComplexity int) int
} }
InviteProjectMemberPayload struct { InviteProjectMembersPayload struct {
Member func(childComplexity int) int Members func(childComplexity int) int
Ok func(childComplexity int) int Ok func(childComplexity int) int
ProjectID func(childComplexity int) int
} }
LabelColor struct { LabelColor struct {
@ -194,7 +195,7 @@ type ComplexityRoot struct {
DeleteTeamMember func(childComplexity int, input DeleteTeamMember) int DeleteTeamMember func(childComplexity int, input DeleteTeamMember) int
DeleteUserAccount func(childComplexity int, input DeleteUserAccount) int DeleteUserAccount func(childComplexity int, input DeleteUserAccount) int
DuplicateTaskGroup func(childComplexity int, input DuplicateTaskGroup) 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 LogoutUser func(childComplexity int, input LogoutUser) int
RemoveTaskLabel func(childComplexity int, input *RemoveTaskLabelInput) int RemoveTaskLabel func(childComplexity int, input *RemoveTaskLabelInput) int
SetTaskChecklistItemComplete func(childComplexity int, input SetTaskChecklistItemComplete) int SetTaskChecklistItemComplete func(childComplexity int, input SetTaskChecklistItemComplete) int
@ -454,7 +455,7 @@ type MutationResolver interface {
UpdateProjectLabel(ctx context.Context, input UpdateProjectLabel) (*db.ProjectLabel, error) UpdateProjectLabel(ctx context.Context, input UpdateProjectLabel) (*db.ProjectLabel, error)
UpdateProjectLabelName(ctx context.Context, input UpdateProjectLabelName) (*db.ProjectLabel, error) UpdateProjectLabelName(ctx context.Context, input UpdateProjectLabelName) (*db.ProjectLabel, error)
UpdateProjectLabelColor(ctx context.Context, input UpdateProjectLabelColor) (*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) DeleteProjectMember(ctx context.Context, input DeleteProjectMember) (*DeleteProjectMemberPayload, error)
UpdateProjectMemberRole(ctx context.Context, input UpdateProjectMemberRole) (*UpdateProjectMemberRolePayload, error) UpdateProjectMemberRole(ctx context.Context, input UpdateProjectMemberRole) (*UpdateProjectMemberRolePayload, error)
CreateTask(ctx context.Context, input NewTask) (*db.Task, 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 return e.complexity.DuplicateTaskGroupPayload.TaskGroup(childComplexity), true
case "InviteProjectMemberPayload.member": case "InviteProjectMembersPayload.members":
if e.complexity.InviteProjectMemberPayload.Member == nil { if e.complexity.InviteProjectMembersPayload.Members == nil {
break break
} }
return e.complexity.InviteProjectMemberPayload.Member(childComplexity), true return e.complexity.InviteProjectMembersPayload.Members(childComplexity), true
case "InviteProjectMemberPayload.ok": case "InviteProjectMembersPayload.ok":
if e.complexity.InviteProjectMemberPayload.Ok == nil { if e.complexity.InviteProjectMembersPayload.Ok == nil {
break 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": case "LabelColor.colorHex":
if e.complexity.LabelColor.ColorHex == nil { 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 return e.complexity.Mutation.DuplicateTaskGroup(childComplexity, args["input"].(DuplicateTaskGroup)), true
case "Mutation.inviteProjectMember": case "Mutation.inviteProjectMembers":
if e.complexity.Mutation.InviteProjectMember == nil { if e.complexity.Mutation.InviteProjectMembers == nil {
break break
} }
args, err := ec.field_Mutation_inviteProjectMember_args(context.TODO(), rawArgs) args, err := ec.field_Mutation_inviteProjectMembers_args(context.TODO(), rawArgs)
if err != nil { if err != nil {
return 0, false 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": case "Mutation.logoutUser":
if e.complexity.Mutation.LogoutUser == nil { if e.complexity.Mutation.LogoutUser == nil {
@ -2869,24 +2877,28 @@ input UpdateProjectLabelColor {
} }
extend type Mutation { extend type Mutation {
# TODO: rename to inviteProjectMember inviteProjectMembers(input: InviteProjectMembers!):
inviteProjectMember(input: InviteProjectMember!): InviteProjectMembersPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
InviteProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
deleteProjectMember(input: DeleteProjectMember!): deleteProjectMember(input: DeleteProjectMember!):
DeleteProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT) DeleteProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
updateProjectMemberRole(input: UpdateProjectMemberRole!): updateProjectMemberRole(input: UpdateProjectMemberRole!):
UpdateProjectMemberRolePayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT) UpdateProjectMemberRolePayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
} }
input InviteProjectMember { input MemberInvite {
projectID: UUID!
userID: UUID userID: UUID
email: String email: String
} }
type InviteProjectMemberPayload { input InviteProjectMembers {
projectID: UUID!
members: [MemberInvite!]!
}
type InviteProjectMembersPayload {
ok: Boolean! ok: Boolean!
member: Member! projectID: UUID!
members: [Member!]!
} }
input DeleteProjectMember { input DeleteProjectMember {
@ -3715,12 +3727,12 @@ func (ec *executionContext) field_Mutation_duplicateTaskGroup_args(ctx context.C
return args, nil 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 var err error
args := map[string]interface{}{} args := map[string]interface{}{}
var arg0 InviteProjectMember var arg0 InviteProjectMembers
if tmp, ok := rawArgs["input"]; ok { 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 { if err != nil {
return nil, err 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) 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() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r)) ec.Error(ctx, ec.Recover(ctx, r))
@ -5187,7 +5199,7 @@ func (ec *executionContext) _InviteProjectMemberPayload_ok(ctx context.Context,
} }
}() }()
fc := &graphql.FieldContext{ fc := &graphql.FieldContext{
Object: "InviteProjectMemberPayload", Object: "InviteProjectMembersPayload",
Field: field, Field: field,
Args: nil, Args: nil,
IsMethod: false, IsMethod: false,
@ -5213,7 +5225,7 @@ func (ec *executionContext) _InviteProjectMemberPayload_ok(ctx context.Context,
return ec.marshalNBoolean2bool(ctx, field.Selections, res) 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() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r)) ec.Error(ctx, ec.Recover(ctx, r))
@ -5221,7 +5233,7 @@ func (ec *executionContext) _InviteProjectMemberPayload_member(ctx context.Conte
} }
}() }()
fc := &graphql.FieldContext{ fc := &graphql.FieldContext{
Object: "InviteProjectMemberPayload", Object: "InviteProjectMembersPayload",
Field: field, Field: field,
Args: nil, Args: nil,
IsMethod: false, IsMethod: false,
@ -5230,7 +5242,7 @@ func (ec *executionContext) _InviteProjectMemberPayload_member(ctx context.Conte
ctx = graphql.WithFieldContext(ctx, fc) ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children ctx = rctx // use context from middleware stack in children
return obj.Member, nil return obj.ProjectID, nil
}) })
if err != nil { if err != nil {
ec.Error(ctx, err) ec.Error(ctx, err)
@ -5242,9 +5254,43 @@ func (ec *executionContext) _InviteProjectMemberPayload_member(ctx context.Conte
} }
return graphql.Null return graphql.Null
} }
res := resTmp.(*Member) res := resTmp.(uuid.UUID)
fc.Result = res 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) { 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) 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() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r)) 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) ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables) 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 { if err != nil {
ec.Error(ctx, err) ec.Error(ctx, err)
return graphql.Null 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) { resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
directive0 := func(rctx context.Context) (interface{}, error) { directive0 := func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children 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) { directive1 := func(ctx context.Context) (interface{}, error) {
roles, err := ec.unmarshalNRoleLevel2ᚕgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐRoleLevelᚄ(ctx, []interface{}{"ADMIN"}) 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 { if tmp == nil {
return nil, nil return nil, nil
} }
if data, ok := tmp.(*InviteProjectMemberPayload); ok { if data, ok := tmp.(*InviteProjectMembersPayload); ok {
return data, nil 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 { if err != nil {
ec.Error(ctx, err) ec.Error(ctx, err)
@ -6613,9 +6659,9 @@ func (ec *executionContext) _Mutation_inviteProjectMember(ctx context.Context, f
} }
return graphql.Null return graphql.Null
} }
res := resTmp.(*InviteProjectMemberPayload) res := resTmp.(*InviteProjectMembersPayload)
fc.Result = res 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) { 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 return it, nil
} }
func (ec *executionContext) unmarshalInputInviteProjectMember(ctx context.Context, obj interface{}) (InviteProjectMember, error) { func (ec *executionContext) unmarshalInputInviteProjectMembers(ctx context.Context, obj interface{}) (InviteProjectMembers, error) {
var it InviteProjectMember var it InviteProjectMembers
var asMap = obj.(map[string]interface{}) var asMap = obj.(map[string]interface{})
for k, v := range asMap { for k, v := range asMap {
@ -15415,15 +15461,9 @@ func (ec *executionContext) unmarshalInputInviteProjectMember(ctx context.Contex
if err != nil { if err != nil {
return it, err return it, err
} }
case "userID": case "members":
var err error var err error
it.UserID, err = ec.unmarshalOUUID2ᚖgithubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v) it.Members, err = ec.unmarshalNMemberInvite2ᚕgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐMemberInviteᚄ(ctx, v)
if err != nil {
return it, err
}
case "email":
var err error
it.Email, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil { if err != nil {
return it, err return it, err
} }
@ -15451,6 +15491,30 @@ func (ec *executionContext) unmarshalInputLogoutUser(ctx context.Context, obj in
return it, nil 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) { func (ec *executionContext) unmarshalInputMemberSearchFilter(ctx context.Context, obj interface{}) (MemberSearchFilter, error) {
var it MemberSearchFilter var it MemberSearchFilter
var asMap = obj.(map[string]interface{}) var asMap = obj.(map[string]interface{})
@ -16797,24 +16861,29 @@ func (ec *executionContext) _DuplicateTaskGroupPayload(ctx context.Context, sel
return out return out
} }
var inviteProjectMemberPayloadImplementors = []string{"InviteProjectMemberPayload"} var inviteProjectMembersPayloadImplementors = []string{"InviteProjectMembersPayload"}
func (ec *executionContext) _InviteProjectMemberPayload(ctx context.Context, sel ast.SelectionSet, obj *InviteProjectMemberPayload) graphql.Marshaler { func (ec *executionContext) _InviteProjectMembersPayload(ctx context.Context, sel ast.SelectionSet, obj *InviteProjectMembersPayload) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, inviteProjectMemberPayloadImplementors) fields := graphql.CollectFields(ec.OperationContext, sel, inviteProjectMembersPayloadImplementors)
out := graphql.NewFieldSet(fields) out := graphql.NewFieldSet(fields)
var invalids uint32 var invalids uint32
for i, field := range fields { for i, field := range fields {
switch field.Name { switch field.Name {
case "__typename": case "__typename":
out.Values[i] = graphql.MarshalString("InviteProjectMemberPayload") out.Values[i] = graphql.MarshalString("InviteProjectMembersPayload")
case "ok": 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 { if out.Values[i] == graphql.Null {
invalids++ invalids++
} }
case "member": case "projectID":
out.Values[i] = ec._InviteProjectMemberPayload_member(ctx, field, obj) 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 { if out.Values[i] == graphql.Null {
invalids++ invalids++
} }
@ -17108,8 +17177,8 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
invalids++ invalids++
} }
case "inviteProjectMember": case "inviteProjectMembers":
out.Values[i] = ec._Mutation_inviteProjectMember(ctx, field) out.Values[i] = ec._Mutation_inviteProjectMembers(ctx, field)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
invalids++ invalids++
} }
@ -19692,22 +19761,22 @@ func (ec *executionContext) marshalNInt2int(ctx context.Context, sel ast.Selecti
return res return res
} }
func (ec *executionContext) unmarshalNInviteProjectMember2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐInviteProjectMember(ctx context.Context, v interface{}) (InviteProjectMember, error) { func (ec *executionContext) unmarshalNInviteProjectMembers2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐInviteProjectMembers(ctx context.Context, v interface{}) (InviteProjectMembers, error) {
return ec.unmarshalInputInviteProjectMember(ctx, v) 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 { func (ec *executionContext) marshalNInviteProjectMembersPayload2githubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐInviteProjectMembersPayload(ctx context.Context, sel ast.SelectionSet, v InviteProjectMembersPayload) graphql.Marshaler {
return ec._InviteProjectMemberPayload(ctx, sel, &v) 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 v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "must not be null") ec.Errorf(ctx, "must not be null")
} }
return graphql.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 { 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) 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 { 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) return ec._MemberList(ctx, sel, &v)
} }

View File

@ -19,6 +19,7 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/jordanknott/taskcafe/internal/auth" "github.com/jordanknott/taskcafe/internal/auth"
"github.com/jordanknott/taskcafe/internal/db" "github.com/jordanknott/taskcafe/internal/db"
"github.com/jordanknott/taskcafe/internal/logger"
"github.com/jordanknott/taskcafe/internal/utils" "github.com/jordanknott/taskcafe/internal/utils"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/vektah/gqlparser/v2/gqlerror" "github.com/vektah/gqlparser/v2/gqlerror"
@ -63,10 +64,10 @@ func NewHandler(repo db.Repository) http.Handler {
default: default:
fieldName = "ProjectID" 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) subjectField := val.FieldByName(fieldName)
if !subjectField.IsValid() { 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") return nil, errors.New("subject field name does not exist on input type")
} }
if fieldName == "TeamID" && subjectField.IsNil() { if fieldName == "TeamID" && subjectField.IsNil() {
@ -76,13 +77,13 @@ func NewHandler(repo db.Repository) http.Handler {
} }
subjectID, ok = subjectField.Interface().(uuid.UUID) subjectID, ok = subjectField.Interface().(uuid.UUID)
if !ok { 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") return nil, errors.New("error while casting subject uuid")
} }
var err error var err error
if level == ActionLevelProject { 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 { if typeArg == ObjectTypeTask {
subjectID, err = repo.GetProjectIDForTask(ctx, subjectID) subjectID, err = repo.GetProjectIDForTask(ctx, subjectID)
} }
@ -96,7 +97,7 @@ func NewHandler(repo db.Repository) http.Handler {
subjectID, err = repo.GetProjectIDForTaskChecklistItem(ctx, subjectID) subjectID, err = repo.GetProjectIDForTaskChecklistItem(ctx, subjectID)
} }
if err != nil { 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 return nil, err
} }
projectRoles, err := GetProjectRoles(ctx, repo, subjectID) 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 return nil, err
} }
for _, validRole := range roles { 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) { 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) 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}) role, err := repo.GetTeamRoleForUserID(ctx, db.GetTeamRoleForUserIDParams{UserID: userID, TeamID: subjectID})
if err != nil { 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 return nil, err
} }
for _, validRole := range roles { for _, validRole := range roles {

View File

@ -177,15 +177,15 @@ type FindUser struct {
UserID uuid.UUID `json:"userID"` UserID uuid.UUID `json:"userID"`
} }
type InviteProjectMember struct { type InviteProjectMembers struct {
ProjectID uuid.UUID `json:"projectID"` ProjectID uuid.UUID `json:"projectID"`
UserID *uuid.UUID `json:"userID"` Members []MemberInvite `json:"members"`
Email *string `json:"email"`
} }
type InviteProjectMemberPayload struct { type InviteProjectMembersPayload struct {
Ok bool `json:"ok"` Ok bool `json:"ok"`
Member *Member `json:"member"` ProjectID uuid.UUID `json:"projectID"`
Members []Member `json:"members"`
} }
type LogoutUser struct { type LogoutUser struct {
@ -208,6 +208,11 @@ type Member struct {
Member *MemberList `json:"member"` Member *MemberList `json:"member"`
} }
type MemberInvite struct {
UserID *uuid.UUID `json:"userID"`
Email *string `json:"email"`
}
type MemberList struct { type MemberList struct {
Teams []db.Team `json:"teams"` Teams []db.Team `json:"teams"`
Projects []db.Project `json:"projects"` Projects []db.Project `json:"projects"`

View File

@ -338,24 +338,28 @@ input UpdateProjectLabelColor {
} }
extend type Mutation { extend type Mutation {
# TODO: rename to inviteProjectMember inviteProjectMembers(input: InviteProjectMembers!):
inviteProjectMember(input: InviteProjectMember!): InviteProjectMembersPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
InviteProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
deleteProjectMember(input: DeleteProjectMember!): deleteProjectMember(input: DeleteProjectMember!):
DeleteProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT) DeleteProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
updateProjectMemberRole(input: UpdateProjectMemberRole!): updateProjectMemberRole(input: UpdateProjectMemberRole!):
UpdateProjectMemberRolePayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT) UpdateProjectMemberRolePayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
} }
input InviteProjectMember { input MemberInvite {
projectID: UUID!
userID: UUID userID: UUID
email: String email: String
} }
type InviteProjectMemberPayload { input InviteProjectMembers {
projectID: UUID!
members: [MemberInvite!]!
}
type InviteProjectMembersPayload {
ok: Boolean! ok: Boolean!
member: Member! projectID: UUID!
members: [Member!]!
} }
input DeleteProjectMember { input DeleteProjectMember {

View File

@ -13,6 +13,7 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/jordanknott/taskcafe/internal/auth" "github.com/jordanknott/taskcafe/internal/auth"
"github.com/jordanknott/taskcafe/internal/db" "github.com/jordanknott/taskcafe/internal/db"
"github.com/jordanknott/taskcafe/internal/logger"
"github.com/lithammer/fuzzysearch/fuzzy" "github.com/lithammer/fuzzysearch/fuzzy"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/vektah/gqlparser/v2/gqlerror" "github.com/vektah/gqlparser/v2/gqlerror"
@ -29,7 +30,7 @@ func (r *mutationResolver) CreateProject(ctx context.Context, input NewProject)
return &db.Project{}, errors.New("user id is missing") return &db.Project{}, errors.New("user id is missing")
} }
createdAt := time.Now().UTC() 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 project db.Project
var err error var err error
if input.TeamID == nil { if input.TeamID == nil {
@ -38,10 +39,10 @@ func (r *mutationResolver) CreateProject(ctx context.Context, input NewProject)
Name: input.Name, Name: input.Name,
}) })
if err != nil { 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 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 { } else {
project, err = r.Repository.CreateTeamProject(ctx, db.CreateTeamProjectParams{ project, err = r.Repository.CreateTeamProject(ctx, db.CreateTeamProjectParams{
CreatedAt: createdAt, CreatedAt: createdAt,
@ -49,13 +50,13 @@ func (r *mutationResolver) CreateProject(ctx context.Context, input NewProject)
TeamID: *input.TeamID, TeamID: *input.TeamID,
}) })
if err != nil { 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 return &db.Project{}, err
} }
} }
_, err = r.Repository.CreateProjectMember(ctx, db.CreateProjectMemberParams{ProjectID: project.ProjectID, UserID: userID, AddedAt: createdAt, RoleCode: "admin"}) _, err = r.Repository.CreateProjectMember(ctx, db.CreateProjectMemberParams{ProjectID: project.ProjectID, UserID: userID, AddedAt: createdAt, RoleCode: "admin"})
if err != nil { 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 &db.Project{}, err
} }
return &project, nil return &project, nil
@ -124,31 +125,33 @@ func (r *mutationResolver) UpdateProjectLabelColor(ctx context.Context, input Up
return &label, err return &label, err
} }
func (r *mutationResolver) InviteProjectMember(ctx context.Context, input InviteProjectMember) (*InviteProjectMemberPayload, error) { func (r *mutationResolver) InviteProjectMembers(ctx context.Context, input InviteProjectMembers) (*InviteProjectMembersPayload, error) {
if input.Email != nil && input.UserID != nil { members := []Member{}
return &InviteProjectMemberPayload{Ok: false}, &gqlerror.Error{ 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", Message: "Both email and userID can not be used to invite a project member",
Extensions: map[string]interface{}{ Extensions: map[string]interface{}{
"code": "403", "code": "403",
}, },
} }
} else if input.Email == nil && input.UserID == nil { } else if invitedMember.Email == nil && invitedMember.UserID == nil {
return &InviteProjectMemberPayload{Ok: false}, &gqlerror.Error{ return &InviteProjectMembersPayload{Ok: false}, &gqlerror.Error{
Message: "Either email or userID must be set to invite a project member", Message: "Either email or userID must be set to invite a project member",
Extensions: map[string]interface{}{ Extensions: map[string]interface{}{
"code": "403", "code": "403",
}, },
} }
} }
if input.UserID != nil { if invitedMember.UserID != nil {
addedAt := time.Now().UTC() addedAt := time.Now().UTC()
_, err := r.Repository.CreateProjectMember(ctx, db.CreateProjectMemberParams{ProjectID: input.ProjectID, UserID: *input.UserID, AddedAt: addedAt, RoleCode: "member"}) _, err := r.Repository.CreateProjectMember(ctx, db.CreateProjectMemberParams{ProjectID: input.ProjectID, UserID: *invitedMember.UserID, AddedAt: addedAt, RoleCode: "member"})
if err != nil { if err != nil {
return &InviteProjectMemberPayload{Ok: false}, err return &InviteProjectMembersPayload{Ok: false}, err
} }
user, err := r.Repository.GetUserAccountByID(ctx, *input.UserID) user, err := r.Repository.GetUserAccountByID(ctx, *invitedMember.UserID)
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
return &InviteProjectMemberPayload{Ok: false}, err return &InviteProjectMembersPayload{Ok: false}, err
} }
var url *string var url *string
@ -157,20 +160,20 @@ func (r *mutationResolver) InviteProjectMember(ctx context.Context, input Invite
} }
profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor} profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor}
role, err := r.Repository.GetRoleForProjectMemberByUserID(ctx, db.GetRoleForProjectMemberByUserIDParams{UserID: *input.UserID, ProjectID: input.ProjectID}) role, err := r.Repository.GetRoleForProjectMemberByUserID(ctx, db.GetRoleForProjectMemberByUserIDParams{UserID: *invitedMember.UserID, ProjectID: input.ProjectID})
if err != nil { if err != nil {
return &InviteProjectMemberPayload{Ok: false}, err return &InviteProjectMembersPayload{Ok: false}, err
} }
return &InviteProjectMemberPayload{Ok: true, Member: &Member{ members = append(members, Member{
ID: *input.UserID, ID: *invitedMember.UserID,
FullName: user.FullName, FullName: user.FullName,
Username: user.Username, Username: user.Username,
ProfileIcon: profileIcon, ProfileIcon: profileIcon,
Role: &db.Role{Code: role.Code, Name: role.Name}, 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) { 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) { func (r *mutationResolver) UpdateProjectMemberRole(ctx context.Context, input UpdateProjectMemberRole) (*UpdateProjectMemberRolePayload, error) {
user, err := r.Repository.GetUserAccountByID(ctx, input.UserID) user, err := r.Repository.GetUserAccountByID(ctx, input.UserID)
if err != nil { if err != nil {
log.WithError(err).Error("get user account") logger.New(ctx).WithError(err).Error("get user account")
return &UpdateProjectMemberRolePayload{Ok: false}, err return &UpdateProjectMemberRolePayload{Ok: false}, err
} }
_, err = r.Repository.UpdateProjectMemberRole(ctx, db.UpdateProjectMemberRoleParams{ProjectID: input.ProjectID, _, err = r.Repository.UpdateProjectMemberRole(ctx, db.UpdateProjectMemberRoleParams{ProjectID: input.ProjectID,
UserID: input.UserID, RoleCode: input.RoleCode.String()}) UserID: input.UserID, RoleCode: input.RoleCode.String()})
if err != nil { 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 return &UpdateProjectMemberRolePayload{Ok: false}, err
} }
role, err := r.Repository.GetRoleForProjectMemberByUserID(ctx, db.GetRoleForProjectMemberByUserIDParams{UserID: user.UserID, ProjectID: input.ProjectID}) role, err := r.Repository.GetRoleForProjectMemberByUserID(ctx, db.GetRoleForProjectMemberByUserIDParams{UserID: user.UserID, ProjectID: input.ProjectID})
if err != nil { 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 return &UpdateProjectMemberRolePayload{Ok: false}, err
} }
var url *string 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) { func (r *mutationResolver) CreateTask(ctx context.Context, input NewTask) (*db.Task, error) {
createdAt := time.Now().UTC() 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}) task, err := r.Repository.CreateTask(ctx, db.CreateTaskParams{input.TaskGroupID, createdAt, input.Name, input.Position})
if err != nil { 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 &db.Task{}, err
} }
return &task, nil return &task, nil
} }
func (r *mutationResolver) DeleteTask(ctx context.Context, input DeleteTaskInput) (*DeleteTaskPayload, error) { func (r *mutationResolver) DeleteTask(ctx context.Context, input DeleteTaskInput) (*DeleteTaskPayload, error) {
log.WithFields(log.Fields{ logger.New(ctx).WithFields(log.Fields{
"taskID": input.TaskID, "taskID": input.TaskID,
}).Info("deleting task") }).Info("deleting task")
err := r.Repository.DeleteTaskByID(ctx, input.TaskID) 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) { func (r *mutationResolver) AssignTask(ctx context.Context, input *AssignTaskInput) (*db.Task, error) {
assignedDate := time.Now().UTC() assignedDate := time.Now().UTC()
assignedTask, err := r.Repository.CreateTaskAssigned(ctx, db.CreateTaskAssignedParams{input.TaskID, input.UserID, assignedDate}) assignedTask, err := r.Repository.CreateTaskAssigned(ctx, db.CreateTaskAssignedParams{input.TaskID, input.UserID, assignedDate})
log.WithFields(log.Fields{ logger.New(ctx).WithFields(log.Fields{
"userID": assignedTask.UserID, "assignedUserID": assignedTask.UserID,
"taskID": assignedTask.TaskID, "taskID": assignedTask.TaskID,
"assignedTaskID": assignedTask.TaskAssignedID, "assignedTaskID": assignedTask.TaskAssignedID,
}).Info("assigned task") }).Info("assigned task")
@ -610,7 +613,7 @@ func (r *mutationResolver) ToggleTaskLabel(ctx context.Context, input ToggleTask
createdAt := time.Now().UTC() createdAt := time.Now().UTC()
if err == sql.ErrNoRows { 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{ _, err := r.Repository.CreateTaskLabelForTask(ctx, db.CreateTaskLabelForTaskParams{
TaskID: input.TaskID, TaskID: input.TaskID,
ProjectLabelID: input.ProjectLabelID, 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) { func (r *mutationResolver) DeleteTeam(ctx context.Context, input DeleteTeam) (*DeleteTeamPayload, error) {
team, err := r.Repository.GetTeamByID(ctx, input.TeamID) team, err := r.Repository.GetTeamByID(ctx, input.TeamID)
if err != nil { if err != nil {
log.Error(err) logger.New(ctx).Error(err)
return &DeleteTeamPayload{Ok: false}, err return &DeleteTeamPayload{Ok: false}, err
} }
projects, err := r.Repository.GetAllProjectsForTeam(ctx, input.TeamID) projects, err := r.Repository.GetAllProjectsForTeam(ctx, input.TeamID)
if err != nil { if err != nil {
log.Error(err) logger.New(ctx).Error(err)
return &DeleteTeamPayload{Ok: false}, err return &DeleteTeamPayload{Ok: false}, err
} }
err = r.Repository.DeleteTeamByID(ctx, input.TeamID) err = r.Repository.DeleteTeamByID(ctx, input.TeamID)
if err != nil { if err != nil {
log.Error(err) logger.New(ctx).Error(err)
return &DeleteTeamPayload{Ok: false}, 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) { func (r *mutationResolver) UpdateTeamMemberRole(ctx context.Context, input UpdateTeamMemberRole) (*UpdateTeamMemberRolePayload, error) {
user, err := r.Repository.GetUserAccountByID(ctx, input.UserID) user, err := r.Repository.GetUserAccountByID(ctx, input.UserID)
if err != nil { if err != nil {
log.WithError(err).Error("get user account") logger.New(ctx).WithError(err).Error("get user account")
return &UpdateTeamMemberRolePayload{Ok: false}, err return &UpdateTeamMemberRolePayload{Ok: false}, err
} }
_, err = r.Repository.UpdateTeamMemberRole(ctx, db.UpdateTeamMemberRoleParams{TeamID: input.TeamID, _, err = r.Repository.UpdateTeamMemberRole(ctx, db.UpdateTeamMemberRoleParams{TeamID: input.TeamID,
UserID: input.UserID, RoleCode: input.RoleCode.String()}) UserID: input.UserID, RoleCode: input.RoleCode.String()})
if err != nil { 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 return &UpdateTeamMemberRolePayload{Ok: false}, err
} }
role, err := r.Repository.GetRoleForTeamMember(ctx, db.GetRoleForTeamMemberParams{UserID: user.UserID, TeamID: input.TeamID}) role, err := r.Repository.GetRoleForTeamMember(ctx, db.GetRoleForTeamMemberParams{UserID: user.UserID, TeamID: input.TeamID})
if err != nil { 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 return &UpdateTeamMemberRolePayload{Ok: false}, err
} }
var url *string 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) { 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) 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 { if err != nil {
return &NotificationEntity{}, err return &NotificationEntity{}, err
} }
@ -905,7 +908,7 @@ func (r *notificationResolver) Actor(ctx context.Context, obj *db.Notification)
if err != nil { if err != nil {
return &NotificationActor{}, err 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) user, err := r.Repository.GetUserAccountByID(ctx, entity.ActorID)
if err != nil { if err != nil {
return &NotificationActor{}, err return &NotificationActor{}, err
@ -935,7 +938,7 @@ func (r *projectResolver) Team(ctx context.Context, obj *db.Project) (*db.Team,
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return nil, nil 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, err
} }
return &team, nil return &team, nil
@ -949,14 +952,14 @@ func (r *projectResolver) Members(ctx context.Context, obj *db.Project) ([]Membe
members := []Member{} members := []Member{}
projectMembers, err := r.Repository.GetProjectMembersForProjectID(ctx, obj.ProjectID) projectMembers, err := r.Repository.GetProjectMembersForProjectID(ctx, obj.ProjectID)
if err != nil { 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 return members, err
} }
for _, projectMember := range projectMembers { for _, projectMember := range projectMembers {
user, err := r.Repository.GetUserAccountByID(ctx, projectMember.UserID) user, err := r.Repository.GetUserAccountByID(ctx, projectMember.UserID)
if err != nil { 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 return members, err
} }
var url *string 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}) role, err := r.Repository.GetRoleForProjectMemberByUserID(ctx, db.GetRoleForProjectMemberByUserIDParams{UserID: user.UserID, ProjectID: obj.ProjectID})
if err != nil { 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 return members, err
} }
profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor} 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) { func (r *queryResolver) FindProject(ctx context.Context, input FindProject) (*db.Project, error) {
userID, role, ok := GetUser(ctx) logger.New(ctx).Info("finding project user")
log.WithFields(log.Fields{"userID": userID, "role": role}).Info("find project user")
if !ok {
return &db.Project{}, nil
}
project, err := r.Repository.GetProjectByID(ctx, input.ProjectID) project, err := r.Repository.GetProjectByID(ctx, input.ProjectID)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return &db.Project{}, &gqlerror.Error{ 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) { func (r *queryResolver) Projects(ctx context.Context, input *ProjectsFilter) ([]db.Project, error) {
userID, orgRole, ok := GetUser(ctx) userID, orgRole, ok := GetUser(ctx)
if !ok { 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 return []db.Project{}, nil
} }
log.WithFields(log.Fields{"userID": userID}).Info("fetching projects") logger.New(ctx).Info("fetching projects")
if input != nil { if input != nil {
return r.Repository.GetAllProjectsForTeam(ctx, *input.TeamID) 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) projects := make(map[string]db.Project)
for _, team := range teams { 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) teamProjects, err := r.Repository.GetAllProjectsForTeam(ctx, team.TeamID)
if err != sql.ErrNoRows && err != nil { if err != sql.ErrNoRows && err != nil {
log.Info("issue getting team projects") log.Info("issue getting team projects")
return []db.Project{}, nil return []db.Project{}, nil
} }
for _, project := range teamProjects { 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 projects[project.ProjectID.String()] = project
} }
} }
visibleProjects, err := r.Repository.GetAllVisibleProjectsForUserID(ctx, userID) visibleProjects, err := r.Repository.GetAllVisibleProjectsForUserID(ctx, userID)
if err != nil { 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 return []db.Project{}, nil
} }
for _, project := range visibleProjects { 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 { 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 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)) allProjects := make([]db.Project, 0, len(projects))
for _, project := range 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) allProjects = append(allProjects, project)
} }
log.Info(allProjects)
return allProjects, nil 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) { func (r *queryResolver) Teams(ctx context.Context) ([]db.Team, error) {
userID, orgRole, ok := GetUser(ctx) userID, orgRole, ok := GetUser(ctx)
if !ok { 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") return []db.Team{}, errors.New("internal error")
} }
if orgRole == "admin" { if orgRole == "admin" {
@ -1123,7 +1121,7 @@ func (r *queryResolver) Teams(ctx context.Context) ([]db.Team, error) {
teams := make(map[string]db.Team) teams := make(map[string]db.Team)
adminTeams, err := r.Repository.GetTeamsForUserIDWhereAdmin(ctx, userID) adminTeams, err := r.Repository.GetTeamsForUserIDWhereAdmin(ctx, userID)
if err != nil { 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 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) visibleProjects, err := r.Repository.GetAllVisibleProjectsForUserID(ctx, userID)
if err != nil { 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 return []db.Team{}, err
} }
for _, project := range visibleProjects { 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 { 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) team, err := r.Repository.GetTeamByID(ctx, project.TeamID)
if err != nil { if err != nil {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
continue 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 return []db.Team{}, err
} }
teams[project.TeamID.String()] = team 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) user, err := r.Repository.GetUserAccountByID(ctx, userID)
if err == sql.ErrNoRows { 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 return &MePayload{}, nil
} else if err != nil { } else if err != nil {
return &MePayload{}, err 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) { func (r *queryResolver) Notifications(ctx context.Context) ([]db.Notification, error) {
userID, ok := GetUserID(ctx) userID, ok := GetUserID(ctx)
log.WithFields(log.Fields{"userID": userID}).Info("fetching notifications") logger.New(ctx).Info("fetching notifications")
if !ok { if !ok {
return []db.Notification{}, errors.New("user id is missing") 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) { 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 { if err != nil {
return []MemberSearchResult{}, err return []MemberSearchResult{}, err
} }
@ -1233,7 +1231,7 @@ func (r *queryResolver) SearchMembers(ctx context.Context, input MemberSearchFil
memberList := map[uuid.UUID]bool{} memberList := map[uuid.UUID]bool{}
for _, rank := range rankedList { for _, rank := range rankedList {
if _, ok := memberList[masterList[rank.Target]]; !ok { 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] userID := masterList[rank.Target]
user, err := r.Repository.GetUserAccountByID(ctx, userID) user, err := r.Repository.GetUserAccountByID(ctx, userID)
if err != nil { if err != nil {
@ -1313,7 +1311,7 @@ func (r *taskResolver) Assigned(ctx context.Context, obj *db.Task) ([]Member, er
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
role = db.Role{Code: "owner", Name: "Owner"} role = db.Role{Code: "owner", Name: "Owner"}
} else { } 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 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) teamMembers, err := r.Repository.GetTeamMembersForTeamID(ctx, obj.TeamID)
if err != nil { 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 return members, err
} }
for _, teamMember := range teamMembers { for _, teamMember := range teamMembers {
user, err := r.Repository.GetUserAccountByID(ctx, teamMember.UserID) user, err := r.Repository.GetUserAccountByID(ctx, teamMember.UserID)
if err != nil { 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 return members, err
} }
var url *string 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}) role, err := r.Repository.GetRoleForTeamMember(ctx, db.GetRoleForTeamMemberParams{UserID: user.UserID, TeamID: obj.TeamID})
if err != nil { 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 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) { func (r *userAccountResolver) Role(ctx context.Context, obj *db.UserAccount) (*db.Role, error) {
role, err := r.Repository.GetRoleForUserID(ctx, obj.UserID) role, err := r.Repository.GetRoleForUserID(ctx, obj.UserID)
if err != nil { if err != nil {
log.Info("beep!") logger.New(ctx).WithError(err).Error("get role for user id")
log.WithError(err).Error("get role for user id")
return &db.Role{}, err return &db.Role{}, err
} }
return &db.Role{Code: role.Code, Name: role.Name}, nil return &db.Role{Code: role.Code, Name: role.Name}, nil

View File

@ -1,22 +1,26 @@
extend type Mutation { extend type Mutation {
# TODO: rename to inviteProjectMember inviteProjectMembers(input: InviteProjectMembers!):
inviteProjectMember(input: InviteProjectMember!): InviteProjectMembersPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
InviteProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
deleteProjectMember(input: DeleteProjectMember!): deleteProjectMember(input: DeleteProjectMember!):
DeleteProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT) DeleteProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
updateProjectMemberRole(input: UpdateProjectMemberRole!): updateProjectMemberRole(input: UpdateProjectMemberRole!):
UpdateProjectMemberRolePayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT) UpdateProjectMemberRolePayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
} }
input InviteProjectMember { input MemberInvite {
projectID: UUID!
userID: UUID userID: UUID
email: String email: String
} }
type InviteProjectMemberPayload { input InviteProjectMembers {
projectID: UUID!
members: [MemberInvite!]!
}
type InviteProjectMembersPayload {
ok: Boolean! ok: Boolean!
member: Member! projectID: UUID!
members: [Member!]!
} }
input DeleteProjectMember { input DeleteProjectMember {

View File

@ -1,89 +1,21 @@
package logger package logger
import ( import (
"fmt" "context"
"net/http"
"time"
"github.com/go-chi/chi/middleware" "github.com/google/uuid"
"github.com/sirupsen/logrus" "github.com/jordanknott/taskcafe/internal/utils"
log "github.com/sirupsen/logrus"
) )
// NewStructuredLogger creates a new logger for chi router // New returns a log entry with the reqID and userID fields populated if they exist
func NewStructuredLogger(logger *logrus.Logger) func(next http.Handler) http.Handler { func New(ctx context.Context) *log.Entry {
return middleware.RequestLogger(&StructuredLogger{logger}) entry := log.NewEntry(log.StandardLogger())
} if reqID, ok := ctx.Value(utils.ReqIDKey).(uuid.UUID); ok {
entry = entry.WithField("reqID", reqID)
// 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
} }
if userID, ok := ctx.Value(utils.UserIDKey).(uuid.UUID); ok {
scheme := "http" entry = entry.WithField("userID", userID)
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 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)
}
}

View 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)
}
}

View File

@ -19,6 +19,7 @@ type AuthenticationMiddleware struct {
// Middleware returns the middleware handler // Middleware returns the middleware handler
func (m *AuthenticationMiddleware) Middleware(next http.Handler) http.Handler { func (m *AuthenticationMiddleware) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestID := uuid.New()
bearerTokenRaw := r.Header.Get("Authorization") bearerTokenRaw := r.Header.Get("Authorization")
splitToken := strings.Split(bearerTokenRaw, "Bearer") splitToken := strings.Split(bearerTokenRaw, "Bearer")
if len(splitToken) != 2 { 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(r.Context(), utils.UserIDKey, userID)
ctx = context.WithValue(ctx, utils.RestrictedModeKey, accessClaims.Restricted) ctx = context.WithValue(ctx, utils.RestrictedModeKey, accessClaims.Restricted)
ctx = context.WithValue(ctx, utils.OrgRoleKey, accessClaims.OrgRole) ctx = context.WithValue(ctx, utils.OrgRoleKey, accessClaims.OrgRole)
ctx = context.WithValue(ctx, utils.ReqIDKey, requestID)
next.ServeHTTP(w, r.WithContext(ctx)) next.ServeHTTP(w, r.WithContext(ctx))
}) })

View File

@ -6,6 +6,8 @@ type ContextKey string
const ( const (
// UserIDKey is the key for the user id of the authenticated user // UserIDKey is the key for the user id of the authenticated user
UserIDKey ContextKey = "userID" 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 is the key for whether the authenticated user only has access to install route
RestrictedModeKey ContextKey = "restricted_mode" RestrictedModeKey ContextKey = "restricted_mode"
// OrgRoleKey is the key for the organization role code of the authenticated user // OrgRoleKey is the key for the organization role code of the authenticated user