fix: перенёс уровни в бд и пофиксид их отображение на фронте
Backend CI / build-and-test (push) Successful in 52s
Frontend CI / build-and-check (push) Failing after 5m15s
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 16s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 1m0s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Successful in 32s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 13s
Backend CI / build-and-test (push) Successful in 52s
Frontend CI / build-and-check (push) Failing after 5m15s
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 16s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 1m0s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Successful in 32s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 13s
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using NSubstitute;
|
||||
using UniVerse.Application.DTOs.Notifications;
|
||||
@@ -18,6 +17,7 @@ public class GamificationServiceTests
|
||||
public async Task CheckAndAwardAchievementsAsync_AwardsModernConditionsOnce()
|
||||
{
|
||||
await using var db = CreateDbContext();
|
||||
SeedLevelThresholds(db);
|
||||
var service = CreateService(db);
|
||||
|
||||
db.Users.Add(new User
|
||||
@@ -78,6 +78,7 @@ public class GamificationServiceTests
|
||||
public async Task CheckAndAwardAchievementsAsync_CountsConsecutiveIsoWeeksAcrossYears()
|
||||
{
|
||||
await using var db = CreateDbContext();
|
||||
SeedLevelThresholds(db);
|
||||
var service = CreateService(db);
|
||||
|
||||
db.Users.Add(new User { Id = 1, Email = "student@test.local" });
|
||||
@@ -98,6 +99,38 @@ public class GamificationServiceTests
|
||||
Assert.True(await db.UserAchievements.AnyAsync(ua => ua.UserId == 1 && ua.AchievementId == 1001));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 1)]
|
||||
[InlineData(99, 1)]
|
||||
[InlineData(100, 2)]
|
||||
[InlineData(299, 2)]
|
||||
[InlineData(300, 3)]
|
||||
public async Task CalculateLevelAsync_UsesDatabaseThresholds(int xp, int expectedLevel)
|
||||
{
|
||||
await using var db = CreateDbContext();
|
||||
SeedLevelThresholds(db);
|
||||
var service = CreateService(db);
|
||||
|
||||
var level = await service.CalculateLevelAsync(xp);
|
||||
|
||||
Assert.Equal(expectedLevel, level);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(120, 100, 300)]
|
||||
[InlineData(350, 300, null)]
|
||||
public async Task GetLevelProgressAsync_ReturnsCurrentAndNextThresholds(int xp, int currentLevelXp, int? nextLevelXp)
|
||||
{
|
||||
await using var db = CreateDbContext();
|
||||
SeedLevelThresholds(db);
|
||||
var service = CreateService(db);
|
||||
|
||||
var progress = await service.GetLevelProgressAsync(xp);
|
||||
|
||||
Assert.Equal(currentLevelXp, progress.CurrentLevelXp);
|
||||
Assert.Equal(nextLevelXp, progress.NextLevelXp);
|
||||
}
|
||||
|
||||
private static AppDbContext CreateDbContext()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<AppDbContext>()
|
||||
@@ -119,16 +152,16 @@ public class GamificationServiceTests
|
||||
notifications.SendAsync(Arg.Any<NotificationMessage>(), Arg.Any<CancellationToken>())
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string?>
|
||||
{
|
||||
["Gamification:XpThresholds:0"] = "0",
|
||||
["Gamification:XpThresholds:1"] = "100",
|
||||
["Gamification:XpThresholds:2"] = "300"
|
||||
})
|
||||
.Build();
|
||||
return new GamificationService(db, notifications, NullLogger<GamificationService>.Instance);
|
||||
}
|
||||
|
||||
return new GamificationService(db, configuration, notifications, NullLogger<GamificationService>.Instance);
|
||||
private static void SeedLevelThresholds(AppDbContext db)
|
||||
{
|
||||
db.LevelThresholds.AddRange(
|
||||
new LevelThreshold { Level = 1, RequiredXp = 0 },
|
||||
new LevelThreshold { Level = 2, RequiredXp = 100 },
|
||||
new LevelThreshold { Level = 3, RequiredXp = 300 });
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
private static Achievement Achievement(int id, string name, string condition, int coinReward) => new()
|
||||
|
||||
@@ -156,7 +156,7 @@ public class ApiWebApplicationFactory : WebApplicationFactory<Program>
|
||||
|
||||
stub.GetByIdAsync(Arg.Any<int>()).Returns(userDto);
|
||||
stub.UpdateProfileAsync(Arg.Any<int>(), Arg.Any<UpdateUserRequest>()).Returns(userDto);
|
||||
stub.GetStatsAsync(Arg.Any<int>()).Returns(new UserStatsDto(0, 0, 0, 0, 0, 1, 0));
|
||||
stub.GetStatsAsync(Arg.Any<int>()).Returns(new UserStatsDto(0, 0, 0, 0, 0, 1, 0, 0, 100));
|
||||
stub.GetAllAsync(Arg.Any<UserFilterRequest>()).Returns(pagedUsers);
|
||||
stub.SetRolesAsync(Arg.Any<int>(), Arg.Any<IReadOnlyCollection<UserRole>>()).Returns(Task.CompletedTask);
|
||||
stub.SetActiveAsync(Arg.Any<int>(), Arg.Any<bool>()).Returns(Task.CompletedTask);
|
||||
@@ -274,7 +274,8 @@ public class ApiWebApplicationFactory : WebApplicationFactory<Program>
|
||||
stub.AwardCoinsAsync(Arg.Any<int>(), Arg.Any<int>(), Arg.Any<CoinTransactionType>(),
|
||||
Arg.Any<int?>(), Arg.Any<int?>(), Arg.Any<string?>()).Returns(Task.CompletedTask);
|
||||
stub.CheckAndAwardAchievementsAsync(Arg.Any<int>()).Returns(Task.CompletedTask);
|
||||
stub.CalculateLevel(Arg.Any<int>()).Returns(1);
|
||||
stub.CalculateLevelAsync(Arg.Any<int>()).Returns(Task.FromResult(1));
|
||||
stub.GetLevelProgressAsync(Arg.Any<int>()).Returns(Task.FromResult(new LevelProgressDto(0, 100)));
|
||||
return stub;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using NSubstitute;
|
||||
using UniVerse.Application.DTOs.Notifications;
|
||||
using UniVerse.Application.Interfaces;
|
||||
using UniVerse.Domain.Entities;
|
||||
using UniVerse.Infrastructure.Data;
|
||||
using UniVerse.Infrastructure.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace UniVerse.Api.Tests.Users;
|
||||
|
||||
public class UserServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task GetStatsAsync_ReturnsLevelProgressThresholds()
|
||||
{
|
||||
await using var db = CreateDbContext();
|
||||
SeedLevelThresholds(db);
|
||||
db.Users.Add(new User { Id = 1, Email = "student@test.local", Xp = 120 });
|
||||
await db.SaveChangesAsync();
|
||||
var service = CreateService(db);
|
||||
|
||||
var stats = await service.GetStatsAsync(1);
|
||||
|
||||
Assert.Equal(2, stats.Level);
|
||||
Assert.Equal(100, stats.CurrentLevelXp);
|
||||
Assert.Equal(300, stats.NextLevelXp);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetStatsAsync_ReturnsNullNextLevelAtMaxConfiguredLevel()
|
||||
{
|
||||
await using var db = CreateDbContext();
|
||||
SeedLevelThresholds(db);
|
||||
db.Users.Add(new User { Id = 1, Email = "student@test.local", Xp = 350 });
|
||||
await db.SaveChangesAsync();
|
||||
var service = CreateService(db);
|
||||
|
||||
var stats = await service.GetStatsAsync(1);
|
||||
|
||||
Assert.Equal(3, stats.Level);
|
||||
Assert.Equal(300, stats.CurrentLevelXp);
|
||||
Assert.Null(stats.NextLevelXp);
|
||||
}
|
||||
|
||||
private static AppDbContext CreateDbContext()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<AppDbContext>()
|
||||
.UseInMemoryDatabase($"UserServiceTests_{Guid.NewGuid()}")
|
||||
.Options;
|
||||
return new AppDbContext(options);
|
||||
}
|
||||
|
||||
private static UserService CreateService(AppDbContext db)
|
||||
{
|
||||
var notifications = Substitute.For<INotificationService>();
|
||||
notifications.CreateUserNotificationAsync(
|
||||
Arg.Any<int>(),
|
||||
Arg.Any<string>(),
|
||||
Arg.Any<string>(),
|
||||
Arg.Any<string>(),
|
||||
Arg.Any<CancellationToken>())
|
||||
.Returns(callInfo => new UserNotificationDto(1, callInfo.ArgAt<string>(1), callInfo.ArgAt<string>(2), callInfo.ArgAt<string>(3), false, DateTime.UtcNow));
|
||||
notifications.SendAsync(Arg.Any<NotificationMessage>(), Arg.Any<CancellationToken>())
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var gamification = new GamificationService(db, notifications, NullLogger<GamificationService>.Instance);
|
||||
return new UserService(db, gamification);
|
||||
}
|
||||
|
||||
private static void SeedLevelThresholds(AppDbContext db)
|
||||
{
|
||||
db.LevelThresholds.AddRange(
|
||||
new LevelThreshold { Level = 1, RequiredXp = 0 },
|
||||
new LevelThreshold { Level = 2, RequiredXp = 100 },
|
||||
new LevelThreshold { Level = 3, RequiredXp = 300 });
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
@@ -22,9 +22,6 @@
|
||||
"BaseUrl": "https://schedule.rdcenter.ru",
|
||||
"ApiKey": ""
|
||||
},
|
||||
"Gamification": {
|
||||
"XpThresholds": [0, 100, 300, 600, 1000, 1500, 2500, 4000]
|
||||
},
|
||||
"Serilog": {
|
||||
"MinimumLevel": {
|
||||
"Default": "Information",
|
||||
|
||||
@@ -5624,6 +5624,15 @@
|
||||
"achievementsCount": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"currentLevelXp": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"nextLevelXp": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
||||
@@ -11,3 +11,8 @@ public record CoinTransactionDto(
|
||||
string? Description,
|
||||
DateTime CreatedAt
|
||||
);
|
||||
|
||||
public record LevelProgressDto(
|
||||
int CurrentLevelXp,
|
||||
int? NextLevelXp
|
||||
);
|
||||
|
||||
@@ -22,7 +22,9 @@ public record UserStatsDto(
|
||||
int Xp,
|
||||
int Coins,
|
||||
int Level,
|
||||
int AchievementsCount
|
||||
int AchievementsCount,
|
||||
int CurrentLevelXp,
|
||||
int? NextLevelXp
|
||||
);
|
||||
|
||||
public record UpdateUserRequest(
|
||||
|
||||
@@ -10,7 +10,8 @@ public interface IGamificationService
|
||||
Task AwardCoinsAsync(int userId, int amount, CoinTransactionType type,
|
||||
int? reviewId = null, int? achievementId = null, string? description = null);
|
||||
Task CheckAndAwardAchievementsAsync(int userId);
|
||||
int CalculateLevel(int xp);
|
||||
Task<int> CalculateLevelAsync(int xp);
|
||||
Task<LevelProgressDto> GetLevelProgressAsync(int xp);
|
||||
Task<List<UserAchievementDto>> GetUserAchievementsAsync(int userId);
|
||||
Task<PagedResult<CoinTransactionDto>> GetTransactionsAsync(int userId, PaginationRequest pagination);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace UniVerse.Domain.Entities;
|
||||
|
||||
public class LevelThreshold
|
||||
{
|
||||
public int Level { get; set; }
|
||||
public int RequiredXp { get; set; }
|
||||
}
|
||||
@@ -23,6 +23,7 @@ public class AppDbContext : DbContext
|
||||
public DbSet<Achievement> Achievements { get; set; } = null!;
|
||||
public DbSet<UserAchievement> UserAchievements { get; set; } = null!;
|
||||
public DbSet<CoinTransaction> CoinTransactions { get; set; } = null!;
|
||||
public DbSet<LevelThreshold> LevelThresholds { get; set; } = null!;
|
||||
public DbSet<UserNotification> UserNotifications { get; set; } = null!;
|
||||
public DbSet<RefreshToken> RefreshTokens { get; set; } = null!;
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using UniVerse.Domain.Entities;
|
||||
|
||||
namespace UniVerse.Infrastructure.Data.Configurations;
|
||||
|
||||
public class LevelThresholdConfiguration : IEntityTypeConfiguration<LevelThreshold>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<LevelThreshold> builder)
|
||||
{
|
||||
builder.ToTable("level_thresholds", table =>
|
||||
{
|
||||
table.HasCheckConstraint("CK_level_thresholds_level_positive", "level > 0");
|
||||
table.HasCheckConstraint("CK_level_thresholds_required_xp_non_negative", "required_xp >= 0");
|
||||
});
|
||||
|
||||
builder.HasKey(t => t.Level);
|
||||
builder.Property(t => t.Level).HasColumnName("level").ValueGeneratedNever();
|
||||
builder.Property(t => t.RequiredXp).HasColumnName("required_xp").IsRequired();
|
||||
builder.HasIndex(t => t.RequiredXp).IsUnique();
|
||||
|
||||
builder.HasData(
|
||||
new LevelThreshold { Level = 1, RequiredXp = 0 },
|
||||
new LevelThreshold { Level = 2, RequiredXp = 100 },
|
||||
new LevelThreshold { Level = 3, RequiredXp = 300 },
|
||||
new LevelThreshold { Level = 4, RequiredXp = 600 },
|
||||
new LevelThreshold { Level = 5, RequiredXp = 1000 },
|
||||
new LevelThreshold { Level = 6, RequiredXp = 1500 },
|
||||
new LevelThreshold { Level = 7, RequiredXp = 2500 },
|
||||
new LevelThreshold { Level = 8, RequiredXp = 4000 }
|
||||
);
|
||||
}
|
||||
}
|
||||
+1107
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,58 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
|
||||
|
||||
namespace UniVerse.Infrastructure.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class LevelThresholds : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "level_thresholds",
|
||||
columns: table => new
|
||||
{
|
||||
level = table.Column<int>(type: "integer", nullable: false),
|
||||
required_xp = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_level_thresholds", x => x.level);
|
||||
table.CheckConstraint("CK_level_thresholds_level_positive", "level > 0");
|
||||
table.CheckConstraint("CK_level_thresholds_required_xp_non_negative", "required_xp >= 0");
|
||||
});
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "level_thresholds",
|
||||
columns: new[] { "level", "required_xp" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ 1, 0 },
|
||||
{ 2, 100 },
|
||||
{ 3, 300 },
|
||||
{ 4, 600 },
|
||||
{ 5, 1000 },
|
||||
{ 6, 1500 },
|
||||
{ 7, 2500 },
|
||||
{ 8, 4000 }
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_level_thresholds_required_xp",
|
||||
table: "level_thresholds",
|
||||
column: "required_xp",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "level_thresholds");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -334,6 +334,71 @@ namespace UniVerse.Infrastructure.Migrations
|
||||
b.ToTable("lecture_enrollments", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("UniVerse.Domain.Entities.LevelThreshold", b =>
|
||||
{
|
||||
b.Property<int>("Level")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("level");
|
||||
|
||||
b.Property<int>("RequiredXp")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("required_xp");
|
||||
|
||||
b.HasKey("Level");
|
||||
|
||||
b.HasIndex("RequiredXp")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("level_thresholds", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("CK_level_thresholds_level_positive", "level > 0");
|
||||
|
||||
t.HasCheckConstraint("CK_level_thresholds_required_xp_non_negative", "required_xp >= 0");
|
||||
});
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Level = 1,
|
||||
RequiredXp = 0
|
||||
},
|
||||
new
|
||||
{
|
||||
Level = 2,
|
||||
RequiredXp = 100
|
||||
},
|
||||
new
|
||||
{
|
||||
Level = 3,
|
||||
RequiredXp = 300
|
||||
},
|
||||
new
|
||||
{
|
||||
Level = 4,
|
||||
RequiredXp = 600
|
||||
},
|
||||
new
|
||||
{
|
||||
Level = 5,
|
||||
RequiredXp = 1000
|
||||
},
|
||||
new
|
||||
{
|
||||
Level = 6,
|
||||
RequiredXp = 1500
|
||||
},
|
||||
new
|
||||
{
|
||||
Level = 7,
|
||||
RequiredXp = 2500
|
||||
},
|
||||
new
|
||||
{
|
||||
Level = 8,
|
||||
RequiredXp = 4000
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("UniVerse.Domain.Entities.Location", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
||||
@@ -221,7 +221,7 @@ public class AuthService : IAuthService
|
||||
.Include(u => u.Roles)
|
||||
.FirstOrDefaultAsync(u => u.Id == userId)
|
||||
?? throw new NotFoundException("User", userId);
|
||||
return user.ToDto(_gamification.CalculateLevel(user.Xp));
|
||||
return user.ToDto(await _gamification.CalculateLevelAsync(user.Xp));
|
||||
}
|
||||
|
||||
private async Task TrySendLoginNotificationAsync(User user, string? ipAddress)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Globalization;
|
||||
using UniVerse.Application.DTOs.Achievements;
|
||||
@@ -17,17 +16,16 @@ namespace UniVerse.Infrastructure.Services;
|
||||
public class GamificationService : IGamificationService
|
||||
{
|
||||
private readonly AppDbContext _db;
|
||||
private readonly IConfiguration _config;
|
||||
private readonly INotificationService _notifications;
|
||||
private readonly ILogger<GamificationService> _logger;
|
||||
private List<LevelThreshold>? _levelThresholds;
|
||||
|
||||
public GamificationService(
|
||||
AppDbContext db,
|
||||
IConfiguration config,
|
||||
INotificationService notifications,
|
||||
ILogger<GamificationService> logger)
|
||||
{
|
||||
_db = db; _config = config; _notifications = notifications; _logger = logger;
|
||||
_db = db; _notifications = notifications; _logger = logger;
|
||||
}
|
||||
|
||||
public async Task AwardCoinsAsync(int userId, int amount, CoinTransactionType type,
|
||||
@@ -83,7 +81,7 @@ public class GamificationService : IGamificationService
|
||||
"attendance_streak_weeks" => attendanceStreakWeeks >= value,
|
||||
"attended_registered" => attended >= value,
|
||||
"coins_earned" => earnedCoins >= value,
|
||||
"level_reached" => CalculateLevel(user.Xp) >= value,
|
||||
"level_reached" => await CalculateLevelAsync(user.Xp) >= value,
|
||||
"profile_completed" => profileCompleted && value <= 1,
|
||||
"first_activity" => firstActivity && value <= 1,
|
||||
_ => false
|
||||
@@ -184,13 +182,47 @@ public class GamificationService : IGamificationService
|
||||
return DateOnly.FromDateTime(ISOWeek.ToDateTime(isoYear, isoWeek, DayOfWeek.Monday));
|
||||
}
|
||||
|
||||
public int CalculateLevel(int xp)
|
||||
public async Task<int> CalculateLevelAsync(int xp)
|
||||
{
|
||||
var thresholds = _config.GetSection("Gamification:XpThresholds").Get<int[]>()
|
||||
?? [0, 100, 300, 600, 1000, 1500, 2500, 4000];
|
||||
for (int i = thresholds.Length - 1; i >= 0; i--)
|
||||
if (xp >= thresholds[i]) return i + 1;
|
||||
return 1;
|
||||
var thresholds = await GetLevelThresholdsAsync();
|
||||
return thresholds
|
||||
.Where(t => xp >= t.RequiredXp)
|
||||
.OrderBy(t => t.RequiredXp)
|
||||
.ThenBy(t => t.Level)
|
||||
.LastOrDefault()?.Level ?? thresholds[0].Level;
|
||||
}
|
||||
|
||||
public async Task<LevelProgressDto> GetLevelProgressAsync(int xp)
|
||||
{
|
||||
var thresholds = await GetLevelThresholdsAsync();
|
||||
var current = thresholds
|
||||
.Where(t => xp >= t.RequiredXp)
|
||||
.OrderBy(t => t.RequiredXp)
|
||||
.ThenBy(t => t.Level)
|
||||
.LastOrDefault() ?? thresholds[0];
|
||||
var next = thresholds
|
||||
.Where(t => t.RequiredXp > current.RequiredXp)
|
||||
.OrderBy(t => t.RequiredXp)
|
||||
.ThenBy(t => t.Level)
|
||||
.FirstOrDefault();
|
||||
|
||||
return new LevelProgressDto(current.RequiredXp, next?.RequiredXp);
|
||||
}
|
||||
|
||||
private async Task<List<LevelThreshold>> GetLevelThresholdsAsync()
|
||||
{
|
||||
if (_levelThresholds is { Count: > 0 }) return _levelThresholds;
|
||||
|
||||
_levelThresholds = await _db.LevelThresholds
|
||||
.AsNoTracking()
|
||||
.OrderBy(t => t.RequiredXp)
|
||||
.ThenBy(t => t.Level)
|
||||
.ToListAsync();
|
||||
|
||||
if (_levelThresholds.Count == 0)
|
||||
_levelThresholds.Add(new LevelThreshold { Level = 1, RequiredXp = 0 });
|
||||
|
||||
return _levelThresholds;
|
||||
}
|
||||
|
||||
public async Task<List<UserAchievementDto>> GetUserAchievementsAsync(int userId) =>
|
||||
|
||||
@@ -26,7 +26,7 @@ public class UserService : IUserService
|
||||
.Include(u => u.Roles)
|
||||
.FirstOrDefaultAsync(u => u.Id == id)
|
||||
?? throw new NotFoundException("User", id);
|
||||
return user.ToDto(_gamification.CalculateLevel(user.Xp));
|
||||
return user.ToDto(await _gamification.CalculateLevelAsync(user.Xp));
|
||||
}
|
||||
|
||||
public async Task<UserDto> UpdateProfileAsync(int id, UpdateUserRequest request)
|
||||
@@ -42,7 +42,7 @@ public class UserService : IUserService
|
||||
|
||||
await _db.SaveChangesAsync();
|
||||
await _gamification.CheckAndAwardAchievementsAsync(id);
|
||||
return user.ToDto(_gamification.CalculateLevel(user.Xp));
|
||||
return user.ToDto(await _gamification.CalculateLevelAsync(user.Xp));
|
||||
}
|
||||
|
||||
public async Task<UserStatsDto> GetStatsAsync(int id)
|
||||
@@ -55,9 +55,13 @@ public class UserService : IUserService
|
||||
var reviews = await _db.Reviews.CountAsync(r => r.UserId == id);
|
||||
var achievements = await _db.UserAchievements.CountAsync(ua => ua.UserId == id);
|
||||
|
||||
var level = await _gamification.CalculateLevelAsync(user.Xp);
|
||||
var levelProgress = await _gamification.GetLevelProgressAsync(user.Xp);
|
||||
|
||||
return new UserStatsDto(
|
||||
totalLectures, attended, reviews,
|
||||
user.Xp, user.Coins, _gamification.CalculateLevel(user.Xp), achievements
|
||||
user.Xp, user.Coins, level, achievements,
|
||||
levelProgress.CurrentLevelXp, levelProgress.NextLevelXp
|
||||
);
|
||||
}
|
||||
|
||||
@@ -94,7 +98,10 @@ public class UserService : IUserService
|
||||
.Take(filter.PageSize)
|
||||
.ToListAsync();
|
||||
|
||||
var items = users.Select(u => u.ToDto(_gamification.CalculateLevel(u.Xp))).ToList();
|
||||
var items = new List<UserDto>(users.Count);
|
||||
foreach (var user in users)
|
||||
items.Add(user.ToDto(await _gamification.CalculateLevelAsync(user.Xp)));
|
||||
|
||||
return PagedResult<UserDto>.Create(items, total, filter.Page, filter.PageSize);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user