The app_store module in src/cluster_erp/modules/app_store/ is a platform module. It keeps the catalogue of sellable apps, the uploaded licence, which roles may use which app, users' requests for apps, and which apps the deployment has installed. Administrators manage it through the store and app manager apps; every user's launcher is rendered from its registry. The enforcement on other modules' APIs is in server/entitlements.py and is explained in app store licensing and entitlements.
At a glance¶
| Item | Value | Source |
|---|---|---|
| Package | src/cluster_erp/modules/app_store/ |
folder |
| Module class | Module |
src/cluster_erp/modules/app_store/module.py:18 |
name / version |
app_store / 0.1.0 |
src/cluster_erp/modules/app_store/module.py:26-32 |
| API prefix | /api/v1/app_store (ModuleBase.api_prefix) |
src/cluster_erp/core/module_base.py:37-38, src/cluster_erp/modules/app_store/module.py:54-57 |
| Sub-modules | 0; a flat module with license.py, models.py, routes.py, services.py, and catalog.json |
folder listing |
| Tables | 5: app_store_apps, app_store_licenses, app_store_assignments, app_store_requests, app_store_installs |
src/cluster_erp/modules/app_store/models.py:32-138 |
| Entities in metadata | 0; no sidebar content | src/cluster_erp/modules/app_store/module.py:65-79 |
| Web app presence | System apps store, app-library, and app-manager in the catalogue; gallery folders in webapp/store/ |
src/cluster_erp/modules/app_store/catalog.json |
| Desktop presence | none; no folder in src/cluster_erp/desktop/modules/ |
folder |
What the module does¶
- Converges the catalogue table to the shipped
catalog.jsonat every start-up and installs new apps on first boot (src/cluster_erp/modules/app_store/module.py:38-52,src/cluster_erp/modules/app_store/services.py:91-135). - Serves each user's registry: every enabled app with its entitled, installed, seat, and reconsent state (
src/cluster_erp/modules/app_store/routes.py:18-39). - Stores and re-verifies the licence (
src/cluster_erp/modules/app_store/services.py:191-265). - Keeps role seat rules and app requests (
src/cluster_erp/modules/app_store/services.py:267-361). - Installs, reconsents, and uninstalls apps (
src/cluster_erp/modules/app_store/services.py:169-189). - Serves per-app JavaScript bundles from
gated_apps/after a layer check (src/cluster_erp/modules/app_store/routes.py:95-155).
Sub-modules¶
| Sub-module | Purpose | Main entities | Router | Source |
|---|---|---|---|---|
| none (flat module) | Catalogue, licence, seats, requests, installs | StoreApp, AppLicense, AppAssignment, AppRequest, AppInstall |
/api/v1/app_store |
src/cluster_erp/modules/app_store/routes.py (router) |
Data model¶
| Entity | Table | Key fields | Notes | Source |
|---|---|---|---|---|
StoreApp |
app_store_apps |
app_key, enabled, sort, manifest |
manifest is JSONB with labels, screen wiring, and covered modules |
src/cluster_erp/modules/app_store/models.py:14-52 |
AppLicense |
app_store_licenses |
raw_text, licensee, status_cache, last_seen_at |
newest live row wins | src/cluster_erp/modules/app_store/models.py:55-75 |
AppAssignment |
app_store_assignments |
app_key, role_id, intent |
unique (app_key, role_id) among live rows |
src/cluster_erp/modules/app_store/models.py:77-104 |
AppRequest |
app_store_requests |
app_key, requested_by, status, decided_by, decision_reason |
src/cluster_erp/modules/app_store/models.py:107-120 |
|
AppInstall |
app_store_installs |
app_key, status, app_version, consented_modules |
unique app_key among live rows |
src/cluster_erp/modules/app_store/models.py:123-162 |
The catalogue holds 28 apps, 6 of them system apps, counted from the entries of catalog.json.
Screens and metadata¶
The module declares no metadata entities; its surfaces are dedicated apps, not metadata screens (src/cluster_erp/modules/app_store/module.py:65-79). The licence card shows License Status (webapp/i18n/en.json key license.status_title) and the store shows License required for an unlicensed app (webapp/i18n/en.json key store.license_needed). The client code is documented in gated app bundles.
Status and actions¶
| Record | Values | Source |
|---|---|---|
| Licence state | none, invalid, active, grace, read_only |
src/cluster_erp/modules/app_store/license.py:9-14 |
AppInstall.status |
installed, uninstalled |
src/cluster_erp/modules/app_store/services.py:169-189 |
AppRequest.status |
pending, approved, denied |
src/cluster_erp/modules/app_store/models.py:114-116, src/cluster_erp/modules/app_store/services.py:352 |
AppAssignment.intent |
required, available, blocked |
src/cluster_erp/modules/app_store/services.py:282 |
| Action | Label | Condition | Endpoint | Permission | Source |
|---|---|---|---|---|---|
| upload licence | Upload and Verify (license.upload) |
licensing armed; document verifies | POST /api/v1/app_store/license |
license.manage |
src/cluster_erp/modules/app_store/routes.py:71-82 |
| remove licence | none in metadata | a licence exists | DELETE /api/v1/app_store/license |
license.manage |
src/cluster_erp/modules/app_store/routes.py:85-92 |
| request app | none in metadata | authenticated | POST /api/v1/app_store/apps/{app_key}/request |
authentication only | src/cluster_erp/modules/app_store/routes.py:184-191 |
| decide request | none in metadata | request pending |
POST /api/v1/app_store/requests/{request_id}/decide |
store_app.assign |
src/cluster_erp/modules/app_store/routes.py:228-264 |
| install, reconsent | none in metadata | known app; reconsent needs installed |
POST /api/v1/app_store/apps/{app_key}/install, /reconsent |
store_app.install |
src/cluster_erp/modules/app_store/routes.py:267-285 |
| uninstall | none in metadata | not a system app | POST /api/v1/app_store/apps/{app_key}/uninstall |
store_app.uninstall |
src/cluster_erp/modules/app_store/routes.py:288-295 |
Business rules¶
- A seat rule
blockedon any of the user's roles wins; once anyrequiredoravailablerule exists, only those roles get the app; an app without rules is open (src/cluster_erp/modules/app_store/services.py:23-47). - Approving an app request makes the app
availablefor every live role of the requester (src/cluster_erp/modules/app_store/services.py:357-361,src/cluster_erp/modules/app_store/routes.py:243-252). - Re-seeding never re-enables an app that an operator withdrew from sale (
src/cluster_erp/modules/app_store/services.py:91-99). - A replaced or removed licence stays as a deactivated row (
src/cluster_erp/modules/app_store/services.py:246-265).
Events and queries¶
The module registers no EventBus handlers (src/cluster_erp/modules/app_store/module.py:59-60). It reads role ids through role_ids_for in modules/auth/guards.py (src/cluster_erp/modules/app_store/routes.py:250-252).
| Direction | Contract class | Handler or emitter | Source |
|---|---|---|---|
| none | none | none | src/cluster_erp/modules/app_store/module.py:59-60 |
Permissions¶
| Permission key | Used by | Source |
|---|---|---|
license.manage |
licence upload and removal | src/cluster_erp/modules/app_store/routes.py:71-92 |
store_app.assign |
assignments, request list, request decision | src/cluster_erp/modules/app_store/routes.py:158-264 |
store_app.install |
install and reconsent | src/cluster_erp/modules/app_store/routes.py:267-285 |
store_app.uninstall |
uninstall | src/cluster_erp/modules/app_store/routes.py:288-295 |
The four codes are seeded as extra codes (src/cluster_erp/server/app.py:801-807). The registry, the licence read, the asset route, and app requests need authentication only (src/cluster_erp/modules/app_store/routes.py:18-66, src/cluster_erp/modules/app_store/routes.py:103-110).
Privacy, retention, and history¶
The module holds no personal data beyond user and role ids on requests and assignments; it has no privacy_handlers.py. All tables derive from BaseModel, so their changes are in change history; see change history.
Analytics facts¶
This module has no analytics_facts.py.
Settings and reference data¶
license_public_key arms licensing; when empty, every catalogue app is entitled (src/cluster_erp/config/settings.py:566-569). The catalogue itself is vendor data in catalog.json (src/cluster_erp/modules/app_store/services.py:50-63).
Tests¶
Found by file name in tests/ and web-client/.
| Kind | File | Covers |
|---|---|---|
| pytest | tests/test_entitlement_stamp.py |
entitlement cache version stamp (2 tests) |
| pytest | tests/test_store_catalog_assets.py |
catalogue screenshots and captions (4 tests) |
| e2e | web-client/_e2e_app_store.mjs, web-client/_e2e_store.mjs, web-client/_e2e_store_uibook.mjs |
store and app manager |
| e2e | web-client/_e2e_license.mjs |
licence upload and states |
Terms introduced¶
| Term | Code identifier | Definition | Turkish term | Source |
|---|---|---|---|---|
| app request | AppRequest |
A user's request to be granted a seat on an app. | uygulama isteği | src/cluster_erp/modules/app_store/models.py:107 |
| system app | manifest.system |
A catalogue app that is always entitled and cannot be uninstalled. | sistem uygulaması | src/cluster_erp/modules/app_store/services.py:182-189 |
Related pages¶
Verified against commit 92f8051 on 2026-11-13.