- Scope update/destroy queries by userId (Computers & Components), matching the pattern already used in index/show/edit - Add missing auth.authenticate() calls on destroy actions - handleComponents now requires userId and scopes component lookups to the current user, preventing cross-user component takeover - handleComponents rejects attaching more than one component of the same 'unique' type to a computer (new InvalidComponentSelectionException, 422) - handlePictures scopes picture lookups by computerId - destroy(Computer) now detaches its components (computerId = null) instead of losing them to cascade delete, and removes the computer's uploads dir - Switch store/update actions to request.validateUsing() for consistency - Fix relative redirects to absolute paths (/computers, /computers/:id) - Untrack .env.production and add it to .gitignore (secrets must not be committed); local file kept on disk
58 lines
2.2 KiB
TypeScript
58 lines
2.2 KiB
TypeScript
import Component from '#models/component'
|
|
import Computer from '#models/computer'
|
|
import InvalidComponentSelectionException from '#exceptions/invalid_component_selection_exception'
|
|
|
|
async function handleComponents(componentsId: number[], computer: Computer, userId: number) {
|
|
const currentComponents = await computer.related('components').query()
|
|
const currentComponentsIds = currentComponents.map((component) => component.id)
|
|
const componentsToAdd = componentsId.filter((id) => !currentComponentsIds.includes(id))
|
|
const componentsToRemove = currentComponentsIds.filter((id) => !componentsId.includes(id))
|
|
|
|
const componentsToAddRecords = await Promise.all(
|
|
componentsToAdd.map((id: number) =>
|
|
Component.query().where('userId', userId).andWhere('id', id).preload('type').firstOrFail()
|
|
)
|
|
)
|
|
|
|
// Ensure at most one component of each "unique" type ends up attached to
|
|
// the computer once the additions/removals are applied.
|
|
const remainingComponentIds = currentComponentsIds.filter(
|
|
(id) => !componentsToRemove.includes(id)
|
|
)
|
|
const remainingComponents = remainingComponentIds.length
|
|
? await Component.query().whereIn('id', remainingComponentIds).preload('type')
|
|
: []
|
|
const finalComponents = [...remainingComponents, ...componentsToAddRecords]
|
|
|
|
const uniqueTypeCounts = new Map<number, number>()
|
|
for (const component of finalComponents) {
|
|
if (!component.type.unique) continue
|
|
const count = (uniqueTypeCounts.get(component.type.id) ?? 0) + 1
|
|
uniqueTypeCounts.set(component.type.id, count)
|
|
if (count > 1) {
|
|
throw new InvalidComponentSelectionException(
|
|
`Only one component of type "${component.type.name}" can be attached to a computer at a time.`
|
|
)
|
|
}
|
|
}
|
|
|
|
await Promise.all(
|
|
componentsToAddRecords.map(async (component) => {
|
|
component.computerId = computer.id
|
|
await component.save()
|
|
})
|
|
)
|
|
await Promise.all(
|
|
componentsToRemove.map(async (id: number) => {
|
|
const component = await Component.query()
|
|
.where('userId', userId)
|
|
.andWhere('id', id)
|
|
.firstOrFail()
|
|
component.computerId = null
|
|
await component.save()
|
|
})
|
|
)
|
|
}
|
|
|
|
export { handleComponents }
|