reduce api | version control
This commit is contained in:
@@ -5,10 +5,9 @@ export const API_BASE_URL =
|
||||
|
||||
export const API_ENDPOINTS = {
|
||||
geometries: `${API_BASE_URL}/geometries`,
|
||||
geometriesBatch: `${API_BASE_URL}/geometries/batch`,
|
||||
geometriesBatchCombined: `${API_BASE_URL}/geometries/batch/combined`,
|
||||
entities: `${API_BASE_URL}/entities`,
|
||||
entitiesBatch: `${API_BASE_URL}/entities/batch`,
|
||||
sections: `${API_BASE_URL}/sections`,
|
||||
submissions: `${API_BASE_URL}/submissions`,
|
||||
vectorTiles: `${API_BASE_URL}/tiles/{z}/{x}/{y}`,
|
||||
rasterTiles: `${API_BASE_URL}/raster-tiles/{z}/{x}/{y}`,
|
||||
vectorTilesMetadata: `${API_BASE_URL}/tiles/metadata/info`,
|
||||
|
||||
@@ -13,51 +13,6 @@ export type Entity = {
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
export type CreateEntityPayload = {
|
||||
name: string;
|
||||
slug?: string | null;
|
||||
description?: string | null;
|
||||
type_id?: string | null;
|
||||
status?: number | null;
|
||||
};
|
||||
|
||||
export type EntityBatchCreateChange =
|
||||
| ({
|
||||
action: "create";
|
||||
entity: CreateEntityPayload & { id?: string };
|
||||
})
|
||||
| ({
|
||||
action: "create";
|
||||
id?: string;
|
||||
} & CreateEntityPayload);
|
||||
|
||||
export type EntityBatchUpdateChange =
|
||||
| ({
|
||||
action: "update";
|
||||
id: string;
|
||||
entity: Partial<CreateEntityPayload> & { id?: string };
|
||||
})
|
||||
| ({
|
||||
action: "update";
|
||||
id: string;
|
||||
} & Partial<CreateEntityPayload>);
|
||||
|
||||
export type EntityBatchDeleteChange = {
|
||||
action: "delete";
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type EntityBatchChange =
|
||||
| EntityBatchCreateChange
|
||||
| EntityBatchUpdateChange
|
||||
| EntityBatchDeleteChange;
|
||||
|
||||
export type EntityBatchSaveResponse = {
|
||||
success: boolean;
|
||||
applied: number;
|
||||
created_entity_ids: string[];
|
||||
};
|
||||
|
||||
export async function fetchEntities(query?: { q?: string }): Promise<Entity[]> {
|
||||
const params = new URLSearchParams();
|
||||
if (query?.q) {
|
||||
@@ -82,27 +37,3 @@ export async function searchEntitiesByName(
|
||||
|
||||
return requestJson<Entity[]>(`${API_ENDPOINTS.entities}/search?${params.toString()}`);
|
||||
}
|
||||
|
||||
export async function createEntity(payload: CreateEntityPayload): Promise<Entity> {
|
||||
return requestJson<Entity>(API_ENDPOINTS.entities, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateEntity(id: string, payload: CreateEntityPayload): Promise<Entity> {
|
||||
return requestJson<Entity>(`${API_ENDPOINTS.entities}/${id}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}
|
||||
|
||||
export async function saveEntityBatchChanges(changes: EntityBatchChange[]): Promise<EntityBatchSaveResponse> {
|
||||
return requestJson<EntityBatchSaveResponse>(API_ENDPOINTS.entitiesBatch, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ changes }),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { API_ENDPOINTS } from "@/api/config";
|
||||
import { EntityBatchChange } from "@/api/entities";
|
||||
import { requestJson } from "@/api/http";
|
||||
import { Change, FeatureCollection, Geometry } from "@/lib/useEditorState";
|
||||
import { FeatureCollection } from "@/lib/useEditorState";
|
||||
|
||||
export type GeometriesBBoxQuery = {
|
||||
minLng: number;
|
||||
@@ -12,43 +11,6 @@ export type GeometriesBBoxQuery = {
|
||||
entity_id?: string;
|
||||
};
|
||||
|
||||
export type GeometryCreatePayload = {
|
||||
geometry: Geometry;
|
||||
type?: string | null;
|
||||
time_start?: number | null;
|
||||
time_end?: number | null;
|
||||
binding?: string[];
|
||||
entity_id?: string | null;
|
||||
entity_ids?: string[];
|
||||
};
|
||||
|
||||
export type GeometryUpdatePayload = {
|
||||
geometry: Geometry;
|
||||
type?: string | null;
|
||||
time_start?: number | null;
|
||||
time_end?: number | null;
|
||||
binding?: string[];
|
||||
entity_id?: string | null;
|
||||
entity_ids?: string[];
|
||||
};
|
||||
|
||||
export type GeometryCreateResponse = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type BatchSaveResponse = {
|
||||
success: boolean;
|
||||
applied: number;
|
||||
};
|
||||
|
||||
export type CombinedBatchSaveResponse = {
|
||||
success: boolean;
|
||||
applied: number;
|
||||
entity_applied: number;
|
||||
geometry_applied: number;
|
||||
created_entity_ids: string[];
|
||||
};
|
||||
|
||||
function buildBBoxQueryString(params: GeometriesBBoxQuery): string {
|
||||
const query = new URLSearchParams({
|
||||
minLng: String(params.minLng),
|
||||
@@ -72,47 +34,3 @@ export async function fetchGeometriesByBBox(params: GeometriesBBoxQuery): Promis
|
||||
const url = `${API_ENDPOINTS.geometries}?${buildBBoxQueryString(params)}`;
|
||||
return requestJson<FeatureCollection>(url);
|
||||
}
|
||||
|
||||
export async function saveGeometryBatchChanges(changes: Change[]): Promise<BatchSaveResponse> {
|
||||
return requestJson<BatchSaveResponse>(API_ENDPOINTS.geometriesBatch, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ changes }),
|
||||
});
|
||||
}
|
||||
|
||||
export async function saveCombinedGeometryEntityBatchChanges(
|
||||
entityChanges: EntityBatchChange[],
|
||||
geometryChanges: Change[]
|
||||
): Promise<CombinedBatchSaveResponse> {
|
||||
return requestJson<CombinedBatchSaveResponse>(API_ENDPOINTS.geometriesBatchCombined, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
entity_changes: entityChanges,
|
||||
geometry_changes: geometryChanges,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
export async function createGeometry(payload: GeometryCreatePayload): Promise<GeometryCreateResponse> {
|
||||
return requestJson<GeometryCreateResponse>(API_ENDPOINTS.geometries, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateGeometry(id: string | number, payload: GeometryUpdatePayload): Promise<{ success: boolean }> {
|
||||
return requestJson<{ success: boolean }>(`${API_ENDPOINTS.geometries}/${id}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteGeometry(id: string | number): Promise<{ success: boolean }> {
|
||||
return requestJson<{ success: boolean }>(`${API_ENDPOINTS.geometries}/${id}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,3 +20,11 @@ export async function requestJson<T>(input: RequestInfo | URL, init?: RequestIni
|
||||
|
||||
return (await res.json()) as T;
|
||||
}
|
||||
|
||||
export function jsonRequestInit(method: string, body: unknown): RequestInit {
|
||||
return {
|
||||
method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
};
|
||||
}
|
||||
|
||||
195
api/sections.ts
Normal file
195
api/sections.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
import { API_ENDPOINTS } from "@/api/config";
|
||||
import { jsonRequestInit, requestJson } from "@/api/http";
|
||||
|
||||
export type SectionState = {
|
||||
section_id?: string;
|
||||
status: "editing" | "submitted" | "approved" | "rejected";
|
||||
head_commit_id: string | null;
|
||||
version: number;
|
||||
locked_by: string | null;
|
||||
locked_at: string | null;
|
||||
lock_expires_at: string | null;
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
export type Section = {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string | null;
|
||||
user_id: string | null;
|
||||
created_by: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
state: Omit<SectionState, "section_id" | "updated_at">;
|
||||
};
|
||||
|
||||
export type SectionCommit = {
|
||||
id: string;
|
||||
section_id: string;
|
||||
parent_commit_id: string | null;
|
||||
commit_no: number;
|
||||
kind: "manual" | "restore";
|
||||
restored_from_commit_id: string | null;
|
||||
created_by: string;
|
||||
created_at: string;
|
||||
title: string | null;
|
||||
note: string | null;
|
||||
snapshot_hash: string | null;
|
||||
snapshot?: unknown;
|
||||
};
|
||||
|
||||
export type SectionSubmission = {
|
||||
id: string;
|
||||
section_id: string;
|
||||
commit_id: string;
|
||||
submitted_by: string;
|
||||
submitted_at: string;
|
||||
status: "pending" | "approved" | "rejected" | "conflicted";
|
||||
reviewed_by: string | null;
|
||||
reviewed_at: string | null;
|
||||
review_note: string | null;
|
||||
snapshot_hash: string | null;
|
||||
snapshot?: unknown;
|
||||
};
|
||||
|
||||
export type EditorLoadResponse = {
|
||||
section: Section;
|
||||
state: SectionState;
|
||||
commit: SectionCommit | null;
|
||||
snapshot: unknown;
|
||||
};
|
||||
|
||||
export type CreateSectionInput = {
|
||||
id?: string;
|
||||
title: string;
|
||||
description?: string | null;
|
||||
user_id?: string;
|
||||
created_by?: string;
|
||||
};
|
||||
|
||||
export type CreateCommitInput = {
|
||||
snapshot: unknown;
|
||||
created_by?: string;
|
||||
user_id?: string;
|
||||
expected_version?: number;
|
||||
expected_head_commit_id?: string | null;
|
||||
title?: string | null;
|
||||
note?: string | null;
|
||||
};
|
||||
|
||||
export async function fetchSections(): Promise<Section[]> {
|
||||
return requestJson<Section[]>(API_ENDPOINTS.sections);
|
||||
}
|
||||
|
||||
export async function createSection(input: CreateSectionInput): Promise<Section> {
|
||||
return requestJson<Section>(API_ENDPOINTS.sections, jsonRequestInit("POST", input));
|
||||
}
|
||||
|
||||
export async function openSectionEditor(sectionId: string, userId?: string): Promise<EditorLoadResponse> {
|
||||
const params = new URLSearchParams();
|
||||
if (userId) params.set("user_id", userId);
|
||||
return requestJson<EditorLoadResponse>(sectionUrl(sectionId, "editor", params));
|
||||
}
|
||||
|
||||
export async function lockSection(sectionId: string, userId: string): Promise<{ state: SectionState }> {
|
||||
return requestJson<{ state: SectionState }>(
|
||||
sectionUrl(sectionId, "lock"),
|
||||
jsonRequestInit("POST", { user_id: userId })
|
||||
);
|
||||
}
|
||||
|
||||
export async function unlockSection(sectionId: string, userId: string): Promise<{ success: boolean }> {
|
||||
return requestJson<{ success: boolean }>(
|
||||
sectionUrl(sectionId, "unlock"),
|
||||
jsonRequestInit("POST", { user_id: userId })
|
||||
);
|
||||
}
|
||||
|
||||
export async function createSectionCommit(
|
||||
sectionId: string,
|
||||
input: CreateCommitInput
|
||||
): Promise<{ commit: SectionCommit; state: SectionState }> {
|
||||
return requestJson<{ commit: SectionCommit; state: SectionState }>(
|
||||
sectionUrl(sectionId, "commits"),
|
||||
jsonRequestInit("POST", input)
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetchSectionCommits(
|
||||
sectionId: string,
|
||||
options?: { includeSnapshot?: boolean }
|
||||
): Promise<SectionCommit[]> {
|
||||
const params = new URLSearchParams();
|
||||
if (options?.includeSnapshot) params.set("include_snapshot", "1");
|
||||
return requestJson<SectionCommit[]>(sectionUrl(sectionId, "commits", params));
|
||||
}
|
||||
|
||||
export async function restoreSectionCommit(
|
||||
sectionId: string,
|
||||
input: {
|
||||
commit_id: string;
|
||||
created_by?: string;
|
||||
user_id?: string;
|
||||
expected_version?: number;
|
||||
expected_head_commit_id?: string | null;
|
||||
title?: string | null;
|
||||
note?: string | null;
|
||||
}
|
||||
): Promise<{ commit: SectionCommit; state: SectionState }> {
|
||||
return requestJson<{ commit: SectionCommit; state: SectionState }>(
|
||||
sectionUrl(sectionId, "restore"),
|
||||
jsonRequestInit("POST", input)
|
||||
);
|
||||
}
|
||||
|
||||
export async function submitSection(
|
||||
sectionId: string,
|
||||
input: { commit_id?: string; submitted_by?: string; user_id?: string }
|
||||
): Promise<SectionSubmission> {
|
||||
return requestJson<SectionSubmission>(sectionUrl(sectionId, "submit"), jsonRequestInit("POST", input));
|
||||
}
|
||||
|
||||
export async function fetchSectionSubmissions(
|
||||
sectionId: string,
|
||||
options?: { includeSnapshot?: boolean }
|
||||
): Promise<SectionSubmission[]> {
|
||||
const params = new URLSearchParams();
|
||||
if (options?.includeSnapshot) params.set("include_snapshot", "1");
|
||||
return requestJson<SectionSubmission[]>(sectionUrl(sectionId, "submissions", params));
|
||||
}
|
||||
|
||||
export async function approveSubmission(
|
||||
submissionId: string,
|
||||
input: { reviewed_by?: string; user_id?: string; review_note?: string | null }
|
||||
): Promise<SectionSubmission> {
|
||||
return requestJson<SectionSubmission>(
|
||||
submissionUrl(submissionId, "approve"),
|
||||
jsonRequestInit("POST", input)
|
||||
);
|
||||
}
|
||||
|
||||
export async function rejectSubmission(
|
||||
submissionId: string,
|
||||
input: { reviewed_by?: string; user_id?: string; review_note?: string | null }
|
||||
): Promise<SectionSubmission> {
|
||||
return requestJson<SectionSubmission>(
|
||||
submissionUrl(submissionId, "reject"),
|
||||
jsonRequestInit("POST", input)
|
||||
);
|
||||
}
|
||||
|
||||
function sectionUrl(sectionId: string, path?: string, params?: URLSearchParams): string {
|
||||
return appendQuery(
|
||||
`${API_ENDPOINTS.sections}/${encodeURIComponent(sectionId)}${path ? `/${path}` : ""}`,
|
||||
params
|
||||
);
|
||||
}
|
||||
|
||||
function submissionUrl(submissionId: string, path: "approve" | "reject"): string {
|
||||
return `${API_ENDPOINTS.submissions}/${encodeURIComponent(submissionId)}/${path}`;
|
||||
}
|
||||
|
||||
function appendQuery(url: string, params?: URLSearchParams): string {
|
||||
const suffix = params?.toString();
|
||||
return suffix ? `${url}?${suffix}` : url;
|
||||
}
|
||||
Reference in New Issue
Block a user