Add initial implementation of API, database, and user management components.

This commit is contained in:
2026-03-15 23:17:51 +01:00
commit 0543120f3b
53 changed files with 2241 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
using Microsoft.EntityFrameworkCore;
using MikrocopDb.Entities;
namespace MikrocopDb;
public sealed class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<UserEntity> Users => Set<UserEntity>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserEntity>(entity =>
{
entity.HasKey(x => x.Id);
entity.Property(x => x.UserName).HasMaxLength(100).IsRequired();
entity.Property(x => x.FullName).HasMaxLength(200).IsRequired();
entity.Property(x => x.Email).HasMaxLength(200).IsRequired();
entity.Property(x => x.MobileNumber).HasMaxLength(30).IsRequired();
entity.Property(x => x.Language).HasMaxLength(20).IsRequired();
entity.Property(x => x.Culture).HasMaxLength(20).IsRequired();
entity.Property(x => x.PasswordHash).IsRequired();
entity.Property(x => x.PasswordSalt).HasMaxLength(128).IsRequired();
entity.HasIndex(x => x.UserName).IsUnique();
entity.HasIndex(x => x.Email).IsUnique();
});
}
}

View File

@@ -0,0 +1,15 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
namespace MikrocopDb;
public sealed class AppDbContextFactory : IDesignTimeDbContextFactory<AppDbContext>
{
public AppDbContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>();
optionsBuilder.UseSqlite("Data Source=mikrocop.db");
return new AppDbContext(optionsBuilder.Options);
}
}

View File

@@ -0,0 +1,14 @@
namespace MikrocopDb.Entities;
public sealed class UserEntity
{
public Guid Id { get; set; }
public required string UserName { get; set; }
public required string FullName { get; set; }
public required string Email { get; set; }
public required string MobileNumber { get; set; }
public required string Language { get; set; }
public required string Culture { get; set; }
public required string PasswordHash { get; set; }
public required string PasswordSalt { get; set; }
}

View File

@@ -0,0 +1,81 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using MikrocopDb;
#nullable disable
namespace MikrocopDb.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20260315212014_Initial")]
partial class Initial
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "10.0.5");
modelBuilder.Entity("MikrocopDb.Entities.UserEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Culture")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<string>("Email")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("FullName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("Language")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<string>("MobileNumber")
.IsRequired()
.HasMaxLength(30)
.HasColumnType("TEXT");
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("PasswordSalt")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("TEXT");
b.Property<string>("UserName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("Email")
.IsUnique();
b.HasIndex("UserName")
.IsUnique();
b.ToTable("Users");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,53 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace MikrocopDb.Migrations
{
/// <inheritdoc />
public partial class Initial : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column<Guid>(type: "TEXT", nullable: false),
UserName = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
FullName = table.Column<string>(type: "TEXT", maxLength: 200, nullable: false),
Email = table.Column<string>(type: "TEXT", maxLength: 200, nullable: false),
MobileNumber = table.Column<string>(type: "TEXT", maxLength: 30, nullable: false),
Language = table.Column<string>(type: "TEXT", maxLength: 20, nullable: false),
Culture = table.Column<string>(type: "TEXT", maxLength: 20, nullable: false),
PasswordHash = table.Column<string>(type: "TEXT", nullable: false),
PasswordSalt = table.Column<string>(type: "TEXT", maxLength: 128, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_Users_Email",
table: "Users",
column: "Email",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Users_UserName",
table: "Users",
column: "UserName",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Users");
}
}
}

View File

@@ -0,0 +1,78 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using MikrocopDb;
#nullable disable
namespace MikrocopDb.Migrations
{
[DbContext(typeof(AppDbContext))]
partial class AppDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "10.0.5");
modelBuilder.Entity("MikrocopDb.Entities.UserEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Culture")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<string>("Email")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("FullName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("Language")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<string>("MobileNumber")
.IsRequired()
.HasMaxLength(30)
.HasColumnType("TEXT");
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("PasswordSalt")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("TEXT");
b.Property<string>("UserName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("Email")
.IsUnique();
b.HasIndex("UserName")
.IsUnique();
b.ToTable("Users");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,13 @@
using MikrocopDb.Entities;
namespace MikrocopDb.Repositories;
public interface IUserRepository
{
Task<UserEntity?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
Task<UserEntity?> GetByUserNameAsync(string userName, CancellationToken cancellationToken = default);
Task<UserEntity?> GetByEmailAsync(string email, CancellationToken cancellationToken = default);
Task AddAsync(UserEntity user, CancellationToken cancellationToken = default);
Task UpdateAsync(UserEntity user, CancellationToken cancellationToken = default);
Task DeleteAsync(UserEntity user, CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,47 @@
using Microsoft.EntityFrameworkCore;
using MikrocopDb.Entities;
namespace MikrocopDb.Repositories;
public sealed class UserRepository : IUserRepository
{
private readonly AppDbContext _dbContext;
public UserRepository(AppDbContext dbContext)
{
_dbContext = dbContext;
}
public Task<UserEntity?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
{
return _dbContext.Users.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
}
public Task<UserEntity?> GetByUserNameAsync(string userName, CancellationToken cancellationToken = default)
{
return _dbContext.Users.FirstOrDefaultAsync(x => x.UserName == userName, cancellationToken);
}
public Task<UserEntity?> GetByEmailAsync(string email, CancellationToken cancellationToken = default)
{
return _dbContext.Users.FirstOrDefaultAsync(x => x.Email == email, cancellationToken);
}
public async Task AddAsync(UserEntity user, CancellationToken cancellationToken = default)
{
await _dbContext.Users.AddAsync(user, cancellationToken);
await _dbContext.SaveChangesAsync(cancellationToken);
}
public async Task UpdateAsync(UserEntity user, CancellationToken cancellationToken = default)
{
_dbContext.Users.Update(user);
await _dbContext.SaveChangesAsync(cancellationToken);
}
public async Task DeleteAsync(UserEntity user, CancellationToken cancellationToken = default)
{
_dbContext.Users.Remove(user);
await _dbContext.SaveChangesAsync(cancellationToken);
}
}