using Microsoft.Extensions.Logging; using StellaOps.Artifact.Core; namespace StellaOps.Artifact.Infrastructure; public sealed partial class S3UnifiedArtifactStore { private async Task TryGetExistingAsync( ArtifactStoreRequest request, string fullKey, CancellationToken ct) { if (request.Overwrite || _options.AllowOverwrite) { return null; } var exists = await _client.ObjectExistsAsync(_options.BucketName, fullKey, ct).ConfigureAwait(false); if (!exists) { return null; } _logger.LogInformation("Artifact already exists at {Key}, skipping", fullKey); var entry = await _indexRepository.GetAsync( request.BomRef, request.SerialNumber, request.ArtifactId, ct).ConfigureAwait(false); return entry == null ? null : ArtifactStoreResult.Succeeded(fullKey, entry.Sha256, entry.SizeBytes, wasCreated: false); } private async Task<(byte[] ContentBytes, string Sha256)> ReadContentAsync( ArtifactStoreRequest request, CancellationToken ct) { using var memoryStream = new MemoryStream(); await request.Content.CopyToAsync(memoryStream, ct).ConfigureAwait(false); var contentBytes = memoryStream.ToArray(); var sha256 = ComputeSha256(contentBytes); return (contentBytes, sha256); } private async Task WriteContentAsync( ArtifactStoreRequest request, string fullKey, byte[] contentBytes, string sha256, CancellationToken ct) { if (_options.EnableDeduplication) { var existingBySha = await _indexRepository.FindBySha256Async(sha256, ct).ConfigureAwait(false); if (existingBySha.Count > 0) { var existingKey = existingBySha[0].StorageKey; _logger.LogInformation( "Deduplicating artifact {ArtifactId} - content matches {ExistingKey}", request.ArtifactId, existingKey); return existingKey; } } await UploadAsync(fullKey, request, contentBytes, ct).ConfigureAwait(false); return fullKey; } private async Task UploadAsync(string key, ArtifactStoreRequest request, byte[] contentBytes, CancellationToken ct) { using var uploadStream = new MemoryStream(contentBytes); var metadata = BuildS3Metadata(request); await _client.PutObjectAsync( _options.BucketName, key, uploadStream, request.ContentType, metadata, ct).ConfigureAwait(false); } private ArtifactIndexEntry BuildIndexEntry( ArtifactStoreRequest request, string storageKey, string sha256, long sizeBytes) { var now = _timeProvider.GetUtcNow(); return new ArtifactIndexEntry { Id = _guidProvider.NewGuid(), TenantId = request.TenantId, BomRef = request.BomRef, SerialNumber = request.SerialNumber, ArtifactId = request.ArtifactId, StorageKey = storageKey, Type = request.Type, ContentType = request.ContentType, Sha256 = sha256, SizeBytes = sizeBytes, CreatedAt = now }; } }