Initial import into Gitea
This commit is contained in:
188
Management/Controllers/Admin/AdminClientDocumentsController.cs
Normal file
188
Management/Controllers/Admin/AdminClientDocumentsController.cs
Normal file
@@ -0,0 +1,188 @@
|
||||
using Management.Data;
|
||||
using Management.Security;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using System.Data;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Management.Controllers.Admin;
|
||||
|
||||
/// <summary>
|
||||
/// Client document management (admin view).
|
||||
/// Admins can list, upload, download, and delete documents for any client.
|
||||
/// All documents created here have scope='client' and a required clientId.
|
||||
///
|
||||
/// POST /api/admin/client-documents/list - List docs for a client
|
||||
/// POST /api/admin/client-documents/upload - Upload doc for a client
|
||||
/// GET /api/admin/client-documents/{id}/download
|
||||
/// DELETE /api/admin/client-documents/{id}
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/admin/client-documents")]
|
||||
public sealed class AdminClientDocumentsController : AdminControllerBase
|
||||
{
|
||||
private readonly IConfiguration _config;
|
||||
|
||||
public AdminClientDocumentsController(SqlService sql, ClientContext client, IConfiguration config, ILogger<AdminClientDocumentsController> log)
|
||||
: base(sql, client, log)
|
||||
{
|
||||
_config = config;
|
||||
}
|
||||
|
||||
// ── POST /api/admin/client-documents/list ────────────────────────────────
|
||||
[HttpPost("list")]
|
||||
public async Task<IActionResult> List([FromBody] JsonElement body, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var clientId = body.TryGetProperty("clientId", out var cid) ? cid.GetString() : null;
|
||||
if (string.IsNullOrWhiteSpace(clientId))
|
||||
return BadRequest(new { ok = false, error = "clientId is required" });
|
||||
|
||||
var rqst = JsonSerializer.Serialize(new { scope = "client", clientId });
|
||||
var result = await Sql.ExecProcAsync("dbo.usp_DocumentList", "list", rqst, ct: ct);
|
||||
return Content(result, "application/json");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Client document list failed");
|
||||
return StatusCode(500, new { ok = false, message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
// ── POST /api/admin/client-documents/upload ──────────────────────────────
|
||||
[HttpPost("upload")]
|
||||
[RequestSizeLimit(52_428_800)]
|
||||
public async Task<IActionResult> Upload(
|
||||
IFormFile file,
|
||||
[FromForm] string clientId,
|
||||
[FromForm] string category,
|
||||
[FromForm] string? description = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (file == null || file.Length == 0)
|
||||
return BadRequest(new { ok = false, message = "No file provided" });
|
||||
|
||||
if (string.IsNullOrWhiteSpace(clientId))
|
||||
return BadRequest(new { ok = false, message = "clientId is required" });
|
||||
|
||||
try
|
||||
{
|
||||
byte[] fileBytes;
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
await file.CopyToAsync(ms, ct);
|
||||
fileBytes = ms.ToArray();
|
||||
}
|
||||
|
||||
var rqst = JsonSerializer.Serialize(new
|
||||
{
|
||||
docFileName = file.FileName,
|
||||
docMimeType = file.ContentType,
|
||||
docFileSize = file.Length,
|
||||
docCategory = category,
|
||||
docDescription = description,
|
||||
docUploadedBy = Client.Email,
|
||||
docScope = "client",
|
||||
docCltId = clientId
|
||||
});
|
||||
|
||||
Logger.LogInformation("[ClientDocs] Upload {FileName} for client {ClientId} by {User}",
|
||||
file.FileName, clientId, Client.Email);
|
||||
|
||||
var result = await ExecUploadAsync(rqst, fileBytes, ct);
|
||||
return Content(result, "application/json");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Client document upload failed: {FileName}", file?.FileName);
|
||||
return StatusCode(500, new { ok = false, message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
// ── GET /api/admin/client-documents/{id}/download ────────────────────────
|
||||
[HttpGet("{id:long}/download")]
|
||||
public async Task<IActionResult> Download(long id, CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cs = _config.GetConnectionString("Sql")
|
||||
?? throw new InvalidOperationException("Missing ConnectionStrings:Sql");
|
||||
|
||||
await using var conn = new SqlConnection(cs);
|
||||
await conn.OpenAsync(ct);
|
||||
|
||||
await using var cmd = new SqlCommand("dbo.usp_Document", conn)
|
||||
{
|
||||
CommandType = CommandType.StoredProcedure,
|
||||
CommandTimeout = 60
|
||||
};
|
||||
cmd.Parameters.Add(new SqlParameter("@action", SqlDbType.VarChar, 50) { Value = "document.download" });
|
||||
cmd.Parameters.Add(new SqlParameter("@rqst", SqlDbType.NVarChar, -1) { Value = JsonSerializer.Serialize(new { docId = id }) });
|
||||
|
||||
await using var reader = await cmd.ExecuteReaderAsync(ct);
|
||||
if (!await reader.ReadAsync(ct))
|
||||
return NotFound(new { ok = false, message = "Document not found" });
|
||||
|
||||
var fileName = reader.GetString(reader.GetOrdinal("docFileName"));
|
||||
var mimeType = reader.GetString(reader.GetOrdinal("docMimeType"));
|
||||
var content = (byte[])reader["docContent"];
|
||||
|
||||
return File(content, mimeType, fileName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Client document download failed: docId={DocId}", id);
|
||||
return StatusCode(500, new { ok = false, message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
// ── DELETE /api/admin/client-documents/{id} ──────────────────────────────
|
||||
[HttpDelete("{id:long}")]
|
||||
public async Task<IActionResult> Delete(long id, CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.LogInformation("[ClientDocs] Delete docId={DocId} by {User}", id, Client.Email);
|
||||
var rqst = JsonSerializer.Serialize(new { docId = id });
|
||||
var result = await Sql.ExecProcAsync("dbo.usp_Document", "document.delete", rqst, ct: ct);
|
||||
return Content(result, "application/json");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Client document delete failed: docId={DocId}", id);
|
||||
return StatusCode(500, new { ok = false, message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Upload helper ────────────────────────────────────────────────────────
|
||||
private async Task<string> ExecUploadAsync(string rqst, byte[] fileContent, CancellationToken ct)
|
||||
{
|
||||
var cs = _config.GetConnectionString("Sql")
|
||||
?? throw new InvalidOperationException("Missing ConnectionStrings:Sql");
|
||||
|
||||
await using var conn = new SqlConnection(cs);
|
||||
await conn.OpenAsync(ct);
|
||||
|
||||
await using var cmd = new SqlCommand("dbo.usp_Document", conn)
|
||||
{
|
||||
CommandType = CommandType.StoredProcedure,
|
||||
CommandTimeout = 60
|
||||
};
|
||||
|
||||
cmd.Parameters.Add(new SqlParameter("@action", SqlDbType.VarChar, 50) { Value = "document.upload" });
|
||||
cmd.Parameters.Add(new SqlParameter("@rqst", SqlDbType.NVarChar, -1) { Value = rqst });
|
||||
cmd.Parameters.Add(new SqlParameter("@filecontent", SqlDbType.VarBinary, -1) { Value = fileContent });
|
||||
|
||||
var pResp = new SqlParameter("@resp", SqlDbType.NVarChar, -1)
|
||||
{
|
||||
Direction = ParameterDirection.Output
|
||||
};
|
||||
cmd.Parameters.Add(pResp);
|
||||
|
||||
await cmd.ExecuteNonQueryAsync(ct);
|
||||
|
||||
return pResp.Value as string
|
||||
?? JsonSerializer.Serialize(new { ok = false, message = "No response from database" });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user