Docs: Final formatting and cleanup of V3 Frontend Guide
This commit is contained in:
@@ -6,23 +6,23 @@ This guide explains how to update or create frontend functions to interact with
|
||||
|
||||
## 1. Key Differences (V2 vs V3)
|
||||
|
||||
| Feature | CRUD V2 (Legacy) | CRUD V3 (Modern) |
|
||||
| --- | --- | --- |
|
||||
| **Base Prefix** | `/v2/crud` | `/v3/crud` |
|
||||
| **List Suffix** | Uses `/list` | **No suffix** (e.g., `/v3/crud/journal/`) |
|
||||
| **Nested Path** | Not supported in URL | **Supported** (e.g., `/v3/crud/journal/{id}/journal_entry/`) |
|
||||
| **View Selection**| `tbl_alt`, `mdl_alt` | **`view` parameter** (e.g., `?view=enriched`) |
|
||||
| **Complex Search**| Limited to GET `jp` | **POST `/search`** (Unlimited size + Hybrid params) |
|
||||
| **Full-Text Search**| Manual column names | **Reserved `q` property** in SearchQuery |
|
||||
| Feature | CRUD V2 (Legacy) | CRUD V3 (Modern) |
|
||||
| ------------------ | --------------------- | ---------------------------------------------------- |
|
||||
| **Base Prefix** | `/v2/crud` | `/v3/crud` |
|
||||
| **List Suffix** | Uses `/list` | **No suffix** (e.g., `/v3/crud/journal/`) |
|
||||
| **Nested Path** | Not supported in URL | **Supported** (e.g., `/v3/crud/journal/{id}/entry/`) |
|
||||
| **View Selection** | `tbl_alt`, `mdl_alt` | **`view` parameter** (e.g., `?view=enriched`) |
|
||||
| **Complex Search** | Limited to GET `jp` | **POST `/search`** (Unlimited size + Hybrid params) |
|
||||
| **Full-Text** | Manual column names | **Reserved `q` property** in SearchQuery |
|
||||
|
||||
---
|
||||
|
||||
## 2. Authentication and Security (Mandatory in V3)
|
||||
## 2. Authentication and Security (Mandatory)
|
||||
|
||||
As of January 2026, the V3 architecture enforces strict **Multi-Tenant Isolation** and **Machine Authorization**. Most requests now require two levels of validation.
|
||||
As of January 2026, the V3 architecture enforces strict **Multi-Tenant Isolation** and **Machine Authorization**. Most requests require two levels of validation.
|
||||
|
||||
### A. The "Entry Ticket" (API Key)
|
||||
**Mandatory for all requests.** To prevent unauthorized clients from using the API, every request must provide a valid `x-aether-api-key` in the header. This identifies the application or script (e.g., the Svelte frontend, the Flask legacy app, or an AI agent).
|
||||
**Mandatory for all requests.** Every request must provide a valid `x-aether-api-key` in the header. This identifies the application or script.
|
||||
|
||||
* **Header:** `x-aether-api-key: <your_app_key>`
|
||||
* **Status Code:** `403 Forbidden` if missing, invalid, or expired.
|
||||
@@ -30,27 +30,20 @@ As of January 2026, the V3 architecture enforces strict **Multi-Tenant Isolation
|
||||
### B. The "Visa" (Account Context)
|
||||
Once the API Key is validated, you must specify the context of your request.
|
||||
|
||||
1. **Standard User Access**: Provide the `x-account-id` (the random string ID). This restricts all results to that specific tenant.
|
||||
1. **Standard User Access**: Provide the `x-account-id` (the random string ID).
|
||||
* **Header:** `x-account-id: <account_id_random>`
|
||||
2. **Administrative Bypass**: For authorized scripts needing global access, use the bypass header.
|
||||
2. **Administrative Bypass**: For authorized scripts needing global access.
|
||||
* **Header:** `x-no-account-id: bypass`
|
||||
3. **Authentication Requirement**: Most V3 CRUD endpoints also require a standard Bearer token in the `Authorization` header for user-level actions.
|
||||
3. **Authentication Requirement**: Standard `Authorization: Bearer <jwt>` is still required for user-level actions.
|
||||
|
||||
### C. The "Bootstrap Paradox" Exception (`site_domain`)
|
||||
There is one critical exception to strict authentication: **`site_domain` search**.
|
||||
The **`site_domain` search** endpoint allows unauthenticated (**guest**) access so the frontend can resolve site configuration *before* login.
|
||||
|
||||
Because the frontend needs to lookup the site configuration (to know which account it's on) *before* it has an Account ID or User token, the following endpoint allows unauthenticated (**guest**) access:
|
||||
|
||||
**Endpoint:** `POST /v3/crud/site_domain/search`
|
||||
|
||||
This is the only V3 search allowed without an Account ID or JWT. However, it **still requires a valid API Key**.
|
||||
* **Endpoint:** `POST /v3/crud/site_domain/search`
|
||||
* **Note:** Still requires a valid API Key.
|
||||
|
||||
### D. Fail-Fast on Auth Failures (401/403)
|
||||
To prevent unnecessary server load and looping, the frontend implements a **Fail-Fast** policy for authentication and authorization errors.
|
||||
|
||||
* **401 Unauthorized**: Means the token is missing, invalid, or expired.
|
||||
* **403 Forbidden**: Means the token is valid, but you do not have permission to access the specific resource (or you attempted a cross-tenant request without bypass).
|
||||
* **Protocol**: If the API returns a 401 or 403, the frontend **must not retry** the request automatically. It should stop immediately and, if applicable, redirect the user to the login page or display a "Permission Denied" message.
|
||||
The frontend implements a **Fail-Fast** policy for 401/403 errors. If the API returns these codes, the frontend **must not retry** automatically. It should stop and redirect the user or display an error.
|
||||
|
||||
---
|
||||
|
||||
@@ -69,7 +62,7 @@ export async function get_ae_obj_v3({ api_cfg, obj_type, obj_id, view = 'default
|
||||
```
|
||||
|
||||
### B. Advanced & Hybrid Search (POST)
|
||||
The `/search` endpoint combines the power of complex logical bodies with the simplicity of query parameters.
|
||||
The `/search` endpoint combines complex logical bodies with simple query parameters.
|
||||
|
||||
```ts
|
||||
export async function search_ae_obj_v3({
|
||||
@@ -77,30 +70,19 @@ export async function search_ae_obj_v3({
|
||||
obj_type,
|
||||
search_query, // { q: "search term", and: [...] }
|
||||
enabled = 'enabled',
|
||||
view = 'default',
|
||||
for_obj_type,
|
||||
for_obj_id
|
||||
view = 'default'
|
||||
}) {
|
||||
const endpoint = `/v3/crud/${obj_type}/search`;
|
||||
|
||||
// Standard filters can be passed as query params
|
||||
const params: any = { enabled, view };
|
||||
if (for_obj_type) params.for_obj_type = for_obj_type;
|
||||
if (for_obj_id) params.for_obj_id = for_obj_id;
|
||||
const params = { enabled, view };
|
||||
|
||||
return await post_object({
|
||||
api_cfg,
|
||||
endpoint,
|
||||
params,
|
||||
data: search_query
|
||||
});
|
||||
return await post_object({ api_cfg, endpoint, params, data: search_query });
|
||||
}
|
||||
```
|
||||
|
||||
### C. Standardized Global Search (`q`)
|
||||
Use the `q` property in your search body for a keyword search.
|
||||
- **Wildcard Support**: Setting `q: "%"` will bypass all text filtering and return all records (respecting only logical filters like `enable`).
|
||||
- **Fallback**: If the table lacks a `default_qry_str` column, the API automatically performs a `LIKE` search across all searchable fields.
|
||||
- **Wildcard**: `q: "%"` returns all records (respecting logical filters).
|
||||
- **Fallback**: Performs a `LIKE` search across all searchable fields if no index exists.
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -111,12 +93,10 @@ Use the `q` property in your search body for a keyword search.
|
||||
|
||||
---
|
||||
|
||||
## 4. Create, Update, & Delete (POST, PATCH, DELETE)
|
||||
|
||||
V3 supports both top-level operations and nested parent/child operations.
|
||||
## 4. Create, Update, & Delete
|
||||
|
||||
### A. Create (POST)
|
||||
When creating objects, V3 strictly validates the incoming JSON against the `mdl_in` Pydantic model.
|
||||
V3 validates incoming JSON against the `mdl_in` Pydantic model.
|
||||
|
||||
```ts
|
||||
// POST /v3/crud/{obj_type}/
|
||||
@@ -124,17 +104,10 @@ export async function create_ae_obj_v3({ api_cfg, obj_type, data }) {
|
||||
const endpoint = `/v3/crud/${obj_type}/`;
|
||||
return await post_object({ api_cfg, endpoint, data });
|
||||
}
|
||||
|
||||
// POST /v3/crud/{parent_obj_type}/{parent_obj_id}/{child_obj_type}/
|
||||
// Note: Parent ID is automatically injected into the child record.
|
||||
export async function create_nested_obj_v3({ api_cfg, parent_type, parent_id, child_type, data }) {
|
||||
const endpoint = `/v3/crud/${parent_type}/${parent_id}/${child_type}/`;
|
||||
return await post_object({ api_cfg, endpoint, data });
|
||||
}
|
||||
```
|
||||
|
||||
### B. Update (PATCH)
|
||||
V3 uses `PATCH` for partial updates. Only the fields provided in the body will be modified in the database.
|
||||
V3 uses `PATCH` for partial updates. Only provided fields are modified.
|
||||
|
||||
```ts
|
||||
// PATCH /v3/crud/{obj_type}/{obj_id}
|
||||
@@ -145,202 +118,79 @@ export async function update_ae_obj_v3({ api_cfg, obj_type, obj_id, data }) {
|
||||
```
|
||||
|
||||
### C. Delete (DELETE)
|
||||
The `DELETE` method is used for removal. The backend may implement soft-delete (hide/disable) depending on the configuration.
|
||||
Supports soft-delete (`method=hide`) or hard-delete (`method=delete`).
|
||||
|
||||
```ts
|
||||
// DELETE /v3/crud/{obj_type}/{obj_id}
|
||||
export async function delete_ae_obj_v3({ api_cfg, obj_type, obj_id }) {
|
||||
// DELETE /v3/crud/{obj_type}/{obj_id}?method=hide
|
||||
export async function delete_ae_obj_v3({ api_cfg, obj_type, obj_id, method = 'delete' }) {
|
||||
const endpoint = `/v3/crud/${obj_type}/${obj_id}`;
|
||||
return await delete_object({ api_cfg, endpoint });
|
||||
return await delete_object({ api_cfg, endpoint, params: { method } });
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Specialized & Context Endpoints
|
||||
## 5. Specialized Endpoints
|
||||
|
||||
### A. Context Resolution (FQDN)
|
||||
Used during initial load to find the `account_id` associated with the domain.
|
||||
|
||||
**Legacy Method:** `GET /crud/site/domain/{fqdn}?use_alt_table=true&use_alt_base=true`
|
||||
**Modern V3 Method (Preferred):** `POST /v3/crud/site_domain/search` with `{ "q": "your-domain.com" }`
|
||||
|
||||
### B. Schema Discovery
|
||||
### A. Schema Discovery
|
||||
**Path**: `GET /v3/crud/{obj_type}/schema`
|
||||
Returns DB column definitions and Pydantic field rules for dynamic UI builders.
|
||||
|
||||
Used for developer tools or dynamic UI builders to understand the structure of an AE object.
|
||||
|
||||
```ts
|
||||
// GET /v3/crud/account/schema
|
||||
export async function get_obj_schema_v3({ api_cfg, obj_type }) {
|
||||
const endpoint = `/v3/crud/${obj_type}/schema`;
|
||||
return await get_object({ api_cfg, endpoint });
|
||||
}
|
||||
```
|
||||
### B. Secure File Downloads (JWT in URL)
|
||||
For browsers downloading files, you can pass the JWT directly in the query string.
|
||||
- `GET /v3/crud/hosted_file/{id}/?jwt={token}`
|
||||
|
||||
---
|
||||
|
||||
## 6. Secure File Downloads (URL Parameter)
|
||||
For `hosted_file` and `event_file`, browsers often need to download files without complex header modifications. In these cases, you can pass the JWT directly in the URL.
|
||||
|
||||
```ts
|
||||
// Example: Creating a secure download link for a browser
|
||||
// GET /v3/crud/hosted_file/{id}/?jwt={token}
|
||||
const downloadUrl = `${BASE_URL}/v3/crud/hosted_file/${fileId}/?jwt=${jwtToken}`;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
## 7. The "ID Vision" Standard (2026 Refactor)
|
||||
|
||||
|
||||
|
||||
V3 has moved to a **String-Only ID Vision**. In this paradigm, the frontend (SvelteKit) and external agents NEVER handle or store internal database integers.
|
||||
|
||||
## 6. The "ID Vision" Standard (2026)
|
||||
|
||||
V3 uses a **String-Only ID Vision**. The frontend and external agents NEVER handle or store internal database integers.
|
||||
|
||||
### A. Automatic ID Mapping (Output)
|
||||
|
||||
All V3 endpoints now automatically rename internal `id_random` fields to clean, short names in the JSON response.
|
||||
|
||||
- **`id`**: The unique random identifier for the primary record.
|
||||
|
||||
- **`account_id`**: The unique random identifier for the parent account.
|
||||
|
||||
- **`site_id`, `person_id`, etc**: All related IDs follow this clean naming.
|
||||
|
||||
|
||||
|
||||
**Example Response:**
|
||||
|
||||
```json
|
||||
|
||||
{
|
||||
|
||||
"id": "OGQK-02-04-94",
|
||||
|
||||
"name": "Scott's Journal",
|
||||
|
||||
"account_id": "nqOzejLCDXM"
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
Internal `id_random` fields are renamed to clean, short names in the JSON response.
|
||||
- **`id`**: Unique random identifier for the record.
|
||||
- **`account_id`**: Unique random identifier for the parent account.
|
||||
|
||||
### B. Intelligent Mapping (Input/Search)
|
||||
|
||||
The backend is now "Vision Aware." You can send string IDs back to the API using the same clean names.
|
||||
|
||||
- **Search Filters**: You can filter by `account_id` using the random string value. The API handles the mapping to the DB integer automatically.
|
||||
|
||||
- **Create/Update**: You can send `account_id: "nqOzejLCDXM"` in a payload. The `sanitize_payload` logic resolves this to the correct integer before saving.
|
||||
|
||||
|
||||
|
||||
**Preferred Filter Pattern:**
|
||||
|
||||
```json
|
||||
|
||||
{
|
||||
|
||||
"and": [{ "field": "account_id", "op": "eq", "value": "nqOzejLCDXM" }]
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
You can send string IDs back to the API using the same clean names.
|
||||
- **Search**: Filter by `account_id` using the random string value.
|
||||
- **Save**: Send `account_id: "nqOzejLCDXM"` in a payload; the API resolves it to an integer.
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
## 8. Standard Patterns & Common Pitfalls (2026 Update)
|
||||
## 7. Standard Patterns & Pitfalls
|
||||
|
||||
### A. Permissive Update Mode (Available)
|
||||
|
||||
**Usage:** High-performance state syncing.
|
||||
|
||||
To simplify frontend state management, V3 supports a **Permissive Update Mode**. When enabled, the backend will **ignore** extra fields in your `PATCH` or `POST` payload (like `_processed_at`, `created_on`, or UI-specific keys) instead of returning a `400 Bad Request`.
|
||||
|
||||
To simplify state management, V3 supports a **Permissive Update Mode** which ignores unknown extra fields in the payload.
|
||||
- **Header:** `x-ae-ignore-extra-fields: true`
|
||||
- **Use when:** Sending back objects that contain read-only technical metadata.
|
||||
|
||||
**When to use:**
|
||||
- When sending back an object fetched via `GET` that contains read-only technical metadata.
|
||||
- When the frontend adds temporary "state" keys to objects that shouldn't be saved to the DB.
|
||||
|
||||
---
|
||||
|
||||
### B. Handle 'NULL' vs '0' for Booleans
|
||||
|
||||
|
||||
|
||||
**Pitfall:** Fields like `hide` or `priority` can be `NULL` in the database.
|
||||
|
||||
|
||||
|
||||
**Standard:** Ensure your synchronization logic in components handles `null`, `undefined`, and `0` identically for boolean checks to prevent "ghost" changes being detected by Svelte 5.
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
### C. Structured Error Handling (Rich Bubbling)
|
||||
|
||||
|
||||
|
||||
V3 provides machine-readable error objects in the `meta.details` field for most failures (400/500). Instead of parsing strings, your error handler should check the `category` and `code`.
|
||||
|
||||
|
||||
### B. Structured Error Handling (Rich Bubbling)
|
||||
V3 returns machine-readable error objects in `meta.details` for failures (400/500).
|
||||
|
||||
**Example Error Response:**
|
||||
|
||||
```json
|
||||
|
||||
{
|
||||
|
||||
"meta": {
|
||||
|
||||
"success": false,
|
||||
|
||||
"status_code": 400,
|
||||
|
||||
"status_message": "Failed to update object.",
|
||||
|
||||
"details": {
|
||||
|
||||
"category": "database_schema",
|
||||
|
||||
"code": 1054,
|
||||
|
||||
"message": "Unknown column 'unauthorized_field' in 'SET'",
|
||||
|
||||
"message": "Unknown column 'notes' in 'SET'",
|
||||
"recoverable": false
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
**Common Categories:**
|
||||
- **`database_duplicate`**: Non-unique value (Code 1062).
|
||||
- **`database_constraint`**: Foreign key violation (Codes 1451, 1452).
|
||||
- **`database_schema`**: Invalid column name (Codes 1054, 1146).
|
||||
- **`database_connection`**: Temporary DB issues (`recoverable: true`).
|
||||
- **`validation`**: Pydantic validation failed.
|
||||
|
||||
- **`database_duplicate`**: Attempted to create a record with a non-unique value (Code 1062).
|
||||
|
||||
- **`database_constraint`**: Foreign key or integrity violation (Codes 1451, 1452).
|
||||
|
||||
- **`database_schema`**: Invalid column name or table configuration (Codes 1054, 1146).
|
||||
|
||||
- **`database_connection`**: Temporary DB unavailability (`recoverable: true`).
|
||||
|
||||
- **`validation`**: Pydantic validation failed (Check `details` for field-specific errors).
|
||||
### C. Booleans: 'NULL' vs '0'
|
||||
**Pitfall:** Fields like `hide` can be `NULL` in the DB.
|
||||
**Standard:** Handle `null`, `undefined`, and `0` identically for boolean checks.
|
||||
|
||||
Reference in New Issue
Block a user