test: combine and modernize private-network checks; remove legacy disabled variants
This commit is contained in:
132
documentation/GUIDE__V3_FRONTEND_API.md
Normal file
132
documentation/GUIDE__V3_FRONTEND_API.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# Aether API V3 Frontend Integration Guide (Svelte/TypeScript)
|
||||
|
||||
This guide defines the standards for interacting with the **Aether API V3 CRUD** and **Action** endpoints.
|
||||
|
||||
---
|
||||
|
||||
## 1. Authentication and Security (Mandatory)
|
||||
|
||||
V3 architecture enforces strict **Multi-Tenant Isolation** and **Machine Authorization**. Requests require two levels of validation.
|
||||
|
||||
### A. The "Entry Ticket" (API Key)
|
||||
**Mandatory for all requests.** identifies the application or client.
|
||||
* **Header:** `x-aether-api-key: <your_app_key>`
|
||||
* **Status Code:** `403 Forbidden` if missing or invalid.
|
||||
|
||||
### B. The "Visa" (Account Context)
|
||||
Required for any non-public data (Journals, Badges, Users, etc.).
|
||||
1. **Standard Access**: Provide the `x-account-id` (the random string ID).
|
||||
* **Header:** `x-account-id: <account_id>`
|
||||
2. **Administrative Bypass**: For authorized scripts needing global access.
|
||||
* **Header:** `x-no-account-id: bypass`
|
||||
3. **Token Access**: Provide a **JWT** in the query string.
|
||||
* **Query Param:** `?jwt=<token>`
|
||||
|
||||
> [!CAUTION]
|
||||
> **UNSUPPORTED HEADERS:** The header `x-aether-api-token` is **NOT recognized** by the V3 API. If you send it, the backend will treat you as a guest and block access to private data.
|
||||
|
||||
---
|
||||
|
||||
## 2. Bootstrapping (The FQDN Handshake)
|
||||
|
||||
When the frontend first loads and doesn't know the `account_id`, it performs a "handshake" using its domain name.
|
||||
|
||||
**Endpoint:** `POST /v3/crud/site_domain/search`
|
||||
**Body:**
|
||||
```json
|
||||
{
|
||||
"and": [
|
||||
{ "field": "fqdn", "op": "eq", "value": "demo.oneskyit.com" }
|
||||
]
|
||||
}
|
||||
```
|
||||
**Results:**
|
||||
* Returns 200 + a list containing the `account_id` (random string ID) and `site_id` (random string ID).
|
||||
* ** デザイン Choice:** If the domain is not found, it returns **200 OK with an empty list `[]`**. It is NOT a 404.
|
||||
|
||||
---
|
||||
|
||||
## 3. Standard CRUD Patterns
|
||||
|
||||
### A. GET by ID
|
||||
Used when the ID is known.
|
||||
* **Endpoint:** `GET /v3/crud/{obj_type}/{id}`
|
||||
* **Security:** Returns 403 if the record doesn't belong to your `x-account-id`.
|
||||
|
||||
### B. POST Search
|
||||
The primary way to retrieve data.
|
||||
* **Endpoint:** `POST /v3/crud/{obj_type}/search`
|
||||
* **Security:** Automatically filters results to only show records belonging to your `x-account-id`. If no account context is provided, it will return **0 records** for private objects.
|
||||
|
||||
### C. POST Create / PATCH Update
|
||||
Modify data in the system.
|
||||
* **Endpoints:**
|
||||
* `POST /v3/crud/{obj_type}/`
|
||||
* `PATCH /v3/crud/{obj_type}/{id}`
|
||||
* **Strict Mode (Default):** The API validates your payload against the Pydantic model. If you send fields that do not exist in the model, the database might return a 400 "Unknown column" error.
|
||||
* **Permissive Mode (Header):** To allow the frontend to send "extra" fields (like local UI state) without causing errors, use the following header:
|
||||
* **Header:** `x-ae-ignore-extra-fields: true`
|
||||
* **Behavior:** When set to `true`, the backend will automatically strip any fields from the payload that are not defined in the object's model before attempting to save to the database.
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
### A. List Lookups
|
||||
Retrieve a ranked and filtered list of lookup items.
|
||||
* **Endpoint:** `GET /v3/lookup/{lu_type}/list`
|
||||
* **Available Types:** `country`, `country_subdivision`, `time_zone`
|
||||
* **Parameters:**
|
||||
* `site_id` (Optional): Random ID of the site to apply a **Whitelist Policy**.
|
||||
* `only_priority` (Optional): Set to `true` to return only high-priority items (e.g., common time zones).
|
||||
* `for_type` / `for_id` (Optional): Context for object-specific overrides.
|
||||
* `include_disabled` (Optional): Set to `true` to see shadowed/disabled records.
|
||||
|
||||
### B. Resolve Identity
|
||||
Resolves a string (code, group, or name) to a single record.
|
||||
* **Endpoint:** `GET /v3/lookup/{lu_type}/resolve?q=VALUE`
|
||||
* **Usage:** Use this when you have an external code (e.g., ISO "US") and need the full Aether record.
|
||||
|
||||
### C. Site Whitelist Policy
|
||||
To limit lookups for a specific site, add a `lookup_policy` to the `site.cfg_json` field.
|
||||
**Schema:**
|
||||
```json
|
||||
{
|
||||
"lookup_policy": {
|
||||
"country": ["US", "CA", "GB"],
|
||||
"time_zone": ["America/New_York"]
|
||||
}
|
||||
}
|
||||
```
|
||||
*Note: Whitelist values must match the `group` field in the database.*
|
||||
|
||||
---
|
||||
|
||||
## 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:
|
||||
|
||||
* **Endpoint:** `GET /v3/crud/event_file/{event_file_id}`
|
||||
* **Query Parameter:** Add `inc_hosted_file=true`
|
||||
* Example: `/v3/crud/event_file/<event_file_id>?inc_hosted_file=true`
|
||||
|
||||
**Response Impact:**
|
||||
|
||||
1. **Top-Level Convenience Fields:** The response will include top-level fields for commonly needed hosted file data. These are populated directly from the SQL view via JOINs.
|
||||
* `hosted_file_hash_sha256` (string)
|
||||
* `hosted_file_subdirectory_path` (string)
|
||||
* `hosted_file_content_type` (string)
|
||||
* `hosted_file_size` (string - in bytes)
|
||||
2. **Nested Hosted File Object:** A full `hosted_file` object will be nested under the `hosted_file` key. This object (`Hosted_File_Base` model) will contain all its standard fields, including `id` (random string ID), `hash_sha256`, `content_type`, `size`, etc.
|
||||
|
||||
---
|
||||
|
||||
## 5. Troubleshooting 403 Forbidden
|
||||
|
||||
If you receive a 403 on a valid ID:
|
||||
1. Verify `x-aether-api-key` is correct.
|
||||
2. Ensure you are sending `x-account-id` and NOT `x-aether-api-token`.
|
||||
3. Verify the record actually belongs to the account ID you are sending.
|
||||
4. Check if the object is marked `public_read: True` in the registry. (Posts and Archive Content allow guest access; Journals and Badges do not).
|
||||
@@ -170,13 +170,12 @@ export async function create_ae_obj__archive_content({
|
||||
return null;
|
||||
}
|
||||
|
||||
const result = await api.create_ae_obj_v3({
|
||||
const result = await api.create_nested_obj_v3({
|
||||
api_cfg,
|
||||
obj_type: 'archive_content',
|
||||
fields: {
|
||||
archive_id_random: archive_id,
|
||||
...data_kv
|
||||
},
|
||||
parent_type: 'archive',
|
||||
parent_id: archive_id,
|
||||
child_type: 'archive_content',
|
||||
fields: data_kv,
|
||||
params,
|
||||
log_lvl
|
||||
});
|
||||
|
||||
@@ -241,108 +241,115 @@ export async function load_ae_obj_li__event_badge_template({
|
||||
|
||||
// Updated 2026-01-20 to V3
|
||||
export async function create_ae_obj__event_badge_template({
|
||||
api_cfg,
|
||||
event_id,
|
||||
data_kv,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
api_cfg,
|
||||
event_id,
|
||||
data_kv,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any;
|
||||
event_id: string;
|
||||
data_kv: key_val;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
api_cfg: any;
|
||||
event_id: string;
|
||||
data_kv: key_val;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
}) {
|
||||
const result = await api.create_ae_obj_v3({
|
||||
api_cfg,
|
||||
obj_type: 'event_badge_template',
|
||||
fields: {
|
||||
event_id: event_id,
|
||||
...data_kv
|
||||
},
|
||||
log_lvl
|
||||
});
|
||||
const result = await api.create_nested_obj_v3({
|
||||
api_cfg,
|
||||
for_obj_type: 'event',
|
||||
for_obj_id: event_id,
|
||||
obj_type: 'event_badge_template',
|
||||
fields: { ...data_kv },
|
||||
log_lvl
|
||||
});
|
||||
|
||||
if (result && try_cache) {
|
||||
const processed_obj_li = await process_ae_badge_template_props({
|
||||
obj_li: [result],
|
||||
log_lvl
|
||||
});
|
||||
await db_save_ae_obj_li__ae_obj({
|
||||
db_instance: db_events,
|
||||
table_name: 'badge_template',
|
||||
obj_li: processed_obj_li,
|
||||
properties_to_save,
|
||||
log_lvl
|
||||
});
|
||||
}
|
||||
return result;
|
||||
if (result && try_cache) {
|
||||
const processed_obj_li = await process_ae_badge_template_props({
|
||||
obj_li: [result],
|
||||
log_lvl
|
||||
});
|
||||
await db_save_ae_obj_li__ae_obj({
|
||||
db_instance: db_events,
|
||||
table_name: 'badge_template',
|
||||
obj_li: processed_obj_li,
|
||||
properties_to_save,
|
||||
log_lvl
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Updated 2026-01-20 to V3
|
||||
export async function delete_ae_obj_id__event_badge_template({
|
||||
api_cfg,
|
||||
event_badge_template_id,
|
||||
method = 'delete',
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
api_cfg,
|
||||
event_id,
|
||||
event_badge_template_id,
|
||||
method = 'delete',
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any;
|
||||
event_badge_template_id: string;
|
||||
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
api_cfg: any;
|
||||
event_id: string;
|
||||
event_badge_template_id: string;
|
||||
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
}) {
|
||||
const result = await api.delete_ae_obj_v3({
|
||||
api_cfg,
|
||||
obj_type: 'event_badge_template',
|
||||
obj_id: event_badge_template_id,
|
||||
method,
|
||||
log_lvl
|
||||
});
|
||||
const result = await api.delete_nested_ae_obj_v3({
|
||||
api_cfg,
|
||||
for_obj_type: 'event',
|
||||
for_obj_id: event_id,
|
||||
obj_type: 'event_badge_template',
|
||||
obj_id: event_badge_template_id,
|
||||
method,
|
||||
log_lvl
|
||||
});
|
||||
|
||||
if (try_cache) {
|
||||
await db_events.badge_template.delete(event_badge_template_id);
|
||||
}
|
||||
return result;
|
||||
if (try_cache) {
|
||||
await db_events.badge_template.delete(event_badge_template_id);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Updated 2026-01-20 to V3
|
||||
export async function update_ae_obj__event_badge_template({
|
||||
api_cfg,
|
||||
event_badge_template_id,
|
||||
data_kv,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
api_cfg,
|
||||
event_id,
|
||||
event_badge_template_id,
|
||||
data_kv,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any;
|
||||
event_badge_template_id: string;
|
||||
data_kv: key_val;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
api_cfg: any;
|
||||
event_id: string;
|
||||
event_badge_template_id: string;
|
||||
data_kv: key_val;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
}) {
|
||||
const result = await api.update_ae_obj_v3({
|
||||
api_cfg,
|
||||
obj_type: 'event_badge_template',
|
||||
obj_id: event_badge_template_id,
|
||||
fields: data_kv,
|
||||
log_lvl
|
||||
});
|
||||
const result = await api.update_nested_obj_v3({
|
||||
api_cfg,
|
||||
for_obj_type: 'event',
|
||||
for_obj_id: event_id,
|
||||
obj_type: 'event_badge_template',
|
||||
obj_id: event_badge_template_id,
|
||||
fields: data_kv,
|
||||
log_lvl
|
||||
});
|
||||
|
||||
if (result && try_cache) {
|
||||
const processed_obj_li = await process_ae_badge_template_props({
|
||||
obj_li: [result],
|
||||
log_lvl
|
||||
});
|
||||
await db_save_ae_obj_li__ae_obj({
|
||||
db_instance: db_events,
|
||||
table_name: 'badge_template',
|
||||
obj_li: processed_obj_li,
|
||||
properties_to_save,
|
||||
log_lvl
|
||||
});
|
||||
}
|
||||
return result;
|
||||
if (result && try_cache) {
|
||||
const processed_obj_li = await process_ae_badge_template_props({
|
||||
obj_li: [result],
|
||||
log_lvl
|
||||
});
|
||||
await db_save_ae_obj_li__ae_obj({
|
||||
db_instance: db_events,
|
||||
table_name: 'badge_template',
|
||||
obj_li: processed_obj_li,
|
||||
properties_to_save,
|
||||
log_lvl
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Updated 2026-01-20 to V3
|
||||
|
||||
@@ -178,133 +178,140 @@ export async function load_ae_obj_li__event_device({
|
||||
|
||||
// Updated 2026-01-20 to V3
|
||||
export async function create_ae_obj__event_device({
|
||||
api_cfg,
|
||||
event_id,
|
||||
data_kv,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
api_cfg,
|
||||
event_id,
|
||||
data_kv,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any;
|
||||
event_id: string;
|
||||
data_kv: key_val;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
api_cfg: any;
|
||||
event_id: string;
|
||||
data_kv: key_val;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
}): Promise<ae_EventDevice | null> {
|
||||
if (log_lvl) {
|
||||
console.log(`*** create_ae_obj__event_device() *** [V3] event_id=${event_id}`);
|
||||
}
|
||||
if (log_lvl) {
|
||||
console.log(`*** create_ae_obj__event_device() *** [V3] event_id=${event_id}`);
|
||||
}
|
||||
|
||||
const result = await api.create_ae_obj_v3({
|
||||
api_cfg,
|
||||
obj_type: 'event_device',
|
||||
fields: {
|
||||
event_id,
|
||||
...data_kv
|
||||
},
|
||||
log_lvl
|
||||
});
|
||||
const result = await api.create_nested_obj_v3({
|
||||
api_cfg,
|
||||
for_obj_type: 'event',
|
||||
for_obj_id: event_id,
|
||||
obj_type: 'event_device',
|
||||
fields: { ...data_kv },
|
||||
log_lvl
|
||||
});
|
||||
|
||||
if (result) {
|
||||
const processed = await process_ae_obj__event_device_props({
|
||||
obj_li: [result],
|
||||
log_lvl
|
||||
});
|
||||
const processed_obj = processed[0];
|
||||
if (result) {
|
||||
const processed = await process_ae_obj__event_device_props({
|
||||
obj_li: [result],
|
||||
log_lvl
|
||||
});
|
||||
const processed_obj = processed[0];
|
||||
|
||||
if (try_cache) {
|
||||
await db_save_ae_obj_li__ae_obj({
|
||||
db_instance: db_events,
|
||||
table_name: 'device',
|
||||
obj_li: [processed_obj],
|
||||
properties_to_save,
|
||||
log_lvl
|
||||
});
|
||||
}
|
||||
return processed_obj;
|
||||
}
|
||||
if (try_cache) {
|
||||
await db_save_ae_obj_li__ae_obj({
|
||||
db_instance: db_events,
|
||||
table_name: 'device',
|
||||
obj_li: [processed_obj],
|
||||
properties_to_save,
|
||||
log_lvl
|
||||
});
|
||||
}
|
||||
return processed_obj;
|
||||
}
|
||||
|
||||
return null;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Updated 2026-01-20 to V3
|
||||
export async function delete_ae_obj_id__event_device({
|
||||
api_cfg,
|
||||
event_device_id,
|
||||
method = 'delete',
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
api_cfg,
|
||||
event_id,
|
||||
event_device_id,
|
||||
method = 'delete',
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any;
|
||||
event_device_id: string;
|
||||
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
api_cfg: any;
|
||||
event_id: string;
|
||||
event_device_id: string;
|
||||
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
}) {
|
||||
if (log_lvl) {
|
||||
console.log(`*** delete_ae_obj_id__event_device() *** [V3] id=${event_device_id}`);
|
||||
}
|
||||
if (log_lvl) {
|
||||
console.log(`*** delete_ae_obj_id__event_device() *** [V3] id=${event_device_id}`);
|
||||
}
|
||||
|
||||
const result = await api.delete_ae_obj_v3({
|
||||
api_cfg,
|
||||
obj_type: 'event_device',
|
||||
obj_id: event_device_id,
|
||||
method,
|
||||
log_lvl
|
||||
});
|
||||
const result = await api.delete_nested_ae_obj_v3({
|
||||
api_cfg,
|
||||
for_obj_type: 'event',
|
||||
for_obj_id: event_id,
|
||||
obj_type: 'event_device',
|
||||
obj_id: event_device_id,
|
||||
method,
|
||||
log_lvl
|
||||
});
|
||||
|
||||
if (try_cache) {
|
||||
await db_events.device.delete(event_device_id);
|
||||
}
|
||||
if (try_cache) {
|
||||
await db_events.device.delete(event_device_id);
|
||||
}
|
||||
|
||||
return result;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Updated 2026-01-20 to V3
|
||||
export async function update_ae_obj__event_device({
|
||||
api_cfg,
|
||||
event_device_id,
|
||||
data_kv,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
api_cfg,
|
||||
event_id,
|
||||
event_device_id,
|
||||
data_kv,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any;
|
||||
event_device_id: string;
|
||||
data_kv: key_val;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
api_cfg: any;
|
||||
event_id: string;
|
||||
event_device_id: string;
|
||||
data_kv: key_val;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
}): Promise<ae_EventDevice | null> {
|
||||
if (log_lvl) {
|
||||
console.log(`*** update_ae_obj__event_device() *** [V3] id=${event_device_id}`);
|
||||
}
|
||||
if (log_lvl) {
|
||||
console.log(`*** update_ae_obj__event_device() *** [V3] id=${event_device_id}`);
|
||||
}
|
||||
|
||||
const result = await api.update_ae_obj_v3({
|
||||
api_cfg,
|
||||
obj_type: 'event_device',
|
||||
obj_id: event_device_id,
|
||||
fields: data_kv,
|
||||
log_lvl
|
||||
});
|
||||
const result = await api.update_nested_obj_v3({
|
||||
api_cfg,
|
||||
for_obj_type: 'event',
|
||||
for_obj_id: event_id,
|
||||
obj_type: 'event_device',
|
||||
obj_id: event_device_id,
|
||||
fields: data_kv,
|
||||
log_lvl
|
||||
});
|
||||
|
||||
if (result) {
|
||||
const processed = await process_ae_obj__event_device_props({
|
||||
obj_li: [result],
|
||||
log_lvl
|
||||
});
|
||||
const processed_obj = processed[0];
|
||||
if (result) {
|
||||
const processed = await process_ae_obj__event_device_props({
|
||||
obj_li: [result],
|
||||
log_lvl
|
||||
});
|
||||
const processed_obj = processed[0];
|
||||
|
||||
if (try_cache) {
|
||||
await db_save_ae_obj_li__ae_obj({
|
||||
db_instance: db_events,
|
||||
table_name: 'device',
|
||||
obj_li: [processed_obj],
|
||||
properties_to_save,
|
||||
log_lvl
|
||||
});
|
||||
}
|
||||
return processed_obj;
|
||||
}
|
||||
if (try_cache) {
|
||||
await db_save_ae_obj_li__ae_obj({
|
||||
db_instance: db_events,
|
||||
table_name: 'device',
|
||||
obj_li: [processed_obj],
|
||||
properties_to_save,
|
||||
log_lvl
|
||||
});
|
||||
}
|
||||
return processed_obj;
|
||||
}
|
||||
|
||||
return null;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Updated 2026-01-20 to V3
|
||||
|
||||
@@ -177,78 +177,113 @@ async function _handle_nested_loads(location_obj: any, { api_cfg, inc_file_li, i
|
||||
|
||||
// Updated 2026-01-20 to V3
|
||||
export async function create_ae_obj__event_location({
|
||||
api_cfg,
|
||||
event_id,
|
||||
data_kv,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
api_cfg,
|
||||
event_id,
|
||||
data_kv,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any;
|
||||
event_id: string;
|
||||
data_kv: key_val;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
api_cfg: any;
|
||||
event_id: string;
|
||||
data_kv: key_val;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
}): Promise<ae_EventLocation | null> {
|
||||
const result = await api.create_ae_obj_v3({
|
||||
api_cfg, obj_type: 'event_location',
|
||||
fields: { event_id, ...data_kv },
|
||||
log_lvl
|
||||
});
|
||||
const result = await api.create_nested_obj_v3({
|
||||
api_cfg,
|
||||
for_obj_type: 'event',
|
||||
for_obj_id: event_id,
|
||||
obj_type: 'event_location',
|
||||
fields: { ...data_kv },
|
||||
log_lvl
|
||||
});
|
||||
|
||||
if (result) {
|
||||
const processed = await process_ae_obj__event_location_props({ obj_li: [result], log_lvl });
|
||||
const processed_obj = processed[0];
|
||||
if (try_cache) {
|
||||
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'location', obj_li: [processed_obj], properties_to_save, log_lvl });
|
||||
}
|
||||
return processed_obj;
|
||||
}
|
||||
return null;
|
||||
if (result) {
|
||||
const processed = await process_ae_obj__event_location_props({ obj_li: [result], log_lvl });
|
||||
const processed_obj = processed[0];
|
||||
if (try_cache) {
|
||||
await db_save_ae_obj_li__ae_obj({
|
||||
db_instance: db_events,
|
||||
table_name: 'location',
|
||||
obj_li: [processed_obj],
|
||||
properties_to_save,
|
||||
log_lvl
|
||||
});
|
||||
}
|
||||
return processed_obj;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Updated 2026-01-20 to V3
|
||||
export async function delete_ae_obj_id__event_location({
|
||||
api_cfg,
|
||||
event_location_id,
|
||||
method = 'delete',
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
api_cfg,
|
||||
event_id,
|
||||
event_location_id,
|
||||
method = 'delete',
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any;
|
||||
event_location_id: string;
|
||||
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
api_cfg: any;
|
||||
event_id: string;
|
||||
event_location_id: string;
|
||||
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
}) {
|
||||
const result = await api.delete_ae_obj_v3({ api_cfg, obj_type: 'event_location', obj_id: event_location_id, method, log_lvl });
|
||||
if (try_cache) await db_events.location.delete(event_location_id);
|
||||
return result;
|
||||
const result = await api.delete_nested_ae_obj_v3({
|
||||
api_cfg,
|
||||
for_obj_type: 'event',
|
||||
for_obj_id: event_id,
|
||||
obj_type: 'event_location',
|
||||
obj_id: event_location_id,
|
||||
method,
|
||||
log_lvl
|
||||
});
|
||||
if (try_cache) await db_events.location.delete(event_location_id);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Updated 2026-01-20 to V3
|
||||
export async function update_ae_obj__event_location({
|
||||
api_cfg,
|
||||
event_location_id,
|
||||
data_kv,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
api_cfg,
|
||||
event_id,
|
||||
event_location_id,
|
||||
data_kv,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any;
|
||||
event_location_id: string;
|
||||
data_kv: key_val;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
api_cfg: any;
|
||||
event_id: string;
|
||||
event_location_id: string;
|
||||
data_kv: key_val;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
}): Promise<ae_EventLocation | null> {
|
||||
const result = await api.update_ae_obj_v3({ api_cfg, obj_type: 'event_location', obj_id: event_location_id, fields: data_kv, log_lvl });
|
||||
if (result) {
|
||||
const processed = await process_ae_obj__event_location_props({ obj_li: [result], log_lvl });
|
||||
const processed_obj = processed[0];
|
||||
if (try_cache) {
|
||||
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'location', obj_li: [processed_obj], properties_to_save, log_lvl });
|
||||
}
|
||||
return processed_obj;
|
||||
}
|
||||
return null;
|
||||
const result = await api.update_nested_obj_v3({
|
||||
api_cfg,
|
||||
for_obj_type: 'event',
|
||||
for_obj_id: event_id,
|
||||
obj_type: 'event_location',
|
||||
obj_id: event_location_id,
|
||||
fields: data_kv,
|
||||
log_lvl
|
||||
});
|
||||
if (result) {
|
||||
const processed = await process_ae_obj__event_location_props({ obj_li: [result], log_lvl });
|
||||
const processed_obj = processed[0];
|
||||
if (try_cache) {
|
||||
await db_save_ae_obj_li__ae_obj({
|
||||
db_instance: db_events,
|
||||
table_name: 'location',
|
||||
obj_li: [processed_obj],
|
||||
properties_to_save,
|
||||
log_lvl
|
||||
});
|
||||
}
|
||||
return processed_obj;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Updated 2026-01-20 to V3
|
||||
|
||||
@@ -197,35 +197,42 @@ async function _refresh_presentation_li_background({ api_cfg, for_obj_type, for_
|
||||
|
||||
// Updated 2026-01-20 to V3
|
||||
export async function create_ae_obj__event_presentation({
|
||||
api_cfg,
|
||||
event_id,
|
||||
event_session_id,
|
||||
data_kv,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
api_cfg,
|
||||
event_session_id,
|
||||
data_kv,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any;
|
||||
event_id: string;
|
||||
event_session_id: string;
|
||||
data_kv: key_val;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
api_cfg: any;
|
||||
event_session_id: string;
|
||||
data_kv: key_val;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
}): Promise<ae_EventPresentation | null> {
|
||||
const result = await api.create_ae_obj_v3({
|
||||
api_cfg, obj_type: 'event_presentation',
|
||||
fields: { event_id, event_session_id, ...data_kv },
|
||||
log_lvl
|
||||
});
|
||||
const result = await api.create_nested_obj_v3({
|
||||
api_cfg,
|
||||
for_obj_type: 'event_session',
|
||||
for_obj_id: event_session_id,
|
||||
obj_type: 'event_presentation',
|
||||
fields: { ...data_kv },
|
||||
log_lvl
|
||||
});
|
||||
|
||||
if (result) {
|
||||
const processed = await process_ae_obj__event_presentation_props({ obj_li: [result], log_lvl });
|
||||
const processed_obj = processed[0];
|
||||
if (try_cache) {
|
||||
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'presentation', obj_li: [processed_obj], properties_to_save, log_lvl });
|
||||
}
|
||||
return processed_obj;
|
||||
}
|
||||
return null;
|
||||
if (result) {
|
||||
const processed = await process_ae_obj__event_presentation_props({ obj_li: [result], log_lvl });
|
||||
const processed_obj = processed[0];
|
||||
if (try_cache) {
|
||||
await db_save_ae_obj_li__ae_obj({
|
||||
db_instance: db_events,
|
||||
table_name: 'presentation',
|
||||
obj_li: [processed_obj],
|
||||
properties_to_save,
|
||||
log_lvl
|
||||
});
|
||||
}
|
||||
return processed_obj;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Updated 2026-01-20 to V3
|
||||
|
||||
@@ -173,82 +173,113 @@ async function _refresh_presenter_li_background({ api_cfg, for_obj_type, for_obj
|
||||
|
||||
// Updated 2026-01-20 to V3
|
||||
export async function create_ae_obj__event_presenter({
|
||||
api_cfg,
|
||||
event_id,
|
||||
event_session_id,
|
||||
event_presentation_id,
|
||||
data_kv,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
api_cfg,
|
||||
event_presentation_id,
|
||||
data_kv,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any;
|
||||
event_id: string;
|
||||
event_session_id: string;
|
||||
event_presentation_id: string;
|
||||
data_kv: key_val;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
api_cfg: any;
|
||||
event_presentation_id: string;
|
||||
data_kv: key_val;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
}): Promise<ae_EventPresenter | null> {
|
||||
const result = await api.create_ae_obj_v3({
|
||||
api_cfg, obj_type: 'event_presenter',
|
||||
fields: { event_id, event_session_id, event_presentation_id, ...data_kv },
|
||||
log_lvl
|
||||
});
|
||||
const result = await api.create_nested_obj_v3({
|
||||
api_cfg,
|
||||
for_obj_type: 'event_presentation',
|
||||
for_obj_id: event_presentation_id,
|
||||
obj_type: 'event_presenter',
|
||||
fields: { ...data_kv },
|
||||
log_lvl
|
||||
});
|
||||
|
||||
if (result) {
|
||||
const processed = await process_ae_obj__event_presenter_props({ obj_li: [result], log_lvl });
|
||||
const processed_obj = processed[0];
|
||||
if (try_cache) {
|
||||
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'presenter', obj_li: [processed_obj], properties_to_save, log_lvl });
|
||||
}
|
||||
return processed_obj;
|
||||
}
|
||||
return null;
|
||||
if (result) {
|
||||
const processed = await process_ae_obj__event_presenter_props({ obj_li: [result], log_lvl });
|
||||
const processed_obj = processed[0];
|
||||
if (try_cache) {
|
||||
await db_save_ae_obj_li__ae_obj({
|
||||
db_instance: db_events,
|
||||
table_name: 'presenter',
|
||||
obj_li: [processed_obj],
|
||||
properties_to_save,
|
||||
log_lvl
|
||||
});
|
||||
}
|
||||
return processed_obj;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Updated 2026-01-20 to V3
|
||||
export async function delete_ae_obj_id__event_presenter({
|
||||
api_cfg,
|
||||
event_presenter_id,
|
||||
method = 'delete',
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
api_cfg,
|
||||
event_presentation_id,
|
||||
event_presenter_id,
|
||||
method = 'delete',
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any;
|
||||
event_presenter_id: string;
|
||||
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
api_cfg: any;
|
||||
event_presentation_id: string;
|
||||
event_presenter_id: string;
|
||||
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
}) {
|
||||
const result = await api.delete_ae_obj_v3({ api_cfg, obj_type: 'event_presenter', obj_id: event_presenter_id, method, log_lvl });
|
||||
if (try_cache) await db_events.presenter.delete(event_presenter_id);
|
||||
return result;
|
||||
const result = await api.delete_nested_ae_obj_v3({
|
||||
api_cfg,
|
||||
for_obj_type: 'event_presentation',
|
||||
for_obj_id: event_presentation_id,
|
||||
obj_type: 'event_presenter',
|
||||
obj_id: event_presenter_id,
|
||||
method,
|
||||
log_lvl
|
||||
});
|
||||
if (try_cache) await db_events.presenter.delete(event_presenter_id);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Updated 2026-01-20 to V3
|
||||
export async function update_ae_obj__event_presenter({
|
||||
api_cfg,
|
||||
event_presenter_id,
|
||||
data_kv,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
api_cfg,
|
||||
event_presentation_id,
|
||||
event_presenter_id,
|
||||
data_kv,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any;
|
||||
event_presenter_id: string;
|
||||
data_kv: key_val;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
api_cfg: any;
|
||||
event_presentation_id: string;
|
||||
event_presenter_id: string;
|
||||
data_kv: key_val;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
}): Promise<ae_EventPresenter | null> {
|
||||
const result = await api.update_ae_obj_v3({ api_cfg, obj_type: 'event_presenter', obj_id: event_presenter_id, fields: data_kv, log_lvl });
|
||||
if (result) {
|
||||
const processed = await process_ae_obj__event_presenter_props({ obj_li: [result], log_lvl });
|
||||
const processed_obj = processed[0];
|
||||
if (try_cache) {
|
||||
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'presenter', obj_li: [processed_obj], properties_to_save, log_lvl });
|
||||
}
|
||||
return processed_obj;
|
||||
}
|
||||
return null;
|
||||
const result = await api.update_nested_obj_v3({
|
||||
api_cfg,
|
||||
for_obj_type: 'event_presentation',
|
||||
for_obj_id: event_presentation_id,
|
||||
obj_type: 'event_presenter',
|
||||
obj_id: event_presenter_id,
|
||||
fields: data_kv,
|
||||
log_lvl
|
||||
});
|
||||
if (result) {
|
||||
const processed = await process_ae_obj__event_presenter_props({ obj_li: [result], log_lvl });
|
||||
const processed_obj = processed[0];
|
||||
if (try_cache) {
|
||||
await db_save_ae_obj_li__ae_obj({
|
||||
db_instance: db_events,
|
||||
table_name: 'presenter',
|
||||
obj_li: [processed_obj],
|
||||
properties_to_save,
|
||||
log_lvl
|
||||
});
|
||||
}
|
||||
return processed_obj;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Updated 2026-01-21 to Restore Full Aether Search Logic
|
||||
|
||||
@@ -249,47 +249,110 @@ async function _refresh_session_li_background({ api_cfg, for_obj_type, for_obj_i
|
||||
}
|
||||
|
||||
export async function create_ae_obj__event_session({
|
||||
api_cfg, event_id, data_kv, try_cache = true, log_lvl = 0
|
||||
api_cfg,
|
||||
event_id,
|
||||
data_kv,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any; event_id: string; data_kv: key_val; try_cache?: boolean; log_lvl?: number;
|
||||
api_cfg: any;
|
||||
event_id: string;
|
||||
data_kv: key_val;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
}): Promise<ae_EventSession | null> {
|
||||
const result = await api.create_ae_obj_v3({ api_cfg, obj_type: 'event_session', fields: { event_id, ...data_kv }, log_lvl });
|
||||
if (result) {
|
||||
const processed = await process_ae_obj__event_session_props({ obj_li: [result], log_lvl });
|
||||
const processed_obj = processed[0];
|
||||
if (try_cache) {
|
||||
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'session', obj_li: [processed_obj], properties_to_save, log_lvl });
|
||||
}
|
||||
return processed_obj;
|
||||
}
|
||||
return null;
|
||||
const result = await api.create_nested_obj_v3({
|
||||
api_cfg,
|
||||
for_obj_type: 'event',
|
||||
for_obj_id: event_id,
|
||||
obj_type: 'event_session',
|
||||
fields: { ...data_kv },
|
||||
log_lvl
|
||||
});
|
||||
if (result) {
|
||||
const processed = await process_ae_obj__event_session_props({ obj_li: [result], log_lvl });
|
||||
const processed_obj = processed[0];
|
||||
if (try_cache) {
|
||||
await db_save_ae_obj_li__ae_obj({
|
||||
db_instance: db_events,
|
||||
table_name: 'session',
|
||||
obj_li: [processed_obj],
|
||||
properties_to_save,
|
||||
log_lvl
|
||||
});
|
||||
}
|
||||
return processed_obj;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function delete_ae_obj_id__event_session({
|
||||
api_cfg, event_session_id, method = 'delete', try_cache = true, log_lvl = 0
|
||||
api_cfg,
|
||||
event_id,
|
||||
event_session_id,
|
||||
method = 'delete',
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any; event_session_id: string; method?: 'delete' | 'soft_delete' | 'disable' | 'hide'; try_cache?: boolean; log_lvl?: number;
|
||||
api_cfg: any;
|
||||
event_id: string;
|
||||
event_session_id: string;
|
||||
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
}) {
|
||||
const result = await api.delete_ae_obj_v3({ api_cfg, obj_type: 'event_session', obj_id: event_session_id, method, log_lvl });
|
||||
if (try_cache) await db_events.session.delete(event_session_id);
|
||||
return result;
|
||||
const result = await api.delete_nested_ae_obj_v3({
|
||||
api_cfg,
|
||||
for_obj_type: 'event',
|
||||
for_obj_id: event_id,
|
||||
obj_type: 'event_session',
|
||||
obj_id: event_session_id,
|
||||
method,
|
||||
log_lvl
|
||||
});
|
||||
if (try_cache) await db_events.session.delete(event_session_id);
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function update_ae_obj__event_session({
|
||||
api_cfg, event_session_id, data_kv, try_cache = true, log_lvl = 0
|
||||
api_cfg,
|
||||
event_id,
|
||||
event_session_id,
|
||||
data_kv,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any; event_session_id: string; data_kv: key_val; try_cache?: boolean; log_lvl?: number;
|
||||
api_cfg: any;
|
||||
event_id: string;
|
||||
event_session_id: string;
|
||||
data_kv: key_val;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
}): Promise<ae_EventSession | null> {
|
||||
const result = await api.update_ae_obj_v3({ api_cfg, obj_type: 'event_session', obj_id: event_session_id, fields: data_kv, log_lvl });
|
||||
if (result) {
|
||||
const processed = await process_ae_obj__event_session_props({ obj_li: [result], log_lvl });
|
||||
const processed_obj = processed[0];
|
||||
if (try_cache) {
|
||||
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'session', obj_li: [processed_obj], properties_to_save, log_lvl });
|
||||
}
|
||||
return processed_obj;
|
||||
}
|
||||
return null;
|
||||
const result = await api.update_nested_obj_v3({
|
||||
api_cfg,
|
||||
for_obj_type: 'event',
|
||||
for_obj_id: event_id,
|
||||
obj_type: 'event_session',
|
||||
obj_id: event_session_id,
|
||||
fields: data_kv,
|
||||
log_lvl
|
||||
});
|
||||
if (result) {
|
||||
const processed = await process_ae_obj__event_session_props({ obj_li: [result], log_lvl });
|
||||
const processed_obj = processed[0];
|
||||
if (try_cache) {
|
||||
await db_save_ae_obj_li__ae_obj({
|
||||
db_instance: db_events,
|
||||
table_name: 'session',
|
||||
obj_li: [processed_obj],
|
||||
properties_to_save,
|
||||
log_lvl
|
||||
});
|
||||
}
|
||||
return processed_obj;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function search__event_session({
|
||||
|
||||
@@ -295,65 +295,87 @@ async function _refresh_exhibit_li_background({ api_cfg, event_id, enabled, hidd
|
||||
* Exhibit Create (V3)
|
||||
*/
|
||||
export async function create_ae_obj__exhibit({
|
||||
api_cfg, event_id, data_kv, try_cache = true, log_lvl = 0
|
||||
api_cfg,
|
||||
event_id,
|
||||
data_kv,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any; event_id: string; data_kv: key_val; try_cache?: boolean; log_lvl?: number;
|
||||
api_cfg: any;
|
||||
event_id: string;
|
||||
data_kv: key_val;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
}): Promise<ae_EventExhibit | null> {
|
||||
const result = await api.create_ae_obj_v3({
|
||||
api_cfg,
|
||||
obj_type: 'event_exhibit',
|
||||
fields: { event_id: event_id, ...data_kv },
|
||||
log_lvl
|
||||
});
|
||||
const result = await api.create_nested_obj_v3({
|
||||
api_cfg,
|
||||
for_obj_type: 'event',
|
||||
for_obj_id: event_id,
|
||||
obj_type: 'event_exhibit',
|
||||
fields: { ...data_kv },
|
||||
log_lvl
|
||||
});
|
||||
|
||||
if (result) {
|
||||
const processed = await process_ae_obj__exhibit_props({ obj_li: [result], log_lvl });
|
||||
const processed_obj = processed[0];
|
||||
if (try_cache) {
|
||||
await db_save_ae_obj_li__ae_obj({
|
||||
db_instance: db_events,
|
||||
table_name: 'exhibit',
|
||||
obj_li: [processed_obj],
|
||||
properties_to_save: properties_to_save_exhibit,
|
||||
log_lvl
|
||||
});
|
||||
}
|
||||
return processed_obj;
|
||||
}
|
||||
return null;
|
||||
if (result) {
|
||||
const processed = await process_ae_obj__exhibit_props({ obj_li: [result], log_lvl });
|
||||
const processed_obj = processed[0];
|
||||
if (try_cache) {
|
||||
await db_save_ae_obj_li__ae_obj({
|
||||
db_instance: db_events,
|
||||
table_name: 'exhibit',
|
||||
obj_li: [processed_obj],
|
||||
properties_to_save: properties_to_save_exhibit,
|
||||
log_lvl
|
||||
});
|
||||
}
|
||||
return processed_obj;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exhibit Update (V3)
|
||||
*/
|
||||
export async function update_ae_obj__exhibit({
|
||||
api_cfg, exhibit_id, data_kv, try_cache = true, log_lvl = 0
|
||||
api_cfg,
|
||||
event_id,
|
||||
exhibit_id,
|
||||
data_kv,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any; exhibit_id: string; data_kv: key_val; try_cache?: boolean; log_lvl?: number;
|
||||
api_cfg: any;
|
||||
event_id: string;
|
||||
exhibit_id: string;
|
||||
data_kv: key_val;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
}): Promise<ae_EventExhibit | null> {
|
||||
const result = await api.update_ae_obj_v3({
|
||||
api_cfg,
|
||||
obj_type: 'event_exhibit',
|
||||
obj_id: exhibit_id,
|
||||
fields: data_kv,
|
||||
log_lvl
|
||||
});
|
||||
const result = await api.update_nested_obj_v3({
|
||||
api_cfg,
|
||||
for_obj_type: 'event',
|
||||
for_obj_id: event_id,
|
||||
obj_type: 'event_exhibit',
|
||||
obj_id: exhibit_id,
|
||||
fields: data_kv,
|
||||
log_lvl
|
||||
});
|
||||
|
||||
if (result) {
|
||||
const processed = await process_ae_obj__exhibit_props({ obj_li: [result], log_lvl });
|
||||
const processed_obj = processed[0];
|
||||
if (try_cache) {
|
||||
await db_save_ae_obj_li__ae_obj({
|
||||
db_instance: db_events,
|
||||
table_name: 'exhibit',
|
||||
obj_li: [processed_obj],
|
||||
properties_to_save: properties_to_save_exhibit,
|
||||
log_lvl
|
||||
});
|
||||
}
|
||||
return processed_obj;
|
||||
}
|
||||
return null;
|
||||
if (result) {
|
||||
const processed = await process_ae_obj__exhibit_props({ obj_li: [result], log_lvl });
|
||||
const processed_obj = processed[0];
|
||||
if (try_cache) {
|
||||
await db_save_ae_obj_li__ae_obj({
|
||||
db_instance: db_events,
|
||||
table_name: 'exhibit',
|
||||
obj_li: [processed_obj],
|
||||
properties_to_save: properties_to_save_exhibit,
|
||||
log_lvl
|
||||
});
|
||||
}
|
||||
return processed_obj;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -308,90 +308,95 @@ async function _refresh_tracking_li_background({ api_cfg, exhibit_id, enabled, h
|
||||
* Lead Capture (V3)
|
||||
*/
|
||||
export async function create_ae_obj__exhibit_tracking({
|
||||
api_cfg,
|
||||
exhibit_id,
|
||||
event_badge_id,
|
||||
external_person_id,
|
||||
group,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
api_cfg,
|
||||
exhibit_id,
|
||||
event_badge_id,
|
||||
external_person_id,
|
||||
group,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any;
|
||||
exhibit_id: string;
|
||||
event_badge_id: string;
|
||||
external_person_id: string;
|
||||
group?: string;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
api_cfg: any;
|
||||
exhibit_id: string;
|
||||
event_badge_id: string;
|
||||
external_person_id: string;
|
||||
group?: string;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
}): Promise<ae_EventExhibitTracking | null> {
|
||||
const result = await api.create_ae_obj_v3({
|
||||
api_cfg,
|
||||
obj_type: 'event_exhibit_tracking',
|
||||
fields: {
|
||||
event_exhibit_id: exhibit_id,
|
||||
event_badge_id: event_badge_id,
|
||||
external_person_id,
|
||||
group
|
||||
},
|
||||
log_lvl
|
||||
});
|
||||
const result = await api.create_nested_obj_v3({
|
||||
api_cfg,
|
||||
for_obj_type: 'event_exhibit',
|
||||
for_obj_id: exhibit_id,
|
||||
obj_type: 'event_exhibit_tracking',
|
||||
fields: {
|
||||
event_badge_id: event_badge_id,
|
||||
external_person_id,
|
||||
group
|
||||
},
|
||||
log_lvl
|
||||
});
|
||||
|
||||
if (result) {
|
||||
const processed = await process_ae_obj__exhibit_tracking_props({ obj_li: [result], log_lvl });
|
||||
const processed_obj = processed[0];
|
||||
if (try_cache) {
|
||||
await db_save_ae_obj_li__ae_obj({
|
||||
db_instance: db_events,
|
||||
table_name: 'exhibit_tracking',
|
||||
obj_li: [processed_obj],
|
||||
properties_to_save: properties_to_save_exhibit_tracking,
|
||||
log_lvl
|
||||
});
|
||||
}
|
||||
return processed_obj;
|
||||
}
|
||||
return null;
|
||||
if (result) {
|
||||
const processed = await process_ae_obj__exhibit_tracking_props({ obj_li: [result], log_lvl });
|
||||
const processed_obj = processed[0];
|
||||
if (try_cache) {
|
||||
await db_save_ae_obj_li__ae_obj({
|
||||
db_instance: db_events,
|
||||
table_name: 'exhibit_tracking',
|
||||
obj_li: [processed_obj],
|
||||
properties_to_save: properties_to_save_exhibit_tracking,
|
||||
log_lvl
|
||||
});
|
||||
}
|
||||
return processed_obj;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lead Update (V3)
|
||||
*/
|
||||
export async function update_ae_obj__exhibit_tracking({
|
||||
api_cfg,
|
||||
exhibit_tracking_id,
|
||||
data,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
api_cfg,
|
||||
exhibit_id,
|
||||
exhibit_tracking_id,
|
||||
data,
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any;
|
||||
exhibit_tracking_id: string;
|
||||
data: any;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
api_cfg: any;
|
||||
exhibit_id: string;
|
||||
exhibit_tracking_id: string;
|
||||
data: any;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
}): Promise<ae_EventExhibitTracking | null> {
|
||||
const result = await api.update_ae_obj_v3({
|
||||
api_cfg,
|
||||
obj_type: 'event_exhibit_tracking',
|
||||
obj_id: exhibit_tracking_id,
|
||||
fields: data,
|
||||
log_lvl
|
||||
});
|
||||
const result = await api.update_nested_obj_v3({
|
||||
api_cfg,
|
||||
for_obj_type: 'event_exhibit',
|
||||
for_obj_id: exhibit_id,
|
||||
obj_type: 'event_exhibit_tracking',
|
||||
obj_id: exhibit_tracking_id,
|
||||
fields: data,
|
||||
log_lvl
|
||||
});
|
||||
|
||||
if (result) {
|
||||
const processed = await process_ae_obj__exhibit_tracking_props({ obj_li: [result], log_lvl });
|
||||
const processed_obj = processed[0];
|
||||
if (try_cache) {
|
||||
await db_save_ae_obj_li__ae_obj({
|
||||
db_instance: db_events,
|
||||
table_name: 'exhibit_tracking',
|
||||
obj_li: [processed_obj],
|
||||
properties_to_save: properties_to_save_exhibit_tracking,
|
||||
log_lvl
|
||||
});
|
||||
}
|
||||
return processed_obj;
|
||||
}
|
||||
return null;
|
||||
if (result) {
|
||||
const processed = await process_ae_obj__exhibit_tracking_props({ obj_li: [result], log_lvl });
|
||||
const processed_obj = processed[0];
|
||||
if (try_cache) {
|
||||
await db_save_ae_obj_li__ae_obj({
|
||||
db_instance: db_events,
|
||||
table_name: 'exhibit_tracking',
|
||||
obj_li: [processed_obj],
|
||||
properties_to_save: properties_to_save_exhibit_tracking,
|
||||
log_lvl
|
||||
});
|
||||
}
|
||||
return processed_obj;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
// This test attaches to the Chrome DevTools Protocol to capture low-level
|
||||
// network events (including those from service workers). Run this with the
|
||||
// real Chrome binary (channel: 'chrome') to reproduce the exact browser
|
||||
// behavior that shows the PNA prompt.
|
||||
|
||||
test('CDP: detect private/local network requests and PNA preflights', async ({ page }) => {
|
||||
const privateRequests: Array<any> = [];
|
||||
|
||||
function isPrivateHostname(hostname: string) {
|
||||
if (!hostname) return false;
|
||||
if (hostname === 'localhost' || hostname.endsWith('.localhost')) return true;
|
||||
if (/^(127)\.|^(10)\.|^(192\.168)\.|^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create a CDP session for the page to listen to Network events
|
||||
const client = await page.context().newCDPSession(page);
|
||||
await client.send('Network.enable');
|
||||
|
||||
client.on('Network.requestWillBeSent', (params) => {
|
||||
try {
|
||||
const url = params.request.url;
|
||||
const hostname = new URL(url).hostname;
|
||||
const headers = params.request.headers || {};
|
||||
const pna = headers['access-control-request-private-network'] === 'true' || headers['Access-Control-Request-Private-Network'] === 'true';
|
||||
|
||||
if (isPrivateHostname(hostname) || pna) {
|
||||
privateRequests.push({
|
||||
url,
|
||||
method: params.request.method,
|
||||
initiator: params.initiator?.type,
|
||||
pnaPreflight: pna,
|
||||
headers
|
||||
});
|
||||
}
|
||||
} catch (e) {}
|
||||
});
|
||||
|
||||
// Also capture console messages to surface the same errors you see in Chrome.
|
||||
const consoleMessages: string[] = [];
|
||||
page.on('console', (msg) => {
|
||||
consoleMessages.push(`${msg.type()}: ${msg.text()}`);
|
||||
});
|
||||
|
||||
// Navigate to the site that triggers the behavior in your environment.
|
||||
await page.goto('http://demo.localhost:5173/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
if (privateRequests.length > 0) {
|
||||
console.error('CDP detected private/local requests or PNA preflights:');
|
||||
for (const r of privateRequests) console.error(JSON.stringify(r, null, 2));
|
||||
}
|
||||
|
||||
if (consoleMessages.length) {
|
||||
console.log('Captured console messages:');
|
||||
consoleMessages.forEach((m) => console.log(m));
|
||||
}
|
||||
|
||||
expect(privateRequests.length, 'No private/local network requests or PNA preflights should be made').toBe(0);
|
||||
});
|
||||
84
tests/disabled/private_network_combined.test.ts
Normal file
84
tests/disabled/private_network_combined.test.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
// Combined private-network detection test.
|
||||
// Keeps both high-level request observation and low-level CDP capture in one
|
||||
// place so you can run this when troubleshooting PNA / localhost access issues.
|
||||
|
||||
function isPrivateHostname(hostname: string) {
|
||||
if (!hostname) return false;
|
||||
if (hostname === 'localhost' || hostname.endsWith('.localhost')) return true;
|
||||
if (/^(127)\.|^(10)\.|^(192\.168)\.|^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
test('detect private/local network requests and PNA preflights (combined)', async ({ page }) => {
|
||||
const privateRequests: Array<any> = [];
|
||||
const cdpPrivateRequests: Array<any> = [];
|
||||
const consoleMessages: string[] = [];
|
||||
|
||||
page.on('console', (msg) => consoleMessages.push(`${msg.type()}: ${msg.text()}`));
|
||||
|
||||
page.on('request', (request) => {
|
||||
try {
|
||||
const url = new URL(request.url());
|
||||
const headers = request.headers();
|
||||
const hostname = url.hostname;
|
||||
const pnaHeader = (headers['access-control-request-private-network'] || headers['Access-Control-Request-Private-Network']);
|
||||
|
||||
if (isPrivateHostname(hostname) || pnaHeader === 'true') {
|
||||
privateRequests.push({ url: request.url(), method: request.method(), resourceType: request.resourceType(), pnaPreflight: pnaHeader === 'true', headers });
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore parse errors
|
||||
}
|
||||
});
|
||||
|
||||
page.on('requestfailed', (r) => {
|
||||
try {
|
||||
const url = new URL(r.url());
|
||||
if (isPrivateHostname(url.hostname)) {
|
||||
privateRequests.push({ url: r.url(), method: r.method(), failure: r.failure() });
|
||||
}
|
||||
} catch (e) {}
|
||||
});
|
||||
|
||||
// CDP capture (may not be available in some browser channels)
|
||||
try {
|
||||
const client = await page.context().newCDPSession(page);
|
||||
await client.send('Network.enable');
|
||||
client.on('Network.requestWillBeSent', (params) => {
|
||||
try {
|
||||
const url = params.request.url;
|
||||
const hostname = new URL(url).hostname;
|
||||
const headers = params.request.headers || {};
|
||||
const pna = headers['access-control-request-private-network'] === 'true' || headers['Access-Control-Request-Private-Network'] === 'true';
|
||||
if (isPrivateHostname(hostname) || pna) {
|
||||
cdpPrivateRequests.push({ url, method: params.request.method, initiator: params.initiator?.type, pnaPreflight: pna, headers });
|
||||
}
|
||||
} catch (e) {}
|
||||
});
|
||||
} catch (e) {
|
||||
// CDP not available in this context; continue with high-level capture
|
||||
}
|
||||
|
||||
await page.goto('http://demo.localhost:5173/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Report findings for easier debugging
|
||||
if (privateRequests.length > 0 || cdpPrivateRequests.length > 0 || consoleMessages.length > 0) {
|
||||
if (consoleMessages.length) {
|
||||
console.log('Console messages:');
|
||||
consoleMessages.forEach((m) => console.log(m));
|
||||
}
|
||||
if (privateRequests.length) {
|
||||
console.error('Detected private/local requests or PNA preflights (high-level):');
|
||||
for (const r of privateRequests) console.error(JSON.stringify(r, null, 2));
|
||||
}
|
||||
if (cdpPrivateRequests.length) {
|
||||
console.error('Detected private/local requests or PNA preflights (CDP):');
|
||||
for (const r of cdpPrivateRequests) console.error(JSON.stringify(r, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
expect(privateRequests.length + cdpPrivateRequests.length, 'No private/local network requests or PNA preflights should be made').toBe(0);
|
||||
});
|
||||
@@ -1,62 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
// Detect requests to private/local address space or PNA preflight headers.
|
||||
test('detect private/local network requests and PNA preflights', async ({ page }) => {
|
||||
const privateRequests: Array<any> = [];
|
||||
|
||||
function isPrivateHostname(hostname: string) {
|
||||
if (!hostname) return false;
|
||||
// hostnames that resolve to local addresses or explicitly localhost
|
||||
if (hostname === 'localhost' || hostname.endsWith('.localhost')) return true;
|
||||
// IPv4 private ranges
|
||||
if (/^(127)\.|^(10)\.|^(192\.168)\.|^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
page.on('request', (request) => {
|
||||
try {
|
||||
const url = new URL(request.url());
|
||||
const headers = request.headers();
|
||||
const hostname = url.hostname;
|
||||
|
||||
const pnaHeader = (headers['access-control-request-private-network'] || headers['Access-Control-Request-Private-Network']);
|
||||
|
||||
if (isPrivateHostname(hostname) || pnaHeader === 'true') {
|
||||
privateRequests.push({
|
||||
url: request.url(),
|
||||
method: request.method(),
|
||||
resourceType: request.resourceType(),
|
||||
pnaPreflight: pnaHeader === 'true',
|
||||
headers
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore parse errors
|
||||
}
|
||||
});
|
||||
|
||||
page.on('requestfailed', (r) => {
|
||||
// capture failed requests that might be private and blocked
|
||||
try {
|
||||
const url = new URL(r.url());
|
||||
if (isPrivateHostname(url.hostname)) {
|
||||
privateRequests.push({ url: r.url(), method: r.method(), failure: r.failure() });
|
||||
}
|
||||
} catch (e) {}
|
||||
});
|
||||
|
||||
// Navigate to the dev site used in other tests.
|
||||
await page.goto('http://demo.localhost:5173/');
|
||||
|
||||
// Wait for network to settle.
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
if (privateRequests.length > 0) {
|
||||
console.error('Detected private/local requests or PNA preflights:');
|
||||
for (const r of privateRequests) {
|
||||
console.error(JSON.stringify(r, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
expect(privateRequests.length, 'No private/local network requests or PNA preflights should be made').toBe(0);
|
||||
});
|
||||
@@ -1,105 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { ae_app_local_data_defaults } from '../_helpers/ae_defaults';
|
||||
|
||||
test.describe('V3 API Header Integrity', () => {
|
||||
test.setTimeout(7000);
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Log browser console errors to the terminal for easier debugging.
|
||||
page.on('pageerror', (err) => console.error(`BROWSER ERROR: ${err.message}`));
|
||||
page.on('console', (msg) => {
|
||||
if (msg.type() === 'error' || msg.type() === 'warn') {
|
||||
console.error(`BROWSER [${msg.type().toUpperCase()}]: ${msg.text()}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Mock all local /v3/ API requests so tests are deterministic.
|
||||
await page.route('**/v3/**', async (route) => {
|
||||
const req = route.request();
|
||||
const url = req.url();
|
||||
|
||||
// Handshake mock so app boots predictably
|
||||
if (url.includes('site_domain/search')) {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
data: [
|
||||
{
|
||||
id: 'test-site-domain-id',
|
||||
account_id: 'test-account-id',
|
||||
site_id: 'test-site-id',
|
||||
account_name: 'Test Account',
|
||||
enable: '1',
|
||||
cfg_json: {}
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Provide lightweight responses for endpoints we assert on
|
||||
if (url.includes('/v3/lookup/country/list')) {
|
||||
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: [] }) });
|
||||
}
|
||||
|
||||
if (url.includes('/v3/crud/user/search')) {
|
||||
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: [] }) });
|
||||
}
|
||||
|
||||
// Default handler
|
||||
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: [] }) });
|
||||
});
|
||||
});
|
||||
|
||||
test('Verify lookup requests include the unauthenticated bypass header', async ({ page }) => {
|
||||
// Prepare the browser's localStorage with the necessary state for this test.
|
||||
await page.addInitScript((defaults) => {
|
||||
const testData = { ...defaults, account_id: 'test-account-id', manager_access: true };
|
||||
window.localStorage.setItem('ae_loc', JSON.stringify(testData));
|
||||
}, ae_app_local_data_defaults);
|
||||
|
||||
// Start waiting for the lookup request *before* navigating.
|
||||
const requestPromise = page.waitForRequest((request) =>
|
||||
request.url().includes('/v3/lookup/country/list')
|
||||
);
|
||||
|
||||
// Navigate to the page that triggers the lookup.
|
||||
await page.goto('/core/lookups');
|
||||
|
||||
// Wait for the request to be captured.
|
||||
const request = await requestPromise;
|
||||
const headers = request.headers();
|
||||
|
||||
// Assert that the correct bypass headers were used.
|
||||
expect(headers['x-no-account-id']).toBe('Nothing to See Here');
|
||||
expect(headers['x-aether-api-key']).toBeDefined();
|
||||
});
|
||||
|
||||
test('Verify Account ID Scavenging from localStorage on CRUD requests', async ({ page }) => {
|
||||
const testAccountId = 'scavenged-account-id-123';
|
||||
|
||||
// Prepare the browser's localStorage with a specific account ID.
|
||||
await page.addInitScript(({ defaults, id }) => {
|
||||
const testData = { ...defaults, account_id: id, manager_access: true };
|
||||
window.localStorage.setItem('ae_loc', JSON.stringify(testData));
|
||||
},{ defaults: ae_app_local_data_defaults, id: testAccountId });
|
||||
|
||||
// Start waiting for the CRUD request.
|
||||
const requestPromise = page.waitForRequest((request) => {
|
||||
const url = request.url();
|
||||
// The /core/users page triggers a 'user' search on load.
|
||||
return url.includes('/v3/crud/user/search');
|
||||
});
|
||||
|
||||
// Navigate to a page that is guaranteed to make a standard CRUD call.
|
||||
await page.goto('/core/users');
|
||||
|
||||
// Wait for the request to be captured.
|
||||
const request = await requestPromise;
|
||||
const headers = request.headers();
|
||||
|
||||
// Assert that the scavenged account ID was correctly included in the header.
|
||||
expect(headers['x-account-id']).toBe(testAccountId);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user