feat(storage-service): Add Social Service to Docker Compose and enhance IAM service integration

- Introduced a new social-service in the Docker Compose configuration for local development, including build context, environment variables, and health checks.
- Updated architecture documentation to reflect the new storage service structure and its components, including user storage quotas and file management.
- Enhanced README files to provide clearer instructions on service setup, configuration, and API endpoints for file storage management.
- Implemented caching mechanisms in the IAM service client for improved performance and reduced latency in user information retrieval.
- Updated appsettings for development to include caching settings for IAM service interactions.
This commit is contained in:
Ho Ngoc Hai
2026-01-13 00:28:41 +07:00
parent 928a22fe3e
commit 4a1a0ef79c
385 changed files with 28872 additions and 808 deletions

View File

@@ -0,0 +1,187 @@
using Asp.Versioning;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MembershipService.API.Application.Commands;
using MembershipService.API.Application.Queries;
using Swashbuckle.AspNetCore.Annotations;
namespace MembershipService.API.Controllers;
/// <summary>
/// EN: Controller for managing members.
/// VI: Controller để quản lý members.
/// </summary>
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")]
[Authorize]
[SwaggerTag("Member management endpoints")]
public class MembersController : ControllerBase
{
private readonly IMediator _mediator;
private readonly ILogger<MembersController> _logger;
public MembersController(IMediator mediator, ILogger<MembersController> logger)
{
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <summary>
/// EN: Get member by ID.
/// VI: Lấy member theo ID.
/// </summary>
[HttpGet("{id:guid}")]
[SwaggerOperation(Summary = "Get member by ID", Description = "Retrieves a member by their unique identifier")]
[SwaggerResponse(200, "Member found", typeof(MemberDto))]
[SwaggerResponse(404, "Member not found")]
[SwaggerResponse(401, "Unauthorized")]
public async Task<ActionResult<MemberDto>> GetById(Guid id)
{
var member = await _mediator.Send(new GetMemberByIdQuery(id));
if (member == null)
{
return NotFound(new { message = $"Member {id} not found" });
}
return Ok(member);
}
/// <summary>
/// EN: Get current user's member profile.
/// VI: Lấy profile member của user hiện tại.
/// </summary>
[HttpGet("me")]
[SwaggerOperation(Summary = "Get my member profile", Description = "Retrieves the member profile for the authenticated user")]
[SwaggerResponse(200, "Member found", typeof(MemberDto))]
[SwaggerResponse(404, "Member not found")]
[SwaggerResponse(401, "Unauthorized")]
public async Task<ActionResult<MemberDto>> GetMe()
{
var userId = GetCurrentUserId();
if (userId == null)
{
return Unauthorized();
}
var member = await _mediator.Send(new GetMemberByIdQuery(userId.Value));
if (member == null)
{
return NotFound(new { message = "Member profile not found" });
}
return Ok(member);
}
/// <summary>
/// EN: Get paginated list of members.
/// VI: Lấy danh sách members phân trang.
/// </summary>
[HttpGet]
[SwaggerOperation(Summary = "Get members list", Description = "Retrieves a paginated list of members")]
[SwaggerResponse(200, "Members retrieved", typeof(GetMembersResult))]
[SwaggerResponse(401, "Unauthorized")]
public async Task<ActionResult<GetMembersResult>> GetAll(
[FromQuery] int pageIndex = 0,
[FromQuery] int pageSize = 10,
[FromQuery] string? search = null)
{
var result = await _mediator.Send(new GetMembersQuery
{
PageIndex = pageIndex,
PageSize = pageSize,
SearchTerm = search
});
return Ok(result);
}
/// <summary>
/// EN: Create a new member.
/// VI: Tạo member mới.
/// </summary>
[HttpPost]
[SwaggerOperation(Summary = "Create member", Description = "Creates a new member profile")]
[SwaggerResponse(201, "Member created", typeof(CreateMemberResult))]
[SwaggerResponse(400, "Invalid request")]
[SwaggerResponse(409, "Member already exists")]
[SwaggerResponse(401, "Unauthorized")]
public async Task<ActionResult<CreateMemberResult>> Create([FromBody] CreateMemberCommand command)
{
try
{
var result = await _mediator.Send(command);
return CreatedAtAction(nameof(GetById), new { id = result.MemberId }, result);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("already exists"))
{
return Conflict(new { message = ex.Message });
}
}
/// <summary>
/// EN: Update member profile.
/// VI: Cập nhật profile member.
/// </summary>
[HttpPut("{id:guid}")]
[SwaggerOperation(Summary = "Update member profile", Description = "Updates a member's profile information")]
[SwaggerResponse(200, "Member updated", typeof(UpdateMemberProfileResult))]
[SwaggerResponse(400, "Invalid request")]
[SwaggerResponse(404, "Member not found")]
[SwaggerResponse(401, "Unauthorized")]
public async Task<ActionResult<UpdateMemberProfileResult>> UpdateProfile(
Guid id,
[FromBody] UpdateMemberProfileCommand command)
{
command.MemberId = id;
try
{
var result = await _mediator.Send(command);
return Ok(result);
}
catch (KeyNotFoundException ex)
{
return NotFound(new { message = ex.Message });
}
}
/// <summary>
/// EN: Change membership level.
/// VI: Thay đổi cấp thành viên.
/// </summary>
[HttpPut("{id:guid}/level")]
[SwaggerOperation(Summary = "Change membership level", Description = "Changes a member's membership level")]
[SwaggerResponse(200, "Level changed", typeof(ChangeMembershipLevelResult))]
[SwaggerResponse(400, "Invalid request")]
[SwaggerResponse(404, "Member not found")]
[SwaggerResponse(401, "Unauthorized")]
public async Task<ActionResult<ChangeMembershipLevelResult>> ChangeLevel(
Guid id,
[FromBody] ChangeMembershipLevelCommand command)
{
command.MemberId = id;
try
{
var result = await _mediator.Send(command);
return Ok(result);
}
catch (KeyNotFoundException ex)
{
return NotFound(new { message = ex.Message });
}
catch (ArgumentException ex)
{
return BadRequest(new { message = ex.Message });
}
}
private Guid? GetCurrentUserId()
{
var userIdClaim = User.FindFirst("sub")?.Value ?? User.FindFirst("id")?.Value;
if (Guid.TryParse(userIdClaim, out var userId))
{
return userId;
}
return null;
}
}