Merge pull request #1537 from omnivore-app/group-rules

group rules
This commit is contained in:
Hongbo Wu 2022-12-13 09:54:20 +08:00 committed by GitHub
commit 4247835dc5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 171 additions and 22 deletions

View file

@ -187,14 +187,23 @@ const appendIdsFilter = (body: SearchBody, ids: string[]): void => {
}
const appendRecommendedBy = (body: SearchBody, recommendedBy: string): void => {
const query =
recommendedBy === '*'
? {
exists: {
field: 'recommendations',
},
}
: {
term: {
'recommendations.name': recommendedBy,
},
}
body.query.bool.must.push({
nested: {
path: 'recommendations',
query: {
term: {
'recommendations.name': recommendedBy,
},
},
query,
},
})
}

View file

@ -1,4 +1,9 @@
import { Page, PageContext, Recommendation } from './types'
import {
ArticleSavingRequestStatus,
Page,
PageContext,
Recommendation,
} from './types'
import { createPage, getPageByParam, updatePage } from './pages'
export const addRecommendation = async (
@ -66,7 +71,14 @@ export const addRecommendation = async (
sharedAt: new Date(),
highlights,
readAt: undefined,
labels: [],
labels: undefined,
subscription: undefined,
unsubHttpUrl: undefined,
unsubMailTo: undefined,
_id: undefined,
archivedAt: undefined,
state: ArticleSavingRequestStatus.Succeeded,
taskName: undefined,
}
return createPage(newPage, ctx)

View file

@ -55,7 +55,10 @@ export interface SearchBody {
nested: {
path: 'recommendations'
query: {
term: {
exists?: {
field: string
}
term?: {
'recommendations.name': string
}
}

View file

@ -32,4 +32,16 @@ export class Group {
@OneToMany(() => GroupMembership, (groupMembership) => groupMembership.group)
members!: GroupMembership[]
@Column('text', { nullable: true })
description?: string | null
@Column('text', { nullable: true })
topics?: string | null
@Column('boolean', { default: false })
onlyAdminCanPost!: boolean
@Column('boolean', { default: false })
onlyAdminCanSeeMembers!: boolean
}

View file

@ -287,9 +287,13 @@ export enum CreateGroupErrorCode {
}
export type CreateGroupInput = {
description?: InputMaybe<Scalars['String']>;
expiresInDays?: InputMaybe<Scalars['Int']>;
maxMembers?: InputMaybe<Scalars['Int']>;
name: Scalars['String'];
onlyAdminCanPost?: InputMaybe<Scalars['Boolean']>;
onlyAdminCanSeeMembers?: InputMaybe<Scalars['Boolean']>;
topics?: InputMaybe<Array<Scalars['String']>>;
};
export type CreateGroupResult = CreateGroupError | CreateGroupSuccess;
@ -1866,11 +1870,15 @@ export type Recommendation = {
export type RecommendationGroup = {
__typename?: 'RecommendationGroup';
admins: Array<User>;
canPost: Scalars['Boolean'];
canSeeMembers: Scalars['Boolean'];
createdAt: Scalars['Date'];
description?: Maybe<Scalars['String']>;
id: Scalars['ID'];
inviteUrl: Scalars['String'];
members: Array<User>;
name: Scalars['String'];
topics?: Maybe<Array<Scalars['String']>>;
updatedAt: Scalars['Date'];
};
@ -4959,11 +4967,15 @@ export type RecommendationResolvers<ContextType = ResolverContext, ParentType ex
export type RecommendationGroupResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['RecommendationGroup'] = ResolversParentTypes['RecommendationGroup']> = {
admins?: Resolver<Array<ResolversTypes['User']>, ParentType, ContextType>;
canPost?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
canSeeMembers?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
createdAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
description?: Resolver<Maybe<ResolversTypes['String']>, 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>;
topics?: Resolver<Maybe<Array<ResolversTypes['String']>>, ParentType, ContextType>;
updatedAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};

View file

@ -243,9 +243,13 @@ enum CreateGroupErrorCode {
}
input CreateGroupInput {
description: String
expiresInDays: Int
maxMembers: Int
name: String!
onlyAdminCanPost: Boolean
onlyAdminCanSeeMembers: Boolean
topics: [String!]
}
union CreateGroupResult = CreateGroupError | CreateGroupSuccess
@ -1312,11 +1316,15 @@ type Recommendation {
type RecommendationGroup {
admins: [User!]!
canPost: Boolean!
canSeeMembers: Boolean!
createdAt: Date!
description: String
id: ID!
inviteUrl: String!
members: [User!]!
name: String!
topics: [String!]
updatedAt: Date!
}

View file

@ -39,6 +39,7 @@ import { In } from 'typeorm'
import { getPageByParam } from '../../elastic/pages'
import { enqueueRecommendation } from '../../utils/createTask'
import { env } from '../../env'
import { analytics } from '../../utils/analytics'
export const createGroupResolver = authorized<
CreateGroupSuccess,
@ -70,6 +71,20 @@ export const createGroupResolver = authorized<
name: input.name,
maxMembers: input.maxMembers,
expiresInDays: input.expiresInDays,
description: input.description,
topics: input.topics,
onlyAdminCanPost: input.onlyAdminCanPost,
onlyAdminCanSeeMembers: input.onlyAdminCanSeeMembers,
})
analytics.track({
userId: uid,
event: 'group_created',
properties: {
group_id: group.id,
group_name: group.name,
group_invite_code: invite.code,
},
})
await createLabelAndRuleForGroup(uid, group.name)
@ -83,6 +98,10 @@ export const createGroupResolver = authorized<
inviteUrl,
admins: [user],
members: [user],
canSeeMembers: true,
canPost: true,
description: group.description,
topics: group.topics?.split(','),
},
}
} catch (error) {
@ -264,6 +283,15 @@ export const joinGroupResolver = authorized<
const group = await joinGroup(user, inviteCode)
analytics.track({
userId: uid,
event: 'group_joined',
properties: {
group_id: group.id,
group_name: group.name,
},
})
await createLabelAndRuleForGroup(user.id, group.name)
return {
@ -403,6 +431,14 @@ export const leaveGroupResolver = authorized<
const success = await leaveGroup(user, groupId)
analytics.track({
userId: uid,
event: 'group_left',
properties: {
group_id: groupId,
},
})
return {
success,
}

View file

@ -2158,6 +2158,10 @@ const schema = gql`
name: String! @sanitize(maxLength: 140)
maxMembers: Int
expiresInDays: Int
description: String
topics: [String!]
onlyAdminCanPost: Boolean
onlyAdminCanSeeMembers: Boolean
}
union CreateGroupResult = CreateGroupSuccess | CreateGroupError
@ -2174,6 +2178,10 @@ const schema = gql`
members: [User!]!
createdAt: Date!
updatedAt: Date!
canPost: Boolean!
description: String
topics: [String!]
canSeeMembers: Boolean!
}
type CreateGroupError {

View file

@ -17,6 +17,10 @@ export const createGroup = async (input: {
name: string
maxMembers?: number | null
expiresInDays?: number | null
description?: string | null
topics?: string[] | null
onlyAdminCanPost?: boolean | null
onlyAdminCanSeeMembers?: boolean | null
}): Promise<[Group, Invite]> => {
const [group, invite] = await AppDataSource.transaction<[Group, Invite]>(
async (t) => {
@ -32,6 +36,10 @@ export const createGroup = async (input: {
const group = await t.getRepository(Group).save({
name: input.name,
createdBy: input.admin,
description: input.description,
topics: input.topics?.join(','),
onlyAdminCanPost: input.onlyAdminCanPost ?? false,
onlyAdminCanSeeMembers: input.onlyAdminCanSeeMembers ?? false,
})
const code = nanoid(8)
@ -71,13 +79,17 @@ export const getRecommendationGroups = async (
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)
})
const canSeeMembers = gm.group.onlyAdminCanSeeMembers ? gm.isAdmin : true
if (canSeeMembers) {
// Return all members
gm.group.members.forEach((m) => {
const user = userDataToUser(m.user)
if (m.isAdmin) {
admins.push(user)
}
members.push(user)
})
}
return {
id: gm.group.id,
@ -87,6 +99,10 @@ export const getRecommendationGroups = async (
inviteUrl: getInviteUrl(gm.invite),
admins,
members,
topics: gm.group.topics?.split(','),
description: gm.group.description,
canPost: gm.group.onlyAdminCanPost ? gm.isAdmin : true,
canSeeMembers,
}
})
}
@ -135,19 +151,26 @@ having count(*) < $4`,
})
const admins: GraphqlUser[] = []
const members: GraphqlUser[] = []
group.members.forEach((m) => {
const user = userDataToUser(m.user)
if (m.isAdmin) {
admins.push(user)
}
members.push(user)
})
if (!group.onlyAdminCanSeeMembers) {
// Return all members
group.members.forEach((m) => {
const user = userDataToUser(m.user)
if (m.isAdmin) {
admins.push(user)
}
members.push(user)
})
}
return {
...group,
inviteUrl: getInviteUrl(invite),
admins,
members,
topics: group.topics?.split(','),
description: group.description,
canPost: !group.onlyAdminCanPost,
canSeeMembers: !group.onlyAdminCanSeeMembers,
}
}

View file

@ -0,0 +1,13 @@
-- Type: DO
-- Name: add_description_to_group
-- Description: Add description, topics, only_admin_can_post, only_admin_can_see_members to group table
BEGIN;
ALTER TABLE omnivore."group"
ADD COLUMN description text,
ADD COLUMN topics text,
ADD COLUMN only_admin_can_post boolean NOT NULL DEFAULT false,
ADD COLUMN only_admin_can_see_members boolean NOT NULL DEFAULT false;
COMMIT;

View file

@ -0,0 +1,13 @@
-- Type: UNDO
-- Name: add_description_to_group
-- Description: Add description, topics, only_admin_can_post, only_admin_can_see_members to group table
BEGIN;
ALTER TABLE omnivore."group"
DROP COLUMN IF EXISTS description,
DROP COLUMN IF EXISTS topics,
DROP COLUMN IF EXISTS only_admin_can_post,
DROP COLUMN IF EXISTS only_admin_can_see_members;
COMMIT;