Files
speckle-server/packages/server/modules/accessrequests/services/stream.ts
T
Kristaps Fabians Geikins 0b2ca9a515 feat: add missing FE1 fields to the FE2 GQL schema (#2471)
* WIP version create

* commitCreate migrated

* minor cleanup

* commitReceived migrated

* added Project.object

* Project.comment introduced

* moving away old API usages in FE1

* ProjectMutations.batchDelete

* project pending access requests

* WIP project access req tests

* project access req tests done

* ModelByName test

* version mutation tests

* project.object tests

* batch delete tests

* minor improvements to redirect logging
2024-07-10 11:33:53 +02:00

186 lines
5.0 KiB
TypeScript

import {
AccessRequestCreationError,
AccessRequestProcessingError
} from '@/modules/accessrequests/errors'
import { AccessRequestsEmitter } from '@/modules/accessrequests/events/emitter'
import { StreamAccessRequestGraphQLReturn } from '@/modules/accessrequests/helpers/graphTypes'
import {
AccessRequestType,
createNewRequest,
deleteRequestById,
generateId,
getPendingAccessRequest,
getPendingAccessRequests,
getUsersPendingAccessRequest,
ServerAccessRequestRecord,
StreamAccessRequestRecord
} from '@/modules/accessrequests/repositories'
import { StreamInvalidAccessError } from '@/modules/core/errors/stream'
import { TokenResourceIdentifier } from '@/modules/core/domain/tokens/types'
import { Roles, StreamRoles } from '@/modules/core/helpers/mainConstants'
import { getStream } from '@/modules/core/repositories/streams'
import {
addOrUpdateStreamCollaborator,
validateStreamAccess
} from '@/modules/core/services/streams/streamAccessService'
import { ensureError } from '@/modules/shared/helpers/errorHelper'
import { MaybeNullOrUndefined, Nullable } from '@/modules/shared/helpers/typeHelper'
function buildStreamAccessRequestGraphQLReturn(
record: ServerAccessRequestRecord<AccessRequestType.Stream, string>
): StreamAccessRequestGraphQLReturn {
return {
id: record.id,
requesterId: record.requesterId,
streamId: record.resourceId,
createdAt: record.createdAt
}
}
export async function getUserProjectAccessRequest(
userId: string,
projectId: string
): Promise<Nullable<StreamAccessRequestRecord>> {
const req = await getUsersPendingAccessRequest(
userId,
AccessRequestType.Stream,
projectId
)
return req || null
}
export async function getUserStreamAccessRequest(
userId: string,
streamId: string
): Promise<Nullable<StreamAccessRequestGraphQLReturn>> {
const req = await getUserProjectAccessRequest(userId, streamId)
if (!req) return null
return buildStreamAccessRequestGraphQLReturn(req)
}
/**
* Create new project access request
*/
export async function requestProjectAccess(userId: string, projectId: string) {
const [stream, existingRequest] = await Promise.all([
getStream({ userId, streamId: projectId }),
getUserStreamAccessRequest(userId, projectId)
])
if (existingRequest) {
throw new AccessRequestCreationError(
'User already has a pending access request for this resource'
)
}
if (!stream) {
throw new AccessRequestCreationError(
"Can't request access to a non-existant resource"
)
}
if (stream.role) {
throw new AccessRequestCreationError(
'User already has access to the specified resource'
)
}
const req = await createNewRequest<AccessRequestType.Stream, string>({
id: generateId(),
requesterId: userId,
resourceType: AccessRequestType.Stream,
resourceId: projectId
})
await AccessRequestsEmitter.emit(AccessRequestsEmitter.events.Created, {
request: req
})
return req
}
/**
* Create new stream access request
*/
export async function requestStreamAccess(userId: string, streamId: string) {
const req = await requestProjectAccess(userId, streamId)
return buildStreamAccessRequestGraphQLReturn(req)
}
/**
* Get pending project access requests
*/
export async function getPendingProjectRequests(
projectId: string
): Promise<StreamAccessRequestRecord[]> {
return await getPendingAccessRequests(AccessRequestType.Stream, projectId)
}
/**
* Get pending stream access requests
*/
export async function getPendingStreamRequests(
streamId: string
): Promise<StreamAccessRequestGraphQLReturn[]> {
const reqs = await getPendingProjectRequests(streamId)
return reqs.map(buildStreamAccessRequestGraphQLReturn)
}
/**
* Accept or decline a pending access request
*/
export async function processPendingStreamRequest(
userId: string,
requestId: string,
accept: boolean,
role: StreamRoles = Roles.Stream.Contributor,
resourceAccessRules: MaybeNullOrUndefined<TokenResourceIdentifier[]>
) {
const req = await getPendingAccessRequest(requestId, AccessRequestType.Stream)
if (!req) {
throw new AccessRequestProcessingError('No request with this ID exists')
}
try {
await validateStreamAccess(
userId,
req.resourceId,
Roles.Stream.Owner,
resourceAccessRules
)
} catch (e: unknown) {
const err = ensureError(e, 'Stream access validation failed')
if (err instanceof StreamInvalidAccessError) {
throw new AccessRequestProcessingError(
'You must own the stream to process access requests',
{ cause: err }
)
} else {
throw err
}
}
if (accept) {
await addOrUpdateStreamCollaborator(
req.resourceId,
req.requesterId,
role,
userId,
resourceAccessRules
)
}
await deleteRequestById(req.id)
await AccessRequestsEmitter.emit(AccessRequestsEmitter.events.Finalized, {
request: req,
approved: accept ? { role } : undefined,
finalizedBy: userId
})
return req
}
export const processPendingProjectRequest = processPendingStreamRequest