diff --git a/Notesnook.API/Controllers/S3Controller.cs b/Notesnook.API/Controllers/S3Controller.cs
index 9db9762..6ba6c50 100644
--- a/Notesnook.API/Controllers/S3Controller.cs
+++ b/Notesnook.API/Controllers/S3Controller.cs
@@ -1,127 +1,128 @@
-/*
-This file is part of the Notesnook Sync Server project (https://notesnook.com/)
-
-Copyright (C) 2023 Streetwriters (Private) Limited
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the Affero GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-Affero GNU General Public License for more details.
-
-You should have received a copy of the Affero GNU General Public License
-along with this program. If not, see .
-*/
-
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Mvc;
-using Amazon.S3.Model;
-using System.Threading.Tasks;
-using System.Security.Claims;
-using Notesnook.API.Interfaces;
-using System;
-
-namespace Notesnook.API.Controllers
-{
- [ApiController]
- [Route("s3")]
- [ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
- public class S3Controller : ControllerBase
- {
- private IS3Service S3Service { get; set; }
- public S3Controller(IS3Service s3Service)
- {
- S3Service = s3Service;
- }
-
- [HttpPut]
- [Authorize("Pro")]
- public IActionResult Upload([FromQuery] string name)
- {
- var userId = this.User.FindFirstValue("sub");
- var url = S3Service.GetUploadObjectUrl(userId, name);
- if (url == null) return BadRequest("Could not create signed url.");
- return Ok(url);
- }
-
-
- [HttpGet("multipart")]
- [Authorize("Pro")]
- public async Task MultipartUpload([FromQuery] string name, [FromQuery] int parts, [FromQuery] string uploadId)
- {
- var userId = this.User.FindFirstValue("sub");
- try
- {
- var meta = await S3Service.StartMultipartUploadAsync(userId, name, parts, uploadId);
- return Ok(meta);
- }
- catch (Exception ex) { return BadRequest(ex.Message); }
- }
-
- [HttpDelete("multipart")]
- [Authorize("Pro")]
- public async Task AbortMultipartUpload([FromQuery] string name, [FromQuery] string uploadId)
- {
- var userId = this.User.FindFirstValue("sub");
- try
- {
- await S3Service.AbortMultipartUploadAsync(userId, name, uploadId);
- return Ok();
- }
- catch (Exception ex) { return BadRequest(ex.Message); }
- }
-
- [HttpPost("multipart")]
- [Authorize("Pro")]
- public async Task CompleteMultipartUpload([FromBody] CompleteMultipartUploadRequest uploadRequest)
- {
- var userId = this.User.FindFirstValue("sub");
- try
- {
- await S3Service.CompleteMultipartUploadAsync(userId, uploadRequest);
- return Ok();
- }
- catch (Exception ex) { return BadRequest(ex.Message); }
- }
-
- [HttpGet]
- [Authorize("Sync")]
- public IActionResult Download([FromQuery] string name)
- {
- var userId = this.User.FindFirstValue("sub");
- var url = S3Service.GetDownloadObjectUrl(userId, name);
- if (url == null) return BadRequest("Could not create signed url.");
- return Ok(url);
- }
-
- [HttpHead]
- [Authorize("Sync")]
- public async Task Info([FromQuery] string name)
- {
- var userId = this.User.FindFirstValue("sub");
- var size = await S3Service.GetObjectSizeAsync(userId, name);
- HttpContext.Response.Headers.ContentLength = size;
- return Ok();
- }
-
- [HttpDelete]
- [Authorize("Sync")]
- public async Task DeleteAsync([FromQuery] string name)
- {
- try
- {
- var userId = this.User.FindFirstValue("sub");
- await S3Service.DeleteObjectAsync(userId, name);
- return Ok();
- }
- catch (Exception ex)
- {
- return BadRequest(ex.Message);
- }
- }
- }
-}
+/*
+This file is part of the Notesnook Sync Server project (https://notesnook.com/)
+
+Copyright (C) 2023 Streetwriters (Private) Limited
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the Affero GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+Affero GNU General Public License for more details.
+
+You should have received a copy of the Affero GNU General Public License
+along with this program. If not, see .
+*/
+
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Amazon.S3.Model;
+using System.Threading.Tasks;
+using System.Security.Claims;
+using Notesnook.API.Interfaces;
+using System;
+using Notesnook.API.Models;
+
+namespace Notesnook.API.Controllers
+{
+ [ApiController]
+ [Route("s3")]
+ [ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
+ public class S3Controller : ControllerBase
+ {
+ private IS3Service S3Service { get; set; }
+ public S3Controller(IS3Service s3Service)
+ {
+ S3Service = s3Service;
+ }
+
+ [HttpPut]
+ [Authorize("Pro")]
+ public IActionResult Upload([FromQuery] string name)
+ {
+ var userId = this.User.FindFirstValue("sub");
+ var url = S3Service.GetUploadObjectUrl(userId, name);
+ if (url == null) return BadRequest("Could not create signed url.");
+ return Ok(url);
+ }
+
+
+ [HttpGet("multipart")]
+ [Authorize("Pro")]
+ public async Task MultipartUpload([FromQuery] string name, [FromQuery] int parts, [FromQuery] string uploadId)
+ {
+ var userId = this.User.FindFirstValue("sub");
+ try
+ {
+ var meta = await S3Service.StartMultipartUploadAsync(userId, name, parts, uploadId);
+ return Ok(meta);
+ }
+ catch (Exception ex) { return BadRequest(ex.Message); }
+ }
+
+ [HttpDelete("multipart")]
+ [Authorize("Pro")]
+ public async Task AbortMultipartUpload([FromQuery] string name, [FromQuery] string uploadId)
+ {
+ var userId = this.User.FindFirstValue("sub");
+ try
+ {
+ await S3Service.AbortMultipartUploadAsync(userId, name, uploadId);
+ return Ok();
+ }
+ catch (Exception ex) { return BadRequest(ex.Message); }
+ }
+
+ [HttpPost("multipart")]
+ [Authorize("Pro")]
+ public async Task CompleteMultipartUpload([FromBody] CompleteMultipartUploadRequestWrapper uploadRequestWrapper)
+ {
+ var userId = this.User.FindFirstValue("sub");
+ try
+ {
+ await S3Service.CompleteMultipartUploadAsync(userId, uploadRequestWrapper.ToRequest());
+ return Ok();
+ }
+ catch (Exception ex) { return BadRequest(ex.Message); }
+ }
+
+ [HttpGet]
+ [Authorize("Sync")]
+ public IActionResult Download([FromQuery] string name)
+ {
+ var userId = this.User.FindFirstValue("sub");
+ var url = S3Service.GetDownloadObjectUrl(userId, name);
+ if (url == null) return BadRequest("Could not create signed url.");
+ return Ok(url);
+ }
+
+ [HttpHead]
+ [Authorize("Sync")]
+ public async Task Info([FromQuery] string name)
+ {
+ var userId = this.User.FindFirstValue("sub");
+ var size = await S3Service.GetObjectSizeAsync(userId, name);
+ HttpContext.Response.Headers.ContentLength = size;
+ return Ok();
+ }
+
+ [HttpDelete]
+ [Authorize("Sync")]
+ public async Task DeleteAsync([FromQuery] string name)
+ {
+ try
+ {
+ var userId = this.User.FindFirstValue("sub");
+ await S3Service.DeleteObjectAsync(userId, name);
+ return Ok();
+ }
+ catch (Exception ex)
+ {
+ return BadRequest(ex.Message);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Notesnook.API/Models/CompleteMultipartUploadRequestWrapper.cs b/Notesnook.API/Models/CompleteMultipartUploadRequestWrapper.cs
new file mode 100644
index 0000000..2fc9fd3
--- /dev/null
+++ b/Notesnook.API/Models/CompleteMultipartUploadRequestWrapper.cs
@@ -0,0 +1,30 @@
+using System.Collections.Generic;
+using Amazon.S3.Model;
+
+namespace Notesnook.API.Models;
+
+public class CompleteMultipartUploadRequestWrapper
+{
+ public string Key { get; set; }
+ public List PartETags { get; set; }
+ public string UploadId { get; set; }
+
+ public CompleteMultipartUploadRequest ToRequest()
+ {
+ CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest();
+ completeMultipartUploadRequest.Key = Key;
+ completeMultipartUploadRequest.UploadId = UploadId;
+ completeMultipartUploadRequest.PartETags = [];
+ foreach (var partETagWrapper in PartETags)
+ {
+ var partETag = new PartETag
+ {
+ PartNumber = partETagWrapper.PartNumber,
+ ETag = partETagWrapper.ETag
+ };
+ completeMultipartUploadRequest.PartETags.Add(partETag);
+ }
+
+ return completeMultipartUploadRequest;
+ }
+}
\ No newline at end of file
diff --git a/Notesnook.API/Models/PartETagWrapper.cs b/Notesnook.API/Models/PartETagWrapper.cs
new file mode 100644
index 0000000..46b4c9f
--- /dev/null
+++ b/Notesnook.API/Models/PartETagWrapper.cs
@@ -0,0 +1,7 @@
+namespace Notesnook.API.Models;
+
+public class PartETagWrapper
+{
+ public int PartNumber { get; set; }
+ public string ETag { get; set; }
+}
\ No newline at end of file