mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-29 19:35:20 +02:00
feat(web): automations contracts, API client, atoms and hooks
Foundation for the v1 automations UI. Mirrors backend Pydantic schemas into Zod and wires the data layer end-to-end so feature surfaces can be built on top. contracts/types/automation.types.ts: - AutomationStatus, TriggerType, RunStatus enums. - AutomationDefinition envelope (PlanStep, TriggerSpec, Execution, Metadata, Inputs). - AutomationCreate/Update/Detail/Summary/List + listParams. - TriggerCreate/Update/Detail. - RunSummary/Detail/List + runListParams. lib/apis/automations-api.service.ts: - list/get/create/update/delete automations. - add/update/remove triggers (sub-resource). - list/get runs (read-only sub-resource). - safeParse on every write, 204-safe deletes. atoms/automations/: - automationsListAtom (active search space, first page). - 6 mutation atoms with toast + cache invalidation. hooks/: - use-automations.ts wraps the list atom. - use-automation.ts: parameterized detail by id. - use-automation-runs.ts: useAutomationRuns + useAutomationRun. lib/query-client/cache-keys.ts: automations namespace (list, detail, runs, run) keyed by (id, limit, offset) where relevant. Smoke: zod round-trip OK on backend-shape payloads (Automation, AutomationCreate, Trigger, Run); typecheck clean for new files; biome clean.
This commit is contained in:
parent
d48bb2033b
commit
b18a5fdca9
8 changed files with 548 additions and 0 deletions
102
surfsense_web/lib/apis/automations-api.service.ts
Normal file
102
surfsense_web/lib/apis/automations-api.service.ts
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
import {
|
||||
type AutomationCreateRequest,
|
||||
type AutomationListParams,
|
||||
type AutomationUpdateRequest,
|
||||
automation,
|
||||
automationCreateRequest,
|
||||
automationListResponse,
|
||||
automationUpdateRequest,
|
||||
type RunListParams,
|
||||
run,
|
||||
runListResponse,
|
||||
type TriggerCreateRequest,
|
||||
type TriggerUpdateRequest,
|
||||
trigger,
|
||||
triggerCreateRequest,
|
||||
triggerUpdateRequest,
|
||||
} from "@/contracts/types/automation.types";
|
||||
import { ValidationError } from "../error";
|
||||
import { baseApiService } from "./base-api.service";
|
||||
|
||||
const BASE = "/api/v1/automations";
|
||||
|
||||
function rejectIfInvalid<T>(
|
||||
parsed: { success: true; data: T } | { success: false; error: { issues: { message: string }[] } }
|
||||
): T {
|
||||
if (!parsed.success) {
|
||||
throw new ValidationError(
|
||||
`Invalid request: ${parsed.error.issues.map((i) => i.message).join(", ")}`
|
||||
);
|
||||
}
|
||||
return parsed.data;
|
||||
}
|
||||
|
||||
class AutomationsApiService {
|
||||
// ---- Automations ---------------------------------------------------------
|
||||
|
||||
listAutomations = async (params: AutomationListParams) => {
|
||||
const qs = new URLSearchParams({
|
||||
search_space_id: String(params.search_space_id),
|
||||
limit: String(params.limit),
|
||||
offset: String(params.offset),
|
||||
});
|
||||
return baseApiService.get(`${BASE}?${qs.toString()}`, automationListResponse);
|
||||
};
|
||||
|
||||
getAutomation = async (automationId: number) => {
|
||||
return baseApiService.get(`${BASE}/${automationId}`, automation);
|
||||
};
|
||||
|
||||
createAutomation = async (request: AutomationCreateRequest) => {
|
||||
const data = rejectIfInvalid(automationCreateRequest.safeParse(request));
|
||||
return baseApiService.post(BASE, automation, { body: data });
|
||||
};
|
||||
|
||||
updateAutomation = async (automationId: number, request: AutomationUpdateRequest) => {
|
||||
const data = rejectIfInvalid(automationUpdateRequest.safeParse(request));
|
||||
return baseApiService.patch(`${BASE}/${automationId}`, automation, { body: data });
|
||||
};
|
||||
|
||||
// Server returns 204; baseApiService now resolves to null and skips schema validation.
|
||||
deleteAutomation = async (automationId: number) => {
|
||||
return baseApiService.delete(`${BASE}/${automationId}`);
|
||||
};
|
||||
|
||||
// ---- Triggers (sub-resource) --------------------------------------------
|
||||
|
||||
addTrigger = async (automationId: number, request: TriggerCreateRequest) => {
|
||||
const data = rejectIfInvalid(triggerCreateRequest.safeParse(request));
|
||||
return baseApiService.post(`${BASE}/${automationId}/triggers`, trigger, { body: data });
|
||||
};
|
||||
|
||||
updateTrigger = async (
|
||||
automationId: number,
|
||||
triggerId: number,
|
||||
request: TriggerUpdateRequest
|
||||
) => {
|
||||
const data = rejectIfInvalid(triggerUpdateRequest.safeParse(request));
|
||||
return baseApiService.patch(`${BASE}/${automationId}/triggers/${triggerId}`, trigger, {
|
||||
body: data,
|
||||
});
|
||||
};
|
||||
|
||||
removeTrigger = async (automationId: number, triggerId: number) => {
|
||||
return baseApiService.delete(`${BASE}/${automationId}/triggers/${triggerId}`);
|
||||
};
|
||||
|
||||
// ---- Runs (sub-resource, read-only) -------------------------------------
|
||||
|
||||
listRuns = async (automationId: number, params: RunListParams) => {
|
||||
const qs = new URLSearchParams({
|
||||
limit: String(params.limit),
|
||||
offset: String(params.offset),
|
||||
});
|
||||
return baseApiService.get(`${BASE}/${automationId}/runs?${qs.toString()}`, runListResponse);
|
||||
};
|
||||
|
||||
getRun = async (automationId: number, runId: number) => {
|
||||
return baseApiService.get(`${BASE}/${automationId}/runs/${runId}`, run);
|
||||
};
|
||||
}
|
||||
|
||||
export const automationsApiService = new AutomationsApiService();
|
||||
Loading…
Add table
Add a link
Reference in a new issue