@ -3,17 +3,19 @@ import {
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
OneToMany,
|
||||
OneToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm'
|
||||
|
||||
import { User } from '../user'
|
||||
import { GroupMembership } from './group_membership'
|
||||
|
||||
@Entity()
|
||||
export class Group {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id?: string
|
||||
id!: string
|
||||
|
||||
@Column('text')
|
||||
name!: string
|
||||
@ -23,8 +25,11 @@ export class Group {
|
||||
createdBy!: User
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt?: Date
|
||||
createdAt!: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt?: Date
|
||||
updatedAt!: Date
|
||||
|
||||
@OneToMany(() => GroupMembership, (groupMembership) => groupMembership.group)
|
||||
members!: GroupMembership[]
|
||||
}
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
@ -20,7 +22,7 @@ export class GroupMembership {
|
||||
@JoinColumn()
|
||||
user!: User
|
||||
|
||||
@OneToOne(() => Group)
|
||||
@ManyToOne(() => Group, (group) => group.members)
|
||||
@JoinColumn()
|
||||
group!: Group
|
||||
|
||||
@ -33,4 +35,7 @@ export class GroupMembership {
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt?: Date
|
||||
|
||||
@Column('boolean', { default: false })
|
||||
isAdmin!: boolean
|
||||
}
|
||||
|
||||
@ -33,4 +33,7 @@ export class Profile {
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt!: Date
|
||||
|
||||
@Column('boolean', { default: false })
|
||||
private!: boolean
|
||||
}
|
||||
|
||||
@ -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>;
|
||||
@ -824,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']>;
|
||||
@ -1066,6 +1106,7 @@ export type Mutation = {
|
||||
addPopularRead: AddPopularReadResult;
|
||||
createArticle: CreateArticleResult;
|
||||
createArticleSavingRequest: CreateArticleSavingRequestResult;
|
||||
createGroup: CreateGroupResult;
|
||||
createHighlight: CreateHighlightResult;
|
||||
createHighlightReply: CreateHighlightReplyResult;
|
||||
createLabel: CreateLabelResult;
|
||||
@ -1140,6 +1181,11 @@ export type MutationCreateArticleSavingRequestArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateGroupArgs = {
|
||||
input: CreateGroupInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateHighlightArgs = {
|
||||
input: CreateHighlightInput;
|
||||
};
|
||||
@ -1527,6 +1573,7 @@ export type Query = {
|
||||
getFollowers: GetFollowersResult;
|
||||
getFollowing: GetFollowingResult;
|
||||
getUserPersonalization: GetUserPersonalizationResult;
|
||||
groups: GroupsResult;
|
||||
hello?: Maybe<Scalars['String']>;
|
||||
integrations: IntegrationsResult;
|
||||
labels: LabelsResult;
|
||||
@ -1696,6 +1743,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 +2967,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;
|
||||
@ -3023,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>;
|
||||
@ -3090,6 +3157,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 +3399,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;
|
||||
@ -3421,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;
|
||||
@ -3475,6 +3550,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 +3906,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>;
|
||||
@ -4234,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>;
|
||||
@ -4417,6 +4521,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'>>;
|
||||
@ -4557,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>;
|
||||
@ -4616,6 +4722,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 +5458,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>;
|
||||
@ -5422,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>;
|
||||
@ -5468,6 +5591,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>;
|
||||
|
||||
@ -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!]!
|
||||
}
|
||||
@ -728,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!
|
||||
@ -948,6 +984,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!
|
||||
@ -1115,6 +1152,7 @@ type Query {
|
||||
getFollowers(userId: ID): GetFollowersResult!
|
||||
getFollowing(userId: ID): GetFollowingResult!
|
||||
getUserPersonalization: GetUserPersonalizationResult!
|
||||
groups: GroupsResult!
|
||||
hello: String
|
||||
integrations: IntegrationsResult!
|
||||
labels: LabelsResult!
|
||||
@ -1181,6 +1219,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!
|
||||
|
||||
@ -26,6 +26,7 @@ import {
|
||||
articleSavingRequestResolver,
|
||||
createArticleResolver,
|
||||
createArticleSavingRequestResolver,
|
||||
createGroupResolver,
|
||||
createHighlightResolver,
|
||||
createLabelResolver,
|
||||
createNewsletterEmailResolver,
|
||||
@ -54,6 +55,7 @@ import {
|
||||
getUserResolver,
|
||||
googleLoginResolver,
|
||||
googleSignupResolver,
|
||||
groupsResolver,
|
||||
integrationsResolver,
|
||||
labelsResolver,
|
||||
logOutResolver,
|
||||
@ -186,6 +188,7 @@ export const functionResolvers = {
|
||||
saveFilter: saveFilterResolver,
|
||||
deleteFilter: deleteFilterResolver,
|
||||
moveFilter: moveFilterResolver,
|
||||
createGroup: createGroupResolver,
|
||||
},
|
||||
Query: {
|
||||
me: getMeUserResolver,
|
||||
@ -216,6 +219,7 @@ export const functionResolvers = {
|
||||
rules: rulesResolver,
|
||||
deviceTokens: deviceTokensResolver,
|
||||
filters: filtersResolver,
|
||||
groups: groupsResolver,
|
||||
},
|
||||
User: {
|
||||
async sharedArticles(
|
||||
@ -634,4 +638,6 @@ export const functionResolvers = {
|
||||
...resultResolveTypeResolver('Filters'),
|
||||
...resultResolveTypeResolver('DeleteFilter'),
|
||||
...resultResolveTypeResolver('MoveFilter'),
|
||||
...resultResolveTypeResolver('CreateGroup'),
|
||||
...resultResolveTypeResolver('Groups'),
|
||||
}
|
||||
|
||||
@ -23,3 +23,4 @@ export * from './api_key'
|
||||
export * from './integrations'
|
||||
export * from './rules'
|
||||
export * from './filters'
|
||||
export * from './recommendations'
|
||||
|
||||
118
packages/api/src/resolvers/recommendations/index.ts
Normal file
118
packages/api/src/resolvers/recommendations/index.ts
Normal file
@ -0,0 +1,118 @@
|
||||
import {
|
||||
CreateGroupError,
|
||||
CreateGroupErrorCode,
|
||||
CreateGroupSuccess,
|
||||
GroupsError,
|
||||
GroupsErrorCode,
|
||||
GroupsSuccess,
|
||||
MutationCreateGroupArgs,
|
||||
} from '../../generated/graphql'
|
||||
import {
|
||||
createGroup,
|
||||
getInviteUrl,
|
||||
getRecommendationGroups,
|
||||
} from '../../services/create_group'
|
||||
import { authorized, userDataToUser } from '../../utils/helpers'
|
||||
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 = getInviteUrl(invite)
|
||||
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],
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
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],
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
@ -2137,6 +2137,52 @@ 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
|
||||
}
|
||||
|
||||
union GroupsResult = GroupsSuccess | GroupsError
|
||||
|
||||
type GroupsSuccess {
|
||||
groups: [RecommendationGroup!]!
|
||||
}
|
||||
|
||||
type GroupsError {
|
||||
errorCodes: [GroupsErrorCode!]!
|
||||
}
|
||||
|
||||
enum GroupsErrorCode {
|
||||
UNAUTHORIZED
|
||||
BAD_REQUEST
|
||||
}
|
||||
|
||||
# Mutations
|
||||
type Mutation {
|
||||
googleLogin(input: GoogleLoginInput!): LoginResult!
|
||||
@ -2214,6 +2260,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
|
||||
@ -2269,6 +2316,7 @@ const schema = gql`
|
||||
rules(enabled: Boolean): RulesResult!
|
||||
deviceTokens: DeviceTokensResult!
|
||||
filters: FiltersResult!
|
||||
groups: GroupsResult!
|
||||
}
|
||||
`
|
||||
|
||||
|
||||
@ -4,15 +4,28 @@ 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
|
||||
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) => {
|
||||
// Max number of groups a user can create
|
||||
const maxGroups = 3
|
||||
const groupCount = await getRepository(Group).countBy({
|
||||
createdBy: { id: input.admin.id },
|
||||
})
|
||||
if (groupCount >= maxGroups) {
|
||||
throw new Error('Max groups reached')
|
||||
}
|
||||
|
||||
const group = await t.getRepository(Group).save({
|
||||
name: input.name,
|
||||
createdBy: input.admin,
|
||||
@ -28,7 +41,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,9 +49,45 @@ export const createGroup = async (input: {
|
||||
user: input.admin,
|
||||
group,
|
||||
invite,
|
||||
isAdmin: true,
|
||||
})
|
||||
return [group, invite]
|
||||
}
|
||||
)
|
||||
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}`
|
||||
}
|
||||
|
||||
9
packages/db/migrations/0101.do.add_is_admin_to_group_membership.sql
Executable file
9
packages/db/migrations/0101.do.add_is_admin_to_group_membership.sql
Executable file
@ -0,0 +1,9 @@
|
||||
-- Type: DO
|
||||
-- Name: add_is_admin_to_group_membership
|
||||
-- Description: Add is_admin field to group_membership table
|
||||
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE omnivore.group_membership ADD COLUMN is_admin BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
|
||||
COMMIT;
|
||||
9
packages/db/migrations/0101.undo.add_is_admin_to_group_membership.sql
Executable file
9
packages/db/migrations/0101.undo.add_is_admin_to_group_membership.sql
Executable file
@ -0,0 +1,9 @@
|
||||
-- Type: UNDO
|
||||
-- Name: add_is_admin_to_group_membership
|
||||
-- Description: Add is_admin field to group_membership table
|
||||
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE omnivore.group_membership DROP COLUMN IF EXISTS is_admin;
|
||||
|
||||
COMMIT;
|
||||
Reference in New Issue
Block a user