feat: add PATCH /tags/{id}/rules/{then_id} to activate/deactivate rules

- openapi.yaml: new PATCH endpoint with is_active body, returns TagRule
- backend/service: SetRuleActive calls repo.SetActive then returns updated rule
- backend/handler: PatchRule validates body and delegates to service
- backend/router: register PATCH /:tag_id/rules/:then_tag_id
- frontend: TagRuleEditor uses PATCH instead of delete+recreate
- mock: handle PATCH /tags/{id}/rules/{then_id}

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-05 23:31:12 +03:00
parent 871250345a
commit 21f3acadf0
6 changed files with 97 additions and 6 deletions
@@ -62,13 +62,9 @@
busy = true;
error = '';
const thenTagId = rule.then_tag_id!;
const newActive = !rule.is_active;
try {
await api.delete(`/tags/${tagId}/rules/${thenTagId}`);
const updated = await api.post<TagRule>(`/tags/${tagId}/rules`, {
then_tag_id: thenTagId,
is_active: newActive,
apply_to_existing: false,
const updated = await api.patch<TagRule>(`/tags/${tagId}/rules/${thenTagId}`, {
is_active: !rule.is_active,
});
onRulesChange(rules.map((r) => r.then_tag_id === thenTagId ? updated : r));
} catch (e) {
+13
View File
@@ -402,6 +402,19 @@ export function mockApiPlugin(): Plugin {
return json(res, 201, { tag_id: tid, then_tag_id: thenId, then_tag_name: t?.name ?? null, is_active: isActive });
}
// PATCH /tags/{id}/rules/{then_id} — activate / deactivate
const tagRulesPatchMatch = path.match(/^\/tags\/([^/]+)\/rules\/([^/]+)$/);
if (method === 'PATCH' && tagRulesPatchMatch) {
const [, tid, thenId] = tagRulesPatchMatch;
const body = (await readBody(req)) as Record<string, unknown>;
const isActive = body.is_active as boolean;
const ruleMap = tagRules.get(tid);
if (!ruleMap?.has(thenId)) return json(res, 404, { code: 'not_found', message: 'Rule not found' });
ruleMap.set(thenId, isActive);
const t = MOCK_TAGS.find((x) => x.id === thenId);
return json(res, 200, { tag_id: tid, then_tag_id: thenId, then_tag_name: t?.name ?? null, is_active: isActive });
}
// DELETE /tags/{id}/rules/{then_id}
const tagRulesDelMatch = path.match(/^\/tags\/([^/]+)\/rules\/([^/]+)$/);
if (method === 'DELETE' && tagRulesDelMatch) {