diff --git a/OPENAPI_DOC.yml b/OPENAPI_DOC.yml index f72bf004..9f25d65e 100644 --- a/OPENAPI_DOC.yml +++ b/OPENAPI_DOC.yml @@ -8174,9 +8174,23 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' /api/engine/v2/chatgpt/plugin/{system_id}/capabilities: + summary: Gives you the capabilities of a PlaceOS system, such as a meeting room + or building. + description: 'Gives you the capabilities of a PlaceOS system, such as a meeting + room or building. + + Call capabilities first: it describes what the system can do and who the user + is, + + including their name, email and local time. Then call function_schema for a + + capability to see its functions, and call_function to run one. Calls are made + as the + + signed in user, with their permissions.' get: summary: obtain the list of capabilities that this API can provide, must be - called if the user requests some related functionality, to abtain details + called if the user requests some related functionality, to obtain details of the current user such as their name and email address and the current local time of the user. tags: @@ -8274,6 +8288,20 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' /api/engine/v2/chatgpt/plugin/{system_id}/function_schema/{capability_id}: + summary: Gives you the capabilities of a PlaceOS system, such as a meeting room + or building. + description: 'Gives you the capabilities of a PlaceOS system, such as a meeting + room or building. + + Call capabilities first: it describes what the system can do and who the user + is, + + including their name, email and local time. Then call function_schema for a + + capability to see its functions, and call_function to run one. Calls are made + as the + + signed in user, with their permissions.' get: summary: if a request could benefit from a capability, obtain the list of function schemas by providing the id string @@ -8381,6 +8409,20 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' /api/engine/v2/chatgpt/plugin/{system_id}/call_function/{capability_id}/{function_name}: + summary: Gives you the capabilities of a PlaceOS system, such as a meeting room + or building. + description: 'Gives you the capabilities of a PlaceOS system, such as a meeting + room or building. + + Call capabilities first: it describes what the system can do and who the user + is, + + including their name, email and local time. Then call function_schema for a + + capability to see its functions, and call_function to run one. Calls are made + as the + + signed in user, with their permissions.' post: summary: Executes functionality offered by a capability, you'll need to obtain the function schema to perform requests. Then to use this operation you'll @@ -8392,7 +8434,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/NamedTuple_function_params__String_' + $ref: '#/components/schemas/NamedTuple_function_params__Hash_String__JSON__Any____String_' required: true parameters: - name: system_id @@ -9035,7 +9077,11 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' post: - summary: add a new domain + summary: add a new domain. Organisation admins create domains in their own + description: 'add a new domain. Organisation admins create domains in their + own + + organisation; cluster admins name the organisation.' tags: - Domains operationId: PlaceOS::Api::Domains_create @@ -9045,7 +9091,15 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Model__Authority' required: true - parameters: [] + parameters: + - name: organisation_id + in: query + description: the organisation that owns the new domain + example: 0192f1c4-7a6b-7c4d-9f3e-1a2b3c4d5e6f + schema: + type: string + format: uuid + nullable: true responses: 201: description: Created @@ -9348,6 +9402,14 @@ paths: required: true schema: type: string + - name: organisation_id + in: query + description: move the domain to this organisation (cluster admins only) + example: 0192f1c4-7a6b-7c4d-9f3e-1a2b3c4d5e6f + schema: + type: string + format: uuid + nullable: true responses: 200: description: OK @@ -9542,6 +9604,14 @@ paths: required: true schema: type: string + - name: organisation_id + in: query + description: move the domain to this organisation (cluster admins only) + example: 0192f1c4-7a6b-7c4d-9f3e-1a2b3c4d5e6f + schema: + type: string + format: uuid + nullable: true responses: 200: description: OK @@ -11586,7 +11656,16 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Model__Module' required: true - parameters: [] + parameters: + - name: organisation_id + in: query + description: the organisation that owns a module not bound to a system (cluster + admins only) + example: 0192f1c4-7a6b-7c4d-9f3e-1a2b3c4d5e6f + schema: + type: string + format: uuid + nullable: true responses: 201: description: Created @@ -12983,8 +13062,8 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' /api/engine/v2/systems: - summary: 'Systems, typically rooms or spaces: a collection of modules (devices) - placed in zones' + summary: Systems are typically rooms or spaces, they can represent digital signage + endpoints and are a collection of modules (devices) placed in zones get: summary: list the systems in a cluster tags: @@ -13199,7 +13278,11 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' post: - summary: adds a new system + summary: adds a new system. The system belongs to its zones' organisation, or + description: 'adds a new system. The system belongs to its zones'' organisation, + or + + the caller''s when the zones are unowned.' tags: - Systems operationId: PlaceOS::Api::Systems_create @@ -13296,8 +13379,8 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' /api/engine/v2/systems/with_emails: - summary: 'Systems, typically rooms or spaces: a collection of modules (devices) - placed in zones' + summary: Systems are typically rooms or spaces, they can represent digital signage + endpoints and are a collection of modules (devices) placed in zones get: summary: Finds all the systems with the specified email address tags: @@ -13401,8 +13484,8 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' /api/engine/v2/systems/{sys_id}: - summary: 'Systems, typically rooms or spaces: a collection of modules (devices) - placed in zones' + summary: Systems are typically rooms or spaces, they can represent digital signage + endpoints and are a collection of modules (devices) placed in zones get: summary: Renders a control system tags: @@ -13822,8 +13905,8 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' /api/engine/v2/systems/{sys_id}/zones: - summary: 'Systems, typically rooms or spaces: a collection of modules (devices) - placed in zones' + summary: Systems are typically rooms or spaces, they can represent digital signage + endpoints and are a collection of modules (devices) placed in zones get: summary: Return all zones for this system tags: @@ -13923,8 +14006,8 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' /api/engine/v2/systems/{sys_id}/metadata: - summary: 'Systems, typically rooms or spaces: a collection of modules (devices) - placed in zones' + summary: Systems are typically rooms or spaces, they can represent digital signage + endpoints and are a collection of modules (devices) placed in zones get: summary: Return metadata for the system tags: @@ -14027,8 +14110,8 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' /api/engine/v2/systems/{sys_id}/settings: - summary: 'Systems, typically rooms or spaces: a collection of modules (devices) - placed in zones' + summary: Systems are typically rooms or spaces, they can represent digital signage + endpoints and are a collection of modules (devices) placed in zones get: summary: Receive the collated settings for a system tags: @@ -14128,8 +14211,8 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' /api/engine/v2/systems/{sys_id}/module/{module_id}: - summary: 'Systems, typically rooms or spaces: a collection of modules (devices) - placed in zones' + summary: Systems are typically rooms or spaces, they can represent digital signage + endpoints and are a collection of modules (devices) placed in zones put: summary: Adds the module to the system if it doesn't already exist tags: @@ -14333,8 +14416,8 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' /api/engine/v2/systems/{sys_id}/start: - summary: 'Systems, typically rooms or spaces: a collection of modules (devices) - placed in zones' + summary: Systems are typically rooms or spaces, they can represent digital signage + endpoints and are a collection of modules (devices) placed in zones post: summary: Start modules tags: @@ -14434,8 +14517,8 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' /api/engine/v2/systems/{sys_id}/stop: - summary: 'Systems, typically rooms or spaces: a collection of modules (devices) - placed in zones' + summary: Systems are typically rooms or spaces, they can represent digital signage + endpoints and are a collection of modules (devices) placed in zones post: summary: Stop modules tags: @@ -14535,8 +14618,8 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' /api/engine/v2/systems/{sys_id}/{module_slug}/{method}: - summary: 'Systems, typically rooms or spaces: a collection of modules (devices) - placed in zones' + summary: Systems are typically rooms or spaces, they can represent digital signage + endpoints and are a collection of modules (devices) placed in zones post: summary: Runs a function in a system module tags: @@ -14651,8 +14734,8 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' /api/engine/v2/systems/{sys_id}/types: - summary: 'Systems, typically rooms or spaces: a collection of modules (devices) - placed in zones' + summary: Systems are typically rooms or spaces, they can represent digital signage + endpoints and are a collection of modules (devices) placed in zones get: summary: Look-up a module types in a system, returning a count of each type tags: @@ -14750,8 +14833,8 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' /api/engine/v2/systems/{sys_id}/{module_slug}: - summary: 'Systems, typically rooms or spaces: a collection of modules (devices) - placed in zones' + summary: Systems are typically rooms or spaces, they can represent digital signage + endpoints and are a collection of modules (devices) placed in zones get: summary: Returns the state of an associated module tags: @@ -14857,8 +14940,8 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' /api/engine/v2/systems/{sys_id}/{module_slug}/{key}: - summary: 'Systems, typically rooms or spaces: a collection of modules (devices) - placed in zones' + summary: Systems are typically rooms or spaces, they can represent digital signage + endpoints and are a collection of modules (devices) placed in zones get: summary: Returns the state lookup for a given key on a module tags: @@ -14971,8 +15054,8 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' /api/engine/v2/systems/{sys_id}/functions/{module_slug}: - summary: 'Systems, typically rooms or spaces: a collection of modules (devices) - placed in zones' + summary: Systems are typically rooms or spaces, they can represent digital signage + endpoints and are a collection of modules (devices) placed in zones get: summary: Lists functions available on the driver description: 'Lists functions available on the driver @@ -15081,8 +15164,8 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' /api/engine/v2/systems/control: - summary: 'Systems, typically rooms or spaces: a collection of modules (devices) - placed in zones' + summary: Systems are typically rooms or spaces, they can represent digital signage + endpoints and are a collection of modules (devices) placed in zones get: summary: the websocket API endpoint description: 'the websocket API endpoint @@ -17447,52 +17530,59 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/groups: - summary: Groups of users, used to grant permissions to zones and features - get: - summary: List groups. sys_admin sees all within the current authority; other - description: 'List groups. sys_admin sees all within the current authority; - other - - users see only groups they''re a member of (direct or transitive). - - Optionally filter by `parent_id` for tree queries (pass the - - literal string `root` for root-level groups). Pass `q` for a + /api/engine/v2/grants: + summary: 'Grants: explicit, revocable, optionally time-boxed reach for a user + into a' + description: 'Grants: explicit, revocable, optionally time-boxed reach for a user + into a - case-insensitive substring search on `name`.' + partner, organisation or domain they do not belong to (PPT-526)' + get: + summary: grants on a scope, or held by a user tags: - - Groups - operationId: PlaceOS::Api::Groups_index + - Grants + operationId: PlaceOS::Api::Grants_index parameters: - - name: parent_id + - name: scope_type in: query - description: filter by parent group id; pass 'root' for root-level groups + description: partner, organisation or authority + example: organisation schema: type: string nullable: true - - name: q + - name: scope_id in: query - description: case-insensitive substring search on name (SQL ILIKE) + description: the id of the partner, organisation or domain + example: 0192f1c4-7a6b-7c4d-9f3e-1a2b3c4d5e6f schema: type: string nullable: true - - name: include_children_count + - name: user_id in: query - description: include children_count for each group (useful for tree views) - example: "true" + description: grants held by this user + example: user-1234 schema: - type: boolean + type: string + nullable: true + - name: q + in: query + description: filters results by the given text; words match as prefixes against + the resource's searchable fields and all words must match + schema: + type: string - name: limit in: query + description: the maximum number of results to return + example: "10000" schema: type: integer - format: Int32 + format: UInt32 - name: offset in: query + description: the starting offset of the result set, used to implement pagination schema: type: integer - format: Int32 + format: UInt32 - name: ref in: query description: deprecated, ignored — pagination follows the `Link` header's @@ -17515,7 +17605,7 @@ paths: schema: type: array items: - $ref: '#/components/schemas/PlaceOS__Model__Group' + $ref: '#/components/schemas/PlaceOS__Model__Grant' 409: description: Conflict content: @@ -17595,18 +17685,14 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' post: - summary: Create a new group. A root group (parent_id nil) requires - description: 'Create a new group. A root group (parent_id nil) requires - - sys_admin; a child group requires Manage on the parent.' tags: - - Groups - operationId: PlaceOS::Api::Groups_create + - Grants + operationId: PlaceOS::Api::Grants_create requestBody: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Group' + $ref: '#/components/schemas/PlaceOS__Api__Grants__GrantBody' required: true parameters: [] responses: @@ -17615,7 +17701,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Group' + $ref: '#/components/schemas/PlaceOS__Model__Grant' 409: description: Conflict content: @@ -17694,13 +17780,17 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/groups/{id}: - summary: Groups of users, used to grant permissions to zones and features + /api/engine/v2/grants/{id}: + summary: 'Grants: explicit, revocable, optionally time-boxed reach for a user + into a' + description: 'Grants: explicit, revocable, optionally time-boxed reach for a user + into a + + partner, organisation or domain they do not belong to (PPT-526)' get: - summary: Show group details. Visible if sys_admin or a member. tags: - - Groups - operationId: PlaceOS::Api::Groups_show + - Grants + operationId: PlaceOS::Api::Grants_show parameters: - name: id in: path @@ -17714,7 +17804,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Group' + $ref: '#/components/schemas/PlaceOS__Model__Grant' 409: description: Conflict content: @@ -17793,20 +17883,10 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - put: - summary: Update the group (name / description / parent / features). - description: 'Update the group (name / description / parent / features). - - Changing `features` requires Manage on the parent group.' + delete: tags: - - Groups - operationId: PlaceOS::Api::Groups_update - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Group' - required: true + - Grants + operationId: PlaceOS::Api::Grants_destroy parameters: - name: id in: path @@ -17815,12 +17895,8 @@ paths: type: string format: uuid responses: - 200: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Group' + 202: + description: Accepted 409: description: Conflict content: @@ -17899,21 +17975,75 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - delete: - summary: Destroy a group (cascades children and junctions via FK). + /api/engine/v2/groups: + summary: Groups of users, used to grant permissions to zones and features + get: + summary: List groups. sys_admin sees all within the current authority; other + description: 'List groups. sys_admin sees all within the current authority; + other + + users see only groups they''re a member of (direct or transitive). + + Optionally filter by `parent_id` for tree queries (pass the + + literal string `root` for root-level groups). Pass `q` for a + + case-insensitive substring search on `name`.' tags: - Groups - operationId: PlaceOS::Api::Groups_destroy + operationId: PlaceOS::Api::Groups_index parameters: - - name: id - in: path - required: true + - name: parent_id + in: query + description: filter by parent group id; pass 'root' for root-level groups schema: type: string - format: uuid + nullable: true + - name: q + in: query + description: case-insensitive substring search on name (SQL ILIKE) + schema: + type: string + nullable: true + - name: include_children_count + in: query + description: include children_count for each group (useful for tree views) + example: "true" + schema: + type: boolean + - name: limit + in: query + schema: + type: integer + format: Int32 + - name: offset + in: query + schema: + type: integer + format: Int32 + - name: ref + in: query + description: deprecated, ignored — pagination follows the `Link` header's + offset + schema: + type: string + nullable: true + - name: fields + in: query + description: deprecated, ignored — search covers the resource's indexed fields + schema: + type: array + items: + type: string responses: - 202: - description: Accepted + 200: + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PlaceOS__Model__Group' 409: description: Conflict content: @@ -17992,30 +18122,24 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - patch: - summary: Update the group (name / description / parent / features). - description: 'Update the group (name / description / parent / features). + post: + summary: Create a new group. A root group (parent_id nil) requires + description: 'Create a new group. A root group (parent_id nil) requires - Changing `features` requires Manage on the parent group.' + sys_admin; a child group requires Manage on the parent.' tags: - Groups - operationId: PlaceOS::Api::Groups_update{2} + operationId: PlaceOS::Api::Groups_create requestBody: content: application/json: schema: $ref: '#/components/schemas/PlaceOS__Model__Group' required: true - parameters: - - name: id - in: path - required: true - schema: - type: string - format: uuid + parameters: [] responses: - 200: - description: OK + 201: + description: Created content: application/json: schema: @@ -18098,18 +18222,13 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/groups/{id}/features: + /api/engine/v2/groups/{id}: summary: Groups of users, used to grant permissions to zones and features get: - summary: Effective feature flags for this group. - description: 'Effective feature flags for this group. - - The group''s own `features` merged over every ancestor''s, per subsystem, - - with the deepest group winning each key. Shape: `{subsystem => {key => value}}`.' + summary: Show group details. Visible if sys_admin or a member. tags: - Groups - operationId: PlaceOS::Api::Groups_features + operationId: PlaceOS::Api::Groups_show parameters: - name: id in: path @@ -18117,20 +18236,13 @@ paths: schema: type: string format: uuid - - name: subsystem - in: query - description: only return this subsystem's features - example: signage - schema: - type: string - nullable: true responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/Hash_String__Hash_String__JSON__Any__' + $ref: '#/components/schemas/PlaceOS__Model__Group' 409: description: Conflict content: @@ -18209,38 +18321,34 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/groups/ad_groups/sync: - summary: Groups of users, used to grant permissions to zones and features - post: - summary: Sync a user's group memberships with their AD groups. - description: 'Sync a user''s group memberships with their AD groups. - - Uses each group''s `ad_group_mappings` to add the user to mapped groups - - and remove memberships that were auto-assigned from AD groups the user - - has left; manually added memberships are untouched. Returns the user''s + put: + summary: Update the group (name / description / parent / features). + description: 'Update the group (name / description / parent / features). - group memberships in the current authority. Requires support or sys_admin.' + Changing `features` requires Manage on the parent group.' tags: - Groups - operationId: PlaceOS::Api::Groups_sync_ad_groups + operationId: PlaceOS::Api::Groups_update requestBody: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Api__Groups__AdGroupSync' + $ref: '#/components/schemas/PlaceOS__Model__Group' required: true - parameters: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Model__GroupUser' + $ref: '#/components/schemas/PlaceOS__Model__Group' 409: description: Conflict content: @@ -18319,29 +18427,21 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/groups/current: - summary: Groups of users, used to grant permissions to zones and features - get: + delete: + summary: Destroy a group (cascades children and junctions via FK). tags: - Groups - operationId: PlaceOS::Api::Groups_current + operationId: PlaceOS::Api::Groups_destroy parameters: - - name: subsystem - in: query - description: filter to groups participating in this subsystem (e.g. 'signage') - example: signage + - name: id + in: path + required: true schema: type: string - nullable: true + format: uuid responses: - 200: - description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Api__Groups__CurrentGroup' + 202: + description: Accepted 409: description: Conflict content: @@ -18420,62 +18520,34 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/group_history: - summary: Audit history of changes to groups and their memberships - get: - summary: List audit entries. sys_admin sees everything; other callers must - description: 'List audit entries. sys_admin sees everything; other callers must + patch: + summary: Update the group (name / description / parent / features). + description: 'Update the group (name / description / parent / features). - pass `group_id=` and have Manage on it.' + Changing `features` requires Manage on the parent group.' tags: - - History - operationId: PlaceOS::Api::Groups::History_index + - Groups + operationId: PlaceOS::Api::Groups_update{2} + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Group' + required: true parameters: - - name: group_id - in: query + - name: id + in: path + required: true schema: type: string format: uuid - nullable: true - - name: limit - in: query - schema: - type: integer - format: Int32 - - name: offset - in: query - schema: - type: integer - format: Int32 - - name: q - in: query - description: filters results by the given text; words match as prefixes against - the resource's searchable fields and all words must match - schema: - type: string - - name: ref - in: query - description: deprecated, ignored — pagination follows the `Link` header's - offset - schema: - type: string - nullable: true - - name: fields - in: query - description: deprecated, ignored — search covers the resource's indexed fields - schema: - type: array - items: - type: string responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Model__GroupHistory' + $ref: '#/components/schemas/PlaceOS__Model__Group' 409: description: Conflict content: @@ -18554,12 +18626,18 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/group_history/{id}: - summary: Audit history of changes to groups and their memberships + /api/engine/v2/groups/{id}/features: + summary: Groups of users, used to grant permissions to zones and features get: + summary: Effective feature flags for this group. + description: 'Effective feature flags for this group. + + The group''s own `features` merged over every ancestor''s, per subsystem, + + with the deepest group winning each key. Shape: `{subsystem => {key => value}}`.' tags: - - History - operationId: PlaceOS::Api::Groups::History_show + - Groups + operationId: PlaceOS::Api::Groups_features parameters: - name: id in: path @@ -18567,13 +18645,20 @@ paths: schema: type: string format: uuid + - name: subsystem + in: query + description: only return this subsystem's features + example: signage + schema: + type: string + nullable: true responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__GroupHistory' + $ref: '#/components/schemas/Hash_String__Hash_String__JSON__Any__' 409: description: Conflict content: @@ -18652,53 +18737,29 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/group_invitations: - summary: Invitations for users to join groups - get: - summary: List invitations. Optionally filter by `group_id`. Non-admin - description: 'List invitations. Optionally filter by `group_id`. Non-admin + /api/engine/v2/groups/ad_groups/sync: + summary: Groups of users, used to grant permissions to zones and features + post: + summary: Sync a user's group memberships with their AD groups. + description: 'Sync a user''s group memberships with their AD groups. - callers must have Manage on the target group.' + Uses each group''s `ad_group_mappings` to add the user to mapped groups + + and remove memberships that were auto-assigned from AD groups the user + + has left; manually added memberships are untouched. Returns the user''s + + group memberships in the current authority. Requires support or sys_admin.' tags: - - Invitations - operationId: PlaceOS::Api::Groups::Invitations_index - parameters: - - name: group_id - in: query - schema: - type: string - format: uuid - nullable: true - - name: limit - in: query - schema: - type: integer - format: Int32 - - name: offset - in: query - schema: - type: integer - format: Int32 - - name: q - in: query - description: filters results by the given text; words match as prefixes against - the resource's searchable fields and all words must match - schema: - type: string - - name: ref - in: query - description: deprecated, ignored — pagination follows the `Link` header's - offset - schema: - type: string - nullable: true - - name: fields - in: query - description: deprecated, ignored — search covers the resource's indexed fields - schema: - type: array - items: - type: string + - Groups + operationId: PlaceOS::Api::Groups_sync_ad_groups + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Groups__AdGroupSync' + required: true + parameters: [] responses: 200: description: OK @@ -18707,7 +18768,7 @@ paths: schema: type: array items: - $ref: '#/components/schemas/PlaceOS__Model__GroupInvitation' + $ref: '#/components/schemas/PlaceOS__Model__GroupUser' 409: description: Conflict content: @@ -18786,30 +18847,29 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - post: - summary: Custom body-driven create so we can return the plaintext secret - description: 'Custom body-driven create so we can return the plaintext secret - - alongside the invitation. Authorisation check is inline here (not - - a before_action) so the body is consumed exactly once.' + /api/engine/v2/groups/current: + summary: Groups of users, used to grant permissions to zones and features + get: tags: - - Invitations - operationId: PlaceOS::Api::Groups::Invitations_create - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Groups__Invitations__InvitationCreatePayload' - required: true - parameters: [] + - Groups + operationId: PlaceOS::Api::Groups_current + parameters: + - name: subsystem + in: query + description: filter to groups participating in this subsystem (e.g. 'signage') + example: signage + schema: + type: string + nullable: true responses: - 201: - description: Created + 200: + description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Api__Groups__Invitations__InvitationCreatedResponse' + type: array + items: + $ref: '#/components/schemas/PlaceOS__Api__Groups__CurrentGroup' 409: description: Conflict content: @@ -18888,26 +18948,62 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/group_invitations/{id}: - summary: Invitations for users to join groups + /api/engine/v2/group_history: + summary: Audit history of changes to groups and their memberships get: + summary: List audit entries. sys_admin sees everything; other callers must + description: 'List audit entries. sys_admin sees everything; other callers must + + pass `group_id=` and have Manage on it.' tags: - - Invitations - operationId: PlaceOS::Api::Groups::Invitations_show + - History + operationId: PlaceOS::Api::Groups::History_index parameters: - - name: id - in: path - required: true + - name: group_id + in: query schema: type: string format: uuid + nullable: true + - name: limit + in: query + schema: + type: integer + format: Int32 + - name: offset + in: query + schema: + type: integer + format: Int32 + - name: q + in: query + description: filters results by the given text; words match as prefixes against + the resource's searchable fields and all words must match + schema: + type: string + - name: ref + in: query + description: deprecated, ignored — pagination follows the `Link` header's + offset + schema: + type: string + nullable: true + - name: fields + in: query + description: deprecated, ignored — search covers the resource's indexed fields + schema: + type: array + items: + type: string responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__GroupInvitation' + type: array + items: + $ref: '#/components/schemas/PlaceOS__Model__GroupHistory' 409: description: Conflict content: @@ -18986,10 +19082,12 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - delete: + /api/engine/v2/group_history/{id}: + summary: Audit history of changes to groups and their memberships + get: tags: - - Invitations - operationId: PlaceOS::Api::Groups::Invitations_destroy + - History + operationId: PlaceOS::Api::Groups::History_show parameters: - name: id in: path @@ -18998,8 +19096,12 @@ paths: type: string format: uuid responses: - 202: - description: Accepted + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__GroupHistory' 409: description: Conflict content: @@ -19078,30 +19180,62 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/group_invitations/{id}/accept: + /api/engine/v2/group_invitations: summary: Invitations for users to join groups - post: - summary: Accept the invitation, materialising it into a GroupUser for the - description: 'Accept the invitation, materialising it into a GroupUser for the + get: + summary: List invitations. Optionally filter by `group_id`. Non-admin + description: 'List invitations. Optionally filter by `group_id`. Non-admin - current user. The invitation row is destroyed on success.' + callers must have Manage on the target group.' tags: - Invitations - operationId: PlaceOS::Api::Groups::Invitations_accept + operationId: PlaceOS::Api::Groups::Invitations_index parameters: - - name: id - in: path - required: true + - name: group_id + in: query schema: type: string format: uuid + nullable: true + - name: limit + in: query + schema: + type: integer + format: Int32 + - name: offset + in: query + schema: + type: integer + format: Int32 + - name: q + in: query + description: filters results by the given text; words match as prefixes against + the resource's searchable fields and all words must match + schema: + type: string + - name: ref + in: query + description: deprecated, ignored — pagination follows the `Link` header's + offset + schema: + type: string + nullable: true + - name: fields + in: query + description: deprecated, ignored — search covers the resource's indexed fields + schema: + type: array + items: + type: string responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__GroupUser' + type: array + items: + $ref: '#/components/schemas/PlaceOS__Model__GroupInvitation' 409: description: Conflict content: @@ -19180,69 +19314,30 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/group_users: - summary: Group memberships, the users in each group and their permissions - get: - summary: GroupUser rows. Optional `group_id` scopes to one group; - description: 'GroupUser rows. Optional `group_id` scopes to one group; + post: + summary: Custom body-driven create so we can return the plaintext secret + description: 'Custom body-driven create so we can return the plaintext secret - `user_id=me` returns the current user''s own rows. Non-admin + alongside the invitation. Authorisation check is inline here (not - callers without Manage on the specified group see only their own.' + a before_action) so the body is consumed exactly once.' tags: - - Users - operationId: PlaceOS::Api::Groups::Users_index - parameters: - - name: group_id - in: query - schema: - type: string - format: uuid - nullable: true - - name: user_id - in: query - schema: - type: string - nullable: true - - name: limit - in: query - schema: - type: integer - format: Int32 - - name: offset - in: query - schema: - type: integer - format: Int32 - - name: q - in: query - description: filters results by the given text; words match as prefixes against - the resource's searchable fields and all words must match - schema: - type: string - - name: ref - in: query - description: deprecated, ignored — pagination follows the `Link` header's - offset - schema: - type: string - nullable: true - - name: fields - in: query - description: deprecated, ignored — search covers the resource's indexed fields - schema: - type: array - items: - type: string + - Invitations + operationId: PlaceOS::Api::Groups::Invitations_create + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Groups__Invitations__InvitationCreatePayload' + required: true + parameters: [] responses: - 200: - description: OK + 201: + description: Created content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Model__GroupUser' + $ref: '#/components/schemas/PlaceOS__Api__Groups__Invitations__InvitationCreatedResponse' 409: description: Conflict content: @@ -19321,24 +19416,26 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - post: + /api/engine/v2/group_invitations/{id}: + summary: Invitations for users to join groups + get: tags: - - Users - operationId: PlaceOS::Api::Groups::Users_create - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__GroupUser' + - Invitations + operationId: PlaceOS::Api::Groups::Invitations_show + parameters: + - name: id + in: path required: true - parameters: [] + schema: + type: string + format: uuid responses: - 201: - description: Created + 200: + description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__GroupUser' + $ref: '#/components/schemas/PlaceOS__Model__GroupInvitation' 409: description: Conflict content: @@ -19417,31 +19514,20 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/group_users/{user_id}/{group_id}: - summary: Group memberships, the users in each group and their permissions - get: + delete: tags: - - Users - operationId: PlaceOS::Api::Groups::Users_show + - Invitations + operationId: PlaceOS::Api::Groups::Invitations_destroy parameters: - - name: user_id - in: path - required: true - schema: - type: string - - name: group_id + - name: id in: path required: true schema: type: string format: uuid responses: - 200: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__GroupUser' + 202: + description: Accepted 409: description: Conflict content: @@ -19520,23 +19606,18 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - put: + /api/engine/v2/group_invitations/{id}/accept: + summary: Invitations for users to join groups + post: + summary: Accept the invitation, materialising it into a GroupUser for the + description: 'Accept the invitation, materialising it into a GroupUser for the + + current user. The invitation row is destroyed on success.' tags: - - Users - operationId: PlaceOS::Api::Groups::Users_update - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__GroupUser' - required: true + - Invitations + operationId: PlaceOS::Api::Groups::Invitations_accept parameters: - - name: user_id - in: path - required: true - schema: - type: string - - name: group_id + - name: id in: path required: true schema: @@ -19627,25 +19708,69 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - delete: + /api/engine/v2/group_users: + summary: Group memberships, the users in each group and their permissions + get: + summary: GroupUser rows. Optional `group_id` scopes to one group; + description: 'GroupUser rows. Optional `group_id` scopes to one group; + + `user_id=me` returns the current user''s own rows. Non-admin + + callers without Manage on the specified group see only their own.' tags: - Users - operationId: PlaceOS::Api::Groups::Users_destroy + operationId: PlaceOS::Api::Groups::Users_index parameters: + - name: group_id + in: query + schema: + type: string + format: uuid + nullable: true - name: user_id - in: path - required: true + in: query schema: type: string - - name: group_id - in: path - required: true + nullable: true + - name: limit + in: query + schema: + type: integer + format: Int32 + - name: offset + in: query + schema: + type: integer + format: Int32 + - name: q + in: query + description: filters results by the given text; words match as prefixes against + the resource's searchable fields and all words must match schema: type: string - format: uuid + - name: ref + in: query + description: deprecated, ignored — pagination follows the `Link` header's + offset + schema: + type: string + nullable: true + - name: fields + in: query + description: deprecated, ignored — search covers the resource's indexed fields + schema: + type: array + items: + type: string responses: - 202: - description: Accepted + 200: + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PlaceOS__Model__GroupUser' 409: description: Conflict content: @@ -19724,16 +19849,108 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - patch: + post: tags: - Users - operationId: PlaceOS::Api::Groups::Users_update{2} + operationId: PlaceOS::Api::Groups::Users_create requestBody: content: application/json: schema: $ref: '#/components/schemas/PlaceOS__Model__GroupUser' required: true + parameters: [] + responses: + 201: + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__GroupUser' + 409: + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 503: + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 404: + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 429: + description: Too Many Requests + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 422: + description: Unprocessable Entity + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 502: + description: Bad Gateway + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 504: + description: Gateway Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 406: + description: Not Acceptable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + 408: + description: Request Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 400: + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 415: + description: Unsupported Media Type + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + /api/engine/v2/group_users/{user_id}/{group_id}: + summary: Group memberships, the users in each group and their permissions + get: + tags: + - Users + operationId: PlaceOS::Api::Groups::Users_show parameters: - name: user_id in: path @@ -19831,64 +20048,35 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/group_zones: - summary: The zones a group has access to, and the permissions it has there - get: - summary: List GroupZone rows. Filterable by group_id / zone_id. + put: tags: - - Zones - operationId: PlaceOS::Api::Groups::Zones_index + - Users + operationId: PlaceOS::Api::Groups::Users_update + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__GroupUser' + required: true parameters: - - name: group_id - in: query - schema: - type: string - format: uuid - nullable: true - - name: zone_id - in: query - schema: - type: string - nullable: true - - name: limit - in: query - schema: - type: integer - format: Int32 - - name: offset - in: query - schema: - type: integer - format: Int32 - - name: q - in: query - description: filters results by the given text; words match as prefixes against - the resource's searchable fields and all words must match + - name: user_id + in: path + required: true schema: type: string - - name: ref - in: query - description: deprecated, ignored — pagination follows the `Link` header's - offset + - name: group_id + in: path + required: true schema: type: string - nullable: true - - name: fields - in: query - description: deprecated, ignored — search covers the resource's indexed fields - schema: - type: array - items: - type: string + format: uuid responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Model__GroupZone' + $ref: '#/components/schemas/PlaceOS__Model__GroupUser' 409: description: Conflict content: @@ -19967,24 +20155,25 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - post: + delete: tags: - - Zones - operationId: PlaceOS::Api::Groups::Zones_create - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__GroupZone' + - Users + operationId: PlaceOS::Api::Groups::Users_destroy + parameters: + - name: user_id + in: path required: true - parameters: [] + schema: + type: string + - name: group_id + in: path + required: true + schema: + type: string + format: uuid responses: - 201: - description: Created - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__GroupZone' + 202: + description: Accepted 409: description: Conflict content: @@ -20063,31 +20252,35 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/group_zones/{group_id}/{zone_id}: - summary: The zones a group has access to, and the permissions it has there - get: + patch: tags: - - Zones - operationId: PlaceOS::Api::Groups::Zones_show + - Users + operationId: PlaceOS::Api::Groups::Users_update{2} + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__GroupUser' + required: true parameters: - - name: group_id + - name: user_id in: path required: true schema: type: string - format: uuid - - name: zone_id + - name: group_id in: path required: true schema: type: string + format: uuid responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__GroupZone' + $ref: '#/components/schemas/PlaceOS__Model__GroupUser' 409: description: Conflict content: @@ -20166,35 +20359,64 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - put: + /api/engine/v2/group_zones: + summary: The zones a group has access to, and the permissions it has there + get: + summary: List GroupZone rows. Filterable by group_id / zone_id. tags: - Zones - operationId: PlaceOS::Api::Groups::Zones_update - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__GroupZone' - required: true + operationId: PlaceOS::Api::Groups::Zones_index parameters: - name: group_id - in: path - required: true + in: query schema: type: string format: uuid + nullable: true - name: zone_id - in: path - required: true + in: query + schema: + type: string + nullable: true + - name: limit + in: query + schema: + type: integer + format: Int32 + - name: offset + in: query + schema: + type: integer + format: Int32 + - name: q + in: query + description: filters results by the given text; words match as prefixes against + the resource's searchable fields and all words must match schema: type: string + - name: ref + in: query + description: deprecated, ignored — pagination follows the `Link` header's + offset + schema: + type: string + nullable: true + - name: fields + in: query + description: deprecated, ignored — search covers the resource's indexed fields + schema: + type: array + items: + type: string responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__GroupZone' + type: array + items: + $ref: '#/components/schemas/PlaceOS__Model__GroupZone' 409: description: Conflict content: @@ -20273,25 +20495,24 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - delete: + post: tags: - Zones - operationId: PlaceOS::Api::Groups::Zones_destroy - parameters: - - name: group_id - in: path - required: true - schema: - type: string - format: uuid - - name: zone_id - in: path + operationId: PlaceOS::Api::Groups::Zones_create + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__GroupZone' required: true - schema: - type: string + parameters: [] responses: - 202: - description: Accepted + 201: + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__GroupZone' 409: description: Conflict content: @@ -20370,16 +20591,12 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - patch: + /api/engine/v2/group_zones/{group_id}/{zone_id}: + summary: The zones a group has access to, and the permissions it has there + get: tags: - Zones - operationId: PlaceOS::Api::Groups::Zones_update{2} - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__GroupZone' - required: true + operationId: PlaceOS::Api::Groups::Zones_show parameters: - name: group_id in: path @@ -20477,42 +20694,35 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/metadata/{name}/bulk: - summary: Metadata, named JSON documents attached to zones, systems and users (e.g. - floor maps, booking rules) - get: - summary: Fetch metadata with a specific name for multiple resources - description: 'Fetch metadata with a specific name for multiple resources - - - Allows bulk retrieval of metadata across multiple parent entities' + put: tags: - - Metadata - operationId: PlaceOS::Api::Metadata_bulk_fetch + - Zones + operationId: PlaceOS::Api::Groups::Zones_update + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__GroupZone' + required: true parameters: - - name: name + - name: group_id in: path - description: the name of the metadata key to fetch - example: settings required: true schema: type: string - - name: parent_ids - in: query - description: comma-separated list of parent IDs (zones, systems, users, etc.) - example: zone-1,zone-2,zone-3 + format: uuid + - name: zone_id + in: path required: true schema: - type: array - items: - type: string + type: string responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/Hash_String__PlaceOS__Model__Metadata__Interface___Nil_' + $ref: '#/components/schemas/PlaceOS__Model__GroupZone' 409: description: Conflict content: @@ -20591,39 +20801,25 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/metadata/{id}: - summary: Metadata, named JSON documents attached to zones, systems and users (e.g. - floor maps, booking rules) - get: - summary: Fetch metadata for a model - description: 'Fetch metadata for a model - - - Filter for a specific metadata by name via `name` param' + delete: tags: - - Metadata - operationId: PlaceOS::Api::Metadata_show + - Zones + operationId: PlaceOS::Api::Groups::Zones_destroy parameters: - - name: id + - name: group_id in: path - description: the parent id of the metadata to be returned required: true schema: type: string - - name: name - in: query - description: the name of the metadata key - example: config + format: uuid + - name: zone_id + in: path + required: true schema: type: string - nullable: true responses: - 200: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/Hash_String__PlaceOS__Model__Metadata__Interface_' + 202: + description: Accepted 409: description: Conflict content: @@ -20702,24 +20898,25 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - put: - summary: replace the metadata with this new metadata - description: 'replace the metadata with this new metadata - - udpates are signalled on the `placeos/metadata/changed` channel' + patch: tags: - - Metadata - operationId: PlaceOS::Api::Metadata_update + - Zones + operationId: PlaceOS::Api::Groups::Zones_update{2} requestBody: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Metadata__Interface' + $ref: '#/components/schemas/PlaceOS__Model__GroupZone' required: true parameters: - - name: id + - name: group_id + in: path + required: true + schema: + type: string + format: uuid + - name: zone_id in: path - description: the parent id of the metadata to be replaced required: true schema: type: string @@ -20729,7 +20926,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Metadata__Interface' + $ref: '#/components/schemas/PlaceOS__Model__GroupZone' 409: description: Conflict content: @@ -20808,134 +21005,42 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - delete: - summary: remove a metadata entry from the database + /api/engine/v2/metadata/{name}/bulk: + summary: Metadata, named JSON documents attached to zones, systems and users (e.g. + floor maps, booking rules) + get: + summary: Fetch metadata with a specific name for multiple resources + description: 'Fetch metadata with a specific name for multiple resources + + + Allows bulk retrieval of metadata across multiple parent entities' tags: - Metadata - operationId: PlaceOS::Api::Metadata_destroy + operationId: PlaceOS::Api::Metadata_bulk_fetch parameters: - - name: id + - name: name in: path - description: the parent id of the metadata to be returned + description: the name of the metadata key to fetch + example: settings required: true schema: type: string - - name: name + - name: parent_ids in: query - description: the name of the metadata key - example: config - required: true - schema: - type: string - responses: - 202: - description: Accepted - 409: - description: Conflict - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' - 401: - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' - 503: - description: Service Unavailable - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' - 403: - description: Forbidden - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' - 404: - description: Not Found - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' - 429: - description: Too Many Requests - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' - 422: - description: Unprocessable Entity - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' - 502: - description: Bad Gateway - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' - 504: - description: Gateway Timeout - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' - 406: - description: Not Acceptable - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - 408: - description: Request Timeout - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' - 400: - description: Bad Request - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' - 415: - description: Unsupported Media Type - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - patch: - summary: update only the keys provided on the selected metadata - description: 'update only the keys provided on the selected metadata - - udpates are signalled on the `placeos/metadata/changed` channel' - tags: - - Metadata - operationId: PlaceOS::Api::Metadata_merge - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Metadata__Interface' - required: true - parameters: - - name: id - in: path - description: the parent id of the metadata to be updated + description: comma-separated list of parent IDs (zones, systems, users, etc.) + example: zone-1,zone-2,zone-3 required: true schema: - type: string + type: array + items: + type: string responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Metadata__Interface' + $ref: '#/components/schemas/Hash_String__PlaceOS__Model__Metadata__Interface___Nil_' 409: description: Conflict content: @@ -21014,20 +21119,18 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/metadata/{id}/children: + /api/engine/v2/metadata/{id}: summary: Metadata, named JSON documents attached to zones, systems and users (e.g. floor maps, booking rules) get: - summary: Fetch metadata for Zone children - description: 'Fetch metadata for Zone children - + summary: Fetch metadata for a model + description: 'Fetch metadata for a model - Filter for a specific metadata by name via `name` param. - Includes the parent metadata by default via `include_parent` param.' + Filter for a specific metadata by name via `name` param' tags: - Metadata - operationId: PlaceOS::Api::Metadata_children_metadata + operationId: PlaceOS::Api::Metadata_show parameters: - name: id in: path @@ -21035,15 +21138,9 @@ paths: required: true schema: type: string - - name: include_parent - in: query - description: the parent metadata is included in the results by default - example: "false" - schema: - type: boolean - name: name in: query - description: filter for a particular metadata key + description: the name of the metadata key example: config schema: type: string @@ -21054,9 +21151,7 @@ paths: content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Api__Metadata__Children' + $ref: '#/components/schemas/Hash_String__PlaceOS__Model__Metadata__Interface_' 409: description: Conflict content: @@ -21135,27 +21230,24 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/metadata/{id}/name: - summary: Metadata, named JSON documents attached to zones, systems and users (e.g. - floor maps, booking rules) - patch: - summary: rename a metadata field - description: 'rename a metadata field + put: + summary: replace the metadata with this new metadata + description: 'replace the metadata with this new metadata udpates are signalled on the `placeos/metadata/changed` channel' tags: - Metadata - operationId: PlaceOS::Api::Metadata_rename + operationId: PlaceOS::Api::Metadata_update requestBody: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Api__Metadata__Rename' + $ref: '#/components/schemas/PlaceOS__Model__Metadata__Interface' required: true parameters: - name: id in: path - description: the parent id of the metadata to be renamed + description: the parent id of the metadata to be replaced required: true schema: type: string @@ -21244,14 +21336,11 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/metadata/{id}/history: - summary: Metadata, named JSON documents attached to zones, systems and users (e.g. - floor maps, booking rules) - get: - summary: Returns the version history for a Settings model + delete: + summary: remove a metadata entry from the database tags: - Metadata - operationId: PlaceOS::Api::Metadata_history + operationId: PlaceOS::Api::Metadata_destroy parameters: - name: id in: path @@ -21263,29 +21352,12 @@ paths: in: query description: the name of the metadata key example: config + required: true schema: type: string - nullable: true - - name: limit - in: query - description: the maximum number of results to return - example: "10000" - schema: - type: integer - format: Int32 - - name: offset - in: query - description: the starting offset of the result set. Used to implement pagination - schema: - type: integer - format: Int32 responses: - 200: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/Hash_String__Array_PlaceOS__Model__Metadata__Interface__' + 202: + description: Accepted 409: description: Conflict content: @@ -21364,19 +21436,34 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/mqtt/user: - post: - summary: 'For MQTT JWT access: https://github.com/iegomez/mosquitto-go-auth#remote-mode' - description: 'For MQTT JWT access: https://github.com/iegomez/mosquitto-go-auth#remote-mode + patch: + summary: update only the keys provided on the selected metadata + description: 'update only the keys provided on the selected metadata - jwt_response_mode: status, jwt_params_mode: form' + udpates are signalled on the `placeos/metadata/changed` channel' tags: - - MQTT - operationId: PlaceOS::Api::MQTT_mqtt_user - parameters: [] + - Metadata + operationId: PlaceOS::Api::Metadata_merge + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Metadata__Interface' + required: true + parameters: + - name: id + in: path + description: the parent id of the metadata to be updated + required: true + schema: + type: string responses: 200: description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Metadata__Interface' 409: description: Conflict content: @@ -21455,131 +21542,49 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/mqtt/access: - post: - summary: 'Sends a form with the following params: topic, clientid, acc (1: read, - 2: write, 3: readwrite, 4: subscribe)' - description: 'Sends a form with the following params: topic, clientid, acc (1: - read, 2: write, 3: readwrite, 4: subscribe) + /api/engine/v2/metadata/{id}/children: + summary: Metadata, named JSON documents attached to zones, systems and users (e.g. + floor maps, booking rules) + get: + summary: Fetch metadata for Zone children + description: 'Fetch metadata for Zone children - example payload: acc=4&clientid=clientId-NwsUNfV30&topic=%2A' + + Filter for a specific metadata by name via `name` param. + + Includes the parent metadata by default via `include_parent` param.' tags: - - MQTT - operationId: PlaceOS::Api::MQTT_mqtt_access + - Metadata + operationId: PlaceOS::Api::Metadata_children_metadata parameters: - - name: topic - in: query + - name: id + in: path + description: the parent id of the metadata to be returned required: true schema: type: string - - name: acc + - name: include_parent in: query - description: access level required - required: true + description: the parent metadata is included in the results by default + example: "false" schema: - type: string - enum: - - read - - write - - subscribe - - deny - - none - - all - - name: clientid + type: boolean + - name: name in: query + description: filter for a particular metadata key + example: config schema: type: string nullable: true responses: 200: description: OK - 409: - description: Conflict - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' - 401: - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' - 503: - description: Service Unavailable - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' - 403: - description: Forbidden - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' - 404: - description: Not Found - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' - 429: - description: Too Many Requests - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' - 422: - description: Unprocessable Entity - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' - 502: - description: Bad Gateway - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' - 504: - description: Gateway Timeout content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' - 406: - description: Not Acceptable - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - 408: - description: Request Timeout - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' - 400: - description: Bad Request - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' - 415: - description: Unsupported Media Type - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/notifications/google: - post: - tags: - - PushNotifications - operationId: PlaceOS::Api::PushNotifications_google - parameters: [] - responses: - 200: - description: OK + type: array + items: + $ref: '#/components/schemas/PlaceOS__Api__Metadata__Children' 409: description: Conflict content: @@ -21658,21 +21663,37 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/notifications/office365: - post: + /api/engine/v2/metadata/{id}/name: + summary: Metadata, named JSON documents attached to zones, systems and users (e.g. + floor maps, booking rules) + patch: + summary: rename a metadata field + description: 'rename a metadata field + + udpates are signalled on the `placeos/metadata/changed` channel' tags: - - PushNotifications - operationId: PlaceOS::Api::PushNotifications_microsoft + - Metadata + operationId: PlaceOS::Api::Metadata_rename requestBody: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Api__MicrosoftNotification' + $ref: '#/components/schemas/PlaceOS__Api__Metadata__Rename' required: true - parameters: [] + parameters: + - name: id + in: path + description: the parent id of the metadata to be renamed + required: true + schema: + type: string responses: 200: description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Metadata__Interface' 409: description: Conflict content: @@ -21751,64 +21772,48 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/oauth_apps: - summary: OAuth applications, the clients permitted to sign users in to PlaceOS + /api/engine/v2/metadata/{id}/history: + summary: Metadata, named JSON documents attached to zones, systems and users (e.g. + floor maps, booking rules) get: - summary: lists the frontend applications enabled on the domains + summary: Returns the version history for a Settings model tags: - - OAuthApplications - operationId: PlaceOS::Api::OAuthApplications_index + - Metadata + operationId: PlaceOS::Api::Metadata_history parameters: - - name: authority_id - in: query - description: the ID of the domain to be listed; ignored for non-admin/support - callers (forced to their own authority) - example: auth-12345 + - name: id + in: path + description: the parent id of the metadata to be returned + required: true schema: type: string - nullable: true - - name: q + - name: name in: query - description: filters results by the given text; words match as prefixes against - the resource's searchable fields and all words must match + description: the name of the metadata key + example: config schema: type: string + nullable: true - name: limit in: query description: the maximum number of results to return example: "10000" schema: type: integer - format: UInt32 + format: Int32 - name: offset in: query - description: the starting offset of the result set, used to implement pagination + description: the starting offset of the result set. Used to implement pagination schema: type: integer - format: UInt32 - - name: ref - in: query - description: deprecated, ignored — pagination follows the `Link` header's - offset - schema: - type: string - nullable: true - - name: fields - in: query - description: deprecated, ignored — search covers the resource's indexed fields - schema: - type: array - items: - type: string + format: Int32 responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Model__DoorkeeperApplication' + $ref: '#/components/schemas/Hash_String__Array_PlaceOS__Model__Metadata__Interface__' 409: description: Conflict content: @@ -21887,25 +21892,19 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + /api/engine/v2/mqtt/user: post: - summary: add a new user interface application + summary: 'For MQTT JWT access: https://github.com/iegomez/mosquitto-go-auth#remote-mode' + description: 'For MQTT JWT access: https://github.com/iegomez/mosquitto-go-auth#remote-mode + + jwt_response_mode: status, jwt_params_mode: form' tags: - - OAuthApplications - operationId: PlaceOS::Api::OAuthApplications_create - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__DoorkeeperApplication' - required: true + - MQTT + operationId: PlaceOS::Api::MQTT_mqtt_user parameters: [] responses: - 201: - description: Created - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__DoorkeeperApplication' + 200: + description: OK 409: description: Conflict content: @@ -21984,26 +21983,131 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/oauth_apps/{id}: - summary: OAuth applications, the clients permitted to sign users in to PlaceOS - get: - summary: show the details of the applications + /api/engine/v2/mqtt/access: + post: + summary: 'Sends a form with the following params: topic, clientid, acc (1: read, + 2: write, 3: readwrite, 4: subscribe)' + description: 'Sends a form with the following params: topic, clientid, acc (1: + read, 2: write, 3: readwrite, 4: subscribe) + + example payload: acc=4&clientid=clientId-NwsUNfV30&topic=%2A' tags: - - OAuthApplications - operationId: PlaceOS::Api::OAuthApplications_show + - MQTT + operationId: PlaceOS::Api::MQTT_mqtt_access parameters: - - name: id - in: path + - name: topic + in: query required: true schema: type: string + - name: acc + in: query + description: access level required + required: true + schema: + type: string + enum: + - read + - write + - subscribe + - deny + - none + - all + - name: clientid + in: query + schema: + type: string + nullable: true responses: 200: description: OK + 409: + description: Conflict content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__DoorkeeperApplication' + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 503: + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 404: + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 429: + description: Too Many Requests + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 422: + description: Unprocessable Entity + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 502: + description: Bad Gateway + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 504: + description: Gateway Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 406: + description: Not Acceptable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + 408: + description: Request Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 400: + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 415: + description: Unsupported Media Type + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + /api/engine/v2/notifications/google: + post: + tags: + - PushNotifications + operationId: PlaceOS::Api::PushNotifications_google + parameters: [] + responses: + 200: + description: OK 409: description: Conflict content: @@ -22082,30 +22186,21 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - put: - summary: udpate an application + /api/engine/v2/notifications/office365: + post: tags: - - OAuthApplications - operationId: PlaceOS::Api::OAuthApplications_update + - PushNotifications + operationId: PlaceOS::Api::PushNotifications_microsoft requestBody: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__DoorkeeperApplication' - required: true - parameters: - - name: id - in: path + $ref: '#/components/schemas/PlaceOS__Api__MicrosoftNotification' required: true - schema: - type: string + parameters: [] responses: 200: description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__DoorkeeperApplication' 409: description: Conflict content: @@ -22184,20 +22279,64 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - delete: - summary: remove an application + /api/engine/v2/oauth_apps: + summary: OAuth applications, the clients permitted to sign users in to PlaceOS + get: + summary: lists the frontend applications enabled on the domains tags: - OAuthApplications - operationId: PlaceOS::Api::OAuthApplications_destroy + operationId: PlaceOS::Api::OAuthApplications_index parameters: - - name: id - in: path - required: true + - name: authority_id + in: query + description: the ID of the domain to be listed; ignored for non-admin/support + callers (forced to their own authority) + example: auth-12345 + schema: + type: string + nullable: true + - name: q + in: query + description: filters results by the given text; words match as prefixes against + the resource's searchable fields and all words must match + schema: + type: string + - name: limit + in: query + description: the maximum number of results to return + example: "10000" + schema: + type: integer + format: UInt32 + - name: offset + in: query + description: the starting offset of the result set, used to implement pagination + schema: + type: integer + format: UInt32 + - name: ref + in: query + description: deprecated, ignored — pagination follows the `Link` header's + offset schema: type: string + nullable: true + - name: fields + in: query + description: deprecated, ignored — search covers the resource's indexed fields + schema: + type: array + items: + type: string responses: - 202: - description: Accepted + 200: + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PlaceOS__Model__DoorkeeperApplication' 409: description: Conflict content: @@ -22276,26 +22415,21 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - patch: - summary: udpate an application + post: + summary: add a new user interface application tags: - OAuthApplications - operationId: PlaceOS::Api::OAuthApplications_update{2} + operationId: PlaceOS::Api::OAuthApplications_create requestBody: content: application/json: schema: $ref: '#/components/schemas/PlaceOS__Model__DoorkeeperApplication' required: true - parameters: - - name: id - in: path - required: true - schema: - type: string + parameters: [] responses: - 200: - description: OK + 201: + description: Created content: application/json: schema: @@ -22378,144 +22512,26 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/emails: - summary: Emails awaiting review or delivery + /api/engine/v2/oauth_apps/{id}: + summary: OAuth applications, the clients permitted to sign users in to PlaceOS get: - summary: list queued/processed mail + summary: show the details of the applications tags: - - PendingMails - operationId: PlaceOS::Api::PendingMails_index + - OAuthApplications + operationId: PlaceOS::Api::OAuthApplications_show parameters: - - name: group_id - in: query - description: only mail whose zones are anchored to this group; non-support - callers need Read on the group - example: group-uuid - schema: - type: string - format: uuid - nullable: true - - name: zones - in: query - description: only mail referencing any of these zones - example: zone-1234,zone-5678 - schema: - type: array - items: - type: string - nullable: true - - name: source_service - in: query - description: only mail queued by this service - example: bookings - schema: - type: string - nullable: true - - name: source_reference - in: query - description: only mail with this external reference / correlation id - example: booking-1234 - schema: - type: string - nullable: true - - name: authority_id - in: query - description: filter by authority (system admin / support only; regular users - are scoped to their own authority) - example: authority-1234 - schema: - type: string - nullable: true - - name: user_id - in: query - description: only mail triggered by this user - example: user-1234 - schema: - type: string - nullable: true - - name: include_expired - in: query - description: include mail past its expiry (default false) - example: "true" - schema: - type: boolean - - name: include_rejected - in: query - description: include mail that has been rejected (default false) - example: "true" - schema: - type: boolean - - name: unsent_only - in: query - description: only mail that is neither sent nor rejected (default false) - example: "true" - schema: - type: boolean - - name: sent_after - in: query - description: mail sent at or after this time (also matches rejected time when - include_rejected=true) - schema: - type: string - format: date-time - nullable: true - - name: sent_before - in: query - description: mail sent at or before this time (also matches rejected time - when include_rejected=true) - schema: - type: string - format: date-time - nullable: true - - name: send_at_after - in: query - description: mail scheduled to send at or after this time - schema: - type: string - format: date-time - nullable: true - - name: q - in: query - description: filters results by the given text; words match as prefixes against - the resource's searchable fields and all words must match - schema: - type: string - - name: limit - in: query - description: the maximum number of results to return - example: "10000" - schema: - type: integer - format: UInt32 - - name: offset - in: query - description: the starting offset of the result set, used to implement pagination - schema: - type: integer - format: UInt32 - - name: ref - in: query - description: deprecated, ignored — pagination follows the `Link` header's - offset + - name: id + in: path + required: true schema: type: string - nullable: true - - name: fields - in: query - description: deprecated, ignored — search covers the resource's indexed fields - schema: - type: array - items: - type: string responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Model__PendingMail' + $ref: '#/components/schemas/PlaceOS__Model__DoorkeeperApplication' 409: description: Conflict content: @@ -22594,13 +22610,17 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/emails/{id}: - summary: Emails awaiting review or delivery - get: - summary: show the selected mail + put: + summary: udpate an application tags: - - PendingMails - operationId: PlaceOS::Api::PendingMails_show + - OAuthApplications + operationId: PlaceOS::Api::OAuthApplications_update + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__DoorkeeperApplication' + required: true parameters: - name: id in: path @@ -22613,7 +22633,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__PendingMail' + $ref: '#/components/schemas/PlaceOS__Model__DoorkeeperApplication' 409: description: Conflict content: @@ -22693,10 +22713,10 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' delete: - summary: remove a queued/processed mail + summary: remove an application tags: - - PendingMails - operationId: PlaceOS::Api::PendingMails_destroy + - OAuthApplications + operationId: PlaceOS::Api::OAuthApplications_destroy parameters: - name: id in: path @@ -22784,33 +22804,30 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/emails/{id}/reject: - summary: Emails awaiting review or delivery - post: - summary: mark a mail as rejected (it will not be sent) + patch: + summary: udpate an application tags: - - PendingMails - operationId: PlaceOS::Api::PendingMails_reject + - OAuthApplications + operationId: PlaceOS::Api::OAuthApplications_update{2} + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__DoorkeeperApplication' + required: true parameters: - name: id in: path required: true schema: type: string - - name: rejected_reason - in: query - description: human readable reason the mail was rejected - example: recipient opted out - schema: - type: string - nullable: true responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__PendingMail' + $ref: '#/components/schemas/PlaceOS__Model__DoorkeeperApplication' 409: description: Conflict content: @@ -22889,33 +22906,20 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/emails/{id}/sent: - summary: Emails awaiting review or delivery - post: - summary: mark a mail as sent + /api/engine/v2/organisations/current: + summary: 'Organisations: the customers that own domains and estate (PPT-526)' + get: tags: - - PendingMails - operationId: PlaceOS::Api::PendingMails_sent - parameters: - - name: id - in: path - required: true - schema: - type: string - - name: sent_by - in: query - description: the worker / service that sent the mail - example: mailer - schema: - type: string - nullable: true + - Organisations + operationId: PlaceOS::Api::Organisations_current + parameters: [] responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__PendingMail' + $ref: '#/components/schemas/PlaceOS__Api__Organisations__CurrentReach' 409: description: Conflict content: @@ -22994,24 +22998,64 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/emails/cleanup: - summary: Emails awaiting review or delivery - delete: - summary: delete all sent, rejected and expired mail for an authority (admin/support) + /api/engine/v2/organisations: + summary: 'Organisations: the customers that own domains and estate (PPT-526)' + get: + summary: list the organisations in reach tags: - - PendingMails - operationId: PlaceOS::Api::PendingMails_cleanup + - Organisations + operationId: PlaceOS::Api::Organisations_index parameters: - - name: authority_id + - name: partner_id in: query - description: authority to clean up (defaults to the caller's authority) - example: authority-1234 + description: only organisations under this partner + example: 0192f1c4-7a6b-7c4d-9f3e-1a2b3c4d5e6f schema: type: string + format: uuid nullable: true + - name: q + in: query + description: filters results by the given text; words match as prefixes against + the resource's searchable fields and all words must match + schema: + type: string + - name: limit + in: query + description: the maximum number of results to return + example: "10000" + schema: + type: integer + format: UInt32 + - name: offset + in: query + description: the starting offset of the result set, used to implement pagination + schema: + type: integer + format: UInt32 + - name: ref + in: query + description: deprecated, ignored — pagination follows the `Link` header's + offset + schema: + type: string + nullable: true + - name: fields + in: query + description: deprecated, ignored — search covers the resource's indexed fields + schema: + type: array + items: + type: string responses: - 202: - description: Accepted + 200: + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PlaceOS__Model__Organisation' 409: description: Conflict content: @@ -23090,46 +23134,44 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/public_events/guest_token/{system_id}: post: - summary: Issues a short-lived guest JWT after verifying the reCAPTCHA challenge. - description: 'Issues a short-lived guest JWT after verifying the reCAPTCHA challenge. - - - The resulting token is scoped to the requested public system and grants: - - - read access to the cached `:public_events` driver status - - - the ability to call `register_attendee` via the `/register` route - - - Mirrors the WebRTC `guest_entry` flow: - - - authority.internals["recaptcha_secret"] → live Google verification + summary: '`payer` and `partner_staff` are not mass-assignable, so they travel + as' + description: '`payer` and `partner_staff` are not mass-assignable, so they travel + as - - authority.internals["recaptcha_skip"] = true → skip (dev / test only)' + parameters' tags: - - PublicEvents - operationId: PlaceOS::Api::PublicEvents_guest_token + - Organisations + operationId: PlaceOS::Api::Organisations_create requestBody: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Api__PublicEvents__TokenRequest' + $ref: '#/components/schemas/PlaceOS__Model__Organisation' required: true parameters: - - name: system_id - in: path - required: true + - name: payer + in: query + description: 'who is invoiced: partner or organisation' + example: partner schema: type: string + nullable: true + - name: partner_staff + in: query + description: true for the partner's own staff organisation + example: "false" + schema: + type: boolean + nullable: true responses: - 200: - description: OK + 201: + description: Created content: application/json: schema: - $ref: '#/components/schemas/String' + $ref: '#/components/schemas/PlaceOS__Model__Organisation' 409: description: Conflict content: @@ -23208,71 +23250,26 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/public_events/{system_id}/events: + /api/engine/v2/organisations/{id}: + summary: 'Organisations: the customers that own domains and estate (PPT-526)' get: - summary: Returns the cached list of public events for the given system. - description: 'Returns the cached list of public events for the given system. - - - Reads the `:public_events` status key directly from Redis — no live - - driver round-trip is made. The response is sourced from the cache that - - the PublicEvents driver maintains via its Bookings subscription. - - - Guest JWTs are accepted provided the system id appears in their roles.' tags: - - PublicEvents - operationId: PlaceOS::Api::PublicEvents_index + - Organisations + operationId: PlaceOS::Api::Organisations_show parameters: - - name: system_id + - name: id in: path required: true schema: type: string - - name: q - in: query - description: filters results by the given text; words match as prefixes against - the resource's searchable fields and all words must match - schema: - type: string - - name: limit - in: query - description: the maximum number of results to return - example: "10000" - schema: - type: integer - format: UInt32 - - name: offset - in: query - description: the starting offset of the result set, used to implement pagination - schema: - type: integer - format: UInt32 - - name: ref - in: query - description: deprecated, ignored — pagination follows the `Link` header's - offset - schema: - type: string - nullable: true - - name: fields - in: query - description: deprecated, ignored — search covers the resource's indexed fields - schema: - type: array - items: - type: string + format: uuid responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/JSON__Any' + $ref: '#/components/schemas/PlaceOS__Model__Organisation' 409: description: Conflict content: @@ -23351,38 +23348,44 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/public_events/{system_id}/register: - post: - summary: Registers an external attendee for a public calendar event. - description: 'Registers an external attendee for a public calendar event. - - - Delegates to the `register_attendee(event_id, name, email)` function on - - the PublicEvents driver. The driver appends the attendee to the calendar - - event and returns `true` on success. - - - Guest JWTs are accepted provided the system id appears in their roles.' + put: tags: - - PublicEvents - operationId: PlaceOS::Api::PublicEvents_register + - Organisations + operationId: PlaceOS::Api::Organisations_update requestBody: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Api__PublicEvents__RegistrationRequest' + $ref: '#/components/schemas/PlaceOS__Model__Organisation' required: true parameters: - - name: system_id + - name: id in: path required: true schema: type: string + format: uuid + - name: payer + in: query + description: 'who is invoiced: partner or organisation' + example: partner + schema: + type: string + nullable: true + - name: partner_staff + in: query + description: true for the partner's own staff organisation + example: "false" + schema: + type: boolean + nullable: true responses: 200: description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Organisation' 409: description: Conflict content: @@ -23461,56 +23464,21 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/repositories: - summary: Git repositories containing drivers and frontend interfaces - get: - summary: lists the repositories added to the system + delete: + summary: Deletes are RESTRICTed by the database while anything is owned tags: - - Repositories - operationId: PlaceOS::Api::Repositories_index + - Organisations + operationId: PlaceOS::Api::Organisations_destroy parameters: - - name: q - in: query - description: filters results by the given text; words match as prefixes against - the resource's searchable fields and all words must match - schema: - type: string - - name: limit - in: query - description: the maximum number of results to return - example: "10000" - schema: - type: integer - format: UInt32 - - name: offset - in: query - description: the starting offset of the result set, used to implement pagination - schema: - type: integer - format: UInt32 - - name: ref - in: query - description: deprecated, ignored — pagination follows the `Link` header's - offset + - name: id + in: path + required: true schema: type: string - nullable: true - - name: fields - in: query - description: deprecated, ignored — search covers the resource's indexed fields - schema: - type: array - items: - type: string + format: uuid responses: - 200: - description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Model__Repository' + 202: + description: Accepted 409: description: Conflict content: @@ -23589,25 +23557,44 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - post: - summary: adds a new repository, either a frontends or driver repository + patch: tags: - - Repositories - operationId: PlaceOS::Api::Repositories_create + - Organisations + operationId: PlaceOS::Api::Organisations_update{2} requestBody: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Repository' + $ref: '#/components/schemas/PlaceOS__Model__Organisation' required: true - parameters: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + - name: payer + in: query + description: 'who is invoiced: partner or organisation' + example: partner + schema: + type: string + nullable: true + - name: partner_staff + in: query + description: true for the partner's own staff organisation + example: "false" + schema: + type: boolean + nullable: true responses: - 201: - description: Created + 200: + description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Repository' + $ref: '#/components/schemas/PlaceOS__Model__Organisation' 409: description: Conflict content: @@ -23686,26 +23673,39 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/repositories/{id}: - summary: Git repositories containing drivers and frontend interfaces - get: - summary: returns the details of a saved repository + /api/engine/v2/organisations/{id}/claim: + summary: 'Organisations: the customers that own domains and estate (PPT-526)' + post: + summary: 'Assigns unowned zone trees to the organisation: the zones named, their' + description: 'Assigns unowned zone trees to the organisation: the zones named, + their + + descendants, the systems in those zones and the modules of those + + systems. Rows already owned by another organisation are left alone.' tags: - - Repositories - operationId: PlaceOS::Api::Repositories_show + - Organisations + operationId: PlaceOS::Api::Organisations_claim + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Organisations__ClaimBody' + required: true parameters: - name: id in: path required: true schema: type: string + format: uuid responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Repository' + $ref: '#/components/schemas/PlaceOS__Api__Organisations__ClaimResult' 409: description: Conflict content: @@ -23784,30 +23784,60 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - put: - summary: updates a repositories details + /api/engine/v2/partners: + summary: 'Partners: integrators that bring in and manage organisations, and the' + description: 'Partners: integrators that bring in and manage organisations, and + the + + management partner that is PlaceOS itself (PPT-526)' + get: + summary: cluster admins list every partner; everyone else sees their own tags: - - Repositories - operationId: PlaceOS::Api::Repositories_update - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Repository' - required: true + - Partners + operationId: PlaceOS::Api::Partners_index parameters: - - name: id - in: path - required: true + - name: q + in: query + description: filters results by the given text; words match as prefixes against + the resource's searchable fields and all words must match schema: type: string + - name: limit + in: query + description: the maximum number of results to return + example: "10000" + schema: + type: integer + format: UInt32 + - name: offset + in: query + description: the starting offset of the result set, used to implement pagination + schema: + type: integer + format: UInt32 + - name: ref + in: query + description: deprecated, ignored — pagination follows the `Link` header's + offset + schema: + type: string + nullable: true + - name: fields + in: query + description: deprecated, ignored — search covers the resource's indexed fields + schema: + type: array + items: + type: string responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Repository' + type: array + items: + $ref: '#/components/schemas/PlaceOS__Model__Partner' 409: description: Conflict content: @@ -23886,20 +23916,24 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - delete: - summary: removes a repository from the server + post: tags: - - Repositories - operationId: PlaceOS::Api::Repositories_destroy - parameters: - - name: id - in: path + - Partners + operationId: PlaceOS::Api::Partners_create + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Partner' required: true - schema: - type: string + parameters: [] responses: - 202: - description: Accepted + 201: + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Partner' 409: description: Conflict content: @@ -23978,30 +24012,30 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - patch: - summary: updates a repositories details + /api/engine/v2/partners/{id}: + summary: 'Partners: integrators that bring in and manage organisations, and the' + description: 'Partners: integrators that bring in and manage organisations, and + the + + management partner that is PlaceOS itself (PPT-526)' + get: tags: - - Repositories - operationId: PlaceOS::Api::Repositories_update{2} - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Repository' - required: true + - Partners + operationId: PlaceOS::Api::Partners_show parameters: - name: id in: path required: true schema: type: string + format: uuid responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Repository' + $ref: '#/components/schemas/PlaceOS__Model__Partner' 409: description: Conflict content: @@ -24080,26 +24114,30 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/repositories/{id}/pull: - summary: Git repositories containing drivers and frontend interfaces - post: - summary: checks the remote for any new commits and pulls them locally + put: tags: - - Repositories - operationId: PlaceOS::Api::Repositories_pull + - Partners + operationId: PlaceOS::Api::Partners_update + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Partner' + required: true parameters: - name: id in: path required: true schema: type: string + format: uuid responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/NamedTuple_commit_hash__String_' + $ref: '#/components/schemas/PlaceOS__Model__Partner' 409: description: Conflict content: @@ -24178,25 +24216,21 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/repositories/interfaces: - summary: Git repositories containing drivers and frontend interfaces - get: - summary: Determine loaded interfaces and their current commit - description: 'Determine loaded interfaces and their current commit - - - Returns a hash of folder_name to commit' + delete: + summary: Deletes are RESTRICTed by the database while organisations remain tags: - - Repositories - operationId: PlaceOS::Api::Repositories_loaded_interfaces - parameters: [] + - Partners + operationId: PlaceOS::Api::Partners_destroy + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid responses: - 200: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/Hash_String__String_' + 202: + description: Accepted 409: description: Conflict content: @@ -24275,28 +24309,30 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/repositories/{id}/drivers: - summary: Git repositories containing drivers and frontend interfaces - get: - summary: lists the drivers in a repository + patch: tags: - - Repositories - operationId: PlaceOS::Api::Repositories_drivers + - Partners + operationId: PlaceOS::Api::Partners_update{2} + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Partner' + required: true parameters: - name: id in: path required: true schema: type: string + format: uuid responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/String' + $ref: '#/components/schemas/PlaceOS__Model__Partner' 409: description: Conflict content: @@ -24375,25 +24411,135 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/repositories/{id}/folders: - summary: Git repositories containing drivers and frontend interfaces + /api/engine/v2/emails: + summary: Emails awaiting review or delivery get: - summary: lists the folders tree in a repository + summary: list queued/processed mail tags: - - Repositories - operationId: PlaceOS::Api::Repositories_folders + - PendingMails + operationId: PlaceOS::Api::PendingMails_index parameters: - - name: id - in: path - required: true + - name: group_id + in: query + description: only mail whose zones are anchored to this group; non-support + callers need Read on the group + example: group-uuid schema: type: string - - name: include_dots + format: uuid + nullable: true + - name: zones in: query - description: include dots folders, default to false + description: only mail referencing any of these zones + example: zone-1234,zone-5678 + schema: + type: array + items: + type: string + nullable: true + - name: source_service + in: query + description: only mail queued by this service + example: bookings + schema: + type: string + nullable: true + - name: source_reference + in: query + description: only mail with this external reference / correlation id + example: booking-1234 + schema: + type: string + nullable: true + - name: authority_id + in: query + description: filter by authority (system admin / support only; regular users + are scoped to their own authority) + example: authority-1234 + schema: + type: string + nullable: true + - name: user_id + in: query + description: only mail triggered by this user + example: user-1234 + schema: + type: string + nullable: true + - name: include_expired + in: query + description: include mail past its expiry (default false) example: "true" schema: type: boolean + - name: include_rejected + in: query + description: include mail that has been rejected (default false) + example: "true" + schema: + type: boolean + - name: unsent_only + in: query + description: only mail that is neither sent nor rejected (default false) + example: "true" + schema: + type: boolean + - name: sent_after + in: query + description: mail sent at or after this time (also matches rejected time when + include_rejected=true) + schema: + type: string + format: date-time + nullable: true + - name: sent_before + in: query + description: mail sent at or before this time (also matches rejected time + when include_rejected=true) + schema: + type: string + format: date-time + nullable: true + - name: send_at_after + in: query + description: mail scheduled to send at or after this time + schema: + type: string + format: date-time + nullable: true + - name: q + in: query + description: filters results by the given text; words match as prefixes against + the resource's searchable fields and all words must match + schema: + type: string + - name: limit + in: query + description: the maximum number of results to return + example: "10000" + schema: + type: integer + format: UInt32 + - name: offset + in: query + description: the starting offset of the result set, used to implement pagination + schema: + type: integer + format: UInt32 + - name: ref + in: query + description: deprecated, ignored — pagination follows the `Link` header's + offset + schema: + type: string + nullable: true + - name: fields + in: query + description: deprecated, ignored — search covers the resource's indexed fields + schema: + type: array + items: + type: string responses: 200: description: OK @@ -24402,7 +24548,7 @@ paths: schema: type: array items: - $ref: '#/components/schemas/String' + $ref: '#/components/schemas/PlaceOS__Model__PendingMail' 409: description: Conflict content: @@ -24481,38 +24627,26 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/repositories/{id}/files: - summary: Git repositories containing drivers and frontend interfaces + /api/engine/v2/emails/{id}: + summary: Emails awaiting review or delivery get: - summary: lists the files in an interface repository that match a glob pattern. - description: 'lists the files in an interface repository that match a glob pattern. - - Paths are returned relative to where they are served, i.e. `//path/to/file.html`' + summary: show the selected mail tags: - - Repositories - operationId: PlaceOS::Api::Repositories_files + - PendingMails + operationId: PlaceOS::Api::PendingMails_show parameters: - name: id in: path required: true schema: type: string - - name: pattern - in: query - description: glob pattern. Without a `/` it matches file names at any depth, - otherwise the full path (use `**` to recurse) - example: '*.html' - schema: - type: string responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/String' + $ref: '#/components/schemas/PlaceOS__Model__PendingMail' 409: description: Conflict content: @@ -24591,50 +24725,20 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/repositories/{id}/commits: - summary: Git repositories containing drivers and frontend interfaces - get: - summary: Returns the commits for a repository or file + delete: + summary: remove a queued/processed mail tags: - - Repositories - operationId: PlaceOS::Api::Repositories_commits + - PendingMails + operationId: PlaceOS::Api::PendingMails_destroy parameters: - name: id in: path required: true schema: type: string - - name: limit - in: query - description: the maximum number of commits to return - example: "50" - schema: - type: integer - format: Int32 - nullable: true - - name: driver - in: query - description: the path to the file we want commits for - example: path/to/file.cr - schema: - type: string - nullable: true - - name: branch - in: query - description: the branch to grab commits from - example: main - schema: - type: string - nullable: true responses: - 200: - description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/GitRepository__Commit' + 202: + description: Accepted 409: description: Conflict content: @@ -24713,139 +24817,33 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/repositories/{id}/details: - summary: Git repositories containing drivers and frontend interfaces - get: - summary: Returns the metadata of the driver - description: 'Returns the metadata of the driver - - For payload information, look at https://github.com/placeos/driver/blob/master/docs/command_line_options.md#discovery-and-defaults' + /api/engine/v2/emails/{id}/reject: + summary: Emails awaiting review or delivery + post: + summary: mark a mail as rejected (it will not be sent) tags: - - Repositories - operationId: PlaceOS::Api::Repositories_details + - PendingMails + operationId: PlaceOS::Api::PendingMails_reject parameters: - name: id in: path required: true schema: type: string - - name: driver - in: query - description: the file we would like metadata for - example: path/to/file.cr - required: true - schema: - type: string - - name: commit + - name: rejected_reason in: query - description: the commit level of the file - example: 3f67a66 - required: true - schema: - type: string - responses: - 200: - description: OK - 409: - description: Conflict - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' - 401: - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' - 503: - description: Service Unavailable - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' - 403: - description: Forbidden - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' - 404: - description: Not Found - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' - 429: - description: Too Many Requests - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' - 422: - description: Unprocessable Entity - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' - 502: - description: Bad Gateway - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' - 504: - description: Gateway Timeout - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' - 406: - description: Not Acceptable - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - 408: - description: Request Timeout - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' - 400: - description: Bad Request - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' - 415: - description: Unsupported Media Type - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/repositories/{id}/branches: - summary: Git repositories containing drivers and frontend interfaces - get: - summary: returns the list of branches in the repository - tags: - - Repositories - operationId: PlaceOS::Api::Repositories_branches - parameters: - - name: id - in: path - required: true + description: human readable reason the mail was rejected + example: recipient opted out schema: type: string + nullable: true responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/String' + $ref: '#/components/schemas/PlaceOS__Model__PendingMail' 409: description: Conflict content: @@ -24924,28 +24922,33 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/repositories/{id}/releases: - summary: Git repositories containing drivers and frontend interfaces - get: - summary: returns the list of releases in the repository, i.e. github releases + /api/engine/v2/emails/{id}/sent: + summary: Emails awaiting review or delivery + post: + summary: mark a mail as sent tags: - - Repositories - operationId: PlaceOS::Api::Repositories_releases + - PendingMails + operationId: PlaceOS::Api::PendingMails_sent parameters: - name: id in: path required: true schema: type: string + - name: sent_by + in: query + description: the worker / service that sent the mail + example: mailer + schema: + type: string + nullable: true responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/String' + $ref: '#/components/schemas/PlaceOS__Model__PendingMail' 409: description: Conflict content: @@ -25024,28 +25027,24 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/repositories/{id}/tags: - summary: Git repositories containing drivers and frontend interfaces - get: - summary: Returns an array of tags for the repository + /api/engine/v2/emails/cleanup: + summary: Emails awaiting review or delivery + delete: + summary: delete all sent, rejected and expired mail for an authority (admin/support) tags: - - Repositories - operationId: PlaceOS::Api::Repositories_tags + - PendingMails + operationId: PlaceOS::Api::PendingMails_cleanup parameters: - - name: id - in: path - required: true + - name: authority_id + in: query + description: authority to clean up (defaults to the caller's authority) + example: authority-1234 schema: type: string + nullable: true responses: - 200: - description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/String' + 202: + description: Accepted 409: description: Conflict content: @@ -25124,15 +25123,35 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/repositories/{id}/default_branch: - summary: Git repositories containing drivers and frontend interfaces - get: - summary: returns the default branch of the specified repository + /api/engine/v2/public_events/guest_token/{system_id}: + post: + summary: Issues a short-lived guest JWT after verifying the reCAPTCHA challenge. + description: 'Issues a short-lived guest JWT after verifying the reCAPTCHA challenge. + + + The resulting token is scoped to the requested public system and grants: + + - read access to the cached `:public_events` driver status + + - the ability to call `register_attendee` via the `/register` route + + + Mirrors the WebRTC `guest_entry` flow: + + - authority.internals["recaptcha_secret"] → live Google verification + + - authority.internals["recaptcha_skip"] = true → skip (dev / test only)' tags: - - Repositories - operationId: PlaceOS::Api::Repositories_default_branch + - PublicEvents + operationId: PlaceOS::Api::PublicEvents_guest_token + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__PublicEvents__TokenRequest' + required: true parameters: - - name: id + - name: system_id in: path required: true schema: @@ -25222,42 +25241,71 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/repositories/remote_default_branch: - summary: Git repositories containing drivers and frontend interfaces + /api/engine/v2/public_events/{system_id}/events: get: - summary: returns the default branch of the specified repository + summary: Returns the cached list of public events for the given system. + description: 'Returns the cached list of public events for the given system. + + + Reads the `:public_events` status key directly from Redis — no live + + driver round-trip is made. The response is sourced from the cache that + + the PublicEvents driver maintains via its Bookings subscription. + + + Guest JWTs are accepted provided the system id appears in their roles.' tags: - - Repositories - operationId: PlaceOS::Api::Repositories_remote_default_branch + - PublicEvents + operationId: PlaceOS::Api::PublicEvents_index parameters: - - name: repository_url - in: query - description: the git url that represents the repository - example: https://github.com/PlaceOS/drivers.git + - name: system_id + in: path required: true schema: type: string - - name: username + - name: q in: query - description: a username for access if required - example: steve + description: filters results by the given text; words match as prefixes against + the resource's searchable fields and all words must match schema: type: string - nullable: true - - name: password + - name: limit in: query - description: the password or access token as required - example: ab34cfe4567 + description: the maximum number of results to return + example: "10000" + schema: + type: integer + format: UInt32 + - name: offset + in: query + description: the starting offset of the result set, used to implement pagination + schema: + type: integer + format: UInt32 + - name: ref + in: query + description: deprecated, ignored — pagination follows the `Link` header's + offset schema: type: string nullable: true + - name: fields + in: query + description: deprecated, ignored — search covers the resource's indexed fields + schema: + type: array + items: + type: string responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/String' + type: array + items: + $ref: '#/components/schemas/JSON__Any' 409: description: Conflict content: @@ -25336,44 +25384,38 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/repositories/remote_branches: - summary: Git repositories containing drivers and frontend interfaces - get: - summary: lists the branches of the specified repository + /api/engine/v2/public_events/{system_id}/register: + post: + summary: Registers an external attendee for a public calendar event. + description: 'Registers an external attendee for a public calendar event. + + + Delegates to the `register_attendee(event_id, name, email)` function on + + the PublicEvents driver. The driver appends the attendee to the calendar + + event and returns `true` on success. + + + Guest JWTs are accepted provided the system id appears in their roles.' tags: - - Repositories - operationId: PlaceOS::Api::Repositories_remote_branches + - PublicEvents + operationId: PlaceOS::Api::PublicEvents_register + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__PublicEvents__RegistrationRequest' + required: true parameters: - - name: repository_url - in: query - description: the git url that represents the repository - example: https://github.com/PlaceOS/drivers.git + - name: system_id + in: path required: true schema: type: string - - name: username - in: query - description: a username for access if required - example: steve - schema: - type: string - nullable: true - - name: password - in: query - description: the password or access token as required - example: ab34cfe4567 - schema: - type: string - nullable: true responses: 200: description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/String' 409: description: Conflict content: @@ -25452,49 +25494,47 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/repositories/remote_commits: + /api/engine/v2/repositories: summary: Git repositories containing drivers and frontend interfaces get: - summary: lists the commits of the specified repository + summary: lists the repositories added to the system tags: - Repositories - operationId: PlaceOS::Api::Repositories_remote_commits + operationId: PlaceOS::Api::Repositories_index parameters: - - name: branch + - name: q in: query - description: the branch to grab commits from - example: main + description: filters results by the given text; words match as prefixes against + the resource's searchable fields and all words must match schema: type: string - nullable: true - - name: depth + - name: limit in: query - description: the number of commits to return - example: "50" + description: the maximum number of results to return + example: "10000" schema: type: integer - format: Int32 - - name: repository_url + format: UInt32 + - name: offset in: query - description: the git url that represents the repository - example: https://github.com/PlaceOS/drivers.git - required: true + description: the starting offset of the result set, used to implement pagination schema: - type: string - - name: username + type: integer + format: UInt32 + - name: ref in: query - description: a username for access if required - example: steve + description: deprecated, ignored — pagination follows the `Link` header's + offset schema: type: string nullable: true - - name: password + - name: fields in: query - description: the password or access token as required - example: ab34cfe4567 + description: deprecated, ignored — search covers the resource's indexed fields schema: - type: string - nullable: true + type: array + items: + type: string responses: 200: description: OK @@ -25503,7 +25543,7 @@ paths: schema: type: array items: - $ref: '#/components/schemas/GitRepository__Commit' + $ref: '#/components/schemas/PlaceOS__Model__Repository' 409: description: Conflict content: @@ -25582,44 +25622,25 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/repositories/remote_tags: - summary: Git repositories containing drivers and frontend interfaces - get: - summary: Returns an array of tags for the specified repository + post: + summary: adds a new repository, either a frontends or driver repository tags: - Repositories - operationId: PlaceOS::Api::Repositories_remote_tags - parameters: - - name: repository_url - in: query - description: the git url that represents the repository - example: https://github.com/PlaceOS/drivers.git + operationId: PlaceOS::Api::Repositories_create + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Repository' required: true - schema: - type: string - - name: username - in: query - description: a username for access if required - example: steve - schema: - type: string - nullable: true - - name: password - in: query - description: the password or access token as required - example: ab34cfe4567 - schema: - type: string - nullable: true + parameters: [] responses: - 200: - description: OK + 201: + description: Created content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/String' + $ref: '#/components/schemas/PlaceOS__Model__Repository' 409: description: Conflict content: @@ -25698,17 +25719,26 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2: + /api/engine/v2/repositories/{id}: + summary: Git repositories containing drivers and frontend interfaces get: - summary: returns 200 OK when the service is healthy (can connect to the databases - etc) + summary: returns the details of a saved repository tags: - - Root - operationId: PlaceOS::Api::Root_root - parameters: [] + - Repositories + operationId: PlaceOS::Api::Repositories_show + parameters: + - name: id + in: path + required: true + schema: + type: string responses: 200: description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Repository' 409: description: Conflict content: @@ -25787,20 +25817,30 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/platform: - get: - summary: provides release details of the platform + put: + summary: updates a repositories details tags: - - Root - operationId: PlaceOS::Api::Root_platform_info - parameters: [] + - Repositories + operationId: PlaceOS::Api::Repositories_update + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Repository' + required: true + parameters: + - name: id + in: path + required: true + schema: + type: string responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Api__Root__PlatformInfo' + $ref: '#/components/schemas/PlaceOS__Model__Repository' 409: description: Conflict content: @@ -25879,20 +25919,20 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/version: - get: - summary: provides the version of this service + delete: + summary: removes a repository from the server tags: - - Root - operationId: PlaceOS::Api::Root_version - parameters: [] + - Repositories + operationId: PlaceOS::Api::Repositories_destroy + parameters: + - name: id + in: path + required: true + schema: + type: string responses: - 200: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Version' + 202: + description: Accepted 409: description: Conflict content: @@ -25971,22 +26011,30 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/cluster/versions: - get: - summary: provides the core node versions + patch: + summary: updates a repositories details tags: - - Root - operationId: PlaceOS::Api::Root_cluster_version - parameters: [] + - Repositories + operationId: PlaceOS::Api::Repositories_update{2} + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Repository' + required: true + parameters: + - name: id + in: path + required: true + schema: + type: string responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Model__Version' + $ref: '#/components/schemas/PlaceOS__Model__Repository' 409: description: Conflict content: @@ -26065,23 +26113,26 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/scopes: - get: - summary: returns a list of introspected scopes available for use against the - API + /api/engine/v2/repositories/{id}/pull: + summary: Git repositories containing drivers and frontend interfaces + post: + summary: checks the remote for any new commits and pulls them locally tags: - - Root - operationId: PlaceOS::Api::Root_scopes - parameters: [] + - Repositories + operationId: PlaceOS::Api::Repositories_pull + parameters: + - name: id + in: path + required: true + schema: + type: string responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/String' + $ref: '#/components/schemas/NamedTuple_commit_hash__String_' 409: description: Conflict content: @@ -26160,26 +26211,25 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signal: - post: - summary: Can be used in a similar manner to a webhook for drivers - description: 'Can be used in a similar manner to a webhook for drivers + /api/engine/v2/repositories/interfaces: + summary: Git repositories containing drivers and frontend interfaces + get: + summary: Determine loaded interfaces and their current commit + description: 'Determine loaded interfaces and their current commit - pushes arbitrary data to channels in redis' + + Returns a hash of folder_name to commit' tags: - - Root - operationId: PlaceOS::Api::Root_signal - parameters: - - name: channel - in: query - description: the path we would like to send data to - example: /my/data/channel - required: true - schema: - type: string + - Repositories + operationId: PlaceOS::Api::Repositories_loaded_interfaces + parameters: [] responses: 200: description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Hash_String__String_' 409: description: Conflict content: @@ -26258,29 +26308,28 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/reindex: - post: - summary: 'DEPRECATED: search is served directly from PostgreSQL (PPT-2644); - there' - description: 'DEPRECATED: search is served directly from PostgreSQL (PPT-2644); - there - - is no external index to rebuild. Kept as a no-op so existing admin UIs - - and automation don''t error. Remove in a future release.' + /api/engine/v2/repositories/{id}/drivers: + summary: Git repositories containing drivers and frontend interfaces + get: + summary: lists the drivers in a repository tags: - - Root - operationId: PlaceOS::Api::Root_reindex + - Repositories + operationId: PlaceOS::Api::Repositories_drivers parameters: - - name: backfill - in: query - description: deprecated, has no effect - example: "true" + - name: id + in: path + required: true schema: - type: boolean + type: string responses: 200: description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/String' 409: description: Conflict content: @@ -26359,23 +26408,34 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/backfill: - post: - summary: 'DEPRECATED: search is served directly from PostgreSQL (PPT-2644); - the' - description: 'DEPRECATED: search is served directly from PostgreSQL (PPT-2644); - the - - database is always authoritative. Kept as a no-op so existing admin UIs - - and automation don''t error. Remove in a future release.' + /api/engine/v2/repositories/{id}/folders: + summary: Git repositories containing drivers and frontend interfaces + get: + summary: lists the folders tree in a repository tags: - - Root - operationId: PlaceOS::Api::Root_backfill - parameters: [] + - Repositories + operationId: PlaceOS::Api::Repositories_folders + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: include_dots + in: query + description: include dots folders, default to false + example: "true" + schema: + type: boolean responses: 200: description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/String' 409: description: Conflict content: @@ -26454,50 +26514,29 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/schema: - summary: JSON schemas used to validate metadata and settings + /api/engine/v2/repositories/{id}/files: + summary: Git repositories containing drivers and frontend interfaces get: - summary: return the list of schemas - description: 'return the list of schemas + summary: lists the files in an interface repository that match a glob pattern. + description: 'lists the files in an interface repository that match a glob pattern. - schemas can be used to ensure metadata conforms to the desired state' + Paths are returned relative to where they are served, i.e. `//path/to/file.html`' tags: - - Schema - operationId: PlaceOS::Api::Schema_index + - Repositories + operationId: PlaceOS::Api::Repositories_files parameters: - - name: q - in: query - description: filters results by the given text; words match as prefixes against - the resource's searchable fields and all words must match + - name: id + in: path + required: true schema: type: string - - name: limit - in: query - description: the maximum number of results to return - example: "10000" - schema: - type: integer - format: UInt32 - - name: offset - in: query - description: the starting offset of the result set, used to implement pagination - schema: - type: integer - format: UInt32 - - name: ref + - name: pattern in: query - description: deprecated, ignored — pagination follows the `Link` header's - offset + description: glob pattern. Without a `/` it matches file names at any depth, + otherwise the full path (use `**` to recurse) + example: '*.html' schema: type: string - nullable: true - - name: fields - in: query - description: deprecated, ignored — search covers the resource's indexed fields - schema: - type: array - items: - type: string responses: 200: description: OK @@ -26506,7 +26545,7 @@ paths: schema: type: array items: - $ref: '#/components/schemas/PlaceOS__Model__JsonSchema' + $ref: '#/components/schemas/String' 409: description: Conflict content: @@ -26585,25 +26624,50 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - post: - summary: add a new schema + /api/engine/v2/repositories/{id}/commits: + summary: Git repositories containing drivers and frontend interfaces + get: + summary: Returns the commits for a repository or file tags: - - Schema - operationId: PlaceOS::Api::Schema_create - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__JsonSchema' + - Repositories + operationId: PlaceOS::Api::Repositories_commits + parameters: + - name: id + in: path required: true - parameters: [] + schema: + type: string + - name: limit + in: query + description: the maximum number of commits to return + example: "50" + schema: + type: integer + format: Int32 + nullable: true + - name: driver + in: query + description: the path to the file we want commits for + example: path/to/file.cr + schema: + type: string + nullable: true + - name: branch + in: query + description: the branch to grab commits from + example: main + schema: + type: string + nullable: true responses: - 201: - description: Created + 200: + description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__JsonSchema' + type: array + items: + $ref: '#/components/schemas/GitRepository__Commit' 409: description: Conflict content: @@ -26682,26 +26746,41 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/schema/{id}: - summary: JSON schemas used to validate metadata and settings + /api/engine/v2/repositories/{id}/details: + summary: Git repositories containing drivers and frontend interfaces get: - summary: return the details of a schema + summary: Returns the metadata of the driver + description: 'Returns the metadata of the driver + + For payload information, look at https://github.com/placeos/driver/blob/master/docs/command_line_options.md#discovery-and-defaults + + The driver is compiled if it hasn''t been already, which can take a while' tags: - - Schema - operationId: PlaceOS::Api::Schema_show + - Repositories + operationId: PlaceOS::Api::Repositories_details parameters: - name: id in: path required: true schema: type: string + - name: driver + in: query + description: the file we would like metadata for + example: path/to/file.cr + required: true + schema: + type: string + - name: commit + in: query + description: the commit level of the file + example: 3f67a66 + required: true + schema: + type: string responses: 200: description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__JsonSchema' 409: description: Conflict content: @@ -26780,17 +26859,13 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - put: - summary: update a schema details + /api/engine/v2/repositories/{id}/branches: + summary: Git repositories containing drivers and frontend interfaces + get: + summary: returns the list of branches in the repository tags: - - Schema - operationId: PlaceOS::Api::Schema_update - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__JsonSchema' - required: true + - Repositories + operationId: PlaceOS::Api::Repositories_branches parameters: - name: id in: path @@ -26803,7 +26878,9 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__JsonSchema' + type: array + items: + $ref: '#/components/schemas/String' 409: description: Conflict content: @@ -26882,11 +26959,13 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - delete: - summary: remove a schema + /api/engine/v2/repositories/{id}/releases: + summary: Git repositories containing drivers and frontend interfaces + get: + summary: returns the list of releases in the repository, i.e. github releases tags: - - Schema - operationId: PlaceOS::Api::Schema_destroy + - Repositories + operationId: PlaceOS::Api::Repositories_releases parameters: - name: id in: path @@ -26894,8 +26973,14 @@ paths: schema: type: string responses: - 202: - description: Accepted + 200: + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/String' 409: description: Conflict content: @@ -26974,17 +27059,13 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - patch: - summary: update a schema details + /api/engine/v2/repositories/{id}/tags: + summary: Git repositories containing drivers and frontend interfaces + get: + summary: Returns an array of tags for the repository tags: - - Schema - operationId: PlaceOS::Api::Schema_update{2} - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__JsonSchema' - required: true + - Repositories + operationId: PlaceOS::Api::Repositories_tags parameters: - name: id in: path @@ -26997,7 +27078,9 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__JsonSchema' + type: array + items: + $ref: '#/components/schemas/String' 409: description: Conflict content: @@ -27076,56 +27159,26 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/short_url: - summary: Short URLs and QR codes that redirect to longer links + /api/engine/v2/repositories/{id}/default_branch: + summary: Git repositories containing drivers and frontend interfaces get: - summary: list the short URLs for this domain + summary: returns the default branch of the specified repository tags: - - ShortURL - operationId: PlaceOS::Api::ShortURL_index + - Repositories + operationId: PlaceOS::Api::Repositories_default_branch parameters: - - name: q - in: query - description: filters results by the given text; words match as prefixes against - the resource's searchable fields and all words must match - schema: - type: string - - name: limit - in: query - description: the maximum number of results to return - example: "10000" - schema: - type: integer - format: UInt32 - - name: offset - in: query - description: the starting offset of the result set, used to implement pagination - schema: - type: integer - format: UInt32 - - name: ref - in: query - description: deprecated, ignored — pagination follows the `Link` header's - offset + - name: id + in: path + required: true schema: type: string - nullable: true - - name: fields - in: query - description: deprecated, ignored — search covers the resource's indexed fields - schema: - type: array - items: - type: string responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Model__Shortener' + $ref: '#/components/schemas/String' 409: description: Conflict content: @@ -27204,25 +27257,42 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - post: - summary: add a new short URL + /api/engine/v2/repositories/remote_default_branch: + summary: Git repositories containing drivers and frontend interfaces + get: + summary: returns the default branch of the specified repository tags: - - ShortURL - operationId: PlaceOS::Api::ShortURL_create - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Shortener' + - Repositories + operationId: PlaceOS::Api::Repositories_remote_default_branch + parameters: + - name: repository_url + in: query + description: the git url that represents the repository + example: https://github.com/PlaceOS/drivers.git required: true - parameters: [] + schema: + type: string + - name: username + in: query + description: a username for access if required + example: steve + schema: + type: string + nullable: true + - name: password + in: query + description: the password or access token as required + example: ab34cfe4567 + schema: + type: string + nullable: true responses: - 201: - description: Created + 200: + description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Shortener' + $ref: '#/components/schemas/String' 409: description: Conflict content: @@ -27301,26 +27371,44 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/short_url/{id}: - summary: Short URLs and QR codes that redirect to longer links + /api/engine/v2/repositories/remote_branches: + summary: Git repositories containing drivers and frontend interfaces get: - summary: return the details of the requested shortened URL + summary: lists the branches of the specified repository tags: - - ShortURL - operationId: PlaceOS::Api::ShortURL_show + - Repositories + operationId: PlaceOS::Api::Repositories_remote_branches parameters: - - name: id - in: path + - name: repository_url + in: query + description: the git url that represents the repository + example: https://github.com/PlaceOS/drivers.git required: true schema: type: string + - name: username + in: query + description: a username for access if required + example: steve + schema: + type: string + nullable: true + - name: password + in: query + description: the password or access token as required + example: ab34cfe4567 + schema: + type: string + nullable: true responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Shortener' + type: array + items: + $ref: '#/components/schemas/String' 409: description: Conflict content: @@ -27399,30 +27487,58 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - put: - summary: update the details of a short URL + /api/engine/v2/repositories/remote_commits: + summary: Git repositories containing drivers and frontend interfaces + get: + summary: lists the commits of the specified repository tags: - - ShortURL - operationId: PlaceOS::Api::ShortURL_update - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Shortener' - required: true + - Repositories + operationId: PlaceOS::Api::Repositories_remote_commits parameters: - - name: id - in: path + - name: branch + in: query + description: the branch to grab commits from + example: main + schema: + type: string + nullable: true + - name: depth + in: query + description: the number of commits to return + example: "50" + schema: + type: integer + format: Int32 + - name: repository_url + in: query + description: the git url that represents the repository + example: https://github.com/PlaceOS/drivers.git required: true schema: type: string + - name: username + in: query + description: a username for access if required + example: steve + schema: + type: string + nullable: true + - name: password + in: query + description: the password or access token as required + example: ab34cfe4567 + schema: + type: string + nullable: true responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Shortener' + type: array + items: + $ref: '#/components/schemas/GitRepository__Commit' 409: description: Conflict content: @@ -27501,20 +27617,44 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - delete: - summary: remove a short URL + /api/engine/v2/repositories/remote_tags: + summary: Git repositories containing drivers and frontend interfaces + get: + summary: Returns an array of tags for the specified repository tags: - - ShortURL - operationId: PlaceOS::Api::ShortURL_destroy + - Repositories + operationId: PlaceOS::Api::Repositories_remote_tags parameters: - - name: id - in: path + - name: repository_url + in: query + description: the git url that represents the repository + example: https://github.com/PlaceOS/drivers.git required: true schema: type: string + - name: username + in: query + description: a username for access if required + example: steve + schema: + type: string + nullable: true + - name: password + in: query + description: the password or access token as required + example: ab34cfe4567 + schema: + type: string + nullable: true responses: - 202: - description: Accepted + 200: + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/String' 409: description: Conflict content: @@ -27593,30 +27733,17 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - patch: - summary: update the details of a short URL + /api/engine/v2: + get: + summary: returns 200 OK when the service is healthy (can connect to the databases + etc) tags: - - ShortURL - operationId: PlaceOS::Api::ShortURL_update{2} - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Shortener' - required: true - parameters: - - name: id - in: path - required: true - schema: - type: string + - Root + operationId: PlaceOS::Api::Root_root + parameters: [] responses: 200: description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Shortener' 409: description: Conflict content: @@ -27695,22 +27822,20 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/short_url/{id}/redirect: - summary: Short URLs and QR codes that redirect to longer links + /api/engine/v2/platform: get: - summary: redirects to the URI specified by the provided short URL id + summary: provides release details of the platform tags: - - ShortURL - operationId: PlaceOS::Api::ShortURL_redirect - parameters: - - name: id - in: path - required: true - schema: - type: string + - Root + operationId: PlaceOS::Api::Root_platform_info + parameters: [] responses: - 303: - description: See Other + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Root__PlatformInfo' 409: description: Conflict content: @@ -27789,22 +27914,20 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/short_url/{id}/qr_code.svg: - summary: Short URLs and QR codes that redirect to longer links + /api/engine/v2/version: get: - summary: obtains an SVG QR code for the specified short URL + summary: provides the version of this service tags: - - ShortURL - operationId: PlaceOS::Api::ShortURL_svg_qr - parameters: - - name: id - in: path - required: true - schema: - type: string + - Root + operationId: PlaceOS::Api::Root_version + parameters: [] responses: 200: description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Version' 409: description: Conflict content: @@ -27883,28 +28006,22 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/short_url/{id}/qr_code.png: - summary: Short URLs and QR codes that redirect to longer links + /api/engine/v2/cluster/versions: get: - summary: obtains an PNG QR code for the specified short URL with optional resolution + summary: provides the core node versions tags: - - ShortURL - operationId: PlaceOS::Api::ShortURL_png_qr - parameters: - - name: id - in: path - required: true - schema: - type: string - - name: size - in: query - description: size of the QR code in pixels. Between 72px and 512px - schema: - type: integer - format: Int32 + - Root + operationId: PlaceOS::Api::Root_cluster_version + parameters: [] responses: 200: description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PlaceOS__Model__Version' 409: description: Conflict content: @@ -27983,36 +28100,115 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/short_url/qr_code: - summary: Short URLs and QR codes that redirect to longer links + /api/engine/v2/scopes: get: - summary: helper for generating QR codes with user defined content + summary: returns a list of introspected scopes available for use against the + API tags: - - ShortURL - operationId: PlaceOS::Api::ShortURL_generate_qr_code + - Root + operationId: PlaceOS::Api::Root_scopes + parameters: [] + responses: + 200: + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/String' + 409: + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 503: + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 404: + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 429: + description: Too Many Requests + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 422: + description: Unprocessable Entity + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 502: + description: Bad Gateway + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 504: + description: Gateway Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 406: + description: Not Acceptable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + 408: + description: Request Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 400: + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 415: + description: Unsupported Media Type + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + /api/engine/v2/signal: + post: + summary: Can be used in a similar manner to a webhook for drivers + description: 'Can be used in a similar manner to a webhook for drivers + + pushes arbitrary data to channels in redis' + tags: + - Root + operationId: PlaceOS::Api::Root_signal parameters: - - name: content - in: query - description: the contents of the QR code - required: true - schema: - type: string - - name: format - in: query - description: file format of the response - schema: - type: string - enum: - - svg - - png - - name: size - in: query - description: size of the QR code in pixels. Between 72px and 512px - schema: - type: integer - format: Int32 - - name: id + - name: channel in: query + description: the path we would like to send data to + example: /my/data/channel required: true schema: type: string @@ -28097,33 +28293,29 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/ai/capabilities: - summary: Generate and edit signage artwork. - description: 'Generate and edit signage artwork. - - - A request validates, reserves a slot per vendor call, writes a job row and - - hands off to a fiber, answering 202 immediately. The client then long polls - - `jobs/:id`, which holds the connection until something changes or the wait + /api/engine/v2/reindex: + post: + summary: 'DEPRECATED: search is served directly from PostgreSQL (PPT-2644); + there' + description: 'DEPRECATED: search is served directly from PostgreSQL (PPT-2644); + there - runs out, so a finished candidate reaches the browser within about half a + is no external index to rebuild. Kept as a no-op so existing admin UIs - second of landing without a socket.' - get: - summary: what this domain can do, and what the caller has left of their quota + and automation don''t error. Remove in a future release.' tags: - - SignageAI - operationId: PlaceOS::Api::SignageAI_capabilities - parameters: [] + - Root + operationId: PlaceOS::Api::Root_reindex + parameters: + - name: backfill + in: query + description: deprecated, has no effect + example: "true" + schema: + type: boolean responses: 200: description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__SignageAI__Capabilities' 409: description: Conflict content: @@ -28202,39 +28394,23 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/ai/generate: - summary: Generate and edit signage artwork. - description: 'Generate and edit signage artwork. - - - A request validates, reserves a slot per vendor call, writes a job row and - - hands off to a fiber, answering 202 immediately. The client then long polls - - `jobs/:id`, which holds the connection until something changes or the wait + /api/engine/v2/backfill: + post: + summary: 'DEPRECATED: search is served directly from PostgreSQL (PPT-2644); + the' + description: 'DEPRECATED: search is served directly from PostgreSQL (PPT-2644); + the - runs out, so a finished candidate reaches the browser within about half a + database is always authoritative. Kept as a no-op so existing admin UIs - second of landing without a socket.' - post: - summary: start a generate + and automation don''t error. Remove in a future release.' tags: - - SignageAI - operationId: PlaceOS::Api::SignageAI_generate - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__SignageAI__GenerateParams' - required: true + - Root + operationId: PlaceOS::Api::Root_backfill parameters: [] responses: - 202: - description: Accepted - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__SignageAI__JobResponse' + 200: + description: OK 409: description: Conflict content: @@ -28313,39 +28489,59 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/ai/edit: - summary: Generate and edit signage artwork. - description: 'Generate and edit signage artwork. - - - A request validates, reserves a slot per vendor call, writes a job row and - - hands off to a fiber, answering 202 immediately. The client then long polls - - `jobs/:id`, which holds the connection until something changes or the wait - - runs out, so a finished candidate reaches the browser within about half a + /api/engine/v2/schema: + summary: JSON schemas used to validate metadata and settings + get: + summary: return the list of schemas + description: 'return the list of schemas - second of landing without a socket.' - post: - summary: start an edit, or a refine of an earlier job + schemas can be used to ensure metadata conforms to the desired state' tags: - - SignageAI - operationId: PlaceOS::Api::SignageAI_edit - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__SignageAI__EditParams' - required: true - parameters: [] + - Schema + operationId: PlaceOS::Api::Schema_index + parameters: + - name: q + in: query + description: filters results by the given text; words match as prefixes against + the resource's searchable fields and all words must match + schema: + type: string + - name: limit + in: query + description: the maximum number of results to return + example: "10000" + schema: + type: integer + format: UInt32 + - name: offset + in: query + description: the starting offset of the result set, used to implement pagination + schema: + type: integer + format: UInt32 + - name: ref + in: query + description: deprecated, ignored — pagination follows the `Link` header's + offset + schema: + type: string + nullable: true + - name: fields + in: query + description: deprecated, ignored — search covers the resource's indexed fields + schema: + type: array + items: + type: string responses: - 202: - description: Accepted + 200: + description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Api__SignageAI__JobResponse' + type: array + items: + $ref: '#/components/schemas/PlaceOS__Model__JsonSchema' 409: description: Conflict content: @@ -28424,55 +28620,25 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/ai/jobs/{id}: - summary: Generate and edit signage artwork. - description: 'Generate and edit signage artwork. - - - A request validates, reserves a slot per vendor call, writes a job row and - - hands off to a fiber, answering 202 immediately. The client then long polls - - `jobs/:id`, which holds the connection until something changes or the wait - - runs out, so a finished candidate reaches the browser within about half a - - second of landing without a socket.' - get: - summary: 'long poll: holds until the job changes past `since`, or `wait` runs - out' + post: + summary: add a new schema tags: - - SignageAI - operationId: PlaceOS::Api::SignageAI_show_job - parameters: - - name: id - in: path + - Schema + operationId: PlaceOS::Api::Schema_create + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__JsonSchema' required: true - schema: - type: string - format: uuid - - name: wait - in: query - description: seconds to hold the request open, 0 to answer immediately - example: "25" - schema: - type: integer - format: Int32 - - name: since - in: query - description: the version the caller already has - example: "3" - schema: - type: integer - format: Int32 - nullable: true + parameters: [] responses: - 200: - description: OK + 201: + description: Created content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Api__SignageAI__JobResponse' + $ref: '#/components/schemas/PlaceOS__Model__JsonSchema' 409: description: Conflict content: @@ -28551,46 +28717,26 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/ai/jobs: - summary: Generate and edit signage artwork. - description: 'Generate and edit signage artwork. - - - A request validates, reserves a slot per vendor call, writes a job row and - - hands off to a fiber, answering 202 immediately. The client then long polls - - `jobs/:id`, which holds the connection until something changes or the wait - - runs out, so a finished candidate reaches the browser within about half a - - second of landing without a socket.' + /api/engine/v2/schema/{id}: + summary: JSON schemas used to validate metadata and settings get: - summary: the caller's recent jobs, for the recent generations list + summary: return the details of a schema tags: - - SignageAI - operationId: PlaceOS::Api::SignageAI_index_jobs + - Schema + operationId: PlaceOS::Api::Schema_show parameters: - - name: mine - in: query - description: only the caller's own jobs - example: "true" - schema: - type: boolean - - name: limit - in: query + - name: id + in: path + required: true schema: - type: integer - format: Int32 + type: string responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Api__SignageAI__JobResponse' + $ref: '#/components/schemas/PlaceOS__Model__JsonSchema' 409: description: Conflict content: @@ -28669,39 +28815,30 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/ai/jobs/{id}/cancel: - summary: Generate and edit signage artwork. - description: 'Generate and edit signage artwork. - - - A request validates, reserves a slot per vendor call, writes a job row and - - hands off to a fiber, answering 202 immediately. The client then long polls - - `jobs/:id`, which holds the connection until something changes or the wait - - runs out, so a finished candidate reaches the browser within about half a - - second of landing without a socket.' - post: - summary: ask a running job to stop. Calls already with a vendor run to completion. + put: + summary: update a schema details tags: - - SignageAI - operationId: PlaceOS::Api::SignageAI_cancel + - Schema + operationId: PlaceOS::Api::Schema_update + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__JsonSchema' + required: true parameters: - name: id in: path required: true schema: type: string - format: uuid responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Api__SignageAI__JobResponse' + $ref: '#/components/schemas/PlaceOS__Model__JsonSchema' 409: description: Conflict content: @@ -28780,46 +28917,20 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/ai/jobs/{id}/claim: - summary: Generate and edit signage artwork. - description: 'Generate and edit signage artwork. - - - A request validates, reserves a slot per vendor call, writes a job row and - - hands off to a fiber, answering 202 immediately. The client then long polls - - `jobs/:id`, which holds the connection until something changes or the wait - - runs out, so a finished candidate reaches the browser within about half a - - second of landing without a socket.' - post: - summary: record that a candidate became a media item, so the sweep leaves it - alone + delete: + summary: remove a schema tags: - - SignageAI - operationId: PlaceOS::Api::SignageAI_claim - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__SignageAI__ClaimParams' - required: true + - Schema + operationId: PlaceOS::Api::Schema_destroy parameters: - name: id in: path required: true schema: type: string - format: uuid responses: - 200: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__SignageAI__JobResponse' + 202: + description: Accepted 409: description: Conflict content: @@ -28898,49 +29009,30 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/ai/usage: - summary: Generate and edit signage artwork. - description: 'Generate and edit signage artwork. - - - A request validates, reserves a slot per vendor call, writes a job row and - - hands off to a fiber, answering 202 immediately. The client then long polls - - `jobs/:id`, which holds the connection until something changes or the wait - - runs out, so a finished candidate reaches the browser within about half a - - second of landing without a socket.' - get: - summary: spend per provider and model, for the Backoffice usage tab + patch: + summary: update a schema details tags: - - SignageAI - operationId: PlaceOS::Api::SignageAI_usage + - Schema + operationId: PlaceOS::Api::Schema_update{2} + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__JsonSchema' + required: true parameters: - - name: from - in: query - description: unix seconds, defaults to 30 days ago - schema: - type: integer - format: Int64 - nullable: true - - name: to - in: query - description: unix seconds, defaults to now + - name: id + in: path + required: true schema: - type: integer - format: Int64 - nullable: true + type: string responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Model__SignageAIJob__UsageRow' + $ref: '#/components/schemas/PlaceOS__Model__JsonSchema' 409: description: Conflict content: @@ -29019,36 +29111,14 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/ai/providers: - summary: Vendor credentials for signage image generation. - description: 'Vendor credentials for signage image generation. - - - The Data Stores rule: sys_admin writes, support reads. Responses never carry - - the credentials, so every route renders `as_json` rather than the row.' + /api/engine/v2/short_url: + summary: Short URLs and QR codes that redirect to longer links get: - summary: 'The rows this domain may use: its own, and the shared fallback.' - description: 'The rows this domain may use: its own, and the shared fallback. - - - There is deliberately no way to list another domain''s rows. The previous - - version returned every row in the deployment when called without a - - parameter, which handed one customer''s endpoint, model list and quotas to - - any administrator of any other.' + summary: list the short URLs for this domain tags: - - SignageAIProviders - operationId: PlaceOS::Api::SignageAIProviders_index + - ShortURL + operationId: PlaceOS::Api::ShortURL_index parameters: - - name: include_shared - in: query - description: include the shared fallback row - example: "true" - schema: - type: boolean - name: q in: query description: filters results by the given text; words match as prefixes against @@ -29090,7 +29160,7 @@ paths: schema: type: array items: - $ref: '#/components/schemas/NamedTuple_id__String__name__String__provider__String__authority_id__String___Nil__endpoint__String___Nil__location__String___Nil__default_model__String___Nil__allowed_models__Array_String___enabled__Bool__is_default__Bool__quotas__JSON__Any__created_at__Int64__updated_at__Int64_' + $ref: '#/components/schemas/PlaceOS__Model__Shortener' 409: description: Conflict content: @@ -29170,14 +29240,15 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' post: + summary: add a new short URL tags: - - SignageAIProviders - operationId: PlaceOS::Api::SignageAIProviders_create + - ShortURL + operationId: PlaceOS::Api::ShortURL_create requestBody: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Api__SignageAIProviders__ProviderParams' + $ref: '#/components/schemas/PlaceOS__Model__Shortener' required: true parameters: [] responses: @@ -29186,7 +29257,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/NamedTuple_id__String__name__String__provider__String__authority_id__String___Nil__endpoint__String___Nil__location__String___Nil__default_model__String___Nil__allowed_models__Array_String___enabled__Bool__is_default__Bool__quotas__JSON__Any__created_at__Int64__updated_at__Int64_' + $ref: '#/components/schemas/PlaceOS__Model__Shortener' 409: description: Conflict content: @@ -29265,32 +29336,26 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/ai/providers/{id}: - summary: Vendor credentials for signage image generation. - description: 'Vendor credentials for signage image generation. - - - The Data Stores rule: sys_admin writes, support reads. Responses never carry - - the credentials, so every route renders `as_json` rather than the row.' + /api/engine/v2/short_url/{id}: + summary: Short URLs and QR codes that redirect to longer links get: + summary: return the details of the requested shortened URL tags: - - SignageAIProviders - operationId: PlaceOS::Api::SignageAIProviders_show + - ShortURL + operationId: PlaceOS::Api::ShortURL_show parameters: - name: id in: path required: true schema: type: string - format: uuid responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/NamedTuple_id__String__name__String__provider__String__authority_id__String___Nil__endpoint__String___Nil__location__String___Nil__default_model__String___Nil__allowed_models__Array_String___enabled__Bool__is_default__Bool__quotas__JSON__Any__created_at__Int64__updated_at__Int64_' + $ref: '#/components/schemas/PlaceOS__Model__Shortener' 409: description: Conflict content: @@ -29370,14 +29435,15 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' put: + summary: update the details of a short URL tags: - - SignageAIProviders - operationId: PlaceOS::Api::SignageAIProviders_update + - ShortURL + operationId: PlaceOS::Api::ShortURL_update requestBody: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Api__SignageAIProviders__ProviderParams' + $ref: '#/components/schemas/PlaceOS__Model__Shortener' required: true parameters: - name: id @@ -29385,14 +29451,13 @@ paths: required: true schema: type: string - format: uuid responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/NamedTuple_id__String__name__String__provider__String__authority_id__String___Nil__endpoint__String___Nil__location__String___Nil__default_model__String___Nil__allowed_models__Array_String___enabled__Bool__is_default__Bool__quotas__JSON__Any__created_at__Int64__updated_at__Int64_' + $ref: '#/components/schemas/PlaceOS__Model__Shortener' 409: description: Conflict content: @@ -29472,16 +29537,16 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' delete: + summary: remove a short URL tags: - - SignageAIProviders - operationId: PlaceOS::Api::SignageAIProviders_destroy + - ShortURL + operationId: PlaceOS::Api::ShortURL_destroy parameters: - name: id in: path required: true schema: type: string - format: uuid responses: 202: description: Accepted @@ -29564,14 +29629,15 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' patch: + summary: update the details of a short URL tags: - - SignageAIProviders - operationId: PlaceOS::Api::SignageAIProviders_update{2} + - ShortURL + operationId: PlaceOS::Api::ShortURL_update{2} requestBody: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Api__SignageAIProviders__ProviderParams' + $ref: '#/components/schemas/PlaceOS__Model__Shortener' required: true parameters: - name: id @@ -29579,14 +29645,13 @@ paths: required: true schema: type: string - format: uuid responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/NamedTuple_id__String__name__String__provider__String__authority_id__String___Nil__endpoint__String___Nil__location__String___Nil__default_model__String___Nil__allowed_models__Array_String___enabled__Bool__is_default__Bool__quotas__JSON__Any__created_at__Int64__updated_at__Int64_' + $ref: '#/components/schemas/PlaceOS__Model__Shortener' 409: description: Conflict content: @@ -29665,38 +29730,116 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/ai/providers/{id}/test: - summary: Vendor credentials for signage image generation. - description: 'Vendor credentials for signage image generation. - - - The Data Stores rule: sys_admin writes, support reads. Responses never carry - - the credentials, so every route renders `as_json` rather than the row.' - post: - summary: 'Prove a row''s credentials work, without leaving anything behind: - one small' - description: 'Prove a row''s credentials work, without leaving anything behind: - one small - - image, discarded.' + /api/engine/v2/short_url/{id}/redirect: + summary: Short URLs and QR codes that redirect to longer links + get: + summary: redirects to the URI specified by the provided short URL id tags: - - SignageAIProviders - operationId: PlaceOS::Api::SignageAIProviders_test + - ShortURL + operationId: PlaceOS::Api::ShortURL_redirect parameters: - name: id in: path required: true schema: type: string - format: uuid responses: - 200: - description: OK + 303: + description: See Other + 409: + description: Conflict content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Api__SignageAIProviders__TestResult' + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 503: + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 404: + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 429: + description: Too Many Requests + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 422: + description: Unprocessable Entity + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 502: + description: Bad Gateway + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 504: + description: Gateway Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 406: + description: Not Acceptable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + 408: + description: Request Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 400: + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 415: + description: Unsupported Media Type + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + /api/engine/v2/short_url/{id}/qr_code.svg: + summary: Short URLs and QR codes that redirect to longer links + get: + summary: obtains an SVG QR code for the specified short URL + tags: + - ShortURL + operationId: PlaceOS::Api::ShortURL_svg_qr + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + 200: + description: OK 409: description: Conflict content: @@ -29775,84 +29918,28 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/media: - summary: 'Digital signage media: images, videos, web pages and plugins that can - be played' + /api/engine/v2/short_url/{id}/qr_code.png: + summary: Short URLs and QR codes that redirect to longer links get: - summary: list media items for the current authority. - description: 'list media items for the current authority. - - - Non-admin callers see only items linked to groups they''re a member - - of (direct or transitive). `group_id=...` scopes to a single - - group; `q=...` is a case-insensitive substring search across - - `name` and `description`; `tags=...` returns items carrying any of - - the supplied tags.' + summary: obtains an PNG QR code for the specified short URL with optional resolution tags: - - PlaylistMedia - operationId: PlaceOS::Api::PlaylistMedia_index + - ShortURL + operationId: PlaceOS::Api::ShortURL_png_qr parameters: - - name: group_id - in: query - description: filter to items linked to this group (caller must have Read on - the group) - schema: - type: string - format: uuid - nullable: true - - name: q - in: query - description: case-insensitive substring search on name and description (SQL - ILIKE) + - name: id + in: path + required: true schema: type: string - nullable: true - - name: tags - in: query - description: return items carrying any of these tags - example: promo,lobby - schema: - type: array - items: - type: string - nullable: true - - name: limit - in: query - schema: - type: integer - format: Int32 - - name: offset + - name: size in: query + description: size of the QR code in pixels. Between 72px and 512px schema: type: integer format: Int32 - - name: ref - in: query - description: deprecated, ignored — pagination follows the `Link` header's - offset - schema: - type: string - nullable: true - - name: fields - in: query - description: deprecated, ignored — search covers the resource's indexed fields - schema: - type: array - items: - type: string responses: 200: description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Model__Playlist__Item' 409: description: Conflict content: @@ -29931,40 +30018,42 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - post: - summary: add a new media item. Non-admin callers must supply `group_id` - description: 'add a new media item. Non-admin callers must supply `group_id` - - (and hold Create permission on that group); the item is - - auto-linked via a GroupPlaylistItem junction row so the creator - - can see it immediately.' + /api/engine/v2/short_url/qr_code: + summary: Short URLs and QR codes that redirect to longer links + get: + summary: helper for generating QR codes with user defined content tags: - - PlaylistMedia - operationId: PlaceOS::Api::PlaylistMedia_create - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Playlist__Item' - required: true + - ShortURL + operationId: PlaceOS::Api::ShortURL_generate_qr_code parameters: - - name: group_id + - name: content in: query - description: group id to auto-link the new item to (required for non-admin - callers) + description: the contents of the QR code + required: true + schema: + type: string + - name: format + in: query + description: file format of the response + schema: + type: string + enum: + - svg + - png + - name: size + in: query + description: size of the QR code in pixels. Between 72px and 512px + schema: + type: integer + format: Int32 + - name: id + in: query + required: true schema: type: string - format: uuid - nullable: true responses: - 201: - description: Created - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Playlist__Item' + 200: + description: OK 409: description: Conflict content: @@ -30043,42 +30132,33 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/media/tags: - summary: 'Digital signage media: images, videos, web pages and plugins that can - be played' - get: - summary: return the distinct tags in use by media. Scopes the same way as - description: 'return the distinct tags in use by media. Scopes the same way - as + /api/engine/v2/signage/ai/capabilities: + summary: Generate and edit signage artwork. + description: 'Generate and edit signage artwork. - `index`: `group_id=...` limits to media linked to that group (caller - must have Read on it); without `group_id`, admin/support callers see + A request validates, reserves a slot per vendor call, writes a job row and - every tag in the authority while regular users see tags from media in + hands off to a fiber, answering 202 immediately. The client then long polls - groups they can read.' + `jobs/:id`, which holds the connection until something changes or the wait + + runs out, so a finished candidate reaches the browser within about half a + + second of landing without a socket.' + get: + summary: what this domain can do, and what the caller has left of their quota tags: - - PlaylistMedia - operationId: PlaceOS::Api::PlaylistMedia_tags - parameters: - - name: group_id - in: query - description: limit to media linked to this group (caller must have Read on - the group) - schema: - type: string - format: uuid - nullable: true + - SignageAI + operationId: PlaceOS::Api::SignageAI_capabilities + parameters: [] responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/String' + $ref: '#/components/schemas/PlaceOS__Api__SignageAI__Capabilities' 409: description: Conflict content: @@ -30157,48 +30237,39 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - delete: - summary: remove a tag from every media item carrying it, or with - description: 'remove a tag from every media item carrying it, or with + /api/engine/v2/signage/ai/generate: + summary: Generate and edit signage artwork. + description: 'Generate and edit signage artwork. - `remove_media=true` remove the tagged media itself. Scoping and - permissions per `tag_operation_scope`: authority-wide is admin/support + A request validates, reserves a slot per vendor call, writes a job row and - only, `group_id` requires Read and Update (or Manage) on that group — + hands off to a fiber, answering 202 immediately. The client then long polls - Read and Delete when removing media. Removing media with `group_id` + `jobs/:id`, which holds the connection until something changes or the wait - behaves like `DELETE /:id?group_id=`: the items are unlinked from that + runs out, so a finished candidate reaches the browser within about half a - group and only deleted once no group links remain.' + second of landing without a socket.' + post: + summary: start a generate tags: - - PlaylistMedia - operationId: PlaceOS::Api::PlaylistMedia_remove_tag - parameters: - - name: tag - in: query - description: the tag to be removed + - SignageAI + operationId: PlaceOS::Api::SignageAI_generate + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__SignageAI__GenerateParams' required: true - schema: - type: string - - name: remove_media - in: query - description: if true, removes the tag by removing all the media with this - tag - schema: - type: boolean - - name: group_id - in: query - description: limit to media linked to this group (caller must have Read and - Update on the group, Read and Delete when removing media) - schema: - type: string - format: uuid - nullable: true + parameters: [] responses: - 200: - description: OK + 202: + description: Accepted + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__SignageAI__JobResponse' 409: description: Conflict content: @@ -30277,43 +30348,39 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - patch: - summary: rename a tag on every media item carrying it. Items that already carry - description: 'rename a tag on every media item carrying it. Items that already - carry + /api/engine/v2/signage/ai/edit: + summary: Generate and edit signage artwork. + description: 'Generate and edit signage artwork. - `new_tag` end up with it once. Scoping and permissions per - `tag_operation_scope`: authority-wide is admin/support only, `group_id` + A request validates, reserves a slot per vendor call, writes a job row and - requires Read and Update (or Manage) on that group.' + hands off to a fiber, answering 202 immediately. The client then long polls + + `jobs/:id`, which holds the connection until something changes or the wait + + runs out, so a finished candidate reaches the browser within about half a + + second of landing without a socket.' + post: + summary: start an edit, or a refine of an earlier job tags: - - PlaylistMedia - operationId: PlaceOS::Api::PlaylistMedia_rename_tag - parameters: - - name: current_tag - in: query - description: the existing tag to be renamed - required: true - schema: - type: string - - name: new_tag - in: query - description: the replacement tag name + - SignageAI + operationId: PlaceOS::Api::SignageAI_edit + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__SignageAI__EditParams' required: true - schema: - type: string - - name: group_id - in: query - description: limit to media linked to this group (caller must have Read and - Update on the group) - schema: - type: string - format: uuid - nullable: true + parameters: [] responses: - 200: - description: OK + 202: + description: Accepted + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__SignageAI__JobResponse' 409: description: Conflict content: @@ -30392,34 +30459,47 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/media/tag_counts: - summary: 'Digital signage media: images, videos, web pages and plugins that can - be played' - get: - summary: return the number of media items carrying each tag. Scopes the same - description: 'return the number of media items carrying each tag. Scopes the - same + /api/engine/v2/signage/ai/jobs/{id}: + summary: Generate and edit signage artwork. + description: 'Generate and edit signage artwork. - way as `index`/`tags`: `group_id=...` limits to media linked to that - group (caller must have Read on it); without `group_id`, admin/support + A request validates, reserves a slot per vendor call, writes a job row and - callers see counts across the authority while regular users see counts + hands off to a fiber, answering 202 immediately. The client then long polls - from media in groups they can read. An item carrying a tag twice still + `jobs/:id`, which holds the connection until something changes or the wait - counts once.' + runs out, so a finished candidate reaches the browser within about half a + + second of landing without a socket.' + get: + summary: 'long poll: holds until the job changes past `since`, or `wait` runs + out' tags: - - PlaylistMedia - operationId: PlaceOS::Api::PlaylistMedia_tag_counts + - SignageAI + operationId: PlaceOS::Api::SignageAI_show_job parameters: - - name: group_id - in: query - description: limit to media linked to this group (caller must have Read on - the group) + - name: id + in: path + required: true schema: type: string format: uuid + - name: wait + in: query + description: seconds to hold the request open, 0 to answer immediately + example: "25" + schema: + type: integer + format: Int32 + - name: since + in: query + description: the version the caller already has + example: "3" + schema: + type: integer + format: Int32 nullable: true responses: 200: @@ -30427,7 +30507,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Hash_String__Int32_' + $ref: '#/components/schemas/PlaceOS__Api__SignageAI__JobResponse' 409: description: Conflict content: @@ -30506,31 +30586,46 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/media/{id}: - summary: 'Digital signage media: images, videos, web pages and plugins that can - be played' - get: - summary: return the details of the requested media item, including the groups - description: 'return the details of the requested media item, including the - groups + /api/engine/v2/signage/ai/jobs: + summary: Generate and edit signage artwork. + description: 'Generate and edit signage artwork. - it has been shared with and the playlists it appears in' + + A request validates, reserves a slot per vendor call, writes a job row and + + hands off to a fiber, answering 202 immediately. The client then long polls + + `jobs/:id`, which holds the connection until something changes or the wait + + runs out, so a finished candidate reaches the browser within about half a + + second of landing without a socket.' + get: + summary: the caller's recent jobs, for the recent generations list tags: - - PlaylistMedia - operationId: PlaceOS::Api::PlaylistMedia_show + - SignageAI + operationId: PlaceOS::Api::SignageAI_index_jobs parameters: - - name: id - in: path - required: true + - name: mine + in: query + description: only the caller's own jobs + example: "true" schema: - type: string + type: boolean + - name: limit + in: query + schema: + type: integer + format: Int32 responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Playlist__Item' + type: array + items: + $ref: '#/components/schemas/PlaceOS__Api__SignageAI__JobResponse' 409: description: Conflict content: @@ -30609,30 +30704,39 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - put: - summary: update the details of a media item + /api/engine/v2/signage/ai/jobs/{id}/cancel: + summary: Generate and edit signage artwork. + description: 'Generate and edit signage artwork. + + + A request validates, reserves a slot per vendor call, writes a job row and + + hands off to a fiber, answering 202 immediately. The client then long polls + + `jobs/:id`, which holds the connection until something changes or the wait + + runs out, so a finished candidate reaches the browser within about half a + + second of landing without a socket.' + post: + summary: ask a running job to stop. Calls already with a vendor run to completion. tags: - - PlaylistMedia - operationId: PlaceOS::Api::PlaylistMedia_update - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Playlist__Item' - required: true + - SignageAI + operationId: PlaceOS::Api::SignageAI_cancel parameters: - name: id in: path required: true schema: type: string + format: uuid responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Playlist__Item' + $ref: '#/components/schemas/PlaceOS__Api__SignageAI__JobResponse' 409: description: Conflict content: @@ -30711,37 +30815,46 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - delete: - summary: remove a media item from the library. With `group_id` the item is - description: 'remove a media item from the library. With `group_id` the item - is + /api/engine/v2/signage/ai/jobs/{id}/claim: + summary: Generate and edit signage artwork. + description: 'Generate and edit signage artwork. - unlinked from that group (its `GroupPlaylistItem` row is removed) and - remains available to any other groups it is shared with; when that was + A request validates, reserves a slot per vendor call, writes a job row and - its last group link the item itself is deleted so nothing is orphaned.' + hands off to a fiber, answering 202 immediately. The client then long polls + + `jobs/:id`, which holds the connection until something changes or the wait + + runs out, so a finished candidate reaches the browser within about half a + + second of landing without a socket.' + post: + summary: record that a candidate became a media item, so the sweep leaves it + alone tags: - - PlaylistMedia - operationId: PlaceOS::Api::PlaylistMedia_destroy + - SignageAI + operationId: PlaceOS::Api::SignageAI_claim + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__SignageAI__ClaimParams' + required: true parameters: - name: id in: path required: true schema: type: string - - name: group_id - in: query - description: remove the item from this group instead of deleting it outright - (caller needs Delete or Manage on the group); the item is deleted if no - group links remain - schema: - type: string format: uuid - nullable: true responses: - 202: - description: Accepted + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__SignageAI__JobResponse' 409: description: Conflict content: @@ -30820,30 +30933,49 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - patch: - summary: update the details of a media item + /api/engine/v2/signage/ai/usage: + summary: Generate and edit signage artwork. + description: 'Generate and edit signage artwork. + + + A request validates, reserves a slot per vendor call, writes a job row and + + hands off to a fiber, answering 202 immediately. The client then long polls + + `jobs/:id`, which holds the connection until something changes or the wait + + runs out, so a finished candidate reaches the browser within about half a + + second of landing without a socket.' + get: + summary: spend per provider and model, for the Backoffice usage tab tags: - - PlaylistMedia - operationId: PlaceOS::Api::PlaylistMedia_update{2} - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Playlist__Item' - required: true + - SignageAI + operationId: PlaceOS::Api::SignageAI_usage parameters: - - name: id - in: path - required: true + - name: from + in: query + description: unix seconds, defaults to 30 days ago schema: - type: string + type: integer + format: Int64 + nullable: true + - name: to + in: query + description: unix seconds, defaults to now + schema: + type: integer + format: Int64 + nullable: true responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Playlist__Item' + type: array + items: + $ref: '#/components/schemas/PlaceOS__Model__SignageAIJob__UsageRow' 409: description: Conflict content: @@ -30922,23 +31054,78 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/media/{id}/thumbnail: - summary: 'Digital signage media: images, videos, web pages and plugins that can - be played' + /api/engine/v2/signage/ai/providers: + summary: Vendor credentials for signage image generation. + description: 'Vendor credentials for signage image generation. + + + The Data Stores rule: sys_admin writes, support reads. Responses never carry + + the credentials, so every route renders `as_json` rather than the row.' get: - summary: redirects to the thumbnail image URL + summary: 'The rows this domain may use: its own, and the shared fallback.' + description: 'The rows this domain may use: its own, and the shared fallback. + + + There is deliberately no way to list another domain''s rows. The previous + + version returned every row in the deployment when called without a + + parameter, which handed one customer''s endpoint, model list and quotas to + + any administrator of any other.' tags: - - PlaylistMedia - operationId: PlaceOS::Api::PlaylistMedia_thumbnail + - SignageAIProviders + operationId: PlaceOS::Api::SignageAIProviders_index parameters: - - name: id - in: path - required: true + - name: include_shared + in: query + description: include the shared fallback row + example: "true" + schema: + type: boolean + - name: q + in: query + description: filters results by the given text; words match as prefixes against + the resource's searchable fields and all words must match schema: type: string + - name: limit + in: query + description: the maximum number of results to return + example: "10000" + schema: + type: integer + format: UInt32 + - name: offset + in: query + description: the starting offset of the result set, used to implement pagination + schema: + type: integer + format: UInt32 + - name: ref + in: query + description: deprecated, ignored — pagination follows the `Link` header's + offset + schema: + type: string + nullable: true + - name: fields + in: query + description: deprecated, ignored — search covers the resource's indexed fields + schema: + type: array + items: + type: string responses: 200: description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/NamedTuple_id__String__name__String__provider__String__authority_id__String___Nil__endpoint__String___Nil__location__String___Nil__default_model__String___Nil__allowed_models__Array_String___enabled__Bool__is_default__Bool__quotas__JSON__Any__created_at__Int64__updated_at__Int64_' 409: description: Conflict content: @@ -31017,44 +31204,24 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/media/share: - summary: 'Digital signage media: images, videos, web pages and plugins that can - be played' post: - summary: Share one or more media items into another signage group via - description: "Share one or more media items into another signage group via\n`GroupPlaylistItem` - junctions. Existing junctions are preserved\n(no duplicates); the response - separates newly-created links from\nones that were already in place.\n\nPermissions:\n- - sys_admin / support: any signage group in the caller's authority.\n- regular - user: must hold Share or Manage on the target group, and\n must have Read - on each item they're trying to share.\n\nAll items and the target group must - share the caller's authority." tags: - - PlaylistMedia - operationId: PlaceOS::Api::PlaylistMedia_share - parameters: - - name: items - in: query - description: comma-separated item ids to share into the target group - required: true - schema: - type: array - items: - type: string - - name: to - in: query - description: target group id (must participate in the 'signage' subsystem) + - SignageAIProviders + operationId: PlaceOS::Api::SignageAIProviders_create + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__SignageAIProviders__ProviderParams' required: true - schema: - type: string - format: uuid + parameters: [] responses: - 200: - description: OK + 201: + description: Created content: application/json: schema: - $ref: '#/components/schemas/NamedTuple_linked__Array_String___already_present__Array_String__' + $ref: '#/components/schemas/NamedTuple_id__String__name__String__provider__String__authority_id__String___Nil__endpoint__String___Nil__location__String___Nil__default_model__String___Nil__allowed_models__Array_String___enabled__Bool__is_default__Bool__quotas__JSON__Any__created_at__Int64__updated_at__Int64_' 409: description: Conflict content: @@ -31133,74 +31300,32 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/playlists: - summary: Digital signage playlists, ordered media and the schedule it plays on - get: - summary: list media playlists in the current authority. - description: 'list media playlists in the current authority. - - - Non-admin callers see only playlists linked to groups they''re a - - member of (direct or transitive). Pass `group_id=...` to scope to + /api/engine/v2/signage/ai/providers/{id}: + summary: Vendor credentials for signage image generation. + description: 'Vendor credentials for signage image generation. - a specific group (caller must have Read on that group). Pass - `q=...` for a case-insensitive substring search over `name` and + The Data Stores rule: sys_admin writes, support reads. Responses never carry - `description`.' + the credentials, so every route renders `as_json` rather than the row.' + get: tags: - - Playlist - operationId: PlaceOS::Api::Playlist_index + - SignageAIProviders + operationId: PlaceOS::Api::SignageAIProviders_show parameters: - - name: group_id - in: query - description: filter to playlists linked to this group (caller must have Read - on the group) + - name: id + in: path + required: true schema: type: string format: uuid - nullable: true - - name: q - in: query - description: case-insensitive substring search on name and description (SQL - ILIKE) - schema: - type: string - nullable: true - - name: limit - in: query - schema: - type: integer - format: Int32 - - name: offset - in: query - schema: - type: integer - format: Int32 - - name: ref - in: query - description: deprecated, ignored — pagination follows the `Link` header's - offset - schema: - type: string - nullable: true - - name: fields - in: query - description: deprecated, ignored — search covers the resource's indexed fields - schema: - type: array - items: - type: string responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Model__Playlist' + $ref: '#/components/schemas/NamedTuple_id__String__name__String__provider__String__authority_id__String___Nil__endpoint__String___Nil__location__String___Nil__default_model__String___Nil__allowed_models__Array_String___enabled__Bool__is_default__Bool__quotas__JSON__Any__created_at__Int64__updated_at__Int64_' 409: description: Conflict content: @@ -31279,44 +31404,30 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - post: - summary: add a new media playlist. Non-admin callers must supply `group_id` - description: 'add a new media playlist. Non-admin callers must supply `group_id` - - and hold Create permission on that group — the playlist is - - auto-linked to the group via a GroupPlaylist junction row so the - - creator can see it immediately. Admin/support callers may omit - - `group_id`, in which case the playlist is created unlinked - - (admin-only visibility).' + put: tags: - - Playlist - operationId: PlaceOS::Api::Playlist_create + - SignageAIProviders + operationId: PlaceOS::Api::SignageAIProviders_update requestBody: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Playlist' + $ref: '#/components/schemas/PlaceOS__Api__SignageAIProviders__ProviderParams' required: true parameters: - - name: group_id - in: query - description: group id to auto-link the new playlist to (required for non-admin - callers) + - name: id + in: path + required: true schema: type: string format: uuid - nullable: true responses: - 201: - description: Created + 200: + description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Playlist' + $ref: '#/components/schemas/NamedTuple_id__String__name__String__provider__String__authority_id__String___Nil__endpoint__String___Nil__location__String___Nil__default_model__String___Nil__allowed_models__Array_String___enabled__Bool__is_default__Bool__quotas__JSON__Any__created_at__Int64__updated_at__Int64_' 409: description: Conflict content: @@ -31395,30 +31506,20 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/playlists/{id}: - summary: Digital signage playlists, ordered media and the schedule it plays on - get: - summary: return the details of the requested media playlist, including the - description: 'return the details of the requested media playlist, including - the - - groups it has been shared with' + delete: tags: - - Playlist - operationId: PlaceOS::Api::Playlist_show + - SignageAIProviders + operationId: PlaceOS::Api::SignageAIProviders_destroy parameters: - name: id in: path required: true schema: type: string + format: uuid responses: - 200: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Playlist' + 202: + description: Accepted 409: description: Conflict content: @@ -31497,16 +31598,15 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - put: - summary: update the details of a media playlist + patch: tags: - - Playlist - operationId: PlaceOS::Api::Playlist_update + - SignageAIProviders + operationId: PlaceOS::Api::SignageAIProviders_update{2} requestBody: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Playlist' + $ref: '#/components/schemas/PlaceOS__Api__SignageAIProviders__ProviderParams' required: true parameters: - name: id @@ -31514,13 +31614,14 @@ paths: required: true schema: type: string + format: uuid responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Playlist' + $ref: '#/components/schemas/NamedTuple_id__String__name__String__provider__String__authority_id__String___Nil__endpoint__String___Nil__location__String___Nil__default_model__String___Nil__allowed_models__Array_String___enabled__Bool__is_default__Bool__quotas__JSON__Any__created_at__Int64__updated_at__Int64_' 409: description: Conflict content: @@ -31599,39 +31700,38 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - delete: - summary: remove a media playlist from the library. With `group_id` the playlist - description: 'remove a media playlist from the library. With `group_id` the - playlist + /api/engine/v2/signage/ai/providers/{id}/test: + summary: Vendor credentials for signage image generation. + description: 'Vendor credentials for signage image generation. - is unlinked from that group (its `GroupPlaylist` row is removed) and - remains available to any other groups it is shared with; when that was + The Data Stores rule: sys_admin writes, support reads. Responses never carry - its last group link the playlist itself is deleted so nothing is + the credentials, so every route renders `as_json` rather than the row.' + post: + summary: 'Prove a row''s credentials work, without leaving anything behind: + one small' + description: 'Prove a row''s credentials work, without leaving anything behind: + one small - orphaned.' + image, discarded.' tags: - - Playlist - operationId: PlaceOS::Api::Playlist_destroy + - SignageAIProviders + operationId: PlaceOS::Api::SignageAIProviders_test parameters: - name: id in: path required: true schema: type: string - - name: group_id - in: query - description: remove the playlist from this group instead of deleting it outright - (caller needs Delete or Manage on the group); the playlist is deleted if - no group links remain - schema: - type: string format: uuid - nullable: true responses: - 202: - description: Accepted + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__SignageAIProviders__TestResult' 409: description: Conflict content: @@ -31710,30 +31810,84 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - patch: - summary: update the details of a media playlist + /api/engine/v2/signage/media: + summary: 'Digital signage media: images, videos, web pages and plugins that can + be played' + get: + summary: list media items for the current authority. + description: 'list media items for the current authority. + + + Non-admin callers see only items linked to groups they''re a member + + of (direct or transitive). `group_id=...` scopes to a single + + group; `q=...` is a case-insensitive substring search across + + `name` and `description`; `tags=...` returns items carrying any of + + the supplied tags.' tags: - - Playlist - operationId: PlaceOS::Api::Playlist_update{2} - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Playlist' - required: true + - PlaylistMedia + operationId: PlaceOS::Api::PlaylistMedia_index parameters: - - name: id - in: path - required: true + - name: group_id + in: query + description: filter to items linked to this group (caller must have Read on + the group) + schema: + type: string + format: uuid + nullable: true + - name: q + in: query + description: case-insensitive substring search on name and description (SQL + ILIKE) + schema: + type: string + nullable: true + - name: tags + in: query + description: return items carrying any of these tags + example: promo,lobby + schema: + type: array + items: + type: string + nullable: true + - name: limit + in: query + schema: + type: integer + format: Int32 + - name: offset + in: query + schema: + type: integer + format: Int32 + - name: ref + in: query + description: deprecated, ignored — pagination follows the `Link` header's + offset schema: type: string + nullable: true + - name: fields + in: query + description: deprecated, ignored — search covers the resource's indexed fields + schema: + type: array + items: + type: string responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Playlist' + type: array + items: + $ref: '#/components/schemas/PlaceOS__Model__Playlist__Item' 409: description: Conflict content: @@ -31812,43 +31966,40 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/playlists/share: - summary: Digital signage playlists, ordered media and the schedule it plays on post: - summary: Share one or more playlists into another signage group via - description: "Share one or more playlists into another signage group via\n`GroupPlaylist` - junctions. Existing junctions are preserved\n(no duplicates); the response - separates newly-created links\nfrom ones that were already in place.\n\nPermissions:\n- - sys_admin / support: any signage group in the caller's authority.\n- regular - user: must hold Share or Manage on the target group, and\n must have Read - on each playlist they're trying to share.\n\nAll playlists and the target - group must share the caller's\nauthority." + summary: add a new media item. Non-admin callers must supply `group_id` + description: 'add a new media item. Non-admin callers must supply `group_id` + + (and hold Create permission on that group); the item is + + auto-linked via a GroupPlaylistItem junction row so the creator + + can see it immediately.' tags: - - Playlist - operationId: PlaceOS::Api::Playlist_share - parameters: - - name: items - in: query - description: comma-separated playlist ids to share into the target group + - PlaylistMedia + operationId: PlaceOS::Api::PlaylistMedia_create + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Playlist__Item' required: true - schema: - type: array - items: - type: string - - name: to + parameters: + - name: group_id in: query - description: target group id (must participate in the 'signage' subsystem) - required: true + description: group id to auto-link the new item to (required for non-admin + callers) schema: type: string format: uuid + nullable: true responses: - 200: - description: OK + 201: + description: Created content: application/json: schema: - $ref: '#/components/schemas/NamedTuple_linked__Array_String___already_present__Array_String__' + $ref: '#/components/schemas/PlaceOS__Model__Playlist__Item' 409: description: Conflict content: @@ -31927,26 +32078,42 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/playlists/{id}/media: - summary: Digital signage playlists, ordered media and the schedule it plays on + /api/engine/v2/signage/media/tags: + summary: 'Digital signage media: images, videos, web pages and plugins that can + be played' get: - summary: get the current list of media for the playlist + summary: return the distinct tags in use by media. Scopes the same way as + description: 'return the distinct tags in use by media. Scopes the same way + as + + `index`: `group_id=...` limits to media linked to that group (caller + + must have Read on it); without `group_id`, admin/support callers see + + every tag in the authority while regular users see tags from media in + + groups they can read.' tags: - - Playlist - operationId: PlaceOS::Api::Playlist_media + - PlaylistMedia + operationId: PlaceOS::Api::PlaylistMedia_tags parameters: - - name: id - in: path - required: true + - name: group_id + in: query + description: limit to media linked to this group (caller must have Read on + the group) schema: type: string + format: uuid + nullable: true responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Playlist__Revision' + type: array + items: + $ref: '#/components/schemas/String' 409: description: Conflict content: @@ -32025,30 +32192,48 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - post: - summary: provide an update list of media for a playlist + delete: + summary: remove a tag from every media item carrying it, or with + description: 'remove a tag from every media item carrying it, or with + + `remove_media=true` remove the tagged media itself. Scoping and + + permissions per `tag_operation_scope`: authority-wide is admin/support + + only, `group_id` requires Read and Update (or Manage) on that group — + + Read and Delete when removing media. Removing media with `group_id` + + behaves like `DELETE /:id?group_id=`: the items are unlinked from that + + group and only deleted once no group links remain.' tags: - - Playlist - operationId: PlaceOS::Api::Playlist_update_media - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/Array_String_' - required: true + - PlaylistMedia + operationId: PlaceOS::Api::PlaylistMedia_remove_tag parameters: - - name: id - in: path + - name: tag + in: query + description: the tag to be removed required: true schema: type: string + - name: remove_media + in: query + description: if true, removes the tag by removing all the media with this + tag + schema: + type: boolean + - name: group_id + in: query + description: limit to media linked to this group (caller must have Read and + Update on the group, Read and Delete when removing media) + schema: + type: string + format: uuid + nullable: true responses: 200: description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Playlist__Revision' 409: description: Conflict content: @@ -32127,33 +32312,43 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/playlists/{id}/media/revisions: - summary: Digital signage playlists, ordered media and the schedule it plays on - get: - summary: returns the previous versions of a playlist + patch: + summary: rename a tag on every media item carrying it. Items that already carry + description: 'rename a tag on every media item carrying it. Items that already + carry + + `new_tag` end up with it once. Scoping and permissions per + + `tag_operation_scope`: authority-wide is admin/support only, `group_id` + + requires Read and Update (or Manage) on that group.' tags: - - Playlist - operationId: PlaceOS::Api::Playlist_media_revisions + - PlaylistMedia + operationId: PlaceOS::Api::PlaylistMedia_rename_tag parameters: - - name: id - in: path + - name: current_tag + in: query + description: the existing tag to be renamed required: true schema: type: string - - name: limit + - name: new_tag in: query + description: the replacement tag name + required: true schema: - type: integer - format: Int32 + type: string + - name: group_id + in: query + description: limit to media linked to this group (caller must have Read and + Update on the group) + schema: + type: string + format: uuid + nullable: true responses: 200: description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Model__Playlist__Revision' 409: description: Conflict content: @@ -32232,32 +32427,42 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/playlists/{id}/media/schedule: - summary: Digital signage playlists, ordered media and the schedule it plays on - post: - summary: add scheduled media to a distribution playlist + /api/engine/v2/signage/media/tag_counts: + summary: 'Digital signage media: images, videos, web pages and plugins that can + be played' + get: + summary: return the number of media items carrying each tag. Scopes the same + description: 'return the number of media items carrying each tag. Scopes the + same + + way as `index`/`tags`: `group_id=...` limits to media linked to that + + group (caller must have Read on it); without `group_id`, admin/support + + callers see counts across the authority while regular users see counts + + from media in groups they can read. An item carrying a tag twice still + + counts once.' tags: - - Playlist - operationId: PlaceOS::Api::Playlist_schedule_media - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Playlist__ItemSchedule' - required: true + - PlaylistMedia + operationId: PlaceOS::Api::PlaylistMedia_tag_counts parameters: - - name: id - in: path - required: true + - name: group_id + in: query + description: limit to media linked to this group (caller must have Read on + the group) schema: type: string + format: uuid + nullable: true responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Playlist__Revision' + $ref: '#/components/schemas/Hash_String__Int32_' 409: description: Conflict content: @@ -32336,36 +32541,31 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/playlists/{id}/media/schedule/{item_id}: - summary: Digital signage playlists, ordered media and the schedule it plays on - patch: + /api/engine/v2/signage/media/{id}: + summary: 'Digital signage media: images, videos, web pages and plugins that can + be played' + get: + summary: return the details of the requested media item, including the groups + description: 'return the details of the requested media item, including the + groups + + it has been shared with and the playlists it appears in' tags: - - Playlist - operationId: PlaceOS::Api::Playlist_update_schedule - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Playlist__ItemSchedule' - required: true + - PlaylistMedia + operationId: PlaceOS::Api::PlaylistMedia_show parameters: - name: id in: path required: true schema: type: string - - name: item_id - in: path - required: true - schema: - type: string responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Playlist__ItemSchedule' + $ref: '#/components/schemas/PlaceOS__Model__Playlist__Item' 409: description: Conflict content: @@ -32444,13 +32644,17 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/playlists/{id}/media/approve: - summary: Digital signage playlists, ordered media and the schedule it plays on - post: - summary: approve a playlist for publication on displays + put: + summary: update the details of a media item tags: - - Playlist - operationId: PlaceOS::Api::Playlist_approve_media + - PlaylistMedia + operationId: PlaceOS::Api::PlaylistMedia_update + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Playlist__Item' + required: true parameters: - name: id in: path @@ -32463,7 +32667,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Bool' + $ref: '#/components/schemas/PlaceOS__Model__Playlist__Item' 409: description: Conflict content: @@ -32542,36 +32746,37 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/playlists/approvers: - summary: Digital signage playlists, ordered media and the schedule it plays on - get: - summary: list the users who can approve playlists for a group (those with the - description: 'list the users who can approve playlists for a group (those with - the + delete: + summary: remove a media item from the library. With `group_id` the item is + description: 'remove a media item from the library. With `group_id` the item + is - Approve or Manage permission), climbing to the nearest ancestor group + unlinked from that group (its `GroupPlaylistItem` row is removed) and - that has approvers.' + remains available to any other groups it is shared with; when that was + + its last group link the item itself is deleted so nothing is orphaned.' tags: - - Playlist - operationId: PlaceOS::Api::Playlist_approvers + - PlaylistMedia + operationId: PlaceOS::Api::PlaylistMedia_destroy parameters: + - name: id + in: path + required: true + schema: + type: string - name: group_id in: query - description: group to find approvers for - required: true + description: remove the item from this group instead of deleting it outright + (caller needs Delete or Manage on the group); the item is deleted if no + group links remain schema: type: string format: uuid + nullable: true responses: - 200: - description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Api__Playlist__Approver' + 202: + description: Accepted 409: description: Conflict content: @@ -32650,24 +32855,16 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/playlists/{id}/media/request_approval: - summary: Digital signage playlists, ordered media and the schedule it plays on - post: - summary: request approval for the current playlist's media. Notifies the group's - description: 'request approval for the current playlist''s media. Notifies the - group''s - - approvers (or a specific `approver_id`) by queuing a PendingMail for the - - signage mailer. Any member of the group (or a parent group) may request.' + patch: + summary: update the details of a media item tags: - - Playlist - operationId: PlaceOS::Api::Playlist_request_approval + - PlaylistMedia + operationId: PlaceOS::Api::PlaylistMedia_update{2} requestBody: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Api__Playlist__ApprovalMessage' + $ref: '#/components/schemas/PlaceOS__Model__Playlist__Item' required: true parameters: - name: id @@ -32675,23 +32872,13 @@ paths: required: true schema: type: string - - name: group_id - in: query - description: group whose approvers should be notified - required: true - schema: - type: string - format: uuid - - name: approver_id - in: query - description: notify only this approver (must have approve or manage permission - in the group) - schema: - type: string - nullable: true responses: 200: description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Playlist__Item' 409: description: Conflict content: @@ -32770,74 +32957,23 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/plugins: - summary: Digital signage plugins, interactive web content that can be added to - playlists + /api/engine/v2/signage/media/{id}/thumbnail: + summary: 'Digital signage media: images, videos, web pages and plugins that can + be played' get: - summary: list signage plugins for this domain and shared plugins (no authority) + summary: redirects to the thumbnail image URL tags: - - SignagePlugins - operationId: PlaceOS::Api::SignagePlugins_index + - PlaylistMedia + operationId: PlaceOS::Api::PlaylistMedia_thumbnail parameters: - - name: enabled - in: query - description: only return plugins that are enabled - example: "true" - schema: - type: boolean - nullable: true - - name: plugin_type - in: query - description: only return plugins of this type - example: widget - schema: - type: string - enum: - - plugin - - widget - nullable: true - - name: q - in: query - description: filters results by the given text; words match as prefixes against - the resource's searchable fields and all words must match - schema: - type: string - - name: limit - in: query - description: the maximum number of results to return - example: "10000" - schema: - type: integer - format: UInt32 - - name: offset - in: query - description: the starting offset of the result set, used to implement pagination - schema: - type: integer - format: UInt32 - - name: ref - in: query - description: deprecated, ignored — pagination follows the `Link` header's - offset + - name: id + in: path + required: true schema: type: string - nullable: true - - name: fields - in: query - description: deprecated, ignored — search covers the resource's indexed fields - schema: - type: array - items: - type: string responses: 200: description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Model__SignagePlugin' 409: description: Conflict content: @@ -32916,25 +33052,44 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + /api/engine/v2/signage/media/share: + summary: 'Digital signage media: images, videos, web pages and plugins that can + be played' post: - summary: add a new signage plugin + summary: Share one or more media items into another signage group via + description: "Share one or more media items into another signage group via\n`GroupPlaylistItem` + junctions. Existing junctions are preserved\n(no duplicates); the response + separates newly-created links from\nones that were already in place.\n\nPermissions:\n- + sys_admin / support: any signage group in the caller's authority.\n- regular + user: must hold Share or Manage on the target group, and\n must have Read + on each item they're trying to share.\n\nAll items and the target group must + share the caller's authority." tags: - - SignagePlugins - operationId: PlaceOS::Api::SignagePlugins_create - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__SignagePlugin' + - PlaylistMedia + operationId: PlaceOS::Api::PlaylistMedia_share + parameters: + - name: items + in: query + description: comma-separated item ids to share into the target group required: true - parameters: [] + schema: + type: array + items: + type: string + - name: to + in: query + description: target group id (must participate in the 'signage' subsystem) + required: true + schema: + type: string + format: uuid responses: - 201: - description: Created + 200: + description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__SignagePlugin' + $ref: '#/components/schemas/NamedTuple_linked__Array_String___already_present__Array_String__' 409: description: Conflict content: @@ -33013,27 +33168,74 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/plugins/{id}: - summary: Digital signage plugins, interactive web content that can be added to - playlists + /api/engine/v2/signage/playlists: + summary: Digital signage playlists, ordered media and the schedule it plays on get: - summary: return the details of the requested signage plugin + summary: list media playlists in the current authority. + description: 'list media playlists in the current authority. + + + Non-admin callers see only playlists linked to groups they''re a + + member of (direct or transitive). Pass `group_id=...` to scope to + + a specific group (caller must have Read on that group). Pass + + `q=...` for a case-insensitive substring search over `name` and + + `description`.' tags: - - SignagePlugins - operationId: PlaceOS::Api::SignagePlugins_show + - Playlist + operationId: PlaceOS::Api::Playlist_index parameters: - - name: id - in: path - required: true + - name: group_id + in: query + description: filter to playlists linked to this group (caller must have Read + on the group) + schema: + type: string + format: uuid + nullable: true + - name: q + in: query + description: case-insensitive substring search on name and description (SQL + ILIKE) + schema: + type: string + nullable: true + - name: limit + in: query + schema: + type: integer + format: Int32 + - name: offset + in: query + schema: + type: integer + format: Int32 + - name: ref + in: query + description: deprecated, ignored — pagination follows the `Link` header's + offset schema: type: string + nullable: true + - name: fields + in: query + description: deprecated, ignored — search covers the resource's indexed fields + schema: + type: array + items: + type: string responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__SignagePlugin' + type: array + items: + $ref: '#/components/schemas/PlaceOS__Model__Playlist' 409: description: Conflict content: @@ -33112,30 +33314,44 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - put: - summary: update the details of a signage plugin + post: + summary: add a new media playlist. Non-admin callers must supply `group_id` + description: 'add a new media playlist. Non-admin callers must supply `group_id` + + and hold Create permission on that group — the playlist is + + auto-linked to the group via a GroupPlaylist junction row so the + + creator can see it immediately. Admin/support callers may omit + + `group_id`, in which case the playlist is created unlinked + + (admin-only visibility).' tags: - - SignagePlugins - operationId: PlaceOS::Api::SignagePlugins_update + - Playlist + operationId: PlaceOS::Api::Playlist_create requestBody: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__SignagePlugin' + $ref: '#/components/schemas/PlaceOS__Model__Playlist' required: true parameters: - - name: id - in: path - required: true + - name: group_id + in: query + description: group id to auto-link the new playlist to (required for non-admin + callers) schema: type: string + format: uuid + nullable: true responses: - 200: - description: OK + 201: + description: Created content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__SignagePlugin' + $ref: '#/components/schemas/PlaceOS__Model__Playlist' 409: description: Conflict content: @@ -33214,11 +33430,17 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - delete: - summary: remove a signage plugin + /api/engine/v2/signage/playlists/{id}: + summary: Digital signage playlists, ordered media and the schedule it plays on + get: + summary: return the details of the requested media playlist, including the + description: 'return the details of the requested media playlist, including + the + + groups it has been shared with' tags: - - SignagePlugins - operationId: PlaceOS::Api::SignagePlugins_destroy + - Playlist + operationId: PlaceOS::Api::Playlist_show parameters: - name: id in: path @@ -33226,8 +33448,12 @@ paths: schema: type: string responses: - 202: - description: Accepted + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Playlist' 409: description: Conflict content: @@ -33306,16 +33532,16 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - patch: - summary: update the details of a signage plugin + put: + summary: update the details of a media playlist tags: - - SignagePlugins - operationId: PlaceOS::Api::SignagePlugins_update{2} + - Playlist + operationId: PlaceOS::Api::Playlist_update requestBody: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__SignagePlugin' + $ref: '#/components/schemas/PlaceOS__Model__Playlist' required: true parameters: - name: id @@ -33329,7 +33555,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__SignagePlugin' + $ref: '#/components/schemas/PlaceOS__Model__Playlist' 409: description: Conflict content: @@ -33408,82 +33634,141 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/template_mappings: - summary: Assigns digital signage templates to systems (displays) - get: - summary: list the template mappings in the current authority — what templates - description: 'list the template mappings in the current authority — what templates + delete: + summary: remove a media playlist from the library. With `group_id` the playlist + description: 'remove a media playlist from the library. With `group_id` the + playlist - are applied to which displays and zones. Filtering by display also + is unlinked from that group (its `GroupPlaylist` row is removed) and - returns the mappings on the zones that display belongs to. Non-admin + remains available to any other groups it is shared with; when that was - callers see only mappings of templates linked to groups they hold + its last group link the playlist itself is deleted so nothing is - Read on, or applied to displays / zones they can view.' + orphaned.' tags: - - SignageTemplateMappings - operationId: PlaceOS::Api::SignageTemplateMappings_index + - Playlist + operationId: PlaceOS::Api::Playlist_destroy parameters: - - name: control_system_id - in: query - description: filter to mappings applied to this display (control system id), - including those applied to the zones it belongs to - schema: - type: string - nullable: true - - name: zone_id - in: query - description: filter to mappings applied to this zone + - name: id + in: path + required: true schema: type: string - nullable: true - - name: template_id + - name: group_id in: query - description: filter to mappings of this template + description: remove the playlist from this group instead of deleting it outright + (caller needs Delete or Manage on the group); the playlist is deleted if + no group links remain schema: type: string format: uuid nullable: true - - name: limit - in: query - schema: - type: integer - format: Int32 - - name: offset - in: query - schema: - type: integer - format: Int32 - - name: q - in: query - description: filters results by the given text; words match as prefixes against - the resource's searchable fields and all words must match - schema: - type: string - - name: ref - in: query - description: deprecated, ignored — pagination follows the `Link` header's - offset + responses: + 202: + description: Accepted + 409: + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 503: + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 404: + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 429: + description: Too Many Requests + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 422: + description: Unprocessable Entity + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 502: + description: Bad Gateway + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 504: + description: Gateway Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 406: + description: Not Acceptable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + 408: + description: Request Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 400: + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 415: + description: Unsupported Media Type + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + patch: + summary: update the details of a media playlist + tags: + - Playlist + operationId: PlaceOS::Api::Playlist_update{2} + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Playlist' + required: true + parameters: + - name: id + in: path + required: true schema: type: string - nullable: true - - name: fields - in: query - description: deprecated, ignored — search covers the resource's indexed fields - schema: - type: array - items: - type: string responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate__SystemTemplate' + $ref: '#/components/schemas/PlaceOS__Model__Playlist' 409: description: Conflict content: @@ -33562,34 +33847,43 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + /api/engine/v2/signage/playlists/share: + summary: Digital signage playlists, ordered media and the schedule it plays on post: - summary: apply a template to a display (control system) or a zone — exactly - one - description: 'apply a template to a display (control system) or a zone — exactly - one - - of the two. A mapping without a schedule is the default template for - - the pairing (at most one); scheduled mappings show the template during - - the schedule. Requires Create permission on the template''s groups.' + summary: Share one or more playlists into another signage group via + description: "Share one or more playlists into another signage group via\n`GroupPlaylist` + junctions. Existing junctions are preserved\n(no duplicates); the response + separates newly-created links\nfrom ones that were already in place.\n\nPermissions:\n- + sys_admin / support: any signage group in the caller's authority.\n- regular + user: must hold Share or Manage on the target group, and\n must have Read + on each playlist they're trying to share.\n\nAll playlists and the target + group must share the caller's\nauthority." tags: - - SignageTemplateMappings - operationId: PlaceOS::Api::SignageTemplateMappings_create - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate__SystemTemplate' + - Playlist + operationId: PlaceOS::Api::Playlist_share + parameters: + - name: items + in: query + description: comma-separated playlist ids to share into the target group required: true - parameters: [] + schema: + type: array + items: + type: string + - name: to + in: query + description: target group id (must participate in the 'signage' subsystem) + required: true + schema: + type: string + format: uuid responses: - 201: - description: Created + 200: + description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate__SystemTemplate' + $ref: '#/components/schemas/NamedTuple_linked__Array_String___already_present__Array_String__' 409: description: Conflict content: @@ -33668,30 +33962,26 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/template_mappings/{id}: - summary: Assigns digital signage templates to systems (displays) + /api/engine/v2/signage/playlists/{id}/media: + summary: Digital signage playlists, ordered media and the schedule it plays on get: - summary: return the details of a template mapping, including the applied - description: 'return the details of a template mapping, including the applied - - template and its background media' + summary: get the current list of media for the playlist tags: - - SignageTemplateMappings - operationId: PlaceOS::Api::SignageTemplateMappings_show + - Playlist + operationId: PlaceOS::Api::Playlist_media parameters: - name: id in: path required: true schema: type: string - format: uuid responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate__SystemTemplate' + $ref: '#/components/schemas/PlaceOS__Model__Playlist__Revision' 409: description: Conflict content: @@ -33770,24 +34060,16 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - put: - summary: update the schedule of a template mapping. Only the schedule can be - description: 'update the schedule of a template mapping. Only the schedule can - be - - changed — apply a template elsewhere by removing the mapping and - - creating a new one. An explicit `"schedule": null` clears the schedule, - - making this mapping the default for its pairing.' + post: + summary: provide an update list of media for a playlist tags: - - SignageTemplateMappings - operationId: PlaceOS::Api::SignageTemplateMappings_update + - Playlist + operationId: PlaceOS::Api::Playlist_update_media requestBody: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate__SystemTemplate' + $ref: '#/components/schemas/Array_String_' required: true parameters: - name: id @@ -33795,14 +34077,13 @@ paths: required: true schema: type: string - format: uuid responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate__SystemTemplate' + $ref: '#/components/schemas/PlaceOS__Model__Playlist__Revision' 409: description: Conflict content: @@ -33881,21 +34162,33 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - delete: - summary: remove a template mapping from a display or zone + /api/engine/v2/signage/playlists/{id}/media/revisions: + summary: Digital signage playlists, ordered media and the schedule it plays on + get: + summary: returns the previous versions of a playlist tags: - - SignageTemplateMappings - operationId: PlaceOS::Api::SignageTemplateMappings_destroy + - Playlist + operationId: PlaceOS::Api::Playlist_media_revisions parameters: - name: id in: path required: true schema: type: string - format: uuid + - name: limit + in: query + schema: + type: integer + format: Int32 responses: - 202: - description: Accepted + 200: + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PlaceOS__Model__Playlist__Revision' 409: description: Conflict content: @@ -33974,24 +34267,18 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - patch: - summary: update the schedule of a template mapping. Only the schedule can be - description: 'update the schedule of a template mapping. Only the schedule can - be - - changed — apply a template elsewhere by removing the mapping and - - creating a new one. An explicit `"schedule": null` clears the schedule, - - making this mapping the default for its pairing.' + /api/engine/v2/signage/playlists/{id}/media/schedule: + summary: Digital signage playlists, ordered media and the schedule it plays on + post: + summary: add scheduled media to a distribution playlist tags: - - SignageTemplateMappings - operationId: PlaceOS::Api::SignageTemplateMappings_update{2} + - Playlist + operationId: PlaceOS::Api::Playlist_schedule_media requestBody: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate__SystemTemplate' + $ref: '#/components/schemas/PlaceOS__Model__Playlist__ItemSchedule' required: true parameters: - name: id @@ -33999,14 +34286,13 @@ paths: required: true schema: type: string - format: uuid responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate__SystemTemplate' + $ref: '#/components/schemas/PlaceOS__Model__Playlist__Revision' 409: description: Conflict content: @@ -34085,86 +34371,36 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/templates: - summary: Digital signage templates, layouts for content on displays - get: - summary: list signage templates in the current authority. Like the default - description: 'list signage templates in the current authority. Like the default - - `show`, a template with staged changes is returned as its pending - - draft (falling back to the approved row) so listings always reflect - - the latest edits — pass `approved=true` for the live versions only. - - - Non-admin callers see only templates linked to groups they''re a - - member of (direct or transitive). Pass `group_id=...` to scope to - - a specific group (caller must have Read on that group). Pass - - `q=...` for a case-insensitive substring search over `name` and - - `description`.' + /api/engine/v2/signage/playlists/{id}/media/schedule/{item_id}: + summary: Digital signage playlists, ordered media and the schedule it plays on + patch: tags: - - SignageTemplates - operationId: PlaceOS::Api::SignageTemplates_index + - Playlist + operationId: PlaceOS::Api::Playlist_update_schedule + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Playlist__ItemSchedule' + required: true parameters: - - name: group_id - in: query - description: filter to templates linked to this group (caller must have Read - on the group) - schema: - type: string - format: uuid - nullable: true - - name: q - in: query - description: case-insensitive substring search on name and description (SQL - ILIKE) + - name: id + in: path + required: true schema: type: string - nullable: true - - name: approved - in: query - description: return the approved (live) versions even when pending drafts - exist - schema: - type: boolean - - name: limit - in: query - schema: - type: integer - format: Int32 - - name: offset - in: query - schema: - type: integer - format: Int32 - - name: ref - in: query - description: deprecated, ignored — pagination follows the `Link` header's - offset + - name: item_id + in: path + required: true schema: type: string - nullable: true - - name: fields - in: query - description: deprecated, ignored — search covers the resource's indexed fields - schema: - type: array - items: - type: string responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate' + $ref: '#/components/schemas/PlaceOS__Model__Playlist__ItemSchedule' 409: description: Conflict content: @@ -34243,44 +34479,26 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + /api/engine/v2/signage/playlists/{id}/media/approve: + summary: Digital signage playlists, ordered media and the schedule it plays on post: - summary: add a new signage template. Non-admin callers must supply `group_id` - description: 'add a new signage template. Non-admin callers must supply `group_id` - - and hold Create permission on that group — the template is - - auto-linked to the group via a GroupSignageTemplate junction row so - - the creator can see it immediately. Admin/support callers may omit - - `group_id`, in which case the template is created unlinked - - (admin-only visibility). New templates start unapproved.' + summary: approve a playlist for publication on displays tags: - - SignageTemplates - operationId: PlaceOS::Api::SignageTemplates_create - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate' - required: true + - Playlist + operationId: PlaceOS::Api::Playlist_approve_media parameters: - - name: group_id - in: query - description: group id to auto-link the new template to (required for non-admin - callers) + - name: id + in: path + required: true schema: type: string - format: uuid - nullable: true responses: - 201: - description: Created + 200: + description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate' + $ref: '#/components/schemas/Bool' 409: description: Conflict content: @@ -34359,42 +34577,36 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/templates/{id}: - summary: Digital signage templates, layouts for content on displays + /api/engine/v2/signage/playlists/approvers: + summary: Digital signage playlists, ordered media and the schedule it plays on get: - summary: return the details of the requested template, including the groups - it - description: 'return the details of the requested template, including the groups - it - - has been shared with. By default the pending draft is returned when one + summary: list the users who can approve playlists for a group (those with the + description: 'list the users who can approve playlists for a group (those with + the - exists (falling back to the approved version); pass `approved=true` for + Approve or Manage permission), climbing to the nearest ancestor group - the approved version always.' + that has approvers.' tags: - - SignageTemplates - operationId: PlaceOS::Api::SignageTemplates_show + - Playlist + operationId: PlaceOS::Api::Playlist_approvers parameters: - - name: id - in: path + - name: group_id + in: query + description: group to find approvers for required: true schema: type: string format: uuid - - name: approved - in: query - description: return the approved (live) version even when a pending draft - exists - schema: - type: boolean responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate' + type: array + items: + $ref: '#/components/schemas/PlaceOS__Api__Playlist__Approver' 409: description: Conflict content: @@ -34473,42 +34685,48 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - put: - summary: 'update a template. Approved templates are never modified in place:' - description: 'update a template. Approved templates are never modified in place: - - changes to layout fields (background_item_id, layouts, - - full_screen_takeover) are staged on a draft which must be approved - - before going live. Metadata (name, description, tags) is applied + /api/engine/v2/signage/playlists/{id}/media/request_approval: + summary: Digital signage playlists, ordered media and the schedule it plays on + post: + summary: request approval for the current playlist's media. Notifies the group's + description: 'request approval for the current playlist''s media. Notifies the + group''s - directly without approval. Responds with the pending version — the + approvers (or a specific `approver_id`) by queuing a PendingMail for the - same record the default `show` returns.' + signage mailer. Any member of the group (or a parent group) may request.' tags: - - SignageTemplates - operationId: PlaceOS::Api::SignageTemplates_update + - Playlist + operationId: PlaceOS::Api::Playlist_request_approval requestBody: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate' + $ref: '#/components/schemas/PlaceOS__Api__Playlist__ApprovalMessage' required: true parameters: - name: id in: path required: true + schema: + type: string + - name: group_id + in: query + description: group whose approvers should be notified + required: true schema: type: string format: uuid + - name: approver_id + in: query + description: notify only this approver (must have approve or manage permission + in the group) + schema: + type: string + nullable: true responses: 200: description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate' 409: description: Conflict content: @@ -34587,39 +34805,74 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - delete: - summary: remove a signage template (pending drafts and group links cascade). - description: 'remove a signage template (pending drafts and group links cascade). - - With `group_id` the template is unlinked from that group (its - - `GroupSignageTemplate` row is removed) and remains available to any - - other groups it is shared with; when that was its last group link the - - template itself is deleted so nothing is orphaned.' + /api/engine/v2/signage/plugins: + summary: Digital signage plugins, interactive web content that can be added to + playlists + get: + summary: list signage plugins for this domain and shared plugins (no authority) tags: - - SignageTemplates - operationId: PlaceOS::Api::SignageTemplates_destroy + - SignagePlugins + operationId: PlaceOS::Api::SignagePlugins_index parameters: - - name: id - in: path - required: true + - name: enabled + in: query + description: only return plugins that are enabled + example: "true" + schema: + type: boolean + nullable: true + - name: plugin_type + in: query + description: only return plugins of this type + example: widget schema: type: string - format: uuid - - name: group_id + enum: + - plugin + - widget + nullable: true + - name: q in: query - description: remove the template from this group instead of deleting it outright - (caller needs Delete or Manage on the group); the template is deleted if - no group links remain + description: filters results by the given text; words match as prefixes against + the resource's searchable fields and all words must match + schema: + type: string + - name: limit + in: query + description: the maximum number of results to return + example: "10000" + schema: + type: integer + format: UInt32 + - name: offset + in: query + description: the starting offset of the result set, used to implement pagination + schema: + type: integer + format: UInt32 + - name: ref + in: query + description: deprecated, ignored — pagination follows the `Link` header's + offset schema: type: string - format: uuid nullable: true + - name: fields + in: query + description: deprecated, ignored — search covers the resource's indexed fields + schema: + type: array + items: + type: string responses: - 202: - description: Accepted + 200: + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PlaceOS__Model__SignagePlugin' 409: description: Conflict content: @@ -34698,42 +34951,25 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - patch: - summary: 'update a template. Approved templates are never modified in place:' - description: 'update a template. Approved templates are never modified in place: - - changes to layout fields (background_item_id, layouts, - - full_screen_takeover) are staged on a draft which must be approved - - before going live. Metadata (name, description, tags) is applied - - directly without approval. Responds with the pending version — the - - same record the default `show` returns.' + post: + summary: add a new signage plugin tags: - - SignageTemplates - operationId: PlaceOS::Api::SignageTemplates_update{2} + - SignagePlugins + operationId: PlaceOS::Api::SignagePlugins_create requestBody: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate' - required: true - parameters: - - name: id - in: path + $ref: '#/components/schemas/PlaceOS__Model__SignagePlugin' required: true - schema: - type: string - format: uuid + parameters: [] responses: - 200: - description: OK + 201: + description: Created content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate' + $ref: '#/components/schemas/PlaceOS__Model__SignagePlugin' 409: description: Conflict content: @@ -34812,23 +35048,27 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/templates/{id}/draft: - summary: Digital signage templates, layouts for content on displays - delete: - summary: discard the pending draft, reverting to the approved version + /api/engine/v2/signage/plugins/{id}: + summary: Digital signage plugins, interactive web content that can be added to + playlists + get: + summary: return the details of the requested signage plugin tags: - - SignageTemplates - operationId: PlaceOS::Api::SignageTemplates_destroy_draft + - SignagePlugins + operationId: PlaceOS::Api::SignagePlugins_show parameters: - name: id in: path required: true schema: type: string - format: uuid responses: - 202: - description: Accepted + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__SignagePlugin' 409: description: Conflict content: @@ -34907,43 +35147,30 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/templates/share: - summary: Digital signage templates, layouts for content on displays - post: - summary: Share one or more templates into another signage group via - description: "Share one or more templates into another signage group via\n`GroupSignageTemplate` - junctions. Existing junctions are preserved\n(no duplicates); the response - separates newly-created links\nfrom ones that were already in place.\n\nPermissions:\n- - sys_admin / support: any signage group in the caller's authority.\n- regular - user: must hold Share or Manage on the target group, and\n must have Read - on each template they're trying to share.\n\nAll templates and the target - group must share the caller's\nauthority." + put: + summary: update the details of a signage plugin tags: - - SignageTemplates - operationId: PlaceOS::Api::SignageTemplates_share - parameters: - - name: items - in: query - description: comma-separated template ids to share into the target group + - SignagePlugins + operationId: PlaceOS::Api::SignagePlugins_update + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__SignagePlugin' required: true - schema: - type: array - items: - type: string - - name: to - in: query - description: target group id (must participate in the 'signage' subsystem) + parameters: + - name: id + in: path required: true schema: type: string - format: uuid responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/NamedTuple_linked__Array_String___already_present__Array_String__' + $ref: '#/components/schemas/PlaceOS__Model__SignagePlugin' 409: description: Conflict content: @@ -35022,32 +35249,20 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/templates/{id}/approve: - summary: Digital signage templates, layouts for content on displays - post: - summary: approve the pending changes for a template. Promotes the pending - description: 'approve the pending changes for a template. Promotes the pending - - draft onto the live template (or approves a template that has never - - been approved). 406 when there is nothing to approve.' + delete: + summary: remove a signage plugin tags: - - SignageTemplates - operationId: PlaceOS::Api::SignageTemplates_approve + - SignagePlugins + operationId: PlaceOS::Api::SignagePlugins_destroy parameters: - name: id in: path required: true schema: type: string - format: uuid responses: - 200: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate' + 202: + description: Accepted 409: description: Conflict content: @@ -35126,36 +35341,30 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/templates/approvers: - summary: Digital signage templates, layouts for content on displays - get: - summary: list the users who can approve templates for a group (those with the - description: 'list the users who can approve templates for a group (those with - the - - Approve or Manage permission), climbing to the nearest ancestor group - - that has approvers.' + patch: + summary: update the details of a signage plugin tags: - - SignageTemplates - operationId: PlaceOS::Api::SignageTemplates_approvers + - SignagePlugins + operationId: PlaceOS::Api::SignagePlugins_update{2} + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__SignagePlugin' + required: true parameters: - - name: group_id - in: query - description: group to find approvers for + - name: id + in: path required: true schema: type: string - format: uuid responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Api__SignageTemplates__Approver' + $ref: '#/components/schemas/PlaceOS__Model__SignagePlugin' 409: description: Conflict content: @@ -35234,49 +35443,82 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/templates/{id}/request_approval: - summary: Digital signage templates, layouts for content on displays - post: - summary: request approval for a template's pending changes. Notifies the group's - description: 'request approval for a template''s pending changes. Notifies the - group''s + /api/engine/v2/signage/template_mappings: + summary: Assigns digital signage templates to systems (displays) + get: + summary: list the template mappings in the current authority — what templates + description: 'list the template mappings in the current authority — what templates - approvers (or a specific `approver_id`) by queuing a PendingMail for the + are applied to which displays and zones. Filtering by display also - signage mailer. Any member of the group (or a parent group) may request.' + returns the mappings on the zones that display belongs to. Non-admin + + callers see only mappings of templates linked to groups they hold + + Read on, or applied to displays / zones they can view.' tags: - - SignageTemplates - operationId: PlaceOS::Api::SignageTemplates_request_approval - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__SignageTemplates__ApprovalMessage' - required: true + - SignageTemplateMappings + operationId: PlaceOS::Api::SignageTemplateMappings_index parameters: - - name: id - in: path - required: true + - name: control_system_id + in: query + description: filter to mappings applied to this display (control system id), + including those applied to the zones it belongs to schema: type: string - format: uuid - - name: group_id + nullable: true + - name: zone_id in: query - description: group whose approvers should be notified - required: true + description: filter to mappings applied to this zone + schema: + type: string + nullable: true + - name: template_id + in: query + description: filter to mappings of this template schema: type: string format: uuid - - name: approver_id + nullable: true + - name: limit in: query - description: notify only this approver (must have approve or manage permission - in the group) + schema: + type: integer + format: Int32 + - name: offset + in: query + schema: + type: integer + format: Int32 + - name: q + in: query + description: filters results by the given text; words match as prefixes against + the resource's searchable fields and all words must match + schema: + type: string + - name: ref + in: query + description: deprecated, ignored — pagination follows the `Link` header's + offset schema: type: string nullable: true + - name: fields + in: query + description: deprecated, ignored — search covers the resource's indexed fields + schema: + type: array + items: + type: string responses: 200: description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate__SystemTemplate' 409: description: Conflict content: @@ -35355,38 +35597,34 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/{system_id}: - get: - summary: return all the details for displaying signage + post: + summary: apply a template to a display (control system) or a zone — exactly + one + description: 'apply a template to a display (control system) or a zone — exactly + one + + of the two. A mapping without a schedule is the default template for + + the pairing (at most one); scheduled mappings show the template during + + the schedule. Requires Create permission on the template''s groups.' tags: - - Signage - operationId: PlaceOS::Api::Signage_display - parameters: - - name: system_id - in: path + - SignageTemplateMappings + operationId: PlaceOS::Api::SignageTemplateMappings_create + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate__SystemTemplate' required: true - schema: - type: string - - name: item_id - in: query - description: currently playing item, if the player is playing content - example: playlist_items-1234 - schema: - type: string - nullable: true - - name: preview - in: query - description: is this the preview player - example: "true" - schema: - type: boolean + parameters: [] responses: - 200: - description: OK + 201: + description: Created content: application/json: schema: - $ref: '#/components/schemas/_PlaceOS__Model__ControlSystem___Nil_' + $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate__SystemTemplate' 409: description: Conflict content: @@ -35465,27 +35703,30 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/{system_id}/metrics: - post: - summary: update the metrics for production players + /api/engine/v2/signage/template_mappings/{id}: + summary: Assigns digital signage templates to systems (displays) + get: + summary: return the details of a template mapping, including the applied + description: 'return the details of a template mapping, including the applied + + template and its background media' tags: - - Signage - operationId: PlaceOS::Api::Signage_update_metrics - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Signage__Metrics' - required: true + - SignageTemplateMappings + operationId: PlaceOS::Api::SignageTemplateMappings_show parameters: - - name: system_id + - name: id in: path required: true schema: type: string + format: uuid responses: - 202: - description: Accepted + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate__SystemTemplate' 409: description: Conflict content: @@ -35564,44 +35805,39 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/{system_id}/static/{item_id}: - get: - summary: Obtains the temporary link to the media item or a screenshot of the - live - description: 'Obtains the temporary link to the media item or a screenshot of - the live + put: + summary: update the schedule of a template mapping. Only the schedule can be + description: 'update the schedule of a template mapping. Only the schedule can + be - item if a web page, then redirects to that URL' + changed — apply a template elsewhere by removing the mapping and + + creating a new one. An explicit `"schedule": null` clears the schedule, + + making this mapping the default for its pairing.' tags: - - Signage - operationId: PlaceOS::Api::Signage_fetch_static_media - parameters: - - name: system_id - in: path - description: the display showing the media, sizes web page captures - example: sys-1234 + - SignageTemplateMappings + operationId: PlaceOS::Api::SignageTemplateMappings_update + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate__SystemTemplate' required: true - schema: - type: string - - name: item_id + parameters: + - name: id in: path - description: the link to the media item for display on an e-ink or other static - device - example: playlist_items-1234 required: true schema: type: string - - name: expires_after - in: query - description: how many minutes the media can be cached before taking a new - screenshot - example: "60" - schema: - type: integer - format: UInt32 + format: uuid responses: 200: description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate__SystemTemplate' 409: description: Conflict content: @@ -35680,48 +35916,21 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/signage/{system_id}/samsung/eink/{item_id}/{expires_after}: - get: - summary: Returns the samsung EMDX e-ink display manifest for the provided media - item - description: 'Returns the samsung EMDX e-ink display manifest for the provided - media item - - keeping URL short as samsung only supports ~250 chars for the manifest URL' + delete: + summary: remove a template mapping from a display or zone tags: - - Signage - operationId: PlaceOS::Api::Signage_samsung_eink_manifest + - SignageTemplateMappings + operationId: PlaceOS::Api::SignageTemplateMappings_destroy parameters: - - name: system_id - in: path - description: the display showing the media, sizes web page captures - example: sys-1234 - required: true - schema: - type: string - - name: item_id + - name: id in: path - description: the link to the media item for display on an e-ink or other static - device - example: playlist_items-1234 required: true schema: type: string - - name: expires_after - in: path - description: how many minutes the media can be cached before taking a new - screenshot - example: "60" - schema: - type: integer - format: UInt32 + format: uuid responses: - 200: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Signage__SamsungEinkManifest' + 202: + description: Accepted 409: description: Conflict content: @@ -35800,63 +36009,39 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/storages: - summary: File storage providers (e.g. S3) used for uploads - get: - summary: returns the list of available storages for provided authority + patch: + summary: update the schedule of a template mapping. Only the schedule can be + description: 'update the schedule of a template mapping. Only the schedule can + be + + changed — apply a template elsewhere by removing the mapping and + + creating a new one. An explicit `"schedule": null` clears the schedule, + + making this mapping the default for its pairing.' tags: - - Storages - operationId: PlaceOS::Api::Storages_index + - SignageTemplateMappings + operationId: PlaceOS::Api::SignageTemplateMappings_update{2} + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate__SystemTemplate' + required: true parameters: - - name: auth_id - in: query - description: return storages which are in the authority provided - example: auth-1234 - schema: - type: string - nullable: true - - name: q - in: query - description: filters results by the given text; words match as prefixes against - the resource's searchable fields and all words must match - schema: - type: string - - name: limit - in: query - description: the maximum number of results to return - example: "10000" - schema: - type: integer - format: UInt32 - - name: offset - in: query - description: the starting offset of the result set, used to implement pagination - schema: - type: integer - format: UInt32 - - name: ref - in: query - description: deprecated, ignored — pagination follows the `Link` header's - offset + - name: id + in: path + required: true schema: type: string - nullable: true - - name: fields - in: query - description: deprecated, ignored — search covers the resource's indexed fields - schema: - type: array - items: - type: string + format: uuid responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Model__Storage' + $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate__SystemTemplate' 409: description: Conflict content: @@ -35935,25 +36120,86 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - post: - summary: adds a new storage + /api/engine/v2/signage/templates: + summary: Digital signage templates, layouts for content on displays + get: + summary: list signage templates in the current authority. Like the default + description: 'list signage templates in the current authority. Like the default + + `show`, a template with staged changes is returned as its pending + + draft (falling back to the approved row) so listings always reflect + + the latest edits — pass `approved=true` for the live versions only. + + + Non-admin callers see only templates linked to groups they''re a + + member of (direct or transitive). Pass `group_id=...` to scope to + + a specific group (caller must have Read on that group). Pass + + `q=...` for a case-insensitive substring search over `name` and + + `description`.' tags: - - Storages - operationId: PlaceOS::Api::Storages_create - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Storage' - required: true - parameters: [] + - SignageTemplates + operationId: PlaceOS::Api::SignageTemplates_index + parameters: + - name: group_id + in: query + description: filter to templates linked to this group (caller must have Read + on the group) + schema: + type: string + format: uuid + nullable: true + - name: q + in: query + description: case-insensitive substring search on name and description (SQL + ILIKE) + schema: + type: string + nullable: true + - name: approved + in: query + description: return the approved (live) versions even when pending drafts + exist + schema: + type: boolean + - name: limit + in: query + schema: + type: integer + format: Int32 + - name: offset + in: query + schema: + type: integer + format: Int32 + - name: ref + in: query + description: deprecated, ignored — pagination follows the `Link` header's + offset + schema: + type: string + nullable: true + - name: fields + in: query + description: deprecated, ignored — search covers the resource's indexed fields + schema: + type: array + items: + type: string responses: - 201: - description: Created + 200: + description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Storage' + type: array + items: + $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate' 409: description: Conflict content: @@ -36032,26 +36278,44 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/storages/{id}: - summary: File storage providers (e.g. S3) used for uploads - get: - summary: returns the details of a Storage + post: + summary: add a new signage template. Non-admin callers must supply `group_id` + description: 'add a new signage template. Non-admin callers must supply `group_id` + + and hold Create permission on that group — the template is + + auto-linked to the group via a GroupSignageTemplate junction row so + + the creator can see it immediately. Admin/support callers may omit + + `group_id`, in which case the template is created unlinked + + (admin-only visibility). New templates start unapproved.' tags: - - Storages - operationId: PlaceOS::Api::Storages_show - parameters: - - name: id - in: path + - SignageTemplates + operationId: PlaceOS::Api::SignageTemplates_create + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate' required: true + parameters: + - name: group_id + in: query + description: group id to auto-link the new template to (required for non-admin + callers) schema: type: string + format: uuid + nullable: true responses: - 200: - description: OK + 201: + description: Created content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Storage' + $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate' 409: description: Conflict content: @@ -36130,30 +36394,42 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - put: - summary: updates a storage details + /api/engine/v2/signage/templates/{id}: + summary: Digital signage templates, layouts for content on displays + get: + summary: return the details of the requested template, including the groups + it + description: 'return the details of the requested template, including the groups + it + + has been shared with. By default the pending draft is returned when one + + exists (falling back to the approved version); pass `approved=true` for + + the approved version always.' tags: - - Storages - operationId: PlaceOS::Api::Storages_update - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Storage' - required: true + - SignageTemplates + operationId: PlaceOS::Api::SignageTemplates_show parameters: - name: id in: path required: true schema: type: string + format: uuid + - name: approved + in: query + description: return the approved (live) version even when a pending draft + exists + schema: + type: boolean responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Storage' + $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate' 409: description: Conflict content: @@ -36232,20 +36508,42 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - delete: - summary: removes a storage + put: + summary: 'update a template. Approved templates are never modified in place:' + description: 'update a template. Approved templates are never modified in place: + + changes to layout fields (background_item_id, layouts, + + full_screen_takeover) are staged on a draft which must be approved + + before going live. Metadata (name, description, tags) is applied + + directly without approval. Responds with the pending version — the + + same record the default `show` returns.' tags: - - Storages - operationId: PlaceOS::Api::Storages_destroy + - SignageTemplates + operationId: PlaceOS::Api::SignageTemplates_update + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate' + required: true parameters: - name: id in: path required: true schema: type: string + format: uuid responses: - 202: - description: Accepted + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate' 409: description: Conflict content: @@ -36324,30 +36622,39 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - patch: - summary: updates a storage details + delete: + summary: remove a signage template (pending drafts and group links cascade). + description: 'remove a signage template (pending drafts and group links cascade). + + With `group_id` the template is unlinked from that group (its + + `GroupSignageTemplate` row is removed) and remains available to any + + other groups it is shared with; when that was its last group link the + + template itself is deleted so nothing is orphaned.' tags: - - Storages - operationId: PlaceOS::Api::Storages_update{2} - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Storage' - required: true + - SignageTemplates + operationId: PlaceOS::Api::SignageTemplates_destroy parameters: - name: id in: path required: true schema: type: string + format: uuid + - name: group_id + in: query + description: remove the template from this group instead of deleting it outright + (caller needs Delete or Manage on the group); the template is deleted if + no group links remain + schema: + type: string + format: uuid + nullable: true responses: - 200: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Storage' + 202: + description: Accepted 409: description: Conflict content: @@ -36426,96 +36733,42 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/systems/{sys_id}/triggers: - summary: The triggers applied to a system, and their state - get: - summary: return the list of triggers associated with the system specified + patch: + summary: 'update a template. Approved templates are never modified in place:' + description: 'update a template. Approved templates are never modified in place: + + changes to layout fields (background_item_id, layouts, + + full_screen_takeover) are staged on a draft which must be approved + + before going live. Metadata (name, description, tags) is applied + + directly without approval. Responds with the pending version — the + + same record the default `show` returns.' tags: - - SystemTriggers - operationId: PlaceOS::Api::SystemTriggers_index + - SignageTemplates + operationId: PlaceOS::Api::SignageTemplates_update{2} + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate' + required: true parameters: - - name: sys_id + - name: id in: path - description: the system to filter on - example: sys-1234 required: true schema: type: string - - name: complete - in: query - description: provide the control system details? - example: "false" - schema: - type: boolean - - name: important - in: query - description: only return triggers marked as important? - example: "true" - schema: - type: boolean - - name: triggered - in: query - description: only return triggers that have recently been triggered? - example: "true" - schema: - type: boolean - - name: trigger_id - in: query - description: filter by a particular trigger type - example: trig-1234 - schema: - type: string - nullable: true - - name: as_of - in: query - description: return triggers updated before the time specified, unix epoch - example: "123456" - schema: - type: integer - format: Int64 - nullable: true - - name: q - in: query - description: filters results by the given text; words match as prefixes against - the resource's searchable fields and all words must match - schema: - type: string - - name: limit - in: query - description: the maximum number of results to return - example: "10000" - schema: - type: integer - format: UInt32 - - name: offset - in: query - description: the starting offset of the result set, used to implement pagination - schema: - type: integer - format: UInt32 - - name: ref - in: query - description: deprecated, ignored — pagination follows the `Link` header's - offset - schema: - type: string - nullable: true - - name: fields - in: query - description: deprecated, ignored — search covers the resource's indexed fields - schema: - type: array - items: - type: string + format: uuid responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Model__TriggerInstance' + $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate' 409: description: Conflict content: @@ -36594,32 +36847,23 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - post: - summary: add a trigger to a system + /api/engine/v2/signage/templates/{id}/draft: + summary: Digital signage templates, layouts for content on displays + delete: + summary: discard the pending draft, reverting to the approved version tags: - - SystemTriggers - operationId: PlaceOS::Api::SystemTriggers_create - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__TriggerInstance' - required: true + - SignageTemplates + operationId: PlaceOS::Api::SignageTemplates_destroy_draft parameters: - - name: sys_id + - name: id in: path - description: the id of the system - example: sys-1234 required: true schema: type: string + format: uuid responses: - 201: - description: Created - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__TriggerInstance' + 202: + description: Accepted 409: description: Conflict content: @@ -36698,37 +36942,43 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/systems/{sys_id}/triggers/{trig_id}: - summary: The triggers applied to a system, and their state - get: - summary: return a particular trigger instance + /api/engine/v2/signage/templates/share: + summary: Digital signage templates, layouts for content on displays + post: + summary: Share one or more templates into another signage group via + description: "Share one or more templates into another signage group via\n`GroupSignageTemplate` + junctions. Existing junctions are preserved\n(no duplicates); the response + separates newly-created links\nfrom ones that were already in place.\n\nPermissions:\n- + sys_admin / support: any signage group in the caller's authority.\n- regular + user: must hold Share or Manage on the target group, and\n must have Read + on each template they're trying to share.\n\nAll templates and the target + group must share the caller's\nauthority." tags: - - SystemTriggers - operationId: PlaceOS::Api::SystemTriggers_show + - SignageTemplates + operationId: PlaceOS::Api::SignageTemplates_share parameters: - - name: sys_id - in: path + - name: items + in: query + description: comma-separated template ids to share into the target group required: true schema: - type: string - - name: trig_id - in: path - description: the id of the trigger - example: trig-1234 + type: array + items: + type: string + - name: to + in: query + description: target group id (must participate in the 'signage' subsystem) required: true schema: type: string - - name: complete - in: query - schema: - type: boolean + format: uuid responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__TriggerInstance' + $ref: '#/components/schemas/NamedTuple_linked__Array_String___already_present__Array_String__' 409: description: Conflict content: @@ -36807,37 +37057,32 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - put: - summary: update the details of a trigger instance + /api/engine/v2/signage/templates/{id}/approve: + summary: Digital signage templates, layouts for content on displays + post: + summary: approve the pending changes for a template. Promotes the pending + description: 'approve the pending changes for a template. Promotes the pending + + draft onto the live template (or approves a template that has never + + been approved). 406 when there is nothing to approve.' tags: - - SystemTriggers - operationId: PlaceOS::Api::SystemTriggers_update - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__SystemTriggers__UpdateParams' - required: true + - SignageTemplates + operationId: PlaceOS::Api::SignageTemplates_approve parameters: - - name: sys_id - in: path - required: true - schema: - type: string - - name: trig_id + - name: id in: path - description: the id of the trigger - example: trig-1234 required: true schema: type: string + format: uuid responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__TriggerInstance' + $ref: '#/components/schemas/PlaceOS__Model__SignageTemplate' 409: description: Conflict content: @@ -36916,27 +37161,36 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - delete: - summary: remove a trigger from a system + /api/engine/v2/signage/templates/approvers: + summary: Digital signage templates, layouts for content on displays + get: + summary: list the users who can approve templates for a group (those with the + description: 'list the users who can approve templates for a group (those with + the + + Approve or Manage permission), climbing to the nearest ancestor group + + that has approvers.' tags: - - SystemTriggers - operationId: PlaceOS::Api::SystemTriggers_destroy + - SignageTemplates + operationId: PlaceOS::Api::SignageTemplates_approvers parameters: - - name: sys_id - in: path - required: true - schema: - type: string - - name: trig_id - in: path - description: the id of the trigger - example: trig-1234 + - name: group_id + in: query + description: group to find approvers for required: true schema: type: string + format: uuid responses: - 202: - description: Accepted + 200: + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PlaceOS__Api__SignageTemplates__Approver' 409: description: Conflict content: @@ -37015,37 +37269,49 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - patch: - summary: update the details of a trigger instance + /api/engine/v2/signage/templates/{id}/request_approval: + summary: Digital signage templates, layouts for content on displays + post: + summary: request approval for a template's pending changes. Notifies the group's + description: 'request approval for a template''s pending changes. Notifies the + group''s + + approvers (or a specific `approver_id`) by queuing a PendingMail for the + + signage mailer. Any member of the group (or a parent group) may request.' tags: - - SystemTriggers - operationId: PlaceOS::Api::SystemTriggers_update{2} + - SignageTemplates + operationId: PlaceOS::Api::SignageTemplates_request_approval requestBody: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Api__SystemTriggers__UpdateParams' + $ref: '#/components/schemas/PlaceOS__Api__SignageTemplates__ApprovalMessage' required: true parameters: - - name: sys_id + - name: id in: path required: true schema: type: string - - name: trig_id - in: path - description: the id of the trigger - example: trig-1234 + format: uuid + - name: group_id + in: query + description: group whose approvers should be notified required: true schema: type: string + format: uuid + - name: approver_id + in: query + description: notify only this approver (must have approve or manage permission + in the group) + schema: + type: string + nullable: true responses: 200: description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__TriggerInstance' 409: description: Conflict content: @@ -37124,57 +37390,38 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/admin_consent/{id}: + /api/engine/v2/signage/{system_id}: get: + summary: return all the details for displaying signage tags: - - TenantConsent - operationId: PlaceOS::Api::TenantConsent_index + - Signage + operationId: PlaceOS::Api::Signage_display parameters: - - name: id + - name: system_id in: path required: true schema: type: string - - name: q - in: query - description: filters results by the given text; words match as prefixes against - the resource's searchable fields and all words must match - schema: - type: string - - name: limit - in: query - description: the maximum number of results to return - example: "10000" - schema: - type: integer - format: UInt32 - - name: offset - in: query - description: the starting offset of the result set, used to implement pagination - schema: - type: integer - format: UInt32 - - name: ref + - name: item_id in: query - description: deprecated, ignored — pagination follows the `Link` header's - offset + description: currently playing item, if the player is playing content + example: playlist_items-1234 schema: type: string nullable: true - - name: fields + - name: preview in: query - description: deprecated, ignored — search covers the resource's indexed fields + description: is this the preview player + example: "true" schema: - type: array - items: - type: string + type: boolean responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/NamedTuple_url__String_' + $ref: '#/components/schemas/_PlaceOS__Model__ControlSystem___Nil_' 409: description: Conflict content: @@ -37253,50 +37500,27 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/admin_consent/callback: - get: + /api/engine/v2/signage/{system_id}/metrics: + post: + summary: update the metrics for production players tags: - - TenantConsent - operationId: PlaceOS::Api::TenantConsent_azure_admin_consent_callback + - Signage + operationId: PlaceOS::Api::Signage_update_metrics + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Signage__Metrics' + required: true parameters: - - name: tenant - in: query - description: Azure AD tenant identifier - example: abc123 - schema: - type: string - nullable: true - - name: admin_consent - in: query - description: Indicates if admin consent was granted - example: "True" - schema: - type: boolean - nullable: true - - name: state - in: query - description: Custom state (sent in state param) - example: uuid-1234 - schema: - type: string - nullable: true - - name: error - in: query - description: Error code if consent failed - example: access_denied - schema: - type: string - nullable: true - - name: error_description - in: query - description: Description of the error - example: The admin denied the request + - name: system_id + in: path + required: true schema: type: string - nullable: true responses: - 200: - description: OK + 202: + description: Accepted 409: description: Conflict content: @@ -37375,34 +37599,44 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/admin_consent/flow/{flow_id}: + /api/engine/v2/signage/{system_id}/static/{item_id}: get: - summary: progress of an in-flight admin-consent flow, polled by the progress - page. - description: 'progress of an in-flight admin-consent flow, polled by the progress - page. - - The flow id is an unguessable capability token; the payload contains no + summary: Obtains the temporary link to the media item or a screenshot of the + live + description: 'Obtains the temporary link to the media item or a screenshot of + the live - secrets.' + item if a web page, then redirects to that URL' tags: - - TenantConsent - operationId: PlaceOS::Api::TenantConsent_flow_status + - Signage + operationId: PlaceOS::Api::Signage_fetch_static_media parameters: - - name: flow_id + - name: system_id in: path - description: Flow identifier issued by the consent callback - example: uuid-1234 + description: the display showing the media, sizes web page captures + example: sys-1234 required: true schema: type: string + - name: item_id + in: path + description: the link to the media item for display on an e-ink or other static + device + example: playlist_items-1234 + required: true + schema: + type: string + - name: expires_after + in: query + description: how many minutes the media can be cached before taking a new + screenshot + example: "60" + schema: + type: integer + format: UInt32 responses: 200: description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__AdminConsentFlow' 409: description: Conflict content: @@ -37481,57 +37715,48 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/triggers: - summary: Triggers, automation rules that run actions when conditions on system - state are met + /api/engine/v2/signage/{system_id}/samsung/eink/{item_id}/{expires_after}: get: - summary: returns the list of available triggers + summary: Returns the samsung EMDX e-ink display manifest for the provided media + item + description: 'Returns the samsung EMDX e-ink display manifest for the provided + media item + + keeping URL short as samsung only supports ~250 chars for the manifest URL' tags: - - Triggers - operationId: PlaceOS::Api::Triggers_index + - Signage + operationId: PlaceOS::Api::Signage_samsung_eink_manifest parameters: - - name: q - in: query - description: filters results by the given text; words match as prefixes against - the resource's searchable fields and all words must match + - name: system_id + in: path + description: the display showing the media, sizes web page captures + example: sys-1234 + required: true schema: type: string - - name: limit - in: query - description: the maximum number of results to return - example: "10000" + - name: item_id + in: path + description: the link to the media item for display on an e-ink or other static + device + example: playlist_items-1234 + required: true schema: - type: integer - format: UInt32 - - name: offset - in: query - description: the starting offset of the result set, used to implement pagination + type: string + - name: expires_after + in: path + description: how many minutes the media can be cached before taking a new + screenshot + example: "60" schema: type: integer format: UInt32 - - name: ref - in: query - description: deprecated, ignored — pagination follows the `Link` header's - offset - schema: - type: string - nullable: true - - name: fields - in: query - description: deprecated, ignored — search covers the resource's indexed fields - schema: - type: array - items: - type: string responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Model__Trigger' + $ref: '#/components/schemas/PlaceOS__Api__Signage__SamsungEinkManifest' 409: description: Conflict content: @@ -37610,25 +37835,63 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - post: - summary: adds a new trigger + /api/engine/v2/storages: + summary: File storage providers (e.g. S3) used for uploads + get: + summary: returns the list of available storages for provided authority tags: - - Triggers - operationId: PlaceOS::Api::Triggers_create - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Trigger' - required: true - parameters: [] + - Storages + operationId: PlaceOS::Api::Storages_index + parameters: + - name: auth_id + in: query + description: return storages which are in the authority provided + example: auth-1234 + schema: + type: string + nullable: true + - name: q + in: query + description: filters results by the given text; words match as prefixes against + the resource's searchable fields and all words must match + schema: + type: string + - name: limit + in: query + description: the maximum number of results to return + example: "10000" + schema: + type: integer + format: UInt32 + - name: offset + in: query + description: the starting offset of the result set, used to implement pagination + schema: + type: integer + format: UInt32 + - name: ref + in: query + description: deprecated, ignored — pagination follows the `Link` header's + offset + schema: + type: string + nullable: true + - name: fields + in: query + description: deprecated, ignored — search covers the resource's indexed fields + schema: + type: array + items: + type: string responses: - 201: - description: Created + 200: + description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Trigger' + type: array + items: + $ref: '#/components/schemas/PlaceOS__Model__Storage' 409: description: Conflict content: @@ -37707,34 +37970,25 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/triggers/{id}: - summary: Triggers, automation rules that run actions when conditions on system - state are met - get: - summary: returns the details of a trigger + post: + summary: adds a new storage tags: - - Triggers - operationId: PlaceOS::Api::Triggers_show - parameters: - - name: id - in: path + - Storages + operationId: PlaceOS::Api::Storages_create + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Storage' required: true - schema: - type: string - - name: instances - in: query - description: return the instances associated with this trigger - example: "true" - schema: - type: boolean - nullable: true + parameters: [] responses: - 200: - description: OK + 201: + description: Created content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Trigger' + $ref: '#/components/schemas/PlaceOS__Model__Storage' 409: description: Conflict content: @@ -37813,17 +38067,13 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - put: - summary: updates a trigger details + /api/engine/v2/storages/{id}: + summary: File storage providers (e.g. S3) used for uploads + get: + summary: returns the details of a Storage tags: - - Triggers - operationId: PlaceOS::Api::Triggers_update - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Trigger' - required: true + - Storages + operationId: PlaceOS::Api::Storages_show parameters: - name: id in: path @@ -37836,7 +38086,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Trigger' + $ref: '#/components/schemas/PlaceOS__Model__Storage' 409: description: Conflict content: @@ -37915,11 +38165,17 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - delete: - summary: removes a trigger + put: + summary: updates a storage details tags: - - Triggers - operationId: PlaceOS::Api::Triggers_destroy + - Storages + operationId: PlaceOS::Api::Storages_update + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Storage' + required: true parameters: - name: id in: path @@ -37927,8 +38183,12 @@ paths: schema: type: string responses: - 202: - description: Accepted + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Storage' 409: description: Conflict content: @@ -38007,17 +38267,11 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - patch: - summary: updates a trigger details + delete: + summary: removes a storage tags: - - Triggers - operationId: PlaceOS::Api::Triggers_update{2} - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Trigger' - required: true + - Storages + operationId: PlaceOS::Api::Storages_destroy parameters: - name: id in: path @@ -38025,12 +38279,8 @@ paths: schema: type: string responses: - 200: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Trigger' + 202: + description: Accepted 409: description: Conflict content: @@ -38109,14 +38359,17 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/triggers/{id}/instances: - summary: Triggers, automation rules that run actions when conditions on system - state are met - get: - summary: Get instances of a trigger, how many systems are using a trigger + patch: + summary: updates a storage details tags: - - Triggers - operationId: PlaceOS::Api::Triggers_instances + - Storages + operationId: PlaceOS::Api::Storages_update{2} + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Storage' + required: true parameters: - name: id in: path @@ -38129,9 +38382,7 @@ paths: content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Model__TriggerInstance' + $ref: '#/components/schemas/PlaceOS__Model__Storage' 409: description: Conflict content: @@ -38210,57 +38461,73 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/uploads: + /api/engine/v2/systems/{sys_id}/triggers: + summary: The triggers applied to a system, and their state get: - summary: returns the list of uploads for current domain authority + summary: return the list of triggers associated with the system specified tags: - - Uploads - operationId: PlaceOS::Api::Uploads_index + - SystemTriggers + operationId: PlaceOS::Api::SystemTriggers_index parameters: - - name: file_search - in: query - description: filters results to returns ones where file_name contains this - search string - example: my-file + - name: sys_id + in: path + description: the system to filter on + example: sys-1234 + required: true schema: type: string - nullable: true - - name: limit + - name: complete in: query - description: the maximum number of results to return - example: "10000" + description: provide the control system details? + example: "false" schema: - type: integer - format: Int32 - - name: offset + type: boolean + - name: important in: query - description: the starting offset of the result set. Used to implement pagination + description: only return triggers marked as important? + example: "true" schema: - type: integer - format: Int32 - - name: tags + type: boolean + - name: triggered in: query - description: return uploads with particular tags - example: staff,email + description: only return triggers that have recently been triggered? + example: "true" schema: - type: array - items: - type: string - nullable: true - - name: storage_id + type: boolean + - name: trigger_id in: query - description: storage id to list uploads from (defaults to authority's default - storage) - example: storage-XXX + description: filter by a particular trigger type + example: trig-1234 schema: type: string nullable: true + - name: as_of + in: query + description: return triggers updated before the time specified, unix epoch + example: "123456" + schema: + type: integer + format: Int64 + nullable: true - name: q in: query description: filters results by the given text; words match as prefixes against the resource's searchable fields and all words must match schema: type: string + - name: limit + in: query + description: the maximum number of results to return + example: "10000" + schema: + type: integer + format: UInt32 + - name: offset + in: query + description: the starting offset of the result set, used to implement pagination + schema: + type: integer + format: UInt32 - name: ref in: query description: deprecated, ignored — pagination follows the `Link` header's @@ -38283,7 +38550,7 @@ paths: schema: type: array items: - $ref: '#/components/schemas/PlaceOS__Model__Upload' + $ref: '#/components/schemas/PlaceOS__Model__TriggerInstance' 409: description: Conflict content: @@ -38363,24 +38630,31 @@ paths: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' post: - summary: initiate a new upload + summary: add a trigger to a system tags: - - Uploads - operationId: PlaceOS::Api::Uploads_create + - SystemTriggers + operationId: PlaceOS::Api::SystemTriggers_create requestBody: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Api__Uploads__UploadInfo' + $ref: '#/components/schemas/PlaceOS__Model__TriggerInstance' required: true - parameters: [] + parameters: + - name: sys_id + in: path + description: the id of the system + example: sys-1234 + required: true + schema: + type: string responses: - 200: - description: OK + 201: + description: Created content: application/json: schema: - $ref: '#/components/schemas/_NamedTuple_type__Symbol__signature__NamedTuple_verb__String__url__String__headers__Hash_String__String____part_list__Array_Int32___part_data__Hash_String__JSON__Any___upload_id__String___Nil__residence__String____NamedTuple_type__Symbol__signature__NamedTuple_verb__String__url__String__headers__Hash_String__String____upload_id__String___Nil__residence__String__' + $ref: '#/components/schemas/PlaceOS__Model__TriggerInstance' 409: description: Conflict content: @@ -38459,42 +38733,37 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/uploads/new: + /api/engine/v2/systems/{sys_id}/triggers/{trig_id}: + summary: The triggers applied to a system, and their state get: - summary: check the storage provider for a new file upload + summary: return a particular trigger instance tags: - - Uploads - operationId: PlaceOS::Api::Uploads_storage_name + - SystemTriggers + operationId: PlaceOS::Api::SystemTriggers_show parameters: - - name: file_name - in: query - description: Name of file which will be uploaded to cloud storage - example: test.jpeg + - name: sys_id + in: path required: true schema: type: string - - name: file_size - in: query - description: Size of file which will be uploaded to cloud storage - example: "1234" + - name: trig_id + in: path + description: the id of the trigger + example: trig-1234 required: true schema: - type: integer - format: Int64 - - name: file_mime + type: string + - name: complete in: query - description: Mime-Type of file which will be uploaded to cloud storage - example: image/jpeg schema: - type: string - nullable: true + type: boolean responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/NamedTuple_residence__String_' + $ref: '#/components/schemas/PlaceOS__Model__TriggerInstance' 409: description: Conflict content: @@ -38573,139 +38842,37 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/uploads/screenshot: - post: - summary: render a web page in a headless browser and store the image as an upload. - description: 'render a web page in a headless browser and store the image as - an upload. - - - waits for the network to go (almost) idle, web fonts to load and a frame - - to paint, then a further `settle` milliseconds for animations. - - Only `https` pages are rendered and any plain `http` load they attempt is - - blocked, which keeps the browser away from internal services.' + put: + summary: update the details of a trigger instance tags: - - Uploads - operationId: PlaceOS::Api::Uploads_screenshot + - SystemTriggers + operationId: PlaceOS::Api::SystemTriggers_update requestBody: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Api__Uploads__ScreenshotInfo' + $ref: '#/components/schemas/PlaceOS__Api__SystemTriggers__UpdateParams' required: true - parameters: [] - responses: - 201: - description: Created - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Upload' - 409: - description: Conflict - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' - 401: - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' - 503: - description: Service Unavailable - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' - 403: - description: Forbidden - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' - 404: - description: Not Found - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' - 429: - description: Too Many Requests - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' - 422: - description: Unprocessable Entity - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' - 502: - description: Bad Gateway - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' - 504: - description: Gateway Timeout - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' - 406: - description: Not Acceptable - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - 408: - description: Request Timeout - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' - 400: - description: Bad Request - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' - 415: - description: Unsupported Media Type - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/uploads/{id}/url: - get: - summary: obtain a temporary link to a private resource - tags: - - Uploads - operationId: PlaceOS::Api::Uploads_get_link parameters: - - name: id + - name: sys_id in: path - description: upload id of the upload - example: uploads-XXX required: true schema: type: string - - name: expiry - in: query - description: Link expiry period in minutes. - example: "60" + - name: trig_id + in: path + description: the id of the trigger + example: trig-1234 + required: true schema: - type: integer - format: Int32 + type: string responses: 200: description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__TriggerInstance' 409: description: Conflict content: @@ -38784,28 +38951,27 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/uploads/{id}/download: - get: - summary: proxy the data from the remote server - description: 'proxy the data from the remote server - - - this should not be used typically, it''s only useful for dumb devices and - firewall rules' + delete: + summary: remove a trigger from a system tags: - - Uploads - operationId: PlaceOS::Api::Uploads_download_proxy_file_contents + - SystemTriggers + operationId: PlaceOS::Api::SystemTriggers_destroy parameters: - - name: id + - name: sys_id in: path - description: upload id of the upload - example: uploads-XXX + required: true + schema: + type: string + - name: trig_id + in: path + description: the id of the trigger + example: trig-1234 required: true schema: type: string responses: - 200: - description: OK + 202: + description: Accepted 409: description: Conflict content: @@ -38884,41 +39050,37 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/uploads/{id}/download/{api_key}/{inline}/{file_name}: - get: - summary: a traditional looking route for downloading files - description: 'a traditional looking route for downloading files - - - the API key route param can be Base64 encoded.' + patch: + summary: update the details of a trigger instance tags: - - Uploads - operationId: PlaceOS::Api::Uploads_download_simple_route - parameters: - - name: id - in: path - description: upload id of the upload - example: uploads-XXX + - SystemTriggers + operationId: PlaceOS::Api::SystemTriggers_update{2} + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__SystemTriggers__UpdateParams' required: true - schema: - type: string - - name: api_key + parameters: + - name: sys_id in: path required: true schema: type: string - - name: inline + - name: trig_id in: path + description: the id of the trigger + example: trig-1234 required: true - schema: - type: boolean - - name: file_name - in: query schema: type: string responses: 200: description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__TriggerInstance' 409: description: Conflict content: @@ -38997,48 +39159,57 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/uploads/{id}/edit: + /api/engine/v2/admin_consent/{id}: get: - summary: obtain a signed request for each chunk of the file being uploaded. - description: 'obtain a signed request for each chunk of the file being uploaded. - - - once all parts are uploaded, you can obtain the final commit request by passing - `?part=finish` - - file_id is required except for the final finish request' tags: - - Uploads - operationId: PlaceOS::Api::Uploads_edit + - TenantConsent + operationId: PlaceOS::Api::TenantConsent_index parameters: - name: id in: path - description: upload id of the upload - example: uploads-XXX required: true schema: type: string - - name: part + - name: q in: query - description: file part which will be uploaded to cloud storage - example: part1 - required: true + description: filters results by the given text; words match as prefixes against + the resource's searchable fields and all words must match schema: type: string - - name: file_id + - name: limit in: query - description: MD5 of the part which will be uploaded to cloud storage - example: pxdXsOVpn6+SAcvZoZQphQ== + description: the maximum number of results to return + example: "10000" + schema: + type: integer + format: UInt32 + - name: offset + in: query + description: the starting offset of the result set, used to implement pagination + schema: + type: integer + format: UInt32 + - name: ref + in: query + description: deprecated, ignored — pagination follows the `Link` header's + offset schema: type: string nullable: true + - name: fields + in: query + description: deprecated, ignored — search covers the resource's indexed fields + schema: + type: array + items: + type: string responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/NamedTuple_type__Symbol__signature__NamedTuple_verb__String__url__String__headers__Hash_String__String____upload_id__String___Nil__body__String___Nil_' + $ref: '#/components/schemas/NamedTuple_url__String_' 409: description: Conflict content: @@ -39117,27 +39288,50 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/uploads/{id}: - put: - summary: mark an upload as complete + /api/engine/v2/admin_consent/callback: + get: tags: - - Uploads - operationId: PlaceOS::Api::Uploads_finished + - TenantConsent + operationId: PlaceOS::Api::TenantConsent_azure_admin_consent_callback parameters: - - name: id - in: path - description: upload id of the upload - example: uploads-XXX - required: true + - name: tenant + in: query + description: Azure AD tenant identifier + example: abc123 + schema: + type: string + nullable: true + - name: admin_consent + in: query + description: Indicates if admin consent was granted + example: "True" + schema: + type: boolean + nullable: true + - name: state + in: query + description: Custom state (sent in state param) + example: uuid-1234 + schema: + type: string + nullable: true + - name: error + in: query + description: Error code if consent failed + example: access_denied schema: type: string + nullable: true + - name: error_description + in: query + description: Description of the error + example: The admin denied the request + schema: + type: string + nullable: true responses: 200: description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/NamedTuple_ok__Bool_' 409: description: Conflict content: @@ -39216,22 +39410,34 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - delete: - summary: delete an uploaded file + /api/engine/v2/admin_consent/flow/{flow_id}: + get: + summary: progress of an in-flight admin-consent flow, polled by the progress + page. + description: 'progress of an in-flight admin-consent flow, polled by the progress + page. + + The flow id is an unguessable capability token; the payload contains no + + secrets.' tags: - - Uploads - operationId: PlaceOS::Api::Uploads_destroy + - TenantConsent + operationId: PlaceOS::Api::TenantConsent_flow_status parameters: - - name: id + - name: flow_id in: path - description: upload id of the upload - example: uploads-XXX + description: Flow identifier issued by the consent callback + example: uuid-1234 required: true schema: type: string responses: - 202: - description: Accepted + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__AdminConsentFlow' 409: description: Conflict content: @@ -39310,46 +39516,57 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - patch: - summary: save your resumable upload progress and grab the next signed request + /api/engine/v2/triggers: + summary: Triggers, automation rules that run actions when conditions on system + state are met + get: + summary: returns the list of available triggers tags: - - Uploads - operationId: PlaceOS::Api::Uploads_update - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Uploads__UpdateInfo' - required: true + - Triggers + operationId: PlaceOS::Api::Triggers_index parameters: - - name: id - in: path - description: upload id of the upload - example: uploads-XXX - required: true + - name: q + in: query + description: filters results by the given text; words match as prefixes against + the resource's searchable fields and all words must match schema: type: string - - name: part + - name: limit in: query - description: the next file part which we need a signed URL for - example: part2 + description: the maximum number of results to return + example: "10000" schema: - type: string - nullable: true - - name: file_id + type: integer + format: UInt32 + - name: offset in: query - description: MD5 of the next part - example: pxdXsOVpn6+SAcvZoZQphQ== + description: the starting offset of the result set, used to implement pagination + schema: + type: integer + format: UInt32 + - name: ref + in: query + description: deprecated, ignored — pagination follows the `Link` header's + offset schema: type: string nullable: true + - name: fields + in: query + description: deprecated, ignored — search covers the resource's indexed fields + schema: + type: array + items: + type: string responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/_NamedTuple_ok__Bool____NamedTuple_type__Symbol__signature__NamedTuple_verb__String__url__String__headers__Hash_String__String____upload_id__String___Nil__body__String___Nil__' + type: array + items: + $ref: '#/components/schemas/PlaceOS__Model__Trigger' 409: description: Conflict content: @@ -39428,26 +39645,25 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/proxy: - get: - summary: proxy the request to the provided URL, forwarding request headers - description: 'proxy the request to the provided URL, forwarding request headers - - and streaming the remote content back to the client' + post: + summary: adds a new trigger tags: - - UrlProxy - operationId: PlaceOS::Api::UrlProxy_proxy - parameters: - - name: url - in: query - description: the fully qualified URL to be proxied - example: https://example.com/image.png + - Triggers + operationId: PlaceOS::Api::Triggers_create + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Trigger' required: true - schema: - type: string + parameters: [] responses: - 200: - description: OK + 201: + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Trigger' 409: description: Conflict content: @@ -39526,21 +39742,34 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/users/current: - summary: Users of the platform, their profiles and permissions + /api/engine/v2/triggers/{id}: + summary: Triggers, automation rules that run actions when conditions on system + state are met get: - summary: Render the current user + summary: returns the details of a trigger tags: - - Users - operationId: PlaceOS::Api::Users_current - parameters: [] + - Triggers + operationId: PlaceOS::Api::Triggers_show + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: instances + in: query + description: return the instances associated with this trigger + example: "true" + schema: + type: boolean + nullable: true responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__User__AdminMetadataResponse' + $ref: '#/components/schemas/PlaceOS__Model__Trigger' 409: description: Conflict content: @@ -39619,28 +39848,30 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/users/resource_token: - summary: Users of the platform, their profiles and permissions - post: - summary: Obtain a token to the current users SSO resources - description: 'Obtain a token to the current users SSO resources - - this token is used for delegated access to things like MS Graph API or Google - API in the context of the user - - we only make this token available to the current user (admin users don''t - have access)' + put: + summary: updates a trigger details tags: - - Users - operationId: PlaceOS::Api::Users_resource_token - parameters: [] + - Triggers + operationId: PlaceOS::Api::Triggers_update + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Trigger' + required: true + parameters: + - name: id + in: path + required: true + schema: + type: string responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Api__Users__AccessToken' + $ref: '#/components/schemas/PlaceOS__Model__Trigger' 409: description: Conflict content: @@ -39719,31 +39950,20 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/users/{id}/resource_token: - summary: Users of the platform, their profiles and permissions - post: - summary: Obtain a token to the specified users SSO resources - description: 'Obtain a token to the specified users SSO resources - - requires the PlaceOS ''users'' scope to be specified explicity for access' + delete: + summary: removes a trigger tags: - - Users - operationId: PlaceOS::Api::Users_user_resource_token + - Triggers + operationId: PlaceOS::Api::Triggers_destroy parameters: - name: id in: path - description: the id of the user - example: user-1234 required: true schema: type: string responses: - 200: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Users__AccessToken' + 202: + description: Accepted 409: description: Conflict content: @@ -39822,25 +40042,30 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - delete: - summary: removes the saved resource token of a user - description: 'removes the saved resource token of a user - - a new one can be obtained via SSO authentication' + patch: + summary: updates a trigger details tags: - - Users - operationId: PlaceOS::Api::Users_delete_resource_token + - Triggers + operationId: PlaceOS::Api::Triggers_update{2} + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Trigger' + required: true parameters: - name: id in: path - description: the id of the user - example: user-1234 required: true schema: type: string responses: - 202: - description: Accepted + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Trigger' 409: description: Conflict content: @@ -39919,66 +40144,20 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/users: - summary: Users of the platform, their profiles and permissions + /api/engine/v2/triggers/{id}/instances: + summary: Triggers, automation rules that run actions when conditions on system + state are met get: - summary: returns a list of users + summary: Get instances of a trigger, how many systems are using a trigger tags: - - Users - operationId: PlaceOS::Api::Users_index + - Triggers + operationId: PlaceOS::Api::Triggers_instances parameters: - - name: include_deleted - in: query - description: include soft deleted users in the results - example: "true" - schema: - type: boolean - - name: include_metadata - in: query - description: include user metadata in the response - example: "true" - schema: - type: boolean - - name: authority_id - in: query - description: admin users can view other domains, ignored for other users - example: auth-12345 - schema: - type: string - nullable: true - - name: q - in: query - description: filters results by the given text; words match as prefixes against - the resource's searchable fields and all words must match - schema: - type: string - - name: limit - in: query - description: the maximum number of results to return - example: "10000" - schema: - type: integer - format: UInt32 - - name: offset - in: query - description: the starting offset of the result set, used to implement pagination - schema: - type: integer - format: UInt32 - - name: ref - in: query - description: deprecated, ignored — pagination follows the `Link` header's - offset + - name: id + in: path + required: true schema: type: string - nullable: true - - name: fields - in: query - description: deprecated, ignored — search covers the resource's indexed fields - schema: - type: array - items: - type: string responses: 200: description: OK @@ -39987,7 +40166,7 @@ paths: schema: type: array items: - $ref: '#/components/schemas/_PlaceOS__Model__User__AdminMetadataResponse___PlaceOS__Model__User__AdminResponse___PlaceOS__Model__User__PublicMetadataResponse___PlaceOS__Model__User__PublicResponse_' + $ref: '#/components/schemas/PlaceOS__Model__TriggerInstance' 409: description: Conflict content: @@ -40066,25 +40245,80 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - post: - summary: add a new local user + /api/engine/v2/uploads: + get: + summary: returns the list of uploads for current domain authority tags: - - Users - operationId: PlaceOS::Api::Users_create - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/JSON__Any' - required: true - parameters: [] + - Uploads + operationId: PlaceOS::Api::Uploads_index + parameters: + - name: file_search + in: query + description: filters results to returns ones where file_name contains this + search string + example: my-file + schema: + type: string + nullable: true + - name: limit + in: query + description: the maximum number of results to return + example: "10000" + schema: + type: integer + format: Int32 + - name: offset + in: query + description: the starting offset of the result set. Used to implement pagination + schema: + type: integer + format: Int32 + - name: tags + in: query + description: return uploads with particular tags + example: staff,email + schema: + type: array + items: + type: string + nullable: true + - name: storage_id + in: query + description: storage id to list uploads from (defaults to authority's default + storage) + example: storage-XXX + schema: + type: string + nullable: true + - name: q + in: query + description: filters results by the given text; words match as prefixes against + the resource's searchable fields and all words must match + schema: + type: string + - name: ref + in: query + description: deprecated, ignored — pagination follows the `Link` header's + offset + schema: + type: string + nullable: true + - name: fields + in: query + description: deprecated, ignored — search covers the resource's indexed fields + schema: + type: array + items: + type: string responses: - 201: - description: Created + 200: + description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__User' + type: array + items: + $ref: '#/components/schemas/PlaceOS__Model__Upload' 409: description: Conflict content: @@ -40163,34 +40397,25 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/users/{id}: - summary: Users of the platform, their profiles and permissions - get: - summary: returns the profile of the selected user + post: + summary: initiate a new upload tags: - - Users - operationId: PlaceOS::Api::Users_show - parameters: - - name: id - in: path - description: the id of the user - example: user-1234 + - Uploads + operationId: PlaceOS::Api::Uploads_create + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Uploads__UploadInfo' required: true - schema: - type: string - - name: include_metadata - in: query - description: include user metadata in the response - example: "true" - schema: - type: boolean + parameters: [] responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/_PlaceOS__Model__User__AdminMetadataResponse___PlaceOS__Model__User__AdminResponse___PlaceOS__Model__User__PublicMetadataResponse___PlaceOS__Model__User__PublicResponse_' + $ref: '#/components/schemas/_NamedTuple_type__Symbol__signature__NamedTuple_verb__String__url__String__headers__Hash_String__String____part_list__Array_Int32___part_data__Hash_String__JSON__Any___upload_id__String___Nil__residence__String____NamedTuple_type__Symbol__signature__NamedTuple_verb__String__url__String__headers__Hash_String__String____upload_id__String___Nil__residence__String__' 409: description: Conflict content: @@ -40269,32 +40494,42 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - put: - summary: udpate a users profile + /api/engine/v2/uploads/new: + get: + summary: check the storage provider for a new file upload tags: - - Users - operationId: PlaceOS::Api::Users_update - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/JSON__Any' - required: true + - Uploads + operationId: PlaceOS::Api::Uploads_storage_name parameters: - - name: id - in: path - description: the id of the user - example: user-1234 + - name: file_name + in: query + description: Name of file which will be uploaded to cloud storage + example: test.jpeg required: true schema: type: string + - name: file_size + in: query + description: Size of file which will be uploaded to cloud storage + example: "1234" + required: true + schema: + type: integer + format: Int64 + - name: file_mime + in: query + description: Mime-Type of file which will be uploaded to cloud storage + example: image/jpeg + schema: + type: string + nullable: true responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__User' + $ref: '#/components/schemas/NamedTuple_residence__String_' 409: description: Conflict content: @@ -40373,26 +40608,37 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - delete: - summary: Destroy user, revoke authentication. + /api/engine/v2/uploads/screenshot: + post: + summary: render a web page in a headless browser and store the image as an upload. + description: 'render a web page in a headless browser and store the image as + an upload. + + + waits for the network to go (almost) idle, web fonts to load and a frame + + to paint, then a further `settle` milliseconds for animations. + + Only `https` pages are rendered and any plain `http` load they attempt is + + blocked, which keeps the browser away from internal services.' tags: - - Users - operationId: PlaceOS::Api::Users_destroy - parameters: - - name: id - in: path - description: the id of the user - example: user-1234 + - Uploads + operationId: PlaceOS::Api::Uploads_screenshot + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Uploads__ScreenshotInfo' required: true - schema: - type: string - - name: force_removal - in: query - schema: - type: boolean + parameters: [] responses: - 202: - description: Accepted + 201: + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Upload' 409: description: Conflict content: @@ -40471,32 +40717,30 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - patch: - summary: udpate a users profile + /api/engine/v2/uploads/{id}/url: + get: + summary: obtain a temporary link to a private resource tags: - - Users - operationId: PlaceOS::Api::Users_update{2} - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/JSON__Any' - required: true + - Uploads + operationId: PlaceOS::Api::Uploads_get_link parameters: - name: id in: path - description: the id of the user - example: user-1234 + description: upload id of the upload + example: uploads-XXX required: true schema: type: string + - name: expiry + in: query + description: Link expiry period in minutes. + example: "60" + schema: + type: integer + format: Int32 responses: 200: description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__User' 409: description: Conflict content: @@ -40575,18 +40819,22 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/users/{id}/revive: - summary: Users of the platform, their profiles and permissions - post: - summary: undelete a user + /api/engine/v2/uploads/{id}/download: + get: + summary: proxy the data from the remote server + description: 'proxy the data from the remote server + + + this should not be used typically, it''s only useful for dumb devices and + firewall rules' tags: - - Users - operationId: PlaceOS::Api::Users_revive + - Uploads + operationId: PlaceOS::Api::Uploads_download_proxy_file_contents parameters: - name: id in: path - description: the id of the user - example: user-1234 + description: upload id of the upload + example: uploads-XXX required: true schema: type: string @@ -40671,35 +40919,41 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/users/{id}/metadata: - summary: Users of the platform, their profiles and permissions + /api/engine/v2/uploads/{id}/download/{api_key}/{inline}/{file_name}: get: - summary: return a users metadata + summary: a traditional looking route for downloading files + description: 'a traditional looking route for downloading files + + + the API key route param can be Base64 encoded.' tags: - - Users - operationId: PlaceOS::Api::Users_metadata + - Uploads + operationId: PlaceOS::Api::Uploads_download_simple_route parameters: - name: id in: path - description: the id of the user - example: user-1234 + description: upload id of the upload + example: uploads-XXX required: true schema: type: string - - name: name + - name: api_key + in: path + required: true + schema: + type: string + - name: inline + in: path + required: true + schema: + type: boolean + - name: file_name in: query - description: filter metadata by a particular entry - example: department schema: type: string - nullable: true responses: 200: description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/Hash_String__PlaceOS__Model__Metadata__Interface_' 409: description: Conflict content: @@ -40778,32 +41032,48 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/users/groups: - summary: Users of the platform, their profiles and permissions + /api/engine/v2/uploads/{id}/edit: get: - summary: Returns the groups these users are in + summary: obtain a signed request for each chunk of the file being uploaded. + description: 'obtain a signed request for each chunk of the file being uploaded. + + + once all parts are uploaded, you can obtain the final commit request by passing + `?part=finish` + + file_id is required except for the final finish request' tags: - - Users - operationId: PlaceOS::Api::Users_groups + - Uploads + operationId: PlaceOS::Api::Uploads_edit parameters: - - name: emails + - name: id + in: path + description: upload id of the upload + example: uploads-XXX + required: true + schema: + type: string + - name: part in: query - description: the user emails whos group membership we are interested - example: user1@org.com,user2@org.com + description: file part which will be uploaded to cloud storage + example: part1 required: true schema: - type: array - items: - type: string + type: string + - name: file_id + in: query + description: MD5 of the part which will be uploaded to cloud storage + example: pxdXsOVpn6+SAcvZoZQphQ== + schema: + type: string + nullable: true responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Model__User__GroupResponse' + $ref: '#/components/schemas/NamedTuple_type__Symbol__signature__NamedTuple_verb__String__url__String__headers__Hash_String__String____upload_id__String___Nil__body__String___Nil_' 409: description: Conflict content: @@ -40882,48 +41152,27 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/users/metadata/search: - summary: Users of the platform, their profiles and permissions - get: - summary: Search User metadata with provided JSON Path query. - description: 'Search User metadata with provided JSON Path query. - - query need to follow JSONPath query expression as described in - - https://www.ietf.org/archive/id/draft-ietf-jsonpath-base-14.html' + /api/engine/v2/uploads/{id}: + put: + summary: mark an upload as complete tags: - - Users - operationId: PlaceOS::Api::Users_search + - Uploads + operationId: PlaceOS::Api::Uploads_finished parameters: - - name: filter - in: query - description: filter expression in JSONPath format - example: $.bookings.allowed_daily_desk_count ? (@>0) + - name: id + in: path + description: upload id of the upload + example: uploads-XXX required: true schema: type: string - - name: limit - in: query - description: the maximum number of results to return - example: "10000" - schema: - type: integer - format: Int32 - - name: offset - in: query - description: the starting offset of the result set. Used to implement pagination - schema: - type: integer - format: Int32 responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Model__User' + $ref: '#/components/schemas/NamedTuple_ok__Bool_' 409: description: Conflict content: @@ -41002,30 +41251,22 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/webhook/{id}: - get: - summary: returns the details of a webhook trigger + delete: + summary: delete an uploaded file tags: - - Webhook - operationId: PlaceOS::Api::Webhook_show + - Uploads + operationId: PlaceOS::Api::Uploads_destroy parameters: - name: id in: path - required: true - schema: - type: string - - name: secret - in: query + description: upload id of the upload + example: uploads-XXX required: true schema: type: string responses: - 200: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Trigger' + 202: + description: Accepted 409: description: Conflict content: @@ -41104,34 +41345,46 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/webrtc/guest_entry/{system_id}: - post: - summary: this route provides guest access to an anonymous chat room - description: 'this route provides guest access to an anonymous chat room - - the guest participant details will be forwarded to any listening systems - - additional fields provided as part of the guest post will also be forwarded' + patch: + summary: save your resumable upload progress and grab the next signed request tags: - - WebRTC - operationId: PlaceOS::Api::WebRTC_guest_entry + - Uploads + operationId: PlaceOS::Api::Uploads_update requestBody: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Api__GuestParticipant' + $ref: '#/components/schemas/PlaceOS__Api__Uploads__UpdateInfo' required: true parameters: - - name: system_id + - name: id in: path - description: either a system id or a unique permalink - example: sys-12345 + description: upload id of the upload + example: uploads-XXX required: true schema: type: string + - name: part + in: query + description: the next file part which we need a signed URL for + example: part2 + schema: + type: string + nullable: true + - name: file_id + in: query + description: MD5 of the next part + example: pxdXsOVpn6+SAcvZoZQphQ== + schema: + type: string + nullable: true responses: 200: description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/_NamedTuple_ok__Bool____NamedTuple_type__Symbol__signature__NamedTuple_verb__String__url__String__headers__Hash_String__String____upload_id__String___Nil__body__String___Nil__' 409: description: Conflict content: @@ -41210,16 +41463,23 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/webrtc/guest/exit: - post: - summary: Guest users should call this route when ending a call gracefully - description: 'Guest users should call this route when ending a call gracefully + /api/engine/v2/proxy: + get: + summary: proxy the request to the provided URL, forwarding request headers + description: 'proxy the request to the provided URL, forwarding request headers - it will remove the authentication token and close any open websockets' + and streaming the remote content back to the client' tags: - - WebRTC - operationId: PlaceOS::Api::WebRTC_guest_exit - parameters: [] + - UrlProxy + operationId: PlaceOS::Api::UrlProxy_proxy + parameters: + - name: url + in: query + description: the fully qualified URL to be proxied + example: https://example.com/image.png + required: true + schema: + type: string responses: 200: description: OK @@ -41301,37 +41561,21 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/webrtc/kick/{user_id}/{session_id}: - post: - summary: Call ended for the user - description: 'Call ended for the user - - similar to guest exit without the token expiration - - other members of the call will stop communicating with them' + /api/engine/v2/users/current: + summary: Users of the platform, their profiles and permissions + get: + summary: Render the current user tags: - - WebRTC - operationId: PlaceOS::Api::WebRTC_kick_user - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__KickReason' - required: true - parameters: - - name: user_id - in: path - required: true - schema: - type: string - - name: session_id - in: path - required: true - schema: - type: string + - Users + operationId: PlaceOS::Api::Users_current + parameters: [] responses: 200: description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__User__AdminMetadataResponse' 409: description: Conflict content: @@ -41410,27 +41654,28 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/webrtc/members/{session_id}: - get: - summary: Obtain a list of the connected users in chat session provided + /api/engine/v2/users/resource_token: + summary: Users of the platform, their profiles and permissions + post: + summary: Obtain a token to the current users SSO resources + description: 'Obtain a token to the current users SSO resources + + this token is used for delegated access to things like MS Graph API or Google + API in the context of the user + + we only make this token available to the current user (admin users don''t + have access)' tags: - - WebRTC - operationId: PlaceOS::Api::WebRTC_members - parameters: - - name: session_id - in: path - required: true - schema: - type: string + - Users + operationId: PlaceOS::Api::Users_resource_token + parameters: [] responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/String' + $ref: '#/components/schemas/PlaceOS__Api__Users__AccessToken' 409: description: Conflict content: @@ -41509,41 +41754,31 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/webrtc/transfer/{user_id}/{session_id}: + /api/engine/v2/users/{id}/resource_token: + summary: Users of the platform, their profiles and permissions post: - summary: for authorised users to move people from one chat to another - description: 'for authorised users to move people from one chat to another + summary: Obtain a token to the specified users SSO resources + description: 'Obtain a token to the specified users SSO resources - the body can be used to pass any custom connection details' + requires the PlaceOS ''users'' scope to be specified explicity for access' tags: - - WebRTC - operationId: PlaceOS::Api::WebRTC_transfer_guest - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/_JSON__Any___Nil_' - required: true + - Users + operationId: PlaceOS::Api::Users_user_resource_token parameters: - - name: user_id + - name: id in: path + description: the id of the user + example: user-1234 required: true schema: type: string - - name: session_id - in: path - schema: - type: string - nullable: true responses: 200: description: OK - 428: - description: Precondition Required content: application/json: schema: - $ref: '#/components/schemas/Bool' + $ref: '#/components/schemas/PlaceOS__Api__Users__AccessToken' 409: description: Conflict content: @@ -41622,30 +41857,25 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/webrtc/room/{system_id}: - get: - summary: this route provides the details of a public chat room - description: 'this route provides the details of a public chat room + delete: + summary: removes the saved resource token of a user + description: 'removes the saved resource token of a user - it takes either the system `id` or `code` for a more friendly lookup' + a new one can be obtained via SSO authentication' tags: - - WebRTC - operationId: PlaceOS::Api::WebRTC_public_room + - Users + operationId: PlaceOS::Api::Users_delete_resource_token parameters: - - name: system_id + - name: id in: path - description: either a system id or a unique permalink - example: sys-12345 + description: the id of the user + example: user-1234 required: true schema: type: string responses: - 200: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__WebRTC__RoomDetails' + 202: + description: Accepted 409: description: Conflict content: @@ -41724,13 +41954,33 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/webrtc/rooms: + /api/engine/v2/users: + summary: Users of the platform, their profiles and permissions get: - summary: this route provides a list of public chat rooms for the current domain + summary: returns a list of users tags: - - WebRTC - operationId: PlaceOS::Api::WebRTC_index + - Users + operationId: PlaceOS::Api::Users_index parameters: + - name: include_deleted + in: query + description: include soft deleted users in the results + example: "true" + schema: + type: boolean + - name: include_metadata + in: query + description: include user metadata in the response + example: "true" + schema: + type: boolean + - name: authority_id + in: query + description: admin users can view other domains, ignored for other users + example: auth-12345 + schema: + type: string + nullable: true - name: q in: query description: filters results by the given text; words match as prefixes against @@ -41772,7 +42022,7 @@ paths: schema: type: array items: - $ref: '#/components/schemas/PlaceOS__Model__ControlSystem' + $ref: '#/components/schemas/_PlaceOS__Model__User__AdminMetadataResponse___PlaceOS__Model__User__AdminResponse___PlaceOS__Model__User__PublicMetadataResponse___PlaceOS__Model__User__PublicResponse_' 409: description: Conflict content: @@ -41851,16 +42101,25 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/webrtc/signaller: - get: - summary: WebRTC signaller websocket endpoint, managing call participants + post: + summary: add a new local user tags: - - WebRTC - operationId: PlaceOS::Api::WebRTC_signaller + - Users + operationId: PlaceOS::Api::Users_create + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/JSON__Any' + required: true parameters: [] responses: - 101: - description: Switching Protocols + 201: + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__User' 409: description: Conflict content: @@ -41939,98 +42198,34 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/zones: - summary: Zones, the hierarchy of places (organisation, region, building, level, - area) that systems belong to + /api/engine/v2/users/{id}: + summary: Users of the platform, their profiles and permissions get: - summary: list the configured zones + summary: returns the profile of the selected user tags: - - Zones - operationId: PlaceOS::Api::Zones_index + - Users + operationId: PlaceOS::Api::Users_show parameters: - - name: parent_id - in: query - description: only return zones who have this zone as a parent (supports comma-separated - list). Use 'root' to get zones with no parent - example: zone-1234,zone-5678 - schema: - type: array - items: - type: string - nullable: true - - name: tags - in: query - description: return zones with particular tags - example: building,level - schema: - type: array - items: - type: string - nullable: true - - name: group_id - in: query - description: filter to zones a group has direct GroupZone anchors on; non-support - callers must have Read on the group. Forces include_children_count=true - so callers can drill down. + - name: id + in: path + description: the id of the user + example: user-1234 + required: true schema: type: string - format: uuid - nullable: true - - name: include_children_count - in: query - description: include children_count for each zone (useful for tree views) - example: "true" - schema: - type: boolean - - name: descendants + - name: include_metadata in: query - description: with group_id, include anchor zones and their descendants, excluding - denied subtrees + description: include user metadata in the response example: "true" schema: type: boolean - - name: q - in: query - description: filters results by the given text; words match as prefixes against - the resource's searchable fields and all words must match - schema: - type: string - - name: limit - in: query - description: the maximum number of results to return - example: "10000" - schema: - type: integer - format: UInt32 - - name: offset - in: query - description: the starting offset of the result set, used to implement pagination - schema: - type: integer - format: UInt32 - - name: ref - in: query - description: deprecated, ignored — pagination follows the `Link` header's - offset - schema: - type: string - nullable: true - - name: fields - in: query - description: deprecated, ignored — search covers the resource's indexed fields - schema: - type: array - items: - type: string responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Model__Zone' + $ref: '#/components/schemas/_PlaceOS__Model__User__AdminMetadataResponse___PlaceOS__Model__User__AdminResponse___PlaceOS__Model__User__PublicMetadataResponse___PlaceOS__Model__User__PublicResponse_' 409: description: Conflict content: @@ -42109,25 +42304,32 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - post: - summary: add a new zone + put: + summary: udpate a users profile tags: - - Zones - operationId: PlaceOS::Api::Zones_create + - Users + operationId: PlaceOS::Api::Users_update requestBody: content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Zone' + $ref: '#/components/schemas/JSON__Any' required: true - parameters: [] + parameters: + - name: id + in: path + description: the id of the user + example: user-1234 + required: true + schema: + type: string responses: - 201: - description: Created + 200: + description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Zone' + $ref: '#/components/schemas/PlaceOS__Model__User' 409: description: Conflict content: @@ -42206,24 +42408,26 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/zones/tags: - summary: Zones, the hierarchy of places (organisation, region, building, level, - area) that systems belong to - get: - summary: returns unique zone tags + delete: + summary: Destroy user, revoke authentication. tags: - - Zones - operationId: PlaceOS::Api::Zones_tags - parameters: [] + - Users + operationId: PlaceOS::Api::Users_destroy + parameters: + - name: id + in: path + description: the id of the user + example: user-1234 + required: true + schema: + type: string + - name: force_removal + in: query + schema: + type: boolean responses: - 200: - description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/String' + 202: + description: Accepted 409: description: Conflict content: @@ -42302,33 +42506,32 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/zones/{id}: - summary: Zones, the hierarchy of places (organisation, region, building, level, - area) that systems belong to - get: - summary: return the details of the zone + patch: + summary: udpate a users profile tags: - - Zones - operationId: PlaceOS::Api::Zones_show + - Users + operationId: PlaceOS::Api::Users_update{2} + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/JSON__Any' + required: true parameters: - name: id in: path + description: the id of the user + example: user-1234 required: true schema: type: string - - name: complete - in: query - description: also return any triggers associated with the zone - example: "true" - schema: - type: boolean responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Zone' + $ref: '#/components/schemas/PlaceOS__Model__User' 409: description: Conflict content: @@ -42407,30 +42610,24 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - put: - summary: update the details of a zone + /api/engine/v2/users/{id}/revive: + summary: Users of the platform, their profiles and permissions + post: + summary: undelete a user tags: - - Zones - operationId: PlaceOS::Api::Zones_update - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Zone' - required: true + - Users + operationId: PlaceOS::Api::Users_revive parameters: - name: id in: path + description: the id of the user + example: user-1234 required: true schema: type: string responses: 200: description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Zone' 409: description: Conflict content: @@ -42509,20 +42706,35 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - delete: - summary: remove a zone and any children zones + /api/engine/v2/users/{id}/metadata: + summary: Users of the platform, their profiles and permissions + get: + summary: return a users metadata tags: - - Zones - operationId: PlaceOS::Api::Zones_destroy + - Users + operationId: PlaceOS::Api::Users_metadata parameters: - name: id in: path + description: the id of the user + example: user-1234 required: true schema: type: string + - name: name + in: query + description: filter metadata by a particular entry + example: department + schema: + type: string + nullable: true responses: - 202: - description: Accepted + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Hash_String__PlaceOS__Model__Metadata__Interface_' 409: description: Conflict content: @@ -42601,30 +42813,32 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - patch: - summary: update the details of a zone + /api/engine/v2/users/groups: + summary: Users of the platform, their profiles and permissions + get: + summary: Returns the groups these users are in tags: - - Zones - operationId: PlaceOS::Api::Zones_update{2} - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Model__Zone' - required: true + - Users + operationId: PlaceOS::Api::Users_groups parameters: - - name: id - in: path + - name: emails + in: query + description: the user emails whos group membership we are interested + example: user1@org.com,user2@org.com required: true schema: - type: string + type: array + items: + type: string responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/PlaceOS__Model__Zone' + type: array + items: + $ref: '#/components/schemas/PlaceOS__Model__User__GroupResponse' 409: description: Conflict content: @@ -42703,34 +42917,48 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/zones/{id}/metadata: - summary: Zones, the hierarchy of places (organisation, region, building, level, - area) that systems belong to + /api/engine/v2/users/metadata/search: + summary: Users of the platform, their profiles and permissions get: - summary: return metadata associcated with the selected zone + summary: Search User metadata with provided JSON Path query. + description: 'Search User metadata with provided JSON Path query. + + query need to follow JSONPath query expression as described in + + https://www.ietf.org/archive/id/draft-ietf-jsonpath-base-14.html' tags: - - Zones - operationId: PlaceOS::Api::Zones_metadata + - Users + operationId: PlaceOS::Api::Users_search parameters: - - name: id - in: path + - name: filter + in: query + description: filter expression in JSONPath format + example: $.bookings.allowed_daily_desk_count ? (@>0) required: true schema: type: string - - name: name + - name: limit in: query - description: only return the metadata key we are interested in - example: workplace-config + description: the maximum number of results to return + example: "10000" schema: - type: string - nullable: true + type: integer + format: Int32 + - name: offset + in: query + description: the starting offset of the result set. Used to implement pagination + schema: + type: integer + format: Int32 responses: 200: description: OK content: application/json: schema: - $ref: '#/components/schemas/Hash_String__PlaceOS__Model__Metadata__Interface_' + type: array + items: + $ref: '#/components/schemas/PlaceOS__Model__User' 409: description: Conflict content: @@ -42809,29 +43037,30 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/zones/{id}/triggers: - summary: Zones, the hierarchy of places (organisation, region, building, level, - area) that systems belong to + /api/engine/v2/webhook/{id}: get: - summary: Return triggers attached to current zone + summary: returns the details of a webhook trigger tags: - - Zones - operationId: PlaceOS::Api::Zones_trigger_instances + - Webhook + operationId: PlaceOS::Api::Webhook_show parameters: - name: id in: path required: true schema: type: string + - name: secret + in: query + required: true + schema: + type: string responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/PlaceOS__Model__Trigger' + $ref: '#/components/schemas/PlaceOS__Model__Trigger' 409: description: Conflict content: @@ -42910,48 +43139,1869 @@ paths: application/json: schema: $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' - /api/engine/v2/zones/{id}/exec/{module_slug}/{method}: - summary: Zones, the hierarchy of places (organisation, region, building, level, - area) that systems belong to + /api/engine/v2/webrtc/guest_entry/{system_id}: post: - summary: Execute a method on a module across all systems in a Zone + summary: this route provides guest access to an anonymous chat room + description: 'this route provides guest access to an anonymous chat room + + the guest participant details will be forwarded to any listening systems + + additional fields provided as part of the guest post will also be forwarded' tags: - - Zones - operationId: PlaceOS::Api::Zones_zone_execute + - WebRTC + operationId: PlaceOS::Api::WebRTC_guest_entry requestBody: content: application/json: schema: - $ref: '#/components/schemas/Array_JSON__Any_' + $ref: '#/components/schemas/PlaceOS__Api__GuestParticipant' required: true parameters: - - name: id - in: path - required: true - schema: - type: string - - name: module_slug - in: path - description: the combined module class and index, index is optional and defaults - to 1 - example: Display_2 - required: true - schema: - type: string - - name: method + - name: system_id in: path - description: the method to execute on the module - example: power + description: either a system id or a unique permalink + example: sys-12345 required: true schema: type: string responses: 200: description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PlaceOS__Api__Zones__ZoneExecResponse' + 409: + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 503: + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 404: + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 429: + description: Too Many Requests + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 422: + description: Unprocessable Entity + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 502: + description: Bad Gateway + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 504: + description: Gateway Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 406: + description: Not Acceptable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + 408: + description: Request Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 400: + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 415: + description: Unsupported Media Type + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + /api/engine/v2/webrtc/guest/exit: + post: + summary: Guest users should call this route when ending a call gracefully + description: 'Guest users should call this route when ending a call gracefully + + it will remove the authentication token and close any open websockets' + tags: + - WebRTC + operationId: PlaceOS::Api::WebRTC_guest_exit + parameters: [] + responses: + 200: + description: OK + 409: + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 503: + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 404: + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 429: + description: Too Many Requests + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 422: + description: Unprocessable Entity + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 502: + description: Bad Gateway + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 504: + description: Gateway Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 406: + description: Not Acceptable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + 408: + description: Request Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 400: + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 415: + description: Unsupported Media Type + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + /api/engine/v2/webrtc/kick/{user_id}/{session_id}: + post: + summary: Call ended for the user + description: 'Call ended for the user + + similar to guest exit without the token expiration + + other members of the call will stop communicating with them' + tags: + - WebRTC + operationId: PlaceOS::Api::WebRTC_kick_user + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__KickReason' + required: true + parameters: + - name: user_id + in: path + required: true + schema: + type: string + - name: session_id + in: path + required: true + schema: + type: string + responses: + 200: + description: OK + 409: + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 503: + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 404: + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 429: + description: Too Many Requests + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 422: + description: Unprocessable Entity + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 502: + description: Bad Gateway + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 504: + description: Gateway Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 406: + description: Not Acceptable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + 408: + description: Request Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 400: + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 415: + description: Unsupported Media Type + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + /api/engine/v2/webrtc/members/{session_id}: + get: + summary: Obtain a list of the connected users in chat session provided + tags: + - WebRTC + operationId: PlaceOS::Api::WebRTC_members + parameters: + - name: session_id + in: path + required: true + schema: + type: string + responses: + 200: + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/String' + 409: + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 503: + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 404: + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 429: + description: Too Many Requests + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 422: + description: Unprocessable Entity + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 502: + description: Bad Gateway + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 504: + description: Gateway Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 406: + description: Not Acceptable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + 408: + description: Request Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 400: + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 415: + description: Unsupported Media Type + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + /api/engine/v2/webrtc/transfer/{user_id}/{session_id}: + post: + summary: for authorised users to move people from one chat to another + description: 'for authorised users to move people from one chat to another + + the body can be used to pass any custom connection details' + tags: + - WebRTC + operationId: PlaceOS::Api::WebRTC_transfer_guest + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/_JSON__Any___Nil_' + required: true + parameters: + - name: user_id + in: path + required: true + schema: + type: string + - name: session_id + in: path + schema: + type: string + nullable: true + responses: + 200: + description: OK + 428: + description: Precondition Required + content: + application/json: + schema: + $ref: '#/components/schemas/Bool' + 409: + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 503: + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 404: + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 429: + description: Too Many Requests + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 422: + description: Unprocessable Entity + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 502: + description: Bad Gateway + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 504: + description: Gateway Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 406: + description: Not Acceptable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + 408: + description: Request Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 400: + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 415: + description: Unsupported Media Type + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + /api/engine/v2/webrtc/room/{system_id}: + get: + summary: this route provides the details of a public chat room + description: 'this route provides the details of a public chat room + + it takes either the system `id` or `code` for a more friendly lookup' + tags: + - WebRTC + operationId: PlaceOS::Api::WebRTC_public_room + parameters: + - name: system_id + in: path + description: either a system id or a unique permalink + example: sys-12345 + required: true + schema: + type: string + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__WebRTC__RoomDetails' + 409: + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 503: + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 404: + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 429: + description: Too Many Requests + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 422: + description: Unprocessable Entity + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 502: + description: Bad Gateway + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 504: + description: Gateway Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 406: + description: Not Acceptable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + 408: + description: Request Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 400: + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 415: + description: Unsupported Media Type + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + /api/engine/v2/webrtc/rooms: + get: + summary: this route provides a list of public chat rooms for the current domain + tags: + - WebRTC + operationId: PlaceOS::Api::WebRTC_index + parameters: + - name: q + in: query + description: filters results by the given text; words match as prefixes against + the resource's searchable fields and all words must match + schema: + type: string + - name: limit + in: query + description: the maximum number of results to return + example: "10000" + schema: + type: integer + format: UInt32 + - name: offset + in: query + description: the starting offset of the result set, used to implement pagination + schema: + type: integer + format: UInt32 + - name: ref + in: query + description: deprecated, ignored — pagination follows the `Link` header's + offset + schema: + type: string + nullable: true + - name: fields + in: query + description: deprecated, ignored — search covers the resource's indexed fields + schema: + type: array + items: + type: string + responses: + 200: + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PlaceOS__Model__ControlSystem' + 409: + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 503: + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 404: + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 429: + description: Too Many Requests + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 422: + description: Unprocessable Entity + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 502: + description: Bad Gateway + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 504: + description: Gateway Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 406: + description: Not Acceptable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + 408: + description: Request Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 400: + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 415: + description: Unsupported Media Type + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + /api/engine/v2/webrtc/signaller: + get: + summary: WebRTC signaller websocket endpoint, managing call participants + tags: + - WebRTC + operationId: PlaceOS::Api::WebRTC_signaller + parameters: [] + responses: + 101: + description: Switching Protocols + 409: + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 503: + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 404: + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 429: + description: Too Many Requests + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 422: + description: Unprocessable Entity + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 502: + description: Bad Gateway + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 504: + description: Gateway Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 406: + description: Not Acceptable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + 408: + description: Request Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 400: + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 415: + description: Unsupported Media Type + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + /api/engine/v2/zones: + summary: Zones, the hierarchy of places (organisation, region, building, level, + area) that systems belong to + get: + summary: list the configured zones + tags: + - Zones + operationId: PlaceOS::Api::Zones_index + parameters: + - name: parent_id + in: query + description: only return zones who have this zone as a parent (supports comma-separated + list). Use 'root' to get zones with no parent + example: zone-1234,zone-5678 + schema: + type: array + items: + type: string + nullable: true + - name: tags + in: query + description: return zones with particular tags + example: building,level + schema: + type: array + items: + type: string + nullable: true + - name: group_id + in: query + description: filter to zones a group has direct GroupZone anchors on; non-support + callers must have Read on the group. Forces include_children_count=true + so callers can drill down. + schema: + type: string + format: uuid + nullable: true + - name: include_children_count + in: query + description: include children_count for each zone (useful for tree views) + example: "true" + schema: + type: boolean + - name: descendants + in: query + description: with group_id, include anchor zones and their descendants, excluding + denied subtrees + example: "true" + schema: + type: boolean + - name: q + in: query + description: filters results by the given text; words match as prefixes against + the resource's searchable fields and all words must match + schema: + type: string + - name: limit + in: query + description: the maximum number of results to return + example: "10000" + schema: + type: integer + format: UInt32 + - name: offset + in: query + description: the starting offset of the result set, used to implement pagination + schema: + type: integer + format: UInt32 + - name: ref + in: query + description: deprecated, ignored — pagination follows the `Link` header's + offset + schema: + type: string + nullable: true + - name: fields + in: query + description: deprecated, ignored — search covers the resource's indexed fields + schema: + type: array + items: + type: string + responses: + 200: + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PlaceOS__Model__Zone' + 409: + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 503: + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 404: + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 429: + description: Too Many Requests + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 422: + description: Unprocessable Entity + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 502: + description: Bad Gateway + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 504: + description: Gateway Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 406: + description: Not Acceptable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + 408: + description: Request Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 400: + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 415: + description: Unsupported Media Type + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + post: + summary: add a new zone. A child zone belongs to its parent's organisation; + a + description: 'add a new zone. A child zone belongs to its parent''s organisation; + a + + root zone belongs to the caller''s, or to the organisation a cluster + + admin names.' + tags: + - Zones + operationId: PlaceOS::Api::Zones_create + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Zone' + required: true + parameters: + - name: organisation_id + in: query + description: the organisation that owns a new root zone (cluster admins only) + example: 0192f1c4-7a6b-7c4d-9f3e-1a2b3c4d5e6f + schema: + type: string + format: uuid + nullable: true + responses: + 201: + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Zone' + 409: + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 503: + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 404: + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 429: + description: Too Many Requests + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 422: + description: Unprocessable Entity + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 502: + description: Bad Gateway + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 504: + description: Gateway Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 406: + description: Not Acceptable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + 408: + description: Request Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 400: + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 415: + description: Unsupported Media Type + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + /api/engine/v2/zones/tags: + summary: Zones, the hierarchy of places (organisation, region, building, level, + area) that systems belong to + get: + summary: returns unique zone tags + tags: + - Zones + operationId: PlaceOS::Api::Zones_tags + parameters: [] + responses: + 200: + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/String' + 409: + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 503: + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 404: + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 429: + description: Too Many Requests + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 422: + description: Unprocessable Entity + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 502: + description: Bad Gateway + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 504: + description: Gateway Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 406: + description: Not Acceptable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + 408: + description: Request Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 400: + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 415: + description: Unsupported Media Type + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + /api/engine/v2/zones/{id}: + summary: Zones, the hierarchy of places (organisation, region, building, level, + area) that systems belong to + get: + summary: return the details of the zone + tags: + - Zones + operationId: PlaceOS::Api::Zones_show + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: complete + in: query + description: also return any triggers associated with the zone + example: "true" + schema: + type: boolean + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Zone' + 409: + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 503: + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 404: + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 429: + description: Too Many Requests + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 422: + description: Unprocessable Entity + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 502: + description: Bad Gateway + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 504: + description: Gateway Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 406: + description: Not Acceptable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + 408: + description: Request Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 400: + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 415: + description: Unsupported Media Type + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + put: + summary: update the details of a zone + tags: + - Zones + operationId: PlaceOS::Api::Zones_update + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Zone' + required: true + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Zone' + 409: + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 503: + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 404: + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 429: + description: Too Many Requests + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 422: + description: Unprocessable Entity + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 502: + description: Bad Gateway + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 504: + description: Gateway Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 406: + description: Not Acceptable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + 408: + description: Request Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 400: + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 415: + description: Unsupported Media Type + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + delete: + summary: remove a zone and any children zones + tags: + - Zones + operationId: PlaceOS::Api::Zones_destroy + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + 202: + description: Accepted + 409: + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 503: + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 404: + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 429: + description: Too Many Requests + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 422: + description: Unprocessable Entity + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 502: + description: Bad Gateway + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 504: + description: Gateway Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 406: + description: Not Acceptable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + 408: + description: Request Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 400: + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 415: + description: Unsupported Media Type + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + patch: + summary: update the details of a zone + tags: + - Zones + operationId: PlaceOS::Api::Zones_update{2} + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Zone' + required: true + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Model__Zone' + 409: + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 503: + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 404: + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 429: + description: Too Many Requests + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 422: + description: Unprocessable Entity + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 502: + description: Bad Gateway + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 504: + description: Gateway Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 406: + description: Not Acceptable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + 408: + description: Request Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 400: + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 415: + description: Unsupported Media Type + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + /api/engine/v2/zones/{id}/metadata: + summary: Zones, the hierarchy of places (organisation, region, building, level, + area) that systems belong to + get: + summary: return metadata associcated with the selected zone + tags: + - Zones + operationId: PlaceOS::Api::Zones_metadata + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: name + in: query + description: only return the metadata key we are interested in + example: workplace-config + schema: + type: string + nullable: true + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Hash_String__PlaceOS__Model__Metadata__Interface_' + 409: + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 503: + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 404: + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 429: + description: Too Many Requests + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 422: + description: Unprocessable Entity + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 502: + description: Bad Gateway + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 504: + description: Gateway Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 406: + description: Not Acceptable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + 408: + description: Request Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 400: + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 415: + description: Unsupported Media Type + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + /api/engine/v2/zones/{id}/triggers: + summary: Zones, the hierarchy of places (organisation, region, building, level, + area) that systems belong to + get: + summary: Return triggers attached to current zone + tags: + - Zones + operationId: PlaceOS::Api::Zones_trigger_instances + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + 200: + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PlaceOS__Model__Trigger' + 409: + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 503: + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 403: + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 404: + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 429: + description: Too Many Requests + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ImageGenError' + 422: + description: Unprocessable Entity + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 502: + description: Bad Gateway + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 504: + description: Gateway Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 406: + description: Not Acceptable + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + 408: + description: Request Timeout + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__CommonError' + 400: + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ParameterError' + 415: + description: Unsupported Media Type + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Application__ContentError' + /api/engine/v2/zones/{id}/exec/{module_slug}/{method}: + summary: Zones, the hierarchy of places (organisation, region, building, level, + area) that systems belong to + post: + summary: Execute a method on a module across all systems in a Zone + tags: + - Zones + operationId: PlaceOS::Api::Zones_zone_execute + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Array_JSON__Any_' + required: true + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: module_slug + in: path + description: the combined module class and index, index is optional and defaults + to 1 + example: Display_2 + required: true + schema: + type: string + - name: method + in: path + description: the method to execute on the module + example: power + required: true + schema: + type: string + responses: + 200: + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PlaceOS__Api__Zones__ZoneExecResponse' 409: description: Conflict content: @@ -43486,6 +45536,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true id: type: string nullable: true @@ -44139,6 +46193,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true id: type: string nullable: true @@ -44295,11 +46353,15 @@ components: - function - description - parameters - NamedTuple_function_params__String_: + NamedTuple_function_params__Hash_String__JSON__Any____String_: type: object properties: function_params: - type: string + anyOf: + - type: object + additionalProperties: + type: object + - type: string required: - function_params JSON__Any: @@ -44625,6 +46687,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true id: type: string nullable: true @@ -44847,6 +46913,10 @@ components: ignore_startstop: type: boolean nullable: true + organisation_id: + type: string + format: uuid + nullable: true has_runtime_error: type: boolean nullable: true @@ -44950,6 +47020,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true place_id: type: string nullable: true @@ -45135,6 +47209,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true control_system_id: type: string nullable: true @@ -45317,6 +47395,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true timezone: type: object nullable: true @@ -45466,6 +47548,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true place_id: type: string nullable: true @@ -45651,6 +47737,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true control_system_id: type: string nullable: true @@ -45807,6 +47897,10 @@ components: ignore_startstop: type: boolean nullable: true + organisation_id: + type: string + format: uuid + nullable: true has_runtime_error: type: boolean nullable: true @@ -45910,6 +48004,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true place_id: type: string nullable: true @@ -46095,6 +48193,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true control_system_id: type: string nullable: true @@ -46602,6 +48704,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true place_id: type: string nullable: true @@ -46787,6 +48893,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true control_system_id: type: string nullable: true @@ -46970,6 +49080,10 @@ components: online: type: boolean nullable: true + organisation_id: + type: string + format: uuid + nullable: true id: type: string nullable: true @@ -47008,6 +49122,10 @@ components: nullable: true online: type: boolean + organisation_id: + type: string + format: uuid + nullable: true x_api_key: type: string required: @@ -47266,6 +49384,63 @@ components: type: object additionalProperties: type: object + PlaceOS__Model__Grant: + type: object + properties: + created_at: + type: string + format: date-time + nullable: true + updated_at: + type: string + format: date-time + nullable: true + id: + type: string + format: uuid + nullable: true + user_id: + type: string + nullable: true + scope_type: + type: string + nullable: true + scope_id: + type: string + nullable: true + permissions: + type: integer + format: Int32 + nullable: true + expires_at: + type: string + format: date-time + nullable: true + granted_by: + type: string + nullable: true + required: [] + PlaceOS__Api__Grants__GrantBody: + type: object + properties: + user_id: + type: string + scope_type: + type: string + scope_id: + type: string + permissions: + type: integer + format: Int32 + expires_at: + type: string + format: date-time + nullable: true + required: + - user_id + - scope_type + - scope_id + - permissions PlaceOS__Model__Group: type: object properties: @@ -47965,6 +50140,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true place_id: type: string nullable: true @@ -48150,6 +50329,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true control_system_id: type: string nullable: true @@ -48340,6 +50523,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true place_id: type: string nullable: true @@ -48525,6 +50712,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true control_system_id: type: string nullable: true @@ -48856,6 +51047,233 @@ components: format: Int64 nullable: true required: [] + PlaceOS__Api__Organisations__CurrentReach: + type: object + properties: + reach: + type: string + enforcing: + type: boolean + organisation: + type: object + properties: + created_at: + type: string + format: date-time + nullable: true + updated_at: + type: string + format: date-time + nullable: true + id: + type: string + format: uuid + nullable: true + name: + type: string + nullable: true + description: + type: string + nullable: true + payer: + type: string + nullable: true + config: + type: object + additionalProperties: + type: object + nullable: true + partner_staff: + type: boolean + nullable: true + partner_id: + type: string + format: uuid + nullable: true + required: [] + nullable: true + partner: + type: object + properties: + created_at: + type: string + format: date-time + nullable: true + updated_at: + type: string + format: date-time + nullable: true + id: + type: string + format: uuid + nullable: true + name: + type: string + nullable: true + description: + type: string + nullable: true + management: + type: boolean + nullable: true + config: + type: object + additionalProperties: + type: object + nullable: true + parent_id: + type: string + format: uuid + nullable: true + required: [] + nullable: true + organisations: + type: array + items: + type: object + properties: + created_at: + type: string + format: date-time + nullable: true + updated_at: + type: string + format: date-time + nullable: true + id: + type: string + format: uuid + nullable: true + name: + type: string + nullable: true + description: + type: string + nullable: true + payer: + type: string + nullable: true + config: + type: object + additionalProperties: + type: object + nullable: true + partner_staff: + type: boolean + nullable: true + partner_id: + type: string + format: uuid + nullable: true + required: [] + nullable: true + required: + - reach + - enforcing + description: 'What the caller can reach: the level, their own organisation and + partner, + + and the organisations in reach. Backoffice reads this once at login.' + PlaceOS__Model__Organisation: + type: object + properties: + created_at: + type: string + format: date-time + nullable: true + updated_at: + type: string + format: date-time + nullable: true + id: + type: string + format: uuid + nullable: true + name: + type: string + nullable: true + description: + type: string + nullable: true + payer: + type: string + nullable: true + config: + type: object + additionalProperties: + type: object + nullable: true + partner_staff: + type: boolean + nullable: true + partner_id: + type: string + format: uuid + nullable: true + required: [] + PlaceOS__Api__Organisations__ClaimBody: + type: object + properties: + zone_ids: + type: array + items: + type: string + required: + - zone_ids + PlaceOS__Api__Organisations__ClaimResult: + type: object + properties: + zones: + type: integer + format: Int64 + systems: + type: integer + format: Int64 + modules: + type: integer + format: Int64 + triggers: + type: integer + format: Int64 + required: + - zones + - systems + - modules + - triggers + PlaceOS__Model__Partner: + type: object + properties: + created_at: + type: string + format: date-time + nullable: true + updated_at: + type: string + format: date-time + nullable: true + id: + type: string + format: uuid + nullable: true + name: + type: string + nullable: true + description: + type: string + nullable: true + management: + type: boolean + nullable: true + config: + type: object + additionalProperties: + type: object + nullable: true + parent_id: + type: string + format: uuid + nullable: true + required: [] PlaceOS__Model__PendingMail: type: object properties: @@ -50229,6 +52647,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true timezone: type: object nullable: true @@ -50378,6 +52800,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true place_id: type: string nullable: true @@ -50563,6 +52989,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true control_system_id: type: string nullable: true @@ -50719,6 +53149,10 @@ components: ignore_startstop: type: boolean nullable: true + organisation_id: + type: string + format: uuid + nullable: true has_runtime_error: type: boolean nullable: true @@ -50822,6 +53256,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true place_id: type: string nullable: true @@ -51007,6 +53445,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true control_system_id: type: string nullable: true @@ -51900,6 +54342,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true control_system_id: type: string nullable: true @@ -53371,6 +55817,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true timezone: type: object nullable: true @@ -53520,6 +55970,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true place_id: type: string nullable: true @@ -53705,6 +56159,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true control_system_id: type: string nullable: true @@ -53861,6 +56319,10 @@ components: ignore_startstop: type: boolean nullable: true + organisation_id: + type: string + format: uuid + nullable: true has_runtime_error: type: boolean nullable: true @@ -53964,6 +56426,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true place_id: type: string nullable: true @@ -54149,6 +56615,10 @@ components: items: type: string nullable: true + organisation_id: + type: string + format: uuid + nullable: true control_system_id: type: string nullable: true diff --git a/shard.lock b/shard.lock index baeab6a2..9ecda821 100644 --- a/shard.lock +++ b/shard.lock @@ -13,6 +13,10 @@ shards: git: https://github.com/spider-gazelle/active-model.git version: 4.5.0 + ameba: + git: https://github.com/crystal-ameba/ameba.git + version: 1.7.0 + awscr-s3: git: https://github.com/taylorfinnell/awscr-s3.git version: 0.10.0 @@ -191,7 +195,7 @@ shards: place_calendar: git: https://github.com/placeos/calendar.git - version: 4.30.0 + version: 4.30.1 placeos-compiler: git: https://github.com/placeos/compiler.git @@ -217,9 +221,9 @@ shards: git: https://github.com/place-labs/log-backend.git version: 0.13.0 - placeos-models: + placeos-models: # Overridden git: https://github.com/placeos/models.git - version: 9.118.1 + version: 9.118.1+git.commit.e21d7904b1d9fe12b7852e9e835a50489647e5f6 placeos-resource: git: https://github.com/place-labs/resource.git diff --git a/shard.override.yml b/shard.override.yml index 4667f5a5..2a60a444 100644 --- a/shard.override.yml +++ b/shard.override.yml @@ -18,7 +18,7 @@ dependencies: github: place-labs/ulid branch: master - # uncomment to test different model branches - # placeos-models: - # github: placeos/models - # branch: add-signage-groups + # PPT-526: build against the multi-tenancy data layer until models #332 merges + placeos-models: + github: placeos/models + branch: PPT-526-partner-client diff --git a/spec/controllers/tenancy_spec.cr b/spec/controllers/tenancy_spec.cr new file mode 100644 index 00000000..35fc9aad --- /dev/null +++ b/spec/controllers/tenancy_spec.cr @@ -0,0 +1,293 @@ +require "../helper" + +private def tenancy_get(path, user) + client.get(path, headers: PlaceOS::Api::Spec::Tenancy.headers(user)) +end + +private def tenancy_post(path, user, body) + client.post(path, headers: PlaceOS::Api::Spec::Tenancy.headers(user), body: body.to_json) +end + +module PlaceOS::Api + # PPT-526: who can see and touch what across organisations on a shared cluster + describe "tenancy" do + fixture = nil.as(Spec::Tenancy::Fixture?) + build = -> { fixture = Spec::Tenancy.build } + + before_each { Utils::Tenancy.enforce = true } + after_each do + Utils::Tenancy.enforce = false + if f = fixture + Spec::Tenancy.teardown(f) + fixture = nil + end + end + + describe "reach" do + it "resolves cluster, partner and organisation reach" do + f = build.call.not_nil! + + cluster = JSON.parse(tenancy_get("#{Organisations.base_route}current", f.placeos.admin).body) + cluster["reach"].should eq "cluster" + cluster["enforcing"].as_bool.should be_true + cluster["organisations"]?.should be_nil + + partner = JSON.parse(tenancy_get("#{Organisations.base_route}current", f.ntt_staff.admin).body) + partner["reach"].should eq "partner" + partner["organisations"].as_a.map(&.["id"].to_s).sort!.should eq [f.ntt_staff.organisation.id.to_s, f.acadian.organisation.id.to_s].sort + + org = JSON.parse(tenancy_get("#{Organisations.base_route}current", f.acadian.admin).body) + org["reach"].should eq "organisation" + org["organisations"].as_a.map(&.["id"].to_s).should eq [f.acadian.organisation.id.to_s] + + plain = JSON.parse(tenancy_get("#{Organisations.base_route}current", f.placeos.user).body) + plain["reach"].should eq "organisation" + end + end + + describe "estate lists" do + it "scopes zones, systems and modules to the caller's organisations" do + f = build.call.not_nil! + + zones = Spec::Tenancy.ids(tenancy_get("#{Zones.base_route}?limit=500", f.acadian.admin).body) + zones.should contain(f.acadian.org_zone.id) + zones.should_not contain(f.ucla.org_zone.id) + zones.should_not contain(f.unowned_zone.id) + + partner_zones = Spec::Tenancy.ids(tenancy_get("#{Zones.base_route}?limit=500", f.ntt_staff.support).body) + partner_zones.should contain(f.acadian.org_zone.id) + partner_zones.should contain(f.ntt_staff.org_zone.id) + partner_zones.should_not contain(f.ucla.org_zone.id) + + cluster_zones = Spec::Tenancy.ids(tenancy_get("#{Zones.base_route}?limit=500", f.placeos.admin).body) + cluster_zones.should contain(f.ucla.org_zone.id) + cluster_zones.should contain(f.unowned_zone.id) + + systems = Spec::Tenancy.ids(tenancy_get("#{Systems.base_route}?limit=500", f.ucla.admin).body) + systems.should eq [f.ucla.system.id] + + modules = Spec::Tenancy.ids(tenancy_get("#{Modules.base_route}?limit=500", f.ucla.admin).body) + modules.should eq [f.ucla.mod.id] + + tenancy_get("#{Zones.base_route}tags", f.acadian.admin).status_code.should eq 200 + end + + it "answers 404 for another organisation's rows and 200 for its own" do + f = build.call.not_nil! + + tenancy_get("#{Zones.base_route}#{f.ucla.org_zone.id}", f.acadian.admin).status_code.should eq 404 + tenancy_get("#{Zones.base_route}#{f.acadian.org_zone.id}", f.acadian.admin).status_code.should eq 200 + tenancy_get("#{Systems.base_route}#{f.ucla.system.id}", f.acadian.admin).status_code.should eq 404 + tenancy_get("#{Modules.base_route}#{f.ucla.mod.id}", f.acadian.admin).status_code.should eq 404 + tenancy_get("#{Zones.base_route}#{f.unowned_zone.id}", f.acadian.admin).status_code.should eq 404 + + # partner staff reach their client, not a stranger + tenancy_get("#{Zones.base_route}#{f.acadian.org_zone.id}", f.ntt_staff.admin).status_code.should eq 200 + tenancy_get("#{Zones.base_route}#{f.ucla.org_zone.id}", f.ntt_staff.admin).status_code.should eq 404 + + # cluster admins reach everything, unowned rows included + tenancy_get("#{Zones.base_route}#{f.ucla.org_zone.id}", f.placeos.admin).status_code.should eq 200 + tenancy_get("#{Zones.base_route}#{f.unowned_zone.id}", f.placeos.admin).status_code.should eq 200 + end + + it "narrows a list to one organisation on request" do + f = build.call.not_nil! + + narrowed = Spec::Tenancy.ids(tenancy_get("#{Zones.base_route}?organisation_id=#{f.ucla.organisation.id}&limit=500", f.placeos.admin).body) + narrowed.should contain(f.ucla.org_zone.id) + narrowed.should_not contain(f.acadian.org_zone.id) + narrowed.should_not contain(f.unowned_zone.id) + + partner_narrowed = Spec::Tenancy.ids(tenancy_get("#{Systems.base_route}?organisation_id=#{f.acadian.organisation.id}", f.ntt_staff.admin).body) + partner_narrowed.should eq [f.acadian.system.id] + + tenancy_get("#{Zones.base_route}?organisation_id=#{f.ucla.organisation.id}", f.acadian.admin).status_code.should eq 404 + + users = Spec::Tenancy.ids(tenancy_get("#{Users.base_route}?organisation_id=#{f.acadian.organisation.id}", f.placeos.admin).body) + users.should contain(f.acadian.user.id) + users.should_not contain(f.ucla.user.id) + end + + it "only logs when enforcement is off" do + f = build.call.not_nil! + Utils::Tenancy.enforce = false + tenancy_get("#{Zones.base_route}#{f.ucla.org_zone.id}", f.acadian.admin).status_code.should eq 200 + end + end + + describe "estate writes" do + it "owns new zones through the caller or the parent" do + f = build.call.not_nil! + + root = tenancy_post(Zones.base_route, f.acadian.admin, {name: "Acadian root #{random_name}", tags: ["building"]}) + root.status_code.should eq 201 + JSON.parse(root.body)["organisation_id"].to_s.should eq f.acadian.organisation.id.to_s + + child = tenancy_post(Zones.base_route, f.acadian.admin, {name: "Level #{random_name}", parent_id: f.acadian.building.id}) + child.status_code.should eq 201 + JSON.parse(child.body)["organisation_id"].to_s.should eq f.acadian.organisation.id.to_s + + foreign_parent = tenancy_post(Zones.base_route, f.acadian.admin, {name: "Sneaky #{random_name}", parent_id: f.ucla.building.id}) + foreign_parent.status_code.should eq 404 + + named = client.post("#{Zones.base_route}?organisation_id=#{f.ucla.organisation.id}", headers: Spec::Tenancy.headers(f.placeos.admin), body: {name: "UCLA root #{random_name}", tags: ["building"]}.to_json) + named.status_code.should eq 201 + JSON.parse(named.body)["organisation_id"].to_s.should eq f.ucla.organisation.id.to_s + + client.post("#{Zones.base_route}?organisation_id=#{f.ucla.organisation.id}", headers: Spec::Tenancy.headers(f.acadian.admin), body: {name: "Not mine #{random_name}"}.to_json).status_code.should eq 403 + end + + it "owns new systems through their zones and refuses mixed zones" do + f = build.call.not_nil! + + own = tenancy_post(Systems.base_route, f.acadian.admin, {name: "Room #{random_name}", zones: [f.acadian.building.id]}) + own.status_code.should eq 201 + JSON.parse(own.body)["organisation_id"].to_s.should eq f.acadian.organisation.id.to_s + + tenancy_post(Systems.base_route, f.acadian.admin, {name: "Room #{random_name}", zones: [f.ucla.building.id]}).status_code.should eq 404 + + mixed = tenancy_post(Systems.base_route, f.placeos.admin, {name: "Room #{random_name}", zones: [f.acadian.building.id, f.ucla.building.id]}) + mixed.status_code.should eq 422 + end + + it "keeps modules inside the system's organisation" do + f = build.call.not_nil! + + attach = client.put("#{Systems.base_route}#{f.acadian.system.id}/module/#{f.ucla.mod.id}", headers: Spec::Tenancy.headers(f.acadian.admin)) + attach.status_code.should eq 404 + + attach = client.put("#{Systems.base_route}#{f.ucla.system.id}/module/#{f.ucla.mod.id}", headers: Spec::Tenancy.headers(f.ucla.admin)) + attach.status_code.should eq 200 + end + end + + describe "domains and users" do + it "lists and creates domains within reach" do + f = build.call.not_nil! + + domains = Spec::Tenancy.ids(tenancy_get("#{Domains.base_route}?limit=500", f.acadian.admin).body) + domains.should eq [f.acadian.authority.id] + tenancy_get("#{Domains.base_route}#{f.ucla.authority.id}", f.acadian.admin).status_code.should eq 404 + + created = tenancy_post(Domains.base_route, f.acadian.admin, {name: "Acadian two", domain: "two.acadian.test"}) + created.status_code.should eq 201 + JSON.parse(created.body)["organisation_id"].to_s.should eq f.acadian.organisation.id.to_s + + staff = client.post("#{Domains.base_route}?organisation_id=#{f.ucla.organisation.id}", headers: Spec::Tenancy.headers(f.placeos.admin), body: {name: "UCLA two", domain: "two.ucla.test"}.to_json) + staff.status_code.should eq 201 + JSON.parse(staff.body)["organisation_id"].to_s.should eq f.ucla.organisation.id.to_s + + moved = client.patch("#{Domains.base_route}#{f.acadian.authority.id}?organisation_id=#{f.ucla.organisation.id}", headers: Spec::Tenancy.headers(f.acadian.admin), body: {name: "renamed"}.to_json) + moved.status_code.should eq 403 + end + + it "lists users within reach only" do + f = build.call.not_nil! + + users = Spec::Tenancy.ids(tenancy_get("#{Users.base_route}?limit=500", f.acadian.admin).body) + users.should contain(f.acadian.user.id) + users.should_not contain(f.ucla.user.id) + + tenancy_get("#{Users.base_route}?authority_id=#{f.ucla.authority.id}", f.acadian.admin).status_code.should eq 404 + tenancy_get("#{Users.base_route}#{f.ucla.user.id}", f.acadian.admin).status_code.should eq 404 + + partner_users = Spec::Tenancy.ids(tenancy_get("#{Users.base_route}?limit=500", f.ntt_staff.admin).body) + partner_users.should contain(f.acadian.user.id) + partner_users.should_not contain(f.ucla.user.id) + end + end + + describe "last organisation admin" do + it "refuses an organisation admin removing their last admin, allows cluster staff" do + f = build.call.not_nil! + + client.delete("#{Users.base_route}#{f.acadian.admin.id}", headers: Spec::Tenancy.headers(f.acadian.admin)).status_code.should eq 403 + + second = Model::Generator.user(f.acadian.authority, admin: true).save! + client.delete("#{Users.base_route}#{second.id}", headers: Spec::Tenancy.headers(f.acadian.admin)).status_code.should eq 202 + + client.delete("#{Users.base_route}#{f.acadian.admin.id}", headers: Spec::Tenancy.headers(f.placeos.admin)).status_code.should eq 202 + end + end + + describe "cluster-only resources" do + it "hides brokers and trigger templates' writes from organisation admins" do + f = build.call.not_nil! + + tenancy_get(Brokers.base_route, f.acadian.admin).status_code.should eq 403 + tenancy_get(Brokers.base_route, f.placeos.admin).status_code.should eq 200 + + template = tenancy_post(Triggers.base_route, f.acadian.admin, {name: "Template #{random_name}"}) + template.status_code.should eq 403 + template = tenancy_post(Triggers.base_route, f.placeos.admin, {name: "Template #{random_name}"}) + template.status_code.should eq 201 + + # templates are readable by every organisation + templates = Spec::Tenancy.ids(tenancy_get("#{Triggers.base_route}?limit=500", f.acadian.admin).body) + templates.should contain(JSON.parse(template.body)["id"].to_s) + + bound = tenancy_post(Triggers.base_route, f.acadian.admin, {name: "Bound #{random_name}", control_system_id: f.acadian.system.id}) + bound.status_code.should eq 201 + JSON.parse(bound.body)["organisation_id"].to_s.should eq f.acadian.organisation.id.to_s + Spec::Tenancy.ids(tenancy_get("#{Triggers.base_route}?limit=500", f.ucla.admin).body).should_not contain(JSON.parse(bound.body)["id"].to_s) + end + end + + describe "organisations, partners and grants" do + it "lets cluster admins manage the hierarchy and others read their own" do + f = build.call.not_nil! + + partner = tenancy_post(Partners.base_route, f.placeos.admin, {name: "Integrator #{random_name}"}) + partner.status_code.should eq 201 + partner_id = JSON.parse(partner.body)["id"].to_s + + organisation = client.post("#{Organisations.base_route}?partner_staff=true", headers: Spec::Tenancy.headers(f.placeos.admin), body: {name: "Integrator staff #{random_name}", partner_id: partner_id}.to_json) + organisation.status_code.should eq 201 + JSON.parse(organisation.body)["partner_staff"].as_bool.should be_true + + tenancy_post(Partners.base_route, f.acadian.admin, {name: "Nope #{random_name}"}).status_code.should eq 403 + + Spec::Tenancy.ids(tenancy_get("#{Organisations.base_route}?limit=500", f.acadian.admin).body).should eq [f.acadian.organisation.id.to_s] + Spec::Tenancy.ids(tenancy_get("#{Partners.base_route}?limit=500", f.acadian.admin).body).should eq [f.ntt.id.to_s] + tenancy_get("#{Partners.base_route}#{f.management.id}", f.acadian.admin).status_code.should eq 404 + + client.patch("#{Organisations.base_route}#{f.acadian.organisation.id}", headers: Spec::Tenancy.headers(f.acadian.admin), body: {name: "renamed"}.to_json).status_code.should eq 403 + end + + it "widens reach through live grants" do + f = build.call.not_nil! + + tenancy_get("#{Zones.base_route}#{f.acadian.org_zone.id}", f.ucla.admin).status_code.should eq 404 + + granted = tenancy_post(Grants.base_route, f.placeos.admin, {user_id: f.ucla.admin.id, scope_type: "organisation", scope_id: f.acadian.organisation.id.to_s, permissions: 1}) + granted.status_code.should eq 201 + tenancy_get("#{Zones.base_route}#{f.acadian.org_zone.id}", f.ucla.admin).status_code.should eq 200 + + client.delete("#{Grants.base_route}#{JSON.parse(granted.body)["id"]}", headers: Spec::Tenancy.headers(f.placeos.admin)).status_code.should eq 202 + tenancy_get("#{Zones.base_route}#{f.acadian.org_zone.id}", f.ucla.admin).status_code.should eq 404 + + expired = tenancy_post(Grants.base_route, f.placeos.admin, {user_id: f.ucla.admin.id, scope_type: "organisation", scope_id: f.acadian.organisation.id.to_s, permissions: 1, expires_at: 1.hour.ago}) + expired.status_code.should eq 201 + tenancy_get("#{Zones.base_route}#{f.acadian.org_zone.id}", f.ucla.admin).status_code.should eq 404 + + # an organisation admin may grant into their own organisation but not a stranger's + tenancy_post(Grants.base_route, f.acadian.admin, {user_id: f.ucla.support.id, scope_type: "organisation", scope_id: f.acadian.organisation.id.to_s}).status_code.should eq 201 + tenancy_post(Grants.base_route, f.acadian.admin, {user_id: f.acadian.admin.id, scope_type: "organisation", scope_id: f.ucla.organisation.id.to_s}).status_code.should eq 403 + end + + it "claims an unowned zone tree for an organisation" do + f = build.call.not_nil! + + claimed = tenancy_post("#{Organisations.base_route}#{f.ucla.organisation.id}/claim", f.placeos.admin, {zone_ids: [f.unowned_zone.id]}) + claimed.status_code.should eq 200 + result = JSON.parse(claimed.body) + result["zones"].should eq 1 + result["systems"].should eq 1 + + Model::Zone.find!(f.unowned_zone.id.as(String)).organisation_id.should eq f.ucla.organisation.id + Model::ControlSystem.find!(f.unowned_system.id.as(String)).organisation_id.should eq f.ucla.organisation.id + tenancy_get("#{Zones.base_route}#{f.unowned_zone.id}", f.ucla.admin).status_code.should eq 200 + end + end + end +end diff --git a/spec/helper.cr b/spec/helper.cr index 489ddfb0..cac9c280 100644 --- a/spec/helper.cr +++ b/spec/helper.cr @@ -94,6 +94,13 @@ def clear_tables {% end %} ).get {% end %} + + # Tenancy rows go last: the estate tables above RESTRICT on organisations. + PlaceOS::Model::Edge.clear + PlaceOS::Model::Broker.clear + PlaceOS::Model::Grant.clear + PlaceOS::Model::Organisation.clear + PlaceOS::Model::Partner.clear end def random_name diff --git a/spec/migration/Dockerfile b/spec/migration/Dockerfile index 11531ab8..76b4070f 100644 --- a/spec/migration/Dockerfile +++ b/spec/migration/Dockerfile @@ -5,7 +5,8 @@ WORKDIR /app COPY . /app # NOTE:: add `--branch my-new-model-branch` if you want to test a particular branch -RUN git clone https://github.com/PlaceOS/models +# PPT-526: the tenancy migrations live on this branch until models #332 merges +RUN git clone --branch PPT-526-partner-client https://github.com/PlaceOS/models RUN mv ./models/migration/db ./db ENV PATH /app/bin:$PATH diff --git a/spec/spec_helpers/tenancy.cr b/spec/spec_helpers/tenancy.cr new file mode 100644 index 00000000..15e5922c --- /dev/null +++ b/spec/spec_helpers/tenancy.cr @@ -0,0 +1,146 @@ +require "placeos-models/spec/generator" + +module PlaceOS::Api::Spec::Tenancy + # One customer organisation with a domain, three users and a small estate + record Org, + organisation : Model::Organisation, + authority : Model::Authority, + admin : Model::User, + support : Model::User, + user : Model::User, + org_zone : Model::Zone, + building : Model::Zone, + system : Model::ControlSystem, + mod : Model::Module + + # The shape of a shared cluster: PlaceOS staff on localhost (cluster reach), + # NTT as a partner with its own staff organisation and one client (Acadian), + # UCLA as a direct customer, and an unowned zone tree. + record Fixture, + management : Model::Partner, + placeos : Org, + ntt : Model::Partner, + ntt_staff : Org, + acadian : Org, + ucla : Org, + unowned_zone : Model::Zone, + unowned_system : Model::ControlSystem + + def self.build : Fixture + management = Model::Generator.partner(management: true).save! + ntt = Model::Generator.partner.save! + + placeos = org("localhost", Model::Generator.organisation(partner: management, partner_staff: true).save!) + ntt_staff = org("ntt.test", Model::Generator.organisation(partner: ntt, partner_staff: true).save!) + acadian = org("acadian.test", Model::Generator.organisation(partner: ntt).save!) + ucla = org("ucla.test", Model::Generator.organisation.save!) + + unowned_zone = Model::Generator.zone.tap { |z| z.name = "Unowned #{random_name}"; z.tags = Set{"org"} }.save! + unowned_system = Model::Generator.control_system.tap do |sys| + sys.name = "Unowned room #{random_name}" + sys.zones = [unowned_zone.id.as(String)] + end.save! + + Fixture.new(management, placeos, ntt, ntt_staff, acadian, ucla, unowned_zone, unowned_system) + end + + def self.org(domain : String, organisation : Model::Organisation) : Org + authority = Model::Authority.find_by_domain(domain) || Model::Generator.authority(domain) + authority.domain = domain + authority.organisation_id = organisation.id + authority.save! + + org_zone = Model::Generator.zone.tap do |z| + z.name = "ORG #{organisation.name}" + z.tags = Set{"org"} + z.organisation_id = organisation.id + end.save! + authority.config_will_change! + authority.config["org_zone"] = JSON::Any.new(org_zone.id.as(String)) + authority.save! + + building = Model::Generator.zone.tap do |z| + z.name = "Building #{organisation.name}" + z.tags = Set{"building"} + z.parent_id = org_zone.id + z.organisation_id = organisation.id + end.save! + + system = Model::Generator.control_system.tap do |sys| + sys.name = "Room #{organisation.name}" + sys.zones = [org_zone.id.as(String), building.id.as(String)] + sys.organisation_id = organisation.id + end.save! + + mod = Model::Generator.module(control_system: system).tap(&.organisation_id = organisation.id).save! + system.modules = [mod.id.as(String)] + system.save! + + Org.new( + organisation: organisation, + authority: authority, + admin: Model::Generator.user(authority, admin: true).save!, + support: Model::Generator.user(authority, support: true).save!, + user: Model::Generator.user(authority).save!, + org_zone: org_zone, + building: building, + system: system, + mod: mod, + ) + end + + # Removes everything a fixture and its example created. The suite only + # clears tables once, so each example must leave the database as it found + # it: other specs list zones and users without a page size. + def self.teardown(f : Fixture) + none = [] of ::PgORM::Value + Model::Module.where("organisation_id IS NOT NULL", none).to_a.each { |row| row.destroy rescue nil } + Model::ControlSystem.where("organisation_id IS NOT NULL", none).to_a.each { |row| row.destroy rescue nil } + Model::Trigger.where("organisation_id IS NOT NULL OR name LIKE 'Template %'", none).to_a.each { |row| row.destroy rescue nil } + Model::Edge.where("organisation_id IS NOT NULL", none).to_a.each { |row| row.destroy rescue nil } + Model::Zone.where("organisation_id IS NOT NULL", none).to_a + .sort_by { |zone| zone.parent_id.presence ? 0 : 1 } + .each { |row| row.destroy rescue nil } + [f.unowned_system, f.unowned_zone].each { |row| row.destroy rescue nil } + + Model::Grant.clear + # fixture domains and anything an example created under them; localhost stays + Model::Authority.where("domain LIKE '%.test'", none).to_a.each { |row| row.destroy rescue nil } + [f.placeos.user, f.placeos.support, f.placeos.admin].each { |row| row.destroy rescue nil } + localhost = Model::Authority.find?(f.placeos.authority.id.as(String)) + if localhost + localhost.organisation_id = nil + localhost.save! + end + Model::Organisation.clear + Model::Partner.clear + end + + # Bearer + Host headers for a user on their own domain + def self.headers(user : Model::User) : HTTP::Headers + authority = user.authority.as(Model::Authority) + permissions = case {user.support, user.sys_admin} + when {true, true} then Model::UserJWT::Permissions::AdminSupport + when {true, false} then Model::UserJWT::Permissions::Support + when {false, true} then Model::UserJWT::Permissions::Admin + else Model::UserJWT::Permissions::User + end + jwt = Model::UserJWT.new( + iss: Model::UserJWT::ISSUER, + iat: 5.minutes.ago, + exp: 1.hour.from_now, + domain: authority.domain, + id: user.id.as(String), + user: Model::UserJWT::Metadata.new(name: user.name.as(String), email: user.email.to_s, permissions: permissions), + ) + HTTP::Headers{ + "Authorization" => "Bearer #{jwt.encode}", + "Content-Type" => "application/json", + "Host" => authority.domain, + } + end + + def self.ids(body : String) : Array(String) + Array(Hash(String, JSON::Any)).from_json(body).map(&.["id"].to_s) + end +end diff --git a/src/constants.cr b/src/constants.cr index d7416cdf..18766f3f 100644 --- a/src/constants.cr +++ b/src/constants.cr @@ -9,6 +9,9 @@ module PlaceOS::Api BUILD_TIME = {{ system("date -u").stringify }} BUILD_COMMIT = {{ env("PLACE_COMMIT") || "DEV" }} + # PPT-526: refuse cross-organisation access (true) or only log what would be refused (false) + PLACE_TENANCY_ENFORCE = ENV["PLACE_TENANCY_ENFORCE"]?.try(&.downcase).in?("true", "1", "yes") || false + PLACE_DISPATCH_HOST = ENV["PLACE_DISPATCH_HOST"]? || "dispatch" PLACE_DISPATCH_PORT = (ENV["PLACE_DISPATCH_PORT"]? || "3000").to_i diff --git a/src/placeos-rest-api/controllers/api_keys.cr b/src/placeos-rest-api/controllers/api_keys.cr index 9c954258..c7ec765c 100644 --- a/src/placeos-rest-api/controllers/api_keys.cr +++ b/src/placeos-rest-api/controllers/api_keys.cr @@ -19,7 +19,9 @@ module PlaceOS::Api def find_current_api_key(id : String) Log.context.set(api_key: id) # Find will raise a 404 (not found) if there is an error - @current_api_key = ::PlaceOS::Model::ApiKey.find!(id) + api_key = ::PlaceOS::Model::ApiKey.find!(id) + ensure_authority_reach!(api_key.authority_id, "api key") + @current_api_key = api_key end getter! current_api_key : ::PlaceOS::Model::ApiKey @@ -33,9 +35,10 @@ module PlaceOS::Api authority_id : String? = nil, ) : Array(::PlaceOS::Model::ApiKey::PublicResponse) # PG full-text search (PPT-2644) - query = ::PlaceOS::Model::ApiKey.all + query = scope_authorities(::PlaceOS::Model::ApiKey.all) if authority = authority_id + ensure_authority_reach!(authority, "domain") query = query.where(authority_id: authority) end @@ -61,6 +64,9 @@ module PlaceOS::Api # create a new API key @[AC::Route::POST("/", body: :api_key, status_code: HTTP::Status::CREATED)] def create(api_key : ::PlaceOS::Model::ApiKey) : ::PlaceOS::Model::ApiKey::PublicResponse + if user_id = api_key.user_id + ensure_authority_reach!(::PlaceOS::Model::User.find!(user_id).authority_id, "user") + end raise Error::ModelValidation.new(api_key.errors) unless api_key.save api_key.to_public_struct end diff --git a/src/placeos-rest-api/controllers/application.cr b/src/placeos-rest-api/controllers/application.cr index a0ad277e..5f6473df 100644 --- a/src/placeos-rest-api/controllers/application.cr +++ b/src/placeos-rest-api/controllers/application.cr @@ -38,6 +38,9 @@ module PlaceOS::Api # Helpers for defining scope checks on controller actions include Utils::Scopes + # Helpers for organisation reach (PPT-526 multi-tenancy) + include Utils::Tenancy + # for converting comma seperated lists # i.e. `"id-1,id-2,id-3"` struct ConvertStringArray diff --git a/src/placeos-rest-api/controllers/authentications.cr b/src/placeos-rest-api/controllers/authentications.cr index c56a71ec..a7ac9e63 100644 --- a/src/placeos-rest-api/controllers/authentications.cr +++ b/src/placeos-rest-api/controllers/authentications.cr @@ -22,7 +22,9 @@ module PlaceOS::Api def find_current_auth(id : String) Log.context.set({{auth_type.id.underscore}}_id: id) # Find will raise a 404 (not found) if there is an error - @current_auth = ::PlaceOS::Model::{{auth_type.id}}Authentication.find!(id) + auth = ::PlaceOS::Model::{{auth_type.id}}Authentication.find!(id) + ensure_authority_reach!(auth.authority_id, "authentication") + @current_auth = auth end getter! current_auth : ::PlaceOS::Model::{{auth_type.id}}Authentication @@ -36,9 +38,10 @@ module PlaceOS::Api authority_id : String? = nil, ) : Array(::PlaceOS::Model::{{auth_type.id}}Authentication) # PG full-text search (PPT-2644) - query = ::PlaceOS::Model::{{auth_type.id}}Authentication.all + query = scope_authorities(::PlaceOS::Model::{{auth_type.id}}Authentication.all) if authority = authority_id + ensure_authority_reach!(authority, "domain") query = query.where(authority_id: authority) end @@ -64,6 +67,7 @@ module PlaceOS::Api # creates a new authentication method @[AC::Route::POST("/", body: :auth, status_code: HTTP::Status::CREATED)] def create(auth : ::PlaceOS::Model::{{auth_type.id}}Authentication) : ::PlaceOS::Model::{{auth_type.id}}Authentication + ensure_authority_reach!(auth.authority_id, "domain") raise Error::ModelValidation.new(auth.errors) unless auth.save auth end diff --git a/src/placeos-rest-api/controllers/brokers.cr b/src/placeos-rest-api/controllers/brokers.cr index 4ab9f934..f365197a 100644 --- a/src/placeos-rest-api/controllers/brokers.cr +++ b/src/placeos-rest-api/controllers/brokers.cr @@ -15,6 +15,8 @@ module PlaceOS::Api before_action :check_admin, except: [:collection, :show] before_action :check_support, only: [:collection, :show] + # brokers are cluster infrastructure + before_action :check_cluster_admin ############################################################################################### diff --git a/src/placeos-rest-api/controllers/build_monitor.cr b/src/placeos-rest-api/controllers/build_monitor.cr index cec327d5..706251ce 100644 --- a/src/placeos-rest-api/controllers/build_monitor.cr +++ b/src/placeos-rest-api/controllers/build_monitor.cr @@ -15,6 +15,7 @@ module PlaceOS::Api before_action :can_write, only: [:cancel] before_action :check_admin, only: [:cancel] + before_action :check_cluster_admin, only: [:cancel] ############################################################################################### diff --git a/src/placeos-rest-api/controllers/cluster.cr b/src/placeos-rest-api/controllers/cluster.cr index 28334958..dd135a23 100644 --- a/src/placeos-rest-api/controllers/cluster.cr +++ b/src/placeos-rest-api/controllers/cluster.cr @@ -12,6 +12,7 @@ module PlaceOS::Api ############################################################################################### before_action :check_admin + before_action :check_cluster_admin before_action :can_read, only: [:nodes, :show] before_action :can_write, only: [:destroy] diff --git a/src/placeos-rest-api/controllers/domains.cr b/src/placeos-rest-api/controllers/domains.cr index 6d7c0392..c5647e82 100644 --- a/src/placeos-rest-api/controllers/domains.cr +++ b/src/placeos-rest-api/controllers/domains.cr @@ -20,7 +20,9 @@ module PlaceOS::Api def find_current_domain(id : String) Log.context.set(authority_id: id) # Find will raise a 404 (not found) if there is an error - @current_domain = ::PlaceOS::Model::Authority.find!(id) + domain = ::PlaceOS::Model::Authority.find!(id) + ensure_reach!(domain) + @current_domain = domain end getter! current_domain : ::PlaceOS::Model::Authority @@ -29,9 +31,13 @@ module PlaceOS::Api # list the domains @[AC::Route::GET("/")] - def index : Array(::PlaceOS::Model::Authority) + def index( + @[AC::Param::Info(description: "only rows owned by this organisation (must be within reach)", example: "0192f1c4-7a6b-7c4d-9f3e-1a2b3c4d5e6f")] + organisation_id : UUID? = nil, + ) : Array(::PlaceOS::Model::Authority) # PG full-text search (PPT-2644) - paginate_search(::PlaceOS::Model::Authority.all, ::PlaceOS::Model::Authority.table_name) + query = narrow_organisation(scope_organisations(::PlaceOS::Model::Authority.all), organisation_id) + paginate_search(query, ::PlaceOS::Model::Authority.table_name) end # skip authentication for the lookup @@ -59,16 +65,30 @@ module PlaceOS::Api # udpate a domains details @[AC::Route::PATCH("/:id", body: :domain)] @[AC::Route::PUT("/:id", body: :domain)] - def update(domain : ::PlaceOS::Model::Authority) : ::PlaceOS::Model::Authority + def update( + domain : ::PlaceOS::Model::Authority, + @[AC::Param::Info(description: "move the domain to this organisation (cluster admins only)", example: "0192f1c4-7a6b-7c4d-9f3e-1a2b3c4d5e6f")] + organisation_id : UUID? = nil, + ) : ::PlaceOS::Model::Authority current = current_domain current.assign_attributes(domain) + if organisation_id + check_cluster_admin + current.organisation_id = organisation_id + end raise Error::ModelValidation.new(current.errors) unless current.save current end - # add a new domain + # add a new domain. Organisation admins create domains in their own + # organisation; cluster admins name the organisation. @[AC::Route::POST("/", body: :domain, status_code: HTTP::Status::CREATED)] - def create(domain : ::PlaceOS::Model::Authority) : ::PlaceOS::Model::Authority + def create( + domain : ::PlaceOS::Model::Authority, + @[AC::Param::Info(description: "the organisation that owns the new domain", example: "0192f1c4-7a6b-7c4d-9f3e-1a2b3c4d5e6f")] + organisation_id : UUID? = nil, + ) : ::PlaceOS::Model::Authority + domain.organisation_id = organisation_for_new_row(organisation_id) raise Error::ModelValidation.new(domain.errors) unless domain.save domain end diff --git a/src/placeos-rest-api/controllers/drivers.cr b/src/placeos-rest-api/controllers/drivers.cr index f302fa1b..75ae0557 100644 --- a/src/placeos-rest-api/controllers/drivers.cr +++ b/src/placeos-rest-api/controllers/drivers.cr @@ -13,6 +13,8 @@ module PlaceOS::Api before_action :can_write, only: [:create, :update, :destroy, :remove] before_action :check_admin, except: [:index, :show, :readme] + # drivers are shared by every organisation; only cluster admins change them + before_action :check_cluster_admin, except: [:index, :show, :readme] # Restrict driver listing and readme access to admin/support. These # endpoints expose internal infrastructure detail (driver inventory, driver diff --git a/src/placeos-rest-api/controllers/edges.cr b/src/placeos-rest-api/controllers/edges.cr index 11a0f814..fc49cd07 100644 --- a/src/placeos-rest-api/controllers/edges.cr +++ b/src/placeos-rest-api/controllers/edges.cr @@ -34,9 +34,14 @@ module PlaceOS::Api def find_current_edge(id : String) Log.context.set(edge_id: id) # Find will raise a 404 (not found) if there is an error - @current_edge = ::PlaceOS::Model::Edge.find!(id) + edge = ::PlaceOS::Model::Edge.find!(id) + ensure_reach!(edge) + @current_edge = edge end + # Fleet-wide monitoring spans every organisation + before_action :check_cluster_admin, only: [:edges_health, :edges_errors, :edges_connections, :edges_module_failures, :edges_statistics, :edges_error_stream, :edges_module_stream, :cleanup_errors, :monitoring_summary] + getter! current_edge : ::PlaceOS::Model::Edge ############################################################################################### @@ -68,9 +73,13 @@ module PlaceOS::Api # list the edges in the system. # an edge can be thought of as a location and each edge location can have multiple nodes servicing it @[AC::Route::GET("/")] - def index : Array(::PlaceOS::Model::Edge) + def index( + @[AC::Param::Info(description: "only rows owned by this organisation (must be within reach)", example: "0192f1c4-7a6b-7c4d-9f3e-1a2b3c4d5e6f")] + organisation_id : UUID? = nil, + ) : Array(::PlaceOS::Model::Edge) # PG full-text search (PPT-2644) - paginate_search(::PlaceOS::Model::Edge.all, ::PlaceOS::Model::Edge.table_name) + query = narrow_organisation(scope_organisations(::PlaceOS::Model::Edge.all), organisation_id) + paginate_search(query, ::PlaceOS::Model::Edge.table_name) end # return the details of an edge location @@ -93,11 +102,13 @@ module PlaceOS::Api @[AC::Route::POST("/", body: :create_body, status_code: HTTP::Status::CREATED)] def create(create_body : ::PlaceOS::Model::Edge::CreateBody) : ::PlaceOS::Model::Edge::KeyResponse user = ::PlaceOS::Model::User.find!(create_body.user_id || current_user.id.as(String)) + ensure_authority_reach!(user.authority_id, "user") new_edge = ::PlaceOS::Model::Edge.for_user( user: user, name: create_body.name, description: create_body.description ) + new_edge.organisation_id = ::PlaceOS::Model::Authority.find?(user.authority_id.as(String)).try(&.organisation_id) # Ensure instance variable initialised new_edge.x_api_key diff --git a/src/placeos-rest-api/controllers/grants.cr b/src/placeos-rest-api/controllers/grants.cr new file mode 100644 index 00000000..63be43d5 --- /dev/null +++ b/src/placeos-rest-api/controllers/grants.cr @@ -0,0 +1,105 @@ +require "placeos-models/grant" + +require "./application" + +module PlaceOS::Api + # Grants: explicit, revocable, optionally time-boxed reach for a user into a + # partner, organisation or domain they do not belong to (PPT-526) + class Grants < Application + base "/api/engine/v2/grants/" + + # Scopes + ############################################################################################### + + before_action :can_read, only: [:index, :show] + before_action :can_write, only: [:create, :destroy] + + before_action :check_admin + + ############################################################################################### + + @[AC::Route::Filter(:before_action, except: [:index, :create])] + def find_current_grant(id : UUID) + Log.context.set(grant_id: id.to_s) + grant = ::PlaceOS::Model::Grant.find!(id) + ensure_scope_reach!(grant.scope_type, grant.scope_id) + @current_grant = grant + end + + getter! current_grant : ::PlaceOS::Model::Grant + + # Cluster admins manage every scope; organisation admins manage grants + # into their own organisation and its domains; partner admins request + # rather than self-grant + protected def ensure_scope_reach!(scope_type : String, scope_id : String) : Nil + return if tenancy.cluster? + own = tenancy.organisation_id + allowed = case scope_type + when ::PlaceOS::Model::Grant::SCOPE_ORGANISATION + own && own.to_s == scope_id + when ::PlaceOS::Model::Grant::SCOPE_AUTHORITY + own && ::PlaceOS::Model::Authority.find?(scope_id).try(&.organisation_id) == own + else + false + end + return if allowed + log_would_refuse("grant scope outside reach") + raise Error::Forbidden.new if Utils::Tenancy.enforce? + end + + ############################################################################################### + + # grants on a scope, or held by a user + @[AC::Route::GET("/")] + def index( + @[AC::Param::Info(description: "partner, organisation or authority", example: "organisation")] + scope_type : String? = nil, + @[AC::Param::Info(description: "the id of the partner, organisation or domain", example: "0192f1c4-7a6b-7c4d-9f3e-1a2b3c4d5e6f")] + scope_id : String? = nil, + @[AC::Param::Info(description: "grants held by this user", example: "user-1234")] + user_id : String? = nil, + ) : Array(::PlaceOS::Model::Grant) + query = ::PlaceOS::Model::Grant.all + if scope_type && scope_id + ensure_scope_reach!(scope_type, scope_id) + query = query.where(scope_type: scope_type, scope_id: scope_id) + elsif user_id + ensure_authority_reach!(::PlaceOS::Model::User.find!(user_id).authority_id, "user") + query = query.where(user_id: user_id) + else + check_cluster_admin + end + paginate_sql(query.order("created_at, id"), ::PlaceOS::Model::Grant.table_name, limit: search_limit, offset: search_offset) + end + + @[AC::Route::GET("/:id")] + def show : ::PlaceOS::Model::Grant + current_grant + end + + record GrantBody, user_id : String, scope_type : String, scope_id : String, permissions : Int32 = 1, expires_at : Time? = nil do + include JSON::Serializable + end + + @[AC::Route::POST("/", body: :body, status_code: HTTP::Status::CREATED)] + def create(body : GrantBody) : ::PlaceOS::Model::Grant + ensure_scope_reach!(body.scope_type, body.scope_id) + ::PlaceOS::Model::User.find!(body.user_id) + grant = ::PlaceOS::Model::Grant.new( + user_id: body.user_id, + scope_type: body.scope_type, + scope_id: body.scope_id, + permissions: body.permissions, + expires_at: body.expires_at, + granted_by: current_user.id, + ) + raise Error::ModelValidation.new(grant.errors) unless grant.save + grant + end + + @[AC::Route::DELETE("/:id", status_code: HTTP::Status::ACCEPTED)] + def destroy : Nil + current_grant.destroy + end + end +end diff --git a/src/placeos-rest-api/controllers/metadata.cr b/src/placeos-rest-api/controllers/metadata.cr index ea36900d..ce486a7f 100644 --- a/src/placeos-rest-api/controllers/metadata.cr +++ b/src/placeos-rest-api/controllers/metadata.cr @@ -26,10 +26,20 @@ module PlaceOS::Api @[AC::Param::Info(name: "id", description: "the parent id of the metadata to be destroyed")] parent_id : String, ) + ensure_parent_reach!(parent_id) unless parent_id == user_token.id return if user_support? || parent_id == user_token.id check_access_level(parent_id, admin_required: true) end + # Every parent-addressed read and write stays inside the caller's reach + @[AC::Route::Filter(:before_action, only: [:show, :children_metadata, :update, :merge, :rename, :history])] + def check_parent_reach( + @[AC::Param::Info(name: "id", description: "the parent id of the metadata")] + parent_id : String, + ) + ensure_parent_reach!(parent_id) unless parent_id == user_token.id + end + ############################################################################################### # Fetch metadata with a specific name for multiple resources @@ -54,6 +64,8 @@ module PlaceOS::Api # Build result hash with parent_id as key and metadata as value result = {} of String => ::PlaceOS::Model::Metadata::Interface? + parent_ids = parent_ids.select { |id| id == user_token.id || parent_in_reach?(id) } + parent_ids.each do |parent_id| metadata = ::PlaceOS::Model::Metadata.for(parent_id, metadata_name).first? result[parent_id] = metadata ? metadata.interface : nil diff --git a/src/placeos-rest-api/controllers/modules.cr b/src/placeos-rest-api/controllers/modules.cr index cdd5822a..ce47712f 100644 --- a/src/placeos-rest-api/controllers/modules.cr +++ b/src/placeos-rest-api/controllers/modules.cr @@ -38,7 +38,9 @@ module PlaceOS::Api def find_current_module(id : String) Log.context.set(module_id: id) # Find will raise a 404 (not found) if there is an error - @current_module = ::PlaceOS::Model::Module.find!(id) + mod = ::PlaceOS::Model::Module.find!(id) + ensure_reach!(mod) + @current_module = mod end getter! current_module : ::PlaceOS::Model::Module @@ -103,7 +105,9 @@ module PlaceOS::Api org_zone = support_org_zone_id if control_system_id - zones = ::PlaceOS::Model::ControlSystem.find!(control_system_id).zones + system = ::PlaceOS::Model::ControlSystem.find!(control_system_id) + ensure_reach!(system) + zones = system.zones # "support" subsystem: Read on the system's zones. return if support_subsystem_grants?(zones, ::PlaceOS::Model::Permissions::Read) # legacy org_zone path: the system must include the org_zone and the @@ -170,10 +174,13 @@ module PlaceOS::Api no_logic : Bool = false, @[AC::Param::Info(description: "return only running modules", example: "true")] running : Bool? = nil, + @[AC::Param::Info(description: "only rows owned by this organisation (must be within reach)", example: "0192f1c4-7a6b-7c4d-9f3e-1a2b3c4d5e6f")] + organisation_id : UUID? = nil, ) : Array(::PlaceOS::Model::Module) # if a system id is present we query the database directly if control_system_id cs = ::PlaceOS::Model::ControlSystem.find!(control_system_id) + ensure_reach!(cs) # Include subset of association data with results results = ::PlaceOS::Model::Module.find_all(cs.modules).compact_map do |mod| next if (driver = mod.driver).nil? @@ -189,10 +196,8 @@ module PlaceOS::Api end # PG full-text search (PPT-2644) - query = ::PlaceOS::Model::Module.all + query = narrow_organisation(scope_organisations(::PlaceOS::Model::Module.all), organisation_id) - # TODO:: we can remove this once there is a tenant_id field on modules - # which will make this much simpler to filter if scope_zones = module_scope_zones # we only want to show modules in use by systems within these zones no_logic = true @@ -368,8 +373,19 @@ module PlaceOS::Api # add a new module / instance of a driver @[AC::Route::POST("/", body: :mod, status_code: HTTP::Status::CREATED)] - def create(mod : ::PlaceOS::Model::Module) : ::PlaceOS::Model::Module + def create( + mod : ::PlaceOS::Model::Module, + @[AC::Param::Info(description: "the organisation that owns a module not bound to a system (cluster admins only)", example: "0192f1c4-7a6b-7c4d-9f3e-1a2b3c4d5e6f")] + organisation_id : UUID? = nil, + ) : ::PlaceOS::Model::Module can_modify?(mod) + mod.organisation_id = if cs_id = mod.control_system_id + system = ::PlaceOS::Model::ControlSystem.find!(cs_id) + ensure_reach!(system) + system.organisation_id + else + organisation_for_new_row(organisation_id) + end raise Error::ModelValidation.new(mod.errors) unless mod.save mod end diff --git a/src/placeos-rest-api/controllers/oauth_applications.cr b/src/placeos-rest-api/controllers/oauth_applications.cr index 8e41ecdc..9cca240e 100644 --- a/src/placeos-rest-api/controllers/oauth_applications.cr +++ b/src/placeos-rest-api/controllers/oauth_applications.cr @@ -20,7 +20,9 @@ module PlaceOS::Api def find_current_app(id : String) Log.context.set(application_id: id) # Find will raise a 404 (not found) if there is an error - @current_app = ::PlaceOS::Model::DoorkeeperApplication.find!(id.to_i) + app = ::PlaceOS::Model::DoorkeeperApplication.find!(id.to_i) + ensure_authority_reach!(app.owner_id, "application") + @current_app = app end getter! current_app : ::PlaceOS::Model::DoorkeeperApplication @@ -38,7 +40,9 @@ module PlaceOS::Api if user_support? # admin/support: optional authority_id filter, no subsystem gating. + query = scope_authorities(query, "owner_id") if authority = authority_id + ensure_authority_reach!(authority, "domain") query = query.where(owner_id: authority) end else @@ -87,6 +91,7 @@ module PlaceOS::Api # add a new user interface application @[AC::Route::POST("/", body: :app, status_code: HTTP::Status::CREATED)] def create(app : ::PlaceOS::Model::DoorkeeperApplication) : ::PlaceOS::Model::DoorkeeperApplication + ensure_authority_reach!(app.owner_id, "domain") raise Error::ModelValidation.new(app.errors) unless app.save app end diff --git a/src/placeos-rest-api/controllers/organisations.cr b/src/placeos-rest-api/controllers/organisations.cr new file mode 100644 index 00000000..2a90b10c --- /dev/null +++ b/src/placeos-rest-api/controllers/organisations.cr @@ -0,0 +1,171 @@ +require "placeos-models/organisation" +require "placeos-models/partner" + +require "./application" + +module PlaceOS::Api + # Organisations: the customers that own domains and estate (PPT-526) + class Organisations < Application + base "/api/engine/v2/organisations/" + + # Scopes + ############################################################################################### + + before_action :can_read, only: [:index, :show, :current] + before_action :can_write, only: [:create, :update, :destroy, :claim] + + before_action :check_support, only: [:index, :show] + before_action :check_cluster_admin, only: [:create, :update, :destroy, :claim] + + ############################################################################################### + + @[AC::Route::Filter(:before_action, except: [:index, :create, :current])] + def find_current_organisation(id : UUID) + Log.context.set(organisation_id: id.to_s) + organisation = ::PlaceOS::Model::Organisation.find!(id) + ensure_reach!(organisation.id, "organisation") + @current_organisation_row = organisation + end + + getter! current_organisation_row : ::PlaceOS::Model::Organisation + + ############################################################################################### + + # What the caller can reach: the level, their own organisation and partner, + # and the organisations in reach. Backoffice reads this once at login. + record CurrentReach, + reach : String, + enforcing : Bool, + organisation : ::PlaceOS::Model::Organisation?, + partner : ::PlaceOS::Model::Partner?, + organisations : Array(::PlaceOS::Model::Organisation)? do + include JSON::Serializable + end + + @[AC::Route::GET("/current")] + def current : CurrentReach + scope = tenancy + organisations = scope.organisation_ids.try do |ids| + ids.empty? ? [] of ::PlaceOS::Model::Organisation : ::PlaceOS::Model::Organisation.find_all(ids.to_a).to_a + end + CurrentReach.new( + reach: scope.reach.to_s.downcase, + enforcing: Utils::Tenancy.enforce?, + organisation: scope.organisation, + partner: scope.partner, + organisations: organisations, + ) + end + + # list the organisations in reach + @[AC::Route::GET("/")] + def index( + @[AC::Param::Info(description: "only organisations under this partner", example: "0192f1c4-7a6b-7c4d-9f3e-1a2b3c4d5e6f")] + partner_id : UUID? = nil, + ) : Array(::PlaceOS::Model::Organisation) + query = ::PlaceOS::Model::Organisation.all + query = query.where(partner_id: partner_id) if partner_id + ids = tenancy.organisation_ids + if ids + list = ids.to_a.map(&.to_s) + query = list.empty? ? query.where("1 = ?", 0) : query.where("id = ANY(ARRAY[#{list.join(", ") { "?" }}]::uuid[])", list) + end + paginate_search(query, ::PlaceOS::Model::Organisation.table_name) + end + + @[AC::Route::GET("/:id")] + def show : ::PlaceOS::Model::Organisation + current_organisation_row + end + + # `payer` and `partner_staff` are not mass-assignable, so they travel as + # parameters + @[AC::Route::POST("/", body: :organisation, status_code: HTTP::Status::CREATED)] + def create( + organisation : ::PlaceOS::Model::Organisation, + @[AC::Param::Info(description: "who is invoiced: partner or organisation", example: "partner")] + payer : String? = nil, + @[AC::Param::Info(description: "true for the partner's own staff organisation", example: "false")] + partner_staff : Bool? = nil, + ) : ::PlaceOS::Model::Organisation + organisation.payer = payer if payer + organisation.partner_staff = partner_staff unless partner_staff.nil? + raise Error::ModelValidation.new(organisation.errors) unless organisation.save + organisation + end + + @[AC::Route::PATCH("/:id", body: :organisation)] + @[AC::Route::PUT("/:id", body: :organisation)] + def update( + organisation : ::PlaceOS::Model::Organisation, + @[AC::Param::Info(description: "who is invoiced: partner or organisation", example: "partner")] + payer : String? = nil, + @[AC::Param::Info(description: "true for the partner's own staff organisation", example: "false")] + partner_staff : Bool? = nil, + ) : ::PlaceOS::Model::Organisation + current = current_organisation_row + current.assign_attributes(organisation) + current.payer = payer if payer + current.partner_staff = partner_staff unless partner_staff.nil? + raise Error::ModelValidation.new(current.errors) unless current.save + current + end + + # Deletes are RESTRICTed by the database while anything is owned + @[AC::Route::DELETE("/:id", status_code: HTTP::Status::ACCEPTED)] + def destroy : Nil + current_organisation_row.destroy + end + + record ClaimBody, zone_ids : Array(String) do + include JSON::Serializable + end + + record ClaimResult, zones : Int64, systems : Int64, modules : Int64, triggers : Int64 do + include JSON::Serializable + end + + # Assigns unowned zone trees to the organisation: the zones named, their + # descendants, the systems in those zones and the modules of those + # systems. Rows already owned by another organisation are left alone. + @[AC::Route::POST("/:id/claim", body: :body)] + def claim(body : ClaimBody) : ClaimResult + organisation_id = current_organisation_row.id.as(UUID).to_s + roots = body.zone_ids.uniq + return ClaimResult.new(0, 0, 0, 0) if roots.empty? + + PgORM::Database.connection do |db| + zones = db.exec(<<-SQL, args: [roots, organisation_id]).rows_affected + WITH RECURSIVE tree AS ( + SELECT id FROM zone WHERE id = ANY($1::text[]) + UNION + SELECT z.id FROM zone z INNER JOIN tree t ON z.parent_id = t.id + ) + UPDATE zone SET organisation_id = $2::uuid + WHERE id IN (SELECT id FROM tree) AND organisation_id IS NULL + SQL + systems = db.exec(<<-SQL, args: [organisation_id]).rows_affected + UPDATE sys SET organisation_id = $1::uuid + WHERE organisation_id IS NULL + AND EXISTS (SELECT 1 FROM zone z WHERE z.id = ANY(sys.zones) AND z.organisation_id = $1::uuid) + AND NOT EXISTS (SELECT 1 FROM zone z WHERE z.id = ANY(sys.zones) AND z.organisation_id IS NOT NULL AND z.organisation_id <> $1::uuid) + SQL + modules = db.exec(<<-SQL, args: [organisation_id]).rows_affected + UPDATE mod SET organisation_id = $1::uuid + WHERE organisation_id IS NULL + AND ( + control_system_id IN (SELECT id FROM sys WHERE organisation_id = $1::uuid) + OR EXISTS (SELECT 1 FROM sys s WHERE mod.id = ANY(s.modules) AND s.organisation_id = $1::uuid) + ) + AND NOT EXISTS (SELECT 1 FROM sys s WHERE mod.id = ANY(s.modules) AND s.organisation_id IS NOT NULL AND s.organisation_id <> $1::uuid) + SQL + triggers = db.exec(<<-SQL, args: [organisation_id]).rows_affected + UPDATE trigger SET organisation_id = $1::uuid + WHERE organisation_id IS NULL + AND control_system_id IN (SELECT id FROM sys WHERE organisation_id = $1::uuid) + SQL + ClaimResult.new(zones, systems, modules, triggers) + end + end + end +end diff --git a/src/placeos-rest-api/controllers/partners.cr b/src/placeos-rest-api/controllers/partners.cr new file mode 100644 index 00000000..8c4cce43 --- /dev/null +++ b/src/placeos-rest-api/controllers/partners.cr @@ -0,0 +1,74 @@ +require "placeos-models/partner" + +require "./application" + +module PlaceOS::Api + # Partners: integrators that bring in and manage organisations, and the + # management partner that is PlaceOS itself (PPT-526) + class Partners < Application + base "/api/engine/v2/partners/" + + # Scopes + ############################################################################################### + + before_action :can_read, only: [:index, :show] + before_action :can_write, only: [:create, :update, :destroy] + + before_action :check_support, only: [:index, :show] + before_action :check_cluster_admin, only: [:create, :update, :destroy] + + ############################################################################################### + + @[AC::Route::Filter(:before_action, except: [:index, :create])] + def find_current_partner(id : UUID) + Log.context.set(partner_id: id.to_s) + partner = ::PlaceOS::Model::Partner.find!(id) + unless tenancy.cluster? || tenancy.partner.try(&.id) == partner.id + log_would_refuse("partner outside reach") + raise Error::NotFound.new("partner not found") if Utils::Tenancy.enforce? + end + @current_partner_row = partner + end + + getter! current_partner_row : ::PlaceOS::Model::Partner + + ############################################################################################### + + # cluster admins list every partner; everyone else sees their own + @[AC::Route::GET("/")] + def index : Array(::PlaceOS::Model::Partner) + query = ::PlaceOS::Model::Partner.all + unless tenancy.cluster? + own = tenancy.partner.try(&.id) + query = own ? query.where(id: own) : query.where("1 = ?", 0) + end + paginate_search(query, ::PlaceOS::Model::Partner.table_name) + end + + @[AC::Route::GET("/:id")] + def show : ::PlaceOS::Model::Partner + current_partner_row + end + + @[AC::Route::POST("/", body: :partner, status_code: HTTP::Status::CREATED)] + def create(partner : ::PlaceOS::Model::Partner) : ::PlaceOS::Model::Partner + raise Error::ModelValidation.new(partner.errors) unless partner.save + partner + end + + @[AC::Route::PATCH("/:id", body: :partner)] + @[AC::Route::PUT("/:id", body: :partner)] + def update(partner : ::PlaceOS::Model::Partner) : ::PlaceOS::Model::Partner + current = current_partner_row + current.assign_attributes(partner) + raise Error::ModelValidation.new(current.errors) unless current.save + current + end + + # Deletes are RESTRICTed by the database while organisations remain + @[AC::Route::DELETE("/:id", status_code: HTTP::Status::ACCEPTED)] + def destroy : Nil + current_partner_row.destroy + end + end +end diff --git a/src/placeos-rest-api/controllers/repositories.cr b/src/placeos-rest-api/controllers/repositories.cr index d01b4963..1be06d91 100644 --- a/src/placeos-rest-api/controllers/repositories.cr +++ b/src/placeos-rest-api/controllers/repositories.cr @@ -16,6 +16,8 @@ module PlaceOS::Api before_action :check_admin, except: [:index, :show] before_action :check_support, only: [:index, :show] + # repositories are shared by every organisation; only cluster admins change them + before_action :check_cluster_admin, only: [:create, :update, :destroy, :remove, :pull] ############################################################################################### diff --git a/src/placeos-rest-api/controllers/schema.cr b/src/placeos-rest-api/controllers/schema.cr index 42b3b24b..f8fb2ffc 100644 --- a/src/placeos-rest-api/controllers/schema.cr +++ b/src/placeos-rest-api/controllers/schema.cr @@ -11,6 +11,7 @@ module PlaceOS::Api before_action :can_read, only: [:index, :show] before_action :check_admin, except: [:index, :show] + before_action :check_cluster_admin, except: [:index, :show] before_action :check_support, only: [:index, :show] ############################################################################################### diff --git a/src/placeos-rest-api/controllers/settings.cr b/src/placeos-rest-api/controllers/settings.cr index e14f96af..cd75b77a 100644 --- a/src/placeos-rest-api/controllers/settings.cr +++ b/src/placeos-rest-api/controllers/settings.cr @@ -32,6 +32,7 @@ module PlaceOS::Api ############################################################################################### def can_view?(parent_id) + ensure_parent_reach!(parent_id) return if user_support? # "support" subsystem: Read on the parent's zones. return if support_subsystem_grants?(support_zones_for_parent(parent_id), ::PlaceOS::Model::Permissions::Read) @@ -39,9 +40,12 @@ module PlaceOS::Api end def can_modify?(setting) + parent_id = setting.parent_id.as(String) + ensure_parent_reach!(parent_id) + # driver settings are shared by every organisation + check_cluster_admin if parent_id.starts_with?(::PlaceOS::Model::Driver.table_name) return if user_admin? raise Error::Forbidden.new("can only modify unencrypted settings") unless setting.encryption_level.none? - parent_id = setting.parent_id.as(String) # "support" subsystem: the verb's bit on the parent's zones. A # parent with no derivable zones (e.g. a driver) stays admin-only. return if support_subsystem_grants?(support_zones_for_parent(parent_id), verb_permission) @@ -102,6 +106,8 @@ module PlaceOS::Api parent_settings else raise Error::Forbidden.new unless user_support? + # the unscoped list spans every organisation + check_cluster_admin # PG full-text search (PPT-2644): `q` matches the settings keys (and # id) — the settings body itself is deliberately not searchable. diff --git a/src/placeos-rest-api/controllers/system-triggers.cr b/src/placeos-rest-api/controllers/system-triggers.cr index 40b0fdf8..b82f94ef 100644 --- a/src/placeos-rest-api/controllers/system-triggers.cr +++ b/src/placeos-rest-api/controllers/system-triggers.cr @@ -22,14 +22,18 @@ module PlaceOS::Api # the legacy org_zone path bypass as usual). @[AC::Route::Filter(:before_action, only: [:index, :show])] def check_trigger_read_permissions(sys_id : String) - zones = ::PlaceOS::Model::ControlSystem.find!(sys_id).zones + system = ::PlaceOS::Model::ControlSystem.find!(sys_id) + ensure_reach!(system) + zones = system.zones ensure_support_access!(zones, ::PlaceOS::Model::Permissions::Read) @secret_visible = secret_visible_on?(zones) end @[AC::Route::Filter(:before_action, only: [:create, :update, :destroy])] def check_trigger_write_permissions(sys_id : String) - zones = ::PlaceOS::Model::ControlSystem.find!(sys_id).zones + system = ::PlaceOS::Model::ControlSystem.find!(sys_id) + ensure_reach!(system) + zones = system.zones ensure_support_access!(zones, verb_permission) @secret_visible = secret_visible_on?(zones) end diff --git a/src/placeos-rest-api/controllers/systems.cr b/src/placeos-rest-api/controllers/systems.cr index 8be90af1..4bb86712 100644 --- a/src/placeos-rest-api/controllers/systems.cr +++ b/src/placeos-rest-api/controllers/systems.cr @@ -38,15 +38,24 @@ module PlaceOS::Api ) Log.context.set(control_system_id: sys_id) # Find will raise a 404 (not found) if there is an error - if sys_id.includes?('@') - systems = find_by_email([sys_id]) - if systems.size > 0 - @current_control_system = systems.first - else - raise Error::NotFound.new("no system with email: #{sys_id}") - end - else - @current_control_system = ::PlaceOS::Model::ControlSystem.find!(sys_id) + system = if sys_id.includes?('@') + systems = find_by_email([sys_id]) + raise Error::NotFound.new("no system with email: #{sys_id}") if systems.empty? + systems.first + else + ::PlaceOS::Model::ControlSystem.find!(sys_id) + end + ensure_reach!(system) + @current_control_system = system + end + + # The lazily gated routes (execute, functions, state, state_lookup, types) + # take the system id without loading the row; a system that exists must + # be in reach. + @[AC::Route::Filter(:before_action, only: [:execute, :functions, :state, :state_lookup, :types, :metadata])] + def check_lazy_reach(sys_id : String) + if system = ::PlaceOS::Model::ControlSystem.find?(sys_id) + ensure_reach!(system) end end @@ -237,9 +246,11 @@ module PlaceOS::Api public : Bool? = nil, @[AC::Param::Info(description: "return systems which are signage", example: "true")] signage : Bool? = nil, + @[AC::Param::Info(description: "only rows owned by this organisation (must be within reach)", example: "0192f1c4-7a6b-7c4d-9f3e-1a2b3c4d5e6f")] + organisation_id : UUID? = nil, ) : Array(::PlaceOS::Model::ControlSystem) # PG full-text search (PPT-2644) - query = ::PlaceOS::Model::ControlSystem.all + query = narrow_organisation(scope_organisations(::PlaceOS::Model::ControlSystem.all), organisation_id) # `zone_id` keeps its original AND semantics — a system must # contain *every* listed zone. The intended use is intersection @@ -344,7 +355,7 @@ module PlaceOS::Api @[AC::Param::Info(name: "in", description: "comma seperated list of emails", example: "room1@org.com,room2@org.com")] emails : Array(String), ) : Array(::PlaceOS::Model::ControlSystem) - systems = ::PlaceOS::Model::ControlSystem.where(email: emails.map(&.strip.downcase)).to_a + systems = scope_organisations(::PlaceOS::Model::ControlSystem.where(email: emails.map(&.strip.downcase))).to_a set_collection_headers(systems.size, ::PlaceOS::Model::ControlSystem.table_name) systems end @@ -405,14 +416,23 @@ module PlaceOS::Api current = current_control_system current.assign_attributes(updated) current.version = version + 1 + if updated.zones_assigned? + owner = organisation_for_zones(current.zones) + if owner && current.organisation_id && owner != current.organisation_id + raise Error::ModelValidation.new([Utils::Tenancy::Failure.new(:zones, "zones belong to another organisation")], "zones belong to another organisation") + end + current.organisation_id ||= owner + end raise Error::ModelValidation.new(current.errors) unless current.save current end - # adds a new system + # adds a new system. The system belongs to its zones' organisation, or + # the caller's when the zones are unowned. @[AC::Route::POST("/", status_code: HTTP::Status::CREATED)] def create : ::PlaceOS::Model::ControlSystem sys = control_system_update + sys.organisation_id = organisation_for_zones(sys.zones) raise Error::ModelValidation.new(sys.errors) unless sys.save sys end @@ -465,7 +485,14 @@ module PlaceOS::Api def add_module( module_id : String, ) : ::PlaceOS::Model::ControlSystem - raise Error::NotFound.new unless ::PlaceOS::Model::Module.exists?(module_id) + mod = ::PlaceOS::Model::Module.find?(module_id) + raise Error::NotFound.new unless mod + ensure_reach!(mod) + # a cluster admin attaching an unowned module adopts it into the system's organisation + if mod.organisation_id.nil? && (owner = current_control_system.organisation_id) + mod.organisation_id = owner + mod.save! + end module_present = current_control_system.modules.includes?(module_id) || ::PlaceOS::Model::ControlSystem.add_module(current_control_system.id.as(String), module_id) raise "Failed to add ControlSystem Module" unless module_present @@ -661,6 +688,7 @@ module PlaceOS::Api ws: ws, request_id: request_id, user: user_token, + organisation_ids: tenancy.organisation_ids, ) end diff --git a/src/placeos-rest-api/controllers/tenant_consent.cr b/src/placeos-rest-api/controllers/tenant_consent.cr index 2c56fac0..164955e0 100644 --- a/src/placeos-rest-api/controllers/tenant_consent.cr +++ b/src/placeos-rest-api/controllers/tenant_consent.cr @@ -35,6 +35,7 @@ module PlaceOS::Api @[AC::Route::GET("/:id")] def index(id : String) : NamedTuple(url: String) authority = ::PlaceOS::Model::Authority.find!(id) + ensure_reach!(authority) update_app_redirect_uri callback_url = URI.encode_www_form(redirect_url) # Not the authority id. `state` comes back from Microsoft through the diff --git a/src/placeos-rest-api/controllers/triggers.cr b/src/placeos-rest-api/controllers/triggers.cr index d6f61812..47d0190c 100644 --- a/src/placeos-rest-api/controllers/triggers.cr +++ b/src/placeos-rest-api/controllers/triggers.cr @@ -22,7 +22,17 @@ module PlaceOS::Api def find_current_trigger(id : String) Log.context.set(trigger_id: id) # Find will raise a 404 (not found) if there is an error - @current_trigger = ::PlaceOS::Model::Trigger.find!(id) + trigger = ::PlaceOS::Model::Trigger.find!(id) + # a trigger bound to a system follows that system; a definition with + # no system is a template any organisation may instantiate + ensure_reach!(trigger) if trigger.control_system_id + @current_trigger = trigger + end + + # Templates are cluster-managed + @[AC::Route::Filter(:before_action, only: [:update, :destroy])] + def check_template_write + check_cluster_admin if current_trigger.control_system_id.nil? end getter! current_trigger : ::PlaceOS::Model::Trigger @@ -31,9 +41,25 @@ module PlaceOS::Api # returns the list of available triggers @[AC::Route::GET("/")] - def index : Array(::PlaceOS::Model::Trigger) + def index( + @[AC::Param::Info(description: "only triggers bound to systems owned by this organisation; templates are always included", example: "0192f1c4-7a6b-7c4d-9f3e-1a2b3c4d5e6f")] + organisation_id : UUID? = nil, + ) : Array(::PlaceOS::Model::Trigger) # PG full-text search (PPT-2644) - paginate_search(::PlaceOS::Model::Trigger.all, ::PlaceOS::Model::Trigger.table_name) + query = ::PlaceOS::Model::Trigger.all + if organisation_id + ensure_reach!(organisation_id, "organisation") + query = query.where("(control_system_id IS NULL OR organisation_id = ?::uuid)", organisation_id.to_s) + end + if ids = tenancy.organisation_ids + list = ids.to_a.map(&.to_s) + query = if list.empty? + query.where("control_system_id IS NULL", [] of String) + else + query.where("(control_system_id IS NULL OR organisation_id = ANY(ARRAY[#{list.join(", ") { "?" }}]::uuid[]))", list) + end + end + paginate_search(query, ::PlaceOS::Model::Trigger.table_name) end # update so we can provide instance details @@ -66,6 +92,13 @@ module PlaceOS::Api # adds a new trigger @[AC::Route::POST("/", body: :trig, status_code: HTTP::Status::CREATED)] def create(trig : ::PlaceOS::Model::Trigger) : ::PlaceOS::Model::Trigger + if cs_id = trig.control_system_id + system = ::PlaceOS::Model::ControlSystem.find!(cs_id) + ensure_reach!(system) + trig.organisation_id = system.organisation_id + else + check_cluster_admin + end raise Error::ModelValidation.new(trig.errors) unless trig.save trig end diff --git a/src/placeos-rest-api/controllers/users.cr b/src/placeos-rest-api/controllers/users.cr index 488abeb5..45fdb436 100644 --- a/src/placeos-rest-api/controllers/users.cr +++ b/src/placeos-rest-api/controllers/users.cr @@ -65,9 +65,11 @@ module PlaceOS::Api raise PgORM::Error::RecordNotFound.new }.tap do |found| # The `:id` lookup above is not authority scoped — only admins may - # resolve users belonging to other authorities. 404 (not 403) so - # foreign ids are indistinguishable from unknown ones. + # resolve users belonging to other authorities, and only within their + # reach. 404 (not 403) so foreign ids are indistinguishable from + # unknown ones. raise PgORM::Error::RecordNotFound.new unless user_admin? || found.authority_id == authority + ensure_authority_reach!(found.authority_id, "user") unless found.authority_id == authority Log.context.set(user_id: found.id) @user = found end @@ -203,6 +205,8 @@ module PlaceOS::Api include_metadata : Bool = false, @[AC::Param::Info(description: "admin users can view other domains, ignored for other users", example: "auth-12345")] authority_id : String? = nil, + @[AC::Param::Info(description: "admin users can list the users of every domain owned by this organisation (must be within reach)", example: "0192f1c4-7a6b-7c4d-9f3e-1a2b3c4d5e6f")] + organisation_id : UUID? = nil, ) : Array(UserDetails) # PG full-text search (PPT-2644) # NOTE: email-shaped queries used to be quote-wrapped for an @@ -217,7 +221,11 @@ module PlaceOS::Api # regular users can only see their own domain query = query.where(authority_id: current_user.authority_id.as(String)) elsif authority = authority_id + ensure_authority_reach!(authority, "domain") query = query.where(authority_id: authority) + else + # admins see every domain within reach, narrowed to one organisation on request + query = narrow_authorities(scope_authorities(query), organisation_id) end results = paginate_search(query, ::PlaceOS::Model::User.table_name) @@ -262,8 +270,9 @@ module PlaceOS::Api # only be set by admins — support-subsystem creators would otherwise # be able to mint sys-admins. new_user.assign_admin_attributes_from_json(body) - # allow sys-admins to create users on other domains + # allow sys-admins to create users on other domains within reach new_user.authority ||= current_authority.as(::PlaceOS::Model::Authority) + ensure_authority_reach!(new_user.authority_id, "domain") else # non-admins always create users in their own authority new_user.authority = current_authority.as(::PlaceOS::Model::Authority) @@ -302,12 +311,27 @@ module PlaceOS::Api user.deleted = true raise Error::ModelValidation.new(user.errors) unless user.save else + ensure_organisation_admin_remains!(user) user_id = user.id user.destroy spawn { Api::Metadata.signal_metadata(current_authority.not_nil!.id.to_s, :destroy_all, {parent_id: user_id}) } end end + # An organisation admin cannot remove their organisation's last admin. + # Cluster admins may, which is how a whole domain gets torn down. + protected def ensure_organisation_admin_remains!(target : ::PlaceOS::Model::User) : Nil + return unless target.sys_admin + return if tenancy.cluster? + organisation_id = ::PlaceOS::Model::Authority.find?(target.authority_id.as(String)).try(&.organisation_id) + return unless organisation_id + remaining = ::PlaceOS::Model::User + .where(sys_admin: true, deleted: false) + .where("authority_id IN (SELECT id FROM authority WHERE organisation_id = ?::uuid)", organisation_id.to_s) + .count + raise Error::Forbidden.new("at least one organisation admin must remain") if remaining <= 1 + end + # undelete a user @[AC::Route::POST("/:id/revive")] def revive diff --git a/src/placeos-rest-api/controllers/zones.cr b/src/placeos-rest-api/controllers/zones.cr index 0379678a..5a719774 100644 --- a/src/placeos-rest-api/controllers/zones.cr +++ b/src/placeos-rest-api/controllers/zones.cr @@ -37,7 +37,9 @@ module PlaceOS::Api def current_zone(id : String) Log.context.set(zone_id: id) # Find will raise a 404 (not found) if there is an error - @current_zone = ::PlaceOS::Model::Zone.find!(id) + zone = ::PlaceOS::Model::Zone.find!(id) + ensure_reach!(zone) + @current_zone = zone end getter! current_zone : ::PlaceOS::Model::Zone @@ -162,6 +164,8 @@ module PlaceOS::Api include_children_count : Bool = false, @[AC::Param::Info(description: "with group_id, include anchor zones and their descendants, excluding denied subtrees", example: "true")] descendants : Bool = false, + @[AC::Param::Info(description: "only rows owned by this organisation (must be within reach)", example: "0192f1c4-7a6b-7c4d-9f3e-1a2b3c4d5e6f")] + organisation_id : UUID? = nil, ) : Array(::PlaceOS::Model::Zone) group_zone_ids = nil if group_id @@ -187,7 +191,7 @@ module PlaceOS::Api # the same for `?parent_id=` as for no param at all parent_id = nil if parent_id.try(&.empty?) - query = ::PlaceOS::Model::Zone.all + query = narrow_organisation(scope_organisations(::PlaceOS::Model::Zone.all), organisation_id) if group_id && descendants query = query.where("id IN (#{group_descendants_sql})", group_id.to_s) @@ -277,8 +281,14 @@ module PlaceOS::Api # returns unique zone tags @[AC::Route::GET("/tags")] def tags : Array(String) + ids = tenancy.organisation_ids + return [] of String if ids && ids.empty? unique_tags = PgORM::Database.connection do |db| - db.query_one "select string_agg(distinct tag, ', ' order by tag) as types from ( select unnest(tags) as tag from zone ) as unnested", &.read(String) + if ids + db.query_one "select string_agg(distinct tag, ', ' order by tag) as types from ( select unnest(tags) as tag from zone where organisation_id = ANY($1::uuid[]) ) as unnested", args: [ids.to_a.map(&.to_s)], &.read(String) + else + db.query_one "select string_agg(distinct tag, ', ' order by tag) as types from ( select unnest(tags) as tag from zone ) as unnested", &.read(String) + end end unique_tags.split(',') rescue DB::ColumnTypeMismatchError @@ -304,14 +314,31 @@ module PlaceOS::Api zone = zone_update current = current_zone current.assign_attributes(zone) + if (parent_id = current.parent_id.presence) && current.parent_id_changed? + parent = ::PlaceOS::Model::Zone.find!(parent_id) + ensure_reach!(parent) + raise Error::ModelValidation.new([Utils::Tenancy::Failure.new(:parent_id, "parent belongs to another organisation")], "parent belongs to another organisation") unless parent.organisation_id == current.organisation_id + end raise Error::ModelValidation.new(current.errors) unless current.save current end - # add a new zone + # add a new zone. A child zone belongs to its parent's organisation; a + # root zone belongs to the caller's, or to the organisation a cluster + # admin names. @[AC::Route::POST("/", status_code: HTTP::Status::CREATED)] - def create : ::PlaceOS::Model::Zone + def create( + @[AC::Param::Info(description: "the organisation that owns a new root zone (cluster admins only)", example: "0192f1c4-7a6b-7c4d-9f3e-1a2b3c4d5e6f")] + organisation_id : UUID? = nil, + ) : ::PlaceOS::Model::Zone zone = zone_update + zone.organisation_id = if parent_id = zone.parent_id.presence + parent = ::PlaceOS::Model::Zone.find!(parent_id) + ensure_reach!(parent) + parent.organisation_id + else + organisation_for_new_row(organisation_id) + end raise Error::ModelValidation.new(zone.errors) unless zone.save zone end @@ -331,6 +358,7 @@ module PlaceOS::Api @[AC::Param::Info(description: "only return the metadata key we are interested in", example: "workplace-config")] name : String? = nil, ) : Hash(String, ::PlaceOS::Model::Metadata::Interface) + ensure_reach!(::PlaceOS::Model::Zone.find!(id)) ::PlaceOS::Model::Metadata.build_metadata(id, name) end diff --git a/src/placeos-rest-api/utilities/tenancy.cr b/src/placeos-rest-api/utilities/tenancy.cr new file mode 100644 index 00000000..70d76511 --- /dev/null +++ b/src/placeos-rest-api/utilities/tenancy.cr @@ -0,0 +1,283 @@ +require "uuid" +require "placeos-models/authority" +require "placeos-models/organisation" +require "placeos-models/partner" +require "placeos-models/grant" + +require "../../constants" + +module PlaceOS::Api + # Resolves, once per request, which organisations the caller may reach, and + # exposes the scope filter and reach checks the controllers apply. + # + # Reach is structural first and granted second: + # - cluster: an admin or support user whose domain's organisation belongs to + # the management partner. Sees every organisation, as before PPT-526. + # - partner: an admin or support user on a partner's staff organisation + # (`organisations.partner_staff`). Sees every organisation under that partner. + # - organisation: everyone else sees their own organisation only. + # Live grants widen the organisation set without changing the level. + # + # Rows with no organisation (unowned) are visible to cluster reach only. + # A caller whose domain has no organisation reaches nothing. + # + # `Tenancy.enforce?` (from `PLACE_TENANCY_ENFORCE`) switches between refusing + # and logging what would have been refused; the resolver runs either way. + module Utils::Tenancy + Log = ::Log.for("tenancy") + + class_property? enforce : Bool = PLACE_TENANCY_ENFORCE + + enum Reach + Cluster + Partner + Organisation + end + + # Shape `Error::ModelValidation` reads failures from + record Failure, field : Symbol, message : String + + # The resolved reach of the current request. `organisation_ids` is nil for + # cluster reach (no filter) and otherwise the exact set, possibly empty. + record Scope, + reach : Reach, + organisation_ids : Set(UUID)?, + organisation : ::PlaceOS::Model::Organisation?, + partner : ::PlaceOS::Model::Partner? do + def cluster? : Bool + reach.cluster? + end + + def includes?(organisation_id : UUID?) : Bool + return true if cluster? + return false if organisation_id.nil? + organisation_ids.as(Set(UUID)).includes?(organisation_id) + end + + def organisation_id : UUID? + organisation.try(&.id) + end + end + + getter current_organisation : ::PlaceOS::Model::Organisation? { current_authority.try(&.organisation) } + getter current_partner : ::PlaceOS::Model::Partner? { current_organisation.try(&.partner_id).try { |id| ::PlaceOS::Model::Partner.find?(id) } } + + # The caller's reach as it will be once enforcement is on. Controllers + # never read this directly for gating; see `tenancy`. + getter resolved_tenancy : Scope { resolve_tenancy } + + # The scope controllers apply. With enforcement off this is always cluster + # reach, so behaviour matches the release before PPT-526. + getter tenancy : Scope do + if Utils::Tenancy.enforce? + resolved_tenancy + else + Scope.new(Reach::Cluster, nil, current_organisation, current_partner) + end + end + + protected def resolve_tenancy : Scope + organisation = current_organisation + partner = current_partner + privileged = user_support? + + if organisation.nil? + Log.warn { {message: "domain has no organisation, caller reaches nothing", authority: current_authority.try(&.id), user_id: user_token.id} } + return Scope.new(Reach::Organisation, Set(UUID).new, nil, nil) + end + + organisation_id = organisation.id.as(UUID) + + if privileged && partner && partner.management + return Scope.new(Reach::Cluster, nil, organisation, partner) + end + + reach, ids = if privileged && partner && organisation.partner_staff + {Reach::Partner, organisation.partner_organisations.to_a.compact_map(&.id).to_set} + else + {Reach::Organisation, Set{organisation_id}} + end + + ids.concat(granted_organisation_ids) + Scope.new(reach, ids, organisation, partner) + end + + # Organisations the caller reaches through live grants that carry Read. + protected def granted_organisation_ids : Set(UUID) + ids = Set(UUID).new + return ids if user_token.guest_scope? + user_id = user_token.id + + ::PlaceOS::Model::Grant.for_user(user_id).each do |grant| + next unless grant.permission_flags.read? || grant.permission_flags.manage? + scope_id = grant.scope_id + case grant.scope_type + when ::PlaceOS::Model::Grant::SCOPE_ORGANISATION + UUID.parse?(scope_id).try { |id| ids << id } + when ::PlaceOS::Model::Grant::SCOPE_PARTNER + if partner_id = UUID.parse?(scope_id) + ::PlaceOS::Model::Organisation.where(partner_id: partner_id).each { |org| org.id.try { |id| ids << id } } + end + when ::PlaceOS::Model::Grant::SCOPE_AUTHORITY + ::PlaceOS::Model::Authority.find?(scope_id).try(&.organisation_id).try { |id| ids << id } + end + end + ids + end + + # ------------------------------------------------------------------ + # Gates + # ------------------------------------------------------------------ + + # Cluster-only routes (repositories, drivers, brokers, cluster, build jobs). + def check_cluster_admin + raise Error::Forbidden.new unless user_admin? + return if tenancy.cluster? + log_would_refuse("cluster admin required") + raise Error::Forbidden.new if Utils::Tenancy.enforce? + end + + # Raises 404 when `organisation_id` is outside the caller's reach, so a + # foreign id is indistinguishable from an unknown one. Unowned rows are in + # reach for cluster callers only. + def ensure_reach!(organisation_id : UUID?, resource : String = "resource") : Nil + return if tenancy.includes?(organisation_id) + log_would_refuse("#{resource} outside reach", organisation_id) + raise Error::NotFound.new("#{resource} not found") if Utils::Tenancy.enforce? + end + + def ensure_reach!(row : ::PlaceOS::Model::Authority | ::PlaceOS::Model::Zone | ::PlaceOS::Model::ControlSystem | ::PlaceOS::Model::Module | ::PlaceOS::Model::Trigger | ::PlaceOS::Model::Edge | ::PlaceOS::Model::Broker) : Nil + ensure_reach!(row.organisation_id, row.class.table_name) + end + + # Reach for a row owned through its domain (users, api keys, auth sources, + # OAuth applications). + def ensure_authority_reach!(authority_id : String?, resource : String = "resource") : Nil + return if tenancy.cluster? + organisation_id = authority_id.try { |id| ::PlaceOS::Model::Authority.find?(id).try(&.organisation_id) } + ensure_reach!(organisation_id, resource) + end + + # True when the caller may act on `organisation_id`; the quiet form of + # `ensure_reach!` for building filtered lists. + def in_reach?(organisation_id : UUID?) : Bool + tenancy.includes?(organisation_id) + end + + # ------------------------------------------------------------------ + # Query scoping + # ------------------------------------------------------------------ + + # Appends the reach filter to a relation on a table with an + # `organisation_id` column. No-op for cluster reach. + def scope_organisations(query, column : String = "organisation_id") + ids = tenancy.organisation_ids + return query if ids.nil? + if ids.empty? + query.where("1 = ?", 0) + else + list = ids.to_a.map(&.to_s) + query.where("#{column} = ANY(ARRAY[#{list.join(", ") { "?" }}]::uuid[])", list) + end + end + + # Narrows a reach-scoped relation to one organisation the caller asked for. + def narrow_organisation(query, organisation_id : UUID?, column : String = "organisation_id") + return query if organisation_id.nil? + ensure_reach!(organisation_id, "organisation") + query.where("#{column} = ?::uuid", organisation_id.to_s) + end + + # As above for a domain-owned table. + def narrow_authorities(query, organisation_id : UUID?, column : String = "authority_id") + return query if organisation_id.nil? + ensure_reach!(organisation_id, "organisation") + query.where("#{column} IN (SELECT id FROM authority WHERE organisation_id = ?::uuid)", organisation_id.to_s) + end + + # As above for tables owned through their domain (`authority_id`). + def scope_authorities(query, column : String = "authority_id") + ids = tenancy.organisation_ids + return query if ids.nil? + if ids.empty? + query.where("1 = ?", 0) + else + list = ids.to_a.map(&.to_s) + query.where("#{column} IN (SELECT id FROM authority WHERE organisation_id = ANY(ARRAY[#{list.join(", ") { "?" }}]::uuid[]))", list) + end + end + + # Domain ids in reach, for routes that take an `authority_id` parameter. + def reachable_authority_ids : Array(String)? + ids = tenancy.organisation_ids + return if ids.nil? + return [] of String if ids.empty? + list = ids.to_a.map(&.to_s) + ::PlaceOS::Model::Authority + .where("organisation_id = ANY(ARRAY[#{list.join(", ") { "?" }}]::uuid[])", list) + .to_a.compact_map(&.id) + end + + # The single organisation implied by a set of zone ids: every zone must be + # in reach and owned by the same organisation. Falls back to the caller's + # own organisation when the zones are unowned or there are none. + def organisation_for_zones(zone_ids : Array(String)) : UUID? + owners = Set(UUID).new + zone_ids.uniq.each do |zone_id| + zone = ::PlaceOS::Model::Zone.find!(zone_id) + ensure_reach!(zone) + zone.organisation_id.try { |id| owners << id } + end + raise Error::ModelValidation.new([Failure.new(:zones, "zones belong to more than one organisation")], "zones belong to more than one organisation") if owners.size > 1 + owners.first? || tenancy.organisation_id + end + + # Reach check for a settings/metadata parent id (zone-, sys-, mod-, user- + # or driver- prefixed). Drivers are global, so a driver parent is always + # in reach for reads. + def ensure_parent_reach!(parent_id : String) : Nil + case parent_id + when .starts_with?("zone-") then ensure_reach!(::PlaceOS::Model::Zone.find!(parent_id)) + when .starts_with?("sys-") then ensure_reach!(::PlaceOS::Model::ControlSystem.find!(parent_id)) + when .starts_with?("mod-") then ensure_reach!(::PlaceOS::Model::Module.find!(parent_id)) + when .starts_with?("user-") then ensure_authority_reach!(::PlaceOS::Model::User.find!(parent_id).authority_id, "user") + when .starts_with?("driver-") then nil + else ensure_reach!(nil, "parent") + end + end + + # Quiet form of `ensure_parent_reach!` for filtering id lists. + def parent_in_reach?(parent_id : String) : Bool + return true if tenancy.cluster? || !Utils::Tenancy.enforce? + ensure_parent_reach!(parent_id) + true + rescue Error::NotFound | PgORM::Error::RecordNotFound + false + end + + # The organisation a newly created estate row belongs to: the caller's own, + # or, for cluster callers, an explicit choice. + def organisation_for_new_row(requested : UUID? = nil) : UUID? + if requested + raise Error::Forbidden.new("organisation outside reach") unless in_reach?(requested) + return requested + end + tenancy.organisation_id + end + + protected def log_would_refuse(reason : String, organisation_id : UUID? = nil) + scope = resolved_tenancy + Log.warn do + { + message: Utils::Tenancy.enforce? ? "tenancy refused" : "tenancy would refuse", + reason: reason, + reach: scope.reach.to_s, + caller_org: scope.organisation_id.try(&.to_s), + target_org: organisation_id.try(&.to_s), + user_id: user_token.id, + path: request.path, + } + end + end + end +end diff --git a/src/placeos-rest-api/websocket/manager.cr b/src/placeos-rest-api/websocket/manager.cr index 1820bf0a..07a200ec 100644 --- a/src/placeos-rest-api/websocket/manager.cr +++ b/src/placeos-rest-api/websocket/manager.cr @@ -27,13 +27,14 @@ module PlaceOS::Api::WebSocket # Creates the session and handles the cleanup # - def create_session(ws, request_id, user) + def create_session(ws, request_id, user, organisation_ids : Set(UUID)? = nil) Log.trace { {request_id: request_id, frame: "OPEN"} } session = Session.new( ws: ws, request_id: request_id, user: user, discovery: discovery, + organisation_ids: organisation_ids, ) session_lock.synchronize { sessions << session } diff --git a/src/placeos-rest-api/websocket/session.cr b/src/placeos-rest-api/websocket/session.cr index d852b9d8..a6f60706 100644 --- a/src/placeos-rest-api/websocket/session.cr +++ b/src/placeos-rest-api/websocket/session.cr @@ -35,11 +35,15 @@ module PlaceOS::Api::WebSocket private getter user : ::PlaceOS::Model::UserJWT + # Organisations the session may touch; nil for cluster reach (PPT-526) + private getter organisation_ids : Set(UUID)? + def initialize( @ws : HTTP::WebSocket, @request_id : String, @user : ::PlaceOS::Model::UserJWT, @discovery : Clustering::Discovery = RemoteDriver.default_discovery, + @organisation_ids : Set(UUID)? = nil, ) # Register event handlers ws.on_message do |message| @@ -72,6 +76,18 @@ module PlaceOS::Api::WebSocket # WebSocket API Handlers ############################################################################## + # True when the system is inside the session's reach. Unknown systems are + # left to the existing lookups, which answer with the module-not-found + # error the clients already handle. + protected def system_in_reach?(system_id : String) : Bool + ids = organisation_ids + return true if ids.nil? + system = ::PlaceOS::Model::ControlSystem.find?(system_id) + return true if system.nil? + owner = system.organisation_id + !owner.nil? && ids.includes?(owner) + end + protected def elevate_security(system_id : String, default : Driver::Proxy::RemoteDriver::Clearance) : Driver::Proxy::RemoteDriver::Clearance return default if default.admin? @@ -538,6 +554,12 @@ module PlaceOS::Api::WebSocket } Log.context.set(**arguments.merge({ws_request_id: @request_id})) + unless system_in_reach?(request.system_id) + Log.warn { {message: "tenancy refused", reason: "system outside reach", system_id: request.system_id, user: @user.id} } + respond error_response(request.id, :module_not_found, "could not find module: sys=#{request.system_id} mod=#{request.module_name}") + return + end + case request.command in .bind? then bind(**arguments) in .unbind? then unbind(**arguments)