Add createGroup API

This commit is contained in:
Hongbo Wu 2022-12-01 18:41:00 +08:00
parent 7f04a381a1
commit 7285ebb940
9 changed files with 229 additions and 6 deletions

View file

@ -13,7 +13,7 @@ import { User } from '../user'
@Entity()
export class Group {
@PrimaryGeneratedColumn('uuid')
id?: string
id!: string
@Column('text')
name!: string
@ -23,8 +23,8 @@ export class Group {
createdBy!: User
@CreateDateColumn()
createdAt?: Date
createdAt!: Date
@UpdateDateColumn()
updatedAt?: Date
updatedAt!: Date
}

View file

@ -33,4 +33,7 @@ export class Profile {
@UpdateDateColumn()
updatedAt!: Date
@Column('boolean', { default: false })
private!: boolean
}

View file

@ -275,6 +275,29 @@ export type CreateArticleSuccess = {
user: User;
};
export type CreateGroupError = {
__typename?: 'CreateGroupError';
errorCodes: Array<CreateGroupErrorCode>;
};
export enum CreateGroupErrorCode {
BadRequest = 'BAD_REQUEST',
Unauthorized = 'UNAUTHORIZED'
}
export type CreateGroupInput = {
expiresInDays?: InputMaybe<Scalars['Int']>;
maxMembers?: InputMaybe<Scalars['Int']>;
name: Scalars['String'];
};
export type CreateGroupResult = CreateGroupError | CreateGroupSuccess;
export type CreateGroupSuccess = {
__typename?: 'CreateGroupSuccess';
group: RecommendationGroup;
};
export type CreateHighlightError = {
__typename?: 'CreateHighlightError';
errorCodes: Array<CreateHighlightErrorCode>;
@ -1066,6 +1089,7 @@ export type Mutation = {
addPopularRead: AddPopularReadResult;
createArticle: CreateArticleResult;
createArticleSavingRequest: CreateArticleSavingRequestResult;
createGroup: CreateGroupResult;
createHighlight: CreateHighlightResult;
createHighlightReply: CreateHighlightReplyResult;
createLabel: CreateLabelResult;
@ -1140,6 +1164,11 @@ export type MutationCreateArticleSavingRequestArgs = {
};
export type MutationCreateGroupArgs = {
input: CreateGroupInput;
};
export type MutationCreateHighlightArgs = {
input: CreateHighlightInput;
};
@ -1696,6 +1725,17 @@ export type RecentSearchesSuccess = {
searches: Array<RecentSearch>;
};
export type RecommendationGroup = {
__typename?: 'RecommendationGroup';
admins: Array<User>;
createdAt: Scalars['Date'];
id: Scalars['ID'];
inviteUrl: Scalars['String'];
members: Array<User>;
name: Scalars['String'];
updatedAt: Scalars['Date'];
};
export type Reminder = {
__typename?: 'Reminder';
archiveUntil: Scalars['Boolean'];
@ -2909,6 +2949,11 @@ export type ResolversTypes = {
CreateArticleSavingRequestResult: ResolversTypes['CreateArticleSavingRequestError'] | ResolversTypes['CreateArticleSavingRequestSuccess'];
CreateArticleSavingRequestSuccess: ResolverTypeWrapper<CreateArticleSavingRequestSuccess>;
CreateArticleSuccess: ResolverTypeWrapper<CreateArticleSuccess>;
CreateGroupError: ResolverTypeWrapper<CreateGroupError>;
CreateGroupErrorCode: CreateGroupErrorCode;
CreateGroupInput: CreateGroupInput;
CreateGroupResult: ResolversTypes['CreateGroupError'] | ResolversTypes['CreateGroupSuccess'];
CreateGroupSuccess: ResolverTypeWrapper<CreateGroupSuccess>;
CreateHighlightError: ResolverTypeWrapper<CreateHighlightError>;
CreateHighlightErrorCode: CreateHighlightErrorCode;
CreateHighlightInput: CreateHighlightInput;
@ -3090,6 +3135,7 @@ export type ResolversTypes = {
RecentSearchesErrorCode: RecentSearchesErrorCode;
RecentSearchesResult: ResolversTypes['RecentSearchesError'] | ResolversTypes['RecentSearchesSuccess'];
RecentSearchesSuccess: ResolverTypeWrapper<RecentSearchesSuccess>;
RecommendationGroup: ResolverTypeWrapper<RecommendationGroup>;
Reminder: ResolverTypeWrapper<Reminder>;
ReminderError: ResolverTypeWrapper<ReminderError>;
ReminderErrorCode: ReminderErrorCode;
@ -3331,6 +3377,10 @@ export type ResolversParentTypes = {
CreateArticleSavingRequestResult: ResolversParentTypes['CreateArticleSavingRequestError'] | ResolversParentTypes['CreateArticleSavingRequestSuccess'];
CreateArticleSavingRequestSuccess: CreateArticleSavingRequestSuccess;
CreateArticleSuccess: CreateArticleSuccess;
CreateGroupError: CreateGroupError;
CreateGroupInput: CreateGroupInput;
CreateGroupResult: ResolversParentTypes['CreateGroupError'] | ResolversParentTypes['CreateGroupSuccess'];
CreateGroupSuccess: CreateGroupSuccess;
CreateHighlightError: CreateHighlightError;
CreateHighlightInput: CreateHighlightInput;
CreateHighlightReplyError: CreateHighlightReplyError;
@ -3475,6 +3525,7 @@ export type ResolversParentTypes = {
RecentSearchesError: RecentSearchesError;
RecentSearchesResult: ResolversParentTypes['RecentSearchesError'] | ResolversParentTypes['RecentSearchesSuccess'];
RecentSearchesSuccess: RecentSearchesSuccess;
RecommendationGroup: RecommendationGroup;
Reminder: Reminder;
ReminderError: ReminderError;
ReminderResult: ResolversParentTypes['ReminderError'] | ResolversParentTypes['ReminderSuccess'];
@ -3830,6 +3881,20 @@ export type CreateArticleSuccessResolvers<ContextType = ResolverContext, ParentT
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type CreateGroupErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['CreateGroupError'] = ResolversParentTypes['CreateGroupError']> = {
errorCodes?: Resolver<Array<ResolversTypes['CreateGroupErrorCode']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type CreateGroupResultResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['CreateGroupResult'] = ResolversParentTypes['CreateGroupResult']> = {
__resolveType: TypeResolveFn<'CreateGroupError' | 'CreateGroupSuccess', ParentType, ContextType>;
};
export type CreateGroupSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['CreateGroupSuccess'] = ResolversParentTypes['CreateGroupSuccess']> = {
group?: Resolver<ResolversTypes['RecommendationGroup'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type CreateHighlightErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['CreateHighlightError'] = ResolversParentTypes['CreateHighlightError']> = {
errorCodes?: Resolver<Array<ResolversTypes['CreateHighlightErrorCode']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
@ -4417,6 +4482,7 @@ export type MutationResolvers<ContextType = ResolverContext, ParentType extends
addPopularRead?: Resolver<ResolversTypes['AddPopularReadResult'], ParentType, ContextType, RequireFields<MutationAddPopularReadArgs, 'name'>>;
createArticle?: Resolver<ResolversTypes['CreateArticleResult'], ParentType, ContextType, RequireFields<MutationCreateArticleArgs, 'input'>>;
createArticleSavingRequest?: Resolver<ResolversTypes['CreateArticleSavingRequestResult'], ParentType, ContextType, RequireFields<MutationCreateArticleSavingRequestArgs, 'input'>>;
createGroup?: Resolver<ResolversTypes['CreateGroupResult'], ParentType, ContextType, RequireFields<MutationCreateGroupArgs, 'input'>>;
createHighlight?: Resolver<ResolversTypes['CreateHighlightResult'], ParentType, ContextType, RequireFields<MutationCreateHighlightArgs, 'input'>>;
createHighlightReply?: Resolver<ResolversTypes['CreateHighlightReplyResult'], ParentType, ContextType, RequireFields<MutationCreateHighlightReplyArgs, 'input'>>;
createLabel?: Resolver<ResolversTypes['CreateLabelResult'], ParentType, ContextType, RequireFields<MutationCreateLabelArgs, 'input'>>;
@ -4616,6 +4682,17 @@ export type RecentSearchesSuccessResolvers<ContextType = ResolverContext, Parent
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type RecommendationGroupResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['RecommendationGroup'] = ResolversParentTypes['RecommendationGroup']> = {
admins?: Resolver<Array<ResolversTypes['User']>, ParentType, ContextType>;
createdAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
inviteUrl?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
members?: Resolver<Array<ResolversTypes['User']>, ParentType, ContextType>;
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
updatedAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type ReminderResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['Reminder'] = ResolversParentTypes['Reminder']> = {
archiveUntil?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
@ -5341,6 +5418,9 @@ export type Resolvers<ContextType = ResolverContext> = {
CreateArticleSavingRequestResult?: CreateArticleSavingRequestResultResolvers<ContextType>;
CreateArticleSavingRequestSuccess?: CreateArticleSavingRequestSuccessResolvers<ContextType>;
CreateArticleSuccess?: CreateArticleSuccessResolvers<ContextType>;
CreateGroupError?: CreateGroupErrorResolvers<ContextType>;
CreateGroupResult?: CreateGroupResultResolvers<ContextType>;
CreateGroupSuccess?: CreateGroupSuccessResolvers<ContextType>;
CreateHighlightError?: CreateHighlightErrorResolvers<ContextType>;
CreateHighlightReplyError?: CreateHighlightReplyErrorResolvers<ContextType>;
CreateHighlightReplyResult?: CreateHighlightReplyResultResolvers<ContextType>;
@ -5468,6 +5548,7 @@ export type Resolvers<ContextType = ResolverContext> = {
RecentSearchesError?: RecentSearchesErrorResolvers<ContextType>;
RecentSearchesResult?: RecentSearchesResultResolvers<ContextType>;
RecentSearchesSuccess?: RecentSearchesSuccessResolvers<ContextType>;
RecommendationGroup?: RecommendationGroupResolvers<ContextType>;
Reminder?: ReminderResolvers<ContextType>;
ReminderError?: ReminderErrorResolvers<ContextType>;
ReminderResult?: ReminderResultResolvers<ContextType>;

View file

@ -232,6 +232,27 @@ type CreateArticleSuccess {
user: User!
}
type CreateGroupError {
errorCodes: [CreateGroupErrorCode!]!
}
enum CreateGroupErrorCode {
BAD_REQUEST
UNAUTHORIZED
}
input CreateGroupInput {
expiresInDays: Int
maxMembers: Int
name: String!
}
union CreateGroupResult = CreateGroupError | CreateGroupSuccess
type CreateGroupSuccess {
group: RecommendationGroup!
}
type CreateHighlightError {
errorCodes: [CreateHighlightErrorCode!]!
}
@ -948,6 +969,7 @@ type Mutation {
addPopularRead(name: String!): AddPopularReadResult!
createArticle(input: CreateArticleInput!): CreateArticleResult!
createArticleSavingRequest(input: CreateArticleSavingRequestInput!): CreateArticleSavingRequestResult!
createGroup(input: CreateGroupInput!): CreateGroupResult!
createHighlight(input: CreateHighlightInput!): CreateHighlightResult!
createHighlightReply(input: CreateHighlightReplyInput!): CreateHighlightReplyResult!
createLabel(input: CreateLabelInput!): CreateLabelResult!
@ -1181,6 +1203,16 @@ type RecentSearchesSuccess {
searches: [RecentSearch!]!
}
type RecommendationGroup {
admins: [User!]!
createdAt: Date!
id: ID!
inviteUrl: String!
members: [User!]!
name: String!
updatedAt: Date!
}
type Reminder {
archiveUntil: Boolean!
id: ID!

View file

@ -26,6 +26,7 @@ import {
articleSavingRequestResolver,
createArticleResolver,
createArticleSavingRequestResolver,
createGroupResolver,
createHighlightResolver,
createLabelResolver,
createNewsletterEmailResolver,
@ -186,6 +187,7 @@ export const functionResolvers = {
saveFilter: saveFilterResolver,
deleteFilter: deleteFilterResolver,
moveFilter: moveFilterResolver,
createGroup: createGroupResolver,
},
Query: {
me: getMeUserResolver,
@ -634,4 +636,5 @@ export const functionResolvers = {
...resultResolveTypeResolver('Filters'),
...resultResolveTypeResolver('DeleteFilter'),
...resultResolveTypeResolver('MoveFilter'),
...resultResolveTypeResolver('CreateGroup'),
}

View file

@ -23,3 +23,4 @@ export * from './api_key'
export * from './integrations'
export * from './rules'
export * from './filters'
export * from './recommendations'

View file

@ -0,0 +1,70 @@
import {
CreateGroupError,
CreateGroupErrorCode,
CreateGroupSuccess,
MutationCreateGroupArgs,
} from '../../generated/graphql'
import { createGroup } from '../../services/create_group'
import { authorized, userDataToUser } from '../../utils/helpers'
import { homePageURL } from '../../env'
import { getRepository } from '../../entity/utils'
import { User } from '../../entity/user'
export const createGroupResolver = authorized<
CreateGroupSuccess,
CreateGroupError,
MutationCreateGroupArgs
>(async (_, { input }, { claims: { uid }, log }) => {
log.info('Creating group', {
input,
labels: {
source: 'resolver',
resolver: 'createGroupResolver',
uid,
},
})
try {
const userData = await getRepository(User).findOne({
where: { id: uid },
relations: ['profile'],
})
if (!userData) {
return {
errorCodes: [CreateGroupErrorCode.Unauthorized],
}
}
const [group, invite] = await createGroup({
admin: userData,
name: input.name,
maxMembers: input.maxMembers,
expiresInDays: input.expiresInDays,
})
const inviteUrl = `${homePageURL()}/invite/${invite.code}`
const user = userDataToUser(userData)
return {
group: {
...group,
inviteUrl,
admins: [user],
members: [user],
},
}
} catch (error) {
log.error('Error creating group', {
error,
labels: {
source: 'resolver',
resolver: 'createGroupResolver',
uid,
},
})
return {
errorCodes: [CreateGroupErrorCode.BadRequest],
}
}
})

View file

@ -2137,6 +2137,37 @@ const schema = gql`
NOT_FOUND
}
input CreateGroupInput {
name: String! @sanitize(maxLength: 140)
maxMembers: Int
expiresInDays: Int
}
union CreateGroupResult = CreateGroupSuccess | CreateGroupError
type CreateGroupSuccess {
group: RecommendationGroup!
}
type RecommendationGroup {
id: ID!
name: String!
inviteUrl: String!
admins: [User!]!
members: [User!]!
createdAt: Date!
updatedAt: Date!
}
type CreateGroupError {
errorCodes: [CreateGroupErrorCode!]!
}
enum CreateGroupErrorCode {
UNAUTHORIZED
BAD_REQUEST
}
# Mutations
type Mutation {
googleLogin(input: GoogleLoginInput!): LoginResult!
@ -2214,6 +2245,7 @@ const schema = gql`
saveFilter(input: SaveFilterInput!): SaveFilterResult!
deleteFilter(id: ID!): DeleteFilterResult!
moveFilter(input: MoveFilterInput!): MoveFilterResult!
createGroup(input: CreateGroupInput!): CreateGroupResult!
}
# FIXME: remove sort from feedArticles after all cached tabs are closed

View file

@ -8,8 +8,8 @@ import { AppDataSource } from '../server'
export const createGroup = async (input: {
admin: User
name: string
maxMembers?: number
expiresInDays?: number
maxMembers?: number | null
expiresInDays?: number | null
}): Promise<[Group, Invite]> => {
const [group, invite] = await AppDataSource.transaction<[Group, Invite]>(
async (t) => {
@ -28,7 +28,7 @@ export const createGroup = async (input: {
group,
code,
createdBy: input.admin,
maxMembers: input.maxMembers || 50,
maxMembers: input.maxMembers || 12,
expirationTime: expirationTime,
})
// Add the admin to the group as its first user
@ -36,6 +36,7 @@ export const createGroup = async (input: {
user: input.admin,
group,
invite,
isAdmin: true,
})
return [group, invite]
}