feat(fe2): invite + list workspace invites (#2629)
* list invites table * invites list works * update last reminded date on resend * fix FE * WIP invitedialog + updated debounced utility * invite create works * exclude users correctly * more adjustments * minor cleanup * using workspace invite server role * test fix * fixed multiple root eslint issues * minor adjustments
This commit is contained in:
committed by
GitHub
parent
03db1cca94
commit
4dae1569cd
@@ -9,7 +9,10 @@ import { ServerInviteResourceFilter } from '@/modules/serverinvites/repositories
|
||||
|
||||
export type FindUserByTarget = (target: string) => Promise<UserWithOptionalRole | null>
|
||||
|
||||
export type ServerInviteRecordInsertModel = Omit<ServerInviteRecord, 'createdAt'>
|
||||
export type ServerInviteRecordInsertModel = Omit<
|
||||
ServerInviteRecord,
|
||||
'createdAt' | 'updatedAt'
|
||||
>
|
||||
|
||||
export type InsertInviteAndDeleteOld = (
|
||||
invite: ServerInviteRecordInsertModel,
|
||||
@@ -57,7 +60,7 @@ export type QueryAllResourceInvites = <
|
||||
filter: Pick<
|
||||
InviteResourceTarget<TargetType, RoleType>,
|
||||
'resourceId' | 'resourceType'
|
||||
>
|
||||
> & { search?: string }
|
||||
) => Promise<ServerInviteRecord<InviteResourceTarget<TargetType, RoleType>>[]>
|
||||
|
||||
export type DeleteAllResourceInvites = <
|
||||
@@ -104,3 +107,5 @@ export type CreateInviteParams = {
|
||||
message?: string | null
|
||||
primaryResourceTarget: PrimaryInviteResourceTarget
|
||||
}
|
||||
|
||||
export type MarkInviteUpdated = (params: { inviteId: string }) => Promise<boolean>
|
||||
|
||||
@@ -54,6 +54,7 @@ export type ServerInviteRecord<
|
||||
target: string
|
||||
inviterId: string
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
message: Nullable<string>
|
||||
resource: PrimaryInviteResourceTarget<Resource>
|
||||
token: string
|
||||
|
||||
@@ -28,7 +28,8 @@ import {
|
||||
insertInviteAndDeleteOldFactory,
|
||||
deleteInviteFactory as deleteInviteFromDbFactory,
|
||||
queryAllUserResourceInvitesFactory,
|
||||
queryAllResourceInvitesFactory
|
||||
queryAllResourceInvitesFactory,
|
||||
markInviteUpdatedfactory
|
||||
} from '@/modules/serverinvites/repositories/serverInvites'
|
||||
import {
|
||||
createProjectInviteFactory,
|
||||
@@ -301,7 +302,8 @@ export = {
|
||||
getStream
|
||||
}),
|
||||
findUserByTarget: findUserByTargetFactory(),
|
||||
findInvite: findInviteFactory({ db })
|
||||
findInvite: findInviteFactory({ db }),
|
||||
markInviteUpdated: markInviteUpdatedfactory({ db })
|
||||
})
|
||||
|
||||
await resendInviteEmail({ inviteId })
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { Knex } from 'knex'
|
||||
|
||||
const TABLE_NAME = 'server_invites'
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
await knex.schema.alterTable(TABLE_NAME, (table) => {
|
||||
table
|
||||
.timestamp('updatedAt', { precision: 3, useTz: true })
|
||||
.defaultTo(knex.fn.now())
|
||||
.notNullable()
|
||||
})
|
||||
|
||||
// set updatedAt to be same value as createdAt
|
||||
await knex(TABLE_NAME).update({ updatedAt: knex.raw('??', ['createdAt']) })
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await knex.schema.alterTable(TABLE_NAME, (table) => {
|
||||
table.dropColumn('updatedAt')
|
||||
})
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { knex, ServerInvites, Streams } from '@/modules/core/dbSchema'
|
||||
import { knex, ServerInvites, Streams, Users } from '@/modules/core/dbSchema'
|
||||
import {
|
||||
getUserByEmail,
|
||||
getUser,
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
FindServerInvite,
|
||||
FindServerInvites,
|
||||
InsertInviteAndDeleteOld,
|
||||
MarkInviteUpdated,
|
||||
QueryAllResourceInvites,
|
||||
QueryAllUserResourceInvites,
|
||||
QueryInvites,
|
||||
@@ -91,7 +92,7 @@ const buildInvitesBaseQuery =
|
||||
|
||||
const q = db(ServerInvites.name)
|
||||
.select<Result>(ServerInvites.cols)
|
||||
.orderBy(ServerInvites.col.createdAt, sort)
|
||||
.orderBy(ServerInvites.col.updatedAt, sort)
|
||||
|
||||
// single built in filter
|
||||
projectInviteValidityFilter(q)
|
||||
@@ -241,13 +242,31 @@ export const queryAllResourceInvitesFactory =
|
||||
filter: Pick<
|
||||
InviteResourceTarget<TargetType, RoleType>,
|
||||
'resourceId' | 'resourceType'
|
||||
>
|
||||
> & { search?: string }
|
||||
) => {
|
||||
if (!filter.resourceId) return []
|
||||
|
||||
return await buildInvitesBaseQuery({ db })<
|
||||
const q = buildInvitesBaseQuery({ db })<
|
||||
ServerInviteRecord<InviteResourceTarget<TargetType, RoleType>>[]
|
||||
>({ filterQuery }).where((q) => filterByResource(q, filter))
|
||||
>({ filterQuery })
|
||||
|
||||
q.where((q) => filterByResource(q, filter))
|
||||
|
||||
if (filter.search) {
|
||||
q.leftJoin(
|
||||
Users.name,
|
||||
Users.col.id,
|
||||
knex.raw('SUBSTRING(?? FROM 2)', [ServerInvites.col.target])
|
||||
).where((w1) => {
|
||||
w1.where(ServerInvites.col.target, 'ILIKE', `%${filter.search}%`).orWhere(
|
||||
Users.col.name,
|
||||
'ILIKE',
|
||||
`%${filter.search}%`
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return await q
|
||||
}
|
||||
|
||||
export const deleteAllResourceInvitesFactory =
|
||||
@@ -481,3 +500,13 @@ export const findInviteByTokenFactory =
|
||||
|
||||
return (await q) || null
|
||||
}
|
||||
|
||||
export const markInviteUpdatedfactory =
|
||||
({ db }: { db: Knex }): MarkInviteUpdated =>
|
||||
async ({ inviteId }) => {
|
||||
const cols = ServerInvites.with({ withoutTablePrefix: true }).col
|
||||
const ret = await db(ServerInvites.name)
|
||||
.where(ServerInvites.col.id, inviteId)
|
||||
.update(cols.updatedAt, new Date())
|
||||
return !!ret
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
FindInvite,
|
||||
FindUserByTarget,
|
||||
InsertInviteAndDeleteOld,
|
||||
MarkInviteUpdated,
|
||||
ServerInviteRecordInsertModel
|
||||
} from '@/modules/serverinvites/domain/operations'
|
||||
import {
|
||||
@@ -188,11 +189,13 @@ export const resendInviteEmailFactory =
|
||||
({
|
||||
buildInviteEmailContents,
|
||||
findUserByTarget,
|
||||
findInvite
|
||||
findInvite,
|
||||
markInviteUpdated
|
||||
}: {
|
||||
buildInviteEmailContents: BuildInviteEmailContents
|
||||
findUserByTarget: FindUserByTarget
|
||||
findInvite: FindInvite
|
||||
markInviteUpdated: MarkInviteUpdated
|
||||
}): ResendInviteEmail =>
|
||||
async (params: { inviteId: string }) => {
|
||||
const sendInviteEmail = sendInviteEmailFactory({ buildInviteEmailContents })
|
||||
@@ -221,4 +224,6 @@ export const resendInviteEmailFactory =
|
||||
targetUser,
|
||||
targetData
|
||||
})
|
||||
|
||||
await markInviteUpdated({ inviteId })
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ import {
|
||||
UseStreamInviteDocument
|
||||
} from '@/test/graphql/generated/graphql'
|
||||
import { grantStreamPermissions } from '@/modules/core/repositories/streams'
|
||||
import { ServerInviteRecord } from '@/modules/serverinvites/domain/types'
|
||||
import { reduce } from 'lodash'
|
||||
|
||||
async function cleanup() {
|
||||
await truncateTables([ServerInvites.name, Streams.name, Users.name])
|
||||
@@ -503,6 +505,17 @@ describe('[Stream & Server Invites]', () => {
|
||||
)
|
||||
|
||||
const inviteIds = invites.map((i) => i.inviteId)
|
||||
const inviteLastRemindedDates = reduce(
|
||||
await ServerInvites.knex<ServerInviteRecord[]>().whereIn(
|
||||
ServerInvites.col.id,
|
||||
inviteIds
|
||||
),
|
||||
(res, item) => {
|
||||
res[item.id] = item.updatedAt
|
||||
return res
|
||||
},
|
||||
{} as Record<string, Date>
|
||||
)
|
||||
|
||||
const results = await Promise.all(
|
||||
inviteIds.map((inviteId) =>
|
||||
@@ -516,6 +529,22 @@ describe('[Stream & Server Invites]', () => {
|
||||
}
|
||||
|
||||
expect(sendEmailInvocations.length()).to.eq(inviteIds.length)
|
||||
|
||||
const newInviteLastRemindedDates = reduce(
|
||||
await ServerInvites.knex<ServerInviteRecord[]>().whereIn(
|
||||
ServerInvites.col.id,
|
||||
inviteIds
|
||||
),
|
||||
(res, item) => {
|
||||
res[item.id] = item.updatedAt
|
||||
return res
|
||||
},
|
||||
{} as Record<string, Date>
|
||||
)
|
||||
|
||||
for (const [id, newDate] of Object.entries(newInviteLastRemindedDates)) {
|
||||
expect(newDate).to.be.greaterThan(inviteLastRemindedDates[id])
|
||||
}
|
||||
})
|
||||
|
||||
it('they can delete pre-existing invites irregardless of type', async () => {
|
||||
|
||||
Reference in New Issue
Block a user