mirror of
https://github.com/streetwriters/notesnook-sync-server.git
synced 2026-02-12 11:12:44 +00:00
s3: add bulk delete api
This commit is contained in:
@@ -21,20 +21,17 @@ using System;
|
||||
using System.Net.Http;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Amazon.S3.Model;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MongoDB.Driver;
|
||||
using Notesnook.API.Accessors;
|
||||
using Notesnook.API.Helpers;
|
||||
using Notesnook.API.Interfaces;
|
||||
using Notesnook.API.Models;
|
||||
using Streetwriters.Common;
|
||||
using Streetwriters.Common.Accessors;
|
||||
using Streetwriters.Common.Extensions;
|
||||
using Streetwriters.Common.Interfaces;
|
||||
using Streetwriters.Common.Models;
|
||||
|
||||
namespace Notesnook.API.Controllers
|
||||
@@ -212,5 +209,26 @@ namespace Notesnook.API.Controllers
|
||||
return BadRequest(new { error = "Failed to delete attachment." });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("bulk-delete")]
|
||||
public async Task<IActionResult> DeleteBulkAsync([FromBody] DeleteBulkObjectsRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (request.Names == null || request.Names.Length == 0)
|
||||
{
|
||||
return BadRequest(new { error = "No files specified for deletion." });
|
||||
}
|
||||
|
||||
var userId = this.User.GetUserId();
|
||||
await s3Service.DeleteObjectsAsync(userId, request.Names);
|
||||
return Ok();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error deleting objects for user.");
|
||||
return BadRequest(new { error = "Failed to delete attachments." });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,18 +17,16 @@ You should have received a copy of the Affero GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Amazon.S3.Model;
|
||||
using Notesnook.API.Models;
|
||||
using Notesnook.API.Models.Responses;
|
||||
using Streetwriters.Common.Interfaces;
|
||||
|
||||
namespace Notesnook.API.Interfaces
|
||||
{
|
||||
public interface IS3Service
|
||||
{
|
||||
Task DeleteObjectAsync(string userId, string name);
|
||||
Task DeleteObjectsAsync(string userId, string[] names);
|
||||
Task DeleteDirectoryAsync(string userId);
|
||||
Task<long> GetObjectSizeAsync(string userId, string name);
|
||||
Task<string?> GetUploadObjectUrlAsync(string userId, string name);
|
||||
|
||||
25
Notesnook.API/Models/DeleteBulkRequest.cs
Normal file
25
Notesnook.API/Models/DeleteBulkRequest.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Notesnook.API.Models;
|
||||
|
||||
public class DeleteBulkObjectsRequest
|
||||
{
|
||||
public required string[] Names { get; set; }
|
||||
}
|
||||
@@ -24,21 +24,15 @@ using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Amazon;
|
||||
using Amazon.Runtime;
|
||||
using Amazon.S3;
|
||||
using Amazon.S3.Model;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MongoDB.Driver;
|
||||
using Notesnook.API.Accessors;
|
||||
using Notesnook.API.Helpers;
|
||||
using Notesnook.API.Interfaces;
|
||||
using Notesnook.API.Models;
|
||||
using Streetwriters.Common;
|
||||
using Streetwriters.Common.Accessors;
|
||||
using Streetwriters.Common.Enums;
|
||||
using Streetwriters.Common.Interfaces;
|
||||
using Streetwriters.Common.Models;
|
||||
|
||||
namespace Notesnook.API.Services
|
||||
{
|
||||
@@ -110,6 +104,70 @@ namespace Notesnook.API.Services
|
||||
throw new Exception("Could not delete object.");
|
||||
}
|
||||
|
||||
public async Task DeleteObjectsAsync(string userId, string[] names)
|
||||
{
|
||||
var objectsToDelete = new List<KeyVersion>();
|
||||
|
||||
foreach (var name in names)
|
||||
{
|
||||
var objectName = GetFullObjectName(userId, name);
|
||||
if (objectName == null) continue;
|
||||
|
||||
objectsToDelete.Add(new KeyVersion { Key = objectName });
|
||||
}
|
||||
|
||||
if (objectsToDelete.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// S3 DeleteObjectsRequest supports max 1000 keys per request
|
||||
var batchSize = 1000;
|
||||
var deleteErrors = new List<DeleteError>();
|
||||
var failedBatches = 0;
|
||||
|
||||
for (int i = 0; i < objectsToDelete.Count; i += batchSize)
|
||||
{
|
||||
var batch = objectsToDelete.Skip(i).Take(batchSize).ToList();
|
||||
var deleteObjectsResponse = await S3InternalClient.ExecuteWithFailoverAsync(
|
||||
(client) => client.DeleteObjectsAsync(new DeleteObjectsRequest
|
||||
{
|
||||
BucketName = INTERNAL_BUCKET_NAME,
|
||||
Objects = batch,
|
||||
}),
|
||||
operationName: "DeleteObjects",
|
||||
isWriteOperation: true
|
||||
);
|
||||
|
||||
if (!IsSuccessStatusCode((int)deleteObjectsResponse.HttpStatusCode))
|
||||
{
|
||||
failedBatches++;
|
||||
}
|
||||
|
||||
if (deleteObjectsResponse.DeleteErrors.Count > 0)
|
||||
{
|
||||
deleteErrors.AddRange(deleteObjectsResponse.DeleteErrors);
|
||||
}
|
||||
}
|
||||
|
||||
if (failedBatches > 0 || deleteErrors.Count > 0)
|
||||
{
|
||||
var errorParts = new List<string>();
|
||||
|
||||
if (failedBatches > 0)
|
||||
{
|
||||
errorParts.Add($"{failedBatches} batch(es) failed with unsuccessful status code");
|
||||
}
|
||||
|
||||
if (deleteErrors.Count > 0)
|
||||
{
|
||||
errorParts.Add(string.Join(", ", deleteErrors.Select(e => $"{e.Key}: {e.Message}")));
|
||||
}
|
||||
|
||||
throw new Exception(string.Join("; ", errorParts));
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteDirectoryAsync(string userId)
|
||||
{
|
||||
var request = new ListObjectsV2Request
|
||||
|
||||
Reference in New Issue
Block a user