Skip to content
17 changes: 15 additions & 2 deletions src/AzureOpenAIProxy.ApiApp/Endpoints/AdminResourceEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static RouteHandlerBuilder AddNewAdminResource(this WebApplication app)
{
var builder = app.MapPost(AdminEndpointUrls.AdminResources, async (
[FromBody] AdminResourceDetails payload,
IAdminEventService service,
IAdminResourceService service,
ILoggerFactory loggerFactory) =>
{
var logger = loggerFactory.CreateLogger(nameof(AdminResourceEndpoints));
Expand All @@ -32,7 +32,20 @@ public static RouteHandlerBuilder AddNewAdminResource(this WebApplication app)
return Results.BadRequest("Payload is null");
}

return await Task.FromResult(Results.Ok());
try
{
var result = await service.CreateResource(payload);

logger.LogInformation("Created a new resource");

return Results.Ok(result);
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to create a new resource");

return Results.Problem(ex.Message, statusCode: StatusCodes.Status500InternalServerError);
}
})
.Accepts<AdminResourceDetails>(contentType: "application/json")
.Produces<AdminResourceDetails>(statusCode: StatusCodes.Status200OK, contentType: "application/json")
Expand Down
2 changes: 2 additions & 0 deletions src/AzureOpenAIProxy.ApiApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@

// Add admin services
builder.Services.AddAdminEventService();
builder.Services.AddAdminResourceService();

// Add admin repositories
builder.Services.AddAdminEventRepository();
builder.Services.AddAdminResourceRepository();

var app = builder.Build();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using Azure.Data.Tables;

using AzureOpenAIProxy.ApiApp.Configurations;
using AzureOpenAIProxy.ApiApp.Models;

namespace AzureOpenAIProxy.ApiApp.Repositories;

/// <summary>
/// This provides interfaces to the <see cref="AdminResourceRepository"/> class.
/// </summary>
public interface IAdminResourceRepository
{
/// <summary>
/// Creates a new record of resource details.
/// </summary>
/// <param name="resourceDetails">Resource details instance.</param>
/// <returns>Returns the resource details instance created.</returns>
Task<AdminResourceDetails> CreateResource(AdminResourceDetails resourceDetails);
}

/// <summary>
/// This represents the repository entity for the admin resource.
/// </summary>
public class AdminResourceRepository(TableServiceClient tableServiceClient, StorageAccountSettings storageAccountSettings) : IAdminResourceRepository
{
private readonly TableServiceClient _tableServiceClient = tableServiceClient ?? throw new ArgumentNullException(nameof(tableServiceClient));
private readonly StorageAccountSettings _storageAccountSettings = storageAccountSettings ?? throw new ArgumentNullException(nameof(storageAccountSettings));

/// <inheritdoc />
public async Task<AdminResourceDetails> CreateResource(AdminResourceDetails resourceDetails)
{
TableClient tableClient = await GetTableClientAsync();

await tableClient.AddEntityAsync(resourceDetails).ConfigureAwait(false);

return resourceDetails;
}

private async Task<TableClient> GetTableClientAsync()
{
TableClient tableClient = _tableServiceClient.GetTableClient(_storageAccountSettings.TableStorage.TableName);

await tableClient.CreateIfNotExistsAsync().ConfigureAwait(false);

return tableClient;
}
}

/// <summary>
/// This represents the extension class for <see cref="IServiceCollection"/>
/// </summary>
public static class AdminResourceRepositoryExtensions
{
/// <summary>
/// Adds the <see cref="AdminResourceRepository"/> instance to the service collection.
/// </summary>
/// <param name="services"><see cref="IServiceCollection"/> instance.</param>
/// <returns>Returns <see cref="IServiceCollection"/> instance.</returns>
public static IServiceCollection AddAdminResourceRepository(this IServiceCollection services)
{
services.AddScoped<IAdminResourceRepository, AdminResourceRepository>();

return services;
}
}
52 changes: 52 additions & 0 deletions src/AzureOpenAIProxy.ApiApp/Services/AdminResourceService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using AzureOpenAIProxy.ApiApp.Models;
using AzureOpenAIProxy.ApiApp.Repositories;

namespace AzureOpenAIProxy.ApiApp.Services;

/// <summary>
/// This provides interfaces to the <see cref="AdminResourceService"/> class.
/// </summary>
public interface IAdminResourceService
{
/// <summary>
/// Creates a new resource.
/// </summary>
/// <param name="resourceDetails">Resource payload.</param>
/// <returns>Returns the resource payload created.</returns>
Task<AdminResourceDetails> CreateResource(AdminResourceDetails resourceDetails);
}

/// <summary>
/// This represents the service entity for admin resource.
/// </summary>
public class AdminResourceService(IAdminResourceRepository repository) : IAdminResourceService
{
private readonly IAdminResourceRepository _repository = repository ?? throw new ArgumentNullException(nameof(repository));

/// <inheritdoc />
public async Task<AdminResourceDetails> CreateResource(AdminResourceDetails resourceDetails)
{
resourceDetails.PartitionKey = PartitionKeys.ResourceDetails;
resourceDetails.RowKey = resourceDetails.ResourceId.ToString();

var result = await _repository.CreateResource(resourceDetails).ConfigureAwait(false);
return result;
}
}

/// <summary>
/// This represents the extension class for <see cref="IServiceCollection"/>.
/// </summary>
public static class AdminResourceServiceExtensions
{
/// <summary>
/// Adds the <see cref="AdminResourceService"/> instance to the service collection.
/// </summary>
/// <param name="services"><see cref="IServiceCollection"/> instance.</param>
/// <returns>Returns <see cref="IServiceCollection"/> instance.</returns>
public static IServiceCollection AddAdminResourceService(this IServiceCollection services)
{
services.AddScoped<IAdminResourceService, AdminResourceService>();
return services;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
},
"KeyVault": {
"VaultUri": "https://{{key-vault-name}}.vault.azure.net/",
"SecretName": "azure-openai-instances"
"SecretNames": {
"OpenAI": "azure-openai-instances",
"Storage": "storage-connection-string"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Azure;
using Azure.Data.Tables;

using AzureOpenAIProxy.ApiApp.Configurations;
using AzureOpenAIProxy.ApiApp.Models;
using AzureOpenAIProxy.ApiApp.Repositories;

using FluentAssertions;

using Microsoft.Extensions.DependencyInjection;

using NSubstitute;
using NSubstitute.ExceptionExtensions;

namespace AzureOpenAIProxy.ApiApp.Tests.Repositories;

public class AdminResourceRepositoryTests
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Azure;

using AzureOpenAIProxy.ApiApp.Models;
using AzureOpenAIProxy.ApiApp.Repositories;
using AzureOpenAIProxy.ApiApp.Services;

using FluentAssertions;

using Microsoft.Extensions.DependencyInjection;

using NSubstitute;
using NSubstitute.ExceptionExtensions;

namespace AzureOpenAIProxy.ApiApp.Tests.Services;

public class AdminResourceServiceTests
{
}
Loading