update swagger implementation
This commit is contained in:
@@ -19,6 +19,7 @@ public class AuthController : ControllerBase
|
||||
/// Authenticates a user and returns a JWT with role claims.
|
||||
/// </summary>
|
||||
[HttpPost("login")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(ApiResponse<LoginResponse>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status422UnprocessableEntity)]
|
||||
public async Task<IActionResult> Login([FromBody] LoginRequest req)
|
||||
@@ -32,16 +33,13 @@ public class AuthController : ControllerBase
|
||||
/// </summary>
|
||||
[HttpGet("me")]
|
||||
[Authorize]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ApiResponse<UserProfileResponse>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status401Unauthorized)]
|
||||
public async Task<IActionResult> Me()
|
||||
{
|
||||
var userId = Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
|
||||
var user = await _auth.GetCurrentUserAsync(userId);
|
||||
return Ok(ApiResponse<object>.Ok(new
|
||||
{
|
||||
user.Id, user.Username, user.FullName,
|
||||
role = user.Role.ToDbString()
|
||||
}));
|
||||
return Ok(ApiResponse<UserProfileResponse>.Ok(new UserProfileResponse(
|
||||
user.Id, user.Username, user.FullName, user.Role.ToDbString())));
|
||||
}
|
||||
}
|
||||
@@ -30,30 +30,31 @@ public class DigitizationBatchesController : ControllerBase
|
||||
/// Uploads a scanned document and creates a new digitization batch.
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[Consumes("multipart/form-data")]
|
||||
[ProducesResponseType(typeof(ApiResponse<BatchDetailResponse>), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status409Conflict)]
|
||||
[RequestSizeLimit(25 * 1024 * 1024)]
|
||||
public async Task<IActionResult> Create(
|
||||
IFormFile file,
|
||||
[FromForm] CreateBatchRequest req)
|
||||
public async Task<IActionResult> Create([FromForm] CreateBatchForm form)
|
||||
{
|
||||
if (file is null || file.Length == 0)
|
||||
if (form.File is null || form.File.Length == 0)
|
||||
return BadRequest(ApiResponse<object>.Fail(400, "File is required.", "EMPTY_FILE"));
|
||||
|
||||
if (!_allowedMimeTypes.Contains(file.ContentType))
|
||||
if (!_allowedMimeTypes.Contains(form.File.ContentType))
|
||||
return BadRequest(ApiResponse<object>.Fail(400,
|
||||
"Accepted formats: PDF, JPEG, PNG.", "INVALID_MIME_TYPE"));
|
||||
|
||||
var req = form.ToMetadata();
|
||||
var parsedBatchType = BatchTypeExtensions.FromDbString(req.BatchType.ToUpperInvariant());
|
||||
var parsedTrack = string.IsNullOrEmpty(req.Track)
|
||||
? BatchTrack.Backfill
|
||||
: BatchTrackExtensions.FromDbString(req.Track.ToUpperInvariant());
|
||||
var actorUserId = Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
|
||||
|
||||
using var stream = file.OpenReadStream();
|
||||
using var stream = form.File.OpenReadStream();
|
||||
var batch = await _batches.CreateAsync(
|
||||
stream, file.ContentType, parsedBatchType, parsedTrack, req.PatientId, actorUserId);
|
||||
stream, form.File.ContentType, parsedBatchType, parsedTrack, req.PatientId, actorUserId);
|
||||
return StatusCode(201, ApiResponse<BatchDetailResponse>.Created(BatchDetailResponse.FromEntity(batch)));
|
||||
}
|
||||
|
||||
@@ -62,6 +63,7 @@ public class DigitizationBatchesController : ControllerBase
|
||||
/// </summary>
|
||||
[HttpGet("{id:guid}")]
|
||||
[ProducesResponseType(typeof(ApiResponse<BatchDetailResponse>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Get(Guid id)
|
||||
{
|
||||
@@ -76,7 +78,8 @@ public class DigitizationBatchesController : ControllerBase
|
||||
/// Lists batches with optional filters and pagination.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ApiResponse<BatchListResponse>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status401Unauthorized)]
|
||||
public async Task<IActionResult> List(
|
||||
[FromQuery] string? status,
|
||||
[FromQuery] string? batchType,
|
||||
@@ -90,14 +93,12 @@ public class DigitizationBatchesController : ControllerBase
|
||||
BatchTrack? parsedTrack = string.IsNullOrEmpty(track) ? null : BatchTrackExtensions.FromDbString(track.ToUpperInvariant());
|
||||
|
||||
var result = await _batches.ListAsync(parsedStatus, parsedBatchType, assignedTo, parsedTrack, page, pageSize);
|
||||
return Ok(ApiResponse<object>.Ok(new
|
||||
{
|
||||
items = result.Items,
|
||||
page = result.Page,
|
||||
pageSize = result.PageSize,
|
||||
totalCount = result.TotalCount,
|
||||
totalPages = result.TotalPages
|
||||
}));
|
||||
return Ok(ApiResponse<BatchListResponse>.Ok(new BatchListResponse(
|
||||
result.Items.Select(b => BatchDetailResponse.FromEntity(b)).ToList(),
|
||||
result.Page,
|
||||
result.PageSize,
|
||||
result.TotalCount,
|
||||
result.TotalPages)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -105,6 +106,7 @@ public class DigitizationBatchesController : ControllerBase
|
||||
/// </summary>
|
||||
[HttpPatch("{id:guid}/assign")]
|
||||
[ProducesResponseType(typeof(ApiResponse<BatchDetailResponse>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status409Conflict)]
|
||||
public async Task<IActionResult> Assign(Guid id, [FromBody] AssignBatchRequest req)
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
public static class SwaggerServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddVigilCareRecordsSwagger(this IServiceCollection services)
|
||||
{
|
||||
services.AddSwaggerGen(options =>
|
||||
{
|
||||
options.SwaggerDoc("v1", new OpenApiInfo
|
||||
{
|
||||
Title = "VigilCare Records API",
|
||||
Version = "v1",
|
||||
Description = "Digitization workflow API for scanned chart intake, verification, and promotion."
|
||||
});
|
||||
|
||||
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
||||
{
|
||||
Name = "Authorization",
|
||||
Type = SecuritySchemeType.Http,
|
||||
Scheme = "bearer",
|
||||
BearerFormat = "JWT",
|
||||
In = ParameterLocation.Header,
|
||||
Description = "JWT obtained from POST /api/v1/auth/login"
|
||||
});
|
||||
|
||||
options.AddSecurityRequirement(new OpenApiSecurityRequirement
|
||||
{
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = "Bearer"
|
||||
}
|
||||
},
|
||||
Array.Empty<string>()
|
||||
}
|
||||
});
|
||||
|
||||
var xmlPath = Path.Combine(
|
||||
AppContext.BaseDirectory,
|
||||
$"{Assembly.GetExecutingAssembly().GetName().Name}.xml");
|
||||
if (File.Exists(xmlPath))
|
||||
options.IncludeXmlComments(xmlPath);
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/// <summary>Authenticated user profile returned by GET /api/v1/auth/me.</summary>
|
||||
public record UserProfileResponse(
|
||||
Guid Id,
|
||||
string Username,
|
||||
string FullName,
|
||||
string Role
|
||||
);
|
||||
@@ -0,0 +1,8 @@
|
||||
/// <summary>Paginated batch list returned by GET /api/v1/digitization-batches.</summary>
|
||||
public record BatchListResponse(
|
||||
IReadOnlyList<BatchDetailResponse> Items,
|
||||
int Page,
|
||||
int PageSize,
|
||||
int TotalCount,
|
||||
int TotalPages
|
||||
);
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
/// <summary>
|
||||
/// Multipart form for batch creation. Combines the uploaded scan with batch metadata
|
||||
/// so Swagger documents a single <c>multipart/form-data</c> request.
|
||||
/// </summary>
|
||||
public class CreateBatchForm
|
||||
{
|
||||
/// <summary>PDF, JPEG, or PNG scan (max 25 MB).</summary>
|
||||
[Required]
|
||||
public IFormFile File { get; set; } = null!;
|
||||
|
||||
/// <summary>Batch type literal, e.g. PATIENT_REGISTRATION or ENCOUNTER_SUMMARY.</summary>
|
||||
[Required]
|
||||
public string BatchType { get; set; } = null!;
|
||||
|
||||
/// <summary>BACKFILL (default) or LIVE_CAPTURE.</summary>
|
||||
public string? Track { get; set; }
|
||||
|
||||
/// <summary>Optional patient link used for duplicate-document detection.</summary>
|
||||
public Guid? PatientId { get; set; }
|
||||
|
||||
public CreateBatchRequest ToMetadata() => new(BatchType, Track, PatientId);
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using System.Text;using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Minio;
|
||||
@@ -54,28 +52,26 @@ try
|
||||
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen(c =>
|
||||
{
|
||||
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
||||
c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFile));
|
||||
});
|
||||
builder.Services.AddVigilCareRecordsSwagger();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseMiddleware<CorrelationIdMiddleware>();
|
||||
app.UseMiddleware<ExceptionHandlerMiddleware>();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
app.UseSwaggerUI(options =>
|
||||
{
|
||||
options.SwaggerEndpoint("/swagger/v1/swagger.json", "VigilCare Records API v1");
|
||||
options.DocumentTitle = "VigilCare Records API";
|
||||
});
|
||||
}
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.MapControllers();
|
||||
app.Run();
|
||||
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
@@ -83,6 +79,8 @@ try
|
||||
await db.Database.MigrateAsync();
|
||||
await DataSeeder.SeedAsync(db);
|
||||
}
|
||||
|
||||
app.Run();
|
||||
}
|
||||
catch (System.Exception)
|
||||
{
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>$(NoWarn);1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user