/ GUNRPG.Infrastructure / Identity / LiteDbUserStore.cs
LiteDbUserStore.cs
  1  using GUNRPG.Core.Identity;
  2  using LiteDB;
  3  using Microsoft.AspNetCore.Identity;
  4  
  5  namespace GUNRPG.Infrastructure.Identity;
  6  
  7  /// <summary>
  8  /// LiteDB-backed ASP.NET Identity user store.
  9  /// Implements the minimum interfaces required by <see cref="UserManager{TUser}"/>:
 10  /// user CRUD, password hashing storage, and e-mail lookup.
 11  /// </summary>
 12  public sealed class LiteDbUserStore :
 13      IUserStore<ApplicationUser>,
 14      IUserPasswordStore<ApplicationUser>,
 15      IUserEmailStore<ApplicationUser>
 16  {
 17      internal const string CollectionName = "identity_users";
 18  
 19      private readonly ILiteCollection<ApplicationUser> _users;
 20  
 21      public LiteDbUserStore(ILiteDatabase db)
 22      {
 23          _users = db.GetCollection<ApplicationUser>(CollectionName);
 24          _users.EnsureIndex(u => u.NormalizedUserName, unique: true);
 25          _users.EnsureIndex(u => u.NormalizedEmail);
 26      }
 27  
 28      // ── IUserStore ──────────────────────────────────────────────────────────
 29  
 30      public Task<IdentityResult> CreateAsync(ApplicationUser user, CancellationToken ct)
 31      {
 32          try
 33          {
 34              _users.Insert(user);
 35              return Task.FromResult(IdentityResult.Success);
 36          }
 37          catch (LiteException ex)
 38          {
 39              return Task.FromResult(IdentityResult.Failed(new IdentityError
 40              {
 41                  Code = "LiteDbInsertError",
 42                  Description = $"Failed to create user: {ex.Message}",
 43              }));
 44          }
 45      }
 46  
 47      public Task<IdentityResult> UpdateAsync(ApplicationUser user, CancellationToken ct)
 48      {
 49          try
 50          {
 51              if (!_users.Update(user))
 52                  return Task.FromResult(IdentityResult.Failed(new IdentityError
 53                  {
 54                      Code = "UserUpdateFailed",
 55                      Description = "Failed to update user: user not found or not modified.",
 56                  }));
 57  
 58              return Task.FromResult(IdentityResult.Success);
 59          }
 60          catch (LiteException ex)
 61          {
 62              return Task.FromResult(IdentityResult.Failed(new IdentityError
 63              {
 64                  Code = "LiteDbUpdateError",
 65                  Description = $"Failed to update user: {ex.Message}",
 66              }));
 67          }
 68      }
 69  
 70      public Task<IdentityResult> DeleteAsync(ApplicationUser user, CancellationToken ct)
 71      {
 72          try
 73          {
 74              if (!_users.Delete(user.Id))
 75                  return Task.FromResult(IdentityResult.Failed(new IdentityError
 76                  {
 77                      Code = "UserDeleteFailed",
 78                      Description = "Failed to delete user: user not found.",
 79                  }));
 80  
 81              return Task.FromResult(IdentityResult.Success);
 82          }
 83          catch (LiteException ex)
 84          {
 85              return Task.FromResult(IdentityResult.Failed(new IdentityError
 86              {
 87                  Code = "LiteDbDeleteError",
 88                  Description = $"Failed to delete user: {ex.Message}",
 89              }));
 90          }
 91      }
 92  
 93      public Task<ApplicationUser?> FindByIdAsync(string userId, CancellationToken ct) =>
 94          Task.FromResult<ApplicationUser?>(_users.FindById(userId));
 95  
 96      public Task<ApplicationUser?> FindByNameAsync(string normalizedUserName, CancellationToken ct) =>
 97          Task.FromResult<ApplicationUser?>(_users.FindOne(u => u.NormalizedUserName == normalizedUserName));
 98  
 99      public Task<string> GetUserIdAsync(ApplicationUser user, CancellationToken ct) =>
100          Task.FromResult(user.Id);
101  
102      public Task<string?> GetUserNameAsync(ApplicationUser user, CancellationToken ct) =>
103          Task.FromResult(user.UserName);
104  
105      public Task SetUserNameAsync(ApplicationUser user, string? userName, CancellationToken ct)
106      {
107          user.UserName = userName;
108          return Task.CompletedTask;
109      }
110  
111      public Task<string?> GetNormalizedUserNameAsync(ApplicationUser user, CancellationToken ct) =>
112          Task.FromResult(user.NormalizedUserName);
113  
114      public Task SetNormalizedUserNameAsync(ApplicationUser user, string? normalizedName, CancellationToken ct)
115      {
116          user.NormalizedUserName = normalizedName;
117          return Task.CompletedTask;
118      }
119  
120      // ── IUserPasswordStore ──────────────────────────────────────────────────
121  
122      public Task SetPasswordHashAsync(ApplicationUser user, string? passwordHash, CancellationToken ct)
123      {
124          user.PasswordHash = passwordHash;
125          return Task.CompletedTask;
126      }
127  
128      public Task<string?> GetPasswordHashAsync(ApplicationUser user, CancellationToken ct) =>
129          Task.FromResult(user.PasswordHash);
130  
131      public Task<bool> HasPasswordAsync(ApplicationUser user, CancellationToken ct) =>
132          Task.FromResult(user.PasswordHash is not null);
133  
134      // ── IUserEmailStore ─────────────────────────────────────────────────────
135  
136      public Task SetEmailAsync(ApplicationUser user, string? email, CancellationToken ct)
137      {
138          user.Email = email;
139          return Task.CompletedTask;
140      }
141  
142      public Task<string?> GetEmailAsync(ApplicationUser user, CancellationToken ct) =>
143          Task.FromResult(user.Email);
144  
145      public Task<bool> GetEmailConfirmedAsync(ApplicationUser user, CancellationToken ct) =>
146          Task.FromResult(user.EmailConfirmed);
147  
148      public Task SetEmailConfirmedAsync(ApplicationUser user, bool confirmed, CancellationToken ct)
149      {
150          user.EmailConfirmed = confirmed;
151          return Task.CompletedTask;
152      }
153  
154      public Task<ApplicationUser?> FindByEmailAsync(string normalizedEmail, CancellationToken ct) =>
155          Task.FromResult<ApplicationUser?>(_users.FindOne(u => u.NormalizedEmail == normalizedEmail));
156  
157      public Task<string?> GetNormalizedEmailAsync(ApplicationUser user, CancellationToken ct) =>
158          Task.FromResult(user.NormalizedEmail);
159  
160      public Task SetNormalizedEmailAsync(ApplicationUser user, string? normalizedEmail, CancellationToken ct)
161      {
162          user.NormalizedEmail = normalizedEmail;
163          return Task.CompletedTask;
164      }
165  
166      public void Dispose() { /* LiteDatabase lifetime is managed by the DI container. */ }
167  }