Saving updated guide

This commit is contained in:
Scott Idem
2026-03-16 16:52:10 -04:00
parent cf6aa1301b
commit 8f88e043b3

View File

@@ -70,77 +70,7 @@ Modify data in the system.
---
## 4. Flat vs Nested URL Paths
This is the single most common source of mock errors in Playwright tests. The V3 API has two URL structures and the right one to use depends on the object type and the operation.
### Always-Flat Objects
These object types use flat paths for **every** operation — GET, list, search, create, update, delete. They are never children in a nested URL.
**Core modules:**
`account`, `activity_log`, `address`, `contact`, `hosted_file`, `organization`, `page`, `person`, `site`, `user`
**Other modules:**
`archive`, `event`, `journal`, `post`
All operations for these objects use the flat helpers:
| Operation | Function | URL pattern |
|---|---|---|
| GET single | `api.get_ae_obj_v3` | `/v3/crud/{obj_type}/{id}` |
| GET list | `api.get_ae_obj_li_v3` | `/v3/crud/{obj_type}/?for_obj_id=...` |
| Search | `api.search_ae_obj_v3` | `/v3/crud/{obj_type}/search` |
| Create | `api.create_ae_obj_v3` | `/v3/crud/{obj_type}/` |
| Update | `api.patch_ae_obj_v3` | `/v3/crud/{obj_type}/{id}` |
| Delete | `api.delete_ae_obj_v3` | `/v3/crud/{obj_type}/{id}` |
### Nested Objects (Event Sub-objects)
Objects that belong to a parent (e.g. `event_badge`, `event_session`, `event_badge_template`, `event_file`, `event_presenter`, `event_presentation`, `event_device`, `event_location`) use **nested paths for mutations** but **flat paths for reads**.
| Operation | Function | URL pattern |
|---|---|---|
| GET single | `api.get_ae_obj_v3` | `/v3/crud/{obj_type}/{id}`**flat** |
| GET list | `api.get_ae_obj_li_v3` | `/v3/crud/{obj_type}/?for_obj_id=...`**flat** |
| Search | `api.search_ae_obj_v3` | `/v3/crud/{obj_type}/search`**flat** |
| Create | `api.create_nested_obj_v3` | `/v3/crud/{parent_type}/{parent_id}/{child_type}/`**nested** |
| Update | `api.update_nested_obj_v3` | `/v3/crud/{parent_type}/{parent_id}/{child_type}/{child_id}`**nested** |
| Delete | `api.delete_ae_obj_v3` | `/v3/crud/{obj_type}/{id}`**flat** |
**Example — `event_badge` under event `abc123`:**
```
GET /v3/crud/event_badge/badge-xyz ← flat (get_ae_obj_v3)
GET /v3/crud/event_badge/?for_obj_id=abc123 ← flat (get_ae_obj_li_v3)
POST /v3/crud/event_badge/search ← flat (search_ae_obj_v3)
POST /v3/crud/event/abc123/event_badge/ ← nested (create_nested_obj_v3)
PATCH /v3/crud/event/abc123/event_badge/badge-xyz ← nested (update_nested_obj_v3)
DELETE /v3/crud/event_badge/badge-xyz ← flat (delete_ae_obj_v3)
```
### Playwright Mock Rule of Thumb
Since all reads are flat, the most common mock patterns are:
```typescript
// ✅ Search (always flat)
url.includes('/v3/crud/event_badge/search') && method === 'POST'
// ✅ List (always flat, filter by query param)
url.includes('/v3/crud/event_badge/') && url.includes('for_obj_id') && method === 'GET'
// ✅ GET single (always flat)
url.includes('/v3/crud/event_badge/badge-xyz') && method === 'GET'
// ✅ Create (nested for event sub-objects)
url.match(/\/v3\/crud\/event\/[^/]+\/event_badge\/$/) && method === 'POST'
// ✅ Update (nested for event sub-objects)
url.match(/\/v3\/crud\/event\/[^/]+\/event_badge\/badge-xyz/) && method === 'PATCH'
```
---
## 5. V3 Uniform Lookup System
<!-- was section 4 -->
## 4. V3 Uniform Lookup System
The V3 Lookup system provides a hierarchical, deduplicated interface for standardized tables (Countries, Timezones, etc.). It supports global defaults, account overrides, and site-specific whitelisting.
@@ -174,7 +104,7 @@ To limit lookups for a specific site, add a `lookup_policy` to the `site.cfg_jso
---
## 6. Event File Data Retrieval (Hosted Files)
## 5. Event File Data Retrieval (Hosted Files)
Every Event File (`event_file`) **must** have a linked Hosted File (`hosted_file`). The Hosted File itself is a metadata record for binary content (files), which is accessed via separate Action endpoints (e.g., `/v3/action/hosted_file/download`). This API endpoint provides metadata about the associated hosted file. To retrieve this additional metadata:
@@ -193,7 +123,108 @@ Every Event File (`event_file`) **must** have a linked Hosted File (`hosted_file
---
## 7. Troubleshooting 403 Forbidden
## 6. Hosted File Actions: Convert & Clip (Frontend Notes)
These helper endpoints let the frontend request small server-side transformations without uploading new blobs. They return a newly-created `hosted_file` metadata object on success.
- **Convert (PDF → Image)**
- Method: `GET`
- Path: `/v3/action/hosted_file/{hosted_file_id}/convert_file`
- Required query params: `link_to_type`, `link_to_id`
- Optional query params: `filename_no_ext` (defaults to `automated_hosted_file_conversion`), `to_type` (defaults to `webp`)
- Auth: standard V3 headers (`x-aether-api-key`, `x-account-id` / `x-no-account-id` / `?jwt=`)
- Behavior: converts the first page of a PDF to `webp` or `png`, saves a new `hosted_file`, and returns its metadata. Returns 400 on failure.
- **Clip Video**
- Method: `GET`
- Path: `/v3/action/hosted_file/{hosted_file_id}/clip_video`
- Required query params: `link_to_type`, `link_to_id`, `start_time`, `end_time` (format `HH:MM:SS`)
- Optional query params: `filename_no_ext` (defaults to `automated_hosted_file_clip_video`), `reencode` (bool), `scale_down` (bool)
- Auth: standard V3 headers
- Behavior: extracts a clip using `ffmpeg` and saves it as a new `hosted_file`. Defaults to stream-copying to be fast; set `reencode=true` to force H.264 or `scale_down=true` to resize. Returns 400 on failure.
- Behavior: extracts a clip using `ffmpeg` and saves it as a new `hosted_file`.
- Defaults to stream-copying to be fast; set `reencode=true` to force H.264 or `scale_down=true` to resize.
- For longer-running clips you can schedule the job in the background by adding `?background=true`. When scheduled the API returns `202 Accepted` and the clip runs asynchronously on the server; check the returned `hosted_file` record later via the standard V3 `hosted_file` endpoints.
- Returns 400 on synchronous failure; returns 202 when scheduled successfully.
Frontend guidance:
- Call these routes with the same `link_to_type` / `link_to_id` you plan to associate the resulting hosted_file with — the server resolves random IDs for you.
- After a successful response, use the V3 `hosted_file` action endpoints (download/delete) to manage or retrieve the new file.
- These endpoints run synchronously and can take time for large inputs; for heavy or batch workloads use a queued job pattern instead.
- These endpoints may take time for large inputs. Prefer using `?background=true` to schedule work and receive a `202 Accepted` response for async processing. For heavy or batch workloads use a queued job pattern instead.
---
## 7. Event Exhibit Tracking Export (Leads Export)
Allows an exhibitor to download all lead-capture records for their exhibit as a CSV or XLSX file.
- **Method:** `GET`
- **Path:** `/v3/action/event_exhibit/{exhibit_id}/tracking_export`
- **Auth:** Standard V3 headers (`x-aether-api-key` + `x-account-id` or `?jwt=`)
### Query Parameters
| Parameter | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `file_type` | `CSV` \| `XLSX` | `CSV` | Output format. |
| `return_file` | bool | `true` | `true` → file download response. `false` → JSON body with row data. |
### Response
- `Content-Type: text/csv` (CSV) or `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` (XLSX)
- `Content-Disposition: attachment; filename="leads_export_<timestamp>.csv"`
- If there are no tracking records, a valid file with headers only is returned (not a 404).
### Columns Returned
Fixed columns (always present), followed by any custom question columns flattened from `responses_json`:
`event_exhibit_tracking_id`, `created_on`, `updated_on`, `event_exhibit_name`, `event_badge_full_name`, `event_badge_email`, `event_badge_professional_title`, `event_badge_affiliations`, `event_badge_location`, `event_badge_country`, `external_person_id`, `exhibitor_notes`, `priority`, `enable`, `hide`, `[custom question codes…]`
> **Note:** `exhibitor_notes` has HTML tags stripped automatically for clean CSV output.
### Permission Requirement — `leads_api_access`
> [!IMPORTANT]
> This endpoint enforces a **per-exhibit permission flag**. The `event_exhibit` record **must** have `leads_api_access = true` set in the database, OR the caller must have manager-level account access (JWT with `manager: true`).
>
> If `leads_api_access` is `false` or `null` on the exhibit, the API returns:
> ```json
> { "detail": "Access denied: leads API access is not enabled for this exhibit." }
> ```
> **Fix:** Enable the flag on the exhibit record via `PATCH /v3/crud/event_exhibit/{id}` with `{ "leads_api_access": true }`, or set it directly in the database/admin panel.
#### Dual purpose of `leads_api_access`
This flag serves two related but distinct roles:
1. **3rd-party API access (original intent):** Controls whether external systems (exhibitor apps, badge-scanning devices, etc.) are permitted to push or pull lead data for this exhibit via the API.
2. **UI export gate (new):** The frontend should read `leads_api_access` from the exhibit record and use it to show or hide the export/download button. Only render the button when the flag is `true` — this prevents users from triggering a request that will always 403.
The recommended pattern is to fetch the exhibit record first and gate the UI on this field before the user ever sees the export option. The API enforces the same check server-side as a safety net.
### Example Request
```ts
const resp = await fetch(
`https://dev-api.oneskyit.com/v3/action/event_exhibit/${exhibitId}/tracking_export?file_type=CSV&return_file=true`,
{
headers: {
'x-aether-api-key': API_KEY,
'x-account-id': accountId,
},
}
);
// resp is a file blob — use URL.createObjectURL() or trigger a download
const blob = await resp.blob();
const url = URL.createObjectURL(blob);
```
---
## 8. Troubleshooting 403 Forbidden
If you receive a 403 on a valid ID:
1. Verify `x-aether-api-key` is correct.