Add project files.
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
<Solution>
|
||||||
|
<Project Path="WebApi/WebApi.csproj" />
|
||||||
|
</Solution>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"Default": ""
|
||||||
|
},
|
||||||
|
"Storage": {
|
||||||
|
"BucketName": "",
|
||||||
|
"Routes": {
|
||||||
|
"Uploads": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"Default": ""
|
||||||
|
},
|
||||||
|
"PlayListConfiguration": {
|
||||||
|
"Routes": {
|
||||||
|
"ParentFolders": "PlayLists",
|
||||||
|
"OriginalPicturesFolder": "OriginalPictures",
|
||||||
|
"SmallPicturesFolder": "SmallPictures",
|
||||||
|
"MediumPicturesFolder": "MediumPictures",
|
||||||
|
"LargePicturesFolder": "LargePictures",
|
||||||
|
"PresetOriginalPicture": "PresetOriginalPicture.webp",
|
||||||
|
"PresetSmallPicture": "PresetSmallPicture.webp",
|
||||||
|
"PresetMediumPicture": "PresetMediumPicture.webp",
|
||||||
|
"PresetLargePicture": "PresetLargePicture.webp"
|
||||||
|
},
|
||||||
|
"PicturesSizes": {
|
||||||
|
"SmallPictureWidth": 128,
|
||||||
|
"SmallPictureHeight": 128,
|
||||||
|
"MediumPictureWidth": 256,
|
||||||
|
"MediumPictureHeight": 256,
|
||||||
|
"LargePictureWidth": 512,
|
||||||
|
"LargePictureHeight": 512
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Keycloak": {
|
||||||
|
"Address": "1",
|
||||||
|
"Realm": "1",
|
||||||
|
"Username": "1",
|
||||||
|
"Password": "1"
|
||||||
|
},
|
||||||
|
"Database": {
|
||||||
|
"ConnectionString": "Host=localhost;Port=59000;Database=musify_db;Username=postgres;Password=postgres"
|
||||||
|
},
|
||||||
|
"ApplicationStorage": {
|
||||||
|
"BucketName": "webapi-storage"
|
||||||
|
},
|
||||||
|
"InfrastructureStorage": {
|
||||||
|
"Address": "http://localhost:8333",
|
||||||
|
"AccessKey": "admin_access_key",
|
||||||
|
"SecretAccessKey": "admin_secret_key",
|
||||||
|
"ForcePathStyle": true,
|
||||||
|
"UseHttp": true
|
||||||
|
},
|
||||||
|
"Messaging": {
|
||||||
|
"Address": "rabbitmq://127.0.0.1:5672",
|
||||||
|
"Username": "guest",
|
||||||
|
"Password": "guest"
|
||||||
|
},
|
||||||
|
"OpenTelemetry": {
|
||||||
|
"OtlpEndpoint": "http://localhost:4317"
|
||||||
|
},
|
||||||
|
"TrackConfiguration": {
|
||||||
|
"Routes": {
|
||||||
|
"ParentFolder": "Tracks",
|
||||||
|
"OriginalPicturesFolder": "OriginalPictures",
|
||||||
|
"SmallPicturesFolder": "SmallPictures",
|
||||||
|
"MediumPicturesFolder": "MediumPictures",
|
||||||
|
"LargePicturesFolder": "LargePictures",
|
||||||
|
"PresetOriginalPicture": "PresetOriginalPicture.webp",
|
||||||
|
"PresetSmallPicture": "PresetSmallPicture.webp",
|
||||||
|
"PresetMediumPicture": "PresetMediumPicture.webp",
|
||||||
|
"PresetLargePicture": "PresetLargePicture.webp",
|
||||||
|
"OriginalAudiosFolder": "OriginalAudios",
|
||||||
|
"ProcessedAudioFolder": "ProcessedAudios"
|
||||||
|
},
|
||||||
|
"PicturesSizes": {
|
||||||
|
"SmallPictureWidth": 128,
|
||||||
|
"SmallPictureHeight": 128,
|
||||||
|
"MediumPictureWidth": 256,
|
||||||
|
"MediumPictureHeight": 256,
|
||||||
|
"LargePictureWidth": 512,
|
||||||
|
"LargePictureHeight": 512
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AudioTranscoder": {
|
||||||
|
"Routes": {
|
||||||
|
"WorkingDirectory": "D:\\tempsFilesDev"
|
||||||
|
},
|
||||||
|
"TranscodingTimeout": "00:05:00"
|
||||||
|
},
|
||||||
|
"Workers": {
|
||||||
|
"Routes": {
|
||||||
|
"TemporaryFilesDirectory": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace WebApi.DataTransferObjects.PlayLists;
|
||||||
|
|
||||||
|
public record CreatePlayListRequest(
|
||||||
|
string Name,
|
||||||
|
string Description,
|
||||||
|
Guid? PictureIntentId);
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace WebApi.DataTransferObjects.PlayLists;
|
||||||
|
|
||||||
|
public record RequestPlayListPictureUploadRequest(
|
||||||
|
string FileType,
|
||||||
|
string ContentType,
|
||||||
|
long? ExpectedSizeBytes = null);
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace WebApi.DataTransferObjects.PlayLists;
|
||||||
|
|
||||||
|
public record UpdatePlayListRequest(
|
||||||
|
string? NewName,
|
||||||
|
string? NewDescription,
|
||||||
|
Guid? NewPictureIntentId);
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace WebApi.DataTransferObjects.Tracks;
|
||||||
|
|
||||||
|
public record CreateTrackRequest(
|
||||||
|
string Title,
|
||||||
|
Guid PictureIntentId,
|
||||||
|
Guid AudioIntentId);
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace WebApi.DataTransferObjects.Tracks;
|
||||||
|
|
||||||
|
public record RequestTrackUploadUrlsRequest(
|
||||||
|
string PictureFileType,
|
||||||
|
string PictureContentType,
|
||||||
|
string AudioFileType,
|
||||||
|
string AudioContentType,
|
||||||
|
long? ExpectedPictureSizeBytes = null,
|
||||||
|
long? ExpectedAudioSizeBytes = null);
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace WebApi.DataTransferObjects.Users;
|
||||||
|
|
||||||
|
public record CreateUserRequest(
|
||||||
|
Guid Id,
|
||||||
|
string Name,
|
||||||
|
string? FirstName,
|
||||||
|
string? SecondName);
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
using Mediator;
|
||||||
|
using Musify.Application.PlayLists.Commands;
|
||||||
|
using Musify.Application.PlayLists.Queries;
|
||||||
|
using WebApi.DataTransferObjects.PlayLists;
|
||||||
|
using WebApi.Extensions;
|
||||||
|
|
||||||
|
namespace WebApi.Endpoints;
|
||||||
|
|
||||||
|
public static class PlayListEndpoints
|
||||||
|
{
|
||||||
|
public static IEndpointRouteBuilder MapPlayListEndpoints(this IEndpointRouteBuilder app)
|
||||||
|
{
|
||||||
|
var group = app.MapGroup("/api/playlists")
|
||||||
|
.WithTags("PlayLists");
|
||||||
|
|
||||||
|
group.MapGet("/", GetPlayLists)
|
||||||
|
.WithName("GetPlayLists")
|
||||||
|
.WithSummary("Get paginated playlists.");
|
||||||
|
|
||||||
|
group.MapGet("/{id:guid}", GetPlayListById)
|
||||||
|
.WithName("GetPlayListById")
|
||||||
|
.WithSummary("Get a playlist by ID.");
|
||||||
|
|
||||||
|
group.MapGet("/users/{userId:guid}", GetPlayListsByUserId)
|
||||||
|
.WithName("GetPlayListsByUserId")
|
||||||
|
.WithSummary("Get paginated playlists for a user.");
|
||||||
|
|
||||||
|
group.MapPost("/users/{userId:guid}", CreatePlayList)
|
||||||
|
.WithName("CreatePlayList")
|
||||||
|
.WithSummary("Create a new playlist.");
|
||||||
|
|
||||||
|
group.MapPut("/{playlistId:guid}/users/{userId:guid}", UpdatePlayList)
|
||||||
|
.WithName("UpdatePlayList")
|
||||||
|
.WithSummary("Update an existing playlist.");
|
||||||
|
|
||||||
|
group.MapDelete("/{playlistId:guid}/users/{userId:guid}", DeletePlayList)
|
||||||
|
.WithName("DeletePlayList")
|
||||||
|
.WithSummary("Delete a playlist.");
|
||||||
|
|
||||||
|
group.MapPost("/upload-picture/users/{userId:guid}", RequestPlayListPictureUpload)
|
||||||
|
.WithName("RequestPlayListPictureUpload")
|
||||||
|
.WithSummary("Request a pre-signed URL to upload a playlist picture.");
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<IResult> GetPlayLists(
|
||||||
|
IMediator mediator,
|
||||||
|
int pageNumber = 1,
|
||||||
|
int pageSize = 10,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var result = await mediator.Send(new GetPlayListsQuery(pageNumber, pageSize), ct);
|
||||||
|
return result.ToHttpResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<IResult> GetPlayListById(
|
||||||
|
IMediator mediator,
|
||||||
|
Guid id,
|
||||||
|
CancellationToken ct)
|
||||||
|
{
|
||||||
|
var result = await mediator.Send(new GetPlayListByIdQuery(id), ct);
|
||||||
|
return result.ToHttpResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<IResult> GetPlayListsByUserId(
|
||||||
|
IMediator mediator,
|
||||||
|
Guid userId,
|
||||||
|
string? name,
|
||||||
|
int pageNumber = 1,
|
||||||
|
int pageSize = 10,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var result = await mediator.Send(new GetPlayListsByUserIdQuery(userId, name, pageNumber, pageSize), ct);
|
||||||
|
return result.ToHttpResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<IResult> CreatePlayList(
|
||||||
|
IMediator mediator,
|
||||||
|
Guid userId,
|
||||||
|
CreatePlayListRequest request,
|
||||||
|
CancellationToken ct)
|
||||||
|
{
|
||||||
|
var result = await mediator.Send(
|
||||||
|
new CreatePlayListCommand(userId, request.Name, request.Description, request.PictureIntentId),
|
||||||
|
ct);
|
||||||
|
|
||||||
|
if (!result.IsSuccess)
|
||||||
|
return result.ToHttpResult();
|
||||||
|
|
||||||
|
return Results.Created($"/api/playlists/{result.Value.Id}", result.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<IResult> UpdatePlayList(
|
||||||
|
IMediator mediator,
|
||||||
|
Guid userId,
|
||||||
|
Guid playlistId,
|
||||||
|
UpdatePlayListRequest request,
|
||||||
|
CancellationToken ct)
|
||||||
|
{
|
||||||
|
var result = await mediator.Send(
|
||||||
|
new UpdatePlayListCommand(userId, playlistId, request.NewName, request.NewDescription, request.NewPictureIntentId),
|
||||||
|
ct);
|
||||||
|
|
||||||
|
return result.ToHttpResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<IResult> DeletePlayList(
|
||||||
|
IMediator mediator,
|
||||||
|
Guid userId,
|
||||||
|
Guid playlistId,
|
||||||
|
CancellationToken ct)
|
||||||
|
{
|
||||||
|
var result = await mediator.Send(new DeletePlayListCommand(userId, playlistId), ct);
|
||||||
|
return result.ToHttpResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<IResult> RequestPlayListPictureUpload(
|
||||||
|
IMediator mediator,
|
||||||
|
Guid userId,
|
||||||
|
RequestPlayListPictureUploadRequest request,
|
||||||
|
CancellationToken ct)
|
||||||
|
{
|
||||||
|
var result = await mediator.Send(
|
||||||
|
new RequestPlayListPictureUploadCommand(userId, request.FileType, request.ContentType, request.ExpectedSizeBytes),
|
||||||
|
ct);
|
||||||
|
|
||||||
|
return result.ToHttpResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
using Mediator;
|
||||||
|
using Musify.Application.Tracks.Commands;
|
||||||
|
using Musify.Application.Tracks.Queries;
|
||||||
|
using WebApi.DataTransferObjects.Tracks;
|
||||||
|
using WebApi.Extensions;
|
||||||
|
|
||||||
|
namespace WebApi.Endpoints;
|
||||||
|
|
||||||
|
public static class TrackEndpoints
|
||||||
|
{
|
||||||
|
public static IEndpointRouteBuilder MapTrackEndpoints(this IEndpointRouteBuilder app)
|
||||||
|
{
|
||||||
|
var group = app.MapGroup("/api/tracks")
|
||||||
|
.WithTags("Tracks");
|
||||||
|
|
||||||
|
group.MapGet("/", GetTracks)
|
||||||
|
.WithName("GetTracks")
|
||||||
|
.WithSummary("Get paginated tracks.");
|
||||||
|
|
||||||
|
group.MapGet("/{id:guid}", GetTrackById)
|
||||||
|
.WithName("GetTrackById")
|
||||||
|
.WithSummary("Get a track by ID.");
|
||||||
|
|
||||||
|
group.MapGet("/users/{userId:guid}", GetTracksByUserId)
|
||||||
|
.WithName("GetTracksByUserId")
|
||||||
|
.WithSummary("Get paginated tracks for a user.");
|
||||||
|
|
||||||
|
group.MapPost("/users/{userId:guid}", CreateTrack)
|
||||||
|
.WithName("CreateTrack")
|
||||||
|
.WithSummary("Create a new track.");
|
||||||
|
|
||||||
|
group.MapDelete("/{trackId:guid}/users/{userId:guid}", DeleteTrack)
|
||||||
|
.WithName("DeleteTrack")
|
||||||
|
.WithSummary("Delete a track.");
|
||||||
|
|
||||||
|
group.MapPost("/upload-urls/users/{userId:guid}", RequestTrackUploadUrls)
|
||||||
|
.WithName("RequestTrackUploadUrls")
|
||||||
|
.WithSummary("Request pre-signed URLs to upload track picture and audio.");
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<IResult> GetTracks(
|
||||||
|
IMediator mediator,
|
||||||
|
int pageNumber = 1,
|
||||||
|
int pageSize = 10,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var result = await mediator.Send(new GetTracksQuery(pageNumber, pageSize), ct);
|
||||||
|
return result.ToHttpResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<IResult> GetTrackById(
|
||||||
|
IMediator mediator,
|
||||||
|
Guid id,
|
||||||
|
CancellationToken ct)
|
||||||
|
{
|
||||||
|
var result = await mediator.Send(new GetTrackByIdQuery(id), ct);
|
||||||
|
return result.ToHttpResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<IResult> GetTracksByUserId(
|
||||||
|
IMediator mediator,
|
||||||
|
Guid userId,
|
||||||
|
string? name,
|
||||||
|
int pageNumber = 1,
|
||||||
|
int pageSize = 10,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var result = await mediator.Send(new GetTracksByUserIdQuery(userId, name, pageNumber, pageSize), ct);
|
||||||
|
return result.ToHttpResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<IResult> CreateTrack(
|
||||||
|
IMediator mediator,
|
||||||
|
Guid userId,
|
||||||
|
CreateTrackRequest request,
|
||||||
|
CancellationToken ct)
|
||||||
|
{
|
||||||
|
var result = await mediator.Send(
|
||||||
|
new CreateTrackCommand(userId, request.Title, request.PictureIntentId, request.AudioIntentId),
|
||||||
|
ct);
|
||||||
|
|
||||||
|
if (!result.IsSuccess)
|
||||||
|
return result.ToHttpResult();
|
||||||
|
|
||||||
|
return Results.Created($"/api/tracks/{result.Value.Id}", result.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<IResult> DeleteTrack(
|
||||||
|
IMediator mediator,
|
||||||
|
Guid userId,
|
||||||
|
Guid trackId,
|
||||||
|
CancellationToken ct)
|
||||||
|
{
|
||||||
|
var result = await mediator.Send(new DeleteTrackCommand(userId, trackId), ct);
|
||||||
|
return result.ToHttpResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<IResult> RequestTrackUploadUrls(
|
||||||
|
IMediator mediator,
|
||||||
|
Guid userId,
|
||||||
|
RequestTrackUploadUrlsRequest request,
|
||||||
|
CancellationToken ct)
|
||||||
|
{
|
||||||
|
var result = await mediator.Send(
|
||||||
|
new RequestTrackUploadUrlsCommand(
|
||||||
|
userId,
|
||||||
|
request.PictureFileType,
|
||||||
|
request.PictureContentType,
|
||||||
|
request.AudioFileType,
|
||||||
|
request.AudioContentType,
|
||||||
|
request.ExpectedPictureSizeBytes,
|
||||||
|
request.ExpectedAudioSizeBytes),
|
||||||
|
ct);
|
||||||
|
|
||||||
|
return result.ToHttpResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
using Mediator;
|
||||||
|
using Musify.Application.Users.Commands;
|
||||||
|
using Musify.Application.Users.Queries;
|
||||||
|
using WebApi.DataTransferObjects.Users;
|
||||||
|
using WebApi.Extensions;
|
||||||
|
|
||||||
|
namespace WebApi.Endpoints;
|
||||||
|
|
||||||
|
public static class UserEndpoints
|
||||||
|
{
|
||||||
|
public static IEndpointRouteBuilder MapUserEndpoints(this IEndpointRouteBuilder app)
|
||||||
|
{
|
||||||
|
var group = app.MapGroup("/api/users")
|
||||||
|
.WithTags("Users");
|
||||||
|
|
||||||
|
group.MapGet("/", GetUsers)
|
||||||
|
.WithName("GetUsers")
|
||||||
|
.WithSummary("Get paginated users.");
|
||||||
|
|
||||||
|
group.MapGet("/{id:guid}", GetUserById)
|
||||||
|
.WithName("GetUserById")
|
||||||
|
.WithSummary("Get a user by ID.");
|
||||||
|
|
||||||
|
group.MapGet("/keycloak/{id:guid}", GetUserByKeycloak)
|
||||||
|
.WithName("GetUserByKeycloak")
|
||||||
|
.WithSummary("Get a user by Keycloak ID.");
|
||||||
|
|
||||||
|
group.MapPost("/", CreateUser)
|
||||||
|
.WithName("CreateUser")
|
||||||
|
.WithSummary("Create a new user.");
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<IResult> GetUsers(
|
||||||
|
IMediator mediator,
|
||||||
|
int pageNumber = 1,
|
||||||
|
int pageSize = 10,
|
||||||
|
string? usernameSearch = null,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var result = await mediator.Send(new GetUsersQuery(pageNumber, pageSize, usernameSearch), ct);
|
||||||
|
return result.ToHttpResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<IResult> GetUserById(
|
||||||
|
IMediator mediator,
|
||||||
|
Guid id,
|
||||||
|
CancellationToken ct)
|
||||||
|
{
|
||||||
|
var result = await mediator.Send(new GetUserByIdQuery(id), ct);
|
||||||
|
return result.ToHttpResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<IResult> GetUserByKeycloak(
|
||||||
|
IMediator mediator,
|
||||||
|
Guid id,
|
||||||
|
CancellationToken ct)
|
||||||
|
{
|
||||||
|
var result = await mediator.Send(new GetUserByKeycloakQuery(id), ct);
|
||||||
|
return result.ToHttpResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<IResult> CreateUser(
|
||||||
|
IMediator mediator,
|
||||||
|
CreateUserRequest request,
|
||||||
|
CancellationToken ct)
|
||||||
|
{
|
||||||
|
var result = await mediator.Send(
|
||||||
|
new CreateUserCommand(request.Id, request.Name, request.FirstName, request.SecondName),
|
||||||
|
ct);
|
||||||
|
|
||||||
|
if (!result.IsSuccess)
|
||||||
|
return result.ToHttpResult();
|
||||||
|
|
||||||
|
return Results.Created($"/api/users/{result.Value.Id}", result.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using Ardalis.Result;
|
||||||
|
|
||||||
|
namespace WebApi.Extensions;
|
||||||
|
|
||||||
|
public static class ResultHttpExtensions
|
||||||
|
{
|
||||||
|
public static IResult ToHttpResult(this Result result) =>
|
||||||
|
result.Status switch
|
||||||
|
{
|
||||||
|
ResultStatus.Ok or ResultStatus.NoContent => Results.NoContent(),
|
||||||
|
ResultStatus.NotFound => Results.NotFound(result.Errors.FirstOrDefault()),
|
||||||
|
ResultStatus.Invalid => Results.ValidationProblem(ToValidationDictionary(result.ValidationErrors)),
|
||||||
|
ResultStatus.Unauthorized => Results.Unauthorized(),
|
||||||
|
ResultStatus.Forbidden => Results.StatusCode(StatusCodes.Status403Forbidden),
|
||||||
|
ResultStatus.Conflict => Results.Conflict(result.Errors.FirstOrDefault()),
|
||||||
|
_ => Results.Problem(string.Join(", ", result.Errors))
|
||||||
|
};
|
||||||
|
|
||||||
|
public static IResult ToHttpResult<T>(this Result<T> result) =>
|
||||||
|
result.Status switch
|
||||||
|
{
|
||||||
|
ResultStatus.Ok => Results.Ok(result.Value),
|
||||||
|
ResultStatus.NotFound => Results.NotFound(result.Errors.FirstOrDefault()),
|
||||||
|
ResultStatus.Invalid => Results.ValidationProblem(ToValidationDictionary(result.ValidationErrors)),
|
||||||
|
ResultStatus.Unauthorized => Results.Unauthorized(),
|
||||||
|
ResultStatus.Forbidden => Results.StatusCode(StatusCodes.Status403Forbidden),
|
||||||
|
ResultStatus.Conflict => Results.Conflict(result.Errors.FirstOrDefault()),
|
||||||
|
_ => Results.Problem(string.Join(", ", result.Errors))
|
||||||
|
};
|
||||||
|
|
||||||
|
private static Dictionary<string, string[]> ToValidationDictionary(IEnumerable<ValidationError> errors) =>
|
||||||
|
errors
|
||||||
|
.GroupBy(e => e.Identifier)
|
||||||
|
.ToDictionary(g => g.Key, g => g.Select(e => e.ErrorMessage).ToArray());
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using Mediator;
|
||||||
|
using Musify.Application;
|
||||||
|
using Musify.Infrastructure.MassTransit;
|
||||||
|
using Musify.Infrastructure.Persistence;
|
||||||
|
using Musify.Infrastructure.Services;
|
||||||
|
using OpenTelemetry.Logs;
|
||||||
|
using OpenTelemetry.Metrics;
|
||||||
|
using OpenTelemetry.Trace;
|
||||||
|
using WebApi.Options;
|
||||||
|
|
||||||
|
namespace WebApi.Extensions;
|
||||||
|
|
||||||
|
public static class ServiceCollectionExtensions
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddApplicationServices(this IServiceCollection services, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
services.AddApplicationConfigurations(configuration);
|
||||||
|
services.AddDatabase(configuration);
|
||||||
|
services.AddKeycloakService(configuration);
|
||||||
|
services.AddStorageService(configuration);
|
||||||
|
services.AddMassTransitClient(configuration);
|
||||||
|
services.AddMediator();
|
||||||
|
services.AddOpenApi();
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddObservability(this IServiceCollection services, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
var otlpEndpoint = new Uri(configuration
|
||||||
|
.GetRequiredSection(OpenTelemetryOptions.SectionName)
|
||||||
|
.GetRequiredSection(nameof(OpenTelemetryOptions.OtlpEndpoint))
|
||||||
|
.Value!);
|
||||||
|
|
||||||
|
services.AddOpenTelemetry()
|
||||||
|
.WithTracing(tracing => tracing
|
||||||
|
.AddAspNetCoreInstrumentation()
|
||||||
|
.AddHttpClientInstrumentation()
|
||||||
|
.AddOtlpExporter(o => o.Endpoint = otlpEndpoint))
|
||||||
|
.WithMetrics(metrics => metrics
|
||||||
|
.AddAspNetCoreInstrumentation()
|
||||||
|
.AddHttpClientInstrumentation()
|
||||||
|
.AddOtlpExporter(o => o.Endpoint = otlpEndpoint));
|
||||||
|
|
||||||
|
services.Configure<OpenTelemetryLoggerOptions>(logging =>
|
||||||
|
logging.AddOtlpExporter(o => o.Endpoint = otlpEndpoint));
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace WebApi.Options;
|
||||||
|
|
||||||
|
public sealed class OpenTelemetryOptions
|
||||||
|
{
|
||||||
|
public const string SectionName = "OpenTelemetry";
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string OtlpEndpoint { get; init; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using Scalar.AspNetCore;
|
||||||
|
using WebApi.Endpoints;
|
||||||
|
using WebApi.Extensions;
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder();
|
||||||
|
|
||||||
|
builder.Services
|
||||||
|
.AddApplicationServices(builder.Configuration)
|
||||||
|
.AddObservability(builder.Configuration);
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
if (app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.MapOpenApi();
|
||||||
|
app.MapScalarApiReference();
|
||||||
|
}
|
||||||
|
|
||||||
|
app
|
||||||
|
.MapPlayListEndpoints()
|
||||||
|
.MapTrackEndpoints()
|
||||||
|
.MapUserEndpoints();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": false,
|
||||||
|
"applicationUrl": "http://localhost:5111",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UserSecretsId>1c3fa41c-6f07-433b-af79-1f67fad338fd</UserSecretsId>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Ardalis.Result" Version="10.1.0" />
|
||||||
|
<PackageReference Include="AWSSDK.S3" Version="4.0.19" />
|
||||||
|
<PackageReference Include="Keycloak.Net.Core" Version="1.0.38" />
|
||||||
|
<PackageReference Include="MassTransit" Version="9.0.1" />
|
||||||
|
<PackageReference Include="MassTransit.RabbitMQ" Version="9.0.1" />
|
||||||
|
<PackageReference Include="Mediator.Abstractions" Version="3.0.2" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.4" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.5" />
|
||||||
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
|
||||||
|
<PackageReference Include="OpenTelemetry" Version="1.15.0" />
|
||||||
|
<PackageReference Include="OpenTelemetry.Api" Version="1.15.3" />
|
||||||
|
<PackageReference Include="OpenTelemetry.Api.ProviderBuilderExtensions" Version="1.15.0" />
|
||||||
|
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.0" />
|
||||||
|
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.15.0" />
|
||||||
|
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.15.1" />
|
||||||
|
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.15.0" />
|
||||||
|
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.15.0" />
|
||||||
|
<PackageReference Include="Scalar.AspNetCore" Version="2.13.11" />
|
||||||
|
<PackageReference Include="X.PagedList.EF" Version="10.5.9" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Application">
|
||||||
|
<HintPath>..\..\MusifyBackend\Infrastructure\bin\Debug\net10.0\Application.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Domain">
|
||||||
|
<HintPath>..\..\MusifyBackend\Infrastructure\bin\Debug\net10.0\Domain.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Infrastructure">
|
||||||
|
<HintPath>..\..\MusifyBackend\Infrastructure\bin\Debug\net10.0\Infrastructure.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
@WebApi_HostAddress = http://localhost:5111
|
||||||
|
|
||||||
|
GET {{WebApi_HostAddress}}/weatherforecast/
|
||||||
|
Accept: application/json
|
||||||
|
|
||||||
|
###
|
||||||
Reference in New Issue
Block a user