-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implementing Language management. (#43)
- Loading branch information
Showing
51 changed files
with
1,527 additions
and
54 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
name: Build CMS Backend | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
paths: | ||
- 'backend/**' | ||
pull_request: | ||
branches: | ||
- main | ||
paths: | ||
- 'backend/**' | ||
workflow_dispatch: | ||
|
||
defaults: | ||
run: | ||
working-directory: ./backend | ||
|
||
jobs: | ||
build: | ||
name: Build CMS Backend | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Checkout Repository | ||
uses: actions/checkout@v4 | ||
|
||
- name: Setup .NET9 | ||
uses: actions/setup-dotnet@v4 | ||
with: | ||
dotnet-version: 9.x | ||
|
||
- name: Restore dependencies | ||
run: dotnet restore | ||
|
||
- name: Build Solution | ||
run: dotnet build --no-restore | ||
|
||
- name: Test Solution | ||
run: dotnet test --no-build --verbosity normal --filter Category=Unit |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
using Logitar.Cms.Core.Errors; | ||
|
||
namespace Logitar.Cms.Core; | ||
|
||
public abstract class BadRequestException : ErrorException | ||
{ | ||
protected BadRequestException(string? message, Exception? innerException = null) : base(message, innerException) | ||
{ | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
using Logitar.Cms.Core.Errors; | ||
|
||
namespace Logitar.Cms.Core; | ||
|
||
public abstract class ConflictException : ErrorException | ||
{ | ||
protected ConflictException(string? message, Exception? innerException = null) : base(message, innerException) | ||
{ | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
namespace Logitar.Cms.Core.Errors; | ||
|
||
public record Error | ||
{ | ||
public string Code { get; init; } = string.Empty; | ||
public string Message { get; init; } = string.Empty; | ||
public IReadOnlyCollection<ErrorData> Data { get; init; } = []; | ||
|
||
public Error() | ||
{ | ||
} | ||
|
||
public Error(string code, string message, IEnumerable<ErrorData>? data = null) | ||
{ | ||
Code = code; | ||
Message = message; | ||
|
||
if (data != null) | ||
{ | ||
Data = data.ToArray(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
namespace Logitar.Cms.Core.Errors; | ||
|
||
public record ErrorData | ||
{ | ||
public string Key { get; init; } = string.Empty; | ||
public object? Value { get; init; } | ||
|
||
public ErrorData() | ||
{ | ||
} | ||
|
||
public ErrorData(KeyValuePair<string, object?> pair) | ||
{ | ||
Key = pair.Key; | ||
Value = pair.Value; | ||
} | ||
|
||
public ErrorData(string key, object? value) | ||
{ | ||
Key = key; | ||
Value = value; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
namespace Logitar.Cms.Core.Errors; | ||
|
||
public abstract class ErrorException : Exception | ||
{ | ||
public abstract Error Error { get; } | ||
|
||
protected ErrorException(string? message, Exception? innerException = null) : base(message, innerException) | ||
{ | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
using Logitar.EventSourcing; | ||
|
||
namespace Logitar.Cms.Core; | ||
|
||
public interface IApplicationContext | ||
{ | ||
ActorId? ActorId { get; } | ||
} |
76 changes: 76 additions & 0 deletions
76
backend/src/Logitar.Cms.Core/Localization/Commands/CreateOrReplaceLanguageCommand.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
using FluentValidation; | ||
using Logitar.Cms.Core.Localization.Models; | ||
using Logitar.Cms.Core.Localization.Validators; | ||
using Logitar.EventSourcing; | ||
using MediatR; | ||
|
||
namespace Logitar.Cms.Core.Localization.Commands; | ||
|
||
public record CreateOrReplaceLanguageResult(LanguageModel? Language = null, bool Created = false); | ||
|
||
public record CreateOrReplaceLanguageCommand(Guid? Id, CreateOrReplaceLanguagePayload Payload, long? Version) : IRequest<CreateOrReplaceLanguageResult>; | ||
|
||
internal class CreateOrReplaceLanguageCommandHandler : IRequestHandler<CreateOrReplaceLanguageCommand, CreateOrReplaceLanguageResult> | ||
{ | ||
private readonly IApplicationContext _applicationContext; | ||
private readonly ILanguageQuerier _languageQuerier; | ||
private readonly ILanguageRepository _languageRepository; | ||
private readonly IMediator _mediator; | ||
|
||
public CreateOrReplaceLanguageCommandHandler( | ||
IApplicationContext applicationContext, | ||
ILanguageQuerier languageQuerier, | ||
ILanguageRepository languageRepository, | ||
IMediator mediator) | ||
{ | ||
_applicationContext = applicationContext; | ||
_languageQuerier = languageQuerier; | ||
_languageRepository = languageRepository; | ||
_mediator = mediator; | ||
} | ||
|
||
public async Task<CreateOrReplaceLanguageResult> Handle(CreateOrReplaceLanguageCommand command, CancellationToken cancellationToken) | ||
{ | ||
CreateOrReplaceLanguagePayload payload = command.Payload; | ||
new CreateOrReplaceLanguageValidator().ValidateAndThrow(payload); | ||
|
||
ActorId? actorId = _applicationContext.ActorId; | ||
Locale locale = new(payload.Locale); | ||
|
||
LanguageId? languageId = null; | ||
Language? language = null; | ||
if (command.Id.HasValue) | ||
{ | ||
languageId = new(command.Id.Value); | ||
language = await _languageRepository.LoadAsync(languageId.Value, cancellationToken); | ||
} | ||
|
||
bool created = false; | ||
if (language == null) | ||
{ | ||
if (command.Version.HasValue) | ||
{ | ||
return new CreateOrReplaceLanguageResult(); | ||
} | ||
|
||
language = new(locale, isDefault: false, actorId, languageId); | ||
created = true; | ||
} | ||
|
||
Language reference = (command.Version.HasValue | ||
? await _languageRepository.LoadAsync(language.Id, command.Version.Value, cancellationToken) | ||
: null) ?? language; | ||
|
||
if (reference.Locale != locale) | ||
{ | ||
language.Locale = locale; | ||
} | ||
|
||
language.Update(actorId); | ||
|
||
await _mediator.Send(new SaveLanguageCommand(language), cancellationToken); | ||
|
||
LanguageModel model = await _languageQuerier.ReadAsync(language, cancellationToken); | ||
return new CreateOrReplaceLanguageResult(model, created); | ||
} | ||
} |
35 changes: 35 additions & 0 deletions
35
backend/src/Logitar.Cms.Core/Localization/Commands/SaveLanguageCommand.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
using Logitar.Cms.Core.Localization.Events; | ||
using MediatR; | ||
|
||
namespace Logitar.Cms.Core.Localization.Commands; | ||
|
||
public record SaveLanguageCommand(Language Language) : IRequest; | ||
|
||
internal class SaveLanguageCommandHandler : IRequestHandler<SaveLanguageCommand> | ||
{ | ||
private readonly ILanguageQuerier _languageQuerier; | ||
private readonly ILanguageRepository _languageRepository; | ||
|
||
public SaveLanguageCommandHandler(ILanguageQuerier languageQuerier, ILanguageRepository languageRepository) | ||
{ | ||
_languageQuerier = languageQuerier; | ||
_languageRepository = languageRepository; | ||
} | ||
|
||
public async Task Handle(SaveLanguageCommand command, CancellationToken cancellationToken) | ||
{ | ||
Language language = command.Language; | ||
|
||
bool hasLocaleChanged = language.Changes.Any(change => change is LanguageCreated || change is LanguageUpdated updated && updated.Locale != null); | ||
if (hasLocaleChanged) | ||
{ | ||
LanguageId? conflictId = await _languageQuerier.FindIdAsync(language.Locale, cancellationToken); | ||
if (conflictId != null && !conflictId.Equals(language.Id)) | ||
{ | ||
throw new LocaleAlreadyUsedException(language, conflictId.Value); | ||
} | ||
} | ||
|
||
await _languageRepository.SaveAsync(language, cancellationToken); | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
backend/src/Logitar.Cms.Core/Localization/Commands/SetDefaultLanguageCommand.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
using Logitar.Cms.Core.Localization.Models; | ||
using Logitar.EventSourcing; | ||
using MediatR; | ||
|
||
namespace Logitar.Cms.Core.Localization.Commands; | ||
|
||
public record SetDefaultLanguageCommand(Guid Id) : IRequest<LanguageModel?>; | ||
|
||
internal class SetDefaultLanguageCommandHandler : IRequestHandler<SetDefaultLanguageCommand, LanguageModel?> | ||
{ | ||
private readonly IApplicationContext _applicationContext; | ||
private readonly ILanguageQuerier _languageQuerier; | ||
private readonly ILanguageRepository _languageRepository; | ||
|
||
public SetDefaultLanguageCommandHandler(IApplicationContext applicationContext, ILanguageQuerier languageQuerier, ILanguageRepository languageRepository) | ||
{ | ||
_applicationContext = applicationContext; | ||
_languageQuerier = languageQuerier; | ||
_languageRepository = languageRepository; | ||
} | ||
|
||
public async Task<LanguageModel?> Handle(SetDefaultLanguageCommand command, CancellationToken cancellationToken) | ||
{ | ||
LanguageId languageId = new(command.Id); | ||
Language? language = await _languageRepository.LoadAsync(languageId, cancellationToken); | ||
if (language == null) | ||
{ | ||
return null; | ||
} | ||
|
||
if (!language.IsDefault) | ||
{ | ||
LanguageId defaultId = await _languageQuerier.FindDefaultIdAsync(cancellationToken); | ||
Language @default = await _languageRepository.LoadAsync(defaultId, cancellationToken) | ||
?? throw new InvalidOperationException($"The default language 'Id={defaultId}' could not be loaded."); | ||
|
||
ActorId? actorId = _applicationContext.ActorId; | ||
@default.SetDefault(false, actorId); | ||
language.SetDefault(true, actorId); | ||
|
||
await _languageRepository.SaveAsync([@default, language], cancellationToken); | ||
} | ||
|
||
return await _languageQuerier.ReadAsync(language, cancellationToken); | ||
} | ||
} |
Oops, something went wrong.