Add project files.

This commit is contained in:
Sergio Matias Urquin
2025-04-29 18:39:57 -06:00
parent 116793710f
commit 6358f5f199
110 changed files with 4484 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
using Azure.Identity;
using Core.Blueprint.DAL.Infrastructure.Context;
using Core.Blueprint.DAL.Infrastructure.Contracts;
using Core.Blueprint.DAL.Infrastructure.Proxies;
using Core.Blueprint.DAL.Infrastructure.Repository;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System.Reflection;
using Serilog;
using Serilog.Events;
using Serilog.Exceptions;
namespace Core.Blueprint.DAL.Infrastructure.Configure
{
public static class DependencyInjection
{
public static IServiceCollection RegisterInfrastructure(this IServiceCollection services, IConfiguration configuration)
{
var chainedCredentials = new ChainedTokenCredential(
new ManagedIdentityCredential(),
new SharedTokenCacheCredential(),
new VisualStudioCredential(),
new VisualStudioCodeCredential()
);
services.AddAzureClients(cfg =>
{
cfg.AddBlobServiceClient(configuration.GetRequiredSection("Azure:BlobStorage")).WithCredential(chainedCredentials);
cfg.AddSecretClient(configuration.GetRequiredSection("Azure:KeyVault")).WithCredential(chainedCredentials);
});
services.AddDbContext<SqlServerContext>(options => options.UseSqlServer(configuration.GetConnectionString("SQLServerConnString")));
// LogFactory
services.ConfigureLogging();
//MongoDB
services.Configure<MongoDbSettings>(configuration.GetRequiredSection("MongoDbSettings"));
services.AddScoped<IMongoDbSettings>(serviceProvider =>
serviceProvider.GetRequiredService<IOptions<MongoDbSettings>>().Value);
services.AddScoped<IMongoContext, MongoContext>();
services.AddScoped(serviceProvider =>
{
var mongoContext = serviceProvider.GetRequiredService<IMongoContext>();
return mongoContext.Database;
});
services.AddScoped<IProxyBlobStorage, ProxyBlobStorage>();
services.AddScoped<ISampleImageRepository, SampleImageRepository>();
services.AddScoped<ISampleItemRepository, SampleItemRepository>();
services.AddScoped<IBlueprintRepository, BlueprintRepository>();
services.AddScoped<ISecretRepository, SecretRepository>();
return services;
}
public static void ConfigureLogging(this IServiceCollection services)
{
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.Enrich.WithMachineName()
.Enrich.WithProcessId()
.Enrich.WithThreadId()
.Enrich.WithProperty("Application", Assembly.GetExecutingAssembly().GetName().Name)
.Enrich.WithExceptionDetails()
.MinimumLevel.Is(LogEventLevel.Information)
.WriteTo.Console()
.CreateLogger();
services.AddSingleton(Log.Logger);
}
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Core.Blueprint.DAL.Infrastructure.Configure
{
class LoggerExtension
{
}
}

View File

@@ -0,0 +1,135 @@
using Azure.Core;
using Azure.Identity;
using Core.Blueprint.DAL.Infrastructure.Contracts;
using Core.Blueprint.Domain.Shared;
using Microsoft.Extensions.Configuration;
using MongoDB.Driver;
using MongoDB.Driver.Authentication.Oidc;
using System.Reflection;
namespace Core.Blueprint.DAL.Infrastructure.Context
{
public interface IMongoDbSettings
{
string DatabaseName { get; set; }
string ConnectionString { get; set; }
}
public class MongoDbSettings : IMongoDbSettings
{
public string DatabaseName { get; set; }
public string ConnectionString { get; set; }
}
public class HeathOidcCallback : IOidcCallback
{
private readonly string _audience;
public HeathOidcCallback(string audience)
{
_audience = audience;
}
public OidcAccessToken GetOidcAccessToken(OidcCallbackParameters parameters, CancellationToken cancellationToken)
{
TokenRequestContext tokenRequestContext =
new TokenRequestContext(
new[] { _audience }
);
AccessToken token =
new ChainedTokenCredential(
new ManagedIdentityCredential(),
new SharedTokenCacheCredential(),
new VisualStudioCredential(),
new VisualStudioCodeCredential()
)
.GetToken(
tokenRequestContext
);
return new(token.Token, expiresIn: null);
}
public async Task<OidcAccessToken> GetOidcAccessTokenAsync(OidcCallbackParameters parameters, CancellationToken cancellationToken)
{
TokenRequestContext tokenRequestContext =
new TokenRequestContext(
new[] { _audience }
);
AccessToken token =
await new ChainedTokenCredential(
new ManagedIdentityCredential(),
new SharedTokenCacheCredential(),
new VisualStudioCredential(),
new VisualStudioCodeCredential()
)
.GetTokenAsync(
tokenRequestContext, cancellationToken
).ConfigureAwait(false);
return new(token.Token, expiresIn: null);
}
}
public sealed class MongoContext : IMongoContext
{
private IMongoDatabase _database;
public IMongoClient _client { get; private set; }
public IMongoDatabase Database { get { return _database; } }
public MongoUrl _mongoUrl { get; private set; }
//Constructors
public MongoContext(IConfiguration configuration)
{
var mongoClientSettings = MongoClientSettings.FromConnectionString(configuration.GetConnectionString("MongoDBConnString"));
mongoClientSettings.Credential = MongoCredential.CreateOidcCredential(new HeathOidcCallback(configuration.GetValue<string>("MongoDb:Audience")));
_database = new MongoClient(mongoClientSettings).GetDatabase(configuration.GetValue<string>("MongoDb:DatabaseName")); //Mongo Database
_client = Database.Client;
}
public MongoContext(string connectionString, string audience, string databaseName)
{
var mongoClientSettings = MongoClientSettings.FromConnectionString(connectionString);
mongoClientSettings.Credential = MongoCredential.CreateOidcCredential(new HeathOidcCallback(audience));
_client = new MongoClient(mongoClientSettings);
_database = _client.GetDatabase(databaseName);
}
public MongoContext(MongoClient client, string databaseName)
{
_client = client;
_database = client.GetDatabase(databaseName);
}
public void SetUpDatabase(string database)
{
_database = new MongoClient(_mongoUrl).GetDatabase(database);
_client = Database.Client;
}
//Methods
public void DropCollection<TDocument>(string? partitionKey = null) where TDocument : class
{
Database.DropCollection(GetCollectionName<TDocument>(partitionKey));
}
public IMongoCollection<T> GetCollection<T>(string collection)
{
return _database.GetCollection<T>(collection);
}
private string GetCollectionName<TDocument>(string partitionKey)
{
var collectionName = GetAttributeCollectionName<TDocument>();
if (string.IsNullOrEmpty(partitionKey))
{
return collectionName;
}
return $"{partitionKey}-{collectionName}";
}
private string? GetAttributeCollectionName<TDocument>()
{
return (typeof(TDocument).GetTypeInfo()
.GetCustomAttributes(typeof(CollectionAttributeName))
.FirstOrDefault() as CollectionAttributeName)?.Name;
}
}
}

View File

@@ -0,0 +1,16 @@
using Core.Blueprint.Domain.Entities;
using Microsoft.EntityFrameworkCore;
namespace Core.Blueprint.DAL.Infrastructure.Context
{
public sealed class SqlServerContext : DbContext
{
public SqlServerContext(DbContextOptions options) : base(options) { }
public DbSet<SampleItem> BlueprintTests { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<SampleItem>().Property(p => p.Id).HasConversion(a => a, b => b);
}
}
}

View File

@@ -0,0 +1,9 @@
using Core.Blueprint.Domain.Entities;
namespace Core.Blueprint.DAL.Infrastructure.Contracts
{
public interface IBlueprintRepository : IRepositoryIdentityBase<BlueprintCollection>
{
}
}

View File

@@ -0,0 +1,13 @@
using MongoDB.Driver;
namespace Core.Blueprint.DAL.Infrastructure.Contracts
{
public interface IMongoContext
{
IMongoClient _client { get; }
IMongoDatabase Database { get; }
void DropCollection<TDocument>(string partitionKey = null) where TDocument : class;
void SetUpDatabase(string database);
}
}

View File

@@ -0,0 +1,11 @@
using Core.Blueprint.Domain.Dtos;
namespace Core.Blueprint.DAL.Infrastructure.Contracts
{
public interface IProxyBlobStorage
{
IAsyncEnumerable<ImageUrlDto> ListAllItemsAsync();
Task<ImageUrlDto> GetFirstImageUrlAsync();
Task<ImageUrlDto> GetUploadUrl();
}
}

View File

@@ -0,0 +1,11 @@
namespace Core.Blueprint.DAL.Infrastructure.Contracts
{
public interface IRepositoryBase<T> where T : class
{
ValueTask<T> GetByIdAsync(string id);
ValueTask<IQueryable<T>> GetAllAsync();
ValueTask<T> CreateAsync(T blueprint);
Task<bool> UpdateAsync(string id, T blueprint);
Task<bool> DeleteAsync(string id);
}
}

View File

@@ -0,0 +1,9 @@
using Core.Blueprint.Domain.Entities;
namespace Core.Blueprint.DAL.Infrastructure.Contracts
{
public interface IRepositoryIdentityBase<T> : IRepositoryBase<T> where T : AbsEntity
{
ValueTask<T> GetByIdAsync(Guid id) => GetByIdAsync(id.ToString());
}
}

View File

@@ -0,0 +1,11 @@
using Core.Blueprint.Domain.Dtos;
namespace Core.Blueprint.DAL.Infrastructure.Contracts
{
public interface ISampleImageRepository
{
IAsyncEnumerable<ImageUrlDto> GetAllImagesUrls();
Task<ImageUrlDto> GetFirstImageUrl();
Task<ImageUrlDto> GetUploadUrl();
}
}

View File

@@ -0,0 +1,8 @@
using Core.Blueprint.Domain.Entities;
namespace Core.Blueprint.DAL.Infrastructure.Contracts
{
public interface ISampleItemRepository : IRepositoryIdentityBase<SampleItem>
{
}
}

View File

@@ -0,0 +1,11 @@
using Core.Blueprint.Domain.Entities;
namespace Core.Blueprint.DAL.Infrastructure.Contracts
{
public interface ISecretRepository
{
Task<Secret> GetSecret(string secretName, CancellationToken cancellationToken);
Task SetSecret(string secretName, string secretValue, CancellationToken cancellationToken);
Task RemoveSecret(string secretName, CancellationToken cancellationToken);
}
}

View File

@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.13.1" />
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.7.0" />
<PackageReference Include="Azure.Storage.Blobs" Version="12.23.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Azure" Version="1.8.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.0" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.66.2" />
<PackageReference Include="MongoDB.Driver" Version="3.0.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="3.0.1" />
<PackageReference Include="Serilog.Enrichers.Process" Version="3.0.0" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" />
<PackageReference Include="Serilog.Exceptions" Version="8.4.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Core.Blueprint.Domain\Core.Blueprint.Domain.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,95 @@
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Azure.Storage.Sas;
using Core.Blueprint.DAL.Infrastructure.Contracts;
using Core.Blueprint.Domain.Dtos;
using Microsoft.Extensions.Azure;
using System.Reflection.Metadata;
namespace Core.Blueprint.DAL.Infrastructure.Proxies;
public class ProxyBlobStorage : IProxyBlobStorage
{
private readonly BlobServiceClient _blobServiceClient;
private readonly BlobContainerClient _blobContainerClient;
private const string TIMESTAMP_TAG_NAME = "Timestamp";
private UserDelegationKey _blobDelegationKey;
public ProxyBlobStorage(BlobServiceClient blobServiceClient)
{
_blobServiceClient = blobServiceClient;
_blobContainerClient = blobServiceClient.GetBlobContainerClient("blueprint");
_blobDelegationKey = _blobServiceClient.GetUserDelegationKey(DateTimeOffset.UtcNow,
DateTimeOffset.UtcNow.AddHours(2));
}
public async IAsyncEnumerable<ImageUrlDto> ListAllItemsAsync()
{
await using (var blobs = _blobContainerClient.GetBlobsAsync().GetAsyncEnumerator())
{
while (await blobs.MoveNextAsync())
{
yield return new ImageUrlDto
{
Url = _blobContainerClient.GenerateSasUri(BlobContainerSasPermissions.Read, DateTimeOffset.UtcNow.AddMinutes(5))
};
}
}
}
public async Task<ImageUrlDto> GetFirstImageUrlAsync()
{
await using (var blob = _blobContainerClient.GetBlobsAsync().GetAsyncEnumerator())
{
System.Console.WriteLine(_blobContainerClient.Uri.ToString());
await blob.MoveNextAsync();
var blobClient = _blobContainerClient.GetBlobClient(blob.Current.Name);
var sasBuilder = new BlobSasBuilder()
{
BlobContainerName = blobClient.BlobContainerName,
BlobName = blobClient.Name,
Resource = "b",
StartsOn = DateTimeOffset.UtcNow,
ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(5),
};
sasBuilder.SetPermissions(BlobAccountSasPermissions.Read);
var blobUriBuilder = new BlobUriBuilder(blobClient.Uri){
Sas = sasBuilder.ToSasQueryParameters(_blobDelegationKey, _blobServiceClient.AccountName)
};
var uri = blobUriBuilder.ToUri();
System.Console.WriteLine(uri);
return new ImageUrlDto { Url = uri };
}
}
public async Task<ImageUrlDto> GetUploadUrl()
{
await using (var blobs = _blobContainerClient.GetBlobsAsync().GetAsyncEnumerator())
{
await blobs.MoveNextAsync();
var blobClient = _blobContainerClient.GetBlobClient(blobs.Current.Name);
var sasBuilder = new BlobSasBuilder(){
BlobContainerName = blobClient.BlobContainerName,
BlobName = blobClient.Name,
Resource = "b",
StartsOn = DateTimeOffset.UtcNow,
ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(5),
};
sasBuilder.SetPermissions(BlobAccountSasPermissions.Read);
var blobUriBuilder = new BlobUriBuilder(blobClient.Uri){
Sas = sasBuilder.ToSasQueryParameters(_blobDelegationKey, _blobServiceClient.AccountName)
};
var uri = blobUriBuilder.ToUri();
return new ImageUrlDto { Url = uri };
}
}
}

View File

@@ -0,0 +1,52 @@
using Core.Blueprint.DAL.Infrastructure.Contracts;
using Core.Blueprint.Domain.Entities;
using Microsoft.Extensions.Logging;
using MongoDB.Driver;
namespace Core.Blueprint.DAL.Infrastructure.Repository
{
public sealed class BlueprintRepository: IBlueprintRepository
{
private readonly IMongoContext _context;
private readonly ILogger<BlueprintRepository> _logger;
private readonly IMongoCollection<BlueprintCollection> _collection;
public BlueprintRepository(IMongoContext context, IProxyBlobStorage blobProxy, ILogger<BlueprintRepository> logger)
{
_context = context;
_logger = logger;
_collection = _context.Database.GetCollection<BlueprintCollection>("Blueprints");
}
public async ValueTask<BlueprintCollection> GetByIdAsync(string id)
{
var result = await _collection.FindAsync(b => b.Id == id);
return result.First();
}
async ValueTask<IQueryable<BlueprintCollection>> IRepositoryBase<BlueprintCollection>.GetAllAsync()
{
var result = await _collection.FindAsync(_ => true);
return (await result.ToListAsync()).AsQueryable();
}
async ValueTask<BlueprintCollection> IRepositoryBase<BlueprintCollection>.CreateAsync(BlueprintCollection blueprint)
{
blueprint.Id = null;
await _collection.InsertOneAsync(blueprint);
return blueprint;
}
public async Task<bool> UpdateAsync(string id, BlueprintCollection blueprint)
{
var result = await _collection.ReplaceOneAsync(b => b.Id == id, blueprint);
return result.IsAcknowledged && result.ModifiedCount > 0;
}
public async Task<bool> DeleteAsync(string id)
{
var result = await _collection.DeleteOneAsync(b => b.Id == id);
return result.IsAcknowledged && result.DeletedCount > 0;
}
}
}

View File

@@ -0,0 +1,54 @@
using Core.Blueprint.DAL.Infrastructure.Context;
using Core.Blueprint.DAL.Infrastructure.Contracts;
using Core.Blueprint.Domain.Entities;
namespace Core.Blueprint.DAL.Infrastructure.Repository
{
public abstract class RepositorySqlBase<T> : IRepositoryIdentityBase<T> where T: AbsEntity
{
protected SqlServerContext _dbContext;
protected RepositorySqlBase(SqlServerContext dbContext)
{
_dbContext = dbContext;
}
public async ValueTask<T> CreateAsync(T blueprint)
{
blueprint.Id = Guid.NewGuid().ToString();
_dbContext.Add(blueprint);
await _dbContext.SaveChangesAsync();
return blueprint;
}
public async Task<bool> DeleteAsync(string id)
{
var item = await GetByIdAsync(id);
if (item == null)
{
return false;
}
_dbContext.Remove(item);
await _dbContext.SaveChangesAsync();
return true;
}
public async ValueTask<IQueryable<T>> GetAllAsync()
{
return _dbContext.Set<T>();
}
public async ValueTask<T> GetByIdAsync(string id)
{
var result = await _dbContext.FindAsync<T>(id);
return result;
}
public async Task<bool> UpdateAsync(string id, T entity)
{
_dbContext.Update(entity);
await _dbContext.SaveChangesAsync();
return true;
}
}
}

View File

@@ -0,0 +1,33 @@
using Core.Blueprint.DAL.Infrastructure.Contracts;
using Core.Blueprint.Domain.Dtos;
namespace Core.Blueprint.DAL.Infrastructure.Repository
{
public class SampleImageRepository : ISampleImageRepository
{
private readonly IProxyBlobStorage _proxyBlobStorage;
public SampleImageRepository(IProxyBlobStorage proxyBlobStorage)
{
_proxyBlobStorage = proxyBlobStorage;
}
public async IAsyncEnumerable<ImageUrlDto> GetAllImagesUrls()
{
await using (var images = _proxyBlobStorage.ListAllItemsAsync().GetAsyncEnumerator())
{
await images.MoveNextAsync();
yield return images.Current;
}
}
public async Task<ImageUrlDto> GetFirstImageUrl()
{
return await _proxyBlobStorage.GetFirstImageUrlAsync();
}
public async Task<ImageUrlDto> GetUploadUrl()
{
return await _proxyBlobStorage.GetUploadUrl();
}
}
}

View File

@@ -0,0 +1,16 @@
using Core.Blueprint.DAL.Infrastructure.Context;
using Core.Blueprint.DAL.Infrastructure.Contracts;
using Core.Blueprint.Domain.Entities;
using Microsoft.Extensions.Logging;
namespace Core.Blueprint.DAL.Infrastructure.Repository
{
public class SampleItemRepository : RepositorySqlBase<SampleItem>, ISampleItemRepository
{
private readonly ILogger<SampleItemRepository> _logger;
public SampleItemRepository(SqlServerContext dbContext, ILogger<SampleItemRepository> logger) : base(dbContext)
{
_logger = logger;
}
}
}

View File

@@ -0,0 +1,41 @@
using Azure.Security.KeyVault.Secrets;
using Core.Blueprint.DAL.Infrastructure.Contracts;
using Core.Blueprint.Domain.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Core.Blueprint.DAL.Infrastructure.Repository
{
public class SecretRepository : ISecretRepository
{
private readonly SecretClient _client;
public SecretRepository(SecretClient client)
{
_client = client;
}
public async Task<Secret> GetSecret(string secretName, CancellationToken cancellationToken)
{
var ret = await _client.GetSecretAsync(secretName, cancellationToken: cancellationToken);
return new Secret() { Value = ret.Value?.Value };
}
public async Task SetSecret(string secretName, string secretValue, CancellationToken cancellationToken)
{
await _client.SetSecretAsync(new KeyVaultSecret(secretName, secretValue), cancellationToken: cancellationToken);
}
public async Task RemoveSecret(string secretName, CancellationToken cancellationToken)
{
await _client.StartDeleteSecretAsync(secretName, cancellationToken: cancellationToken);
}
}
}