From da3d9cf76b7016e2370c321ae756d603992b3e88 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Fri, 2 Dec 2022 12:32:26 +0800 Subject: [PATCH] Add groups api --- packages/api/src/entity/groups/group.ts | 5 ++ .../api/src/entity/groups/group_membership.ts | 3 +- packages/api/src/generated/graphql.ts | 43 +++++++++++++++ packages/api/src/generated/schema.graphql | 16 ++++++ .../api/src/resolvers/function_resolvers.ts | 3 ++ .../src/resolvers/recommendations/index.ts | 54 +++++++++++++++++-- packages/api/src/schema.ts | 16 ++++++ packages/api/src/services/create_group.ts | 39 ++++++++++++++ 8 files changed, 175 insertions(+), 4 deletions(-) diff --git a/packages/api/src/entity/groups/group.ts b/packages/api/src/entity/groups/group.ts index 4d300d762..4aa22e1ee 100644 --- a/packages/api/src/entity/groups/group.ts +++ b/packages/api/src/entity/groups/group.ts @@ -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[] } diff --git a/packages/api/src/entity/groups/group_membership.ts b/packages/api/src/entity/groups/group_membership.ts index 7362f8edc..30b92d943 100644 --- a/packages/api/src/entity/groups/group_membership.ts +++ b/packages/api/src/entity/groups/group_membership.ts @@ -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 diff --git a/packages/api/src/generated/graphql.ts b/packages/api/src/generated/graphql.ts index e63a31b11..f9db0f7e3 100644 --- a/packages/api/src/generated/graphql.ts +++ b/packages/api/src/generated/graphql.ts @@ -847,6 +847,23 @@ export type GoogleSignupSuccess = { me: User; }; +export type GroupsError = { + __typename?: 'GroupsError'; + errorCodes: Array; +}; + +export enum GroupsErrorCode { + BadRequest = 'BAD_REQUEST', + Unauthorized = 'UNAUTHORIZED' +} + +export type GroupsResult = GroupsError | GroupsSuccess; + +export type GroupsSuccess = { + __typename?: 'GroupsSuccess'; + groups: Array; +}; + export type Highlight = { __typename?: 'Highlight'; annotation?: Maybe; @@ -1556,6 +1573,7 @@ export type Query = { getFollowers: GetFollowersResult; getFollowing: GetFollowingResult; getUserPersonalization: GetUserPersonalizationResult; + groups: GroupsResult; hello?: Maybe; integrations: IntegrationsResult; labels: LabelsResult; @@ -3068,6 +3086,10 @@ export type ResolversTypes = { GoogleSignupInput: GoogleSignupInput; GoogleSignupResult: ResolversTypes['GoogleSignupError'] | ResolversTypes['GoogleSignupSuccess']; GoogleSignupSuccess: ResolverTypeWrapper; + GroupsError: ResolverTypeWrapper; + GroupsErrorCode: GroupsErrorCode; + GroupsResult: ResolversTypes['GroupsError'] | ResolversTypes['GroupsSuccess']; + GroupsSuccess: ResolverTypeWrapper; Highlight: ResolverTypeWrapper; HighlightReply: ResolverTypeWrapper; HighlightStats: ResolverTypeWrapper; @@ -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; }; +export type GroupsErrorResolvers = { + errorCodes?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type GroupsResultResolvers = { + __resolveType: TypeResolveFn<'GroupsError' | 'GroupsSuccess', ParentType, ContextType>; +}; + +export type GroupsSuccessResolvers = { + groups?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type HighlightResolvers = { annotation?: Resolver, ParentType, ContextType>; createdAt?: Resolver; @@ -4623,6 +4662,7 @@ export type QueryResolvers>; getFollowing?: Resolver>; getUserPersonalization?: Resolver; + groups?: Resolver; hello?: Resolver, ParentType, ContextType>; integrations?: Resolver; labels?: Resolver; @@ -5502,6 +5542,9 @@ export type Resolvers = { GoogleSignupError?: GoogleSignupErrorResolvers; GoogleSignupResult?: GoogleSignupResultResolvers; GoogleSignupSuccess?: GoogleSignupSuccessResolvers; + GroupsError?: GroupsErrorResolvers; + GroupsResult?: GroupsResultResolvers; + GroupsSuccess?: GroupsSuccessResolvers; Highlight?: HighlightResolvers; HighlightReply?: HighlightReplyResolvers; HighlightStats?: HighlightStatsResolvers; diff --git a/packages/api/src/generated/schema.graphql b/packages/api/src/generated/schema.graphql index 6c024eefa..447b3a776 100644 --- a/packages/api/src/generated/schema.graphql +++ b/packages/api/src/generated/schema.graphql @@ -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! diff --git a/packages/api/src/resolvers/function_resolvers.ts b/packages/api/src/resolvers/function_resolvers.ts index c23b683a7..3d7a05808 100644 --- a/packages/api/src/resolvers/function_resolvers.ts +++ b/packages/api/src/resolvers/function_resolvers.ts @@ -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'), } diff --git a/packages/api/src/resolvers/recommendations/index.ts b/packages/api/src/resolvers/recommendations/index.ts index a040188f6..68cd8a04a 100644 --- a/packages/api/src/resolvers/recommendations/index.ts +++ b/packages/api/src/resolvers/recommendations/index.ts @@ -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( + 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], + } + } + } +) diff --git a/packages/api/src/schema.ts b/packages/api/src/schema.ts index 4da7c1c8d..0a5ed9910 100755 --- a/packages/api/src/schema.ts +++ b/packages/api/src/schema.ts @@ -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! } ` diff --git a/packages/api/src/services/create_group.ts b/packages/api/src/services/create_group.ts index 6a50cd130..d49b9901a 100644 --- a/packages/api/src/services/create_group.ts +++ b/packages/api/src/services/create_group.ts @@ -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 => { + 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}` +}