♻️ Extract the Storage service to DysonNetwork.Drive microservice
This commit is contained in:
222
DysonNetwork.Drive/Controllers/FileController.cs
Normal file
222
DysonNetwork.Drive/Controllers/FileController.cs
Normal file
@ -0,0 +1,222 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using DysonNetwork.Drive.Interfaces;
|
||||
using DysonNetwork.Drive.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace DysonNetwork.Drive.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/files")]
|
||||
[Authorize]
|
||||
public class FileController : ControllerBase
|
||||
{
|
||||
private readonly IFileService _fileService;
|
||||
private readonly ILogger<FileController> _logger;
|
||||
|
||||
public FileController(IFileService fileService, ILogger<FileController> logger)
|
||||
{
|
||||
_fileService = fileService ?? throw new ArgumentNullException(nameof(fileService));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
[HttpGet("{fileId}")]
|
||||
[ProducesResponseType(typeof(FileResult), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> GetFile(Guid fileId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var file = await _fileService.GetFileAsync(fileId, cancellationToken);
|
||||
var stream = await _fileService.DownloadFileAsync(fileId, cancellationToken);
|
||||
|
||||
return File(stream, file.MimeType, file.OriginalName);
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "File not found: {FileId}", fileId);
|
||||
return NotFound(new { message = $"File with ID {fileId} not found." });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error retrieving file: {FileId}", fileId);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, new { message = "An error occurred while retrieving the file." });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("upload")]
|
||||
[ProducesResponseType(typeof(CloudFile), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> UploadFile(IFormFile file, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (file == null || file.Length == 0)
|
||||
{
|
||||
return BadRequest(new { message = "No file uploaded." });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var stream = file.OpenReadStream();
|
||||
var uploadedFile = await _fileService.UploadFileAsync(
|
||||
stream,
|
||||
file.FileName,
|
||||
file.ContentType,
|
||||
null,
|
||||
cancellationToken);
|
||||
|
||||
return CreatedAtAction(
|
||||
nameof(GetFile),
|
||||
new { fileId = uploadedFile.Id },
|
||||
uploadedFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error uploading file: {FileName}", file?.FileName);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, new { message = "An error occurred while uploading the file." });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("{fileId}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> DeleteFile(Guid fileId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var deleted = await _fileService.DeleteFileAsync(fileId, cancellationToken);
|
||||
if (!deleted)
|
||||
{
|
||||
return NotFound(new { message = $"File with ID {fileId} not found." });
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error deleting file: {FileId}", fileId);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, new { message = "An error occurred while deleting the file." });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{fileId}/metadata")]
|
||||
[ProducesResponseType(typeof(CloudFile), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> GetFileMetadata(Guid fileId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var file = await _fileService.GetFileAsync(fileId, cancellationToken);
|
||||
return Ok(file);
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "File not found: {FileId}", fileId);
|
||||
return NotFound(new { message = $"File with ID {fileId} not found." });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error retrieving file metadata: {FileId}", fileId);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, new { message = "An error occurred while retrieving file metadata." });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{fileId}/metadata")]
|
||||
[ProducesResponseType(typeof(CloudFile), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> UpdateFileMetadata(
|
||||
Guid fileId,
|
||||
[FromBody] Dictionary<string, string> metadata,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (metadata == null || metadata.Count == 0)
|
||||
{
|
||||
return BadRequest(new { message = "No metadata provided." });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var updatedFile = await _fileService.UpdateFileMetadataAsync(fileId, metadata, cancellationToken);
|
||||
return Ok(updatedFile);
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "File not found: {FileId}", fileId);
|
||||
return NotFound(new { message = $"File with ID {fileId} not found." });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error updating file metadata: {FileId}", fileId);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, new { message = "An error occurred while updating file metadata." });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{fileId}/url")]
|
||||
[ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> GetFileUrl(Guid fileId, [FromQuery] int? expiresInSeconds = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
TimeSpan? expiry = expiresInSeconds.HasValue
|
||||
? TimeSpan.FromSeconds(expiresInSeconds.Value)
|
||||
: null;
|
||||
|
||||
var url = await _fileService.GetFileUrlAsync(fileId, expiry, cancellationToken);
|
||||
return Ok(new { url });
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "File not found: {FileId}", fileId);
|
||||
return NotFound(new { message = $"File with ID {fileId} not found." });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error generating file URL: {FileId}", fileId);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, new { message = "An error occurred while generating the file URL." });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{fileId}/thumbnail")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> GetFileThumbnail(
|
||||
Guid fileId,
|
||||
[FromQuery] int? width = null,
|
||||
[FromQuery] int? height = null,
|
||||
[FromQuery] int? expiresInSeconds = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
TimeSpan? expiry = expiresInSeconds.HasValue
|
||||
? TimeSpan.FromSeconds(expiresInSeconds.Value)
|
||||
: null;
|
||||
|
||||
var url = await _fileService.GetFileThumbnailUrlAsync(
|
||||
fileId,
|
||||
width,
|
||||
height,
|
||||
expiry,
|
||||
cancellationToken);
|
||||
|
||||
return Ok(new { url });
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "File not found: {FileId}", fileId);
|
||||
return NotFound(new { message = $"File with ID {fileId} not found." });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error generating thumbnail URL: {FileId}", fileId);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, new { message = "An error occurred while generating the thumbnail URL." });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
325
DysonNetwork.Drive/Controllers/FileReferenceController.cs
Normal file
325
DysonNetwork.Drive/Controllers/FileReferenceController.cs
Normal file
@ -0,0 +1,325 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using DysonNetwork.Drive.Interfaces;
|
||||
using DysonNetwork.Drive.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace DysonNetwork.Drive.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/references")]
|
||||
[Authorize]
|
||||
public class FileReferenceController : ControllerBase
|
||||
{
|
||||
private readonly IFileReferenceService _referenceService;
|
||||
private readonly ILogger<FileReferenceController> _logger;
|
||||
|
||||
public FileReferenceController(
|
||||
IFileReferenceService referenceService,
|
||||
ILogger<FileReferenceController> logger)
|
||||
{
|
||||
_referenceService = referenceService ?? throw new ArgumentNullException(nameof(referenceService));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(CloudFileReference), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> CreateReference([FromBody] CreateReferenceRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var reference = await _referenceService.CreateReferenceAsync(
|
||||
request.FileId,
|
||||
request.ResourceId,
|
||||
request.ResourceType,
|
||||
request.ReferenceType,
|
||||
request.ReferenceId,
|
||||
request.ReferenceName,
|
||||
request.ReferenceMimeType,
|
||||
request.ReferenceSize,
|
||||
request.ReferenceUrl,
|
||||
request.ReferenceThumbnailUrl,
|
||||
request.ReferencePreviewUrl,
|
||||
request.ReferenceMetadata,
|
||||
request.Metadata,
|
||||
cancellationToken);
|
||||
|
||||
return CreatedAtAction(
|
||||
nameof(GetReference),
|
||||
new { referenceId = reference.Id },
|
||||
reference);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error creating file reference for file {FileId}", request.FileId);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, new { message = "An error occurred while creating the file reference." });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{referenceId}")]
|
||||
[ProducesResponseType(typeof(CloudFileReference), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> GetReference(Guid referenceId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reference = await _referenceService.GetReferenceAsync(referenceId, cancellationToken);
|
||||
return Ok(reference);
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Reference not found: {ReferenceId}", referenceId);
|
||||
return NotFound(new { message = $"Reference with ID {referenceId} not found." });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error retrieving reference: {ReferenceId}", referenceId);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, new { message = "An error occurred while retrieving the reference." });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("file/{fileId}")]
|
||||
[ProducesResponseType(typeof(IEnumerable<CloudFileReference>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> GetReferencesForFile(Guid fileId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var references = await _referenceService.GetReferencesForFileAsync(fileId, cancellationToken);
|
||||
return Ok(references);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error retrieving references for file: {FileId}", fileId);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, new { message = "An error occurred while retrieving references for the file." });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("resource/{resourceType}/{resourceId}")]
|
||||
[ProducesResponseType(typeof(IEnumerable<CloudFileReference>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> GetReferencesForResource(
|
||||
string resourceType,
|
||||
string resourceId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var references = await _referenceService.GetReferencesForResourceAsync(resourceId, resourceType, cancellationToken);
|
||||
return Ok(references);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error retrieving references for resource: {ResourceType}/{ResourceId}", resourceType, resourceId);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, new { message = "An error occurred while retrieving references for the resource." });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("type/{referenceType}")]
|
||||
[ProducesResponseType(typeof(IEnumerable<CloudFileReference>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> GetReferencesOfType(string referenceType, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var references = await _referenceService.GetReferencesOfTypeAsync(referenceType, cancellationToken);
|
||||
return Ok(references);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error retrieving references of type: {ReferenceType}", referenceType);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, new { message = "An error occurred while retrieving references of the specified type." });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("{referenceId}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> DeleteReference(Guid referenceId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var deleted = await _referenceService.DeleteReferenceAsync(referenceId, cancellationToken);
|
||||
if (!deleted)
|
||||
{
|
||||
return NotFound(new { message = $"Reference with ID {referenceId} not found." });
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error deleting reference: {ReferenceId}", referenceId);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, new { message = "An error occurred while deleting the reference." });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("file/{fileId}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> DeleteReferencesForFile(Guid fileId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var count = await _referenceService.DeleteReferencesForFileAsync(fileId, cancellationToken);
|
||||
return Ok(new { count });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error deleting references for file: {FileId}", fileId);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, new { message = "An error occurred while deleting references for the file." });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("resource/{resourceType}/{resourceId}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> DeleteReferencesForResource(
|
||||
string resourceType,
|
||||
string resourceId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var count = await _referenceService.DeleteReferencesForResourceAsync(resourceId, resourceType, cancellationToken);
|
||||
return Ok(new { count });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error deleting references for resource: {ResourceType}/{ResourceId}", resourceType, resourceId);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, new { message = "An error occurred while deleting references for the resource." });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{referenceId}/metadata")]
|
||||
[ProducesResponseType(typeof(CloudFileReference), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> UpdateReferenceMetadata(
|
||||
Guid referenceId,
|
||||
[FromBody] Dictionary<string, object> metadata,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (metadata == null || metadata.Count == 0)
|
||||
{
|
||||
return BadRequest(new { message = "No metadata provided." });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var reference = await _referenceService.UpdateReferenceMetadataAsync(referenceId, metadata, cancellationToken);
|
||||
return Ok(reference);
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Reference not found: {ReferenceId}", referenceId);
|
||||
return NotFound(new { message = $"Reference with ID {referenceId} not found." });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error updating reference metadata: {ReferenceId}", referenceId);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, new { message = "An error occurred while updating the reference metadata." });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{referenceId}/resource")]
|
||||
[ProducesResponseType(typeof(CloudFileReference), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> UpdateReferenceResource(
|
||||
Guid referenceId,
|
||||
[FromBody] UpdateReferenceResourceRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var reference = await _referenceService.UpdateReferenceResourceAsync(
|
||||
referenceId,
|
||||
request.NewResourceId,
|
||||
request.NewResourceType,
|
||||
cancellationToken);
|
||||
|
||||
return Ok(reference);
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Reference not found: {ReferenceId}", referenceId);
|
||||
return NotFound(new { message = $"Reference with ID {referenceId} not found." });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error updating reference resource: {ReferenceId}", referenceId);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, new { message = "An error occurred while updating the reference resource." });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("exists/{fileId}/{resourceType}/{resourceId}")]
|
||||
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> HasReference(
|
||||
Guid fileId,
|
||||
string resourceType,
|
||||
string resourceId,
|
||||
[FromQuery] string? referenceType = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var exists = await _referenceService.HasReferenceAsync(
|
||||
fileId,
|
||||
resourceId,
|
||||
resourceType,
|
||||
referenceType,
|
||||
cancellationToken);
|
||||
|
||||
return Ok(new { exists });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(
|
||||
ex,
|
||||
"Error checking reference existence - File: {FileId}, Resource: {ResourceType}/{ResourceId}, ReferenceType: {ReferenceType}",
|
||||
fileId,
|
||||
resourceType,
|
||||
resourceId,
|
||||
referenceType);
|
||||
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, new { message = "An error occurred while checking reference existence." });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateReferenceRequest
|
||||
{
|
||||
public Guid FileId { get; set; }
|
||||
public string ResourceId { get; set; } = null!;
|
||||
public string ResourceType { get; set; } = null!;
|
||||
public string ReferenceType { get; set; } = null!;
|
||||
public string? ReferenceId { get; set; }
|
||||
public string? ReferenceName { get; set; }
|
||||
public string? ReferenceMimeType { get; set; }
|
||||
public long? ReferenceSize { get; set; }
|
||||
public string? ReferenceUrl { get; set; }
|
||||
public string? ReferenceThumbnailUrl { get; set; }
|
||||
public string? ReferencePreviewUrl { get; set; }
|
||||
public string? ReferenceMetadata { get; set; }
|
||||
public Dictionary<string, object>? Metadata { get; set; }
|
||||
}
|
||||
|
||||
public class UpdateReferenceResourceRequest
|
||||
{
|
||||
public string NewResourceId { get; set; } = null!;
|
||||
public string NewResourceType { get; set; } = null!;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user