Skip to content

Commit 2945894

Browse files
Hoang44444tqha1011coderabbitai[bot]
authored
Feature/voucher (#200)
* fix * feat: adding API POST for Admin * chore: rename voucherRepository to repository * feat: adding POST api/admin/voucher * feat: adding GET API for user to get all the voucher * feat: adding GET API for all voucher for admin * adding the user voucher controller * chore: change the way coding status for api * fix * feat: notifaction when admin created voucher * fix * fix: change update voucher from FromBody to FromFrom * Apply suggestion from @coderabbitai[bot] Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: Tran Quang Ha <[email protected]> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent ae02a63 commit 2945894

18 files changed

Lines changed: 779 additions & 1 deletion

File tree

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using System.Security.Claims;
2+
using Microsoft.AspNetCore.Authorization;
3+
using Microsoft.AspNetCore.Mvc;
4+
using ShoeStore.Application.DTOs;
5+
using ShoeStore.Application.DTOs.VoucherDtos;
6+
using ShoeStore.Application.Interface.VoucherInterface;
7+
8+
namespace ShoeStore.Api.Controllers;
9+
10+
/// <summary>
11+
/// Controller for managing user-specific voucher operations.
12+
/// Provides endpoints for users to retrieve their own vouchers.
13+
/// </summary>
14+
/// <param name="userVoucherService">Service for handling user-voucher relationship operations.</param>
15+
[ApiController]
16+
[Route("api/user/vouchers")]
17+
[Authorize(Roles = "User")]
18+
public class UserVoucherController(IUserVoucherService userVoucherService) : ControllerBase
19+
{
20+
/// <summary>
21+
/// Retrieves all active vouchers associated with a specific user.
22+
/// </summary>
23+
/// <remarks>
24+
/// Requires User role authorization.
25+
/// Returns a list of vouchers that are currently valid and assigned to the user.
26+
/// </remarks>
27+
/// <param name="userGuid">The unique identifier (GUID) of the user whose vouchers are being retrieved.</param>
28+
/// <param name="token">Cancellation token for the request.</param>
29+
/// <response code="200">Vouchers retrieved successfully.</response>
30+
/// <response code="401">Unauthorized; user must be authenticated with User role.</response>
31+
/// <response code="404">Not found; no vouchers found for the specified user.</response>
32+
/// <response code="500">Internal server error; an unexpected error occurred.</response>
33+
/// <returns>An action result containing a paginated list of user vouchers on success, or an error response.</returns>
34+
[ProducesResponseType(typeof(PageResult<ResponseVoucherUserDto>), StatusCodes.Status200OK)]
35+
[ProducesResponseType(typeof(object), StatusCodes.Status401Unauthorized)]
36+
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
37+
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
38+
[HttpGet("user")]
39+
public async Task<IActionResult> GetVouchersForUser(CancellationToken token)
40+
{
41+
var userGuidString = User.FindFirstValue(ClaimTypes.NameIdentifier);
42+
if (string.IsNullOrEmpty(userGuidString) || !Guid.TryParse(userGuidString, out var userGuid))
43+
return Unauthorized();
44+
45+
var result = await userVoucherService.GetAllVoucherForUserAsync(userGuid, token);
46+
return result.Match<IActionResult>(
47+
vouchers => Ok(vouchers),
48+
errors => errors[0].Code switch
49+
{
50+
"NO_VOUCHERS_FOUND" => NotFound(new
51+
{
52+
message = "No vouchers found for this user",
53+
detail = errors[0].Description
54+
}),
55+
_ => BadRequest(new
56+
{
57+
message = "Failed to retrieve vouchers for user",
58+
detail = errors[0].Description
59+
})
60+
});
61+
}
62+
}
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
using System.Security.Claims;
2+
using ErrorOr;
3+
using Microsoft.AspNetCore.Authorization;
4+
using Microsoft.AspNetCore.Mvc;
5+
using ShoeStore.Application.DTOs;
6+
using ShoeStore.Application.DTOs.VoucherDtos;
7+
using ShoeStore.Application.Interface.VoucherInterface;
8+
9+
namespace ShoeStore.Api.Controllers;
10+
11+
/// <summary>
12+
/// Controller for managing vouchers in the system.
13+
/// Provides endpoints for voucher creation, retrieval, update, and deletion (Admin only).
14+
/// </summary>
15+
/// <param name="voucherService">Service for handling voucher logic operations.</param>
16+
[ApiController]
17+
[Route("api/admin/vouchers")]
18+
[Authorize(Roles = "Admin")]
19+
public class VoucherController(IVoucherService voucherService) : ControllerBase
20+
{
21+
/// <summary>
22+
/// Creates a new voucher for the store.
23+
/// </summary>
24+
/// <remarks>
25+
/// Requires Admin role authorization.
26+
/// The request body should include:
27+
/// - <c>VoucherName</c>: Name of the voucher
28+
/// - <c>Discount</c>: Value of the discount
29+
/// - <c>DiscountType</c>: Type of discount (Percentage/FixedAmount)
30+
/// - <c>TotalQuantity</c>: Number of vouchers available
31+
/// - <c>ValidFrom/ValidTo</c>: Expiration dates
32+
/// </remarks>
33+
/// <param name="createVoucherDto">Data transfer object containing voucher creation details.</param>
34+
/// <param name="token">Cancellation token for the request.</param>
35+
/// <response code="201">Voucher created successfully.</response>
36+
/// <response code="400">Bad request; invalid voucher data provided.</response>
37+
/// <response code="401">Unauthorized; user must be authenticated with Admin role.</response>
38+
/// <response code="500">Internal server error; an unexpected error occurred.</response>
39+
/// <returns>An action result with status 201 (Created) on success, or an error response.</returns>
40+
[ProducesResponseType(typeof(object), StatusCodes.Status201Created)]
41+
[ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
42+
[ProducesResponseType(typeof(object), StatusCodes.Status401Unauthorized)]
43+
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
44+
[HttpPost]
45+
public async Task<IActionResult> CreateVoucher([FromBody] CreateVoucherDto createVoucherDto, CancellationToken token)
46+
{
47+
var result = await voucherService.CreateVoucherAsync(createVoucherDto, token);
48+
49+
return await result.MatchAsync<IActionResult>(
50+
async _ =>
51+
{
52+
var adminEmail = User.FindFirstValue(ClaimTypes.Email) ?? "[email protected]";
53+
54+
await voucherService.NotifyUserAboutNewVoucherAsync(
55+
adminEmail: adminEmail,
56+
voucherName: createVoucherDto.VoucherName ?? "New Discount",
57+
validTo: createVoucherDto.ValidTo ?? DateTime.UtcNow,
58+
token: token
59+
);
60+
61+
return Created("", new { message = "Voucher created and users notified" });
62+
},
63+
errors => Task.FromResult<IActionResult>(BadRequest(new
64+
{
65+
message = "Failed to create voucher",
66+
detail = errors[0].Description
67+
}))
68+
);
69+
}
70+
71+
/// <summary>
72+
/// Updates an existing voucher's details.
73+
/// </summary>
74+
/// <remarks>
75+
/// Requires Admin role authorization.
76+
/// Updates the specified voucher with the provided information.
77+
/// </remarks>
78+
/// <param name="voucherGuid">The unique identifier (GUID) of the voucher to update.</param>
79+
/// <param name="updateVoucherDto">Data transfer object containing updated voucher details.</param>
80+
/// <param name="token">Cancellation token for the request.</param>
81+
/// <response code="200">Voucher updated successfully.</response>
82+
/// <response code="400">Bad request; invalid update data provided.</response>
83+
/// <response code="401">Unauthorized; user must be authenticated with Admin role.</response>
84+
/// <response code="404">Not found; the voucher with the specified ID does not exist.</response>
85+
/// <response code="500">Internal server error; an unexpected error occurred.</response>
86+
/// <returns>An action result with status 200 (OK) on success, or an error response.</returns>
87+
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
88+
[ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
89+
[ProducesResponseType(typeof(object), StatusCodes.Status401Unauthorized)]
90+
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
91+
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
92+
[HttpPut("{voucherGuid}")]
93+
public async Task<IActionResult> UpdateVoucher(Guid voucherGuid, [FromForm] UpdateVoucherDto updateVoucherDto,
94+
CancellationToken token)
95+
{
96+
var result = await voucherService.UpdateVoucherAsync(voucherGuid, updateVoucherDto, token);
97+
return result.Match<IActionResult>(
98+
_ => Ok(new { message = "Voucher updated successfully" }),
99+
errors => errors[0].Code switch
100+
{
101+
"VOUCHER_NOT_FOUND" => NotFound(new
102+
{
103+
message = "Voucher not found",
104+
detail = errors[0].Description
105+
}),
106+
_ => BadRequest(new
107+
{
108+
message = "Failed to update voucher",
109+
detail = errors[0].Description
110+
})
111+
});
112+
}
113+
114+
/// <summary>
115+
/// Deletes a specific voucher from the system (soft delete).
116+
/// </summary>
117+
/// <remarks>
118+
/// Requires Admin role authorization.
119+
/// Performs a soft delete by marking the voucher as deleted.
120+
/// </remarks>
121+
/// <param name="voucherGuid">The unique identifier (GUID) of the voucher to delete.</param>
122+
/// <param name="token">Cancellation token for the request.</param>
123+
/// <response code="200">Voucher deleted successfully.</response>
124+
/// <response code="401">Unauthorized; user must be authenticated with Admin role.</response>
125+
/// <response code="404">Not found; the voucher with the specified ID does not exist.</response>
126+
/// <response code="500">Internal server error; an unexpected error occurred.</response>
127+
/// <returns>An action result with status 200 (OK) on success, or an error response.</returns>
128+
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
129+
[ProducesResponseType(typeof(object), StatusCodes.Status401Unauthorized)]
130+
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
131+
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
132+
[HttpDelete("{voucherGuid}")]
133+
public async Task<IActionResult> DeleteVoucher(Guid voucherGuid, CancellationToken token)
134+
{
135+
var result = await voucherService.DeleteVoucherByGuidAsync(voucherGuid, token);
136+
return result.Match<IActionResult>(
137+
_ => Ok(new { message = "Voucher deleted successfully" }),
138+
errors => errors[0].Code switch
139+
{
140+
"VOUCHER_NOT_FOUND" => NotFound(new
141+
{
142+
message = "Voucher not found",
143+
detail = errors[0].Description
144+
}),
145+
_ => BadRequest(new
146+
{
147+
message = "Failed to delete voucher",
148+
detail = errors[0].Description
149+
})
150+
});
151+
}
152+
153+
/// <summary>
154+
/// Deletes all expired vouchers from the system (soft delete).
155+
/// </summary>
156+
/// <remarks>
157+
/// Requires Admin role authorization.
158+
/// Identifies and soft deletes all vouchers whose expiration date has passed.
159+
/// </remarks>
160+
/// <param name="token">Cancellation token for the request.</param>
161+
/// <response code="200">Expired vouchers deleted successfully.</response>
162+
/// <response code="400">Bad request; failed to delete expired vouchers or no expired vouchers found.</response>
163+
/// <response code="401">Unauthorized; user must be authenticated with Admin role.</response>
164+
/// <response code="500">Internal server error; an unexpected error occurred.</response>
165+
/// <returns>An action result with status 200 (OK) on success, or an error response.</returns>
166+
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
167+
[ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
168+
[ProducesResponseType(typeof(object), StatusCodes.Status401Unauthorized)]
169+
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
170+
[HttpDelete("expire")]
171+
public async Task<IActionResult> DeleteExpiredVouchers(CancellationToken token)
172+
{
173+
var result = await voucherService.DeleteVoucherExpireAsync(token);
174+
return result.Match<IActionResult>(
175+
_ => Ok(new { message = "Expired vouchers deleted successfully" }),
176+
errors => BadRequest(new
177+
{
178+
message = "Failed to delete expired vouchers",
179+
detail = errors[0].Description
180+
}));
181+
}
182+
183+
/// <summary>
184+
/// Retrieves a paginated list of vouchers for administrative purposes.
185+
/// </summary>
186+
/// <remarks>
187+
/// Requires Admin role authorization.
188+
/// Provides a list of vouchers with detailed information relevant for administrators.
189+
/// </remarks>
190+
/// <param name="token">Cancellation token for the request.</param>
191+
/// <response code="200">Vouchers retrieved successfully.</response>
192+
/// <response code="401">Unauthorized; user must be authenticated with Admin role.</response>
193+
/// <response code="404">Not found; no vouchers found in the system.</response>
194+
/// <response code="500">Internal server error; an unexpected error occurred.</response>
195+
/// <returns>An action result containing a paginated list of vouchers on success, or an error response.</returns>
196+
[ProducesResponseType(typeof(PageResult<ResponseVoucherAdminDto>), StatusCodes.Status200OK)]
197+
[ProducesResponseType(typeof(object), StatusCodes.Status401Unauthorized)]
198+
[ProducesResponseType(typeof(object), StatusCodes.Status404NotFound)]
199+
[ProducesResponseType(typeof(object), StatusCodes.Status500InternalServerError)]
200+
[HttpGet]
201+
public async Task<IActionResult> GetVouchersForAdmin(CancellationToken token)
202+
{
203+
var result = await voucherService.GetVoucherForAdminAsync(token);
204+
return result.Match<IActionResult>(
205+
vouchers => Ok(vouchers),
206+
errors => errors[0].Code switch
207+
{
208+
"NO_VOUCHERS_FOUND" => NotFound(new
209+
{
210+
message = "No vouchers found",
211+
detail = errors[0].Description
212+
}),
213+
_ => BadRequest(new
214+
{
215+
message = "Failed to retrieve vouchers",
216+
detail = errors[0].Description
217+
})
218+
});
219+
}
220+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+

2+
using ShoeStore.Domain.Enum;
3+
4+
namespace ShoeStore.Application.DTOs.VoucherDtos
5+
{
6+
public class CreateVoucherDto
7+
{
8+
public string? VoucherName { get; set; }
9+
public string? VoucherDescription { get; set; }
10+
public decimal? Discount { get; set; }
11+
public VoucherScope VoucherScope { get; set; } = VoucherScope.Product;
12+
public DiscountType DiscountType { get; set; } = DiscountType.Percentage;
13+
public decimal MaxPriceDiscount { get; set; }
14+
private DateTime? _validFrom;
15+
public DateTime? ValidFrom
16+
{
17+
get => _validFrom;
18+
set => _validFrom = value.HasValue ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : null;
19+
}
20+
21+
private DateTime? _validTo;
22+
public DateTime? ValidTo
23+
{
24+
get => _validTo;
25+
set => _validTo = value.HasValue ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : null;
26+
}
27+
public int? MaxUsagePerUser { get; set; }
28+
public int? TotalQuantity { get; set; }
29+
public decimal? MinOrderPrice { get; set; }
30+
}
31+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace ShoeStore.Application.DTOs.VoucherDtos
6+
{
7+
public class DeleteVoucherDto
8+
{
9+
public bool IsDeleted { get; set; } = true;
10+
}
11+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace ShoeStore.Application.DTOs.VoucherDtos
6+
{
7+
public class ResponseVoucherAdminDto
8+
{
9+
public Guid VoucherGuid { get; set; }
10+
public string? VoucherName { get; set; }
11+
public decimal Discount { get; set; } = 0;
12+
public int? VoucherScope { get; set; }
13+
public int? DiscountType { get; set; }
14+
public decimal? MaxPriceDiscount { get; set; }
15+
public decimal? MinOrderPrice { get; set; }
16+
private DateTime? _validFrom;
17+
public DateTime? ValidFrom
18+
{
19+
get => _validFrom;
20+
set => _validFrom = value.HasValue ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : null;
21+
}
22+
23+
private DateTime? _validTo;
24+
public DateTime? ValidTo
25+
{
26+
get => _validTo;
27+
set => _validTo = value.HasValue ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : null;
28+
}
29+
}
30+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+

2+
namespace ShoeStore.Application.DTOs.VoucherDtos
3+
{
4+
public class ResponseVoucherUserDto
5+
{
6+
public Guid VoucherGuid { get; set; }
7+
public string VoucherName { get; set; } = string.Empty;
8+
public string Description { get; set; } = string.Empty;
9+
public decimal Discount { get; set; } = 0;
10+
private DateTime? _validFrom;
11+
public DateTime? ValidFrom
12+
{
13+
get => _validFrom;
14+
set => _validFrom = value.HasValue ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : null;
15+
}
16+
17+
private DateTime? _validTo;
18+
public DateTime? ValidTo
19+
{
20+
get => _validTo;
21+
set => _validTo = value.HasValue ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : null;
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)