Add mongo

This commit is contained in:
Kasper Juul Hermansen 2021-11-11 21:44:15 +01:00
parent 08623e9975
commit 2e737359c7
Signed by: kjuulh
GPG Key ID: 0F95C140730F2F23
25 changed files with 356 additions and 29 deletions

View File

@ -0,0 +1 @@
FROM mongo

View File

@ -0,0 +1,37 @@
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Todo.Core.Entities;
using Todo.Core.Interfaces.Persistence;
namespace Todo.Api.Controllers
{
[ApiController]
[Route("api/auth")]
[Authorize]
[AllowAnonymous]
public class AuthController : ControllerBase
{
private readonly IUserRepository _userRepository;
public AuthController(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public record RegisterUserRequest
{
[Required] public string Email { get; init; }
[Required] public string Password { get; init; }
}
[HttpPost("register")]
public async Task<IActionResult> Register([FromBody] RegisterUserRequest request)
{
var user = await _userRepository.Register(request.Email, request.Password);
return Ok(user);
}
}
}

View File

@ -0,0 +1,40 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Todo.Core.Interfaces.Persistence;
namespace Todo.Api.Controllers;
[ApiController]
[Route("/api/todos")]
public class TodosController : ControllerBase
{
private readonly ITodoRepository _todoRepository;
public TodosController(ITodoRepository todoRepository)
{
_todoRepository = todoRepository;
}
public record CreateTodoRequest
{
[Required] public string Title { get; init; }
}
[HttpPost]
public async Task<ActionResult<Core.Entities.Todo>> CreateTodo([FromBody] CreateTodoRequest request)
{
var todo = await _todoRepository.CreateTodoAsync(request.Title);
return Ok(todo);
}
[HttpGet]
public async Task<ActionResult<IEnumerable<Core.Entities.Todo>>> GetTodos()
{
var todos = await _todoRepository.GetTodosAsync();
return Ok(todos);
}
}

View File

@ -1,9 +1,9 @@
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80
EXPOSE 443 EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src WORKDIR /src
COPY ["Todo.Api/Todo.Api.csproj", "Todo.Api/"] COPY ["Todo.Api/Todo.Api.csproj", "Todo.Api/"]
RUN dotnet restore "Todo.Api/Todo.Api.csproj" RUN dotnet restore "Todo.Api/Todo.Api.csproj"

View File

@ -1,13 +1,32 @@
using System;
using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
using Todo.Core.Interfaces.Persistence;
namespace Todo.Api.Hubs namespace Todo.Api.Hubs
{ {
public class TodoHub : Hub public class TodoHub : Hub
{ {
private readonly ITodoRepository _todoRepository;
public TodoHub(ITodoRepository todoRepository)
{
_todoRepository = todoRepository;
}
public async Task GetInboxTodos() public async Task GetInboxTodos()
{ {
await Clients.Caller.SendAsync("InboxTodos", "some data"); await Clients.Caller.SendAsync("InboxTodos", "some data");
} }
public async Task CreateTodo(string createTodoRequest)
{
var todo = JsonSerializer.Deserialize<Core.Entities.Todo>(createTodoRequest);
if (todo is null)
throw new InvalidOperationException("Failed to create todo because of invalid request");
await _todoRepository.CreateTodoAsync(todo.Title);
}
} }
} }

View File

@ -1,7 +1,7 @@
using System; global using System;
using System.Collections.Generic; global using System.Collections.Generic;
using System.Linq; global using System.Linq;
using System.Threading.Tasks; global using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;

View File

@ -1,17 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Todo.Api.Hubs; using Todo.Api.Hubs;
using Todo.Persistence;
using Todo.Persistence.Mongo;
namespace Todo.Api namespace Todo.Api
{ {
@ -42,12 +37,15 @@ namespace Todo.Api
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Todo.Api", Version = "v1" }); c.SwaggerDoc("v1", new OpenApiInfo { Title = "Todo.Api", Version = "v1" });
}); });
services.AddPersistence();
services.AddSignalR(); services.AddSignalR();
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{ {
app.MigrateMongoDb();
if (env.IsDevelopment()) if (env.IsDevelopment())
{ {
app.UseDeveloperExceptionPage(); app.UseDeveloperExceptionPage();

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net5.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS> <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup> </PropertyGroup>
@ -9,4 +9,9 @@
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Todo.Core\Todo.Core.csproj" />
<ProjectReference Include="..\Todo.Persistence\Todo.Persistence.csproj" />
</ItemGroup>
</Project> </Project>

View File

@ -1,15 +0,0 @@
using System;
namespace Todo.Api
{
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string Summary { get; set; }
}
}

View File

@ -0,0 +1,11 @@
global using System;
global using System.Linq;
global using System.Threading.Tasks;
global using System.Collections.Generic;
namespace Todo.Core;
public static class DependencyInjection
{
}

View File

@ -0,0 +1,7 @@
namespace Todo.Core.Entities;
public record Todo
{
public string Id { get; init; }
public string Title { get; init; }
}

View File

@ -0,0 +1,8 @@
namespace Todo.Core.Entities
{
public class User
{
public string Id { get; set; }
public string Email { get; set; }
}
}

View File

@ -0,0 +1,8 @@
namespace Todo.Core.Interfaces.Persistence;
public interface ITodoRepository
{
Task<Entities.Todo> CreateTodoAsync(string title);
Task<IEnumerable<Entities.Todo>> GetTodosAsync();
}

View File

@ -0,0 +1,8 @@
using Todo.Core.Entities;
namespace Todo.Core.Interfaces.Persistence;
public interface IUserRepository
{
Task<User> Register(string email, string password);
}

View File

@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,16 @@
global using System;
global using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Todo.Core.Interfaces.Persistence;
using Todo.Persistence.Mongo.Repositories;
namespace Todo.Persistence
{
public static class DependencyInjection
{
public static IServiceCollection AddPersistence(this IServiceCollection services) =>
services
.AddScoped<IUserRepository, UserRepository>()
.AddScoped<ITodoRepository, TodoRepository>();
}
}

View File

@ -0,0 +1,36 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using MongoDB.Driver;
using Todo.Persistence.Mongo.Repositories.Dtos;
namespace Todo.Persistence.Mongo
{
public static class Migrations
{
public static IApplicationBuilder MigrateMongoDb(this IApplicationBuilder app)
{
Task.Run(async () =>
{
var conn = new MongoClient("mongodb://root:example@localhost:27017");
var database = conn.GetDatabase("todo");
await CreateIndexes(database);
}).Wait(10000);
return app;
}
private static async Task CreateIndexes(IMongoDatabase mongoDatabase)
{
Console.WriteLine("Running: CreateIndexes");
var collection = mongoDatabase.GetCollection<MongoUser>("users");
var indexKeysDefinition = Builders<MongoUser>.IndexKeys.Ascending(user => user.Email);
var indexModel =
new CreateIndexModel<MongoUser>(indexKeysDefinition, new CreateIndexOptions() { Unique = true });
await collection.Indexes.CreateOneAsync(indexModel);
Console.WriteLine("Finished: CreateIndexes");
}
}
}

View File

@ -0,0 +1,13 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Todo.Persistence.Mongo.Repositories.Dtos;
public record MongoTodo
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; init; }
[BsonRequired] public string Title { get; init; }
}

View File

@ -0,0 +1,15 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Todo.Persistence.Mongo.Repositories.Dtos
{
public record MongoUser
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; init; }
[BsonRequired] public string Email { get; init; }
[BsonRequired] public string Password { get; set; }
}
}

View File

@ -0,0 +1,37 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MongoDB.Driver;
using Todo.Core.Interfaces.Persistence;
using Todo.Persistence.Mongo.Repositories.Dtos;
namespace Todo.Persistence.Mongo.Repositories;
public class TodoRepository : ITodoRepository
{
private readonly IMongoCollection<MongoTodo> _todosCollection;
public TodoRepository()
{
var conn = new MongoClient("mongodb://root:example@localhost:27017");
var database = conn.GetDatabase("todo");
_todosCollection = database.GetCollection<MongoTodo>("todos");
}
public async Task<Core.Entities.Todo> CreateTodoAsync(string title)
{
var todo = new MongoTodo() { Title = title };
await _todosCollection.InsertOneAsync(todo);
return new Core.Entities.Todo() { Id = todo.Id, Title = todo.Title };
}
public async Task<IEnumerable<Core.Entities.Todo>> GetTodosAsync()
{
var todos = await _todosCollection.FindAsync(_ => true);
return todos
.ToEnumerable()
.Select(t =>
new Core.Entities.Todo() { Id = t.Id, Title = t.Title });
}
}

View File

@ -0,0 +1,28 @@
using System.Threading.Tasks;
using MongoDB.Driver;
using Todo.Core.Entities;
using Todo.Core.Interfaces.Persistence;
using Todo.Persistence.Mongo.Repositories.Dtos;
namespace Todo.Persistence.Mongo.Repositories
{
public class UserRepository : IUserRepository
{
private readonly IMongoCollection<MongoUser> _usersCollection;
public UserRepository()
{
var conn = new MongoClient("mongodb://root:example@localhost:27017");
var database = conn.GetDatabase("todo");
_usersCollection = database.GetCollection<MongoUser>("users");
}
public async Task<User> Register(string email, string password)
{
var dtoUser = new MongoUser() { Email = email, Password = password };
await _usersCollection.InsertOneAsync(dtoUser);
return new User() { Email = dtoUser.Email, Id = dtoUser.Id };
}
}
}

View File

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="2.13.2" />
<PackageReference Include="MongoDB.Driver.Core" Version="2.13.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Todo.Core\Todo.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -2,6 +2,10 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Todo.Api", "Todo.Api\Todo.Api.csproj", "{FA037FDE-DAA4-4A95-ABA5-64DCFEDDBA4E}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Todo.Api", "Todo.Api\Todo.Api.csproj", "{FA037FDE-DAA4-4A95-ABA5-64DCFEDDBA4E}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Todo.Persistence", "Todo.Persistence\Todo.Persistence.csproj", "{7636E7CC-C9A5-41D4-BBFE-B107497A5C8A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Todo.Core", "Todo.Core\Todo.Core.csproj", "{F134CAB6-15A5-45CB-8782-B61AB67B5C9C}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -12,5 +16,13 @@ Global
{FA037FDE-DAA4-4A95-ABA5-64DCFEDDBA4E}.Debug|Any CPU.Build.0 = Debug|Any CPU {FA037FDE-DAA4-4A95-ABA5-64DCFEDDBA4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA037FDE-DAA4-4A95-ABA5-64DCFEDDBA4E}.Release|Any CPU.ActiveCfg = Release|Any CPU {FA037FDE-DAA4-4A95-ABA5-64DCFEDDBA4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA037FDE-DAA4-4A95-ABA5-64DCFEDDBA4E}.Release|Any CPU.Build.0 = Release|Any CPU {FA037FDE-DAA4-4A95-ABA5-64DCFEDDBA4E}.Release|Any CPU.Build.0 = Release|Any CPU
{7636E7CC-C9A5-41D4-BBFE-B107497A5C8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7636E7CC-C9A5-41D4-BBFE-B107497A5C8A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7636E7CC-C9A5-41D4-BBFE-B107497A5C8A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7636E7CC-C9A5-41D4-BBFE-B107497A5C8A}.Release|Any CPU.Build.0 = Release|Any CPU
{F134CAB6-15A5-45CB-8782-B61AB67B5C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F134CAB6-15A5-45CB-8782-B61AB67B5C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F134CAB6-15A5-45CB-8782-B61AB67B5C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F134CAB6-15A5-45CB-8782-B61AB67B5C9C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View File

@ -0,0 +1,7 @@
{
"sdk": {
"version": "6.0",
"rollForward": "latestMajor",
"allowPrerelease": true
}
}

9
src/docker-compose.yml Normal file
View File

@ -0,0 +1,9 @@
services:
db:
build:
context: backend/db
ports:
- 27017:27017
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: example