< Summary

Information
Class: Chronicis.Api.Services.UserService
Assembly: Chronicis.Api
File(s): /home/runner/work/chronicis/chronicis/src/Chronicis.Api/Services/UserService.cs
Line coverage
90%
Covered lines: 72
Uncovered lines: 8
Coverable lines: 80
Total lines: 165
Line coverage: 90%
Branch coverage
100%
Covered branches: 18
Total branches: 18
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetOrCreateUserAsync()100%1010100%
GetUserByIdAsync()100%11100%
UpdateLastLoginAsync()100%22100%
GetUserProfileAsync()100%22100%
CompleteOnboardingAsync()100%44100%
CreateDefaultWorldAsync()100%210%

File(s)

/home/runner/work/chronicis/chronicis/src/Chronicis.Api/Services/UserService.cs

#LineLine coverage
 1using Chronicis.Api.Data;
 2using Chronicis.Shared.DTOs;
 3using Chronicis.Shared.Models;
 4using Microsoft.EntityFrameworkCore;
 5
 6namespace Chronicis.Api.Services;
 7
 8/// <summary>
 9/// Implementation of user management service
 10/// </summary>
 11public class UserService : IUserService
 12{
 13    private readonly ChronicisDbContext _context;
 14    private readonly IWorldService _worldService;
 15    private readonly ILogger<UserService> _logger;
 16
 1317    public UserService(
 1318        ChronicisDbContext context,
 1319        IWorldService worldService,
 1320        ILogger<UserService> logger)
 21    {
 1322        _context = context;
 1323        _worldService = worldService;
 1324        _logger = logger;
 1325    }
 26
 27    public async Task<User> GetOrCreateUserAsync(
 28        string auth0UserId,
 29        string email,
 30        string displayName,
 31        string? avatarUrl)
 32    {
 33        // Try to find existing user
 1234        var user = await _context.Users
 1235            .FirstOrDefaultAsync(u => u.Auth0UserId == auth0UserId);
 36
 1237        if (user == null)
 38        {
 39            // Create new user
 940            user = new User
 941            {
 942                Id = Guid.NewGuid(),
 943                Auth0UserId = auth0UserId,
 944                Email = email,
 945                DisplayName = displayName,
 946                AvatarUrl = avatarUrl,
 947                CreatedAt = DateTime.UtcNow,
 948                LastLoginAt = DateTime.UtcNow,
 949                HasCompletedOnboarding = false
 950            };
 51
 952            _context.Users.Add(user);
 953            await _context.SaveChangesAsync();
 54
 955            _logger.LogDebug("Created new user {UserId} for Auth0 ID {Auth0UserId}", user.Id, auth0UserId);
 56
 57            // Note: We no longer auto-create a default world for new users.
 58            // Users can create their own world or join an existing one via invitation code.
 59        }
 60        else
 61        {
 62            // Update user info in case it changed (e.g., user changed their name/avatar in Auth0)
 363            bool needsUpdate = false;
 64
 365            if (user.Email != email)
 66            {
 167                user.Email = email;
 168                needsUpdate = true;
 69            }
 70
 371            if (user.DisplayName != displayName)
 72            {
 173                user.DisplayName = displayName;
 174                needsUpdate = true;
 75            }
 76
 377            if (user.AvatarUrl != avatarUrl)
 78            {
 179                user.AvatarUrl = avatarUrl;
 180                needsUpdate = true;
 81            }
 82
 83            // Always update last login
 384            user.LastLoginAt = DateTime.UtcNow;
 385            needsUpdate = true;
 86
 387            if (needsUpdate)
 88            {
 389                await _context.SaveChangesAsync();
 90            }
 91        }
 92
 1293        return user;
 1294    }
 95
 96    public async Task<User?> GetUserByIdAsync(Guid userId)
 97    {
 298        return await _context.Users.FindAsync(userId);
 299    }
 100
 101    public async Task UpdateLastLoginAsync(Guid userId)
 102    {
 2103        var user = await _context.Users.FindAsync(userId);
 2104        if (user != null)
 105        {
 1106            user.LastLoginAt = DateTime.UtcNow;
 1107            await _context.SaveChangesAsync();
 108        }
 2109    }
 110
 111    public async Task<UserProfileDto?> GetUserProfileAsync(Guid userId)
 112    {
 2113        var user = await _context.Users.FindAsync(userId);
 2114        if (user == null)
 115        {
 1116            return null;
 117        }
 118
 1119        return new UserProfileDto
 1120        {
 1121            Id = user.Id,
 1122            Email = user.Email,
 1123            DisplayName = user.DisplayName,
 1124            AvatarUrl = user.AvatarUrl,
 1125            CreatedAt = user.CreatedAt,
 1126            LastLoginAt = user.LastLoginAt,
 1127            HasCompletedOnboarding = user.HasCompletedOnboarding
 1128        };
 2129    }
 130
 131    public async Task<bool> CompleteOnboardingAsync(Guid userId)
 132    {
 4133        var user = await _context.Users.FindAsync(userId);
 4134        if (user == null)
 135        {
 1136            _logger.LogWarning("Attempted to complete onboarding for non-existent user {UserId}", userId);
 1137            return false;
 138        }
 139
 3140        if (!user.HasCompletedOnboarding)
 141        {
 2142            user.HasCompletedOnboarding = true;
 2143            await _context.SaveChangesAsync();
 2144            _logger.LogDebug("User {UserId} completed onboarding", userId);
 145        }
 146
 3147        return true;
 4148    }
 149
 150    /// <summary>
 151    /// Creates a default world with root structure for a new user
 152    /// </summary>
 153    private async Task CreateDefaultWorldAsync(Guid userId)
 154    {
 0155        _logger.LogDebug("Creating default world for user {UserId}", userId);
 156
 0157        var createDto = new WorldCreateDto
 0158        {
 0159            Name = "My World",
 0160            Description = "Your personal world for campaigns and adventures"
 0161        };
 162
 0163        await _worldService.CreateWorldAsync(createDto, userId);
 0164    }
 165}