@@ -4,11 +4,13 @@ A lightweight Go service for processing and serving images with YAML-configurabl
44
55## Features
66
7+ - ** Presigned Uploads** : ` /v1/uploads/presign ` - secure direct-to-S3 uploads with validation
78- ** Original Image Serving** : ` /originals/{type}/{image_id} ` - serve original images directly from storage
89- ** Thumbnail Generation** : ` /thumb/{type}/{image_id} ` - on-demand thumbnail generation
9- - ** YAML Configuration** : Different processing rules per image type (avatar, photo, banner)
10+ - ** Unified Configuration** : Profile-based YAML config combining upload and processing rules
1011- ** Multiple Formats** : Convert images to WebP, JPEG, PNG with configurable quality
11- - ** S3 Integration** : Fetch and store images in AWS S3 with folder organization
12+ - ** Video Support** : Ready for video upload and processing (processing features coming soon)
13+ - ** S3 Integration** : Direct S3 uploads with multipart support for large files
1214- ** CDN-Optimized** : Cache-Control and ETag headers for optimal CDN performance
1315- ** Graceful Shutdown** : Production-ready server lifecycle management
1416
@@ -19,17 +21,147 @@ A lightweight Go service for processing and serving images with YAML-configurabl
1921
2022## API Endpoints
2123
24+ ### Presigned Uploads
25+ ```
26+ POST /v1/uploads/presign
27+ ```
28+ Generates presigned URLs for secure direct-to-S3 uploads.
29+
30+ ** Request Body:**
31+ ``` json
32+ {
33+ "key_base" : " unique-file-id" ,
34+ "ext" : " jpg" ,
35+ "mime" : " image/jpeg" ,
36+ "size_bytes" : 1024000 ,
37+ "kind" : " image" ,
38+ "profile" : " avatar" ,
39+ "multipart" : " auto"
40+ }
41+ ```
42+
43+ ** Response for Single Upload:**
44+ ``` json
45+ {
46+ "object_key" : " originals/avatars/ab/unique-file-id.jpg" ,
47+ "upload" : {
48+ "single" : {
49+ "method" : " PUT" ,
50+ "url" : " https://presigned-s3-url" ,
51+ "headers" : {
52+ "Content-Type" : " image/jpeg" ,
53+ "If-None-Match" : " *"
54+ },
55+ "expires_at" : " 2024-01-01T12:00:00Z"
56+ }
57+ }
58+ }
59+ ```
60+
61+ ** Response for Multipart Upload:**
62+ ``` json
63+ {
64+ "object_key" : " originals/avatars/ab/unique-file-id.jpg" ,
65+ "upload" : {
66+ "multipart" : {
67+ "upload_id" : " abc123xyz" ,
68+ "part_size" : 8388608 ,
69+ "parts" : [
70+ {
71+ "part_number" : 1 ,
72+ "method" : " PUT" ,
73+ "url" : " https://presigned-s3-part-url-1" ,
74+ "headers" : {"Content-Type" : " image/jpeg" },
75+ "expires_at" : " 2024-01-01T12:00:00Z"
76+ }
77+ ],
78+ "complete" : {
79+ "method" : " POST" ,
80+ "url" : " https://your-api/v1/uploads/originals%2Favatars%2Fab%2Funique-file-id.jpg/complete/abc123xyz" ,
81+ "headers" : {"Content-Type" : " application/json" },
82+ "expires_at" : " 2024-01-01T12:00:00Z"
83+ },
84+ "abort" : {
85+ "method" : " DELETE" ,
86+ "url" : " https://your-api/v1/uploads/originals%2Favatars%2Fab%2Funique-file-id.jpg/abort/abc123xyz" ,
87+ "headers" : {},
88+ "expires_at" : " 2024-01-01T12:00:00Z"
89+ }
90+ }
91+ }
92+ }
93+ ```
94+
95+ ** Parameters:**
96+ - ` key_base ` : Unique identifier for the file
97+ - ` ext ` : File extension (optional, for backward compatibility)
98+ - ` mime ` : MIME type of the file
99+ - ` size_bytes ` : File size in bytes
100+ - ` kind ` : Media type (` image ` or ` video ` )
101+ - ` profile ` : Configuration profile to use (` avatar ` , ` photo ` , ` video ` , etc.)
102+ - ` multipart ` : Upload strategy (` auto ` , ` force ` , or ` off ` )
103+
104+ ### Multipart Upload Completion
105+ ```
106+ POST /v1/uploads/{object_key}/complete/{upload_id}
107+ ```
108+ Completes a multipart upload by providing the ETags for all uploaded parts.
109+
110+ ** Request Body:**
111+ ``` json
112+ {
113+ "parts" : [
114+ {
115+ "part_number" : 1 ,
116+ "etag" : " \" d41d8cd98f00b204e9800998ecf8427e\" "
117+ },
118+ {
119+ "part_number" : 2 ,
120+ "etag" : " \" 098f6bcd4621d373cade4e832627b4f6\" "
121+ }
122+ ]
123+ }
124+ ```
125+
126+ ** Response:**
127+ ``` json
128+ {
129+ "status" : " completed" ,
130+ "object_key" : " originals/avatars/ab/unique-file-id.jpg"
131+ }
132+ ```
133+
134+ ### Multipart Upload Abort
135+ ```
136+ DELETE /v1/uploads/{object_key}/abort/{upload_id}
137+ ```
138+ Aborts a multipart upload and cleans up any uploaded parts.
139+
140+ ** Response:**
141+ ``` json
142+ {
143+ "status" : " aborted" ,
144+ "upload_id" : " abc123xyz"
145+ }
146+ ```
147+
22148### Thumbnails
23149```
24150GET /thumb/{type}/{image_id}?width=512
151+ POST /thumb/{type}/{image_id}
25152```
26- Generates and serves thumbnails with configurable dimensions.
153+ Generates and serves thumbnails with configurable dimensions. POST requests require authentication.
27154
28- ** Parameters:**
155+ ** GET Parameters:**
29156- ` type ` : Image category (avatar, photo, banner, or any configured type)
30157- ` image_id ` : Unique identifier for the image
31158- ` width ` : Image width in pixels (optional, defaults to the type's ` default_size ` from storage config)
32159
160+ ** POST Parameters:**
161+ - Requires authentication (API key)
162+ - Request body should contain the image data
163+ - Used for uploading images to be processed
164+
33165### Original Images
34166```
35167GET /originals/{type}/{image_id}
@@ -50,43 +182,120 @@ Returns service health status.
50182
51183### Storage Configuration (storage-config.yaml)
52184
53- MediaFlow uses YAML configuration to define processing rules per image type :
185+ MediaFlow uses YAML configuration to define profiles that combine upload settings and processing rules :
54186
55187``` yaml
56- storage_options :
188+ profiles :
57189 avatar :
58- origin_folder : " originals/avatars"
190+ # Upload configuration
191+ kind : " image"
192+ allowed_mimes : ["image/jpeg", "image/png", "image/webp"]
193+ size_max_bytes : 5242880 # 5MB
194+ multipart_threshold_mb : 15
195+ part_size_mb : 8
196+ token_ttl_seconds : 900 # 15 minutes
197+ storage_path : " originals/avatars/{shard?}/{key_base}"
198+ enable_sharding : true
199+
200+ # Processing configuration
59201 thumb_folder : " thumbnails/avatars"
60202 sizes : ["128", "256"]
61203 default_size : " 256"
62204 quality : 90
63205 convert_to : " webp"
64206
65207 photo :
66- origin_folder : " originals/photos"
208+ kind : " image"
209+ allowed_mimes : ["image/jpeg", "image/png", "image/webp"]
210+ size_max_bytes : 20971520 # 20MB
211+ multipart_threshold_mb : 15
212+ part_size_mb : 8
213+ token_ttl_seconds : 900
214+ storage_path : " originals/photos/{shard?}/{key_base}"
215+ enable_sharding : true
216+
67217 thumb_folder : " thumbnails/photos"
68218 sizes : ["256", "512", "1024"]
69219 default_size : " 256"
70220 quality : 90
71221 convert_to : " webp"
72222
73- banner :
74- origin_folder : " originals/banners"
75- thumb_folder : " thumbnails/banners"
76- sizes : ["512", "1024", "2048"]
77- default_size : " 512"
78- quality : 95
79- convert_to : " webp"
80-
223+ video :
224+ kind : " video"
225+ allowed_mimes : ["video/mp4", "video/quicktime", "video/webm"]
226+ size_max_bytes : 104857600 # 100MB
227+ multipart_threshold_mb : 15
228+ part_size_mb : 8
229+ token_ttl_seconds : 1800 # 30 minutes
230+ storage_path : " originals/videos/{shard?}/{key_base}"
231+ enable_sharding : true
232+
233+ thumb_folder : " posters/videos" # Video thumbnails
234+ proxy_folder : " proxies/videos" # Compressed versions
235+ formats : ["mp4", "webm"]
236+ quality : 80
237+
81238 default :
82- origin_folder : " originals"
239+ kind : " image"
240+ allowed_mimes : ["image/jpeg", "image/png"]
241+ size_max_bytes : 10485760 # 10MB
242+ multipart_threshold_mb : 15
243+ part_size_mb : 8
244+ token_ttl_seconds : 900
245+ storage_path : " originals/{shard?}/{key_base}"
246+ enable_sharding : true
247+
83248 thumb_folder : " thumbnails"
84249 sizes : ["256", "512"]
85250 default_size : " 256"
86251 quality : 90
87252 convert_to : " webp"
88253` ` `
89254
255+ ### Configuration Fields
256+
257+ #### Upload Configuration
258+ - ` kind`: Media type (`image` or `video`)
259+ - `allowed_mimes` : Array of allowed MIME types
260+ - `size_max_bytes` : Maximum file size in bytes
261+ - `multipart_threshold_mb` : Size threshold for multipart uploads
262+ - `part_size_mb` : Size of each multipart chunk
263+ - `token_ttl_seconds` : Presigned URL expiration time
264+ - `storage_path` : Template for where files are stored in S3 (supports `{key_base}`, `{ext}`, `{shard}`, `{shard?}`)
265+ - `enable_sharding` : Whether to use sharding for load distribution
266+
267+ # ### Processing Configuration
268+ - `thumb_folder` : Folder for storing thumbnails
269+ - `sizes` : Available thumbnail sizes
270+ - `default_size` : Default thumbnail size if none specified
271+ - `quality` : Image compression quality (1-100)
272+ - `convert_to` : Format to convert images to (`webp`, `jpeg`, etc.)
273+
274+ # ### Storage Path Templates
275+ The `storage_path` field uses a template system to define where files are stored :
276+ - `{key_base}` : The unique file identifier
277+ - `{ext}` : File extension
278+ - `{shard}` : Shard value (only when `enable_sharding: true`)
279+ - `{shard?}` : Optional shard (removed when `enable_sharding: false`)
280+
281+ **Sharding Modes:**
282+
283+ **Auto-sharding** (`enable_sharding: true`):
284+ - ` "originals/{shard?}/{key_base}"` → `originals/ab/my-file.jpg`
285+ - Shards auto-generated from key_base hash
286+ - Clients can optionally provide custom shard in request
287+
288+ **Fixed organization** (`enable_sharding: false`):
289+ - ` "originals/user123/{key_base}"` → `originals/user123/my-file.jpg`
290+ - ` "uploads/{year}/{month}/{key_base}"` → Custom organization
291+ - Any `{shard}` placeholders are removed
292+ - Custom shards in requests are ignored
293+
294+ Examples :
295+ - ` "originals/{key_base}"` → `originals/my-file.jpg`
296+ - ` "uploads/{shard?}/{key_base}"` → `uploads/ab/my-file.jpg` (with sharding)
297+ - ` "users/team-marketing/{key_base}"` → Fixed custom prefix
298+
90299# ## Environment Variables
91300
92301Create a `.env` file for local development :
0 commit comments