Update
All checks were successful
Build & Deploy PLDpro.Web Test to 192.168.1.100 / build-and-deploy (push) Successful in 1m13s
All checks were successful
Build & Deploy PLDpro.Web Test to 192.168.1.100 / build-and-deploy (push) Successful in 1m13s
This commit is contained in:
@@ -9,4 +9,5 @@ public interface IStorageService
|
||||
Task CreateBucketAsync(string bucketName, CancellationToken ct = default);
|
||||
Task<IEnumerable<ObjectItem>> ListObjectsAsync(string bucket, CancellationToken ct = default);
|
||||
Task UploadObjectAsync(string bucket, string key, Stream content, string contentType, CancellationToken ct = default);
|
||||
Task<(Stream Stream, string ContentType, long? ContentLength)> GetObjectAsync(string bucket, string key, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
@@ -33,9 +33,11 @@ public sealed class S3StorageService(IAmazonS3 s3) : IStorageService
|
||||
BucketName = bucket,
|
||||
ContinuationToken = token
|
||||
}, ct);
|
||||
|
||||
items.AddRange(resp.S3Objects.Select(o => new ObjectItem(o.Key, o.Size, o.LastModified)));
|
||||
token = (bool)resp.IsTruncated ? resp.NextContinuationToken : null;
|
||||
if (resp.S3Objects != null)
|
||||
{
|
||||
items.AddRange(resp.S3Objects.Select(o => new ObjectItem(o.Key, o.Size, o.LastModified)));
|
||||
token = (bool)resp.IsTruncated ? resp.NextContinuationToken : null;
|
||||
}
|
||||
} while (token is not null);
|
||||
|
||||
return items;
|
||||
@@ -52,4 +54,24 @@ public sealed class S3StorageService(IAmazonS3 s3) : IStorageService
|
||||
};
|
||||
await _s3.PutObjectAsync(req, ct);
|
||||
}
|
||||
|
||||
|
||||
public async Task<(Stream Stream, string ContentType, long? ContentLength)> GetObjectAsync(
|
||||
string bucket, string key, CancellationToken ct = default)
|
||||
{
|
||||
var resp = await _s3.GetObjectAsync(new GetObjectRequest
|
||||
{
|
||||
BucketName = bucket,
|
||||
Key = key
|
||||
}, ct);
|
||||
|
||||
// ResponseStream NICHT kopieren, sondern direkt zurückgeben (Server streamt es weiter)
|
||||
var contentType = string.IsNullOrWhiteSpace(resp.Headers.ContentType)
|
||||
? "application/octet-stream"
|
||||
: resp.Headers.ContentType;
|
||||
|
||||
long? len = resp.Headers.ContentLength >= 0 ? resp.Headers.ContentLength : null;
|
||||
return (resp.ResponseStream, contentType, len);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
20
Services/Storage/IStorageMetadataRepository.cs
Normal file
20
Services/Storage/IStorageMetadataRepository.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
namespace Pldpro.Web.Services;
|
||||
|
||||
public interface IStorageMetadataRepository
|
||||
{
|
||||
Task EnsureSchemaAsync(CancellationToken ct = default);
|
||||
Task UpsertAsync(string bucket, string fileName, string? path, string key, long? size, string? contentType, CancellationToken ct = default);
|
||||
Task<StorageObject?> TryGetAsync(string bucket, string fileName, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
public sealed record StorageObject(
|
||||
long Id,
|
||||
string Bucket,
|
||||
string FileName,
|
||||
string? Path,
|
||||
string Key,
|
||||
long? Size,
|
||||
string? ContentType,
|
||||
DateTime CreatedUtc
|
||||
);
|
||||
93
Services/Storage/StorageMetaDataRepository.cs
Normal file
93
Services/Storage/StorageMetaDataRepository.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
|
||||
using MySqlConnector;
|
||||
using System.Data;
|
||||
|
||||
namespace Pldpro.Web.Services;
|
||||
|
||||
public sealed class StorageMetadataRepository : IStorageMetadataRepository
|
||||
{
|
||||
private readonly string _connStr;
|
||||
public StorageMetadataRepository(IConfiguration cfg) =>
|
||||
_connStr = cfg.GetConnectionString("StorageDb") ?? throw new InvalidOperationException("ConnectionStrings:StorageDb missing");
|
||||
|
||||
public async Task EnsureSchemaAsync(CancellationToken ct = default)
|
||||
{
|
||||
const string sql = """
|
||||
CREATE TABLE IF NOT EXISTS storage_objects (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
bucket VARCHAR(63) NOT NULL,
|
||||
file_name VARCHAR(255) NOT NULL,
|
||||
path VARCHAR(768) NULL,
|
||||
s3_key VARCHAR(1024) NOT NULL,
|
||||
size BIGINT NULL,
|
||||
content_type VARCHAR(255) NULL,
|
||||
created_utc DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uq_bucket_file (bucket, file_name),
|
||||
INDEX ix_bucket_path (bucket, path(255))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
""";
|
||||
|
||||
await using var conn = new MySqlConnection(_connStr);
|
||||
await conn.OpenAsync(ct);
|
||||
await using var cmd = new MySqlCommand(sql, conn);
|
||||
await cmd.ExecuteNonQueryAsync(ct);
|
||||
}
|
||||
|
||||
public async Task UpsertAsync(string bucket, string fileName, string? path, string key, long? size, string? contentType, CancellationToken ct = default)
|
||||
{
|
||||
const string sql = """
|
||||
INSERT INTO storage_objects (bucket, file_name, path, s3_key, size, content_type)
|
||||
VALUES (@bucket, @name, @path, @key, @size, @ct)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
path = VALUES(path),
|
||||
s3_key = VALUES(s3_key),
|
||||
size = VALUES(size),
|
||||
content_type = VALUES(content_type);
|
||||
""";
|
||||
|
||||
await using var conn = new MySqlConnection(_connStr);
|
||||
await conn.OpenAsync(ct);
|
||||
await using var cmd = new MySqlCommand(sql, conn);
|
||||
cmd.Parameters.AddWithValue("@bucket", bucket);
|
||||
cmd.Parameters.AddWithValue("@name", fileName);
|
||||
cmd.Parameters.AddWithValue("@path", (object?)path ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@key", key);
|
||||
cmd.Parameters.AddWithValue("@size", (object?)size ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@ct", (object?)contentType ?? DBNull.Value);
|
||||
await cmd.ExecuteNonQueryAsync(ct);
|
||||
}
|
||||
|
||||
public async Task<StorageObject?> TryGetAsync(string bucket, string fileName, CancellationToken ct = default)
|
||||
{
|
||||
const string sql = """
|
||||
SELECT id, bucket, file_name, path, s3_key, size, content_type, created_utc
|
||||
FROM storage_objects
|
||||
WHERE bucket = @bucket AND file_name = @name
|
||||
LIMIT 1;
|
||||
""";
|
||||
|
||||
await using var conn = new MySqlConnection(_connStr);
|
||||
await conn.OpenAsync(ct);
|
||||
|
||||
await using var cmd = new MySqlCommand(sql, conn);
|
||||
cmd.Parameters.AddWithValue("@bucket", bucket);
|
||||
cmd.Parameters.AddWithValue("@name", fileName);
|
||||
|
||||
await using var reader = await cmd.ExecuteReaderAsync(CommandBehavior.SingleRow, ct);
|
||||
if (await reader.ReadAsync(ct))
|
||||
{
|
||||
return new StorageObject(
|
||||
reader.GetInt64(0),
|
||||
reader.GetString(1),
|
||||
reader.GetString(2),
|
||||
reader.IsDBNull(3) ? null : reader.GetString(3),
|
||||
reader.GetString(4),
|
||||
reader.IsDBNull(5) ? null : reader.GetInt64(5),
|
||||
reader.IsDBNull(6) ? null : reader.GetString(6),
|
||||
reader.GetDateTime(7)
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user