|
@@ -138,6 +138,7 @@ final class PoliciesController
|
|
|
$id,
|
|
$id,
|
|
|
['name' => $name, 'include_manual_blocks' => $includeManualBlocks, 'threshold_count' => count($thresholds)],
|
|
['name' => $name, 'include_manual_blocks' => $includeManualBlocks, 'threshold_count' => count($thresholds)],
|
|
|
self::auditContext($request),
|
|
self::auditContext($request),
|
|
|
|
|
+ $name,
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
return self::json($response, 201, $created->toArray($this->slugByCategoryId()));
|
|
return self::json($response, 201, $created->toArray($this->slugByCategoryId()));
|
|
@@ -207,6 +208,16 @@ final class PoliciesController
|
|
|
return self::validationFailed($response, $errors);
|
|
return self::validationFailed($response, $errors);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ $slugByCategoryId = $this->slugByCategoryId();
|
|
|
|
|
+
|
|
|
|
|
+ $beforeSnapshot = [
|
|
|
|
|
+ 'name' => $existing->name,
|
|
|
|
|
+ 'description' => $existing->description,
|
|
|
|
|
+ 'include_manual_blocks' => $existing->includeManualBlocks ? 1 : 0,
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ $beforeThresholds = self::thresholdsBySlug($existing->thresholds, $slugByCategoryId);
|
|
|
|
|
+
|
|
|
if ($fields !== []) {
|
|
if ($fields !== []) {
|
|
|
$this->policies->update($id, $fields);
|
|
$this->policies->update($id, $fields);
|
|
|
}
|
|
}
|
|
@@ -219,19 +230,24 @@ final class PoliciesController
|
|
|
return self::error($response, 500, 'update_failed');
|
|
return self::error($response, 500, 'update_failed');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- $changed = array_keys($fields);
|
|
|
|
|
|
|
+ $changes = self::diffFields($beforeSnapshot, $fields);
|
|
|
if ($thresholds !== null) {
|
|
if ($thresholds !== null) {
|
|
|
- $changed[] = 'thresholds';
|
|
|
|
|
|
|
+ $afterThresholds = self::thresholdsBySlug($thresholds, $slugByCategoryId);
|
|
|
|
|
+ if ($beforeThresholds !== $afterThresholds) {
|
|
|
|
|
+ $changes['thresholds'] = ['from' => $beforeThresholds, 'to' => $afterThresholds];
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
$this->audit->emit(
|
|
$this->audit->emit(
|
|
|
AuditAction::POLICY_UPDATED,
|
|
AuditAction::POLICY_UPDATED,
|
|
|
'policy',
|
|
'policy',
|
|
|
$id,
|
|
$id,
|
|
|
- ['changed' => $changed],
|
|
|
|
|
|
|
+ ['name' => $existing->name, 'changes' => $changes],
|
|
|
self::auditContext($request),
|
|
self::auditContext($request),
|
|
|
|
|
+ $updated->name,
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
- return self::json($response, 200, $updated->toArray($this->slugByCategoryId()));
|
|
|
|
|
|
|
+ return self::json($response, 200, $updated->toArray($slugByCategoryId));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -265,6 +281,7 @@ final class PoliciesController
|
|
|
$id,
|
|
$id,
|
|
|
['name' => $existing->name],
|
|
['name' => $existing->name],
|
|
|
self::auditContext($request),
|
|
self::auditContext($request),
|
|
|
|
|
+ $existing->name,
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
return $response->withStatus(204);
|
|
return $response->withStatus(204);
|
|
@@ -404,6 +421,26 @@ final class PoliciesController
|
|
|
return $dt?->format('Y-m-d\TH:i:s\Z');
|
|
return $dt?->format('Y-m-d\TH:i:s\Z');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Convert `[category_id => threshold]` to `[category_slug => threshold]` for
|
|
|
|
|
+ * audit-log readability, sorted by slug so before/after diffs compare cleanly.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param array<int, float> $byId
|
|
|
|
|
+ * @param array<int, string> $slugByCategoryId
|
|
|
|
|
+ * @return array<string, float>
|
|
|
|
|
+ */
|
|
|
|
|
+ private static function thresholdsBySlug(array $byId, array $slugByCategoryId): array
|
|
|
|
|
+ {
|
|
|
|
|
+ $out = [];
|
|
|
|
|
+ foreach ($byId as $catId => $threshold) {
|
|
|
|
|
+ $slug = $slugByCategoryId[$catId] ?? ('category_' . $catId);
|
|
|
|
|
+ $out[$slug] = $threshold;
|
|
|
|
|
+ }
|
|
|
|
|
+ ksort($out);
|
|
|
|
|
+
|
|
|
|
|
+ return $out;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* Resolve a body-supplied `{slug: number}` map to `[category_id => float]`.
|
|
* Resolve a body-supplied `{slug: number}` map to `[category_id => float]`.
|
|
|
* Returns the integer-keyed map on success, or a single human-readable
|
|
* Returns the integer-keyed map on success, or a single human-readable
|