Skip to content

Commit 51016db

Browse files
committed
Enhance subscription and membership management
- Enforced role-based access control in `ClubSettingsController`. - Added `SubscriptionsController` for subscription management. - Introduced commands/handlers for subscription lifecycle actions. - Refactored `MembershipPlan` to use `DurationInMonths`. - Added `WillNotRenew` to `Subscription` for soft cancellations. - Added `IsRefundAllowed` to `ClubSettings` for refund policies. - Improved token service with issuer claim and validation updates. - Enhanced `Member` repository to include related entities. - Added `PaginatedList<T>` for query pagination support. - Updated database schema with new columns and relationships. - Refactored and normalized subscription/insurance payment dates. - Updated tests to reflect changes in subscription and membership. - Removed unused code and improved exception handling.
1 parent 8871cf2 commit 51016db

File tree

48 files changed

+2168
-94
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+2168
-94
lines changed

GymMgmt.Api/Controllers/ClubSettings/ClubSettingsController.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ namespace GymMgmt.Api.Controllers.ClubSettings
1111
{
1212
[Route("api/[controller]")]
1313
[ApiController]
14+
[Authorize(Roles = "Admin")]
1415
public class ClubSettingsController : ControllerBase
1516
{
1617
private readonly IMediator _mediator;
@@ -19,7 +20,7 @@ public ClubSettingsController(IMediator mediator)
1920
_mediator = mediator;
2021
}
2122

22-
[AllowAnonymous]
23+
2324
[HttpPost("AddClubSettings")]
2425
[ProducesResponseType(typeof(ApiResponse<ReadClubSettingsDto>), (int)HttpStatusCode.Created)]
2526
[ProducesResponseType(typeof(ApiResponse<object>), (int)HttpStatusCode.BadRequest)]
@@ -33,7 +34,6 @@ public async Task<ActionResult> CreateClubSettings([FromBody] CreateClubSetting
3334
}
3435

3536
[HttpGet("GetClubSetting")]
36-
[AllowAnonymous]
3737
[ProducesResponseType(StatusCodes.Status200OK)]
3838
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
3939
[ProducesResponseType(typeof(ApiResponse<ReadClubSettingsDto>), (int)HttpStatusCode.OK)]
@@ -45,7 +45,6 @@ public async Task<ActionResult> GetClubSetting()
4545

4646
return Ok(ApiResponse<ReadClubSettingsDto>.Success(result));
4747
}
48-
[AllowAnonymous]
4948
[HttpPut("UpdateClubSetting")]
5049
[ProducesResponseType(typeof(ApiResponse<ReadClubSettingsDto>), (int)HttpStatusCode.OK)]
5150
[ProducesResponseType(typeof(ApiResponse<object>), (int)HttpStatusCode.NotFound)]

GymMgmt.Api/Controllers/Members/MembersController.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
using GymMgmt.Application.Features.Members.GetAllMembers;
44
using GymMgmt.Application.Features.Members.GetMemberById;
55
using GymMgmt.Application.Features.Members.PayInsurrance;
6+
using GymMgmt.Application.Features.Members.UpdateMember;
7+
using GymMgmt.Application.Features.Memberships.UpdateMembershipPlan;
68
using MediatR;
79
using Microsoft.AspNetCore.Authorization;
810
using Microsoft.AspNetCore.Mvc;
@@ -57,6 +59,20 @@ public async Task<ActionResult> GetAllMembers()
5759
return Ok(ApiResponse<IEnumerable<ReadMemberDto>>.Success(result));
5860
}
5961

62+
[AllowAnonymous]
63+
[HttpPut("{id:Guid}")]
64+
[ProducesResponseType(typeof(ApiResponse<ReadMemberDto>), (int)HttpStatusCode.OK)]
65+
[ProducesResponseType(typeof(ApiResponse<object>), (int)HttpStatusCode.BadRequest)]
66+
public async Task<ActionResult> UpdateMember(Guid id,UpdateMemberCommand updateMemberCommand)
67+
{
68+
if (updateMemberCommand.MemberId != id)
69+
{
70+
return BadRequest(ApiResponse<object>.Fail("Route ID and body ID mismatch"));
71+
}
72+
var result = await _mediator.Send(updateMemberCommand);
73+
return Ok(ApiResponse<ReadMemberDto>.Success(result));
74+
}
75+
6076
[AllowAnonymous]
6177
[HttpPost("PayInsurance")]
6278
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using GymMgmt.Api.Middlewares.Responses;
2+
using GymMgmt.Application.Features.Subscriptions.CancelSubscription;
3+
using GymMgmt.Application.Features.Subscriptions.ExtendSubscription;
4+
using GymMgmt.Application.Features.Subscriptions.StartSubscription;
5+
using MediatR;
6+
using Microsoft.AspNetCore.Authorization;
7+
using Microsoft.AspNetCore.Mvc;
8+
using System.Net;
9+
10+
namespace GymMgmt.Api.Controllers.Subscriptions
11+
{
12+
[Route("api/[controller]")]
13+
[ApiController]
14+
public class SubscriptionsController : ControllerBase
15+
{
16+
private readonly IMediator _mediator;
17+
18+
public SubscriptionsController(IMediator mediator)
19+
{
20+
_mediator = mediator;
21+
}
22+
23+
[AllowAnonymous] // TODO: Secure this endpoint
24+
[HttpPost("Start")]
25+
[ProducesResponseType(typeof(ApiResponse<Guid>), (int)HttpStatusCode.OK)]
26+
[ProducesResponseType(typeof(ApiResponse<object>), (int)HttpStatusCode.BadRequest)]
27+
public async Task<ActionResult> StartSubscription([FromBody] StartSubscriptionCommand command)
28+
{
29+
var result = await _mediator.Send(command);
30+
return Ok(ApiResponse<Guid>.Success(result, "Subscription started successfully"));
31+
}
32+
33+
[AllowAnonymous] // TODO: Secure this endpoint
34+
[HttpPost("Extend")]
35+
[ProducesResponseType(typeof(ApiResponse<bool>), (int)HttpStatusCode.OK)]
36+
[ProducesResponseType(typeof(ApiResponse<object>), (int)HttpStatusCode.BadRequest)]
37+
public async Task<ActionResult> ExtendSubscription([FromBody] ExtendSubscriptionCommand command)
38+
{
39+
var result = await _mediator.Send(command);
40+
return Ok(ApiResponse<bool>.Success(result, "Subscription extended successfully"));
41+
}
42+
43+
[AllowAnonymous] // TODO: Secure this endpoint
44+
[HttpPost("Cancel")]
45+
[ProducesResponseType(typeof(ApiResponse<bool>), (int)HttpStatusCode.OK)]
46+
[ProducesResponseType(typeof(ApiResponse<object>), (int)HttpStatusCode.BadRequest)]
47+
public async Task<ActionResult> CancelSubscription([FromBody] CancelSubscriptionCommand command)
48+
{
49+
var result = await _mediator.Send(command);
50+
return Ok(ApiResponse<bool>.Success(result, "Subscription cancelled successfully"));
51+
}
52+
}
53+
}

GymMgmt.Api/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,16 +111,16 @@ public static async Task Main(string[] args)
111111
}
112112
app.UseRouting();
113113
app.UseCors(DefaultCorsPolicyName);
114+
114115

116+
app.UseExceptionsHandlingMiddleware();
115117
app.UseHttpsRedirection();
116118

117119
app.UseAuthentication();
118120
app.UseAuthorization();
119121

120122

121123

122-
app.UseExceptionsHandlingMiddleware();
123-
124124
using (var scope = app.Services.CreateScope())
125125
{
126126
var roleInitializer = scope.ServiceProvider.GetRequiredService<IRoleInitializer>();
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace GymMgmt.Application.Common.Models
8+
{
9+
public sealed record PaginatedList<T>
10+
{
11+
public List<T> Items { get; init; } = new();
12+
public int PageNumber { get; init; }
13+
public int PageSize { get; init; }
14+
public int TotalCount { get; init; }
15+
public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize);
16+
public bool HasPreviousPage => PageNumber > 1;
17+
public bool HasNextPage => PageNumber < TotalPages;
18+
}
19+
}

GymMgmt.Application/Features/ClubSetup/UpdateClubSettings/UpdateClubSettingsCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
namespace GymMgmt.Application.Features.ClubSetup.UpdateClubSettings
77
{
88
public sealed record UpdateClubSettingsCommand(
9-
ClubSettingsId ClubSettingsId,
9+
Guid ClubSettingsId,
1010
decimal InsurranceFeeAmount,
1111
bool AreNewMembersAllowed,
1212
bool IsInsuranceFeeRequired,

GymMgmt.Application/Features/ClubSetup/UpdateClubSettings/UpdateClubSettingsCommandHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public UpdateClubSettingsCommandHandler(IClubSettingsRepository clubSettingsRepo
2525
}
2626
public async Task<ReadClubSettingsDto> Handle(UpdateClubSettingsCommand request, CancellationToken cancellationToken)
2727
{
28-
var existingSettings = await _clubSettingsRepository.FindByIdAsync(request.ClubSettingsId, cancellationToken);
28+
var existingSettings = await _clubSettingsRepository.FindByIdAsync(ClubSettingsId.FromValue(request.ClubSettingsId), cancellationToken);
2929

3030
if (existingSettings == null) {
3131
throw new NotFoundException();

GymMgmt.Application/Features/ClubSetup/UpdateClubSettings/UpdateClubSettingsValidator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ public UpdateClubSettingsValidator(IClubSettingsRepository repository) {
3232
.WithMessage("Subscription Grace Period must be positive");
3333

3434
}
35-
private async Task<bool> ExistAsync(ClubSettingsId clubSettingsId, CancellationToken cancellationToken)
35+
private async Task<bool> ExistAsync(Guid clubSettingsId, CancellationToken cancellationToken)
3636
{
37-
var exists = await _repository.FindByIdAsync( clubSettingsId,cancellationToken);
37+
var exists = await _repository.FindByIdAsync(ClubSettingsId.FromValue(clubSettingsId),cancellationToken);
3838
return exists !=null ;
3939
}
4040
}

GymMgmt.Application/Features/Members/UpdateMember/UpdateMemberCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
namespace GymMgmt.Application.Features.Members.UpdateMember
77
{
88
public sealed record UpdateMemberCommand(
9-
Guid memberId,
9+
Guid MemberId,
1010
string FirstName,
1111
string LastName,
1212
string? Email,

GymMgmt.Application/Features/Members/UpdateMember/UpdateMemberCommandHandler.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using AutoMapper;
22
using GymMgmt.Application.Common.Exceptions;
33
using GymMgmt.Application.Features.Members.GetMemberById;
4+
using GymMgmt.Domain.Common.ValueObjects;
45
using GymMgmt.Domain.Entities.Members;
56
using MediatR;
67
using System;
@@ -24,15 +25,15 @@ public UpdateMemberCommandHandler(IMemberRepository memberRepository, IMapper ma
2425
}
2526
public async Task<ReadMemberDto> Handle(UpdateMemberCommand request, CancellationToken cancellationToken)
2627
{
27-
var memberid = MemberId.FromValue(request.memberId);
28+
var memberid = MemberId.FromValue(request.MemberId);
2829

2930
var member = await _memberRepository.FindByIdAsync(memberid, cancellationToken)
3031
?? throw new NotFoundException(nameof(Member), memberid);
3132

3233
member.UpdateName(request.FirstName, request.LastName);
3334
member.UpdatePhoneNumber(request.PhoneNumber);
34-
member.UpdateAddress(member.Address);
35-
member.UpdateEmail(member.Email);
35+
member.UpdateAddress(_mapper.Map<Address>(request.AddressDto));
36+
member.UpdateEmail(request.Email);
3637

3738
return _mapper.Map<ReadMemberDto>(member);
3839
}

0 commit comments

Comments
 (0)