Add leaveGroup API

This commit is contained in:
Hongbo Wu
2022-12-08 15:32:39 +08:00
parent 79f739c04c
commit 6830da995f
6 changed files with 184 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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