Security hardening, multi-site portability, and a documentation and API-surface sweep

This commit is contained in:
2026-08-14 16:28:34 -04:00
parent 72f8da0e58
commit 265ca2eb01
76 changed files with 3684 additions and 811 deletions

View File

@@ -768,14 +768,23 @@ def create_asset_relationship():
'An asset cannot be related to itself'
)
# Check for duplicate relationship
# Check for duplicate relationship.
#
# Deleting a relationship is SOFT (isactive = False, see
# delete_asset_relationship). The row therefore survives, and this lookup
# found it - so deleting a link and adding it back answered 409 "this
# relationship already exists" about a link the page no longer shows, with
# no way forward from the UI at all. The row cannot simply be inserted
# again either: assetrelationships is unique on (source, target, type).
#
# Reactivating IS the create, for an inactive row.
existing = AssetRelationship.query.filter_by(
sourceassetid=source_id,
targetassetid=target_id,
relationshiptypeid=type_id
).first()
if existing:
if existing and existing.isactive:
return error_response(
ErrorCodes.CONFLICT,
'This relationship already exists',
@@ -798,7 +807,11 @@ def create_asset_relationship():
targetassetid=source_id,
relationshiptypeid=type_id
).first()
if inverse is not None:
# isactive, for the same reason as the duplicate check above: a soft
# deleted inverse is a link somebody already removed, and blocking on it
# made "remove the existing one first" - the instruction in this very
# message - fail to unblock anything.
if inverse is not None and inverse.isactive:
return error_response(
ErrorCodes.CONFLICT,
f"The reverse of this relationship already exists "
@@ -808,14 +821,20 @@ def create_asset_relationship():
http_code=409
)
rel = AssetRelationship(
sourceassetid=source_id,
targetassetid=target_id,
relationshiptypeid=type_id,
notes=data.get('notes')
)
db.session.add(rel)
if existing is not None:
# The soft-deleted row from the duplicate check. Reuse it: the unique
# index on (source, target, type) means there is no second row to add.
rel = existing
rel.isactive = True
rel.notes = data.get('notes')
else:
rel = AssetRelationship(
sourceassetid=source_id,
targetassetid=target_id,
relationshiptypeid=type_id,
notes=data.get('notes')
)
db.session.add(rel)
apply_import_timestamps(rel, data)
db.session.flush()