diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..cd967fc
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,25 @@
+**/.dockerignore
+**/.env
+**/.git
+**/.gitignore
+**/.project
+**/.settings
+**/.toolstarget
+**/.vs
+**/.vscode
+**/.idea
+**/*.*proj.user
+**/*.dbmdl
+**/*.jfm
+**/azds.yaml
+**/bin
+**/charts
+**/docker-compose*
+**/Dockerfile*
+**/node_modules
+**/npm-debug.log
+**/obj
+**/secrets.dev.yaml
+**/values.dev.yaml
+LICENSE
+README.md
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9823957
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,231 @@
+# The 5 approved on boarding samples that need wwwroot/lib/ can add a local .gitignore that allows wwwroot/lib/.
+# with 2.1 templates and excluding .min & .map -> 27 files and < 1 KB
+# 3.0 templates should be even less
+# wwwroot/lib/
+# When wwwroot/lib/ is commented out, excluse .min and .map files
+*.min.css
+*.min.js
+*.map
+
+_build/
+_site/
+Properties/
+
+Project_Readme.html
+
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+.vscode/
+!.vscode/extensions.json
+!.vscode/settings.json
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+.idea/
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+build/
+bld/
+[Bb]in/
+[Oo]bj/
+
+# Visual Studo 2015 cache/options directory
+.vs/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opensdf
+*.sdf
+*.cachefile
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding addin-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+
+# Windows Azure Build Output
+csx/
+*.build.csdef
+
+# Windows Store app package directory
+AppPackages/
+
+# Others
+*.[Cc]ache
+ClientBin/
+[Ss]tyle[Cc]op.*
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.pfx
+*.publishsettings
+node_modules/
+bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+project.lock.json
+__pycache__/
+
+#Mac OSX
+.DS_Store
+
+# Windows thumbnail cache files
+Thumbs.db
+
+# idea
+.idea/
+
+# custom
+/**/**/appsettings.protected.json
+
+**/**/deployment.yml
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..e42216a
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,5 @@
+
+
+ true
+
+
diff --git a/Directory.Packages.props b/Directory.Packages.props
new file mode 100644
index 0000000..1587781
--- /dev/null
+++ b/Directory.Packages.props
@@ -0,0 +1,21 @@
+
+
+ net8.0
+ enable
+ enable
+ 8.0.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Nocr.Users.sln b/Nocr.Users.sln
new file mode 100644
index 0000000..47434f5
--- /dev/null
+++ b/Nocr.Users.sln
@@ -0,0 +1,42 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9CF552C7-A0FB-4C13-A156-8A9EA6C85EC1}"
+ ProjectSection(SolutionItems) = preProject
+ Directory.Build.props = Directory.Build.props
+ Directory.Packages.props = Directory.Packages.props
+ .gitignore = .gitignore
+ README.md = README.md
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nocr.Users.Core", "src\Nocr.Users.Core\Nocr.Users.Core.csproj", "{3E87693C-78DF-469B-BAA3-CB103F8EA80B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nocr.Users.AppServices", "src\Nocr.Users.AppServices\Nocr.Users.AppServices.csproj", "{5CCB085C-860A-4C4C-907C-10183ABCEA9B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nocr.Users.Host", "src\Nocr.Users.Host\Nocr.Users.Host.csproj", "{58D5C9FD-75A9-4FFB-9FBD-BE8E9FCE3016}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nocr.Users.Api.Contracts", "src\Nocr.Users.Api.Contracts\Nocr.Users.Api.Contracts.csproj", "{4DDFB05F-DFC7-4BD6-B593-AD52CF1B002E}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {3E87693C-78DF-469B-BAA3-CB103F8EA80B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3E87693C-78DF-469B-BAA3-CB103F8EA80B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3E87693C-78DF-469B-BAA3-CB103F8EA80B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3E87693C-78DF-469B-BAA3-CB103F8EA80B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5CCB085C-860A-4C4C-907C-10183ABCEA9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5CCB085C-860A-4C4C-907C-10183ABCEA9B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5CCB085C-860A-4C4C-907C-10183ABCEA9B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5CCB085C-860A-4C4C-907C-10183ABCEA9B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {58D5C9FD-75A9-4FFB-9FBD-BE8E9FCE3016}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {58D5C9FD-75A9-4FFB-9FBD-BE8E9FCE3016}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {58D5C9FD-75A9-4FFB-9FBD-BE8E9FCE3016}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {58D5C9FD-75A9-4FFB-9FBD-BE8E9FCE3016}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4DDFB05F-DFC7-4BD6-B593-AD52CF1B002E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4DDFB05F-DFC7-4BD6-B593-AD52CF1B002E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4DDFB05F-DFC7-4BD6-B593-AD52CF1B002E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4DDFB05F-DFC7-4BD6-B593-AD52CF1B002E}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/src/Nocr.Users.Api.Contracts/Nocr.Users.Api.Contracts.csproj b/src/Nocr.Users.Api.Contracts/Nocr.Users.Api.Contracts.csproj
new file mode 100644
index 0000000..f5671d9
--- /dev/null
+++ b/src/Nocr.Users.Api.Contracts/Nocr.Users.Api.Contracts.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/src/Nocr.Users.Api.Contracts/Users/Dto/CreateUserRequest.cs b/src/Nocr.Users.Api.Contracts/Users/Dto/CreateUserRequest.cs
new file mode 100644
index 0000000..2d72be4
--- /dev/null
+++ b/src/Nocr.Users.Api.Contracts/Users/Dto/CreateUserRequest.cs
@@ -0,0 +1,10 @@
+namespace Nocr.Users.Api.Contracts.Users;
+
+public sealed class CreateUserRequest
+{
+ public string Username { get; set; }
+
+ public string? Email { get; set; }
+
+ public long? TelegramId { get; set; }
+}
\ No newline at end of file
diff --git a/src/Nocr.Users.Api.Contracts/Users/IUsersController.cs b/src/Nocr.Users.Api.Contracts/Users/IUsersController.cs
new file mode 100644
index 0000000..37fa074
--- /dev/null
+++ b/src/Nocr.Users.Api.Contracts/Users/IUsersController.cs
@@ -0,0 +1,10 @@
+using RestEase;
+
+namespace Nocr.Users.Api.Contracts.Users;
+
+[BasePath(WebRoutes.Users.Path)]
+public interface IUsersController
+{
+ [Post]
+ Task Create([Body] CreateUserRequest request, CancellationToken cancellationToken = default);
+}
\ No newline at end of file
diff --git a/src/Nocr.Users.Api.Contracts/WebRoutes.cs b/src/Nocr.Users.Api.Contracts/WebRoutes.cs
new file mode 100644
index 0000000..211af22
--- /dev/null
+++ b/src/Nocr.Users.Api.Contracts/WebRoutes.cs
@@ -0,0 +1,11 @@
+namespace Nocr.Users.Api.Contracts;
+
+public static class WebRoutes
+{
+ public const string BasePath = "/api";
+
+ public static class Users
+ {
+ public const string Path = BasePath + "/" + "users";
+ }
+}
\ No newline at end of file
diff --git a/src/Nocr.Users.AppServices/Nocr.Users.AppServices.csproj b/src/Nocr.Users.AppServices/Nocr.Users.AppServices.csproj
new file mode 100644
index 0000000..97bc233
--- /dev/null
+++ b/src/Nocr.Users.AppServices/Nocr.Users.AppServices.csproj
@@ -0,0 +1,15 @@
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Nocr.Users.AppServices/ServiceCollectionExtensions.cs b/src/Nocr.Users.AppServices/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000..bbfa6ab
--- /dev/null
+++ b/src/Nocr.Users.AppServices/ServiceCollectionExtensions.cs
@@ -0,0 +1,16 @@
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Nocr.Users.AppServices;
+
+public static class ServiceCollectionExtensions
+{
+ public static IServiceCollection AddAppServices(this IServiceCollection services)
+ {
+ if (services == null)
+ throw new ArgumentNullException(nameof(services));
+
+ // Add registrations here
+
+ return services;
+ }
+}
\ No newline at end of file
diff --git a/src/Nocr.Users.AppServices/Users/User.cs b/src/Nocr.Users.AppServices/Users/User.cs
new file mode 100644
index 0000000..acfb92e
--- /dev/null
+++ b/src/Nocr.Users.AppServices/Users/User.cs
@@ -0,0 +1,23 @@
+namespace Nocr.Users.AppServices.Users;
+
+public sealed class User
+{
+ public long Id { get; set; }
+
+ public string Username { get; private set; }
+
+ private User(string username)
+ {
+ Username = username;
+ }
+
+ public static User Initialize(string username)
+ {
+ if (string.IsNullOrWhiteSpace(username))
+ {
+ throw new ArgumentNullException(nameof(username));
+ }
+
+ return new User(username);
+ }
+}
\ No newline at end of file
diff --git a/src/Nocr.Users.Core/Dates/DefaultCurrentDateProvider.cs b/src/Nocr.Users.Core/Dates/DefaultCurrentDateProvider.cs
new file mode 100644
index 0000000..8196f57
--- /dev/null
+++ b/src/Nocr.Users.Core/Dates/DefaultCurrentDateProvider.cs
@@ -0,0 +1,6 @@
+namespace Nocr.Users.Core.Dates;
+
+public sealed class DefaultCurrentDateProvider : ICurrentDateProvider
+{
+ public DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
+}
\ No newline at end of file
diff --git a/src/Nocr.Users.Core/Dates/ICurrentDateProvider.cs b/src/Nocr.Users.Core/Dates/ICurrentDateProvider.cs
new file mode 100644
index 0000000..6eabb91
--- /dev/null
+++ b/src/Nocr.Users.Core/Dates/ICurrentDateProvider.cs
@@ -0,0 +1,6 @@
+namespace Nocr.Users.Core.Dates;
+
+public interface ICurrentDateProvider
+{
+ public DateTimeOffset UtcNow { get; }
+}
\ No newline at end of file
diff --git a/src/Nocr.Users.Core/Nocr.Users.Core.csproj b/src/Nocr.Users.Core/Nocr.Users.Core.csproj
new file mode 100644
index 0000000..86926c2
--- /dev/null
+++ b/src/Nocr.Users.Core/Nocr.Users.Core.csproj
@@ -0,0 +1,5 @@
+
+
+ false
+
+
diff --git a/src/Nocr.Users.Host/Dockerfile b/src/Nocr.Users.Host/Dockerfile
new file mode 100644
index 0000000..88f7ca7
--- /dev/null
+++ b/src/Nocr.Users.Host/Dockerfile
@@ -0,0 +1,18 @@
+FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
+WORKDIR /app
+
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+WORKDIR /src
+COPY . .
+
+RUN dotnet restore "src/Nocr.Users.Host/Nocr.Users.Host.csproj"
+WORKDIR "/src/src/Nocr.Users.Host"
+RUN dotnet build "Nocr.Users.Host.csproj" -c Release -o /app/build
+
+FROM build AS publish
+RUN dotnet publish "Nocr.Users.Host.csproj" -c Release -o /app/publish
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENTRYPOINT ["dotnet", "Nocr.Users.Host.dll"]
diff --git a/src/Nocr.Users.Host/Infrastructure/HostBuilderFactory.cs b/src/Nocr.Users.Host/Infrastructure/HostBuilderFactory.cs
new file mode 100644
index 0000000..ffe123e
--- /dev/null
+++ b/src/Nocr.Users.Host/Infrastructure/HostBuilderFactory.cs
@@ -0,0 +1,26 @@
+using Serilog;
+
+namespace Nocr.Users.Host.Infrastructure;
+
+public class HostBuilderFactory where TStartup : class
+{
+ public IHostBuilder CreateHostBuilder(string[] args, string? baseDirectory = null)
+ {
+ var builder = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args)
+ .ConfigureAppConfiguration((_, configurationBuilder) =>
+ {
+ if (!string.IsNullOrWhiteSpace(baseDirectory))
+ configurationBuilder.SetBasePath(baseDirectory);
+
+ configurationBuilder.AddJsonFile("appsettings.protected.json", false);
+ })
+ .ConfigureWebHostDefaults(host => { host.UseStartup(); })
+ .UseSerilog((ctx, logBuilder) =>
+ {
+ logBuilder.ReadFrom.Configuration(ctx.Configuration)
+ .Enrich.FromLogContext();
+ });
+
+ return builder;
+ }
+}
\ No newline at end of file
diff --git a/src/Nocr.Users.Host/Infrastructure/Startup.cs b/src/Nocr.Users.Host/Infrastructure/Startup.cs
new file mode 100644
index 0000000..82567d2
--- /dev/null
+++ b/src/Nocr.Users.Host/Infrastructure/Startup.cs
@@ -0,0 +1,38 @@
+using Nocr.Users.AppServices;
+using Nocr.Users.Core.Dates;
+
+namespace Nocr.Users.Host.Infrastructure;
+
+public class Startup
+{
+ public IConfiguration Configuration { get; }
+
+ public Startup(IConfiguration configuration)
+ {
+ Configuration = configuration;
+ }
+
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddSingleton();
+
+ services.AddControllers();
+
+ services.AddEndpointsApiExplorer();
+ services.AddSwaggerGen();
+
+ services.AddAppServices();
+ }
+
+ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
+ {
+ if (!env.IsProduction())
+ {
+ app.UseSwagger();
+ app.UseSwaggerUI();
+ }
+
+ app.UseRouting();
+ app.UseEndpoints(builder => builder.MapControllers());
+ }
+}
\ No newline at end of file
diff --git a/src/Nocr.Users.Host/Nocr.Users.Host.csproj b/src/Nocr.Users.Host/Nocr.Users.Host.csproj
new file mode 100644
index 0000000..061dccb
--- /dev/null
+++ b/src/Nocr.Users.Host/Nocr.Users.Host.csproj
@@ -0,0 +1,24 @@
+
+
+
+ Linux
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .dockerignore
+
+
+
+
diff --git a/src/Nocr.Users.Host/Program.cs b/src/Nocr.Users.Host/Program.cs
new file mode 100644
index 0000000..33aff4e
--- /dev/null
+++ b/src/Nocr.Users.Host/Program.cs
@@ -0,0 +1,7 @@
+using Nocr.Users.Host.Infrastructure;
+
+var host = new HostBuilderFactory()
+ .CreateHostBuilder(args)
+ .Build();
+
+await host.RunAsync();
\ No newline at end of file
diff --git a/src/Nocr.Users.Host/appsettings.Development.json b/src/Nocr.Users.Host/appsettings.Development.json
new file mode 100644
index 0000000..fdf6b48
--- /dev/null
+++ b/src/Nocr.Users.Host/appsettings.Development.json
@@ -0,0 +1,12 @@
+{
+ "Serilog": {
+ "WriteTo": [
+ {
+ "Name": "Console",
+ "Args": {
+ "outputTemplate": "[{Level:u3}] {Timestamp:MM-dd HH:mm:ss} {TraceId} {SourceContext:l} {Message:lj}{NewLine}{Exception}"
+ }
+ }
+ ]
+ }
+}
diff --git a/src/Nocr.Users.Host/appsettings.Production.json b/src/Nocr.Users.Host/appsettings.Production.json
new file mode 100644
index 0000000..206001f
--- /dev/null
+++ b/src/Nocr.Users.Host/appsettings.Production.json
@@ -0,0 +1,25 @@
+{
+ "Serilog": {
+ "MinimumLevel": {
+ "Default": "Information"
+ },
+ "WriteTo": [
+ {
+ "Name": "Console",
+ "Args": {
+ "outputTemplate": "[{Level:u3}] {Timestamp:MM-dd HH:mm:ss} {TraceId} {SourceContext:l} {Message:lj}{NewLine}{Exception}"
+ }
+ },
+ {
+ "Name": "File",
+ "Args": {
+ "path": "/var/log/nocr/users-.log",
+ "outputTemplate": "[{Level:u3}] {Timestamp:dd-MM-yyyy HH:mm:ss} {TraceId} {SourceContext:l} {Message:lj}{NewLine}{Exception}",
+ "fileSizeLimitBytes": 104857600,
+ "rollingInterval": "Day",
+ "rollOnFileSizeLimit": true
+ }
+ }
+ ]
+ }
+}
diff --git a/src/Nocr.Users.Host/appsettings.json b/src/Nocr.Users.Host/appsettings.json
new file mode 100644
index 0000000..ff9ad20
--- /dev/null
+++ b/src/Nocr.Users.Host/appsettings.json
@@ -0,0 +1,12 @@
+{
+ "Serilog": {
+ "MinimumLevel": {
+ "Default": "Debug",
+ "Override": {
+ "Microsoft": "Information",
+ "Microsoft.AspNetCore": "Error",
+ "System.Net.Http.HttpClient": "Warning"
+ }
+ }
+ }
+}