- Updated the `UploadDocuments` method to sanitize document file IDs before saving, ensuring only valid references are stored. - Introduced `DetachDocumentFileID` method in the `companyService` to remove file IDs from all companies referencing a deleted file, improving data integrity. - Enhanced the `companyRepository` with a new method to handle the removal of document file IDs from the database. - Updated the `filestore` handler to utilize the new detachment functionality when files are deleted, ensuring consistent state across domain entities. This update improves the management of document file IDs within the company domain, enhancing data integrity and reference handling.
MongoDB GridFS File Store Service
A comprehensive file storage service for the Opplens backend using MongoDB GridFS for scalable, distributed file storage.
Overview
The file store service provides a complete solution for managing file uploads, downloads, and deletions using MongoDB's GridFS. It supports:
- Secure file uploads with validation and size limits
- Metadata storage for tracking file information
- File categorization and tagging
- List and search functionality
- Stream-based downloads for efficient memory usage
- Access control and user tracking
Key Features
✅ MongoDB GridFS Backend - Native MongoDB file storage without external dependencies ✅ File Validation - Filename, MIME type, and size validation ✅ Metadata Support - Store rich metadata with files ✅ Category & Tags - Organize files with categories and tags ✅ Stream Processing - Efficient memory usage for large files ✅ Error Handling - Comprehensive error codes and messages ✅ Logging - Detailed operation logging ✅ Thread-Safe - Concurrent operations support
Architecture
┌─────────────────────────────────────┐
│ HTTP Handler (Echo) │
│ - Upload, Download, Delete, List │
└────────────┬────────────────────────┘
│
┌────────────▼────────────────────────┐
│ File Handler Service │
│ - Request validation │
│ - Response formatting │
└────────────┬────────────────────────┘
│
┌────────────▼────────────────────────┐
│ GridFS Service Interface │
│ - Upload, Download, Delete, List │
└────────────┬────────────────────────┘
│
┌────────────▼────────────────────────┐
│ MongoDB GridFS Implementation │
│ - Bucket operations │
│ - Metadata management │
└────────────┬────────────────────────┘
│
┌────────────▼────────────────────────┐
│ MongoDB Database │
│ - fs.files collection │
│ - fs.chunks collection │
└─────────────────────────────────────┘
Installation
The service is automatically initialized in the bootstrap:
// In cmd/web/bootstrap/bootstrap.go
fileStoreService, err := filestore.NewGridFSService(mongoManager, logger)
if err != nil {
logger.Fatal("Failed to initialize file store service", map[string]interface{}{
"error": err.Error(),
})
}
API Endpoints
Upload File
POST /api/v1/files/upload
Upload a file to GridFS storage.
Request:
- Content-Type:
multipart/form-data - Parameters:
file(required): The file to uploadcategory(optional): File category for organizationtags(optional, array): Tags for the filedescription(optional): File description
Response:
{
"file_id": "507f1f77bcf86cd799439011",
"filename": "profile.jpg",
"content_type": "image/jpeg",
"size": 102400,
"message": "File uploaded successfully"
}
Error Responses:
400 Bad Request- No file provided413 Payload Too Large- File exceeds size limit500 Internal Server Error- Upload failed
Download File
GET /api/v1/files/{file_id}/download
Download a file by its ID.
Parameters:
file_id(path, required): The file ID
Response:
- Returns file content with appropriate MIME type and
Content-Dispositionheader
Error Responses:
404 Not Found- File not found500 Internal Server Error- Download failed
Get File Info
GET /api/v1/files/{file_id}/info
Retrieve metadata for a file.
Parameters:
file_id(path, required): The file ID
Response:
{
"file_id": "507f1f77bcf86cd799439011",
"filename": "profile.jpg",
"content_type": "image/jpeg",
"size": 102400,
"uploaded_by": "user_123",
"uploaded_at": "2024-04-19T10:30:00Z",
"category": "profile_images",
"tags": ["user", "profile"],
"description": "User profile picture"
}
List Files
GET /api/v1/files
List files with optional filtering.
Query Parameters:
category(optional): Filter by categorytags(optional, array): Filter by tagslimit(optional, default 50, max 1000): Number of files to returnskip(optional, default 0): Number of files to skip
Response:
{
"files": [
{
"file_id": "507f1f77bcf86cd799439011",
"filename": "profile.jpg",
"content_type": "image/jpeg",
"size": 102400,
"uploaded_by": "user_123",
"uploaded_at": "2024-04-19T10:30:00Z",
"category": "profile_images",
"tags": ["user", "profile"]
}
],
"total": 150,
"limit": 50,
"skip": 0
}
Delete File
DELETE /api/v1/files/{file_id}
Delete a file by its ID.
Parameters:
file_id(path, required): The file ID
Response:
{
"message": "File deleted successfully"
}
Error Responses:
404 Not Found- File not found500 Internal Server Error- Deletion failed
Usage Examples
Upload Profile Image
curl -X POST \
-F "file=@profile.jpg" \
-F "category=profile_images" \
-F "tags=user" \
-F "tags=profile" \
-H "Authorization: Bearer <token>" \
http://localhost:8080/api/v1/files/upload
Download File
curl -X GET \
-H "Authorization: Bearer <token>" \
http://localhost:8080/api/v1/files/507f1f77bcf86cd799439011/download \
-o downloaded_file.jpg
Get File Information
curl -X GET \
-H "Authorization: Bearer <token>" \
http://localhost:8080/api/v1/files/507f1f77bcf86cd799439011/info
List User Profile Images
curl -X GET \
-H "Authorization: Bearer <token>" \
"http://localhost:8080/api/v1/files?category=profile_images&tags=user&limit=20"
File Validation
The service includes built-in validation for file uploads:
Default Validators
Standard File Validator
- Supported MIME types: Images, PDFs, Office documents, compressed files
- Maximum size: 100 MB
- Filename validation and sanitization
Profile Image Validator
- Supported MIME types: JPEG, PNG, GIF, WebP
- Maximum size: 5 MB
- Extension validation
Custom Validation
Create a custom validator:
validator := filestore.NewFileValidator(
10 * 1024 * 1024, // 10 MB max
map[string]bool{
"application/pdf": true,
"image/jpeg": true,
"image/png": true,
},
)
// Validate file
if err := validator.ValidateFile("document.pdf", "application/pdf", fileSize); err != nil {
// Handle validation error
}
Metadata Storage
Files are stored with rich metadata:
{
"filename": "profile.jpg",
"uploadDate": "2024-04-19T10:30:00Z",
"metadata": {
"uploaded_by": "user_123",
"category": "profile_images",
"tags": ["user", "profile"],
"description": "User profile picture",
"content_type": "image/jpeg",
"extra": {
"user_id": "user_123",
"department": "marketing"
}
}
}
Error Handling
The service uses a comprehensive error handling system:
Error Codes
FILE_NOT_FOUND- File does not existUPLOAD_FAILED- Upload operation failedDOWNLOAD_FAILED- Download operation failedDELETE_FAILED- Delete operation failedINVALID_INPUT- Invalid input parametersFILE_TOO_LARGE- File exceeds size limitUNSUPPORTED_FILE_TYPE- MIME type not supportedDUPLICATE_FILE- File already existsACCESS_DENIED- User does not have access
Error Response Format
{
"error": "File is too large",
"code": "FILE_TOO_LARGE",
"message": "Maximum file size is 100 MB"
}
Database Schema
fs.files Collection
{
_id: ObjectId,
filename: String,
uploadDate: Date,
chunkSize: Number,
length: Number,
md5: String,
metadata: {
uploaded_by: String,
category: String,
tags: [String],
description: String,
content_type: String,
extra: {
user_id: String,
// ... additional fields
}
}
}
fs.chunks Collection
{
_id: ObjectId,
files_id: ObjectId,
n: Number,
data: BinData
}
Integration with User Service
To integrate file uploads with user profiles:
- Update User Entity - Add FileID field:
type User struct {
// ... existing fields
ProfileImageFileID *string `bson:"profile_image_file_id"`
}
- Add Upload Endpoint - In user handler:
func (h *Handler) UploadProfileImage(c echo.Context) error {
// Get user ID from context
userID := c.Get("user_id").(string)
// Get file
file, err := c.FormFile("profile_image")
// ... upload logic
// Update user with file ID
// ... update user
}
- Add Download Endpoint - Serve user's profile image:
func (h *Handler) GetProfileImage(c echo.Context) error {
userID := c.Param("user_id")
// Get user
user, err := h.service.GetUserByID(userID)
// Download file
reader, fileInfo, err := h.fileStoreService.Download(*user.ProfileImageFileID)
// ... stream response
}
Performance Considerations
- Chunk Size - GridFS default is 255 KB. For large files, consider increasing this.
- Indexing - Create indexes on frequently queried metadata:
db.fs.files.createIndex({ "metadata.category": 1 })
db.fs.files.createIndex({ "metadata.tags": 1 })
db.fs.files.createIndex({ "metadata.uploaded_by": 1 })
- Cleanup - Implement regular cleanup for unused files:
// Delete file and associated chunks
func (s *GridFSService) Delete(fileID string) error {
// Automatically handled by MongoDB
}
Security Considerations
- File Type Validation - Validate MIME types and extensions
- Filename Sanitization - Remove dangerous characters:
sanitized := filestore.SanitizeFilename(filename)
- Size Limits - Enforce maximum file sizes
- Access Control - Implement proper authorization checks
- Malware Scanning - Consider integrating antivirus scanning
- Rate Limiting - Implement rate limiting on upload endpoints
Testing
Unit tests are included for all operations:
go test ./pkg/filestore -v
Test coverage includes:
- Upload operations
- Download operations
- Delete operations
- File validation
- Error handling
- Metadata storage and retrieval
Troubleshooting
MongoDB Connection Failed
Error: MongoDB connection not available
Solution: Ensure MongoDB is running and configured correctly
File Not Found
Error: file not found
Solution: Verify the file ID is correct
File Too Large
Error: file size exceeds maximum limit
Solution: Check MaxSize configuration and file size
Invalid MIME Type
Error: unsupported file type
Solution: Verify the file type is in the allowed MIME types list
Configuration
Configure file store limits in bootstrap.go:
// Maximum file sizes
const (
MaxUploadSize = 100 * 1024 * 1024 // 100 MB
MaxProfileImageSize = 5 * 1024 * 1024 // 5 MB
)
// Supported MIME types
var AllowedImageTypes = map[string]bool{
"image/jpeg": true,
"image/png": true,
"image/gif": true,
"image/webp": true,
}
Contributing
When extending the file store service:
- Maintain backward compatibility
- Add appropriate error handling
- Update logging for operations
- Include unit tests for new features
- Update documentation
License
This file store service is part of the Opplens backend system.