Add groups api

This commit is contained in:
Hongbo Wu 2022-12-02 12:32:26 +08:00
parent 7285ebb940
commit da3d9cf76b
8 changed files with 175 additions and 4 deletions

View file

@ -3,12 +3,14 @@ import {
CreateDateColumn,
Entity,
JoinColumn,
OneToMany,
OneToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm'
import { User } from '../user'
import { GroupMembership } from './group_membership'
@Entity()
export class Group {
@ -27,4 +29,7 @@ export class Group {
@UpdateDateColumn()
updatedAt!: Date
@OneToMany(() => GroupMembership, (groupMembership) => groupMembership.group)
members!: GroupMembership[]
}

View file

@ -3,6 +3,7 @@ import {
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
OneToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
@ -21,7 +22,7 @@ export class GroupMembership {
@JoinColumn()
user!: User
@OneToOne(() => Group)
@ManyToOne(() => Group, (group) => group.members)
@JoinColumn()
group!: Group

View file

@ -847,6 +847,23 @@ export type GoogleSignupSuccess = {
me: User;
};
export type GroupsError = {
__typename?: 'GroupsError';
errorCodes: Array<GroupsErrorCode>;
};
export enum GroupsErrorCode {
BadRequest = 'BAD_REQUEST',
Unauthorized = 'UNAUTHORIZED'
}
export type GroupsResult = GroupsError | GroupsSuccess;
export type GroupsSuccess = {
__typename?: 'GroupsSuccess';
groups: Array<RecommendationGroup>;
};
export type Highlight = {
__typename?: 'Highlight';
annotation?: Maybe<Scalars['String']>;
@ -1556,6 +1573,7 @@ export type Query = {
getFollowers: GetFollowersResult;
getFollowing: GetFollowingResult;
getUserPersonalization: GetUserPersonalizationResult;
groups: GroupsResult;
hello?: Maybe<Scalars['String']>;
integrations: IntegrationsResult;
labels: LabelsResult;
@ -3068,6 +3086,10 @@ export type ResolversTypes = {
GoogleSignupInput: GoogleSignupInput;
GoogleSignupResult: ResolversTypes['GoogleSignupError'] | ResolversTypes['GoogleSignupSuccess'];
GoogleSignupSuccess: ResolverTypeWrapper<GoogleSignupSuccess>;
GroupsError: ResolverTypeWrapper<GroupsError>;
GroupsErrorCode: GroupsErrorCode;
GroupsResult: ResolversTypes['GroupsError'] | ResolversTypes['GroupsSuccess'];
GroupsSuccess: ResolverTypeWrapper<GroupsSuccess>;
Highlight: ResolverTypeWrapper<Highlight>;
HighlightReply: ResolverTypeWrapper<HighlightReply>;
HighlightStats: ResolverTypeWrapper<HighlightStats>;
@ -3471,6 +3493,9 @@ export type ResolversParentTypes = {
GoogleSignupInput: GoogleSignupInput;
GoogleSignupResult: ResolversParentTypes['GoogleSignupError'] | ResolversParentTypes['GoogleSignupSuccess'];
GoogleSignupSuccess: GoogleSignupSuccess;
GroupsError: GroupsError;
GroupsResult: ResolversParentTypes['GroupsError'] | ResolversParentTypes['GroupsSuccess'];
GroupsSuccess: GroupsSuccess;
Highlight: Highlight;
HighlightReply: HighlightReply;
HighlightStats: HighlightStats;
@ -4299,6 +4324,20 @@ export type GoogleSignupSuccessResolvers<ContextType = ResolverContext, ParentTy
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type GroupsErrorResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['GroupsError'] = ResolversParentTypes['GroupsError']> = {
errorCodes?: Resolver<Array<ResolversTypes['GroupsErrorCode']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type GroupsResultResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['GroupsResult'] = ResolversParentTypes['GroupsResult']> = {
__resolveType: TypeResolveFn<'GroupsError' | 'GroupsSuccess', ParentType, ContextType>;
};
export type GroupsSuccessResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['GroupsSuccess'] = ResolversParentTypes['GroupsSuccess']> = {
groups?: Resolver<Array<ResolversTypes['RecommendationGroup']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type HighlightResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['Highlight'] = ResolversParentTypes['Highlight']> = {
annotation?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
createdAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
@ -4623,6 +4662,7 @@ export type QueryResolvers<ContextType = ResolverContext, ParentType extends Res
getFollowers?: Resolver<ResolversTypes['GetFollowersResult'], ParentType, ContextType, Partial<QueryGetFollowersArgs>>;
getFollowing?: Resolver<ResolversTypes['GetFollowingResult'], ParentType, ContextType, Partial<QueryGetFollowingArgs>>;
getUserPersonalization?: Resolver<ResolversTypes['GetUserPersonalizationResult'], ParentType, ContextType>;
groups?: Resolver<ResolversTypes['GroupsResult'], ParentType, ContextType>;
hello?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
integrations?: Resolver<ResolversTypes['IntegrationsResult'], ParentType, ContextType>;
labels?: Resolver<ResolversTypes['LabelsResult'], ParentType, ContextType>;
@ -5502,6 +5542,9 @@ export type Resolvers<ContextType = ResolverContext> = {
GoogleSignupError?: GoogleSignupErrorResolvers<ContextType>;
GoogleSignupResult?: GoogleSignupResultResolvers<ContextType>;
GoogleSignupSuccess?: GoogleSignupSuccessResolvers<ContextType>;
GroupsError?: GroupsErrorResolvers<ContextType>;
GroupsResult?: GroupsResultResolvers<ContextType>;
GroupsSuccess?: GroupsSuccessResolvers<ContextType>;
Highlight?: HighlightResolvers<ContextType>;
HighlightReply?: HighlightReplyResolvers<ContextType>;
HighlightStats?: HighlightStatsResolvers<ContextType>;

View file

@ -749,6 +749,21 @@ type GoogleSignupSuccess {
me: User!
}
type GroupsError {
errorCodes: [GroupsErrorCode!]!
}
enum GroupsErrorCode {
BAD_REQUEST
UNAUTHORIZED
}
union GroupsResult = GroupsError | GroupsSuccess
type GroupsSuccess {
groups: [RecommendationGroup!]!
}
type Highlight {
annotation: String
createdAt: Date!
@ -1137,6 +1152,7 @@ type Query {
getFollowers(userId: ID): GetFollowersResult!
getFollowing(userId: ID): GetFollowingResult!
getUserPersonalization: GetUserPersonalizationResult!
groups: GroupsResult!
hello: String
integrations: IntegrationsResult!
labels: LabelsResult!

View file

@ -55,6 +55,7 @@ import {
getUserResolver,
googleLoginResolver,
googleSignupResolver,
groupsResolver,
integrationsResolver,
labelsResolver,
logOutResolver,
@ -218,6 +219,7 @@ export const functionResolvers = {
rules: rulesResolver,
deviceTokens: deviceTokensResolver,
filters: filtersResolver,
groups: groupsResolver,
},
User: {
async sharedArticles(
@ -637,4 +639,5 @@ export const functionResolvers = {
...resultResolveTypeResolver('DeleteFilter'),
...resultResolveTypeResolver('MoveFilter'),
...resultResolveTypeResolver('CreateGroup'),
...resultResolveTypeResolver('Groups'),
}

View file

@ -2,11 +2,17 @@ import {
CreateGroupError,
CreateGroupErrorCode,
CreateGroupSuccess,
GroupsError,
GroupsErrorCode,
GroupsSuccess,
MutationCreateGroupArgs,
} from '../../generated/graphql'
import { createGroup } from '../../services/create_group'
import {
createGroup,
getInviteUrl,
getRecommendationGroups,
} from '../../services/create_group'
import { authorized, userDataToUser } from '../../utils/helpers'
import { homePageURL } from '../../env'
import { getRepository } from '../../entity/utils'
import { User } from '../../entity/user'
@ -42,7 +48,7 @@ export const createGroupResolver = authorized<
expiresInDays: input.expiresInDays,
})
const inviteUrl = `${homePageURL()}/invite/${invite.code}`
const inviteUrl = getInviteUrl(invite)
const user = userDataToUser(userData)
return {
@ -68,3 +74,45 @@ export const createGroupResolver = authorized<
}
}
})
export const groupsResolver = authorized<GroupsSuccess, GroupsError>(
async (_, __, { claims: { uid }, log }) => {
log.info('Getting groups', {
labels: {
source: 'resolver',
resolver: 'groupsResolver',
uid,
},
})
try {
const user = await getRepository(User).findOneBy({
id: uid,
})
if (!user) {
return {
errorCodes: [GroupsErrorCode.Unauthorized],
}
}
const groups = await getRecommendationGroups(user)
return {
groups,
}
} catch (error) {
log.error('Error getting groups', {
error,
labels: {
source: 'resolver',
resolver: 'groupsResolver',
uid,
},
})
return {
errorCodes: [GroupsErrorCode.BadRequest],
}
}
}
)

View file

@ -2168,6 +2168,21 @@ const schema = gql`
BAD_REQUEST
}
union GroupsResult = GroupsSuccess | GroupsError
type GroupsSuccess {
groups: [RecommendationGroup!]!
}
type GroupsError {
errorCodes: [GroupsErrorCode!]!
}
enum GroupsErrorCode {
UNAUTHORIZED
BAD_REQUEST
}
# Mutations
type Mutation {
googleLogin(input: GoogleLoginInput!): LoginResult!
@ -2301,6 +2316,7 @@ const schema = gql`
rules(enabled: Boolean): RulesResult!
deviceTokens: DeviceTokensResult!
filters: FiltersResult!
groups: GroupsResult!
}
`

View file

@ -4,6 +4,10 @@ import { Invite } from '../entity/groups/invite'
import { GroupMembership } from '../entity/groups/group_membership'
import { nanoid } from 'nanoid'
import { AppDataSource } from '../server'
import { RecommendationGroup, User as GraphqlUser } from '../generated/graphql'
import { getRepository } from '../entity/utils'
import { homePageURL } from '../env'
import { userDataToUser } from '../utils/helpers'
export const createGroup = async (input: {
admin: User
@ -43,3 +47,38 @@ export const createGroup = async (input: {
)
return [group, invite]
}
export const getRecommendationGroups = async (
user: User
): Promise<RecommendationGroup[]> => {
const groupMembers = await getRepository(GroupMembership).find({
where: { user: { id: user.id } },
relations: ['invite', 'group.members.user.profile'],
})
return groupMembers.map((gm) => {
const admins: GraphqlUser[] = []
const members: GraphqlUser[] = []
gm.group.members.forEach((m) => {
const user = userDataToUser(m.user)
if (m.isAdmin) {
admins.push(user)
}
members.push(user)
})
return {
id: gm.group.id,
name: gm.group.name,
createdAt: gm.group.createdAt,
updatedAt: gm.group.updatedAt,
inviteUrl: getInviteUrl(gm.invite),
admins,
members,
}
})
}
export const getInviteUrl = (invite: Invite) => {
return `${homePageURL()}/invite/${invite.code}`
}