Add leaveGroup API
This commit is contained in:
@ -978,6 +978,24 @@ export type LabelsSuccess = {
|
||||
labels: Array<Label>;
|
||||
};
|
||||
|
||||
export type LeaveGroupError = {
|
||||
__typename?: 'LeaveGroupError';
|
||||
errorCodes: Array<LeaveGroupErrorCode>;
|
||||
};
|
||||
|
||||
export enum LeaveGroupErrorCode {
|
||||
BadRequest = 'BAD_REQUEST',
|
||||
NotFound = 'NOT_FOUND',
|
||||
Unauthorized = 'UNAUTHORIZED'
|
||||
}
|
||||
|
||||
export type LeaveGroupResult = LeaveGroupError | LeaveGroupSuccess;
|
||||
|
||||
export type LeaveGroupSuccess = {
|
||||
__typename?: 'LeaveGroupSuccess';
|
||||
success: Scalars['Boolean'];
|
||||
};
|
||||
|
||||
export type Link = {
|
||||
__typename?: 'Link';
|
||||
highlightStats: HighlightStats;
|
||||
@ -1147,6 +1165,7 @@ export type Mutation = {
|
||||
googleLogin: LoginResult;
|
||||
googleSignup: GoogleSignupResult;
|
||||
joinGroup: JoinGroupResult;
|
||||
leaveGroup: LeaveGroupResult;
|
||||
logOut: LogOutResult;
|
||||
mergeHighlight: MergeHighlightResult;
|
||||
moveFilter: MoveFilterResult;
|
||||
@ -1308,6 +1327,11 @@ export type MutationJoinGroupArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationLeaveGroupArgs = {
|
||||
groupId: Scalars['ID'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationMergeHighlightArgs = {
|
||||
input: MergeHighlightInput;
|
||||
};
|
||||
@ -3215,6 +3239,10 @@ export type ResolversTypes = {
|
||||
LabelsErrorCode: LabelsErrorCode;
|
||||
LabelsResult: ResolversTypes['LabelsError'] | ResolversTypes['LabelsSuccess'];
|
||||
LabelsSuccess: ResolverTypeWrapper<LabelsSuccess>;
|
||||
LeaveGroupError: ResolverTypeWrapper<LeaveGroupError>;
|
||||
LeaveGroupErrorCode: LeaveGroupErrorCode;
|
||||
LeaveGroupResult: ResolversTypes['LeaveGroupError'] | ResolversTypes['LeaveGroupSuccess'];
|
||||
LeaveGroupSuccess: ResolverTypeWrapper<LeaveGroupSuccess>;
|
||||
Link: ResolverTypeWrapper<Link>;
|
||||
LinkShareInfo: ResolverTypeWrapper<LinkShareInfo>;
|
||||
LogOutError: ResolverTypeWrapper<LogOutError>;
|
||||
@ -3633,6 +3661,9 @@ export type ResolversParentTypes = {
|
||||
LabelsError: LabelsError;
|
||||
LabelsResult: ResolversParentTypes['LabelsError'] | ResolversParentTypes['LabelsSuccess'];
|
||||
LabelsSuccess: LabelsSuccess;
|
||||
LeaveGroupError: LeaveGroupError;
|
||||
LeaveGroupResult: ResolversParentTypes['LeaveGroupError'] | ResolversParentTypes['LeaveGroupSuccess'];
|
||||
LeaveGroupSuccess: LeaveGroupSuccess;
|
||||
Link: Link;
|
||||
LinkShareInfo: LinkShareInfo;
|
||||
LogOutError: LogOutError;
|
||||
@ -4571,6 +4602,20 @@ export type LabelsSuccessResolvers<ContextType = ResolverContext, ParentType ext
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type LeaveGroupErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['LeaveGroupError'] = ResolversParentTypes['LeaveGroupError']> = {
|
||||
errorCodes?: Resolver<Array<ResolversTypes['LeaveGroupErrorCode']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type LeaveGroupResultResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['LeaveGroupResult'] = ResolversParentTypes['LeaveGroupResult']> = {
|
||||
__resolveType: TypeResolveFn<'LeaveGroupError' | 'LeaveGroupSuccess', ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type LeaveGroupSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['LeaveGroupSuccess'] = ResolversParentTypes['LeaveGroupSuccess']> = {
|
||||
success?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
export type LinkResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['Link'] = ResolversParentTypes['Link']> = {
|
||||
highlightStats?: Resolver<ResolversTypes['HighlightStats'], ParentType, ContextType>;
|
||||
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
@ -4692,6 +4737,7 @@ export type MutationResolvers<ContextType = ResolverContext, ParentType extends
|
||||
googleLogin?: Resolver<ResolversTypes['LoginResult'], ParentType, ContextType, RequireFields<MutationGoogleLoginArgs, 'input'>>;
|
||||
googleSignup?: Resolver<ResolversTypes['GoogleSignupResult'], ParentType, ContextType, RequireFields<MutationGoogleSignupArgs, 'input'>>;
|
||||
joinGroup?: Resolver<ResolversTypes['JoinGroupResult'], ParentType, ContextType, RequireFields<MutationJoinGroupArgs, 'inviteCode'>>;
|
||||
leaveGroup?: Resolver<ResolversTypes['LeaveGroupResult'], ParentType, ContextType, RequireFields<MutationLeaveGroupArgs, 'groupId'>>;
|
||||
logOut?: Resolver<ResolversTypes['LogOutResult'], ParentType, ContextType>;
|
||||
mergeHighlight?: Resolver<ResolversTypes['MergeHighlightResult'], ParentType, ContextType, RequireFields<MutationMergeHighlightArgs, 'input'>>;
|
||||
moveFilter?: Resolver<ResolversTypes['MoveFilterResult'], ParentType, ContextType, RequireFields<MutationMoveFilterArgs, 'input'>>;
|
||||
@ -5757,6 +5803,9 @@ export type Resolvers<ContextType = ResolverContext> = {
|
||||
LabelsError?: LabelsErrorResolvers<ContextType>;
|
||||
LabelsResult?: LabelsResultResolvers<ContextType>;
|
||||
LabelsSuccess?: LabelsSuccessResolvers<ContextType>;
|
||||
LeaveGroupError?: LeaveGroupErrorResolvers<ContextType>;
|
||||
LeaveGroupResult?: LeaveGroupResultResolvers<ContextType>;
|
||||
LeaveGroupSuccess?: LeaveGroupSuccessResolvers<ContextType>;
|
||||
Link?: LinkResolvers<ContextType>;
|
||||
LinkShareInfo?: LinkShareInfoResolvers<ContextType>;
|
||||
LogOutError?: LogOutErrorResolvers<ContextType>;
|
||||
|
||||
@ -867,6 +867,22 @@ type LabelsSuccess {
|
||||
labels: [Label!]!
|
||||
}
|
||||
|
||||
type LeaveGroupError {
|
||||
errorCodes: [LeaveGroupErrorCode!]!
|
||||
}
|
||||
|
||||
enum LeaveGroupErrorCode {
|
||||
BAD_REQUEST
|
||||
NOT_FOUND
|
||||
UNAUTHORIZED
|
||||
}
|
||||
|
||||
union LeaveGroupResult = LeaveGroupError | LeaveGroupSuccess
|
||||
|
||||
type LeaveGroupSuccess {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type Link {
|
||||
highlightStats: HighlightStats!
|
||||
id: ID!
|
||||
@ -1023,6 +1039,7 @@ type Mutation {
|
||||
googleLogin(input: GoogleLoginInput!): LoginResult!
|
||||
googleSignup(input: GoogleSignupInput!): GoogleSignupResult!
|
||||
joinGroup(inviteCode: String!): JoinGroupResult!
|
||||
leaveGroup(groupId: ID!): LeaveGroupResult!
|
||||
logOut: LogOutResult!
|
||||
mergeHighlight(input: MergeHighlightInput!): MergeHighlightResult!
|
||||
moveFilter(input: MoveFilterInput!): MoveFilterResult!
|
||||
|
||||
@ -59,6 +59,7 @@ import {
|
||||
integrationsResolver,
|
||||
joinGroupResolver,
|
||||
labelsResolver,
|
||||
leaveGroupResolver,
|
||||
logOutResolver,
|
||||
mergeHighlightResolver,
|
||||
moveFilterResolver,
|
||||
@ -195,6 +196,7 @@ export const functionResolvers = {
|
||||
recommend: recommendResolver,
|
||||
joinGroup: joinGroupResolver,
|
||||
recommendHighlights: recommendHighlightsResolver,
|
||||
leaveGroup: leaveGroupResolver,
|
||||
},
|
||||
Query: {
|
||||
me: getMeUserResolver,
|
||||
@ -649,4 +651,5 @@ export const functionResolvers = {
|
||||
...resultResolveTypeResolver('Recommend'),
|
||||
...resultResolveTypeResolver('JoinGroup'),
|
||||
...resultResolveTypeResolver('RecommendHighlights'),
|
||||
...resultResolveTypeResolver('LeaveGroup'),
|
||||
}
|
||||
|
||||
@ -8,8 +8,12 @@ import {
|
||||
JoinGroupError,
|
||||
JoinGroupErrorCode,
|
||||
JoinGroupSuccess,
|
||||
LeaveGroupError,
|
||||
LeaveGroupErrorCode,
|
||||
LeaveGroupSuccess,
|
||||
MutationCreateGroupArgs,
|
||||
MutationJoinGroupArgs,
|
||||
MutationLeaveGroupArgs,
|
||||
MutationRecommendArgs,
|
||||
MutationRecommendHighlightsArgs,
|
||||
RecommendError,
|
||||
@ -24,6 +28,7 @@ import {
|
||||
getInviteUrl,
|
||||
getRecommendationGroups,
|
||||
joinGroup,
|
||||
leaveGroup,
|
||||
} from '../../services/groups'
|
||||
import { authorized, userDataToUser } from '../../utils/helpers'
|
||||
import { getRepository } from '../../entity/utils'
|
||||
@ -368,3 +373,48 @@ export const recommendHighlightsResolver = authorized<
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const leaveGroupResolver = authorized<
|
||||
LeaveGroupSuccess,
|
||||
LeaveGroupError,
|
||||
MutationLeaveGroupArgs
|
||||
>(async (_, { groupId }, { claims: { uid }, log }) => {
|
||||
log.info('Leaving group', {
|
||||
groupId,
|
||||
labels: {
|
||||
source: 'resolver',
|
||||
resolver: 'leaveGroupResolver',
|
||||
uid,
|
||||
},
|
||||
})
|
||||
|
||||
try {
|
||||
const user = await getRepository(User).findOneBy({
|
||||
id: uid,
|
||||
})
|
||||
if (!user) {
|
||||
return {
|
||||
errorCodes: [LeaveGroupErrorCode.Unauthorized],
|
||||
}
|
||||
}
|
||||
|
||||
const success = await leaveGroup(user, groupId)
|
||||
|
||||
return {
|
||||
success,
|
||||
}
|
||||
} catch (error) {
|
||||
log.error('Error leaving group', {
|
||||
error,
|
||||
labels: {
|
||||
source: 'resolver',
|
||||
resolver: 'leaveGroupResolver',
|
||||
uid,
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
errorCodes: [LeaveGroupErrorCode.BadRequest],
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@ -2264,6 +2264,22 @@ const schema = gql`
|
||||
NOT_FOUND
|
||||
}
|
||||
|
||||
union LeaveGroupResult = LeaveGroupSuccess | LeaveGroupError
|
||||
|
||||
type LeaveGroupSuccess {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type LeaveGroupError {
|
||||
errorCodes: [LeaveGroupErrorCode!]!
|
||||
}
|
||||
|
||||
enum LeaveGroupErrorCode {
|
||||
UNAUTHORIZED
|
||||
BAD_REQUEST
|
||||
NOT_FOUND
|
||||
}
|
||||
|
||||
# Mutations
|
||||
type Mutation {
|
||||
googleLogin(input: GoogleLoginInput!): LoginResult!
|
||||
@ -2347,6 +2363,7 @@ const schema = gql`
|
||||
recommendHighlights(
|
||||
input: RecommendHighlightsInput!
|
||||
): RecommendHighlightsResult!
|
||||
leaveGroup(groupId: ID!): LeaveGroupResult!
|
||||
}
|
||||
|
||||
# FIXME: remove sort from feedArticles after all cached tabs are closed
|
||||
|
||||
@ -147,3 +147,51 @@ having count(*) < $4`,
|
||||
members,
|
||||
}
|
||||
}
|
||||
|
||||
export const leaveGroup = async (
|
||||
user: User,
|
||||
groupId: string
|
||||
): Promise<boolean> => {
|
||||
return AppDataSource.transaction(async (t) => {
|
||||
const group = await t
|
||||
.getRepository(Group)
|
||||
.createQueryBuilder('group')
|
||||
.setLock('pessimistic_write')
|
||||
.innerJoinAndSelect('group.members', 'members')
|
||||
.where('group.id = :groupId', { groupId })
|
||||
.getOne()
|
||||
|
||||
if (!group) {
|
||||
throw new Error('Group not found')
|
||||
}
|
||||
|
||||
const membership = await t.getRepository(GroupMembership).findOne({
|
||||
where: { user: { id: user.id }, group: { id: group.id } },
|
||||
})
|
||||
if (!membership) {
|
||||
throw new Error('User not in group')
|
||||
}
|
||||
|
||||
await t.getRepository(GroupMembership).remove(membership)
|
||||
|
||||
if (membership.isAdmin) {
|
||||
// If the user is the admin, we need to promote another user to admin
|
||||
const hasAdmin = group.members.some(
|
||||
(m) => m.isAdmin && m.user.id !== user.id
|
||||
)
|
||||
if (!hasAdmin) {
|
||||
const newAdmin = group.members.find((m) => !m.isAdmin)
|
||||
if (!newAdmin) {
|
||||
// delete the group if there are no more members
|
||||
await t.getRepository(Group).delete({ id: group.id })
|
||||
return true
|
||||
}
|
||||
|
||||
newAdmin.isAdmin = true
|
||||
await t.getRepository(GroupMembership).save(newAdmin)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user