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';
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}

View File

@ -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}) {

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"`

View File

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

View File

@ -13,6 +13,7 @@ import (
"github.com/google/uuid"
"github.com/jordanknott/taskcafe/internal/auth"
"github.com/jordanknott/taskcafe/internal/db"
"github.com/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

View File

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

View File

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

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

View File

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