Media Library Applet
Overview
The Media Library applet is the file store behind the Customer Portal. You create drives (named containers), put directories and files — images, video, audio, PDF — inside them, and set a share policy per file that decides whether the storefront can show it. It also keeps a small list of categories (labels) for the file store. The CP Commerce Admin applet then points its gallery and slideshow widgets at a drive, offers PDF files as menu-item targets, and can copy post images to and from the library.
It is a master-data applet. Nothing in it posts to stock or the General Ledger, and nothing in it creates a document. Its records live in the platform’s file-management (fm) tables — bl_fm_drive_hdr, bl_fm_file_hdr, bl_fm_drive_login_subject_link, bl_fm_label_hdr — and are served through the core2/tnt/dm/fm/ endpoints. The applet itself is built inside the CP Commerce repository (blg-applets-wavelet-cp-commerce, project media-library-applet) and is registered as media-library-applet.
Where it fits
| Direction | Applet / system | How it connects |
|---|---|---|
| Downstream | CP Commerce Admin — Website › Layout Instance › node configuration | The Image Gallery, Video Gallery and Audio Gallery widgets and the slideshow Play List are configured with a drive chosen from this applet’s drives (nodes-edit.component.ts, getDrive() and the selectedDrive / driveGuid controls). |
| Downstream | CP Commerce Admin — Website › Menu Items | A menu item of the PDF type lists only files whose mime type contains pdf and whose share policy is ANYONE_WITH_LINK (menu-item-query.component.ts). |
| Downstream | CP Commerce Admin — Website › Post › Manage Image, Notification › Manage Image | Post images can be created from the library and copied to it (CmsPostImageController: create-from-media-library, create-to-media-library). |
| Downstream | CP Commerce Admin — Facilities / Activities | Each facility and activity keeps its own media links to library files (bl_cms_facilities_media_library_link, bl_cms_activities_media_library_link, both carrying the file’s fm_file_guid and url_key). |
| Downstream | The Customer Portal storefront (the wavelet-cp-commerce app) | Builds every image URL as core2/tnt/dm/fm/files/file/raw/{url_key} (construct-image-url.pipe.ts). That endpoint is anonymous and serves a file only if its share policy is ANYONE_WITH_LINK (FileController.getFileRawByUrlKey). |
| Upstream | Tenant Admin | The Manage Users tab lists the tenant’s confirmed logins (app_login_principal) as candidates for a drive. |
| Upstream | Tenant permissions (TntFmPermissions) | Every drive, file, drive-user and label endpoint checks an API_TNT_DM_FM_* permission unless the login is a tenant owner or admin. |
flowchart LR
D[Drive] --> F[Directory / File]
F --> P{Share policy}
P -- RESTRICTED --> L[login-ep link: only guests in acl_config]
P -- ANYONE_WITH_LINK --> R[raw link: anonymous]
R --> W[Storefront widgets, PDF menu items]
D --> G[CP Commerce Admin gallery and play-list widgets]
Screens and menus
The sidebar has two entries, Drive and Categories; the applet opens on Drive (app.routing.ts, empty path redirects to drives). Both work in the usual two-column stack.
Drive
- Drives Listing — server-side ag-grid: Name, Code, Status, Date Created (sorted newest first). A keyword search (drive name) and an advanced search on Status, Creation Date and Modified Date. The + button opens Create Drive. Each row also triggers a lookup of the creator’s e-mail that is fetched and then discarded (
getRowsFactory), so the listing makes one extra call per row for nothing. - Create Drive — Drive Code, Drive Name, Status, Description; CREATE.
- Drive Edit — three tabs. Details (code read-only, name, description, status, created/modified by and date, Delete Drive); Manage Files; Manage Users. SAVE at the top.
- Manage Files (inside Drive Edit) — a tree grid of the drive’s directories and files (up to 10,000 rows loaded in one call, ordered by sort order), with a quick filter. The + button opens Create File; clicking a row opens Edit File.
- Create File — File Name, Status, Description, Parent Directory, File Type (
DIRECTORYorFILE); for a file, Sort Order and a drop-zone that previews an image, plays a video or renders a PDF before upload. CREATE. - Edit File — the same fields (type read-only) with the stored media shown, a replacement drop-zone, Save, Share File and DELETE.
- Share File — Access Policy (
RESTRICTED/ANYONE_WITH_LINK) and the matching File Link with a Copy Link button. Changing the policy saves immediately; Done only goes back. The “Add people” e-mail and role controls are commented out of the template, so guests cannot be granted from the UI (see Lifecycle). - Manage Users (inside Drive Edit) — the drive’s login-subject links; + opens Select Users to add to the drive (a checkbox grid of the tenant’s logins, Add); clicking a row opens Removing user from the drive (Delete).
Categories
- Categories Listing — Categories Name, Categories Code, Status, Creation Date; keyword search and advanced search (Category Name, Created / Modified Date).
- Create Category / Category Edit — Category Name, Category Code, Status, Description (edit adds the read-only audit fields and a Delete button).
Settings (gear): System Configuration › Field Settings, Default Selection; Client Side Permissions › Applet Access; Server Side Permissions › Permission Set, User Permission, Team Permission, Role Permission; Integration › Triggers (the shared webhook stub); Developer Tools › Applet Log, Reset Applet State (both route to the 404 page). Personalization: Default Selection and the shared Sidebar screen.
No screenshots are published for this applet; none has been captured on a tenant free of customer data.
Configuration
Before you can use it
- A confirmed tenant login for anyone who will manage drives, and — unless the login is a tenant owner or admin — the file-management permissions from
TntFmPermissions, assigned through Settings › Permission Set / User / Team / Role Permission:API_TNT_DM_FM_DRIVE_CREATE/UPDATE/DELETE/READ(orOWNER/ADMIN) for drives; the same set withFILEfor directories and files, plusAPI_TNT_DM_FM_FILE_UPDATE_ACL_CONFIG_POLICYfor the Share File screen;API_TNT_DM_FM_DRIVE_SUBJECT_*for Manage Users;API_TNT_DM_FM_LABEL_*andAPI_TNT_DM_FM_LABEL_LIST_READfor Categories (DriveController,FileController,DriveSubjectController). Uploading a file’s content needsFILE_UPDATE(or owner / admin), notFILE_CREATE— the multipart create endpoint checks the update permission (FileController.createFile, multipart variant). - For the Categories menu only: a label list with code
Media_libraryand label typeCP_COMmust already exist inbl_fm_label_list_hdr. Create Category looks it up and attaches every new category to it (categories-create.component.tsL90-96); nothing in this applet or in the backend creates that list. If it is missing, the create screen fails in the browser before it can call the server (see Troubleshooting). - For the storefront: a website configured in CP Commerce Admin whose widgets or menu items will reference a drive or a file, and files set to
ANYONE_WITH_LINK.
Applet settings
Settings are applet-local in layout only. app.routing.ts routes settings/field-settings and settings/default-selection to the applet’s own components under components/settings-container/; the shared FieldConfigurationComponent from blg-shared-utilities is not routed at all. Neither local screen is functional, so the four-proof test (declared, rendered, persisted, consumed) fails for every control:
- Field Settings is a static template — eight slide toggles (Lines Settings: Unit Discount, SST/VAT/GST, WHT, Blanket Order; Department Settings: Segment, G/L Dimension, Profit Center, Project) with no form binding, a SAVE button with no click handler and an empty component class (
field-configuration.component.html,.ts). Rendered; not declared, not persisted, not consumed. - Default Selection renders Default Branch and Default Location and writes each change into
this.appletContainer.bl_applet_extsunderAPPLET_SETTINGS.appletContaineris never assigned: theappletSettings$input is never bound (the routed host is the shared<app-settings>wrapper, whoseonSaveDefaultis commented out and which renders the child through a barerouter-outlet), so changing a value throws in the browser console and SAVE emits to nobody. No code in the applet readsDEFAULT_BRANCHorDEFAULT_LOCATION. Rendered; not persisted, not consumed. - Personalization › Default Selection has the same defect (
personal-default-settings.component.ts, the subscription that would load the container is commented out at L31-38).
No exposed, working control found (routes and settings components checked at commit 813f007c8, 2026-08-18).
Settings in other applets that control this applet
| Setting | Where it is set | Effect here |
|---|---|---|
| A widget’s Select Drive / Select Drive For Play List | CP Commerce Admin › Website › Layout Instance › node configuration (Image / Video / Audio Gallery, slideshow) | Decides which of this applet’s drives a storefront gallery reads. The widget lists drives by code. |
| A menu item’s PDF target | CP Commerce Admin › Website › Menu Items | Only files with mime type pdf and policy ANYONE_WITH_LINK are offered. |
Feature visibility / permissions
The applet resolves no client-side permission of its own: app.component.ts only wires the layout, and the Applet Access screen is the shared stub. The registry table bl_applet_client_side_perm_dfn could not be queried for media-library-applet while this page was written (the read-only tunnel was down on 2026-09-15), so the row count is unverified. Access is decided by the server-side API_TNT_DM_FM_* permissions listed under Before you can use it; the API answers not authorised when none applies. The Permission Set screen’s target picker (permission.resolver.ts) knows the CP Commerce target views (Company, Branch, CMS Website, Label, Label List, and so on) but has no entry for drives or files, so a permission scoped to one drive cannot be assigned from this applet.
Fields
Required-ness below is the server’s (DriveDataConsistencyObject, FileDataConsistencyObject, DriveSubjectDataConsistencyObject, FmLabelDataConsistencyObject) unless marked client. The server validators check identity, status, revision, audit columns and referential links only; they do not check that a name or code is present, so those rules are enforced by the form alone.
Create Drive / Drive Edit › Details
| Field | Meaning | Required | Notes / validation |
|---|---|---|---|
| Drive Code | bl_fm_drive_hdr.code | Yes (client) | Max 255 characters; read-only after creation. The server does not check uniqueness (DriveDataConsistencyObject has no code rule). |
| Drive Name | bl_fm_drive_hdr.name | Yes (client) | Max 255 |
| Status | bl_fm_drive_hdr.status | Yes | ACTIVE / INACTIVE; the server rejects a null status (DRIVE_HDR_OBJECT_STATUS_IS_NULL_OR_EMPTY) |
| Description | bl_fm_drive_hdr.description | No | |
| Created By / Date, Modified By / Date | Audit columns, shown as the login’s e-mail principal | — | Read-only |
Create File / Edit File
| Field | Meaning | Required | Notes / validation |
|---|---|---|---|
| File Name | bl_fm_file_hdr.file_name | Yes (client) | Max 255. For an uploaded file the server overwrites it with the uploaded file’s own name (FileController.createFile multipart, uploadFile, replaceFile), so the name typed here survives only for directories. |
| Status | bl_fm_file_hdr.status | Yes | ACTIVE / INACTIVE |
| Description | bl_fm_file_hdr.descr | No | |
| Parent Directory | bl_fm_file_hdr.parent_guid | No | Offers NONE plus the drive’s directories. A file’s parent must be a directory: FILE_HDR_OBJECT_PARENT_TYPE_IS_WRONG when the parent is itself a file; FILE_HDR_OBJECT_PARENT_GUID_DOES_NOT_EXIST when it is gone. |
| File Type | bl_fm_file_hdr.type | Yes | DIRECTORY or FILE; read-only on edit. A directory with no parent must belong to a drive (FILE_HDR_OBJECT_DRIVE_GUID_IS_NULL). |
| Sort Order | bl_fm_file_hdr.sort_order | No | Files only; the file tree and the storefront galleries order by it |
| File (drop-zone) | The binary, stored as an app_file_item and linked by app_file_item_guid | Yes on create for type FILE (client: Please select a file) | Accepts audio/*, video/*, image/*, .pdf. On edit, dropping a new file replaces the stored one (replaceFile) after the header is saved; the server records name, extension and mime type from the upload. |
Share File
| Field | Meaning | Required | Notes |
|---|---|---|---|
| Access Policy | bl_fm_file_hdr.acl_policy.access | — | RESTRICTED (default on create) or ANYONE_WITH_LINK. Saved immediately on change through acl-policy/backoffice-ep/{guid}; the change also cascades to the file’s direct children when it is a directory (overwriteChildren defaults to true, one level only). |
| File Link | Computed | — | …/fm/files/file/raw/{url_key}?tenantCode=… for ANYONE_WITH_LINK; …/fm/files/file/url-key/login-ep/{url_key}?tenantCode=… for RESTRICTED. Copy Link copies it. |
Manage Users
| Field | Meaning | Required | Notes |
|---|---|---|---|
| Email / Phone (select grid) | A tenant login (app_login_principal.principal_id) | — | Checkbox grid; Add writes a bl_fm_drive_login_subject_link with status ACTIVE. The server rejects a link whose drive or subject does not exist and a duplicate drive-subject pair (DRIVE_SUBJECT_OBJECT_COMPOUND_PK_ALREADY_EXISTS). |
| Email, Status (edit) | Shown read-only | — | The e-mail is never resolved: the listing’s Email column displays the subject_guid and the edit screen’s Email box is blank (users-listing.component.ts L92, L117). |
Create Category / Category Edit
| Field | Meaning | Required | Notes |
|---|---|---|---|
| Category Name | bl_fm_label_hdr.name | Yes (client) | |
| Category Code | bl_fm_label_hdr.code | Yes (client) | Not checked for uniqueness by the label validator |
| Status | bl_fm_label_hdr.status | Yes | ACTIVE / INACTIVE |
| Description | bl_fm_label_hdr.descr | No | |
| (hidden) label list | bl_fm_label_hdr.label_list_guid | — | Set to the Media_library / CP_COM list found at load time |
Lifecycle and effects
Endpoints. The applet uses five blg-akaun-ts-lib services, all under core2/tnt/dm/fm/: DriveService → drives; FileService → files (JSON create for directories, multipart POST files with a data part for uploads, PUT files, PUT files/replace-file/{guid}, PUT files/acl-policy/backoffice-ep/{guid}, GET files/file/{guid} for the preview, DELETE files/{guid}); DriveSubjectService → drive-subject-links; FileLabelService → labels/backoffice-ep; FileLabelListService → label-lists/backoffice-ep. It does not call the CMS website, menu, post, widget, pricing-scheme or shipping-pricebook endpoints — those services are only injected by the shared permission resolver.
What it writes.
- Create Drive posts a
bl_fm_drive_hdr; SAVE re-reads the drive by guid and puts name, code, description and status back; Delete Drive is a soft delete of the header and its extensions (DriveUow.delete). Files are not touched when their drive is deleted; they stay inbl_fm_file_hdrwith adrive_guidthat no longer resolves. - Create File for a
DIRECTORYposts a header only. For aFILEit sends one multipart request; the server stores the binary as anapp_file_item, generates a randomurl_key, and stamps the default share policy:acl_policy.access = RESTRICTEDandacl_config.guests = [{ the creator, EDITOR }](FileService.createFile,getDefaultAclCfgPolicy). - Save on Edit File re-reads the header, puts the editable fields, then — if a new file was dropped — calls
replace-file.updateFileon the server always restoresurl_key,acl_configandacl_policyfrom the database, so an edit can never change a share link or policy by accident; onlyacl-policy/acl-cfg-policyandreset-url-keycan. - DELETE on Edit File soft-deletes the header (
FileUow.delete). For a directory the applet first checks for children and refuses (Cannot delete this Directory as it has child directories/files); the server has no such rule, and deleting a file does not remove the storedapp_file_item. - Share File › Access Policy puts
{ access }toacl-policy/backoffice-ep/{guid}(permissionAPI_TNT_DM_FM_FILE_UPDATE_ACL_CONFIG_POLICY). TheaclConfigAddGuest$andaclConfigDeleteGuest$effects exist and would callacl-cfg-guest-add/acl-cfg-guest-delete, but no screen dispatches them: the Add people controls are commented out and Done dispatches nothing. - Manage Users › Add posts one
bl_fm_drive_login_subject_link; Delete removes it. Nothing reads these links: no query inDriveUoworFileUowjoinsbl_fm_drive_login_subject_link, and no other backend package references the table. Adding a user to a drive therefore grants no access; access is decided by theAPI_TNT_DM_FM_*permissions and, for reading a file’s content, by its share policy. - Categories post, put and soft-delete
bl_fm_label_hdrrows. No screen attaches a category to a drive or a file — there is no label-link call anywhere in the applet — so a category is a standalone record until something else uses it.
How the share policy is enforced. GET files/file/raw/{url_key} is an anonymous tenant endpoint that returns the binary only when acl_policy.access == ANYONE_WITH_LINK; otherwise not authorised. GET files/file/url-key/login-ep/{url_key} requires a login and that the login appears in the file’s acl_config.guests with role VIEWER, EDITOR or COMMENTER (FileService.fulfillAclCfgRead). Because the UI can only add the creator as a guest (by default) and cannot add anyone else, a RESTRICTED link works for the uploader alone. The storefront always uses the raw link, so every file the storefront must display has to be ANYONE_WITH_LINK.
Posting proof. Not a document applet: no ServerDocTypes entry, no signums, no JournalPostingTypeHandler handler, no stock processor. It writes the bl_fm_* tables and app_file_item only.
Related applets
- CP Commerce Admin — the consumer: gallery and play-list widgets read a drive, PDF menu items read
ANYONE_WITH_LINKfiles, post images move to and from the library, facilities and activities link library files. Its own Manage Image screen is a separate image store for posts, not this applet. - Tenant Admin — the confirmed logins offered by Manage Users, and where the
API_TNT_DM_FM_*permissions a media manager needs are ultimately granted.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| An image or PDF referenced by a storefront widget or menu item does not load | The file’s share policy is RESTRICTED (the default). The storefront requests files/file/raw/{url_key}, which serves only ANYONE_WITH_LINK files; the PDF picker in CP Commerce Admin hides restricted files entirely | Edit File › Share File › set Access Policy to ANYONE_WITH_LINK |
The RESTRICTED link works for the uploader and for nobody else | The login-ep link admits only logins listed in the file’s acl_config.guests; the only guest the UI ever writes is the creator, and the Add people controls are commented out | Use ANYONE_WITH_LINK, or add guests through the API (acl-cfg-guest-add/backoffice-ep/{guid}) |
| A user added under Manage Users still cannot open the drive or its files | bl_fm_drive_login_subject_link is written but read by nothing on the server; access comes from the API_TNT_DM_FM_* permissions | Grant the permissions under Settings › Permission Set / User Permission |
| Create on Create File does nothing, or the toast Please select a file | Type FILE was chosen without dropping a file | Drop or browse a file, then Create |
| The saved file name is not the one typed | The server replaces file_name with the uploaded file’s own name on upload and on replace | Rename the file on disk before uploading; the typed name is kept only for directories |
| Cannot delete this Directory as it has child directories/files | Client-side guard in Edit File | Delete or move the children first (Parent Directory on each child) |
| Create Category fails in the browser before anything is saved | No label list with code Media_library and type CP_COM exists, so label.data[0] is undefined at load and the create request is never sent | Create that label list (the backend seeds none); then retry |
| Changing Default Branch / Default Location in Settings throws and saves nothing | The Default Selection screen’s container is never loaded and no code reads those keys | Nothing to configure; treat both settings screens as inert |
| Only one of several ticked users is added | Select Users › Add fills a single link object inside the loop and posts it once, so the last ticked row wins; the duplicate check queries the wrong column (subject_guid = drive guid) and never fires | Add users one at a time |
| The Manage Users grid shows a GUID under Email | The e-mail lookup is commented out (users-listing.component.ts L92) | Match the GUID against Tenant Admin’s login list |
| Drive deleted but its files still appear in searches | Drive deletion soft-deletes the header only; files keep their drive_guid | Delete the files first, then the drive |
| not authorised on create, save or delete | Missing API_TNT_DM_FM_DRIVE_* / FILE_* / DRIVE_SUBJECT_* / LABEL_* permission and the login is not a tenant owner or admin. Uploads need FILE_UPDATE, not FILE_CREATE | Assign the permission set |
No issue in the shared task tracker describes a user-facing failure of this applet; the repository history for the project is the two 2026-07 sub-query clean-ups and the 2024-11 sort-order and thumbnail work.
Related documentation
- E-Commerce module — where the file store sits between the back office and the storefront.
- CP Commerce Admin — the widgets, menu items, posts and facilities that reference drives and files.