| 249 |
249 |
|
)]
|
| 250 |
250 |
|
#[tracing::instrument(skip_all, name = "synckit::blob_multipart_parts")]
|
| 251 |
251 |
|
pub(super) async fn blob_multipart_parts(
|
|
252 |
+ |
State(db): State<PgPool>,
|
| 252 |
253 |
|
State(storage): State<crate::AppStorage>,
|
| 253 |
254 |
|
sync_user: SyncUser,
|
| 254 |
255 |
|
Json(req): Json<BlobMultipartPartsRequest>,
|
| 257 |
258 |
|
AppError::ServiceUnavailable("SyncKit blob storage is not configured".to_string())
|
| 258 |
259 |
|
})?;
|
| 259 |
260 |
|
|
| 260 |
|
- |
validation::validate_sync_blob_hash(&req.hash)?;
|
| 261 |
261 |
|
if req.size_bytes <= 0 || req.size_bytes > constants::SYNCKIT_MAX_MULTIPART_BLOB_SIZE_BYTES {
|
| 262 |
262 |
|
return Err(AppError::BadRequest(
|
| 263 |
263 |
|
"Blob size is out of range".to_string(),
|
| 264 |
264 |
|
));
|
| 265 |
265 |
|
}
|
| 266 |
266 |
|
|
|
267 |
+ |
// The same gate `start` runs, and it belongs here too: a client that skips
|
|
268 |
+ |
// `start` reaches this route directly, and without the gate an unsubscribed
|
|
269 |
+ |
// first-party user was handed presigned part URLs. The gate also validates
|
|
270 |
+ |
// the hash, which is why the standalone check above it is gone.
|
|
271 |
+ |
match blob_upload_gate(&db, &sync_user, &req.hash).await? {
|
|
272 |
+ |
Some(BlobUploadStop::NoSubscription) => return Ok(no_subscription_response()),
|
|
273 |
+ |
// Mirrors `start`: the content address is already stored for this
|
|
274 |
+ |
// user and app, so there is nothing to upload and no URL to mint.
|
|
275 |
+ |
Some(BlobUploadStop::AlreadyExists) => {
|
|
276 |
+ |
return Ok(Json(BlobMultipartPartsResponse {
|
|
277 |
+ |
parts: Vec::new(),
|
|
278 |
+ |
expires_in: 0,
|
|
279 |
+ |
})
|
|
280 |
+ |
.into_response());
|
|
281 |
+ |
}
|
|
282 |
+ |
None => {}
|
|
283 |
+ |
}
|
|
284 |
+ |
|
| 267 |
285 |
|
let plan =
|
| 268 |
286 |
|
s3_storage::MultipartPlan::auto(req.size_bytes as u64).map_err(AppError::BadRequest)?;
|
| 269 |
287 |
|
|