Working on roles, users and create tenant

This commit is contained in:
Janus C. H. Knudsen 2025-01-14 23:10:30 +01:00
parent 269bf50c78
commit fcffb57ac6
21 changed files with 483 additions and 56 deletions

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfCrmConnectionInfo xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://kdtooling.kupp.at/kdtooling/schema_1.0" />

View file

@ -9,12 +9,12 @@
<None Remove="App.ts" /> <None Remove="App.ts" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Akka" Version="1.5.32" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Core\Core.csproj" /> <ProjectReference Include="..\Core\Core.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Views\Components\" />
</ItemGroup>
</Project> </Project>

View file

@ -1,23 +0,0 @@
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace Application
{
public class HelloTagHelper : TagHelper
{
public string Name { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "div"; // Output bliver et div element
output.Content.SetHtmlContent($"<h1>Hello {Name}</h1>");
}
}
public class ContentManager
{
//public string GetContent(
//
}
}

View file

@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Mvc.Razor;
namespace Application.Common
{
public class ComponentsViewLocationExpander : IViewLocationExpander
{
public void PopulateValues(ViewLocationExpanderContext context)
{
}
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
{
var componentLocations = new[]
{
"/{0}.cshtml/"
//,
//"/Components/{1}/"
};
return componentLocations.Concat(viewLocations);
}
}
}

View file

@ -0,0 +1,12 @@
<nav>
<ul>
<li><a href="/@Model.Title">Index</a></li>
<li>Overblik</li>
<li>Kalender</li>
<li>Salg</li>
<li>Kunder</li>
<li>Kassesystem</li>
<li>Statistik</li>
<li>Integrationers</li>
</ul>
</nav>

View file

@ -0,0 +1,18 @@
using Microsoft.ApplicationInsights;
using Microsoft.AspNetCore.Mvc;
namespace Application.Components.Navigation
{
public class NavigationViewComponent : ViewComponent
{
public NavigationViewComponent(TelemetryClient telemetryClient)
{
}
public IViewComponentResult Invoke()
{
return View(new { Title = "Hej", Description = "Dette edr en beskrivdelse" });
}
}
}

View file

@ -5,9 +5,10 @@
} }
<hello name="John"></hello>
<div class="text-center"> @* <hello name="John"></hello> *@
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://learn.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p> <div>
<h1>Calendar.</h1>
<calendar></calendar>
</div> </div>

View file

@ -9,7 +9,7 @@ namespace PlanTempus.Pages
{ {
private readonly TelemetryClient _telemetry; private readonly TelemetryClient _telemetry;
public IndexModel(TelemetryClient telemetry, Akka.Actor.ActorSystem system) public IndexModel(TelemetryClient telemetry)
{ {
_telemetry = telemetry; _telemetry = telemetry;

View file

@ -9,18 +9,17 @@
</head> </head>
<body> <body>
<swp-container>
<aside>
<aside-top><img src="~/images/pt-logo2.png" /></aside-top>
<div class="container"> @await Component.InvokeAsync("Navigation")
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted"> </aside>
<div class="container"> <main>@RenderBody()</main>
&copy; 2024 - PlanTempus - <a asp-area="" asp-page="/Privacy">Privacy</a> <top>top</top>
</div> <footer>footer</footer>
</footer> </swp-container>
@await RenderSectionAsync("Scripts", required: false) @await RenderSectionAsync("Scripts", required: false)

View file

@ -26,6 +26,11 @@ namespace PlanTempus
//services.AddApplicationInsightsTelemetry(ConfigurationRoot["ApplicationInsights:InstrumentationKey"]); //services.AddApplicationInsightsTelemetry(ConfigurationRoot["ApplicationInsights:InstrumentationKey"]);
services.AddAntiforgery(x => x.HeaderName = "X-ANTI-FORGERY-TOKEN"); services.AddAntiforgery(x => x.HeaderName = "X-ANTI-FORGERY-TOKEN");
services.Configure<Microsoft.AspNetCore.Mvc.Razor.RazorViewEngineOptions>(options =>
{
options.ViewLocationExpanders.Add(new Application.Common.ComponentsViewLocationExpander());
});
} }
public void ConfigureContainer(ContainerBuilder builder) public void ConfigureContainer(ContainerBuilder builder)
{ {

View file

@ -1,22 +1,171 @@
:root {
--top-height: 70px;
--footer-height: 50px;
--theme-aside-background-color: #233242;
--theme-footer-background-color: #233242;
}
@font-face {
font-family: 'rubik-regular';
src: url('../fonts/rubik-regular.woff2') format('woff2');
}
@font-face {
font-family: 'rubik';
src: url('../fonts/rubik-variablefont_wght.woff2') format('woff2');
}
.theme-light {
--theme-aside-background-color: beige;
--theme-footer-background-color: beige;
}
html { html {
font-size: 14px; font-size: 14px;
} font-family: rubik;
font-weight: 100;
@media (min-width: 768px) {
html {
font-size: 16px;
}
}
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
} }
html { html {
position: relative; display: grid;
min-height: 100%; min-height: 100%;
} }
body { body {
margin-bottom: 60px; display: grid;
margin: 0;
}
body {
overflow-x: hidden;
margin: 0;
color: #6a7a8c;
background: #f2f4f5;
position: relative
}
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: .5rem;
font-weight: 400;
line-height: 1.2;
color: var(--bs-heading-color, inherit);
}
.h1, h1 {
font-size: calc(1.35rem + 1.2vw)
}
@media (min-width: 1200px) {
.h1, h1 {
font-size: 2.25rem
}
}
.h2, h2 {
font-size: calc(1.3125rem + .75vw)
}
@media (min-width: 1200px) {
.h2, h2 {
font-size: 1.875rem
}
}
.h3, h3 {
font-size: calc(1.275rem + .3vw)
}
@media (min-width: 1200px) {
.h3, h3 {
font-size: 1.5rem
}
}
.h4, h4 {
font-size: 1.125rem
}
.h5, h5 {
font-size: 1rem
}
.h6, h6 {
font-size: .875rem
}
swp-container {
display: grid;
grid-template-columns: 250px 1.3fr;
grid-template-rows: var(--top-height) 1000px var(--footer-height);
gap: 0px 0px;
grid-auto-flow: row;
grid-template-areas:
"aside top"
"aside main"
"aside footer";
}
aside {
grid-area: aside;
background-color: var(--theme-aside-background-color);
position: sticky;
top: 0;
height: 100vh;
display: grid;
grid-template-rows: var(--top-height) auto;
}
aside aside-top {
background-color: var(--theme-background-color);
overflow: hidden;
justify-self: center;
}
aside aside-top img {
transform: scale(0.7);
}
aside nav {
margin-top: 50px;
}
aside nav ul {
display: grid;
margin-block-start: 0px;
padding-inline-start: 0px;
grid-auto-rows: 50px;
justify-items: stretch;
align-items: stretch;
}
aside nav li {
align-content: center;
color: #fff;
padding-inline: 12px 15px;
border-left: 2px solid transparent;
}
aside nav li:hover {
background: rgba(0, 0, 0, .1);
border-left: 2px solid #137eff;
}
main {
grid-area: main;
background-color: lightgray;
}
top {
grid-area: top;
background-color: #f2f4f5;
position: sticky;
top: 0;
z-index: 1;
box-shadow: 0 2px 4px rgb(200, 200, 200, 0,15);
}
footer {
grid-area: footer;
background-color: var(--theme-footer-background-color);
} }

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

7
Database/Class1.cs Normal file
View file

@ -0,0 +1,7 @@
namespace Database
{
public class Class1
{
}
}

13
Database/Database.csproj Normal file
View file

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Core\Core.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Database.Tenants
{
internal class Class1
{
private async Task InsertInitialData(string schema)
{
// Indsæt roles
var insertRoles = $@"
INSERT INTO {schema}.roles (name) VALUES
('SYSTEM_ADMIN'),
('TENANT_ADMIN'),
('POWER_USER'),
('BASIC_USER')";
await _db.ExecuteAsync(insertRoles);
// Indsæt permissions
var insertPermissions = $@"
INSERT INTO {schema}.permissions (name) VALUES
('OVERVIEW_VIEW'),
('CALENDAR_VIEW'),
('SALES_VIEW'),
('CUSTOMERS_VIEW'),
('POS_VIEW'),
('STATISTICS_VIEW')";
await _db.ExecuteAsync(insertPermissions);
// Indsæt role permissions for system admin (får alle permissions)
var insertAdminPermissions = $@"
INSERT INTO {schema}.role_permissions (role_id, permission_id)
SELECT
(SELECT id FROM {schema}.roles WHERE name = 'SYSTEM_ADMIN'),
id
FROM {schema}.permissions";
await _db.ExecuteAsync(insertAdminPermissions);
// Indsæt navigation templates
var insertTemplates = $@"
INSERT INTO {schema}.navigation_link_templates
(url, permission_id, icon, default_order)
VALUES
('/overview',
(SELECT id FROM {schema}.permissions WHERE name = 'OVERVIEW_VIEW'),
'home', 10),
('/calendar',
(SELECT id FROM {schema}.permissions WHERE name = 'CALENDAR_VIEW'),
'calendar', 20),
('/sales',
(SELECT id FROM {schema}.permissions WHERE name = 'SALES_VIEW'),
'shopping-cart', 30),
('/customers',
(SELECT id FROM {schema}.permissions WHERE name = 'CUSTOMERS_VIEW'),
'users', 40),
('/pos',
(SELECT id FROM {schema}.permissions WHERE name = 'POS_VIEW'),
'credit-card', 50),
('/statistics',
(SELECT id FROM {schema}.permissions WHERE name = 'STATISTICS_VIEW'),
'chart-bar', 60)";
await _db.ExecuteAsync(insertTemplates);
// Indsæt translations
var insertTranslations = $@"
INSERT INTO {schema}.navigation_link_template_translations
(template_id, language, display_name)
VALUES
(1, 'da-DK', 'Overblik'),
(1, 'en-US', 'Overview'),
(2, 'da-DK', 'Kalender'),
(2, 'en-US', 'Calendar'),
(3, 'da-DK', 'Salg'),
(3, 'en-US', 'Sales'),
(4, 'da-DK', 'Kunder'),
(4, 'en-US', 'Customers'),
(5, 'da-DK', 'Kassesystem'),
(5, 'en-US', 'POS System'),
(6, 'da-DK', 'Statistik'),
(6, 'en-US', 'Statistics')";
await _db.ExecuteAsync(insertTranslations);
}
}
}

View file

@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Insight.Database;
namespace Database.Tenants
{
public class TenantSetupService
{
private readonly IDbConnection _db;
public TenantSetupService(IDbConnection db)
{
_db = db;
}
public async Task CreateTenant(string schema)
{
if (!Regex.IsMatch(schema, "^[a-zA-Z0-9_]+$"))
{
throw new ArgumentException("Invalid schema name");
}
await CreateSchema(schema);
await CreateRolesTable(schema);
await CreatePermissionsTable(schema);
await CreateRolePermissionsTable(schema);
await CreateNavigationLinkTemplatesTable(schema);
await CreateNavigationLinkTemplateTranslationsTable(schema);
}
private async Task CreateSchema(string schema)
{
var sql = $"CREATE SCHEMA IF NOT EXISTS {schema}";
await _db.ExecuteAsync(sql);
}
private async Task CreateUser(string user, string password)
{
var sql = $"CREATE USER {user} WITH PASSWORD '{password}';";
await _db.ExecuteAsync(sql);
}
private async Task GrantSchemaRights(string schema, string user)
{
var sql = $"GRANT USAGE ON SCHEMA {schema} TO {user};";
await _db.ExecuteAsync(sql);
var sql1 = $"ALTER DEFAULT PRIVILEGES IN SCHEMA {schema} " +
$"GRANT ALL PRIVILEGES ON TABLES TO {user};";
await _db.ExecuteAsync(sql1);
var sql2 = $"GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA {schema} TO {user};";
await _db.ExecuteAsync(sql2);
}
private async Task CreateRolesTable(string schema)
{
var sql = $@"
CREATE TABLE IF NOT EXISTS {schema}.roles (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE
)";
await _db.ExecuteAsync(sql);
}
private async Task CreatePermissionsTable(string schema)
{
var sql = $@"
CREATE TABLE IF NOT EXISTS {schema}.permissions (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE
)";
await _db.ExecuteAsync(sql);
}
private async Task CreateRolePermissionsTable(string schema)
{
var sql = $@"
CREATE TABLE IF NOT EXISTS {schema}.role_permissions (
role_id INTEGER NOT NULL,
permission_id INTEGER NOT NULL,
PRIMARY KEY (role_id, permission_id),
FOREIGN KEY (role_id) REFERENCES {schema}.roles(id),
FOREIGN KEY (permission_id) REFERENCES {schema}.permissions(id)
)";
await _db.ExecuteAsync(sql);
}
private async Task CreateNavigationLinkTemplatesTable(string schema)
{
var sql = $@"
CREATE TABLE IF NOT EXISTS {schema}.navigation_link_templates (
id SERIAL PRIMARY KEY,
url VARCHAR(500) NOT NULL,
permission_id INTEGER NULL,
icon VARCHAR(100) NULL,
default_order INTEGER NOT NULL,
FOREIGN KEY (permission_id) REFERENCES {schema}.permissions(id)
)";
await _db.ExecuteAsync(sql);
}
private async Task CreateNavigationLinkTemplateTranslationsTable(string schema)
{
var sql = $@"
CREATE TABLE IF NOT EXISTS {schema}.navigation_link_template_translations (
id SERIAL PRIMARY KEY,
template_id INTEGER NOT NULL,
language VARCHAR(10) NOT NULL,
display_name VARCHAR(100) NOT NULL,
FOREIGN KEY (template_id) REFERENCES {schema}.navigation_link_templates(id)
)";
await _db.ExecuteAsync(sql);
}
}
}

View file

@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Application", "Application\Application.csproj", "{111CE8AE-E637-4376-A5A3-88D33E77EA88}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Application", "Application\Application.csproj", "{111CE8AE-E637-4376-A5A3-88D33E77EA88}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Database", "Database\Database.csproj", "{D5096A7F-E6D4-4C87-874E-2D9A6BEAD57F}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -27,6 +29,10 @@ Global
{111CE8AE-E637-4376-A5A3-88D33E77EA88}.Debug|Any CPU.Build.0 = Debug|Any CPU {111CE8AE-E637-4376-A5A3-88D33E77EA88}.Debug|Any CPU.Build.0 = Debug|Any CPU
{111CE8AE-E637-4376-A5A3-88D33E77EA88}.Release|Any CPU.ActiveCfg = Release|Any CPU {111CE8AE-E637-4376-A5A3-88D33E77EA88}.Release|Any CPU.ActiveCfg = Release|Any CPU
{111CE8AE-E637-4376-A5A3-88D33E77EA88}.Release|Any CPU.Build.0 = Release|Any CPU {111CE8AE-E637-4376-A5A3-88D33E77EA88}.Release|Any CPU.Build.0 = Release|Any CPU
{D5096A7F-E6D4-4C87-874E-2D9A6BEAD57F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D5096A7F-E6D4-4C87-874E-2D9A6BEAD57F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D5096A7F-E6D4-4C87-874E-2D9A6BEAD57F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D5096A7F-E6D4-4C87-874E-2D9A6BEAD57F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE