3. Task Store¶
The Task Store lets Agora users browse, import, and publish Task and Trigger definitions shared across GyroTools customers. See Task Store for the end-user guide. This page describes the architecture behind it.
3.1. Architecture¶
The feature spans three tiers:
License server (
gt_task_storeapp) — the actual shared data store, hosted centrally by GyroTools.Agora Django backend (
agora.task_store) — proxies between the Agora frontend and the license server, and stores each user’s link to their GyroTools portal account.Agora Angular frontend — the Task Store / Trigger Store screens and the account-linking UI.
3.2. License Server¶
The license server’s gt_task_store app owns the TaskStoreEntry model (gt_task_store/models.py):
Field |
Description |
|---|---|
|
FK to the portal |
|
|
|
Display metadata shown in the store list and detail views. |
|
Opaque |
|
M2M to |
|
Incremented on every |
TaskStoreEntryViewSet (gt_task_store/rest.py) is a full ModelViewSet with a split authentication scheme:
Reads (
list,retrieve,download) useCalKeyAuthentication— the install’s shared CAL/license key, the same key every Agora installation already sends on/check,/renew,/info. No per-user login is required to browse; only a currently-licensed Agora install.Writes (
create,update,destroy) useTokenAuthenticationwithIsOwnerOrReadOnly— a real per-user identity is required, obtained via account linking (see below).IsOwnerOrReadOnlycomparesobj.owner_idagainst the requesting user.
The download action returns {"type": entry.type, "tasks" | "triggers": entry.tasks} — the bundle key matches whichever key Agora’s own local export/import already uses for that type, so a downloaded bundle is byte-for-byte the same shape as a local export.
AccountLinkView (POST /link/) exchanges a GyroTools portal email/password for the user’s personal API key: it calls Django’s authenticate() against the portal’s user database, then get_api_key_for_user() (the same helper backing the portal’s own “regenerate my API key” flow) to obtain a rest_framework.authtoken token. It is throttled per-IP (AccountLinkThrottle) since it accepts raw credentials.
Note
Anyone who successfully links their portal account can publish — there is no additional role or permission check on the license server beyond IsOwnerOrReadOnly. This is an intentional simplification for the current release and may need revisiting if it becomes a problem in practice.
Admin: the gt_admin Task Store admin list now has a per-row Delete action (gt_admin/tables.py TaskStoreEntryTable, gt_admin/views.py TaskStoreEntryDelete, URL admin-task-store-entry-delete). Previously entries could only be created and edited from the admin, not removed.
3.3. Agora Backend¶
agora.task_store (srv/agora/task_store/) proxies between the Agora frontend and the license server, and owns one model of its own:
TaskStoreAccountLink (models.py) — a one-to-one link from a local Agora User to their GyroTools portal identity:
Field |
Description |
|---|---|
|
|
|
The portal account’s email, shown in the UI as the linked identity. |
|
The portal account’s personal API key, stored encrypted ( |
TaskStoreService (services.py) wraps all calls to the license server:
search_entries/get_entry/download_bundle— reads, authenticated with the install’s own license key (license_service.get_agora_license().key). RaisesTaskStoreServiceExceptionif there is no valid license.link_account/unlink_account/get_account_link— manage the localTaskStoreAccountLinkfor a given Django user.create_entry/update_entry/delete_entry— owner-write actions. Each calls_require_link()first and raisesTaskStoreServiceExceptionif the current user has no linked account, authenticating to the license server with the linked account’s stored API key.import_bundle— a purely local operation (no license-server call): takes an already-downloaded bundle and itstypefield to dispatch to_import_task_bundleor_import_trigger_bundle, reusing the existing task/trigger creation serializers (TaskDefinitionSerializer/TaskDefinitionYamlSerializerfor tasks,TriggerImportSerializerfor triggers) — the same code paths used by local.jsonimport.
api/views.py exposes this over HTTP, all requiring an authenticated Agora session (IsAuthenticated):
TaskStoreEntryListView—GET(search) /POST(create).TaskStoreEntryDetailView—GET/PATCH/DELETEon one entry.TaskStoreEntryDownloadView—GETthe raw bundle.TaskStoreEntryImportView—POSTto import a downloaded bundle into a project. Checkscan_manage_tasks/can_manage_triggerson the target project twice: once up front (rejecting a user with neither permission before any network call), and again after the bundle’s actualtypeis known from the license server, in case the user has only one of the two permissions.TaskStoreAccountView—GETlink status /POSTlink /DELETEunlink.
See REST API for the full endpoint table and request/response examples.
3.4. Agora Frontend¶
Two structurally parallel feature modules, both under project/modules/settings/modules/:
task-store/andtrigger-store/— each hasservices/(*-store.service.tsfor entry CRUD/search/import,*-store-publish.service.tsfor opening the publish modal),store/(NgRx actions/effects/reducers/selectors for search/load/create/update/delete),components/(*-store-list,*-store-detail,*-store-publish-modal), andcontainers/(list/detail containers that connect them to the store).
Account linking lives in its own core module, core/task-store-account/, mirroring the existing core/auth module pattern: a small NgRx feature (taskStoreAccount), a TaskStoreAccountService, imported by both store modules and by the profile module. Keeping it outside either store module lets the Task Store and Trigger Store share one account link without depending on each other.
Entry points in the UI:
Browse — a store icon button in the Task/Trigger toolbar, navigating to the store list screen.
Publish — a button inside the store list screen itself, opening the publish modal. If the account isn’t linked yet, the modal shows the inline link form first; once linked, it shows the picker + metadata form. Editing an owned entry reopens the same modal pre-filled, skipping the picker.
Profile — a standalone Task Store tab (
ui/src/app/profile/task-store-account/) next to API Key/Password/etc., for linking or unlinking outside of the publish flow.
Ownership in the UI is determined by comparing the entry’s owner_email against the current user’s linked email — the Edit/Delete buttons on the detail page are shown only when they match.