♻️ Extract the Storage service to DysonNetwork.Drive microservice

This commit is contained in:
2025-07-06 17:29:26 +08:00
parent 6a3d04af3d
commit 14b79f16f4
71 changed files with 2629 additions and 346 deletions

View File

@ -0,0 +1,321 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using DysonNetwork.Drive.Data;
using DysonNetwork.Drive.Interfaces;
using DysonNetwork.Drive.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using NodaTime;
namespace DysonNetwork.Drive.Services;
public class FileReferenceService : IFileReferenceService, IDisposable
{
private readonly AppDatabase _dbContext;
private readonly IFileService _fileService;
private readonly IClock _clock;
private readonly ILogger<FileReferenceService> _logger;
private bool _disposed = false;
public FileReferenceService(
AppDatabase dbContext,
IFileService fileService,
IClock clock,
ILogger<FileReferenceService> logger)
{
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
_fileService = fileService ?? throw new ArgumentNullException(nameof(fileService));
_clock = clock ?? throw new ArgumentNullException(nameof(clock));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<CloudFileReference> CreateReferenceAsync(
Guid fileId,
string resourceId,
string resourceType,
string referenceType,
string? referenceId = null,
string? referenceName = null,
string? referenceMimeType = null,
long? referenceSize = null,
string? referenceUrl = null,
string? referenceThumbnailUrl = null,
string? referencePreviewUrl = null,
string? referenceMetadata = null,
IDictionary<string, object>? metadata = null,
CancellationToken cancellationToken = default)
{
// Verify file exists
var fileExists = await _fileService.FileExistsAsync(fileId, cancellationToken);
if (!fileExists)
{
throw new FileNotFoundException($"File with ID {fileId} not found.");
}
var reference = new CloudFileReference
{
Id = Guid.NewGuid(),
FileId = fileId,
ResourceId = resourceId,
ResourceType = resourceType,
ReferenceType = referenceType,
ReferenceId = referenceId,
ReferenceName = referenceName,
ReferenceMimeType = referenceMimeType,
ReferenceSize = referenceSize,
ReferenceUrl = referenceUrl,
ReferenceThumbnailUrl = referenceThumbnailUrl,
ReferencePreviewUrl = referencePreviewUrl,
ReferenceMetadata = referenceMetadata,
IsActive = true,
CreatedAt = _clock.GetCurrentInstant().ToDateTimeOffset()
};
if (metadata != null && metadata.Any())
{
var options = new JsonSerializerOptions { WriteIndented = true };
reference.Metadata = JsonDocument.Parse(JsonSerializer.Serialize(metadata, options));
}
_dbContext.FileReferences.Add(reference);
await _dbContext.SaveChangesAsync(cancellationToken);
_logger.LogInformation(
"Created reference {ReferenceId} for file {FileId} to resource {ResourceType}/{ResourceId}",
reference.Id, fileId, resourceType, resourceId);
return reference;
}
public async Task<CloudFileReference> GetReferenceAsync(Guid referenceId, CancellationToken cancellationToken = default)
{
var reference = await _dbContext.FileReferences
.AsNoTracking()
.Include(r => r.File)
.FirstOrDefaultAsync(r => r.Id == referenceId, cancellationToken);
if (reference == null)
{
throw new KeyNotFoundException($"Reference with ID {referenceId} not found.");
}
return reference;
}
public async Task<IEnumerable<CloudFileReference>> GetReferencesForFileAsync(Guid fileId, CancellationToken cancellationToken = default)
{
return await _dbContext.FileReferences
.AsNoTracking()
.Where(r => r.FileId == fileId && r.IsActive)
.ToListAsync(cancellationToken);
}
public async Task<IEnumerable<CloudFileReference>> GetReferencesForResourceAsync(
string resourceId,
string resourceType,
CancellationToken cancellationToken = default)
{
return await _dbContext.FileReferences
.AsNoTracking()
.Where(r => r.ResourceId == resourceId &&
r.ResourceType == resourceType &&
r.IsActive)
.ToListAsync(cancellationToken);
}
public async Task<IEnumerable<CloudFileReference>> GetReferencesOfTypeAsync(
string referenceType,
CancellationToken cancellationToken = default)
{
return await _dbContext.FileReferences
.AsNoTracking()
.Where(r => r.ReferenceType == referenceType && r.IsActive)
.ToListAsync(cancellationToken);
}
public async Task<bool> DeleteReferenceAsync(Guid referenceId, CancellationToken cancellationToken = default)
{
var reference = await _dbContext.FileReferences
.FirstOrDefaultAsync(r => r.Id == referenceId, cancellationToken);
if (reference == null)
{
return false;
}
reference.IsActive = false;
await _dbContext.SaveChangesAsync(cancellationToken);
_logger.LogInformation("Deleted reference {ReferenceId}", referenceId);
return true;
}
public async Task<int> DeleteReferencesForFileAsync(Guid fileId, CancellationToken cancellationToken = default)
{
var references = await _dbContext.FileReferences
.Where(r => r.FileId == fileId && r.IsActive)
.ToListAsync(cancellationToken);
foreach (var reference in references)
{
reference.IsActive = false;
}
var count = await _dbContext.SaveChangesAsync(cancellationToken);
_logger.LogInformation("Deleted {Count} references for file {FileId}", count, fileId);
return count;
}
public async Task<int> DeleteReferencesForResourceAsync(
string resourceId,
string resourceType,
CancellationToken cancellationToken = default)
{
var references = await _dbContext.FileReferences
.Where(r => r.ResourceId == resourceId &&
r.ResourceType == resourceType &&
r.IsActive)
.ToListAsync(cancellationToken);
foreach (var reference in references)
{
reference.IsActive = false;
}
var count = await _dbContext.SaveChangesAsync(cancellationToken);
_logger.LogInformation(
"Deleted {Count} references for resource {ResourceType}/{ResourceId}",
count, resourceType, resourceId);
return count;
}
public async Task<CloudFileReference> UpdateReferenceMetadataAsync(
Guid referenceId,
IDictionary<string, object> metadata,
CancellationToken cancellationToken = default)
{
var reference = await GetReferenceAsync(referenceId, cancellationToken);
var options = new JsonSerializerOptions { WriteIndented = true };
reference.Metadata = JsonDocument.Parse(JsonSerializer.Serialize(metadata, options));
reference.UpdatedAt = _clock.GetCurrentInstant().ToDateTimeOffset();
await _dbContext.SaveChangesAsync(cancellationToken);
_logger.LogInformation("Updated metadata for reference {ReferenceId}", referenceId);
return reference;
}
public async Task<bool> ReferenceExistsAsync(Guid referenceId, CancellationToken cancellationToken = default)
{
return await _dbContext.FileReferences
.AsNoTracking()
.AnyAsync(r => r.Id == referenceId && r.IsActive, cancellationToken);
}
public async Task<bool> HasReferenceAsync(
Guid fileId,
string resourceId,
string resourceType,
string? referenceType = null,
CancellationToken cancellationToken = default)
{
var query = _dbContext.FileReferences
.AsNoTracking()
.Where(r => r.FileId == fileId &&
r.ResourceId == resourceId &&
r.ResourceType == resourceType &&
r.IsActive);
if (!string.IsNullOrEmpty(referenceType))
{
query = query.Where(r => r.ReferenceType == referenceType);
}
return await query.AnyAsync(cancellationToken);
}
public async Task<CloudFileReference> UpdateReferenceResourceAsync(
Guid referenceId,
string newResourceId,
string newResourceType,
CancellationToken cancellationToken = default)
{
var reference = await GetReferenceAsync(referenceId, cancellationToken);
reference.ResourceId = newResourceId;
reference.ResourceType = newResourceType;
reference.UpdatedAt = _clock.GetCurrentInstant().ToDateTimeOffset();
await _dbContext.SaveChangesAsync(cancellationToken);
_logger.LogInformation(
"Updated reference {ReferenceId} to point to resource {ResourceType}/{ResourceId}",
referenceId, newResourceType, newResourceId);
return reference;
}
public async Task<IEnumerable<CloudFile>> GetFilesForResourceAsync(
string resourceId,
string resourceType,
string? referenceType = null,
CancellationToken cancellationToken = default)
{
var query = _dbContext.FileReferences
.AsNoTracking()
.Include(r => r.File)
.Where(r => r.ResourceId == resourceId &&
r.ResourceType == resourceType &&
r.IsActive);
if (!string.IsNullOrEmpty(referenceType))
{
query = query.Where(r => r.ReferenceType == referenceType);
}
var references = await query.ToListAsync(cancellationToken);
return references.Select(r => r.File!);
}
public async Task<IEnumerable<CloudFile>> GetFilesForReferenceTypeAsync(
string referenceType,
CancellationToken cancellationToken = default)
{
var references = await _dbContext.FileReferences
.AsNoTracking()
.Include(r => r.File)
.Where(r => r.ReferenceType == referenceType && r.IsActive)
.ToListAsync(cancellationToken);
return references.Select(r => r.File!);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_dbContext?.Dispose();
}
_disposed = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}