File & Image Storage (S3 / MinIO)
Overview
Every file a user uploads through the CMS passes through a single service, internal/storage.Service,
which wraps S3/MinIO. That covers rich message images, rich menu images, template hero images,
images embedded in content pages, the CSV files behind audiences, customer databases, and import
mappings, and user avatars.
The internal/modules/storage module has no routes of its own (RegisterRoutes is a no-op),
because in the original NestJS code it was a @Global provider with no controller. The module is
just a set of type aliases pointing at the shared service exposed as Deps.Storage.
Business Flow
- A controller receiving
multipart/form-dataconverts the upload into astorage.File, which carriesoriginalName,extension,mimetype, andbuffer— mirroringMemoryStoredFilefrom nestjs-form-data. - It then calls the method that matches the job.
PutTemp/PutTempBuffer— temporary storage before the user confirmsPutImage/PutImageBuffer— stores an image in the folder given byFolderNamePutFile— stores a general file such as a CSV
- Files are organized under each tenant's root folder, resolved by
GetRootFolder. - When reading back:
GetPublicUrl/GetPublicFileUrl— public URLs, needed for assets sent to LINE, which must be externally reachableGeneratePresignedUrl— expiring URLs for files that should not be publicly exposedGetImage/GetPathUrl— fetch a file or translate a path
- Additional operations include
DeleteObject,IsFileExist, andCopyObjectFromPublicUrl. The last is used when duplicating content or cloning a rich menu, where the image must be copied from its existing URL. - Stored CSVs are later read by the CSV Engine when paginating LINE user lists.
Key Files & Functions
| File | Role |
|---|---|
internal/storage/storage.go | The real Service — a port of src/storage/storage.service.ts |
internal/storage/image.go | Image handling: file-type validation and filename normalization |
internal/modules/storage/service.go | Type aliases for Service, File, StorageType, FolderName, ImageFileType, RootFolder |
internal/modules/storage/controller.go | RegisterRoutes is a no-op |
Full method set: GetPublicUrl, GetPathUrl, GetImage, PutTemp, PutImage, PutTempBuffer,
PutImageBuffer, DeleteObject, CopyObjectFromPublicUrl, IsFileExist, GetRootFolder,
PutFile, GetPublicFileUrl, GeneratePresignedUrl
Endpoints that actually upload files — they live in other modules but all funnel into this service.
| Route | Module |
|---|---|
POST /api/content-pages/upload-image | Content Page |
POST /api/menu-builder/upload-image | Menu Builder |
POST /api/template-message/upload-image | Template Message |
POST /api/rich-message (form-data) | Rich Message |
POST /api/rich-menu (form-data) | Rich Menu |
POST /api/friend-track/:id/refs/upload | Friend Track |
POST /api/customer-database and /preview | Customer Database |
POST /api/import-mapping/upload | Import Mapping |
POST /api/line-users/import-all-friends | LINE User Management |
POST /api/user (avatar) | User Management |
Connections to Other Services
- Permission — no routes of its own, so access control is enforced at the calling module's endpoint.
- Infrastructure — S3 or MinIO depending on
StorageType, configured from environment variables ininternal/config/config.go. - Consumers —
Deps.Storageis injected into every module, andDeps.CSVEnginereceives the same storage service.