Conversation
danctila
left a comment
There was a problem hiding this comment.
nice implementation @spokonya 🔥🔥🔥
Some good things to note
- presigned URL usage is great
- good separation of concerns b/t config, storage and handler
- like the swagger docs 🙏
- context propagation is correct
Must fix
- Missing an update to the
.env.sample- Make sure to update this as the ticket mentioned "you should probably update env.sample if you've updated your .env" just so it's easier to keep track over time for both engs and the people who will look at this code after us
- Swagger import commented out
- File location for the S3 storage file
Other suggestions are related to error context, input validation, and nits about code quality/cleanup but overall nothing major to change ✌️
|
|
||
| "github.com/generate/selfserve/config" | ||
| _ "github.com/generate/selfserve/docs" | ||
| //_ "github.com/generate/selfserve/docs" |
There was a problem hiding this comment.
not sure what happened here but i think this breaks swagger docs
Either uncomment the line or describe why it was removed if it was deliberate (might just be for debugging?)
There was a problem hiding this comment.
I think the S3 storage location being under storage/postgres/s3/ might not make the most sense architecturally because S3 is not related to Postgres
I would recommend:
backend/internal/service/storage/
|-postgres/
| |----storage.go
|
|-s3/
|---s3storage.go
Make sure to:
- move the file
- update all imports necessary
| if err != nil { | ||
| return "", err | ||
| } |
There was a problem hiding this comment.
shoudn't return raw errors without context - will make debugging more difficult, etc. so we want to follow our err wrapping setup
suggestion for example:
| if err != nil { | |
| return "", err | |
| } | |
| if err != nil { | |
| return "", fmt.Errorf("failed to generate presigned URL for key %s: %w", key, err) | |
| } |
Also apply similar wrapping to:
-
DeleteFile -
NewS3Storage
| func (s *Storage) GeneratePresignedURL(ctx context.Context, key string, expiration time.Duration) (string, error) { | ||
|
|
||
| presignedURL, err := s.URL.PresignPutObject(ctx, &s3.PutObjectInput{ | ||
| Bucket: aws.String(s.BucketName), | ||
| Key: aws.String(key), | ||
| }, func(opts *s3.PresignOptions) { | ||
| opts.Expires = expiration |
There was a problem hiding this comment.
make sure the functions have input validation
func (s *Storage) GeneratePresignedURL(ctx context.Context, key string, expiration time.Duration) (string, error) {
if key == "" {
return "", fmt.Errorf("key cannot be empty")
}
if expiration <= 0 {
return "", fmt.Errorf("expiration must be positive")
}Also do the same for:
-
DeleteFile(validating that key is not empty)
| return errs.BadRequest("key is required") | ||
| } | ||
|
|
||
| presignedURL, err := h.S3Storage.GeneratePresignedURL(c.Context(), key, 5*time.Minute) |
There was a problem hiding this comment.
would be good to have the 5-minute hardcoded expiration either configurable via environment variable or defined as a package level constant.
for example:
const DefaultPresignedURLExpiration = 5 * time.Minute
...
presignedURL, err := h.S3Storage.GeneratePresignedURL(c.Context(), key, DefaultPresignedURLExpiration)| } | ||
|
|
||
| func (s *Storage) GeneratePresignedURL(ctx context.Context, key string, expiration time.Duration) (string, error) { | ||
|
|
| return c.JSON(fiber.Map{ | ||
| "presigned_url": presignedURL, | ||
| }) | ||
| } No newline at end of file |
There was a problem hiding this comment.
go convention to end files with newline character
| }, nil | ||
| } | ||
|
|
||
| func (s *Storage) GeneratePresignedURL(ctx context.Context, key string, expiration time.Duration) (string, error) { |
There was a problem hiding this comment.
consider adding a contentType parameter to your function.
we want to make the s3 implementation as scalable as possible beyond profile pictures. for profile picture we will want to restrict file types to types like image/jpeg or image/png for example but other uses of s3 won't have the same restrictions
| URL *s3.PresignClient | ||
| } | ||
|
|
||
| func NewS3Storage(cfg config.S3) (*Storage, error) { |
There was a problem hiding this comment.
we have upload (presigned PUT) and delete but looks like no way to retrieve files? we may want a GetFile URL generator similar to your other functions like:
func (s *Storage) GenerateGetURL(ctx context.Context, key string, expiration time.Duration) (string, error) {
presignedURL, err := s.URL.PresignGetObject(ctx, &s3.GetObjectInput{
Bucket: aws.String(s.BucketName),
Key: aws.String(key),
}, func(opts *s3.PresignOptions) {
opts.Expires = expiration
})
...
}| // @Accept json | ||
| // @Produce json | ||
| // @Param key path string true "File key" | ||
| // @Success 200 {string} string "Presigned URL" |
There was a problem hiding this comment.
swagger docs here show @success 200 {string} string but the actual response is a JSON object so it should be
| // @Success 200 {string} string "Presigned URL" | |
| // @Success 200 {object} map[string]string "Presigned URL response" |
Description
Type of Change
Related Issue(s)
Closes #95
Working towards #51
What Changed?
-Added S3 test handler
Testing & Validation
How this was tested
Screenshots/Recordings
Unfinished Work & Known Issues
Notes & Nuances
Pre-Merge Checklist
Code Quality
Testing & CI
Documentation
Reviewer Notes