feat(T4.13): add PackageFeature CRUD support
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 9m41s

- Proto: add feature_ids to Create/Update/Get package messages
- Commands: add FeatureIds to Create/Update commands
- Handlers: sync PackageFeature join entities on create/update
- Queries: include PackageFeatures, map FeatureIds in responses
- Bump NuGet to v0.0.188
This commit is contained in:
masoodafar-web
2026-02-26 21:59:24 +03:30
parent aaaf7fc1ca
commit dcd11351a3
10 changed files with 69 additions and 7 deletions
@@ -22,4 +22,5 @@ public record CreateNewPackageCommand : IRequest<CreateNewPackageResponseDto>
public int MaxNetworkLevel { get; init; } = 15;
public long MagicWalletMaxDeposit { get; init; } = 1_000_000_000;
public long MagicWalletMaxCredit { get; init; } = 2_500_000_000;
public List<long> FeatureIds { get; init; } = new();
}
@@ -16,6 +16,22 @@ public class CreateNewPackageCommandHandler : IRequestHandler<CreateNewPackageCo
await _context.Packages.AddAsync(entity, cancellationToken);
entity.AddDomainEvent(new CreateNewPackageEvent(entity));
await _context.SaveChangesAsync(cancellationToken);
// Sync PackageFeatures
if (request.FeatureIds.Any())
{
foreach (var featureId in request.FeatureIds)
{
_context.PackageFeatures.Add(new PackageFeature
{
PackageId = entity.Id,
ClubFeatureId = featureId,
IsIncluded = true
});
}
await _context.SaveChangesAsync(cancellationToken);
}
return entity.Adapt<CreateNewPackageResponseDto>();
}
}
@@ -24,4 +24,5 @@ public record UpdatePackageCommand : IRequest<Unit>
public int MaxNetworkLevel { get; init; }
public long MagicWalletMaxDeposit { get; init; }
public long MagicWalletMaxCredit { get; init; }
public List<long> FeatureIds { get; init; } = new();
}
@@ -16,6 +16,23 @@ public class UpdatePackageCommandHandler : IRequestHandler<UpdatePackageCommand,
request.Adapt(entity);
_context.Packages.Update(entity);
entity.AddDomainEvent(new UpdatePackageEvent(entity));
// Sync PackageFeatures — remove old, add new
var existingFeatures = await _context.PackageFeatures
.Where(pf => pf.PackageId == request.Id)
.ToListAsync(cancellationToken);
_context.PackageFeatures.RemoveRange(existingFeatures);
foreach (var featureId in request.FeatureIds)
{
_context.PackageFeatures.Add(new PackageFeature
{
PackageId = request.Id,
ClubFeatureId = featureId,
IsIncluded = true
});
}
await _context.SaveChangesAsync(cancellationToken);
return Unit.Value;
}
@@ -14,6 +14,7 @@ public class GetAllPackageByFilterQueryHandler : IRequestHandler<GetAllPackageBy
.Where(x => !x.IsDeleted)
.ApplyOrder(sortBy: request.SortBy)
.AsNoTracking()
.Include(x => x.PackageFeatures)
.AsQueryable();
if (request.Filter is not null)
{
@@ -25,11 +26,24 @@ public class GetAllPackageByFilterQueryHandler : IRequestHandler<GetAllPackageBy
.Where(x => request.Filter.Price == null || x.Price == request.Filter.Price)
;
}
var packages = await query
.PaginatedListAsync(paginationState: request.PaginationState)
.ToListAsync(cancellationToken);
var models = packages.Select(p =>
{
var model = p.Adapt<GetAllPackageByFilterResponseModel>();
model.FeatureIds = p.PackageFeatures?
.Where(pf => pf.IsIncluded)
.Select(pf => pf.ClubFeatureId)
.ToList() ?? new();
return model;
}).ToList();
return new GetAllPackageByFilterResponseDto
{
MetaData = await query.GetMetaData(request.PaginationState, cancellationToken),
Models = await query.PaginatedListAsync(paginationState: request.PaginationState)
.ProjectToType<GetAllPackageByFilterResponseModel>().ToListAsync(cancellationToken)
Models = models
};
}
}
@@ -31,4 +31,5 @@ public class GetAllPackageByFilterResponseDto
public int MaxNetworkLevel { get; set; }
public long MagicWalletMaxDeposit { get; set; }
public long MagicWalletMaxCredit { get; set; }
public List<long> FeatureIds { get; set; } = new();
}
@@ -11,12 +11,19 @@ public class GetPackageQueryHandler : IRequestHandler<GetPackageQuery, GetPackag
public async Task<GetPackageResponseDto> Handle(GetPackageQuery request,
CancellationToken cancellationToken)
{
var response = await _context.Packages
var package = await _context.Packages
.AsNoTracking()
.Include(x => x.PackageFeatures)
.Where(x => x.Id == request.Id)
.ProjectToType<GetPackageResponseDto>()
.FirstOrDefaultAsync(cancellationToken);
.FirstOrDefaultAsync(cancellationToken)
?? throw new NotFoundException(nameof(Package), request.Id);
return response ?? throw new NotFoundException(nameof(Package), request.Id);
var response = package.Adapt<GetPackageResponseDto>();
response.FeatureIds = package.PackageFeatures?
.Where(pf => pf.IsIncluded)
.Select(pf => pf.ClubFeatureId)
.ToList() ?? new();
return response;
}
}
@@ -24,4 +24,5 @@ public class GetPackageResponseDto
public int MaxNetworkLevel { get; set; }
public long MagicWalletMaxDeposit { get; set; }
public long MagicWalletMaxCredit { get; set; }
public List<long> FeatureIds { get; set; } = new();
}