Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 20 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ python -m worker # worker
### 6. Test the API

```bash
curl -X POST http://localhost:8080/api/v1/assets/upload \
curl -X POST http://localhost:8080/api/v1/storage/presign \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"fileName": "image.jpg",
Expand Down Expand Up @@ -201,7 +202,7 @@ kubectl apply -f deploy/k8s/

### Upload Asset

**Endpoint:** `POST /api/v1/assets/upload`
**Endpoint:** `POST /api/v1/storage/presign`

**Request:**
```json
Expand All @@ -215,32 +216,37 @@ kubectl apply -f deploy/k8s/
**Response:**
```json
{
"uploadUrl": "https://<storage-host>/...",
"assetId": "550e8400-e29b-41d4-a716-446655440000",
"method": "PUT",
"headers": {
"Content-Type": "image/jpeg"
},
"objectPath": "media/raw/550e8400-e29b-41d4-a716-446655440000",
"publicUrl": "https://<storage-host>/...",
"expiresAt": 1702468800
"status": "success",
"data": {
"uploadUrl": "https://<storage-host>/...",
"assetId": "550e8400-e29b-41d4-a716-446655440000",
"method": "PUT",
"headers": {
"Content-Type": "image/jpeg"
},
"objectPath": "media/raw/550e8400-e29b-41d4-a716-446655440000",
"publicUrl": "https://<storage-host>/...",
"expiresAt": 1702468800
}
}
```

> The `uploadUrl` / `publicUrl` host depends on the configured storage provider (GCS, S3, or a MinIO endpoint).

### Mark Asset as Uploaded

**Endpoint:** `POST /api/v1/assets/{assetId}/uploaded`
**Endpoint:** `POST /api/v1/assets/{assetId}/complete`

**Response:**
```json
{
"message": "Asset marked as uploaded",
"assetId": "550e8400-e29b-41d4-a716-446655440000"
"status": "success",
"message": "Asset marked as uploaded"
}
```

> Both endpoints require an `Authorization: Bearer <token>` header.

## 🔧 Development

### Project Structure
Expand Down
4 changes: 2 additions & 2 deletions internal/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import (
"github.com/jmoiron/sqlx"
"github.com/rndmcodeguy20/mpiper/internal/config"
"github.com/rndmcodeguy20/mpiper/internal/handler"
appMiddleware "github.com/rndmcodeguy20/mpiper/internal/middleware"
"github.com/rndmcodeguy20/mpiper/internal/metrics"
appMiddleware "github.com/rndmcodeguy20/mpiper/internal/middleware"
"github.com/rndmcodeguy20/mpiper/internal/repository"
"github.com/rndmcodeguy20/mpiper/internal/service"
applogger "github.com/rndmcodeguy20/mpiper/pkg/logger"
Expand Down Expand Up @@ -153,7 +153,7 @@ func NewRouter(cfg config.EnvConfig, db *sqlx.DB, m *metrics.Metrics) *chi.Mux {

r.Route("/assets", func(r chi.Router) {
r.Use(appMiddleware.AuthMiddleware(logger))
r.Get("/{assetID}/complete", assetHandler.MarkAssetUploaded)
r.Post("/{assetID}/complete", assetHandler.MarkAssetUploaded)
})
})

Expand Down
10 changes: 7 additions & 3 deletions internal/service/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ type AssetService interface {
MarkAssetUploaded(ctx context.Context, assetID uuid.UUID) error
}

// presignExpiry is how long returned upload URLs remain valid; it must stay in
// sync with the expiry the storage provider signs into the URL.
const presignExpiry = 5 * time.Minute

type assetService struct {
assetRepo repository.AssetRepository
logger *zap.Logger
Expand Down Expand Up @@ -105,7 +109,7 @@ func (s *assetService) CreateAsset(ctx context.Context, request models.UploadAss
signedUrl, err := s.storageClient.GeneratePresignedURL(spanStorageCtx, s.bucket, objectKey, &storagex.PresignedURLOptions{
Method: "PUT",
ContentType: request.ContentType,
ExpiresInSeconds: 60 * 5, // 5 minutes
ExpiresInSeconds: int64(presignExpiry.Seconds()),
})
spanStorage.End()

Expand Down Expand Up @@ -170,9 +174,9 @@ func (s *assetService) CreateAsset(ctx context.Context, request models.UploadAss
AssetID: assetID.String(),
Method: "PUT",
Headers: map[string]string{"Content-Type": request.ContentType},
ObjectPath: request.FileName,
ObjectPath: objectKey,
PublicUrl: publicUrl,
ExpiresAt: 60 * 5, // 5 minutes
ExpiresAt: time.Now().Add(presignExpiry).Unix(),
}, nil
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/utils/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func WriteErrorResponse(w http.ResponseWriter, err error) {
}
w.WriteHeader(apiErr.StatusCode)
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"success": false,
"status": "error",
"message": apiErr.Message,
"error": map[string]interface{}{
"code": apiErr.Code,
Expand Down