mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Add groups api
This commit is contained in:
parent
7285ebb940
commit
da3d9cf76b
8 changed files with 175 additions and 4 deletions
|
|
@ -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[]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
}
|
||||
`
|
||||
|
||||
|
|
|
|||
|
|
@ -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}`
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue