Compare commits

...

7 Commits

Author SHA1 Message Date
Developer 02
5c51ea679e Merge branch 'master' of http://git.dd:3000/AppStd/EnvelopeGenerator 2024-06-25 13:18:05 +02:00
Developer 02
e264a10a36 String-Lokalisierer für Model.resx im Common-Projekt hinzugefügt. 2024-06-25 13:07:02 +02:00
Developer 02
dd6ab13e6e Hinzugefügt Localizer Dienste für beide api und angular 2024-06-24 15:55:27 +02:00
Developer 02
d94d9f045e Standardthema als FABRIC hinzugefügt 2024-06-24 13:29:58 +02:00
Developer 02
12bfcb1f25 Standard-Lokalisierer für generic-ui hinzugefügt. 2024-06-24 13:11:39 +02:00
Developer 02
63613f2073 Seitenverzeichnis hinzugefügt. Seite 'home' dorthin verschoben und Seite 'envelope' hinzugefügt. 2024-06-24 12:43:30 +02:00
Developer 02
1c11a0e8f0 Http-Interceptor hinzugefügt. 2024-06-24 11:56:49 +02:00
39 changed files with 510 additions and 422 deletions

View File

@@ -6,6 +6,10 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\Model.Designer.vb" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="DigitalData.Core.Abstractions" Version="1.0.0" />
@@ -41,6 +45,24 @@
</ItemGroup>
<ItemGroup>
<Compile Update="Resources\Model.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Model.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources\Model.en.resx">
<CustomToolNamespace>My.Resources</CustomToolNamespace>
<LastGenOutput>Model.en.Designer.vb</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resources\Model.resx">
<CustomToolNamespace>My.Resources</CustomToolNamespace>
<LastGenOutput>Model.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resources\Resource.de-DE.resx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>

View File

@@ -214,12 +214,15 @@
<DependentUpon>Model.en.resx</DependentUpon>
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Compile>
<Compile Include="Strings\Model.Designer.vb">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Model.resx</DependentUpon>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Compile>
<Compile Include="Strings\Model.vb" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="My Project\licenses.licx" />
@@ -256,11 +259,13 @@
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>Model.en.Designer.vb</LastGenOutput>
<CustomToolNamespace>My.Resources</CustomToolNamespace>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="Strings\Model.resx">
<CustomToolNamespace>My.Resources</CustomToolNamespace>
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>Model.Designer.vb</LastGenOutput>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,3 @@
Public Class Model
End Class

View File

@@ -100,19 +100,6 @@ namespace EnvelopeGenerator.Domain.Entities
[NotMapped]
public bool IsAlreadySent => Status > (int) Constants.EnvelopeStatus.EnvelopeSaved;
[NotMapped]
public string? StatusTranslated => Model.ResourceManager.GetString(Status.ToString());
[NotMapped]
public string? ContractTypeTranslated
{
get
{
string? oContractType = ContractType.ToString();
return oContractType is null ? default : Model.ResourceManager.GetString(oContractType);
}
}
public IEnumerable<EnvelopeDocument>? Documents { get; set; }
public IEnumerable<EnvelopeHistory>? History { get; set; }

View File

@@ -179,6 +179,7 @@
</EmbeddedResource>
<EmbeddedResource Include="frmEnvelopeEditor.resx">
<DependentUpon>frmEnvelopeEditor.vb</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="frmEnvelopeMainData.en.resx">
<DependentUpon>frmEnvelopeMainData.vb</DependentUpon>

View File

@@ -45,8 +45,8 @@
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
"maximumWarning": "1.5mb",
"maximumError": "2mb"
},
{
"type": "anyComponentStyle",
@@ -114,4 +114,4 @@
}
}
}
}
}

View File

@@ -2,7 +2,7 @@ import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { NavbarComponent } from "./components/navbar/navbar.component";
import { LoginComponent } from "./components/login/login.component";
import { HomeComponent } from "./components/home/home.component";
import { HomeComponent } from "./pages/home/home.component";
@Component({
selector: 'app-root',

View File

@@ -6,7 +6,8 @@ import { provideAnimationsAsync } from '@angular/platform-browser/animations/asy
import { APP_BASE_HREF } from '@angular/common';
import { UrlService } from './services/url.service';
import { API_URL } from './tokens/index'
import { provideHttpClient, withFetch } from '@angular/common/http';
import { HTTP_INTERCEPTORS, provideHttpClient, withFetch } from '@angular/common/http';
import { HttpRequestInterceptor } from './http.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
@@ -23,6 +24,11 @@ export const appConfig: ApplicationConfig = {
provide: API_URL,
useFactory: (urlService: UrlService) => urlService.getApiUrl(),
deps: [UrlService]
},
{
provide: HTTP_INTERCEPTORS,
useClass: HttpRequestInterceptor,
multi: true
}
]
};

View File

@@ -1,10 +1,10 @@
import { Routes } from '@angular/router';
import { EnvelopeTableComponent } from '../app/components/envelope-table/envelope-table.component'
import { HomeComponent } from '../app/components/home/home.component'
import { HomeComponent } from '../app/pages/home/home.component'
import { authGuard } from './guards/auth.guard'
import { EnvelopeComponent } from './pages/envelope/envelope.component';
export const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'login', component: HomeComponent },
{ path: 'envelope', component: EnvelopeTableComponent, canActivate: [authGuard] }
{ path: 'envelope', component: EnvelopeComponent, canActivate: [authGuard] }
];

View File

@@ -5,8 +5,9 @@
[paging]="paging"
[sorting]="sorting"
[searching]="searching"
[summaries]="summaries"
[infoPanel]="infoPanel"
[localization]="localization"
[theme]="theme"
[autoResizeWidth] = "true"
>
</gui-grid>

View File

@@ -1,7 +1,6 @@
import { Component } from '@angular/core';
import { EnvelopeReceiverService } from '../../services/envelope-receiver.service';
import { NgModule } from '@angular/core';
import { GuiColumn, GuiColumnMenu, GuiGridModule, GuiInfoPanel, GuiLocalization, GuiPaging, GuiPagingDisplay, GuiSearching, GuiSorting, GuiSummaries } from '@generic-ui/ngx-grid';
import { GuiColumn, GuiColumnMenu, GuiGridModule, GuiInfoPanel, GuiLocalization, GuiPaging, GuiPagingDisplay, GuiSearching, GuiSorting, GuiSummaries, GuiTheme } from '@generic-ui/ngx-grid';
@Component({
selector: 'app-envelope-table',
@@ -23,48 +22,43 @@ export class EnvelopeTableComponent {
multiSorting: true
};
paging: GuiPaging = {
enabled: true,
page: 1,
pageSize: 10,
pageSizes: [10, 25, 50],
pagerTop: true,
pagerBottom: true,
display: GuiPagingDisplay.ADVANCED
};
paging: GuiPaging = {
enabled: true,
page: 1,
pageSize: 10,
pageSizes: [10, 25, 50],
pagerTop: true,
pagerBottom: true,
display: GuiPagingDisplay.ADVANCED
};
searching: GuiSearching = {
enabled: true
};
searching: GuiSearching = {
enabled: true
};
summaries: GuiSummaries = {
enabled: true
};
infoPanel: GuiInfoPanel = {
enabled:true,
infoDialog:false,
columnsManager:false,
schemaManager: true
};
infoPanel: GuiInfoPanel = {
enabled: true,
infoDialog: false,
columnsManager: true,
schemaManager: true
};
localization: GuiLocalization = {
translationResolver: (key: string, value: string) => {
return '[de-DE]';
}
};
localization: GuiLocalization = {
translationResolver: (key: string, value: string) => EnvelopeTableComponent.Translation[key] ?? value
};
theme: GuiTheme = GuiTheme.FABRIC;
source: Array<any> = []
constructor(private erService: EnvelopeReceiverService) {
}
constructor(private erService: EnvelopeReceiverService) { }
ngOnInit() {
this.erService.getEnvelopeReceiver().subscribe({
next: res => this.source = res,
error: console.error
});
async ngOnInit() {
this.source = await this.erService.getEnvelopeReceiver();
}
columns: Array<GuiColumn> = [
@@ -88,4 +82,51 @@ export class EnvelopeTableComponent {
header: 'AddedWhen',
field: 'addedWhen'
}];
static readonly Translation: { [key: string]: string } = {
"sourceEmpty": "There are no items to show.",
"pagingItemsPerPage": "Items per page:",
"pagingOf": "of",
"pagingNextPage": "Next",
"pagingPrevPage": "Prev",
"pagingNoItems": "There is no items.",
"infoPanelShowing": "Showing",
"infoPanelItems": "items",
"infoPanelOutOf": "out of",
"infoPanelThemeMangerTooltipText": "Theme manager",
"infoPanelColumnManagerTooltipText": "Column manager",
"infoPanelInfoTooltipText": "info",
"themeManagerModalTitle": "Theme manager",
"themeManagerModalTheme": "Theme:",
"themeManagerModalRowColoring": "Row coloring:",
"themeManagerModalVerticalGrid": "Vertical grid",
"themeManagerModalHorizontalGrid": "HorizontalGrid",
"columnManagerModalTitle": "Manage columns",
"headerMenuMainTab": "Menu",
"headerMenuMainTabColumnSort": "Column sort",
"headerMenuMainTabHideColumn": "Hide column",
"headerMenuMainTabHighlightColumn": "Highlight",
"headerMenuMainTabMoveLeft": "Move left",
"headerMenuMainTabMoveRight": "Move right",
"headerMenuMainTabColumnSortAscending": "Ascending",
"headerMenuMainTabColumnSortDescending": "Descending",
"headerMenuMainTabColumnSortNone": "None",
"headerMenuFilterTab": "Filter",
"headerMenuColumnsTab": "Columns",
"summariesCount": "Count",
"summariesDist": "Dist",
"summariesSum": "Sum",
"summariesAvg": "Avg",
"summariesMin": "Min",
"summariesMax": "Max",
"summariesMed": "Med",
"summariesTruthy": "Truthy",
"summariesFalsy": "Falsy",
"summariesDistinctValuesTooltip": "Distinct values",
"summariesAverageTooltip": "Average",
"summariesMinTooltip": "Min",
"summariesMaxTooltip": "Max",
"summariesMedTooltip": "Median",
"summariesCountTooltip": "Number of items in the grid"
}
}

View File

@@ -3,15 +3,15 @@ import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
export const authGuard: CanActivateFn = (route, state) => {
const authService = inject(AuthService);
const router = inject(Router);
return authService.isAuthenticated().pipe(
map(isAuthenticated => {
if (!isAuthenticated) {
router.navigate(['/']);
router.navigate(['/login']);
}
return isAuthenticated;
})

View File

@@ -0,0 +1,17 @@
import { TestBed } from '@angular/core/testing';
import { HttpInterceptorFn } from '@angular/common/http';
import { httpInterceptor } from './http.interceptor';
describe('httpInterceptor', () => {
const interceptor: HttpInterceptorFn = (req, next) =>
TestBed.runInInjectionContext(() => httpInterceptor(req, next));
beforeEach(() => {
TestBed.configureTestingModule({});
});
it('should be created', () => {
expect(interceptor).toBeTruthy();
});
});

View File

@@ -0,0 +1,21 @@
import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class HttpRequestInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const secureReq = req.clone({
withCredentials: true,
setHeaders: {
'X-Insecure-Request': 'true',
'Content-Type': 'application/json',
...req.headers.keys().reduce((headers, key) => {
headers[key] = req.headers.get(key) || '';
return headers;
}, {} as { [name: string]: string })
}
});
return next.handle(secureReq);
}
}

View File

@@ -1,6 +0,0 @@
export interface EnvelopeReceiver {
name: string | null
privateMessage: string | null
addedWhen: Date
changedWhen: Date | null
}

View File

@@ -0,0 +1,10 @@
<div id="table">
<mat-tab-group>
<mat-tab label="Offene Umschläge">
<app-envelope-table></app-envelope-table>
</mat-tab>
<mat-tab label="Abgeschlossene Umschläge">
<app-envelope-table></app-envelope-table>
</mat-tab>
</mat-tab-group>
</div>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EnvelopeComponent } from './envelope.component';
describe('EnvelopeComponent', () => {
let component: EnvelopeComponent;
let fixture: ComponentFixture<EnvelopeComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [EnvelopeComponent]
})
.compileComponents();
fixture = TestBed.createComponent(EnvelopeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,24 @@
import { Component } from '@angular/core';
import { EnvelopeTableComponent } from "../../components/envelope-table/envelope-table.component";
import { MatTabsModule } from '@angular/material/tabs';
import { LocalizationService } from '../../services/localization.service';
import { firstValueFrom } from 'rxjs/internal/firstValueFrom';
@Component({
selector: 'app-envelope',
standalone: true,
templateUrl: './envelope.component.html',
styleUrl: './envelope.component.scss',
imports: [EnvelopeTableComponent, MatTabsModule]
})
export class EnvelopeComponent {
private localizer: any = {};
constructor(private localizationService: LocalizationService) {
}
async ngOnInit() {
this.localizer = await this.localizationService.getLocalizer()
}
}

View File

@@ -1,5 +1,5 @@
import { Component } from '@angular/core';
import { LoginComponent } from "../login/login.component";
import { LoginComponent } from "../../components/login/login.component";
@Component({
selector: 'app-home',

View File

@@ -1,6 +1,6 @@
import { Injectable, inject } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, catchError } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { Observable, firstValueFrom } from 'rxjs';
import { API_URL } from '../tokens/index';
@Injectable({
@@ -9,16 +9,12 @@ import { API_URL } from '../tokens/index';
export class EnvelopeReceiverService {
private url: string;
constructor(private http: HttpClient) {
constructor(private http: HttpClient) {
const api_url = inject(API_URL);
this.url = `${api_url}/envelopereceiver`;
}
getEnvelopeReceiver(): Observable<any> {
const headers = new HttpHeaders({
'Content-Type': 'application/json',
});
return this.http.get<any>(this.url, { withCredentials: true , headers });
getEnvelopeReceiver(): Promise<any> {
return firstValueFrom(this.http.get<any>(this.url));
}
}

View File

@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { LocalizationService } from './localization.service';
describe('LocalizationService', () => {
let service: LocalizationService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(LocalizationService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,21 @@
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, firstValueFrom } from 'rxjs';
import { API_URL } from '../tokens/index';
@Injectable({
providedIn: 'root'
})
export class LocalizationService {
private url: string;
constructor(private http: HttpClient) {
const api_url = inject(API_URL);
this.url = `${api_url}/localization`;
}
getLocalizer(): Promise<any> {
return firstValueFrom(this.http.get<any>(this.url));
}
}

View File

@@ -64,6 +64,7 @@ namespace EnvelopeGenerator.GeneratorAPI.Controllers
{
IsPersistent = true,
AllowRefresh = true,
ExpiresUtc = DateTime.Now.AddMinutes(180)
};
// Sign in
@@ -72,8 +73,6 @@ namespace EnvelopeGenerator.GeneratorAPI.Controllers
new ClaimsPrincipal(claimsIdentity),
authProperties);
_dirSearchService.SetSearchRootCache(user.Username, login.Password);
return Ok();
}
catch(Exception ex)

View File

@@ -0,0 +1,91 @@
using DigitalData.Core.API;
using EnvelopeGenerator.Application.Resources;
using EnvelopeGenerator.Common;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Localization;
namespace EnvelopeGenerator.GeneratorAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class LocalizationController : ControllerBase
{
private static readonly Guid L_KEY = Guid.NewGuid();
private readonly ILogger<LocalizationController> _logger;
private readonly IStringLocalizer<Model> _mLocalizer;
private readonly IStringLocalizer<Resource> _localizer;
private readonly IMemoryCache _cache;
public LocalizationController(
ILogger<LocalizationController> logger,
IStringLocalizer<Resource> localizer,
IMemoryCache memoryCache,
IStringLocalizer<Model> _modelLocalizer)
{
_logger = logger;
_localizer = localizer;
_cache = memoryCache;
_mLocalizer = _modelLocalizer;
}
[HttpGet]
public IActionResult GetAll() => Ok(_cache.GetOrCreate(Language ?? string.Empty + L_KEY, _ => _mLocalizer.ToDictionary()));
[HttpGet("lang")]
public IActionResult GetLanguage() => Language is null ? NotFound() : Ok(Language);
[HttpPost("lang")]
public IActionResult SetLanguage([FromQuery] string language)
{
if (string.IsNullOrEmpty(language))
return BadRequest();
Language = language;
return Ok();
}
[HttpDelete("lang")]
public IActionResult DeleteLanguage()
{
Language = null;
return Ok();
}
private string? Language
{
get
{
var cookieValue = Request.Cookies[CookieRequestCultureProvider.DefaultCookieName];
if (string.IsNullOrEmpty(cookieValue))
return null;
var culture = CookieRequestCultureProvider.ParseCookieValue(cookieValue)?.Cultures[0];
return culture?.Value ?? null;
}
set
{
if (value is null)
Response.Cookies.Delete(CookieRequestCultureProvider.DefaultCookieName);
else
{
var cookieOptions = new CookieOptions()
{
Expires = DateTimeOffset.UtcNow.AddYears(1),
Secure = false,
SameSite = SameSiteMode.Strict,
HttpOnly = true
};
Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(value)),
cookieOptions);
}
}
}
}
}

View File

@@ -8,7 +8,7 @@
<ItemGroup>
<PackageReference Include="DigitalData.Core.Abstractions" Version="1.0.0" />
<PackageReference Include="DigitalData.Core.API" Version="1.0.0" />
<PackageReference Include="DigitalData.Core.API" Version="1.0.1" />
<PackageReference Include="DigitalData.Core.Application" Version="1.0.0" />
<PackageReference Include="DigitalData.Core.DTO" Version="1.0.0" />
<PackageReference Include="DigitalData.Core.Infrastructure" Version="1.0.0" />
@@ -25,6 +25,7 @@
<ItemGroup>
<ProjectReference Include="..\EnvelopeGenerator.Application\EnvelopeGenerator.Application.csproj" />
<ProjectReference Include="..\EnvelopeGenerator.Common\EnvelopeGenerator.Common.vbproj" />
<ProjectReference Include="..\EnvelopeGenerator.Domain\EnvelopeGenerator.Domain.csproj" />
<ProjectReference Include="..\EnvelopeGenerator.Infrastructure\EnvelopeGenerator.Infrastructure.csproj" />
</ItemGroup>

View File

@@ -4,7 +4,9 @@ using DigitalData.UserManager.Application;
using DigitalData.UserManager.Infrastructure.Repositories;
using EnvelopeGenerator.Application;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Localization;
using Microsoft.EntityFrameworkCore;
using System.Globalization;
var builder = WebApplication.CreateBuilder(args);
@@ -44,7 +46,6 @@ builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationSc
options.Cookie.SameSite = SameSiteMode.Strict; // Protects against CSRF attacks by restricting how cookies are sent with requests from external sites
options.LoginPath = "/api/auth/login";
options.LogoutPath = "/api/auth/logout";
options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
options.SlidingExpiration = true;
});
@@ -74,7 +75,17 @@ if (app.Environment.IsDevelopment())
app.UseCors("AllowSpecificOriginsPolicy");
// Localizer
app.UseCookieBasedLocalizer("de-DE", "en-US");
string[] supportedCultureNames = { "de-DE", "en-US" };
IList<CultureInfo> list = supportedCultureNames.Select((string cn) => new CultureInfo(cn)).ToList();
CultureInfo cultureInfo = list.FirstOrDefault() ?? throw new ArgumentNullException("supportedCultureNames", "Supported cultures cannot be empty.");
RequestLocalizationOptions requestLocalizationOptions = new RequestLocalizationOptions
{
SupportedCultures = list,
SupportedUICultures = list
};
requestLocalizationOptions.RequestCultureProviders.Add(new QueryStringRequestCultureProvider());
app.UseRequestLocalization(requestLocalizationOptions);
app.UseHttpsRedirection();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long