Merge pull request #3189 from specklesystems/fabians/core-ioc-24

chore(server): core IoC 24 - batchMoveCommitsFactory
This commit is contained in:
Alessandro Magionami
2024-10-08 09:55:45 +02:00
committed by GitHub
5 changed files with 210 additions and 138 deletions
@@ -1,3 +1,4 @@
import { Branch } from '@/modules/core/domain/branches/types'
import {
BranchCommit,
CommitWithStreamBranchMetadata,
@@ -5,8 +6,10 @@ import {
CommitBranch
} from '@/modules/core/domain/commits/types'
import {
CommitsMoveInput,
CommitUpdateInput,
ModelVersionsFilter,
MoveVersionsInput,
UpdateVersionInput
} from '@/modules/core/graph/generated/graphql'
import { BranchCommitRecord, StreamCommitRecord } from '@/modules/core/helpers/types'
@@ -208,3 +211,13 @@ export type GetPaginatedBranchCommits = (
items: Commit[]
cursor: string | null
}>
export type MoveCommitsToBranch = (
commitIds: string[],
branchId: string
) => Promise<number | undefined>
export type ValidateAndBatchMoveCommits = (
params: CommitsMoveInput | MoveVersionsInput,
userId: string
) => Promise<Branch>
@@ -29,8 +29,8 @@ const {
getRateLimitResult
} = require('@/modules/core/services/ratelimiter')
const {
batchMoveCommits,
batchDeleteCommits
batchDeleteCommits,
batchMoveCommitsFactory
} = require('@/modules/core/services/commit/batchCommitActions')
const {
validateStreamAccess
@@ -50,23 +50,28 @@ const {
updateCommitFactory,
getSpecificBranchCommitsFactory,
getPaginatedBranchCommitsItemsFactory,
getBranchCommitsTotalCountFactory
getBranchCommitsTotalCountFactory,
getCommitsFactory,
moveCommitsToBranchFactory
} = require('@/modules/core/repositories/commits')
const { db } = require('@/db/knex')
const {
markCommitStreamUpdated,
getStream,
getCommitStream
getCommitStream,
getStreams
} = require('@/modules/core/repositories/streams')
const {
markCommitBranchUpdatedFactory,
getBranchByIdFactory,
getStreamBranchByNameFactory
getStreamBranchByNameFactory,
createBranchFactory
} = require('@/modules/core/repositories/branches')
const {
addCommitDeletedActivity,
addCommitCreatedActivity,
addCommitUpdatedActivity
addCommitUpdatedActivity,
addCommitMovedActivity
} = require('@/modules/activitystream/services/commitActivity')
const { getObject } = require('@/modules/core/repositories/objects')
const { VersionsEmitter } = require('@/modules/core/events/versionsEmitter')
@@ -121,6 +126,15 @@ const getPaginatedBranchCommits = getPaginatedBranchCommitsFactory({
getBranchCommitsTotalCount: getBranchCommitsTotalCountFactory({ db })
})
const batchMoveCommits = batchMoveCommitsFactory({
getCommits: getCommitsFactory({ db }),
getStreams,
getStreamBranchByName: getStreamBranchByNameFactory({ db }),
createBranch: createBranchFactory({ db }),
moveCommitsToBranch: moveCommitsToBranchFactory({ db }),
addCommitMovedActivity
})
/**
* @param {boolean} publicOnly
* @param {string} userId
@@ -8,7 +8,7 @@ import {
import { getServerOrigin } from '@/modules/shared/helpers/envHelper'
import {
batchDeleteCommits,
batchMoveCommits
batchMoveCommitsFactory
} from '@/modules/core/services/commit/batchCommitActions'
import { CommitUpdateError } from '@/modules/core/errors/commit'
import {
@@ -25,14 +25,17 @@ import {
createCommitFactory,
getCommitBranchFactory,
getCommitFactory,
getCommitsFactory,
insertBranchCommitsFactory,
insertStreamCommitsFactory,
moveCommitsToBranchFactory,
switchCommitBranchFactory,
updateCommitFactory
} from '@/modules/core/repositories/commits'
import { db } from '@/db/knex'
import { getObject } from '@/modules/core/repositories/objects'
import {
createBranchFactory,
getBranchByIdFactory,
getStreamBranchByNameFactory,
markCommitBranchUpdatedFactory
@@ -40,11 +43,13 @@ import {
import {
getCommitStream,
getStream,
getStreams,
markCommitStreamUpdated
} from '@/modules/core/repositories/streams'
import { VersionsEmitter } from '@/modules/core/events/versionsEmitter'
import {
addCommitCreatedActivity,
addCommitMovedActivity,
addCommitUpdatedActivity
} from '@/modules/activitystream/services/commitActivity'
@@ -73,6 +78,15 @@ const updateCommitAndNotify = updateCommitAndNotifyFactory({
markCommitBranchUpdated: markCommitBranchUpdatedFactory({ db })
})
const batchMoveCommits = batchMoveCommitsFactory({
getCommits: getCommitsFactory({ db }),
getStreams,
getStreamBranchByName: getStreamBranchByNameFactory({ db }),
createBranch: createBranchFactory({ db }),
moveCommitsToBranch: moveCommitsToBranchFactory({ db }),
addCommitMovedActivity
})
export = {
Project: {
async version(parent, args, ctx) {
@@ -48,7 +48,8 @@ import {
PaginatedBranchCommitsBaseParams,
PaginatedBranchCommitsParams,
GetPaginatedBranchCommitsItems,
GetBranchCommitsTotalCount
GetBranchCommitsTotalCount,
MoveCommitsToBranch
} from '@/modules/core/domain/commits/operations'
const tables = {
@@ -107,25 +108,30 @@ export const getCommitFactory =
* same stream etc. THIS DOESN'T DO ANY VALIDATION!
* @returns The amount of commits that were moved
*/
export async function moveCommitsToBranch(commitIds: string[], branchId: string) {
if (!commitIds?.length) return
export const moveCommitsToBranchFactory =
(deps: { db: Knex }): MoveCommitsToBranch =>
async (commitIds: string[], branchId: string) => {
if (!commitIds?.length) return
// delete old branch commits
await BranchCommits.knex().whereIn(BranchCommits.col.commitId, commitIds).del()
// delete old branch commits
await tables
.branchCommits(deps.db)
.whereIn(BranchCommits.col.commitId, commitIds)
.del()
// insert new ones
const inserts = await BranchCommits.knex().insert(
commitIds.map(
(cId): BranchCommitRecord => ({
branchId,
commitId: cId
})
),
'*'
)
// insert new ones
const inserts = await tables.branchCommits(deps.db).insert(
commitIds.map(
(cId): BranchCommitRecord => ({
branchId,
commitId: cId
})
),
'*'
)
return inserts.length
}
return inserts.length
}
export const deleteCommitsFactory =
(deps: { db: Knex }): DeleteCommits =>
@@ -3,6 +3,15 @@ import {
addCommitDeletedActivity,
addCommitMovedActivity
} from '@/modules/activitystream/services/commitActivity'
import {
GetStreamBranchByName,
StoreBranch
} from '@/modules/core/domain/branches/operations'
import {
GetCommits,
MoveCommitsToBranch,
ValidateAndBatchMoveCommits
} from '@/modules/core/domain/commits/operations'
import {
CommitInvalidAccessError,
CommitBatchUpdateError
@@ -14,14 +23,9 @@ import {
MoveVersionsInput
} from '@/modules/core/graph/generated/graphql'
import { Roles } from '@/modules/core/helpers/mainConstants'
import {
createBranchFactory,
getStreamBranchByNameFactory
} from '@/modules/core/repositories/branches'
import {
deleteCommitsFactory,
getCommitsFactory,
moveCommitsToBranch
getCommitsFactory
} from '@/modules/core/repositories/commits'
import { getStreams } from '@/modules/core/repositories/streams'
import { ensureError } from '@/modules/shared/helpers/errorHelper'
@@ -32,94 +36,107 @@ type CommitBatchInput = OldBatchInput | MoveVersionsInput | DeleteVersionsInput
const isOldBatchInput = (i: CommitBatchInput): i is OldBatchInput => has(i, 'commitIds')
type ValidateBatchBaseRulesDeps = {
getCommits: GetCommits
getStreams: typeof getStreams
}
/**
* Do base validation that's going to be appropriate for all batch actions and return
* the DB entities that were tested
*/
async function validateBatchBaseRules(params: CommitBatchInput, userId: string) {
const commitIds = isOldBatchInput(params) ? params.commitIds : params.versionIds
const validateBatchBaseRulesFactory =
(deps: ValidateBatchBaseRulesDeps) =>
async (params: CommitBatchInput, userId: string) => {
const commitIds = isOldBatchInput(params) ? params.commitIds : params.versionIds
if (!userId) {
throw new CommitInvalidAccessError(
'User must be authenticate to operate with commits'
)
}
if (!commitIds?.length) {
throw new CommitBatchUpdateError('No commits specified')
}
const commits = await getCommitsFactory({ db })(commitIds)
const foundCommitIds = commits.map((c) => c.id)
if (
commitIds.length !== foundCommitIds.length ||
difference(commitIds, foundCommitIds).length > 0
) {
throw new CommitBatchUpdateError('At least one of the commits does not exist')
}
const streamGroups = groupBy(commits, (c) => c.streamId)
const streamIds = Object.keys(streamGroups)
const streams = await getStreams(streamIds, { userId })
if (
streamIds.length !== streams.length ||
difference(
streamIds,
streams.map((s) => s.id)
).length > 0
) {
throw new CommitBatchUpdateError("At least one commit stream wasn't found")
}
const streamsById = keyBy(streams, (s) => s.id)
const commitsWithStreams = commits.map((c) => ({
commit: c,
stream: streamsById[c.streamId]
}))
for (const { commit, stream } of commitsWithStreams) {
if (stream.role !== Roles.Stream.Owner && commit.author !== userId) {
if (!userId) {
throw new CommitInvalidAccessError(
'To operate on these commits you must either own them or their streams'
'User must be authenticate to operate with commits'
)
}
if (!commitIds?.length) {
throw new CommitBatchUpdateError('No commits specified')
}
const commits = await deps.getCommits(commitIds)
const foundCommitIds = commits.map((c) => c.id)
if (
commitIds.length !== foundCommitIds.length ||
difference(commitIds, foundCommitIds).length > 0
) {
throw new CommitBatchUpdateError('At least one of the commits does not exist')
}
const streamGroups = groupBy(commits, (c) => c.streamId)
const streamIds = Object.keys(streamGroups)
const streams = await deps.getStreams(streamIds, { userId })
if (
streamIds.length !== streams.length ||
difference(
streamIds,
streams.map((s) => s.id)
).length > 0
) {
throw new CommitBatchUpdateError("At least one commit stream wasn't found")
}
const streamsById = keyBy(streams, (s) => s.id)
const commitsWithStreams = commits.map((c) => ({
commit: c,
stream: streamsById[c.streamId]
}))
for (const { commit, stream } of commitsWithStreams) {
if (stream.role !== Roles.Stream.Owner && commit.author !== userId) {
throw new CommitInvalidAccessError(
'To operate on these commits you must either own them or their streams'
)
}
}
return { commitsWithStreams, commits, streams }
}
return { commitsWithStreams, commits, streams }
type ValidateCommitsMoveDeps = ValidateBatchBaseRulesDeps & {
getStreamBranchByName: GetStreamBranchByName
}
/**
* Validate batch move params
*/
async function validateCommitsMove(
params: CommitsMoveInput | MoveVersionsInput,
userId: string
) {
const targetBranch = isOldBatchInput(params)
? params.targetBranch
: params.targetModelName
const { streams, commitsWithStreams } = await validateBatchBaseRules(params, userId)
if (streams.length > 1) {
throw new CommitBatchUpdateError('Commits belong to different streams')
}
const stream = streams[0]
const branch = await getStreamBranchByNameFactory({ db })(stream.id, targetBranch)
if (
!branch &&
!(<string[]>[Roles.Stream.Contributor, Roles.Stream.Owner]).includes(
stream.role || ''
const validateCommitsMoveFactory =
(deps: ValidateCommitsMoveDeps) =>
async (params: CommitsMoveInput | MoveVersionsInput, userId: string) => {
const targetBranch = isOldBatchInput(params)
? params.targetBranch
: params.targetModelName
const { streams, commitsWithStreams } = await validateBatchBaseRulesFactory(deps)(
params,
userId
)
) {
throw new CommitBatchUpdateError(
'Non-existant target branch referenced and active user does not have the rights to create a new one'
)
}
return { stream, branch, commitsWithStreams }
}
if (streams.length > 1) {
throw new CommitBatchUpdateError('Commits belong to different streams')
}
const stream = streams[0]
const branch = await deps.getStreamBranchByName(stream.id, targetBranch)
if (
!branch &&
!(<string[]>[Roles.Stream.Contributor, Roles.Stream.Owner]).includes(
stream.role || ''
)
) {
throw new CommitBatchUpdateError(
'Non-existant target branch referenced and active user does not have the rights to create a new one'
)
}
return { stream, branch, commitsWithStreams }
}
/**
* Validate batch delete params
@@ -128,54 +145,62 @@ async function validateCommitsDelete(
params: CommitsDeleteInput | DeleteVersionsInput,
userId: string
) {
const validateBatchBaseRules = validateBatchBaseRulesFactory({
getCommits: getCommitsFactory({ db }),
getStreams
})
return await validateBatchBaseRules(params, userId)
}
/**
* Move a batch of commits belonging to the same stream to another branch
*/
export async function batchMoveCommits(
params: CommitsMoveInput | MoveVersionsInput,
userId: string
) {
const { commitIds, targetBranch } = isOldBatchInput(params)
? params
: { commitIds: params.versionIds, targetBranch: params.targetModelName }
export const batchMoveCommitsFactory =
(
deps: ValidateCommitsMoveDeps & {
createBranch: StoreBranch
moveCommitsToBranch: MoveCommitsToBranch
addCommitMovedActivity: typeof addCommitMovedActivity
}
): ValidateAndBatchMoveCommits =>
async (params: CommitsMoveInput | MoveVersionsInput, userId: string) => {
const { commitIds, targetBranch } = isOldBatchInput(params)
? params
: { commitIds: params.versionIds, targetBranch: params.targetModelName }
const { branch, stream, commitsWithStreams } = await validateCommitsMove(
params,
userId
)
const { branch, stream, commitsWithStreams } = await validateCommitsMoveFactory(
deps
)(params, userId)
try {
const finalBranch =
branch ||
(await createBranchFactory({ db })({
name: targetBranch,
streamId: stream.id,
authorId: userId,
description: null
}))
await moveCommitsToBranch(commitIds, finalBranch.id)
await Promise.all(
commitsWithStreams.map(({ commit, stream }) =>
addCommitMovedActivity({
commitId: commit.id,
try {
const finalBranch =
branch ||
(await deps.createBranch({
name: targetBranch,
streamId: stream.id,
userId,
commit,
originalBranchId: commit.branchId,
newBranchId: finalBranch.id
})
authorId: userId,
description: null
}))
await deps.moveCommitsToBranch(commitIds, finalBranch.id)
await Promise.all(
commitsWithStreams.map(({ commit, stream }) =>
deps.addCommitMovedActivity({
commitId: commit.id,
streamId: stream.id,
userId,
commit,
originalBranchId: commit.branchId,
newBranchId: finalBranch.id
})
)
)
)
return finalBranch
} catch (e) {
const err = ensureError(e)
throw new CommitBatchUpdateError('Batch commit move failed', { cause: err })
return finalBranch
} catch (e) {
const err = ensureError(e)
throw new CommitBatchUpdateError('Batch commit move failed', { cause: err })
}
}
}
/**
* Delete a batch of commits