Web Api layout
This commit is contained in:
parent
5619093f41
commit
da6d5576bf
32 changed files with 515 additions and 29 deletions
|
|
@ -7,6 +7,12 @@
|
|||
"commands": [
|
||||
"dotnet-ef"
|
||||
]
|
||||
},
|
||||
"dotnet-aspnet-codegenerator": {
|
||||
"version": "8.0.0",
|
||||
"commands": [
|
||||
"dotnet-aspnet-codegenerator"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ tab_width = 4
|
|||
|
||||
# New line preferences
|
||||
end_of_line = crlf
|
||||
insert_final_newline = false
|
||||
insert_final_newline = true
|
||||
|
||||
#### .NET Coding Conventions ####
|
||||
[*.{cs,vb}]
|
||||
|
|
|
|||
|
|
@ -4,10 +4,16 @@
|
|||
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" Version="7.0.15" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Abstractions" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" />
|
||||
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.0" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
36
InkForge.Api.Data/ApiDbContext.cs
Normal file
36
InkForge.Api.Data/ApiDbContext.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
using InkForge.Api.Data.Infrastructure;
|
||||
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace InkForge.Api.Data;
|
||||
|
||||
public class ApiDbcontext(
|
||||
DbContextOptions<ApiDbcontext> options
|
||||
) : IdentityDbContext<IdentityUser>(options)
|
||||
{
|
||||
public DbSet<WorkspaceEntity> Workspaces { get; set; } = default!;
|
||||
|
||||
public DbSet<WorkspaceVersionEntity> WorkspaceVersions { get; set; } = default!;
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
base.OnModelCreating(builder);
|
||||
|
||||
builder.Entity<WorkspaceEntity>(options =>
|
||||
{
|
||||
options.OwnsOne(m => m.Value);
|
||||
|
||||
options.HasKey(m => m.Id);
|
||||
});
|
||||
|
||||
builder.Entity<WorkspaceVersionEntity>(options =>
|
||||
{
|
||||
options.OwnsOne(m => m.Value);
|
||||
|
||||
options.HasKey(m => m.Version);
|
||||
options.HasIndex(nameof(WorkspaceVersionEntity.Id), nameof(WorkspaceVersionEntity.Version)).IsUnique();
|
||||
});
|
||||
}
|
||||
}
|
||||
16
InkForge.Api.Data/Domain/Workspace.cs
Normal file
16
InkForge.Api.Data/Domain/Workspace.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace InkForge.Api.Data.Domain;
|
||||
|
||||
public class Workspace
|
||||
{
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
public DateTimeOffset Created { get; set; }
|
||||
|
||||
public IdentityUser Owner { get; set; } = default!;
|
||||
|
||||
public DateTimeOffset Updated { get; set; }
|
||||
|
||||
public DateTimeOffset? Deleted { get; set; }
|
||||
}
|
||||
9
InkForge.Api.Data/Infrastructure/WorkspaceEntities.cs
Normal file
9
InkForge.Api.Data/Infrastructure/WorkspaceEntities.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
using InkForge.Api.Data.Domain;
|
||||
using InkForge.Data;
|
||||
|
||||
namespace InkForge.Api.Data.Infrastructure
|
||||
{
|
||||
public class WorkspaceEntity : Entity<Workspace, int>;
|
||||
|
||||
public class WorkspaceVersionEntity : VersionedEntity<Workspace, int>;
|
||||
}
|
||||
17
InkForge.Api.Data/InkForge.Api.Data.csproj
Normal file
17
InkForge.Api.Data/InkForge.Api.Data.csproj
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\shared\InkForge.Data\InkForge.Data.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
4
InkForge.Api/Areas/Identity/Pages/_ViewStart.cshtml
Normal file
4
InkForge.Api/Areas/Identity/Pages/_ViewStart.cshtml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
@{
|
||||
Layout = "/Pages/Shared/_Layout.cshtml";
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
using Duende.IdentityServer.EntityFramework.Options;
|
||||
|
||||
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace InkForge.Api.Data;
|
||||
|
||||
public class ApiDbcontext(
|
||||
DbContextOptions options,
|
||||
IOptions<OperationalStoreOptions> operationalStoreOptions
|
||||
) : ApiAuthorizationDbContext<IdentityUser>(options, operationalStoreOptions)
|
||||
{
|
||||
}
|
||||
|
|
@ -9,11 +9,16 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" />
|
||||
<PackageReference Include="System.IO.Hashing" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\InkForge.Api.Data\InkForge.Api.Data.csproj" />
|
||||
<ProjectReference Include="..\migrations\InkForge.Api.Sqlite\InkForge.Api.Sqlite.csproj" />
|
||||
<ProjectReference Include="..\shared\InkForge.Data\InkForge.Data.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
94
InkForge.Api/Pages/Shared/_Layout.cshtml
Normal file
94
InkForge.Api/Pages/Shared/_Layout.cshtml
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
@using Microsoft.AspNetCore.Hosting
|
||||
@using Microsoft.AspNetCore.Mvc.ViewEngines
|
||||
@inject IWebHostEnvironment Environment
|
||||
@inject ICompositeViewEngine Engine
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@ViewData["Title"] - InkForge.Api</title>
|
||||
|
||||
<environment include="Development">
|
||||
<link rel="stylesheet" href="~/Identity/lib/bootstrap/dist/css/bootstrap.css" />
|
||||
<link rel="stylesheet" href="~/Identity/css/site.css" />
|
||||
</environment>
|
||||
<environment exclude="Development">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css"
|
||||
integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" crossorigin="anonymous"
|
||||
asp-fallback-href="~/Identity/lib/bootstrap/dist/css/bootstrap.min.css"
|
||||
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
|
||||
<link rel="stylesheet" href="~/Identity/css/site.css" asp-append-version="true" />
|
||||
</environment>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="~/">InkForge.Api</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
|
||||
aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
|
||||
@{
|
||||
var result = Engine.FindView(ViewContext, "_LoginPartial", isMainPage: false);
|
||||
}
|
||||
@if (result.Success)
|
||||
{
|
||||
await Html.RenderPartialAsync("_LoginPartial");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("The default Identity UI layout requires a partial view '_LoginPartial' " +
|
||||
"usually located at '/Pages/_LoginPartial' or at '/Views/Shared/_LoginPartial' to work. Based on your configuration " +
|
||||
$"we have looked at it in the following locations: {System.Environment.NewLine}{string.Join(System.Environment.NewLine, result.SearchedLocations)}.");
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
<partial name="_CookieConsentPartial" optional />
|
||||
<main role="main" class="pb-1">
|
||||
@RenderBody()
|
||||
</main>
|
||||
</div>
|
||||
<footer class="footer border-top pl-3 text-muted">
|
||||
<div class="container">
|
||||
© 2024 - InkForge.Api
|
||||
@{
|
||||
var foundPrivacy = Url.Page("/Privacy", new { area = "" });
|
||||
}
|
||||
@if (foundPrivacy != null)
|
||||
{
|
||||
<a asp-area="" asp-page="/Privacy">Privacy</a>
|
||||
}
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<environment include="Development">
|
||||
<script src="~/Identity/lib/jquery/dist/jquery.js"></script>
|
||||
<script src="~/Identity/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
|
||||
<script src="~/Identity/js/site.js" asp-append-version="true"></script>
|
||||
</environment>
|
||||
<environment exclude="Development">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"
|
||||
asp-fallback-src="~/Identity/lib/jquery/dist/jquery.min.js"
|
||||
asp-fallback-test="window.jQuery"
|
||||
crossorigin="anonymous"
|
||||
integrity="sha384-ZvpUoO/+PpLXR1lu4jmpXWu80pZlYUAfxl5NsBMWOEPSjUn/6Z/hRTt8+pR6L4N2">
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js"
|
||||
asp-fallback-src="~/Identity/lib/bootstrap/dist/js/bootstrap.bundle.min.js"
|
||||
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
|
||||
crossorigin="anonymous"
|
||||
integrity="sha384-U1DAWAznBHeqEIlVSCgzq+c9gqGAJn5c/t99JyeKa9xxaYpSvHU5awsuZVVFIhvj">
|
||||
</script>
|
||||
<script src="~/Identity/js/site.js" asp-append-version="true"></script>
|
||||
</environment>
|
||||
|
||||
@await RenderSectionAsync("Scripts", required: false)
|
||||
</body>
|
||||
</html>
|
||||
27
InkForge.Api/Pages/Shared/_LoginPartial.cshtml
Normal file
27
InkForge.Api/Pages/Shared/_LoginPartial.cshtml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
@using Microsoft.AspNetCore.Identity
|
||||
|
||||
@inject SignInManager<IdentityUser> SignInManager
|
||||
@inject UserManager<IdentityUser> UserManager
|
||||
|
||||
<ul class="navbar-nav">
|
||||
@if (SignInManager.IsSignedIn(User))
|
||||
{
|
||||
<li class="nav-item">
|
||||
<a id="manage" class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @UserManager.GetUserName(User)!</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<form id="logoutForm" class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Action("Index", "Home", new { area = "" })">
|
||||
<button id="logout" type="submit" class="nav-link btn btn-link text-dark border-0">Logout</button>
|
||||
</form>
|
||||
</li>
|
||||
}
|
||||
else
|
||||
{
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" id="register" asp-area="Identity" asp-page="/Account/Register">Register</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" id="login" asp-area="Identity" asp-page="/Account/Login">Login</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
18
InkForge.Api/Pages/Shared/_ValidationScriptsPartial.cshtml
Normal file
18
InkForge.Api/Pages/Shared/_ValidationScriptsPartial.cshtml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<environment include="Development">
|
||||
<script src="~/Identity/lib/jquery-validation/dist/jquery.validate.js"></script>
|
||||
<script src="~/Identity/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
|
||||
</environment>
|
||||
<environment exclude="Development">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.17.0/jquery.validate.min.js"
|
||||
asp-fallback-src="~/Identity/lib/jquery-validation/dist/jquery.validate.min.js"
|
||||
asp-fallback-test="window.jQuery && window.jQuery.validator"
|
||||
crossorigin="anonymous"
|
||||
integrity="sha384-rZfj/ogBloos6wzLGpPkkOr/gpkBNLZ6b6yLy4o+ok+t/SAKlL5mvXLr0OXNi1Hp">
|
||||
</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.11/jquery.validate.unobtrusive.min.js"
|
||||
asp-fallback-src="~/Identity/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
|
||||
asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive"
|
||||
crossorigin="anonymous"
|
||||
integrity="sha384-R3vNCHsZ+A2Lo3d5A6XNP7fdQkeswQWTIPfiYwSpEP3YV079R+93YzTeZRah7f/F">
|
||||
</script>
|
||||
</environment>
|
||||
6
InkForge.Api/Pages/_ViewImports.cshtml
Normal file
6
InkForge.Api/Pages/_ViewImports.cshtml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
@using Microsoft.AspNetCore.Identity
|
||||
@using InkForge.Api.Data
|
||||
|
||||
|
||||
InkForge.Api.Pages
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
3
InkForge.Api/Pages/_ViewStart.cshtml
Normal file
3
InkForge.Api/Pages/_ViewStart.cshtml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
@{
|
||||
Layout = "_Layout";
|
||||
}
|
||||
|
|
@ -1,17 +1,31 @@
|
|||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
using InkForge.Api.Data;
|
||||
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
|
||||
builder.Services.AddHttpClient();
|
||||
builder.Services.AddIdentityApiEndpoints<IdentityUser>()
|
||||
.AddEntityFrameworkStores<ApiDbcontext>()
|
||||
.AddDefaultUI().AddDefaultTokenProviders();
|
||||
var provider = builder.Configuration.GetValue<string>("DbProvider");
|
||||
builder.Services.AddDbContext<ApiDbcontext>(options => _ = provider switch
|
||||
{
|
||||
"Sqlite" => options.UseSqlite(
|
||||
builder.Configuration.GetConnectionString("AuthDb"),
|
||||
x => x.MigrationsAssembly("InkForge.Api.Sqlite")
|
||||
),
|
||||
|
||||
_ => throw new Exception($"Invalid Provider: {provider}")
|
||||
});
|
||||
|
||||
builder.Services.AddControllers();
|
||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
builder.Services.AddIdentityApiEndpoints<IdentityUser>()
|
||||
.AddEntityFrameworkStores<ApiDbcontext>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
|
|
@ -23,11 +37,15 @@ if (app.Environment.IsDevelopment())
|
|||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapIdentityApi<IdentityUser>();
|
||||
|
||||
app.MapControllers();
|
||||
app.MapRazorPages();
|
||||
|
||||
app.Run();
|
||||
|
|
|
|||
|
|
@ -4,5 +4,10 @@
|
|||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"DbProvider": "Sqlite",
|
||||
"ConnectionStrings": {
|
||||
"AuthDb": "Data Source=Identity.db",
|
||||
"WorkspaceDbTemplate": "Data Source=Workspaces/{WorkspaceId}.db"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,5 +5,10 @@
|
|||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"DbProvider": "",
|
||||
"ConnectionStrings": {
|
||||
"AuthDb": "",
|
||||
"WorkspaceDbTemplate": ""
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RootNamespace>InkForge.Desktop</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
|
|
|||
2
app/InkForge.Desktop/Program.cs
Normal file
2
app/InkForge.Desktop/Program.cs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
// See https://aka.ms/new-console-template for more information
|
||||
Console.WriteLine("Hello, World!");
|
||||
23
design/InkForge.Migrations/ApiDbContextFactory.cs
Normal file
23
design/InkForge.Migrations/ApiDbContextFactory.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
using InkForge.Api.Data;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace InkForge.Migrations;
|
||||
|
||||
public class ApiDbContextFactory : MigratingDbContextFactory<ApiDbcontext>
|
||||
{
|
||||
protected override void Configure(
|
||||
DbContextOptionsBuilder<ApiDbcontext> optionsBuilder,
|
||||
string connectionString,
|
||||
string provider
|
||||
) => _ = provider switch
|
||||
{
|
||||
"Sqlite" => optionsBuilder.UseSqlite(connectionString,
|
||||
m => m.MigrationsAssembly("InkForge.Api.Sqlite")
|
||||
),
|
||||
|
||||
_ => throw new Exception($"Invalid DbProvider: {provider}")
|
||||
};
|
||||
|
||||
protected override ApiDbcontext CreateDbContext(DbContextOptions<ApiDbcontext> options) => new(options);
|
||||
}
|
||||
24
design/InkForge.Migrations/InkForge.Migrations.csproj
Normal file
24
design/InkForge.Migrations/InkForge.Migrations.csproj
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\migrations\InkForge.Api.Sqlite\InkForge.Api.Sqlite.csproj" />
|
||||
<ProjectReference Include="..\..\shared\migrations\InkForge.Sqlite\InkForge.Sqlite.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
32
design/InkForge.Migrations/MigratingDbContextFactory.cs
Normal file
32
design/InkForge.Migrations/MigratingDbContextFactory.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
|
||||
namespace InkForge.Migrations;
|
||||
|
||||
public abstract class MigratingDbContextFactory<T> : IDesignTimeDbContextFactory<T>
|
||||
where T : DbContext
|
||||
{
|
||||
public T CreateDbContext(string[] args)
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddCommandLine(args)
|
||||
.Build();
|
||||
|
||||
var options = new DbContextOptionsBuilder<T>();
|
||||
switch (configuration.GetValue<string>("DbProvider"))
|
||||
{
|
||||
case null:
|
||||
throw new Exception("DbProvider not set.");
|
||||
|
||||
case { } provider:
|
||||
Configure(options, configuration.GetConnectionString("DefaultConnection")!, provider);
|
||||
break;
|
||||
}
|
||||
|
||||
return CreateDbContext(options.Options);
|
||||
}
|
||||
|
||||
protected abstract void Configure(DbContextOptionsBuilder<T> optionsBuilder, string connectionString, string provider);
|
||||
|
||||
protected abstract T CreateDbContext(DbContextOptions<T> options);
|
||||
}
|
||||
23
design/InkForge.Migrations/NoteDbContextFactory.cs
Normal file
23
design/InkForge.Migrations/NoteDbContextFactory.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
using InkForge.Data;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace InkForge.Migrations;
|
||||
|
||||
public class NoteDbContextFactory : MigratingDbContextFactory<NoteDbContext>
|
||||
{
|
||||
protected override void Configure(
|
||||
DbContextOptionsBuilder<NoteDbContext> optionsBuilder,
|
||||
string connectionString,
|
||||
string provider
|
||||
) => _ = provider switch
|
||||
{
|
||||
"Sqlite" => optionsBuilder.UseSqlite(connectionString,
|
||||
m => m.MigrationsAssembly("InkForge.Sqlite")
|
||||
),
|
||||
|
||||
_ => throw new Exception($"Invalid DbProvider: {provider}")
|
||||
};
|
||||
|
||||
protected override NoteDbContext CreateDbContext(DbContextOptions<NoteDbContext> options) => new(options);
|
||||
}
|
||||
17
migrations/InkForge.Api.Sqlite/InkForge.Api.Sqlite.csproj
Normal file
17
migrations/InkForge.Api.Sqlite/InkForge.Api.Sqlite.csproj
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\InkForge.Api.Data\InkForge.Api.Data.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
18
shared/InkForge.Data/Domain/Note.cs
Normal file
18
shared/InkForge.Data/Domain/Note.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
using InkForge.Data.Infrastructure;
|
||||
|
||||
namespace InkForge.Data.Domain;
|
||||
|
||||
public class Note
|
||||
{
|
||||
public DateTimeOffset Created { get; set; }
|
||||
|
||||
public NoteEntity? Parent { get; set; }
|
||||
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
public DateTimeOffset Updated { get; set; }
|
||||
|
||||
public DateTimeOffset? Deleted { get; set; }
|
||||
|
||||
public Blob Content { get; set; } = default!;
|
||||
}
|
||||
8
shared/InkForge.Data/Infrastructure/Blob.cs
Normal file
8
shared/InkForge.Data/Infrastructure/Blob.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
namespace InkForge.Data;
|
||||
|
||||
public class Blob
|
||||
{
|
||||
public string Id { get; set; } = default!;
|
||||
|
||||
public byte[] Content { get; set; } = default!;
|
||||
}
|
||||
25
shared/InkForge.Data/Infrastructure/Entities.cs
Normal file
25
shared/InkForge.Data/Infrastructure/Entities.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
using System.Numerics;
|
||||
|
||||
namespace InkForge.Data
|
||||
{
|
||||
public abstract class ValueEntity<TEntity>
|
||||
{
|
||||
public TEntity Value { get; set; } = default!;
|
||||
}
|
||||
|
||||
public abstract class Entity<TEntity, TKey>
|
||||
: ValueEntity<TEntity>
|
||||
where TKey : struct, INumber<TKey>
|
||||
{
|
||||
public TKey? Id { get; set; }
|
||||
}
|
||||
|
||||
public abstract class VersionedEntity<TEntity, TKey>
|
||||
: ValueEntity<TEntity>
|
||||
where TKey : struct, INumber<TKey>
|
||||
{
|
||||
public TKey Id { get; set; }
|
||||
|
||||
public int? Version { get; set; }
|
||||
}
|
||||
}
|
||||
8
shared/InkForge.Data/Infrastructure/NoteEntities.cs
Normal file
8
shared/InkForge.Data/Infrastructure/NoteEntities.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
using InkForge.Data.Domain;
|
||||
|
||||
namespace InkForge.Data.Infrastructure
|
||||
{
|
||||
public class NoteEntity : Entity<Note, int>;
|
||||
|
||||
public class NoteVersionEntity : VersionedEntity<Note, int>;
|
||||
}
|
||||
|
|
@ -7,4 +7,8 @@
|
|||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
34
shared/InkForge.Data/NoteDbContext.cs
Normal file
34
shared/InkForge.Data/NoteDbContext.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
using InkForge.Data.Infrastructure;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace InkForge.Data;
|
||||
|
||||
public class NoteDbContext(
|
||||
DbContextOptions<NoteDbContext> options
|
||||
) : DbContext(options)
|
||||
{
|
||||
public DbSet<Blob> Blobs { get; set; } = default!;
|
||||
|
||||
public DbSet<NoteEntity> Notes { get; set; } = default!;
|
||||
|
||||
public DbSet<NoteVersionEntity> NoteVersions { get; set; } = default!;
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<NoteEntity>(options =>
|
||||
{
|
||||
options.OwnsOne(m => m.Value);
|
||||
|
||||
options.HasKey(m => m.Id);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<NoteVersionEntity>(options =>
|
||||
{
|
||||
options.OwnsOne(m => m.Value);
|
||||
options.Property(m => m.Id).IsRequired();
|
||||
options.HasKey(m => m.Version);
|
||||
options.HasIndex(nameof(NoteVersionEntity.Id), nameof(NoteVersionEntity.Version)).IsUnique();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -7,4 +7,12 @@
|
|||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\InkForge.Data\InkForge.Data.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Loading…
Add table
Add a link
Reference in a new issue