-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/voucher #200
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Feature/voucher #200
Changes from all commits
36f76fe
3d26f35
1ffec96
ae166d7
9d7f388
f7f19ab
78fe4db
3127840
e047a58
21884cc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| using Microsoft.AspNetCore.Authorization; | ||
| using Microsoft.AspNetCore.Mvc; | ||
| using ShoeStore.Application.DTOs; | ||
| using ShoeStore.Application.DTOs.VoucherDtos; | ||
| using ShoeStore.Application.Interface.VoucherInterface; | ||
|
|
||
| namespace ShoeStore.Api.Controllers; | ||
|
|
||
| /// <summary> | ||
| /// Controller for managing user-specific voucher operations. | ||
| /// Provides endpoints for users to retrieve their own vouchers. | ||
| /// </summary> | ||
| /// <param name="userVoucherService">Service for handling user-voucher relationship operations.</param> | ||
| [ApiController] | ||
| [Route("api/user/vouchers")] | ||
| [Authorize(Roles = "User")] | ||
| public class UserVoucherController(IUserVoucherService userVoucherService) : ControllerBase | ||
| { | ||
| /// <summary> | ||
| /// Retrieves all active vouchers associated with a specific user. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Requires User role authorization. | ||
| /// Returns a list of vouchers that are currently valid and assigned to the user. | ||
| /// </remarks> | ||
| /// <param name="userGuid">The unique identifier (GUID) of the user whose vouchers are being retrieved.</param> | ||
| /// <param name="token">Cancellation token for the request.</param> | ||
| /// <response code="200">Vouchers retrieved successfully.</response> | ||
| /// <response code="401">Unauthorized; user must be authenticated with User role.</response> | ||
| /// <response code="404">Not found; no vouchers found for the specified user.</response> | ||
| /// <response code="500">Internal server error; an unexpected error occurred.</response> | ||
| /// <returns>An action result containing a paginated list of user vouchers on success, or an error response.</returns> | ||
| [ProducesResponseType(typeof(PageResult<ResponseVoucherUserDto>), StatusCodes.Status200OK)] | ||
| [ProducesResponseType(typeof(object), StatusCodes.Status401Unauthorized)] | ||
| [ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)] | ||
| [ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)] | ||
| [HttpGet("user/{userGuid}")] | ||
| public async Task<IActionResult> GetVouchersForUser(Guid userGuid, CancellationToken token) | ||
| { | ||
| var result = await userVoucherService.GetAllVoucherForUserAsync(userGuid, token); | ||
| return result.Match<IActionResult>( | ||
| vouchers => Ok(vouchers), | ||
| errors => errors[0].Code switch | ||
| { | ||
| "NO_VOUCHERS_FOUND" => NotFound(new | ||
| { | ||
| message = "No vouchers found for this user", | ||
| detail = errors[0].Description | ||
| }), | ||
| _ => BadRequest(new | ||
| { | ||
| message = "Failed to retrieve vouchers for user", | ||
| detail = errors[0].Description | ||
| }) | ||
| }); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,243 @@ | ||
| using ErrorOr; | ||
| using Microsoft.AspNetCore.Authorization; | ||
| using Microsoft.AspNetCore.Mvc; | ||
| using ShoeStore.Application.DTOs; | ||
| using ShoeStore.Application.DTOs.VoucherDtos; | ||
| using ShoeStore.Application.Interface.VoucherInterface; | ||
|
|
||
| namespace ShoeStore.Api.Controllers; | ||
|
|
||
| /// <summary> | ||
| /// Controller for managing vouchers in the system. | ||
| /// Provides endpoints for voucher creation, retrieval, update, and deletion (Admin only). | ||
| /// </summary> | ||
| /// <param name="voucherService">Service for handling voucher logic operations.</param> | ||
| [ApiController] | ||
| [Route("api/admin/vouchers")] | ||
| [Authorize(Roles = "Admin")] | ||
| public class VoucherController(IVoucherService voucherService) : ControllerBase | ||
| { | ||
| /// <summary> | ||
| /// Creates a new voucher for the store. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Requires Admin role authorization. | ||
| /// The request body should include: | ||
| /// - <c>VoucherName</c>: Name of the voucher | ||
| /// - <c>Discount</c>: Value of the discount | ||
| /// - <c>DiscountType</c>: Type of discount (Percentage/FixedAmount) | ||
| /// - <c>TotalQuantity</c>: Number of vouchers available | ||
| /// - <c>ValidFrom/ValidTo</c>: Expiration dates | ||
| /// </remarks> | ||
| /// <param name="createVoucherDto">Data transfer object containing voucher creation details.</param> | ||
| /// <param name="token">Cancellation token for the request.</param> | ||
| /// <response code="201">Voucher created successfully.</response> | ||
| /// <response code="400">Bad request; invalid voucher data provided.</response> | ||
| /// <response code="401">Unauthorized; user must be authenticated with Admin role.</response> | ||
| /// <response code="500">Internal server error; an unexpected error occurred.</response> | ||
| /// <returns>An action result with status 201 (Created) on success, or an error response.</returns> | ||
| [ProducesResponseType(typeof(object), StatusCodes.Status201Created)] | ||
| [ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)] | ||
| [ProducesResponseType(typeof(object), StatusCodes.Status401Unauthorized)] | ||
| [ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)] | ||
| [HttpPost] | ||
| public async Task<IActionResult> CreateVoucher([FromBody] CreateVoucherDto createVoucherDto, CancellationToken token) | ||
| { | ||
| var result = await voucherService.CreateVoucherAsync(createVoucherDto, token); | ||
|
|
||
| return result.Match<IActionResult>( | ||
| _ => Created("", new { message = "Voucher created successfully" }), | ||
| errors => BadRequest(new | ||
| { | ||
| message = "Failed to create voucher", | ||
| detail = errors[0].Description | ||
| })); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Updates an existing voucher's details. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Requires Admin role authorization. | ||
| /// Updates the specified voucher with the provided information. | ||
| /// </remarks> | ||
| /// <param name="voucherGuid">The unique identifier (GUID) of the voucher to update.</param> | ||
| /// <param name="updateVoucherDto">Data transfer object containing updated voucher details.</param> | ||
| /// <param name="token">Cancellation token for the request.</param> | ||
| /// <response code="200">Voucher updated successfully.</response> | ||
| /// <response code="400">Bad request; invalid update data provided.</response> | ||
| /// <response code="401">Unauthorized; user must be authenticated with Admin role.</response> | ||
| /// <response code="404">Not found; the voucher with the specified ID does not exist.</response> | ||
| /// <response code="500">Internal server error; an unexpected error occurred.</response> | ||
| /// <returns>An action result with status 200 (OK) on success, or an error response.</returns> | ||
| [ProducesResponseType(typeof(object), StatusCodes.Status200OK)] | ||
| [ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)] | ||
| [ProducesResponseType(typeof(object), StatusCodes.Status401Unauthorized)] | ||
| [ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)] | ||
| [ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)] | ||
| [HttpPut("{voucherGuid}")] | ||
| public async Task<IActionResult> UpdateVoucher(Guid voucherGuid, [FromBody] UpdateVoucherDto updateVoucherDto, | ||
| CancellationToken token) | ||
| { | ||
| var result = await voucherService.UpdateVoucherAsync(voucherGuid, updateVoucherDto, token); | ||
| return result.Match<IActionResult>( | ||
| _ => Ok(new { message = "Voucher updated successfully" }), | ||
| errors => errors[0].Code switch | ||
| { | ||
| "VOUCHER_NOT_FOUND" => NotFound(new | ||
| { | ||
| message = "Voucher not found", | ||
| detail = errors[0].Description | ||
| }), | ||
| _ => BadRequest(new | ||
| { | ||
| message = "Failed to update voucher", | ||
| detail = errors[0].Description | ||
| }) | ||
| }); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Deletes a specific voucher from the system (soft delete). | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Requires Admin role authorization. | ||
| /// Performs a soft delete by marking the voucher as deleted. | ||
| /// </remarks> | ||
| /// <param name="voucherGuid">The unique identifier (GUID) of the voucher to delete.</param> | ||
| /// <param name="token">Cancellation token for the request.</param> | ||
| /// <response code="200">Voucher deleted successfully.</response> | ||
| /// <response code="401">Unauthorized; user must be authenticated with Admin role.</response> | ||
| /// <response code="404">Not found; the voucher with the specified ID does not exist.</response> | ||
| /// <response code="500">Internal server error; an unexpected error occurred.</response> | ||
| /// <returns>An action result with status 200 (OK) on success, or an error response.</returns> | ||
| [ProducesResponseType(typeof(object), StatusCodes.Status200OK)] | ||
| [ProducesResponseType(typeof(object), StatusCodes.Status401Unauthorized)] | ||
| [ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)] | ||
| [ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)] | ||
| [HttpDelete("{voucherGuid}")] | ||
| public async Task<IActionResult> DeleteVoucher(Guid voucherGuid, CancellationToken token) | ||
| { | ||
| var result = await voucherService.DeleteVoucherByGuidAsync(voucherGuid, token); | ||
| return result.Match<IActionResult>( | ||
| _ => Ok(new { message = "Voucher deleted successfully" }), | ||
| errors => errors[0].Code switch | ||
| { | ||
| "VOUCHER_NOT_FOUND" => NotFound(new | ||
| { | ||
| message = "Voucher not found", | ||
| detail = errors[0].Description | ||
| }), | ||
| _ => BadRequest(new | ||
| { | ||
| message = "Failed to delete voucher", | ||
| detail = errors[0].Description | ||
| }) | ||
| }); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Deletes all expired vouchers from the system (soft delete). | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Requires Admin role authorization. | ||
| /// Identifies and soft deletes all vouchers whose expiration date has passed. | ||
| /// </remarks> | ||
| /// <param name="token">Cancellation token for the request.</param> | ||
| /// <response code="200">Expired vouchers deleted successfully.</response> | ||
| /// <response code="400">Bad request; failed to delete expired vouchers or no expired vouchers found.</response> | ||
| /// <response code="401">Unauthorized; user must be authenticated with Admin role.</response> | ||
| /// <response code="500">Internal server error; an unexpected error occurred.</response> | ||
| /// <returns>An action result with status 200 (OK) on success, or an error response.</returns> | ||
| [ProducesResponseType(typeof(object), StatusCodes.Status200OK)] | ||
| [ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)] | ||
| [ProducesResponseType(typeof(object), StatusCodes.Status401Unauthorized)] | ||
| [ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)] | ||
| [HttpDelete("expire")] | ||
| public async Task<IActionResult> DeleteExpiredVouchers(CancellationToken token) | ||
| { | ||
| var result = await voucherService.DeleteVoucherExpireAsync(token); | ||
| return result.Match<IActionResult>( | ||
| _ => Ok(new { message = "Expired vouchers deleted successfully" }), | ||
| errors => BadRequest(new | ||
| { | ||
| message = "Failed to delete expired vouchers", | ||
| detail = errors[0].Description | ||
| })); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Retrieves a paginated list of vouchers for administrative purposes. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Requires Admin role authorization. | ||
| /// Provides a list of vouchers with detailed information relevant for administrators. | ||
| /// </remarks> | ||
| /// <param name="token">Cancellation token for the request.</param> | ||
| /// <response code="200">Vouchers retrieved successfully.</response> | ||
| /// <response code="401">Unauthorized; user must be authenticated with Admin role.</response> | ||
| /// <response code="404">Not found; no vouchers found in the system.</response> | ||
| /// <response code="500">Internal server error; an unexpected error occurred.</response> | ||
| /// <returns>An action result containing a paginated list of vouchers on success, or an error response.</returns> | ||
| [ProducesResponseType(typeof(PageResult<ResponseVoucherAdminDto>), StatusCodes.Status200OK)] | ||
| [ProducesResponseType(typeof(object), StatusCodes.Status401Unauthorized)] | ||
| [ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)] | ||
| [ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)] | ||
| [HttpGet] | ||
| public async Task<IActionResult> GetVouchersForAdmin(CancellationToken token) | ||
| { | ||
| var result = await voucherService.GetVoucherForAdminAsync(token); | ||
| return result.Match<IActionResult>( | ||
| vouchers => Ok(vouchers), | ||
| errors => errors[0].Code switch | ||
| { | ||
| "NO_VOUCHERS_FOUND" => NotFound(new | ||
| { | ||
| message = "No vouchers found", | ||
| detail = errors[0].Description | ||
| }), | ||
| _ => BadRequest(new | ||
| { | ||
| message = "Failed to retrieve vouchers", | ||
| detail = errors[0].Description | ||
| }) | ||
| }); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Retrieves all active and non-deleted vouchers in the system. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Requires Admin role authorization. | ||
| /// </remarks> | ||
| /// <param name="token">Cancellation token for the request.</param> | ||
| /// <response code="200">All vouchers retrieved successfully.</response> | ||
| /// <response code="401">Unauthorized; user must be authenticated with Admin role.</response> | ||
| /// <response code="404">Not found; no vouchers found in the system.</response> | ||
| /// <response code="500">Internal server error; an unexpected error occurred.</response> | ||
| /// <returns>An action result containing a list of vouchers on success, or an error response.</returns> | ||
| [ProducesResponseType(typeof(PageResult<ResponseVoucherAdminDto>), StatusCodes.Status200OK)] | ||
| [ProducesResponseType(typeof(object), StatusCodes.Status401Unauthorized)] | ||
| [ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)] | ||
| [ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)] | ||
| [HttpGet("all")] | ||
| public async Task<IActionResult> GetAllVouchers(CancellationToken token) | ||
| { | ||
| var result = await voucherService.GetAllVouchersAsync(token); | ||
| return result.Match<IActionResult>( | ||
| vouchers => Ok(vouchers), | ||
| errors => errors[0].Code switch | ||
| { | ||
| "NO_VOUCHERS_FOUND" => NotFound(new | ||
| { | ||
| message = "No vouchers found", | ||
| detail = errors[0].Description | ||
| }), | ||
| _ => BadRequest(new | ||
| { | ||
| message = "Failed to retrieve all vouchers", | ||
| detail = errors[0].Description | ||
| }) | ||
| }); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| | ||
| namespace ShoeStore.Application.DTOs.VoucherDtos | ||
| { | ||
| public class CreateVoucherDto | ||
| { | ||
| public string? VoucherName { get; set; } | ||
| public string? VoucherDescription { get; set; } | ||
| public decimal? Discount { get; set; } | ||
| public int VoucherScope { get; set; } = 1; // Default to AllProducts | ||
| public int DiscountType { get; set; } = 1; // Default to Percentage | ||
| public decimal MaxPriceDiscount { get; set; } | ||
| private DateTime? _validFrom; | ||
| public DateTime? ValidFrom | ||
| { | ||
| get => _validFrom; | ||
| set => _validFrom = value.HasValue ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : null; | ||
| } | ||
|
|
||
| private DateTime? _validTo; | ||
| public DateTime? ValidTo | ||
| { | ||
| get => _validTo; | ||
| set => _validTo = value.HasValue ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : null; | ||
| } | ||
|
Comment on lines
+13
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: DateTime.SpecifyKind does not convert the DateTime value to UTC. It only sets the Kind property while keeping the same number of ticks (time value) unchanged. The official documentation explicitly states: "The returned DateTime value does not represent the same instant in time as the value parameter, and SpecifyKind is not a time zone conversion method. Instead, it leaves the time specified by the value parameter unchanged, and sets the Kind property to kind." For time zone conversions, use methods like ToUniversalTime or ToLocalTime. Citations:
🏁 Script executed: # First, locate and inspect the CreateVoucherDto.cs file
find . -name "CreateVoucherDto.cs" -type fRepository: tqha1011/ShoeStore Length of output: 134 🏁 Script executed: # Search for DateTime.SpecifyKind usage across the codebase
rg "SpecifyKind" -t cs -C 3Repository: tqha1011/ShoeStore Length of output: 5521 🏁 Script executed: # Search for similar DateTime assignment patterns in DTOs
rg "DateTime\.SpecifyKind|DateTimeKind\.Utc" -t cs --glob "**/*Dto.cs"Repository: tqha1011/ShoeStore Length of output: 419 Use Lines 16 and 23 use 🛠️ Proper UTC normalization+ private static DateTime? NormalizeUtc(DateTime? value)
+ {
+ if (!value.HasValue)
+ {
+ return null;
+ }
+
+ return value.Value.Kind == DateTimeKind.Unspecified
+ ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc)
+ : value.Value.ToUniversalTime();
+ }
+
private DateTime? _validFrom;
public DateTime? ValidFrom
{
get => _validFrom;
- set => _validFrom = value.HasValue ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : null;
+ set => _validFrom = NormalizeUtc(value);
}
private DateTime? _validTo;
public DateTime? ValidTo
{
get => _validTo;
- set => _validTo = value.HasValue ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : null;
+ set => _validTo = NormalizeUtc(value);
}🤖 Prompt for AI Agents |
||
| public int? MaxUsagePerUser { get; set; } | ||
| public int? TotalQuantity { get; set; } | ||
| public decimal? MinOrderPrice { get; set; } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Text; | ||
|
|
||
| namespace ShoeStore.Application.DTOs.VoucherDtos | ||
| { | ||
| public class DeleteVoucherDto | ||
| { | ||
| public bool IsDeleted { get; set; } = true; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Text; | ||
|
|
||
| namespace ShoeStore.Application.DTOs.VoucherDtos | ||
| { | ||
| public class ResponseVoucherAdminDto | ||
| { | ||
| public string? VoucherName { get; set; } | ||
| public decimal Discount { get; set; } = 0; | ||
| public int? VoucherScope { get; set; } | ||
| public int? DiscountType { get; set; } | ||
| public decimal? MaxPriceDiscount { get; set; } | ||
| public decimal? MinOrderPrice { get; set; } | ||
| private DateTime? _validFrom; | ||
| public DateTime? ValidFrom | ||
| { | ||
| get => _validFrom; | ||
| set => _validFrom = value.HasValue ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : null; | ||
| } | ||
|
|
||
| private DateTime? _validTo; | ||
| public DateTime? ValidTo | ||
| { | ||
| get => _validTo; | ||
| set => _validTo = value.HasValue ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : null; | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| | ||
| namespace ShoeStore.Application.DTOs.VoucherDtos | ||
| { | ||
| public class ResponseVoucherUserDto | ||
| { | ||
| public string VoucherName { get; set; } = string.Empty; | ||
| public string Description { get; set; } = string.Empty; | ||
| public decimal Discount { get; set; } = 0; | ||
| private DateTime? _validFrom; | ||
| public DateTime? ValidFrom | ||
| { | ||
| get => _validFrom; | ||
| set => _validFrom = value.HasValue ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : null; | ||
| } | ||
|
|
||
| private DateTime? _validTo; | ||
| public DateTime? ValidTo | ||
| { | ||
| get => _validTo; | ||
| set => _validTo = value.HasValue ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : null; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cái này k cần phải là tham số, lấy từ jwt token dùng find first value