Skip to content

Commit aaef5f6

Browse files
authored
feat: upgrade storage config structure to be future-ready (#7)
* feat: upgrade storage config structure to be future-ready * feat: types for multipart * feat: support for complete and abort urls * feat: add tests, endpoints for completion and consumption of etagging, etc * feat: add more validation after manual edge cases * feat: delete feature md
1 parent 18267dd commit aaef5f6

File tree

17 files changed

+1620
-227
lines changed

17 files changed

+1620
-227
lines changed

Makefile

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,8 @@ stop-buildx:
3535

3636
build-image: setup-buildx
3737
@echo "Building image for AMD64 and 386 🔨"
38-
@docker buildx build --platform linux/amd64,linux/386 -t $(IMAGE_FULL_NAME) .
38+
@docker buildx build --platform linux/amd64,linux/386 -t $(IMAGE_FULL_NAME) --load .
3939
@echo "Image built successfully 🎉"
40-
@echo "Note: Multi-platform images are not loaded locally. Use --push to push to registry."
4140

4241
build-image-arm64: setup-buildx
4342
@echo "Building image for ARM64 🔨"

README.md

Lines changed: 226 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -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
```
24150
GET /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
```
35167
GET /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

92301
Create a `.env` file for local development:

0 commit comments

Comments
 (0)