├── ModularMonolithicArch.WordGame.Presentation ├── Sections │ ├── LetterSection.razor.css │ ├── HintSection.razor │ ├── LetterSection.razor │ ├── CategoryCardSection.razor │ └── LevelCardSection.razor ├── wwwroot │ ├── background.png │ └── exampleJsInterop.js ├── ServiceCollectionExtension.cs ├── ModularMonolithicArch.WordGame.Presentation.csproj ├── _Imports.razor └── Pages │ ├── AddCategoryPage.razor │ ├── CategoriesPage.razor │ └── LevelsPage.razor ├── ModularMonolithicArch.User.Presentation ├── wwwroot │ ├── background.png │ └── exampleJsInterop.js ├── Pages │ ├── AccessDeniedPage.razor │ ├── RegisterPage.razor │ └── LoginPage.razor ├── RedirectToLogin.razor ├── _Imports.razor ├── ModularMonolithicArch.User.Presentation.csproj ├── ServiceCollectionExtension.cs ├── Sections │ └── StatusMessage.razor └── IdentityRedirectManager.cs ├── ModularMonolithicArch.TypeGame.Presentation ├── wwwroot │ ├── background.png │ └── exampleJsInterop.js ├── _Imports.razor ├── ModularMonolithicArch.TypeGame.Presentation.csproj └── Pages │ └── PlayPage.razor.css ├── ModularMonolithicArch.ImageGame.Presentation ├── wwwroot │ ├── background.png │ └── exampleJsInterop.js ├── ServiceCollectionExtension.cs ├── ModularMonolithicArch.ImageGame.Presentation.csproj ├── _Imports.razor └── Pages │ └── AddCategoryPage.razor ├── ModularMonolithicArch.Web ├── ModularMonolithicArch.Web │ ├── Components │ │ ├── Pages │ │ │ ├── Home.razor │ │ │ └── Error.razor │ │ ├── Layout │ │ │ ├── MainLayout.razor │ │ │ ├── Shared │ │ │ │ └── DropDownSection.razor │ │ │ └── MainLayout.razor.css │ │ ├── _Imports.razor │ │ ├── Routes.razor │ │ └── App.razor │ ├── wwwroot │ │ ├── favicon.png │ │ ├── Images │ │ │ └── 6a9f08d8-0e7d-4cd5-95bc-fe5f434f6577.jpg │ │ └── app.css │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── Configuration │ │ ├── ExceptionHandlerExtension.cs │ │ ├── RoleExtension.cs │ │ ├── SignalRExtension.cs │ │ ├── SwaggerOptions.cs │ │ ├── SwaggerExtension.cs │ │ ├── BlazorExtension.cs │ │ └── IdentityExtension.cs │ ├── Exceptions │ │ └── DefaultExceptionHandler.cs │ ├── Properties │ │ └── launchSettings.json │ ├── ModularMonolithicArch.Web.csproj │ └── Program.cs └── ModularMonolithicArch.Web.Client │ ├── wwwroot │ ├── appsettings.json │ └── appsettings.Development.json │ ├── Configuration │ ├── UserInfo.cs │ ├── IdentityConfigurationsExtension.cs │ └── PersistentAuthenticationStateProvider.cs │ ├── Program.cs │ ├── _Imports.razor │ ├── ServiceCollectionExtension.cs │ └── ModularMonolithicArch.Web.Client.csproj ├── ModularMonolithicArch.Shared ├── wwwroot │ └── CustomJs │ │ └── Modal.js ├── Modal │ ├── ModalResult.cs │ ├── ModalOptions.cs │ └── ModalService.cs ├── Sections │ ├── CountDownSection.razor.css │ ├── StopWatchSection.razor.css │ ├── ButtonSection.razor │ ├── InputSection.razor │ ├── AlertSection.razor │ ├── EditableSection.razor │ ├── StopWatchSection.razor │ ├── CountDownSection.razor │ ├── HealthSection.razor │ └── ModalSection.razor ├── _Imports.razor ├── ServiceCollectionExtension.cs └── ModularMonolithicArch.Shared.csproj ├── ModularMonolithicArch.UserModule ├── Shared │ ├── Persistence │ │ ├── Schema.cs │ │ └── UserContext.cs │ └── Domain │ │ └── Entity │ │ └── ApplicationUser.cs ├── Integrations │ ├── Queries │ │ ├── GetUserRequestHandler.cs │ │ ├── GetUserHealthRequestHandler.cs │ │ └── GetCurrentUserRequestHandler.cs │ └── Commands │ │ ├── DecreaceUserHealthRequestHandler.cs │ │ └── IncreaseUsersHealthRequestHandler.cs ├── ModularMonolithicArch.UserModule.csproj ├── ServiceCollectionExtension.cs └── Features │ └── Account │ ├── Login.cs │ └── Register.cs ├── ModularMonolithicArch.WordGame.Infrastructure ├── Context │ ├── Schema.cs │ └── WordGameContext.cs ├── BackroundTasks │ └── HealthBackgroundService.cs ├── ModularMonolithicArch.WordGame.Infrastructure.csproj ├── ServiceCollectionExtension.cs ├── Services │ └── CategoryService.cs └── Migrations │ └── 20240831140842_wordGame.cs ├── ModularMonolithicArch.ImageGame.Infrastructure ├── Context │ ├── Schema.cs │ └── ImageGameContext.cs ├── Hubs │ ├── ImageGameHub.cs │ └── RoomHub.cs ├── ServiceCollectionExtension.cs ├── ModularMonolithicArch.ImageGame.Infrastructure.csproj ├── Migrations │ └── 20240907110201_modify-room.cs └── Services │ └── CategoryService.cs ├── ModularMonolithicArch.User.Contract ├── Messages │ ├── Commands │ │ ├── IncreaseUsersHealthRequest.cs │ │ └── DecreaseUserHealthRequest.cs │ └── Queries │ │ ├── GetCurrentUserRequest.cs │ │ ├── GetUserRequest.cs │ │ └── GetUserHealthRequest.cs ├── ApplicationUserDto.cs └── ModularMonolithicArch.User.Contract.csproj ├── ModularMonolithicArch.WordGame.Domain ├── Core │ ├── Word.cs │ └── Letter.cs ├── Enums │ └── LevelStatus.cs ├── Base │ └── CategoryBase.cs ├── Entities │ ├── Category.cs │ └── Level.cs ├── Repository │ ├── ILetterService.cs │ ├── IWordGameService.cs │ └── IWordService.cs ├── ModularMonolithicArch.WordGame.Domain.csproj ├── Services │ ├── LetterService.cs │ ├── WordGameService.cs │ └── WordService.cs └── ServiceCollectionExtension.cs ├── ModularMonolithicArch.ImageGame.Domain ├── Base │ └── CategoryBase.cs ├── Enums │ └── BoardSize.cs ├── Repository │ ├── IImageService.cs │ └── IImageGameService.cs ├── Entities │ ├── Category.cs │ ├── Image.cs │ └── Room.cs ├── ModularMonolithicArch.ImageGame.Domain.csproj ├── Services │ ├── ImageService.cs │ └── ImageGameService.cs └── ServiceCollectionExtension.cs ├── ModularMonolithicArch.ImageGame.Application ├── Category │ ├── Dto │ │ ├── CategoryDto.cs │ │ └── CategoryDtoValidator.cs │ ├── Mapper │ │ ├── ICategoryMapper.cs │ │ └── CategoryMapper.cs │ ├── Queries │ │ ├── Requests │ │ │ └── GetCategoriesRequest.cs │ │ └── Handlers │ │ │ └── GetCategoriesRequestHandler.cs │ ├── Commands │ │ ├── Requests │ │ │ └── AddCategoryRequest.cs │ │ └── Handlers │ │ │ └── AddCategoryRequestHandler.cs │ └── Repository │ │ └── ICategoryService.cs ├── Image │ ├── Mapper │ │ ├── IImageMapper.cs │ │ └── ImageMapper.cs │ ├── Dto │ │ ├── ImageDto.cs │ │ ├── ImageValidator.cs │ │ └── IFormFileValidator.cs │ ├── Commands │ │ ├── Requests │ │ │ ├── AddImageRequest.cs │ │ │ └── UploadImageRequest.cs │ │ └── Handlers │ │ │ ├── AddImageRequestHandler.cs │ │ │ └── UploadImageRequestHandler.cs │ ├── Queries │ │ ├── Requests │ │ │ └── GetRandomImagesRequest.cs │ │ └── Handlers │ │ │ └── GetRandomImagesRequestHandler.cs │ └── Repository │ │ └── IImageService.cs ├── Room │ ├── Mapper │ │ ├── IRoomMapper.cs │ │ └── RoomMapper.cs │ ├── Commands │ │ ├── Requests │ │ │ ├── DeleteRoomRequest.cs │ │ │ ├── SetWinnerRequest.cs │ │ │ └── AddRoomRequest.cs │ │ └── Handlers │ │ │ ├── SetWinnerRequestHandler.cs │ │ │ ├── DeleteRoomRequestHandler.cs │ │ │ └── AddRoomRequestHandler.cs │ ├── Queries │ │ ├── Requests │ │ │ ├── GetRoomsRequest.cs │ │ │ └── GetRoomRequest.cs │ │ └── Handlers │ │ │ ├── GetRoomsRequestHandler.cs │ │ │ └── GetRoomRequestHandler.cs │ ├── Dto │ │ ├── RoomDtoValidator.cs │ │ └── RoomDto.cs │ └── Repository │ │ └── IRoomService.cs ├── ModularMonolithicArch.ImageGame.Application.csproj └── ServiceCollectionExtension.cs ├── ModularMonolithicArch.WordGame.Application ├── Category │ ├── Dto │ │ ├── CategoryDto.cs │ │ └── CategoryDtoValidator.cs │ ├── Mapper │ │ ├── ICategoryMapper.cs │ │ └── CategoryMapper.cs │ ├── Command │ │ ├── Requests │ │ │ └── AddCategoryRequest.cs │ │ └── Handlers │ │ │ └── AddCategoryRequestHandler.cs │ ├── Queries │ │ ├── Requests │ │ │ └── GetCategoriesRequest.cs │ │ └── Handlers │ │ │ └── GetCategoriesRequestHandler.cs │ └── Repository │ │ └── ICategoryService.cs ├── Level │ ├── Mapper │ │ ├── ILeveMapper.cs │ │ └── LeveMapper.cs │ ├── Commands │ │ ├── Requests │ │ │ ├── DeleteLevelRequest.cs │ │ │ ├── AddLevelRequest.cs │ │ │ └── UpdateLevelRequest.cs │ │ └── Handlers │ │ │ ├── DeleteLevelHandler.cs │ │ │ ├── AddLevelRequestHandler.cs │ │ │ └── UpdateLevelHandler.cs │ ├── Queries │ │ ├── Requests │ │ │ ├── GetLevelRequest.cs │ │ │ └── GetNextLevelRequest.cs │ │ └── Handlers │ │ │ ├── GetLevelRequestHandler.cs │ │ │ └── GetNextLevelRequestHandler.cs │ ├── Dto │ │ ├── LevelDto.cs │ │ └── LevelDtoValidator.cs │ └── Repository │ │ └── ILevelService.cs ├── ModularMonolithicArch.WordGame.Application.csproj └── ServiceCollectionExtension.cs ├── ModularMonolithicArch.User.Api ├── ServiceCollectionExtension.cs ├── ModularMonolithicArch.User.Api.csproj └── Endpoints │ └── Account │ ├── AccountEndpoints.cs │ └── AccountGroup.cs ├── ModularMonolithicArch.WordGame.Api ├── ModularMonolithicArch.WordGame.Api.csproj ├── ServiceCollectionExtension.cs └── Endpoints │ ├── Category │ ├── CategoryGroups.cs │ └── CategoryEndpoints.cs │ └── Level │ ├── LevelGroup.cs │ └── LevelEndpoints.cs ├── ModularMonolithicArch.ImageGame.Api ├── ModularMonolithicArch.ImageGame.Api.csproj ├── ServiceCollectionExtension.cs └── Endpoints │ ├── Category │ ├── CategoryGroup.cs │ └── CategoryEndpoints.cs │ ├── Image │ ├── ImageGroup.cs │ └── ImageEndpoints.cs │ └── Room │ ├── RoomGroup.cs │ └── RoomEndpoints.cs └── .gitattributes /ModularMonolithicArch.WordGame.Presentation/Sections/LetterSection.razor.css: -------------------------------------------------------------------------------- 1 | .one-char-input { 2 | width: 5ch; 3 | } 4 | -------------------------------------------------------------------------------- /ModularMonolithicArch.User.Presentation/wwwroot/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MohammadSajjadian/GameHub/HEAD/ModularMonolithicArch.User.Presentation/wwwroot/background.png -------------------------------------------------------------------------------- /ModularMonolithicArch.TypeGame.Presentation/wwwroot/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MohammadSajjadian/GameHub/HEAD/ModularMonolithicArch.TypeGame.Presentation/wwwroot/background.png -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Presentation/wwwroot/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MohammadSajjadian/GameHub/HEAD/ModularMonolithicArch.WordGame.Presentation/wwwroot/background.png -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Presentation/wwwroot/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MohammadSajjadian/GameHub/HEAD/ModularMonolithicArch.ImageGame.Presentation/wwwroot/background.png -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web/Components/Pages/Home.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | 3 | Home 4 | 5 |

Hello, world!

6 | 7 | Welcome to your new app. 8 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web/wwwroot/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MohammadSajjadian/GameHub/HEAD/ModularMonolithicArch.Web/ModularMonolithicArch.Web/wwwroot/favicon.png -------------------------------------------------------------------------------- /ModularMonolithicArch.Shared/wwwroot/CustomJs/Modal.js: -------------------------------------------------------------------------------- 1 | export function Show() { 2 | $('#mainModal').modal('show'); 3 | } 4 | 5 | export function Hide() { 6 | $('#mainModal').modal('hide'); 7 | } 8 | -------------------------------------------------------------------------------- /ModularMonolithicArch.UserModule/Shared/Persistence/Schema.cs: -------------------------------------------------------------------------------- 1 | namespace ModularMonolithicArch.UserModule.Shared.Persistence; 2 | 3 | internal static class Schema 4 | { 5 | internal const string User = "user"; 6 | } 7 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Shared/Modal/ModalResult.cs: -------------------------------------------------------------------------------- 1 | namespace ModularMonolithicArch.Shared.Modal; 2 | 3 | public class ModalResult 4 | { 5 | public bool IsConfirmed { get; set; } 6 | public bool IsDenied { get; set; } 7 | } 8 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web.Client/wwwroot/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Infrastructure/Context/Schema.cs: -------------------------------------------------------------------------------- 1 | namespace ModularMonolithicArch.WordGame.Infrastructure.Context; 2 | 3 | internal static class Schema 4 | { 5 | internal const string WordGame = "wordGame"; 6 | } 7 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Infrastructure/Context/Schema.cs: -------------------------------------------------------------------------------- 1 | namespace ModularMonolithicArch.ImageGame.Infrastructure.Context; 2 | 3 | internal static class Schema 4 | { 5 | internal const string ImageGame = "imageGame"; 6 | } 7 | -------------------------------------------------------------------------------- /ModularMonolithicArch.User.Contract/Messages/Commands/IncreaseUsersHealthRequest.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace ModularMonolithicArch.User.Contract.Messages.Commands; 4 | 5 | public record IncreaseUsersHealthRequest() : IRequest; 6 | -------------------------------------------------------------------------------- /ModularMonolithicArch.User.Contract/Messages/Queries/GetCurrentUserRequest.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace ModularMonolithicArch.User.Contract.Messages.Queries; 4 | 5 | public record GetCurrentUserRequest() : IRequest; 6 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web.Client/wwwroot/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Shared/Sections/CountDownSection.razor.css: -------------------------------------------------------------------------------- 1 | .clock { 2 | color: var(--color-tone-1); 3 | display: flex; 4 | justify-content: center; 5 | align-items: center; 6 | flex-grow: 1; 7 | overflow: hidden; 8 | } 9 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Shared/Sections/StopWatchSection.razor.css: -------------------------------------------------------------------------------- 1 | .clock { 2 | color: var(--color-tone-1); 3 | display: flex; 4 | justify-content: center; 5 | align-items: center; 6 | flex-grow: 1; 7 | overflow: hidden; 8 | } 9 | -------------------------------------------------------------------------------- /ModularMonolithicArch.User.Contract/Messages/Queries/GetUserRequest.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace ModularMonolithicArch.User.Contract.Messages.Queries; 4 | 5 | public record GetUserRequest(string UserName) : IRequest; 6 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Domain/Core/Word.cs: -------------------------------------------------------------------------------- 1 | namespace ModularMonolithicArch.WordGame.Domain.Core; 2 | 3 | public class Word 4 | { 5 | public string Value { get; set; } = default!; 6 | public List Letters { get; set; } = []; 7 | } 8 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Domain/Enums/LevelStatus.cs: -------------------------------------------------------------------------------- 1 | namespace ModularMonolithicArch.WordGame.Domain.Enums; 2 | 3 | public enum LevelStatus 4 | { 5 | Easy = 20, // Amount of chars to remove (20%). 6 | Medium = 50, 7 | Hard = 80, 8 | } 9 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Domain/Base/CategoryBase.cs: -------------------------------------------------------------------------------- 1 | namespace ModularMonolithicArch.ImageGame.Domain.Base; 2 | 3 | public abstract class CategoryBase 4 | { 5 | public int Id { get; set; } 6 | public string Name { get; set; } = default!; 7 | } 8 | -------------------------------------------------------------------------------- /ModularMonolithicArch.User.Contract/Messages/Commands/DecreaseUserHealthRequest.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace ModularMonolithicArch.User.Contract.Messages.Commands; 4 | 5 | public record DecreaseUserHealthRequest(string UserName) : IRequest; 6 | -------------------------------------------------------------------------------- /ModularMonolithicArch.User.Contract/Messages/Queries/GetUserHealthRequest.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace ModularMonolithicArch.User.Contract.Messages.Queries; 4 | 5 | public record GetUserHealthRequest(string UserName) : IRequest; 6 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Domain/Base/CategoryBase.cs: -------------------------------------------------------------------------------- 1 | namespace ModularMonolithicArch.WordGame.Domain.Base; 2 | 3 | public abstract class CategoryBase 4 | { 5 | public int Id { get; set; } 6 | public string Name { get; set; } = default!; 7 | } 8 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Category/Dto/CategoryDto.cs: -------------------------------------------------------------------------------- 1 | namespace ModularMonolithicArch.ImageGame.Application.Category.Dto; 2 | 3 | public class CategoryDto 4 | { 5 | public int Id { get; set; } 6 | public string Name { get; set; } = default!; 7 | } 8 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Domain/Enums/BoardSize.cs: -------------------------------------------------------------------------------- 1 | namespace ModularMonolithicArch.ImageGame.Domain.Enums; 2 | 3 | public enum BoardSize 4 | { 5 | FiveInFive = 5, 6 | TenInTen = 10, 7 | FifteenInFifteen = 15, 8 | TwentyInTwenty = 20, 9 | } 10 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web/wwwroot/Images/6a9f08d8-0e7d-4cd5-95bc-fe5f434f6577.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MohammadSajjadian/GameHub/HEAD/ModularMonolithicArch.Web/ModularMonolithicArch.Web/wwwroot/Images/6a9f08d8-0e7d-4cd5-95bc-fe5f434f6577.jpg -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Application/Category/Dto/CategoryDto.cs: -------------------------------------------------------------------------------- 1 | namespace ModularMonolithicArch.WordGame.Application.Category.Dto; 2 | 3 | public class CategoryDto 4 | { 5 | public int Id { get; set; } 6 | public string Name { get; set; } = default!; 7 | } 8 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Domain/Core/Letter.cs: -------------------------------------------------------------------------------- 1 | namespace ModularMonolithicArch.WordGame.Domain.Core; 2 | 3 | public class Letter 4 | { 5 | public char Value { get; set; } 6 | public bool IsDisable { get; set; } 7 | public bool IsCorrect { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web.Client/Configuration/UserInfo.cs: -------------------------------------------------------------------------------- 1 | namespace ModularMonolithicArch.Web.Client.Configuration; 2 | 3 | public class UserInfo 4 | { 5 | public required string UserId { get; set; } 6 | public required string Email { get; set; } 7 | } 8 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Shared/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Components.Web 2 | @using Blazored.LocalStorage 3 | @using ModularMonolithicArch.User.Contract.Messages.Queries 4 | @using ModularMonolithicArch.User.Contract.Messages.Commands 5 | @using Shared.Modal 6 | @using MediatR 7 | -------------------------------------------------------------------------------- /ModularMonolithicArch.UserModule/Shared/Domain/Entity/ApplicationUser.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | 3 | namespace ModularMonolithicArch.UserModule.Shared.Domain.Entity; 4 | 5 | public class ApplicationUser : IdentityUser 6 | { 7 | public int Health { get; set; } = 3; 8 | } 9 | -------------------------------------------------------------------------------- /ModularMonolithicArch.User.Presentation/Pages/AccessDeniedPage.razor: -------------------------------------------------------------------------------- 1 | @page "/account/accessDenied" 2 | 3 | Access denied 4 | 5 |
6 |

Access denied

7 |

You do not have access to this resource.

8 |
9 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Domain/Entities/Category.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.WordGame.Domain.Base; 2 | 3 | namespace ModularMonolithicArch.WordGame.Domain.Entities; 4 | 5 | public class Category : CategoryBase 6 | { 7 | public ICollection Levels { get; set; } = default!; 8 | } 9 | -------------------------------------------------------------------------------- /ModularMonolithicArch.TypeGame.Presentation/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Components.Web 2 | @using Microsoft.AspNetCore.Components.Forms 3 | @using static Microsoft.AspNetCore.Components.Web.RenderMode 4 | @using ModularMonolithicArch.Shared.Sections 5 | @using ModularMonolithicArch.Shared.Modal 6 | -------------------------------------------------------------------------------- /ModularMonolithicArch.User.Contract/ApplicationUserDto.cs: -------------------------------------------------------------------------------- 1 | namespace ModularMonolithicArch.User.Contract; 2 | 3 | public class ApplicationUserDto 4 | { 5 | public string Id { get; set; } = string.Empty; 6 | public string UserName { get; set; } = string.Empty; 7 | public int Health { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Application/Level/Mapper/ILeveMapper.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.WordGame.Application.Level.Dto; 2 | 3 | namespace ModularMonolithicArch.WordGame.Application.Level.Mapper; 4 | 5 | public interface ILeveMapper 6 | { 7 | Domain.Entities.Level Map(LevelDto levelDto); 8 | } 9 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Image/Mapper/IImageMapper.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.ImageGame.Application.Image.Dto; 2 | 3 | namespace ModularMonolithicArch.ImageGame.Application.Image.Mapper; 4 | 5 | public interface IImageMapper 6 | { 7 | Domain.Entities.Image Map(ImageDto imageDto); 8 | } 9 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Room/Mapper/IRoomMapper.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.ImageGame.Application.Room.Dto; 2 | 3 | namespace ModularMonolithicArch.ImageGame.Application.Room.Mapper; 4 | 5 | public interface IRoomMapper 6 | { 7 | Domain.Entities.Room Map(RoomDto roomDto, string id); 8 | } 9 | -------------------------------------------------------------------------------- /ModularMonolithicArch.User.Presentation/wwwroot/exampleJsInterop.js: -------------------------------------------------------------------------------- 1 | // This is a JavaScript module that is loaded on demand. It can export any number of 2 | // functions, and may import other JavaScript modules if required. 3 | 4 | export function showPrompt(message) { 5 | return prompt(message, 'Type anything here'); 6 | } 7 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Presentation/wwwroot/exampleJsInterop.js: -------------------------------------------------------------------------------- 1 | // This is a JavaScript module that is loaded on demand. It can export any number of 2 | // functions, and may import other JavaScript modules if required. 3 | 4 | export function showPrompt(message) { 5 | return prompt(message, 'Type anything here'); 6 | } 7 | -------------------------------------------------------------------------------- /ModularMonolithicArch.TypeGame.Presentation/wwwroot/exampleJsInterop.js: -------------------------------------------------------------------------------- 1 | // This is a JavaScript module that is loaded on demand. It can export any number of 2 | // functions, and may import other JavaScript modules if required. 3 | 4 | export function showPrompt(message) { 5 | return prompt(message, 'Type anything here'); 6 | } 7 | -------------------------------------------------------------------------------- /ModularMonolithicArch.User.Presentation/RedirectToLogin.razor: -------------------------------------------------------------------------------- 1 | @inject NavigationManager NavigationManager 2 | 3 | @code { 4 | protected override void OnInitialized() 5 | { 6 | NavigationManager.NavigateTo($"/account/login?returnUrl={Uri.EscapeDataString(NavigationManager.Uri)}", forceLoad: true); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Domain/Repository/ILetterService.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.WordGame.Domain.Core; 2 | 3 | namespace ModularMonolithicArch.WordGame.Domain.Repository; 4 | 5 | public interface ILetterService 6 | { 7 | void MarkAsDisable(Letter letter); 8 | void MarkAsCorrect(Letter letter); 9 | } 10 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Presentation/wwwroot/exampleJsInterop.js: -------------------------------------------------------------------------------- 1 | // This is a JavaScript module that is loaded on demand. It can export any number of 2 | // functions, and may import other JavaScript modules if required. 3 | 4 | export function showPrompt(message) { 5 | return prompt(message, 'Type anything here'); 6 | } 7 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Category/Mapper/ICategoryMapper.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.ImageGame.Application.Category.Dto; 2 | 3 | namespace ModularMonolithicArch.ImageGame.Application.Category.Mapper; 4 | 5 | public interface ICategoryMapper 6 | { 7 | Domain.Entities.Category Map(CategoryDto categoryDto); 8 | } 9 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Domain/Repository/IImageService.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.ImageGame.Domain.Entities; 2 | 3 | namespace ModularMonolithicArch.ImageGame.Domain.Repository; 4 | 5 | public interface IImageService 6 | { 7 | void MarkAsVisible(Image image); 8 | void MarkAsInVisible(Image image); 9 | } 10 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Application/Category/Mapper/ICategoryMapper.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.WordGame.Application.Category.Dto; 2 | 3 | namespace ModularMonolithicArch.WordGame.Application.Category.Mapper; 4 | 5 | public interface ICategoryMapper 6 | { 7 | Domain.Entities.Category Map(CategoryDto categoryDto); 8 | } 9 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Shared/Sections/ButtonSection.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | @code { 4 | 5 | [Parameter, EditorRequired] 6 | public string Text { get; set; } = default!; 7 | 8 | [Parameter] public string? Class { get; set; } 9 | [Parameter] public EventCallback Action { get; set; } 10 | } 11 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Domain/Entities/Category.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.ImageGame.Domain.Base; 2 | 3 | namespace ModularMonolithicArch.ImageGame.Domain.Entities; 4 | 5 | public class Category : CategoryBase 6 | { 7 | public ICollection Rooms { get; set; } = default!; 8 | public ICollection Images { get; set; } = default!; 9 | } 10 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Shared/Sections/InputSection.razor: -------------------------------------------------------------------------------- 1 |
2 | 3 | @ChildContent 4 |
5 | 6 | @code { 7 | [Parameter, EditorRequired] 8 | public string Label { get; set; } = default!; 9 | 10 | [Parameter, EditorRequired] 11 | public RenderFragment ChildContent { get; set; } = default!; 12 | } 13 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Application/Category/Dto/CategoryDtoValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | 3 | namespace ModularMonolithicArch.WordGame.Application.Category.Dto; 4 | 5 | public class CategoryDtoValidator : AbstractValidator 6 | { 7 | public CategoryDtoValidator() 8 | { 9 | RuleFor(c => c.Name).NotEmpty(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Image/Dto/ImageDto.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.ImageGame.Application.Category.Dto; 2 | 3 | namespace ModularMonolithicArch.ImageGame.Application.Image.Dto; 4 | 5 | public class ImageDto 6 | { 7 | public int Id { get; set; } 8 | public string Name { get; set; } = default!; 9 | public CategoryDto CategoryDto { get; set; } = new(); 10 | } 11 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Image/Dto/ImageValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | 3 | namespace ModularMonolithicArch.ImageGame.Application.Image.Dto; 4 | 5 | public class ImageValidator : AbstractValidator 6 | { 7 | public ImageValidator() 8 | { 9 | RuleFor(i => i.CategoryDto.Id).NotEmpty().WithMessage("'Category' must not be empty."); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Room/Commands/Requests/DeleteRoomRequest.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace ModularMonolithicArch.ImageGame.Application.Room.Commands.Requests; 4 | 5 | public record DeleteRoomRequest(int Id) : IRequest 6 | { 7 | public const string route = "/imageGame/room/id"; 8 | 9 | public record Response(int StatusCode); 10 | } 11 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Category/Dto/CategoryDtoValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | 3 | namespace ModularMonolithicArch.ImageGame.Application.Category.Dto; 4 | 5 | public class CategoryDtoValidator : AbstractValidator 6 | { 7 | public CategoryDtoValidator() 8 | { 9 | RuleFor(c => c.Name).NotEmpty().WithMessage("'Name' must not be empty."); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Room/Commands/Requests/SetWinnerRequest.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace ModularMonolithicArch.ImageGame.Application.Room.Commands.Requests; 4 | 5 | public record SetWinnerRequest(int Id, int creatorScore, int guestScore) : IRequest 6 | { 7 | public const string route = "/imageGame/room/scores"; 8 | 9 | public record Response(); 10 | } 11 | -------------------------------------------------------------------------------- /ModularMonolithicArch.User.Contract/ModularMonolithicArch.User.Contract.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Presentation/Sections/HintSection.razor: -------------------------------------------------------------------------------- 1 |
2 |
3 |
💡Hint
4 |

@Text

5 |
6 |
7 | 8 | @code { 9 | [Parameter, EditorRequired] public string Text { get; set; } = default!; 10 | } 11 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Application/Level/Commands/Requests/DeleteLevelRequest.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace ModularMonolithicArch.WordGame.Application.Level.Commands.Requests; 4 | 5 | public record DeleteLevelRequest(int Id) : IRequest 6 | { 7 | public const string route = "/wordGame/level/{id}"; 8 | 9 | public record Response(int StatusCode, string? Message = null); 10 | } 11 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Image/Mapper/ImageMapper.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.ImageGame.Application.Image.Dto; 2 | 3 | namespace ModularMonolithicArch.ImageGame.Application.Image.Mapper; 4 | 5 | public class ImageMapper : IImageMapper 6 | { 7 | public Domain.Entities.Image Map(ImageDto imageDto) 8 | => new() 9 | { 10 | CategoryId = imageDto.CategoryDto.Id, 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | 9 | "ConnectionStrings": { 10 | "DbGame": "Data Source=MOHAMMAD-PC2\\MSSQLSERVER2019;Initial Catalog=DBGame;Integrated Security=True;Encrypt=True;Trust Server Certificate=True" 11 | }, 12 | 13 | "AllowedHosts": "*" 14 | } 15 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Category/Mapper/CategoryMapper.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.ImageGame.Application.Category.Dto; 2 | 3 | namespace ModularMonolithicArch.ImageGame.Application.Category.Mapper; 4 | 5 | public class CategoryMapper : ICategoryMapper 6 | { 7 | public Domain.Entities.Category Map(CategoryDto categoryDto) 8 | => new() 9 | { 10 | Name = categoryDto.Name 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Shared/Sections/AlertSection.razor: -------------------------------------------------------------------------------- 1 | 5 | 6 | @code { 7 | [Parameter, EditorRequired] public string Status { get; set; } = string.Empty; 8 | [Parameter, EditorRequired] public string Message { get; set; } = string.Empty; 9 | } 10 | -------------------------------------------------------------------------------- /ModularMonolithicArch.User.Presentation/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Authorization 2 | @using Microsoft.AspNetCore.Components.Web 3 | @using Microsoft.AspNetCore.Authentication 4 | @using Microsoft.AspNetCore.Identity 5 | @using Microsoft.AspNetCore.Components.Forms 6 | @using Microsoft.AspNetCore.Http 7 | @using ModularMonolithicArch.User.Presentation.Sections 8 | @using ModularMonolithicArch.UserModule.Features.Account 9 | @using MediatR 10 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web.Client/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 2 | using ModularMonolithicArch.Web.Client; 3 | using ModularMonolithicArch.Web.Client.Configuration; 4 | 5 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 6 | 7 | builder.Services.ConfigureSharedWebClient(builder.Configuration); 8 | builder.Services.ConfigureIdentity(); 9 | 10 | await builder.Build().RunAsync(); 11 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Room/Queries/Requests/GetRoomsRequest.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.ImageGame.Application.Room.Dto; 3 | 4 | namespace ModularMonolithicArch.ImageGame.Application.Room.Queries.Requests; 5 | 6 | public record GetRoomsRequest : IRequest 7 | { 8 | public const string route = "/imageGame/room/all"; 9 | 10 | public record Response(List? RoomDtos); 11 | } 12 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Domain/ModularMonolithicArch.ImageGame.Domain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web.Client/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using static Microsoft.AspNetCore.Components.Web.RenderMode 7 | @using Microsoft.AspNetCore.Components.Web.Virtualization 8 | @using Microsoft.JSInterop 9 | @using ModularMonolithicArch.Web.Client 10 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Domain/ModularMonolithicArch.WordGame.Domain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Room/Queries/Requests/GetRoomRequest.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.ImageGame.Application.Room.Dto; 3 | 4 | namespace ModularMonolithicArch.ImageGame.Application.Room.Queries.Requests; 5 | 6 | public record GetRoomRequest(int Id) : IRequest 7 | { 8 | public const string route = "/imageGame/room/id"; 9 | 10 | public record Response(RoomDto? RoomDto, string? Message = null); 11 | } 12 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Shared/Sections/EditableSection.razor: -------------------------------------------------------------------------------- 1 | @if (IsEditable) 2 | { 3 | @MainContent 4 | } 5 | else 6 | { 7 | @EditContent 8 | } 9 | 10 | @code { 11 | [Parameter, EditorRequired] 12 | public bool IsEditable { get; set; } 13 | 14 | [Parameter, EditorRequired] 15 | public RenderFragment MainContent { get; set; } = default!; 16 | 17 | [Parameter, EditorRequired] 18 | public RenderFragment EditContent { get; set; } = default!; 19 | } 20 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Domain/Services/ImageService.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.ImageGame.Domain.Entities; 2 | using ModularMonolithicArch.ImageGame.Domain.Repository; 3 | 4 | namespace ModularMonolithicArch.ImageGame.Domain.Services; 5 | 6 | public class ImageService : IImageService 7 | { 8 | public void MarkAsInVisible(Image image) 9 | => image.IsVisible = false; 10 | 11 | public void MarkAsVisible(Image image) 12 | => image.IsVisible = true; 13 | } 14 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Application/Level/Commands/Requests/AddLevelRequest.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.WordGame.Application.Level.Dto; 3 | 4 | namespace ModularMonolithicArch.WordGame.Application.Level.Commands.Requests; 5 | 6 | public record AddLevelRequest(LevelDto LevelDto) : IRequest 7 | { 8 | public const string route = "/wordGame/level"; 9 | 10 | public record Response(int StatusCode, string? Message = null); 11 | } 12 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Domain/Services/LetterService.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.WordGame.Domain.Core; 2 | using ModularMonolithicArch.WordGame.Domain.Repository; 3 | 4 | namespace ModularMonolithicArch.WordGame.Domain.Services; 5 | 6 | public class LetterService : ILetterService 7 | { 8 | public void MarkAsDisable(Letter letter) 9 | => letter.IsDisable = true; 10 | 11 | public void MarkAsCorrect(Letter letter) 12 | => letter.IsCorrect = true; 13 | } 14 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Category/Queries/Requests/GetCategoriesRequest.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.ImageGame.Application.Category.Dto; 3 | 4 | namespace ModularMonolithicArch.ImageGame.Application.Category.Queries.Requests; 5 | 6 | public record GetCategoriesRequest : IRequest 7 | { 8 | public const string route = "/imageGame/category"; 9 | 10 | public record Response(List? CategoryDtos); 11 | } 12 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Application/Category/Mapper/CategoryMapper.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.WordGame.Application.Category.Dto; 2 | 3 | namespace ModularMonolithicArch.WordGame.Application.Category.Mapper; 4 | 5 | public class CategoryMapper : ICategoryMapper 6 | { 7 | public Domain.Entities.Category Map(CategoryDto categoryDto) 8 | { 9 | return new Domain.Entities.Category 10 | { 11 | Name = categoryDto.Name 12 | }; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Domain/Repository/IWordGameService.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.WordGame.Domain.Core; 2 | 3 | namespace ModularMonolithicArch.WordGame.Domain.Repository; 4 | 5 | public interface IWordGameService 6 | { 7 | List RandomIndices { get; } 8 | bool IsGuideActive { get; } 9 | string Hint { get; } 10 | 11 | void Initialize(Word word, string hint, int percentageOfCharsToHide); 12 | bool IsAnswerCorrect(); 13 | void EnableGuide(); 14 | } 15 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Application/Level/Commands/Requests/UpdateLevelRequest.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.WordGame.Application.Level.Dto; 3 | 4 | namespace ModularMonolithicArch.WordGame.Application.Level.Commands.Requests; 5 | 6 | public record UpdateLevelRequest(LevelDto LevelDto) : IRequest 7 | { 8 | public const string route = "/wordGame/level"; 9 | 10 | public record Response(int StatusCode, string? Messgae = null); 11 | } 12 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Image/Commands/Requests/AddImageRequest.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.ImageGame.Application.Image.Dto; 3 | 4 | namespace ModularMonolithicArch.ImageGame.Application.Image.Commands.Requests; 5 | 6 | public record AddImageRequest(ImageDto ImageDto) : IRequest 7 | { 8 | public const string route = "/imageGame/image"; 9 | 10 | public record Response(int StatusCode, int ImageId, string? Message = null); 11 | } 12 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Domain/Entities/Image.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations.Schema; 2 | 3 | namespace ModularMonolithicArch.ImageGame.Domain.Entities; 4 | 5 | public class Image 6 | { 7 | public int Id { get; set; } 8 | public string? Name { get; set; } 9 | [NotMapped] 10 | public bool IsVisible { get; set; } 11 | 12 | public int CategoryId { get; set; } 13 | [ForeignKey(nameof(CategoryId))] 14 | public Category Category { get; set; } = default!; 15 | } 16 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Application/Level/Queries/Requests/GetLevelRequest.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.WordGame.Application.Level.Dto; 3 | 4 | namespace ModularMonolithicArch.WordGame.Application.Level.Queries.Requests; 5 | 6 | public record GetLevelRequest(int Id) : IRequest 7 | { 8 | public const string route = "/wordGame/level/id"; 9 | 10 | public record Response(LevelDto? LevelDto, int? StatusCode = null, string? Message = null); 11 | } 12 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Category/Commands/Requests/AddCategoryRequest.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.ImageGame.Application.Category.Dto; 3 | 4 | namespace ModularMonolithicArch.ImageGame.Application.Category.Commands.Requests; 5 | 6 | public record AddCategoryRequest(CategoryDto CategoryDto) : IRequest 7 | { 8 | public const string route = "/imageGame/category"; 9 | 10 | public record Response(int StatusCode, string? Message); 11 | } 12 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Image/Commands/Requests/UploadImageRequest.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using Microsoft.AspNetCore.Components.Forms; 3 | 4 | namespace ModularMonolithicArch.ImageGame.Application.Image.Commands.Requests; 5 | 6 | public record UploadImageRequest(int ImageId, IBrowserFile File) : IRequest 7 | { 8 | public const string route = "/imageGame/image/upload/imageId"; 9 | 10 | public record Response(int StatusCode, string? Message = null); 11 | } 12 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Application/Category/Command/Requests/AddCategoryRequest.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.WordGame.Application.Category.Dto; 3 | 4 | namespace ModularMonolithicArch.WordGame.Application.Category.Command.Requests; 5 | 6 | public record AddCategoryRequest(CategoryDto CategoryDto) : IRequest 7 | { 8 | public const string route = "/wordGame/category"; 9 | 10 | public record Response(int? StatusCode = null, string? Message = null); 11 | } 12 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Application/Category/Queries/Requests/GetCategoriesRequest.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.WordGame.Application.Category.Dto; 3 | 4 | namespace ModularMonolithicArch.WordGame.Application.Category.Queries.Requests; 5 | 6 | public record GetCategoriesRequest : IRequest 7 | { 8 | public const string route = "/wordGame/category"; 9 | 10 | public record Response(List? CategoryDtos, int? StatusCode = null, string? Message = null); 11 | } 12 | -------------------------------------------------------------------------------- /ModularMonolithicArch.User.Api/ServiceCollectionExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Routing; 2 | using ModularMonolithicArch.User.Api.Endpoints.Account; 3 | 4 | namespace ModularMonolithicArch.User.Infrastructure; 5 | 6 | public static class ServiceCollectionExtension 7 | { 8 | public static IEndpointRouteBuilder ConfigureUserModuleEndpoints(this IEndpointRouteBuilder app) 9 | { 10 | ArgumentNullException.ThrowIfNull(app); 11 | 12 | app.MapLogOutEndpoint(); 13 | 14 | return app; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Api/ModularMonolithicArch.WordGame.Api.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Image/Queries/Requests/GetRandomImagesRequest.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.ImageGame.Application.Image.Dto; 3 | 4 | namespace ModularMonolithicArch.ImageGame.Application.Image.Queries.Requests; 5 | 6 | public record GetRandomImagesRequest(int CategoryId, int BoardSize, int Seed) : IRequest 7 | { 8 | public const string route = "/imageGame/image/random/categoryId/boardSize/seed"; 9 | 10 | public record Response(List? ImageDtos); 11 | } 12 | -------------------------------------------------------------------------------- /ModularMonolithicArch.User.Api/ModularMonolithicArch.User.Api.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Room/Commands/Requests/AddRoomRequest.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.ImageGame.Application.Room.Dto; 3 | using static ModularMonolithicArch.ImageGame.Application.Room.Commands.Requests.AddRoomRequest; 4 | 5 | namespace ModularMonolithicArch.ImageGame.Application.Room.Commands.Requests; 6 | 7 | public record AddRoomRequest(RoomDto RoomDto) : IRequest 8 | { 9 | public const string route = "/imageGame/room"; 10 | 11 | public record Response(int StatucCode, string? Message = null); 12 | } 13 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Application/Level/Queries/Requests/GetNextLevelRequest.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.WordGame.Application.Level.Dto; 3 | 4 | namespace ModularMonolithicArch.WordGame.Application.Level.Queries.Requests; 5 | 6 | public record GetNextLevelRequest(int Id, int CategoryId) : IRequest 7 | { 8 | public const string route = "/wordGame/level/{id}/category/{categoryId}/next"; 9 | 10 | public record GetNextLevelResponse(LevelDto? LevelDto, int? StatusCode = null, string? Message = null); 11 | } 12 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Api/ModularMonolithicArch.ImageGame.Api.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Domain/Repository/IWordService.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.WordGame.Domain.Core; 2 | 3 | namespace ModularMonolithicArch.WordGame.Domain.Repository; 4 | 5 | public interface IWordService 6 | { 7 | List RemoveRandomLetters(Word word, int percentageOfCharsToHide); 8 | void DisableRemainingLetters(Word word, List randomIndices); 9 | void ClearLettersAtRandomIndices(Word word, List randomIndices); 10 | void MakeLettersGreen(Word word, string insertedLetter, List randomIndices); 11 | bool IsEmptyLetterExist(Word word); 12 | } 13 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Application/Level/Dto/LevelDto.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.WordGame.Application.Category.Dto; 2 | using ModularMonolithicArch.WordGame.Domain.Enums; 3 | 4 | namespace ModularMonolithicArch.WordGame.Application.Level.Dto; 5 | 6 | public class LevelDto 7 | { 8 | public int Id { get; set; } 9 | public int LevelNumber { get; set; } 10 | public string Word { get; set; } = default!; 11 | public string Hint { get; set; } = default!; 12 | 13 | public CategoryDto CategoryDto { get; set; } = new(); 14 | public LevelStatus LevelStatus { get; set; } 15 | } 16 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Infrastructure/Context/WordGameContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using ModularMonolithicArch.WordGame.Domain.Entities; 3 | 4 | namespace ModularMonolithicArch.WordGame.Infrastructure.Context; 5 | 6 | public class WordGameContext(DbContextOptions options) : DbContext(options) 7 | { 8 | public DbSet Categories { get; set; } 9 | public DbSet Levels { get; set; } 10 | 11 | protected override void OnModelCreating(ModelBuilder builder) 12 | { 13 | builder.HasDefaultSchema(Schema.WordGame); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Application/Level/Mapper/LeveMapper.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.WordGame.Application.Level.Dto; 2 | 3 | namespace ModularMonolithicArch.WordGame.Application.Level.Mapper; 4 | 5 | public class LeveMapper : ILeveMapper 6 | { 7 | public Domain.Entities.Level Map(LevelDto levelDto) 8 | => new() 9 | { 10 | LevelNumber = levelDto.LevelNumber, 11 | Word = levelDto.Word.ToLower(), 12 | Hint = levelDto.Hint, 13 | CategoryId = levelDto.CategoryDto.Id, 14 | LevelStatus = levelDto.LevelStatus 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /ModularMonolithicArch.UserModule/Shared/Persistence/UserContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore; 3 | using ModularMonolithicArch.UserModule.Shared.Domain.Entity; 4 | 5 | namespace ModularMonolithicArch.UserModule.Shared.Persistence; 6 | 7 | public class UserContext(DbContextOptions options) : IdentityDbContext(options) 8 | { 9 | protected override void OnModelCreating(ModelBuilder builder) 10 | { 11 | builder.HasDefaultSchema(Schema.User); 12 | 13 | base.OnModelCreating(builder); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Category/Repository/ICategoryService.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.ImageGame.Application.Category.Dto; 2 | 3 | namespace ModularMonolithicArch.ImageGame.Application.Category.Repository; 4 | 5 | public interface ICategoryService 6 | { 7 | Task CreateAsync(CategoryDto categoryDto, CancellationToken cancellationToken); 8 | Task?> GetAllAsync(CancellationToken cancellationToken); 9 | Task DeleteAsync(int id, CancellationToken cancellationToken); 10 | Task UpdateAsync(CategoryDto categoryDto, CancellationToken cancellationToken); 11 | } 12 | -------------------------------------------------------------------------------- /ModularMonolithicArch.User.Api/Endpoints/Account/AccountEndpoints.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Identity; 3 | using Microsoft.AspNetCore.Mvc; 4 | using ModularMonolithicArch.UserModule.Shared.Domain.Entity; 5 | 6 | namespace ModularMonolithicArch.User.Api.Endpoints.Account; 7 | 8 | public static class AccountEndpoints 9 | { 10 | public static async Task LogOutAsync(SignInManager signInManager, [FromForm] string returnUrl) 11 | { 12 | await signInManager.SignOutAsync(); 13 | return Results.LocalRedirect($"~/{returnUrl}"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Application/Category/Repository/ICategoryService.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.WordGame.Application.Category.Dto; 2 | 3 | namespace ModularMonolithicArch.WordGame.Application.Category.Repository; 4 | 5 | public interface ICategoryService 6 | { 7 | Task CreateAsync(CategoryDto categoryDto, CancellationToken cancellationToken); 8 | Task DeleteAsync(int id, CancellationToken cancellationToken); 9 | Task UpdateAsync(CategoryDto categoryDto, CancellationToken cancellationToken); 10 | Task?> GetAllAsync(CancellationToken cancellationToken); 11 | } 12 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Domain/Repository/IImageGameService.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.ImageGame.Domain.Entities; 2 | using ModularMonolithicArch.ImageGame.Domain.Services; 3 | 4 | namespace ModularMonolithicArch.ImageGame.Domain.Repository; 5 | 6 | public interface IImageGameService 7 | { 8 | bool CreatorTurn { get; } 9 | int CreatorScore { get; } 10 | int GuestScore { get; } 11 | List Images { get; set; } 12 | event EventHandler OnEndGame; 13 | 14 | void Initialize(List images, int seed); 15 | Task AddSelectedItem(Image image); 16 | void ResetStates(); 17 | } 18 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Room/Dto/RoomDtoValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | 3 | namespace ModularMonolithicArch.ImageGame.Application.Room.Dto; 4 | 5 | public class RoomDtoValidator : AbstractValidator 6 | { 7 | public RoomDtoValidator() 8 | { 9 | RuleFor(r => r.CategoryId).NotEmpty().WithMessage("'Category' must not be empty."); 10 | 11 | RuleFor(r => r.Time) 12 | .NotEmpty().WithMessage("'Game Time' must not be empty.") 13 | .GreaterThanOrEqualTo(20) 14 | .LessThanOrEqualTo(120); 15 | 16 | RuleFor(r => r.BoardSize).NotEmpty(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web/Components/Layout/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 |
4 | 7 | 8 |
9 |
10 | 11 |
12 | 13 |
14 | @Body 15 |
16 |
17 |
18 | 19 |
20 | An unhandled error has occurred. 21 | Reload 22 | 🗙 23 |
24 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Api/ServiceCollectionExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Routing; 2 | using ModularMonolithicArch.WordGame.Api.Endpoints.Category; 3 | using ModularMonolithicArch.WordGame.Api.Endpoints.Level; 4 | 5 | namespace ModularMonolithicArch.WordGame.Infrastructure; 6 | 7 | public static class ServiceCollectionExtension 8 | { 9 | public static IEndpointRouteBuilder ConfigureWordGameModuleEndpoints(this IEndpointRouteBuilder app) 10 | { 11 | ArgumentNullException.ThrowIfNull(app); 12 | 13 | app.MapCategoryEndpoints(); 14 | app.MapLevelEndpoints(); 15 | 16 | return app; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Infrastructure/Context/ImageGameContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using ModularMonolithicArch.ImageGame.Domain.Entities; 3 | 4 | namespace ModularMonolithicArch.ImageGame.Infrastructure.Context; 5 | 6 | public class ImageGameContext(DbContextOptions options) : DbContext(options) 7 | { 8 | public DbSet Categories { get; set; } 9 | public DbSet Images { get; set; } 10 | public DbSet Rooms { get; set; } 11 | 12 | protected override void OnModelCreating(ModelBuilder builder) 13 | { 14 | builder.HasDefaultSchema(Schema.ImageGame); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Domain/Entities/Level.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.WordGame.Domain.Enums; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace ModularMonolithicArch.WordGame.Domain.Entities; 5 | 6 | public class Level 7 | { 8 | public int Id { get; set; } 9 | public int LevelNumber { get; set; } 10 | public string Word { get; set; } = default!; 11 | public string Hint { get; set; } = default!; 12 | 13 | public LevelStatus LevelStatus { get; set; } 14 | 15 | public int CategoryId { get; set; } 16 | [ForeignKey(nameof(CategoryId))] 17 | public Category Category { get; set; } = default!; 18 | } 19 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web/Components/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Authorization 6 | @using Microsoft.AspNetCore.Components.Web 7 | @using static Microsoft.AspNetCore.Components.Web.RenderMode 8 | @using Microsoft.AspNetCore.Components.Web.Virtualization 9 | @using Microsoft.JSInterop 10 | @using ModularMonolithicArch.Web 11 | @using ModularMonolithicArch.Web.Client 12 | @using ModularMonolithicArch.Web.Components 13 | @using ModularMonolithicArch.Web.Components.Layout.Shared 14 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Presentation/Sections/LetterSection.razor: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 | 7 | @code { 8 | [Parameter, EditorRequired] 9 | public Letter? Letter { get; set; } 10 | 11 | [Parameter, EditorRequired] 12 | public EventCallback CheckAnswer { get; set; } 13 | 14 | [Parameter] 15 | public bool IsGuideActive { get; set; } 16 | } 17 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Domain/ServiceCollectionExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using ModularMonolithicArch.ImageGame.Domain.Repository; 3 | using ModularMonolithicArch.ImageGame.Domain.Services; 4 | 5 | namespace ModularMonolithicArch.ImageGame.Domain; 6 | 7 | public static class ServiceCollectionExtension 8 | { 9 | public static IServiceCollection AddDomain(this IServiceCollection services) 10 | { 11 | ArgumentNullException.ThrowIfNull(services); 12 | 13 | services.AddScoped(); 14 | services.AddScoped(); 15 | 16 | return services; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ModularMonolithicArch.User.Presentation/ModularMonolithicArch.User.Presentation.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Presentation/Sections/CategoryCardSection.razor: -------------------------------------------------------------------------------- 1 | 10 | 11 | @code { 12 | [Parameter, EditorRequired] 13 | public CategoryDto CategoryDto { get; set; } = default!; 14 | [Parameter, EditorRequired] 15 | public Action OnNav { get; set; } = default!; 16 | } 17 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Shared/Modal/ModalOptions.cs: -------------------------------------------------------------------------------- 1 | namespace ModularMonolithicArch.Shared.Modal; 2 | 3 | public class ModalOptions 4 | { 5 | public string? Text { get; set; } 6 | public string? Title { get; set; } 7 | /// 8 | /// From 1 to 6 9 | /// 10 | public int TextSize { get; set; } 11 | 12 | public bool ShowConfirmButton { get; set; } 13 | public string? ConfirmButtonText { get; set; } 14 | public string? ConfirmButtonColor { get; set; } 15 | public bool ShowDenyButton { get; set; } 16 | public string? DenyButtonText { get; set; } 17 | public string? DenyButtonColor { get; set; } 18 | 19 | public bool AllowOutsideClick { get; set; } 20 | } 21 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web.Client/Configuration/IdentityConfigurationsExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components.Authorization; 2 | 3 | namespace ModularMonolithicArch.Web.Client.Configuration; 4 | 5 | public static class IdentityConfigurationsExtension 6 | { 7 | public static IServiceCollection ConfigureIdentity(this IServiceCollection services) 8 | { 9 | ArgumentNullException.ThrowIfNull(services); 10 | 11 | services.AddAuthorizationCore(); 12 | services.AddCascadingAuthenticationState(); 13 | services.AddSingleton(); 14 | 15 | return services; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Room/Mapper/RoomMapper.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.ImageGame.Application.Room.Dto; 2 | 3 | namespace ModularMonolithicArch.ImageGame.Application.Room.Mapper; 4 | 5 | public class RoomMapper : IRoomMapper 6 | { 7 | public Domain.Entities.Room Map(RoomDto roomDto, string id) 8 | { 9 | return new() 10 | { 11 | Id = roomDto.Id, 12 | CreatorId = id, 13 | CreatorUserName = roomDto.CreatorUserName, 14 | CategoryId = roomDto.CategoryId, 15 | ConnectionId = roomDto.CreatorConnectionId, 16 | Time = roomDto.Time, 17 | BoardSize = roomDto.BoardSize, 18 | }; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ModularMonolithicArch.User.Api/Endpoints/Account/AccountGroup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Routing; 4 | 5 | namespace ModularMonolithicArch.User.Api.Endpoints.Account; 6 | 7 | public static class AccountGroup 8 | { 9 | public static IEndpointRouteBuilder MapLogOutEndpoint(this IEndpointRouteBuilder endpoint) 10 | { 11 | ArgumentNullException.ThrowIfNull(endpoint); 12 | 13 | var group = endpoint.MapGroup("/") 14 | .WithTags("GameHub.Presentation"); 15 | 16 | group.MapPost("/logout", AccountEndpoints.LogOutAsync) 17 | .RequireAuthorization(); 18 | 19 | return endpoint; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ModularMonolithicArch.User.Presentation/ServiceCollectionExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using ModularMonolithicArch.UserModule; 4 | 5 | namespace ModularMonolithicArch.User.Presentation; 6 | 7 | public static class ServiceCollectionExtension 8 | { 9 | public static IServiceCollection ConfigureUserModule(this IServiceCollection services, IConfiguration configuration) 10 | { 11 | ArgumentNullException.ThrowIfNull(services); 12 | 13 | services.AddUserModule(configuration); 14 | services.AddMediatR(conf => conf.RegisterServicesFromAssembly(typeof(_Imports).Assembly)); 15 | 16 | return services; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Shared/ServiceCollectionExtension.cs: -------------------------------------------------------------------------------- 1 | using Blazored.LocalStorage; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using ModularMonolithicArch.Shared.Modal; 4 | 5 | namespace ModularMonolithicArch.ImageGame.Presentation; 6 | 7 | public static class ServiceCollectionExtension 8 | { 9 | public static IServiceCollection ConfigureShared(this IServiceCollection services) 10 | { 11 | ArgumentNullException.ThrowIfNull(services); 12 | 13 | services.AddMediatR(conf => conf.RegisterServicesFromAssembly(typeof(ServiceCollectionExtension).Assembly)); 14 | services.AddScoped(); 15 | services.AddBlazoredLocalStorage(); 16 | 17 | return services; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Api/ServiceCollectionExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Routing; 2 | using ModularMonolithicArch.ImageGame.Api.Endpoints.Category; 3 | using ModularMonolithicArch.ImageGame.Api.Endpoints.Image; 4 | using ModularMonolithicArch.ImageGame.Api.Endpoints.Room; 5 | 6 | namespace ModularMonolithicArch.ImageGame.Api; 7 | 8 | public static class ServiceCollectionExtension 9 | { 10 | public static IEndpointRouteBuilder ConfigureImageGameModuleEndpoints(this IEndpointRouteBuilder app) 11 | { 12 | ArgumentNullException.ThrowIfNull(app); 13 | 14 | app.MapRoomEndpoints(); 15 | app.MapImageEndpoints(); 16 | app.MapCategoryEndpoints(); 17 | 18 | return app; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Api/Endpoints/Category/CategoryGroup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Routing; 4 | 5 | namespace ModularMonolithicArch.ImageGame.Api.Endpoints.Category; 6 | 7 | public static class CategoryGroup 8 | { 9 | public static IEndpointRouteBuilder MapCategoryEndpoints(this IEndpointRouteBuilder app) 10 | { 11 | ArgumentNullException.ThrowIfNull(app); 12 | 13 | var group = app.MapGroup("/imageGame/category") 14 | .WithTags("ImageGame Category"); 15 | 16 | group.MapPost("/", CategoryEndpoints.CreateAsync); 17 | group.MapGet("/", CategoryEndpoints.GetAllAsync); 18 | 19 | return app; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Domain/ServiceCollectionExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using ModularMonolithicArch.WordGame.Domain.Repository; 3 | using ModularMonolithicArch.WordGame.Domain.Services; 4 | 5 | namespace ModularMonolithicArch.WordGame.Domain; 6 | 7 | public static class ServiceCollectionExtension 8 | { 9 | public static IServiceCollection AddDomain(this IServiceCollection services) 10 | { 11 | ArgumentNullException.ThrowIfNull(services); 12 | 13 | services.AddScoped(); 14 | services.AddScoped(); 15 | services.AddScoped(); 16 | 17 | return services; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Image/Repository/IImageService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using ModularMonolithicArch.ImageGame.Application.Image.Dto; 3 | 4 | namespace ModularMonolithicArch.ImageGame.Application.Image.Repository; 5 | 6 | public interface IImageService 7 | { 8 | Task CreateAsync(ImageDto imageDto, CancellationToken cancellationToken); 9 | Task UploadImageAsync(int imageId, IFormFile file, CancellationToken cancellationToken); 10 | Task DeleteAsync(int imageId, CancellationToken cancellationToken); 11 | Task?> GetRandomAsync(int categoryId, int boardSize, int seed, CancellationToken cancellationToken); 12 | Task?> GetAllAsync(CancellationToken cancellationToken); 13 | } 14 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Room/Commands/Handlers/SetWinnerRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.ImageGame.Application.Room.Commands.Requests; 3 | using System.Net.Http.Json; 4 | 5 | namespace ModularMonolithicArch.ImageGame.Application.Room.Commands.Handlers; 6 | 7 | public class SetWinnerRequestHandler(IHttpClientFactory httpClientFactory) : IRequestHandler 8 | { 9 | public async Task Handle(SetWinnerRequest request, CancellationToken cancellationToken) 10 | { 11 | await httpClientFactory.CreateClient("Client").PutAsJsonAsync(SetWinnerRequest.route, request, cancellationToken); 12 | return new SetWinnerRequest.Response(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web/Components/Routes.razor: -------------------------------------------------------------------------------- 1 | @using System.Reflection 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | @code{ 11 | Assembly[] assemblies = new[] 12 | { 13 | typeof(Client._Imports).Assembly, 14 | typeof(WordGame.Presentation._Imports).Assembly, 15 | typeof(User.Presentation._Imports).Assembly, 16 | typeof(ImageGame.Presentation._Imports).Assembly, 17 | typeof(TypeGame.Presentation._Imports).Assembly, 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web/Configuration/ExceptionHandlerExtension.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.Web.Exceptions; 2 | 3 | namespace ModularMonolithicArch.Web.Configuration; 4 | 5 | public static class ExceptionHandlerExtension 6 | { 7 | public static IServiceCollection ConfigureExceptionsHandler(this IServiceCollection services) 8 | { 9 | ArgumentNullException.ThrowIfNull(services); 10 | 11 | services.AddExceptionHandler(); 12 | 13 | return services; 14 | } 15 | 16 | public static WebApplication ConfigureExceptionsHandlerMiddlewares(this WebApplication app) 17 | { 18 | ArgumentNullException.ThrowIfNull(app); 19 | 20 | app.UseExceptionHandler(x => { }); 21 | 22 | return app; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Infrastructure/Hubs/ImageGameHub.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.SignalR; 3 | 4 | namespace ModularMonolithicArch.ImageGame.Infrastructure.Hubs; 5 | 6 | [Authorize] 7 | public class ImageGameHub : Hub 8 | { 9 | public async Task SendProcessImageMessageAsync(string creatorId, string guestId, int imageId) 10 | { 11 | await Clients.Users(creatorId, guestId).ReceiveProcessImageMessage(imageId); 12 | } 13 | 14 | public async Task SendDisconnectedMessageAsync(string userId) 15 | { 16 | await Clients.User(userId).ReceiveDisconnectedMessage(); 17 | } 18 | } 19 | 20 | public interface IImageGameHub 21 | { 22 | Task ReceiveProcessImageMessage(int imageId); 23 | Task ReceiveDisconnectedMessage(); 24 | } 25 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Room/Repository/IRoomService.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.ImageGame.Application.Room.Dto; 2 | 3 | namespace ModularMonolithicArch.ImageGame.Application.Room.Repository; 4 | 5 | public interface IRoomService 6 | { 7 | Task CreateAsync(RoomDto roomDto, CancellationToken cancellationToken); 8 | Task AddGuestToRoom(int roomId, CancellationToken cancellationToken); 9 | Task DeleteCurrentUserRoomsAsync(string userName); 10 | Task DeleteAsync(int id, CancellationToken cancellationToken); 11 | Task UpdateScoresAsync(int id, int creatorScore, int guestScore, CancellationToken cancellationToken); 12 | Task GetAsync(int id, CancellationToken cancellationToken); 13 | Task?> GetAllAsync(CancellationToken cancellationToken); 14 | } 15 | -------------------------------------------------------------------------------- /ModularMonolithicArch.TypeGame.Presentation/ModularMonolithicArch.TypeGame.Presentation.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Shared/ModularMonolithicArch.Shared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Application/ModularMonolithicArch.WordGame.Application.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Room/Queries/Handlers/GetRoomsRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.ImageGame.Application.Room.Dto; 3 | using ModularMonolithicArch.ImageGame.Application.Room.Queries.Requests; 4 | using System.Net.Http.Json; 5 | 6 | namespace ModularMonolithicArch.ImageGame.Application.Room.Queries.Handlers; 7 | 8 | public class GetRoomsRequestHandler(IHttpClientFactory httpClientFactory) : IRequestHandler 9 | { 10 | public async Task Handle(GetRoomsRequest request, CancellationToken cancellationToken) 11 | { 12 | var roomDtos = await httpClientFactory.CreateClient("Client").GetFromJsonAsync?>(GetRoomsRequest.route, cancellationToken); 13 | return new GetRoomsRequest.Response(roomDtos); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Room/Dto/RoomDto.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.ImageGame.Domain.Enums; 2 | 3 | namespace ModularMonolithicArch.ImageGame.Application.Room.Dto; 4 | 5 | public class RoomDto 6 | { 7 | public int Id { get; set; } 8 | 9 | public string CreatorId { get; set; } = default!; 10 | public string CreatorUserName { get; set; } = default!; 11 | public string CreatorConnectionId { get; set; } = default!; 12 | public string GuestId { get; set; } = default!; 13 | public string GuestUserName { get; set; } = default!; 14 | public string CategoryName { get; set; } = default!; 15 | 16 | public int CategoryId { get; set; } 17 | public int Time { get; set; } 18 | public int CreatorScore { get; set; } 19 | public int GuestScore { get; set; } 20 | 21 | public BoardSize BoardSize { get; set; } 22 | } 23 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Image/Dto/IFormFileValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using Microsoft.AspNetCore.Http; 3 | 4 | namespace ModularMonolithicArch.ImageGame.Application.Image.Dto; 5 | 6 | public class IFormFileValidator : AbstractValidator 7 | { 8 | public IFormFileValidator() 9 | { 10 | RuleFor(i => i.Name) 11 | .NotEmpty() 12 | .WithMessage("Name is required"); 13 | 14 | RuleFor(i => i.Length) 15 | .LessThanOrEqualTo(2 * 1024 * 1024) // 2 MB in bytes 16 | .WithMessage("'Size' must be less than or equal to 2 MB."); 17 | 18 | RuleFor(i => i.FileName) 19 | .Must(x => x.Contains("jpeg") || x.Contains("jpg") || x.Contains("png")) 20 | .WithMessage("File type is not allowed. Only JPEG and PNG formats are accepted."); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Api/Endpoints/Category/CategoryGroups.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Routing; 4 | 5 | namespace ModularMonolithicArch.WordGame.Api.Endpoints.Category; 6 | 7 | public static class CategoryGroups 8 | { 9 | public static IEndpointRouteBuilder MapCategoryEndpoints(this IEndpointRouteBuilder endpoints) 10 | { 11 | ArgumentNullException.ThrowIfNull(endpoints); 12 | 13 | var group = endpoints.MapGroup("/wordGame/category") 14 | .WithTags("WordGame Category"); 15 | 16 | group.MapPost("/", CategoryEndpoints.CreateAsync); 17 | group.MapGet("/", CategoryEndpoints.GetCategoriesAsync); 18 | group.MapPut("/{userName}/decreaseHealth", CategoryEndpoints.DecreaseHealthAsync); 19 | 20 | return endpoints; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Application/Level/Dto/LevelDtoValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | 3 | namespace ModularMonolithicArch.WordGame.Application.Level.Dto; 4 | 5 | public class LevelDtoValidator : AbstractValidator 6 | { 7 | public LevelDtoValidator() 8 | { 9 | RuleFor(x => x.LevelNumber) 10 | .NotNull() 11 | .GreaterThan(0); 12 | 13 | RuleFor(x => x.Word) 14 | .NotEmpty() 15 | .Matches(@"^[a-zA-Z]*$").WithMessage("Word should only include alphabetic charecters (no numbers or special characters)"); 16 | 17 | RuleFor(x => x.Hint).NotEmpty(); 18 | 19 | RuleFor(x => x.CategoryDto.Id).NotEmpty().WithMessage("'Category' must not be empty."); 20 | 21 | RuleFor(x => x.LevelStatus) 22 | .NotEmpty().WithMessage("'Difficulty' must not be empty."); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Api/Endpoints/Level/LevelGroup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Routing; 4 | 5 | namespace ModularMonolithicArch.WordGame.Api.Endpoints.Level; 6 | 7 | public static class LevelGroup 8 | { 9 | public static IEndpointRouteBuilder MapLevelEndpoints(this IEndpointRouteBuilder endpoints) 10 | { 11 | ArgumentNullException.ThrowIfNull(endpoints); 12 | 13 | var group = endpoints.MapGroup("/wordGame/level") 14 | .WithTags("wordGame Level"); 15 | 16 | group.MapPost("/", LevelEndpoints.CreateAsync).RequireAuthorization(opt => opt.RequireRole("Admin")); 17 | group.MapGet("/{id}", LevelEndpoints.GetAsync); 18 | group.MapGet("/{id}/category/{categoryId}/next", LevelEndpoints.GetNextLevelAsync); 19 | 20 | return endpoints; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Api/Endpoints/Image/ImageGroup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Routing; 4 | 5 | namespace ModularMonolithicArch.ImageGame.Api.Endpoints.Image 6 | { 7 | public static class ImageGroup 8 | { 9 | public static IEndpointRouteBuilder MapImageEndpoints(this IEndpointRouteBuilder app) 10 | { 11 | ArgumentNullException.ThrowIfNull(app); 12 | 13 | var group = app.MapGroup("/imageGame/image") 14 | .WithTags("ImageGame Image"); 15 | 16 | group.MapPost("/", ImageEndpoints.CreateAsync); 17 | group.MapPost("/upload/{imageId}", ImageEndpoints.UploadImageAsync); 18 | group.MapGet("/random/{categoryId}/{boardSize}/{seed}", ImageEndpoints.GetRandomAsync); 19 | 20 | return app; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web/Configuration/RoleExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | using ModularMonolithicArch.UserModule.Shared.Domain.Entity; 3 | 4 | namespace ModularMonolithicArch.Web.Configuration; 5 | 6 | public static class RoleExtension 7 | { 8 | public static async Task ConfigureRole(this WebApplication app) 9 | { 10 | ArgumentNullException.ThrowIfNull(app); 11 | 12 | using (var scope = app.Services.CreateScope()) 13 | { 14 | var services = scope.ServiceProvider; 15 | var roleManager = services.GetRequiredService>(); 16 | var userManager = services.GetRequiredService>(); 17 | 18 | await IdentityExtension.InitializeRole(roleManager, userManager); 19 | } 20 | 21 | return app; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Room/Commands/Handlers/DeleteRoomRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.ImageGame.Application.Room.Commands.Requests; 3 | 4 | namespace ModularMonolithicArch.ImageGame.Application.Room.Commands.Handlers; 5 | 6 | public class DeleteRoomRequestHandler(IHttpClientFactory httpClientFactory) : IRequestHandler 7 | { 8 | public async Task Handle(DeleteRoomRequest request, CancellationToken cancellationToken) 9 | { 10 | var response = await httpClientFactory.CreateClient("Client").DeleteAsync(DeleteRoomRequest.route.Replace("id", request.Id.ToString()), cancellationToken); 11 | 12 | if (!response.IsSuccessStatusCode) 13 | return new DeleteRoomRequest.Response(400); 14 | 15 | return new DeleteRoomRequest.Response(200); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web/Components/App.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Presentation/ServiceCollectionExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using ModularMonolithicArch.WordGame.Application; 4 | using ModularMonolithicArch.WordGame.Domain; 5 | using ModularMonolithicArch.WordGame.Infrastructure; 6 | 7 | namespace ModularMonolithicArch.WordGame.Presentation; 8 | 9 | public static class ServiceCollectionExtension 10 | { 11 | public static IServiceCollection ConfigureWordGameModule(this IServiceCollection services, IConfiguration configuration) 12 | { 13 | ArgumentNullException.ThrowIfNull(services); 14 | 15 | services.AddDomain(); 16 | services.AddApplication(); 17 | services.AddInfraStructure(configuration); 18 | services.AddMediatR(conf => conf.RegisterServicesFromAssembly(typeof(_Imports).Assembly)); 19 | 20 | return services; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Presentation/ServiceCollectionExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using ModularMonolithicArch.ImageGame.Application; 4 | using ModularMonolithicArch.ImageGame.Domain; 5 | using ModularMonolithicArch.ImageGame.Infrastructure; 6 | 7 | namespace ModularMonolithicArch.ImageGame.Presentation; 8 | 9 | public static class ServiceCollectionExtension 10 | { 11 | public static IServiceCollection ConfigureImageGameModule(this IServiceCollection services, IConfiguration configuration) 12 | { 13 | ArgumentNullException.ThrowIfNull(services); 14 | 15 | services.AddDomain(); 16 | services.AddApplication(); 17 | services.AddInfraStructure(configuration); 18 | services.AddMediatR(conf => conf.RegisterServicesFromAssembly(typeof(_Imports).Assembly)); 19 | 20 | return services; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web/Exceptions/DefaultExceptionHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Diagnostics; 2 | using Microsoft.AspNetCore.Mvc; 3 | using System.Net; 4 | 5 | namespace ModularMonolithicArch.Web.Exceptions; 6 | 7 | public class DefaultExceptionHandler : IExceptionHandler 8 | { 9 | public async ValueTask TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken) 10 | { 11 | await httpContext.Response.WriteAsJsonAsync(new ProblemDetails 12 | { 13 | Status = (int)HttpStatusCode.ServiceUnavailable, 14 | Title = "An unexpected error occurred", 15 | Detail = exception.Message, 16 | Type = exception.GetType().ToString(), 17 | Instance = $"{httpContext.Request.Method} {httpContext.Request.Path}" 18 | }, cancellationToken); 19 | 20 | return true; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web.Client/ServiceCollectionExtension.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.ImageGame.Presentation; 2 | using ModularMonolithicArch.User.Presentation; 3 | using ModularMonolithicArch.WordGame.Presentation; 4 | 5 | namespace ModularMonolithicArch.Web.Client; 6 | 7 | public static class ServiceCollectionExtension 8 | { 9 | public static IServiceCollection ConfigureSharedWebClient(this IServiceCollection services, IConfiguration configuration) 10 | { 11 | ArgumentNullException.ThrowIfNull(services); 12 | 13 | services.AddHttpClient("Client", client => client.BaseAddress = new Uri("https://localhost:7197")); 14 | services.ConfigureImageGameModule(configuration); 15 | services.ConfigureUserModule(configuration); 16 | services.ConfigureWordGameModule(configuration); 17 | services.ConfigureShared(); 18 | 19 | return services; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Application/Category/Queries/Handlers/GetCategoriesRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.WordGame.Application.Category.Dto; 3 | using ModularMonolithicArch.WordGame.Application.Category.Queries.Requests; 4 | using System.Net.Http.Json; 5 | 6 | namespace ModularMonolithicArch.WordGame.Application.Category.Queries.Handlers; 7 | 8 | public class GetCategoriesRequestHandler(IHttpClientFactory httpClientFactory) : IRequestHandler 9 | { 10 | public async Task Handle(GetCategoriesRequest request, CancellationToken cancellationToken) 11 | { 12 | List? categoryDtos = await httpClientFactory.CreateClient("Client").GetFromJsonAsync>(GetCategoriesRequest.route, cancellationToken); 13 | return new GetCategoriesRequest.Response(categoryDtos); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Application/Level/Commands/Handlers/DeleteLevelHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.WordGame.Application.Level.Commands.Requests; 3 | 4 | namespace ModularMonolithicArch.WordGame.Application.Level.Commands.Handlers; 5 | 6 | public class DeleteLevelHandler(IHttpClientFactory httpClient) : IRequestHandler 7 | { 8 | public async Task Handle(DeleteLevelRequest request, CancellationToken cancellationToken) 9 | { 10 | var response = await httpClient.CreateClient("Client").DeleteAsync(DeleteLevelRequest.route.Replace("{id}", request.Id.ToString()), cancellationToken); 11 | 12 | if (!response.IsSuccessStatusCode) 13 | return new DeleteLevelRequest.Response(400, "Failed to delete level"); 14 | 15 | return new DeleteLevelRequest.Response(200, "Level deleted successfully"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Api/Endpoints/Room/RoomGroup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Routing; 4 | 5 | namespace ModularMonolithicArch.ImageGame.Api.Endpoints.Room; 6 | 7 | public static class RoomGroup 8 | { 9 | public static IEndpointRouteBuilder MapRoomEndpoints(this IEndpointRouteBuilder app) 10 | { 11 | ArgumentNullException.ThrowIfNull(app); 12 | 13 | var group = app.MapGroup("/imageGame/room") 14 | .WithTags("ImageGame Room") 15 | .RequireAuthorization(); 16 | 17 | group.MapPost("/", RoomEndpoints.CreateAsync); 18 | group.MapGet("/all", RoomEndpoints.GetAllAsync); 19 | group.MapGet("/{id}", RoomEndpoints.GetAsync); 20 | group.MapDelete("/{id}", RoomEndpoints.DeleteAsync); 21 | group.MapPut("/scores", RoomEndpoints.UpdateWinnerAsync); 22 | 23 | return app; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Application/ServiceCollectionExtension.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using ModularMonolithicArch.WordGame.Application.Category.Mapper; 4 | using ModularMonolithicArch.WordGame.Application.Level.Mapper; 5 | 6 | namespace ModularMonolithicArch.WordGame.Application; 7 | 8 | public static class ServiceCollectionExtension 9 | { 10 | public static IServiceCollection AddApplication(this IServiceCollection services) 11 | { 12 | ArgumentNullException.ThrowIfNull(services); 13 | 14 | services.AddValidatorsFromAssembly(typeof(ServiceCollectionExtension).Assembly); 15 | services.AddMediatR(conf => conf.RegisterServicesFromAssembly(typeof(ServiceCollectionExtension).Assembly)); 16 | 17 | services.AddScoped(); 18 | services.AddScoped(); 19 | 20 | return services; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Infrastructure/BackroundTasks/HealthBackgroundService.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | using ModularMonolithicArch.User.Contract.Messages.Commands; 5 | 6 | namespace ModularMonolithicArch.WordGame.Infrastructure.BackroundTasks; 7 | 8 | public class HealthBackgroundService(IServiceProvider serviceProvider) : BackgroundService 9 | { 10 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 11 | { 12 | while (!stoppingToken.IsCancellationRequested) 13 | { 14 | await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken); 15 | 16 | using var scope = serviceProvider.CreateScope(); 17 | var mediator = scope.ServiceProvider.GetRequiredService(); 18 | 19 | _ = await mediator.Send(new IncreaseUsersHealthRequest(), stoppingToken); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Image/Commands/Handlers/AddImageRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.ImageGame.Application.Image.Commands.Requests; 3 | using System.Net.Http.Json; 4 | 5 | namespace ModularMonolithicArch.ImageGame.Application.Image.Commands.Handlers; 6 | 7 | public class AddImageRequestHandler(IHttpClientFactory httpClientFactory) : IRequestHandler 8 | { 9 | public async Task Handle(AddImageRequest request, CancellationToken cancellationToken) 10 | { 11 | var response = await httpClientFactory.CreateClient("Client").PostAsJsonAsync(AddImageRequest.route, request.ImageDto, cancellationToken); 12 | if (!response.IsSuccessStatusCode) 13 | return new(400, 0, "Failed to create image."); 14 | 15 | int imageId = await response.Content.ReadFromJsonAsync(cancellationToken); 16 | return new(200, imageId); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ModularMonolithicArch.User.Presentation/Sections/StatusMessage.razor: -------------------------------------------------------------------------------- 1 | @if (!string.IsNullOrEmpty(DisplayMessage)) 2 | { 3 | var statusMessageClass = DisplayMessage.StartsWith("Error") ? "danger" : "success"; 4 | 7 | } 8 | 9 | @code { 10 | private string? messageFromCookie; 11 | 12 | [Parameter] 13 | public string? Message { get; set; } 14 | 15 | [CascadingParameter] 16 | private HttpContext HttpContext { get; set; } = default!; 17 | 18 | private string? DisplayMessage => Message ?? messageFromCookie; 19 | 20 | protected override void OnInitialized() 21 | { 22 | messageFromCookie = HttpContext.Request.Cookies[IdentityRedirectManager.StatusCookieName]; 23 | 24 | if (messageFromCookie is not null) 25 | { 26 | HttpContext.Response.Cookies.Delete(IdentityRedirectManager.StatusCookieName); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ModularMonolithicArch.UserModule/Integrations/Queries/GetUserRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using Microsoft.AspNetCore.Identity; 3 | using ModularMonolithicArch.User.Contract; 4 | using ModularMonolithicArch.User.Contract.Messages.Queries; 5 | using ModularMonolithicArch.UserModule.Shared.Domain.Entity; 6 | 7 | namespace ModularMonolithicArch.UserModule.Integrations.Queries; 8 | 9 | public class GetUserRequestHandler(UserManager userManager) : IRequestHandler 10 | { 11 | private readonly UserManager userManager = userManager; 12 | 13 | public async Task Handle(GetUserRequest request, CancellationToken cancellationToken) 14 | { 15 | var user = await userManager.FindByNameAsync(request.UserName); 16 | if (user is null) 17 | { 18 | return null; 19 | } 20 | 21 | return new() { Id = user!.Id, UserName = user.UserName! }; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ModularMonolithicArch.UserModule/Integrations/Queries/GetUserHealthRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using Microsoft.AspNetCore.Identity; 3 | using ModularMonolithicArch.User.Contract; 4 | using ModularMonolithicArch.User.Contract.Messages.Queries; 5 | using ModularMonolithicArch.UserModule.Shared.Domain.Entity; 6 | 7 | namespace ModularMonolithicArch.UserModule.Integrations.Queries; 8 | 9 | public class GetUserHealthRequestHandler(UserManager userManager) : IRequestHandler 10 | { 11 | private readonly UserManager userManager = userManager; 12 | 13 | public async Task Handle(GetUserHealthRequest request, CancellationToken cancellationToken) 14 | { 15 | var user = await userManager.FindByNameAsync(request.UserName); 16 | if (user is null) 17 | { 18 | return null; 19 | } 20 | 21 | return new() { Health = user.Health }; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Domain/Entities/Room.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.ImageGame.Domain.Enums; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace ModularMonolithicArch.ImageGame.Domain.Entities; 5 | 6 | public class Room 7 | { 8 | public int Id { get; set; } 9 | 10 | public int CategoryId { get; set; } = default!; 11 | [ForeignKey(nameof(CategoryId))] 12 | public Category Category { get; set; } = default!; 13 | 14 | public string CreatorId { get; set; } = default!; 15 | public string CreatorUserName { get; set; } = string.Empty; 16 | 17 | public string? GuestId { get; set; } 18 | public string? GuestUserName { get; set; } 19 | 20 | public string ConnectionId { get; set; } = default!; 21 | 22 | public int? CreatorScore { get; set; } 23 | public int? GuestScore { get; set; } 24 | public int Time { get; set; } 25 | 26 | public bool IsAvailable { get; set; } = true; 27 | 28 | public BoardSize BoardSize { get; set; } 29 | } 30 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Room/Queries/Handlers/GetRoomRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.ImageGame.Application.Room.Dto; 3 | using ModularMonolithicArch.ImageGame.Application.Room.Queries.Requests; 4 | using System.Net.Http.Json; 5 | 6 | namespace ModularMonolithicArch.ImageGame.Application.Room.Queries.Handlers; 7 | 8 | public class GetRoomRequestHandler(IHttpClientFactory httpClientFactory) : IRequestHandler 9 | { 10 | public async Task Handle(GetRoomRequest request, CancellationToken cancellationToken) 11 | { 12 | RoomDto? roomDto = await httpClientFactory.CreateClient("Client").GetFromJsonAsync(GetRoomRequest.route.Replace("id", request.Id.ToString()), cancellationToken); 13 | if (roomDto is null) 14 | return new GetRoomRequest.Response(null, "The room you’re looking for does not exist."); 15 | 16 | return new GetRoomRequest.Response(roomDto); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Category/Commands/Handlers/AddCategoryRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.ImageGame.Application.Category.Commands.Requests; 3 | using System.Net.Http; 4 | using System.Net.Http.Json; 5 | 6 | namespace ModularMonolithicArch.ImageGame.Application.Category.Commands.Handlers; 7 | 8 | public class AddCategoryRequestHandler(IHttpClientFactory httpClientFactory) : IRequestHandler 9 | { 10 | public async Task Handle(AddCategoryRequest request, CancellationToken cancellationToken) 11 | { 12 | var response = await httpClientFactory.CreateClient("Client").PostAsJsonAsync(AddCategoryRequest.route, request.CategoryDto, cancellationToken); 13 | if (!response.IsSuccessStatusCode) 14 | return new AddCategoryRequest.Response(400, "Failed to create category."); 15 | 16 | return new AddCategoryRequest.Response(200, "Category created successfully."); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Category/Queries/Handlers/GetCategoriesRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.ImageGame.Application.Category.Dto; 3 | using ModularMonolithicArch.ImageGame.Application.Category.Queries.Requests; 4 | using System.Net.Http.Json; 5 | 6 | namespace ModularMonolithicArch.ImageGame.Application.Category.Queries.Handlers; 7 | 8 | public class GetCategoriesRequestHandler(IHttpClientFactory httpClientFactory) : IRequestHandler 9 | { 10 | public async Task Handle(GetCategoriesRequest request, CancellationToken cancellationToken) 11 | { 12 | var categoryDtos = await httpClientFactory.CreateClient("Client").GetFromJsonAsync?>(GetCategoriesRequest.route, cancellationToken); 13 | if (categoryDtos is null) 14 | return new GetCategoriesRequest.Response(null); 15 | 16 | return new GetCategoriesRequest.Response(categoryDtos); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ModularMonolithicArch.UserModule/ModularMonolithicArch.UserModule.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Application/Level/Queries/Handlers/GetLevelRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.WordGame.Application.Level.Dto; 3 | using ModularMonolithicArch.WordGame.Application.Level.Queries.Requests; 4 | using System.Net.Http.Json; 5 | 6 | namespace ModularMonolithicArch.WordGame.Application.Level.Queries.Handlers; 7 | 8 | public class GetLevelRequestHandler(IHttpClientFactory httpClient) : IRequestHandler 9 | { 10 | public async Task Handle(GetLevelRequest request, CancellationToken cancellationToken) 11 | { 12 | LevelDto? levelDto = await httpClient.CreateClient("Client").GetFromJsonAsync(GetLevelRequest.route.Replace("id", request.Id.ToString()), cancellationToken); 13 | 14 | if (levelDto is null) 15 | return new GetLevelRequest.Response(null, 404, "Can’t find the level you’re looking for."); 16 | 17 | return new GetLevelRequest.Response(levelDto, 200); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Image/Queries/Handlers/GetRandomImagesRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.ImageGame.Application.Image.Dto; 3 | using ModularMonolithicArch.ImageGame.Application.Image.Queries.Requests; 4 | using System.Net.Http.Json; 5 | 6 | namespace ModularMonolithicArch.ImageGame.Application.Image.Queries.Handlers; 7 | 8 | public class GetRandomImagesRequestHandler(IHttpClientFactory httpClientFactory) : IRequestHandler 9 | { 10 | public async Task Handle(GetRandomImagesRequest request, CancellationToken cancellationToken) 11 | { 12 | string route = GetRandomImagesRequest.route 13 | .Replace("categoryId", request.CategoryId.ToString()).Replace("boardSize", request.BoardSize.ToString()).Replace("seed", request.Seed.ToString()); 14 | var imageDtos = await httpClientFactory.CreateClient("Client").GetFromJsonAsync?>(route, cancellationToken); 15 | return new(imageDtos); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/ModularMonolithicArch.ImageGame.Application.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web/Configuration/SignalRExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.ResponseCompression; 2 | using ModularMonolithicArch.ImageGame.Infrastructure.Hubs; 3 | 4 | namespace ModularMonolithicArch.Web.Configuration; 5 | 6 | public static class SignalRExtension 7 | { 8 | public static IServiceCollection ConfigureSignalR(this IServiceCollection services) 9 | { 10 | ArgumentNullException.ThrowIfNull(services); 11 | 12 | services.AddSignalR(); 13 | services.AddResponseCompression(opts => 14 | { 15 | opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat( 16 | ["application/octet-stream"]); 17 | }); 18 | 19 | return services; 20 | } 21 | 22 | public static IEndpointRouteBuilder ConfigureSignalRMiddleware(this IEndpointRouteBuilder app) 23 | { 24 | ArgumentNullException.ThrowIfNull(app); 25 | 26 | app.MapHub("/room-hub"); 27 | app.MapHub("/imageGame-hub"); 28 | 29 | return app; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/ServiceCollectionExtension.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using ModularMonolithicArch.ImageGame.Application.Category.Mapper; 4 | using ModularMonolithicArch.ImageGame.Application.Image.Mapper; 5 | using ModularMonolithicArch.ImageGame.Application.Room.Mapper; 6 | 7 | namespace ModularMonolithicArch.ImageGame.Application; 8 | 9 | public static class ServiceCollectionExtension 10 | { 11 | public static IServiceCollection AddApplication(this IServiceCollection services) 12 | { 13 | ArgumentNullException.ThrowIfNull(services); 14 | 15 | services.AddValidatorsFromAssembly(typeof(ServiceCollectionExtension).Assembly); 16 | services.AddMediatR(conf => conf.RegisterServicesFromAssembly(typeof(ServiceCollectionExtension).Assembly)); 17 | 18 | services.AddScoped(); 19 | services.AddScoped(); 20 | services.AddScoped(); 21 | 22 | return services; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Application/Level/Repository/ILevelService.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.WordGame.Application.Level.Dto; 2 | 3 | namespace ModularMonolithicArch.WordGame.Application.Level.Repository; 4 | 5 | public interface ILevelService 6 | { 7 | Task CreateAsync(LevelDto levelDto, CancellationToken cancellationToken); 8 | Task DeleteAsync(int id, CancellationToken cancellationToken); 9 | Task UpdateAsync(LevelDto levelDto, CancellationToken cancellationToken); 10 | Task GetAsync(int id, CancellationToken cancellationToken); 11 | Task GetNextAsync(int id, int categoryId, CancellationToken cancellationToken); 12 | Task?> GetAllAsync(CancellationToken cancellationToken); 13 | Task?> GetAllByIdAsync(int categoryId, CancellationToken cancellationToken); 14 | Task IsLevelExistInCreateAsync(int levelNumber, int categoryId, CancellationToken cancellationToken); 15 | Task IsLevelExistInUpdateAsync(int levelNumber, int id, int categoryId, CancellationToken cancellationToken); 16 | } 17 | -------------------------------------------------------------------------------- /ModularMonolithicArch.UserModule/Integrations/Commands/DecreaceUserHealthRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using Microsoft.AspNetCore.Identity; 3 | using ModularMonolithicArch.User.Contract.Messages.Commands; 4 | using ModularMonolithicArch.UserModule.Shared.Domain.Entity; 5 | 6 | namespace ModularMonolithicArch.UserModule.Integrations.Commands; 7 | 8 | public class DecreaceUserHealthRequestHandler(UserManager userManager) : IRequestHandler 9 | { 10 | public async Task Handle(DecreaseUserHealthRequest request, CancellationToken cancellationToken) 11 | { 12 | if (string.IsNullOrEmpty(request.UserName)) 13 | return false; 14 | 15 | var user = await userManager.FindByNameAsync(request.UserName); 16 | if (user is null) 17 | return false; 18 | 19 | try 20 | { 21 | user.Health--; 22 | await userManager.UpdateAsync(user); 23 | return true; 24 | } 25 | catch 26 | { 27 | return false; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Shared/Sections/StopWatchSection.razor: -------------------------------------------------------------------------------- 1 | @implements IDisposable 2 | @using System.Timers 3 | 4 |
5 |

@Time

6 |
7 | 8 | @code { 9 | private Timer? _timer = null; 10 | public int _time = 0; 11 | 12 | protected string Time { get; set; } = "00:00"; 13 | 14 | public void Start() 15 | { 16 | _timer?.Start(); 17 | } 18 | 19 | public void Stop() 20 | { 21 | _timer?.Stop(); 22 | } 23 | 24 | protected override void OnInitialized() 25 | { 26 | _timer = new Timer(1000); 27 | _timer.Elapsed += async (sender, e) => await OnTimedEvent(sender, e); 28 | _timer.AutoReset = true; 29 | } 30 | 31 | private async Task OnTimedEvent(object? sender, ElapsedEventArgs e) 32 | { 33 | _time++; 34 | 35 | await InvokeAsync(() => 36 | { 37 | Time = TimeSpan.FromSeconds(_time).ToString(@"mm\:ss"); 38 | StateHasChanged(); 39 | }); 40 | } 41 | 42 | void IDisposable.Dispose() 43 | { 44 | _timer?.Dispose(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ModularMonolithicArch.UserModule/Integrations/Queries/GetCurrentUserRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Identity; 4 | using ModularMonolithicArch.User.Contract; 5 | using ModularMonolithicArch.User.Contract.Messages.Queries; 6 | using ModularMonolithicArch.UserModule.Shared.Domain.Entity; 7 | 8 | namespace ModularMonolithicArch.UserModule.Integrations.Queries; 9 | 10 | public class GetCurrentUserRequestHandler(UserManager userManager, IHttpContextAccessor httpContextAccessor) : IRequestHandler 11 | { 12 | private readonly UserManager userManager = userManager; 13 | private readonly IHttpContextAccessor httpContextAccessor = httpContextAccessor; 14 | 15 | public async Task Handle(GetCurrentUserRequest request, CancellationToken cancellationToken) 16 | { 17 | var user = await userManager.FindByNameAsync(httpContextAccessor.HttpContext.User.Identity?.Name!); 18 | return new() { Id = user!.Id, UserName = user.UserName!}; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Application/Category/Command/Handlers/AddCategoryRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.WordGame.Application.Category.Command.Requests; 3 | using System.Net; 4 | using System.Net.Http.Json; 5 | 6 | namespace ModularMonolithicArch.WordGame.Application.Category.Command.Handlers; 7 | 8 | public class AddCategoryRequestHandler(IHttpClientFactory httpClientFactory) : IRequestHandler 9 | { 10 | public async Task Handle(AddCategoryRequest request, CancellationToken cancellationToken) 11 | { 12 | var response = await httpClientFactory.CreateClient("Client").PostAsJsonAsync(AddCategoryRequest.route, request, cancellationToken); 13 | return response.StatusCode switch 14 | { 15 | HttpStatusCode.BadRequest => new AddCategoryRequest.Response(400, "Validation failed"), 16 | HttpStatusCode.UnprocessableEntity => new AddCategoryRequest.Response(422, "Failed to add category"), 17 | _ => new AddCategoryRequest.Response(200, "Category added successfully"), 18 | }; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Room/Commands/Handlers/AddRoomRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.ImageGame.Application.Room.Commands.Requests; 3 | using System.Net; 4 | using System.Net.Http.Json; 5 | 6 | namespace ModularMonolithicArch.ImageGame.Application.Room.Commands.Handlers; 7 | 8 | public class AddRoomRequestHandler(IHttpClientFactory httpClientFactory) : IRequestHandler 9 | { 10 | public async Task Handle(AddRoomRequest request, CancellationToken cancellationToken) 11 | { 12 | var response = await httpClientFactory.CreateClient("Client").PostAsJsonAsync(AddRoomRequest.route, request.RoomDto, cancellationToken); 13 | return response.StatusCode switch 14 | { 15 | HttpStatusCode.BadRequest => new AddRoomRequest.Response(400, "You already have a room. You should delete it to create a new one."), 16 | HttpStatusCode.UnprocessableEntity => new AddRoomRequest.Response(422, "Failed to add room"), 17 | _ => new AddRoomRequest.Response(200, "Room created successfully."), 18 | }; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ModularMonolithicArch.UserModule/ServiceCollectionExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using ModularMonolithicArch.UserModule.Shared.Domain.Entity; 6 | using ModularMonolithicArch.UserModule.Shared.Persistence; 7 | 8 | namespace ModularMonolithicArch.UserModule; 9 | 10 | public static class ServiceCollectionExtension 11 | { 12 | public static IServiceCollection AddUserModule(this IServiceCollection services, IConfiguration configuration) 13 | { 14 | ArgumentNullException.ThrowIfNull(services); 15 | 16 | services.AddDbContextPool(opt => 17 | opt.UseSqlServer(configuration.GetConnectionString("DbGame"), opt => opt.EnableRetryOnFailure(3))); 18 | 19 | services.AddMediatR(conf => conf.RegisterServicesFromAssembly(typeof(ServiceCollectionExtension).Assembly)); 20 | 21 | services.AddIdentityCore() 22 | .AddRoles() 23 | .AddEntityFrameworkStores(); 24 | 25 | return services; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Presentation/ModularMonolithicArch.WordGame.Presentation.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ModularMonolithicArch.UserModule/Features/Account/Login.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using Microsoft.AspNetCore.Identity; 3 | using ModularMonolithicArch.UserModule.Shared.Domain.Entity; 4 | using System.ComponentModel.DataAnnotations; 5 | 6 | namespace ModularMonolithicArch.UserModule.Features.Account; 7 | 8 | public static class Login 9 | { 10 | public class LoginDto 11 | { 12 | [Required] public string Email { get; set; } = default!; 13 | [Required] public string Password { get; set; } = default!; 14 | 15 | public bool RememberMe { get; set; } 16 | } 17 | 18 | public record LoginRequest(LoginDto LoginDto) : IRequest; 19 | 20 | internal class LoginRequestHandler(SignInManager signInManager) : IRequestHandler 21 | { 22 | private readonly SignInManager signInManager = signInManager; 23 | 24 | public async Task Handle(LoginRequest request, CancellationToken cancellationToken) 25 | => await signInManager.PasswordSignInAsync(request.LoginDto.Email, request.LoginDto.Password, request.LoginDto.RememberMe, false); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web/Configuration/SwaggerOptions.cs: -------------------------------------------------------------------------------- 1 | using Asp.Versioning.ApiExplorer; 2 | using Microsoft.Extensions.Options; 3 | using Microsoft.OpenApi.Models; 4 | using Swashbuckle.AspNetCore.SwaggerGen; 5 | 6 | namespace ModularMonolithicArch.Web.Configuration; 7 | 8 | public class SwaggerOptions(IApiVersionDescriptionProvider apiVersionDescriptionProvider) : IConfigureOptions 9 | { 10 | private readonly IApiVersionDescriptionProvider _apiVersionDescriptionProvider = apiVersionDescriptionProvider; 11 | 12 | public void Configure(SwaggerGenOptions options) 13 | { 14 | foreach (var description in _apiVersionDescriptionProvider.ApiVersionDescriptions) 15 | { 16 | options.SwaggerDoc(description.GroupName, CreateOpenApiInfo(description)); 17 | } 18 | } 19 | 20 | private static OpenApiInfo CreateOpenApiInfo(ApiVersionDescription description) 21 | { 22 | var info = new OpenApiInfo() 23 | { 24 | Title = "ASP.NET Core 8 Minimal APIs", 25 | Version = description.ApiVersion.ToString() 26 | }; 27 | return info; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Presentation/ModularMonolithicArch.ImageGame.Presentation.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Infrastructure/ModularMonolithicArch.WordGame.Infrastructure.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Infrastructure/Hubs/RoomHub.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.SignalR; 2 | using ModularMonolithicArch.ImageGame.Application.Room.Dto; 3 | 4 | namespace ModularMonolithicArch.ImageGame.Infrastructure.Hubs; 5 | 6 | public class RoomHub : Hub 7 | { 8 | private const string _groupName = "roomGroup"; 9 | 10 | public async Task AddRoomAsync(RoomDto roomDto) 11 | => await Clients.All.ReceiveAddRoomMessage(roomDto); 12 | 13 | public async Task DeleteRoomAsync(int roomId) 14 | => await Clients.All.ReceiveDeleteRoomMessage(roomId); 15 | 16 | public async Task GoToRoomAsync(int roomId) 17 | => await Clients.Group(_groupName).ReceiveGoToRoomMessage(roomId); 18 | 19 | public async Task ManageGroupAsync(string creatorConnectionId, string guestConnectionId) 20 | { 21 | await Groups.AddToGroupAsync(creatorConnectionId, _groupName); 22 | await Groups.AddToGroupAsync(guestConnectionId, _groupName); 23 | } 24 | } 25 | 26 | public interface IRoomHub 27 | { 28 | Task ReceiveAddRoomMessage(RoomDto roomDto); 29 | Task ReceiveDeleteRoomMessage(int roomId); 30 | Task ReceiveGoToRoomMessage(int roomId); 31 | } 32 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Application/Level/Commands/Handlers/AddLevelRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.WordGame.Application.Level.Commands.Requests; 3 | using System.Net; 4 | using System.Net.Http.Json; 5 | 6 | namespace ModularMonolithicArch.WordGame.Application.Level.Commands.Handlers; 7 | 8 | public class AddLevelRequestHandler(IHttpClientFactory httpClient) : IRequestHandler 9 | { 10 | public async Task Handle(AddLevelRequest request, CancellationToken cancellationToken) 11 | { 12 | var response = await httpClient.CreateClient("Client").PostAsJsonAsync(AddLevelRequest.route, request, cancellationToken); 13 | return response.StatusCode switch 14 | { 15 | HttpStatusCode.BadRequest => new AddLevelRequest.Response(400, "Validation failed"), 16 | HttpStatusCode.Conflict => new AddLevelRequest.Response(409, "Level already exists"), 17 | HttpStatusCode.UnprocessableEntity => new AddLevelRequest.Response(422, "Failed to add level"), 18 | _ => new AddLevelRequest.Response(200, "Level added successfully"), 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web/Configuration/SwaggerExtension.cs: -------------------------------------------------------------------------------- 1 | using Asp.Versioning; 2 | using Microsoft.Extensions.Options; 3 | using Swashbuckle.AspNetCore.SwaggerGen; 4 | 5 | namespace ModularMonolithicArch.Web.Configuration; 6 | 7 | public static class SwaggerExtension 8 | { 9 | public static IServiceCollection ConfigureSwagger(this IServiceCollection services) 10 | { 11 | ArgumentNullException.ThrowIfNull(services); 12 | 13 | services.AddEndpointsApiExplorer(); 14 | services.AddApiVersioning(options => 15 | { 16 | options.DefaultApiVersion = new ApiVersion(1, 0); 17 | options.ReportApiVersions = true; 18 | options.AssumeDefaultVersionWhenUnspecified = true; 19 | options.ApiVersionReader = new HeaderApiVersionReader("apiversion"); 20 | }) 21 | .AddApiExplorer(options => 22 | { 23 | options.GroupNameFormat = "'v'VV"; // Formats the version as follow: "'v'major[.minor]" 24 | }); 25 | services.AddSwaggerGen(); 26 | 27 | services.AddSingleton, SwaggerOptions>(); 28 | 29 | return services; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Application/Level/Queries/Handlers/GetNextLevelRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.WordGame.Application.Level.Dto; 3 | using ModularMonolithicArch.WordGame.Application.Level.Queries.Requests; 4 | using System.Net.Http.Json; 5 | 6 | namespace ModularMonolithicArch.WordGame.Application.Level.Queries.Handlers; 7 | 8 | public class GetNextLevelRequestHandler(IHttpClientFactory httpClient) : IRequestHandler 9 | { 10 | public async Task Handle(GetNextLevelRequest request, CancellationToken cancellationToken) 11 | { 12 | string route = GetNextLevelRequest.route.Replace("{id}", request.Id.ToString()).Replace("{categoryId}", request.CategoryId.ToString()); 13 | LevelDto? levelDto = await httpClient.CreateClient("Client").GetFromJsonAsync(route, cancellationToken); 14 | 15 | if (levelDto is null) 16 | return new GetNextLevelRequest.GetNextLevelResponse(null, 404, Message: "Can’t find the level you’re looking for."); 17 | 18 | return new GetNextLevelRequest.GetNextLevelResponse(levelDto, 200); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Api/Endpoints/Category/CategoryEndpoints.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using Microsoft.AspNetCore.Http; 3 | using ModularMonolithicArch.ImageGame.Application.Category.Dto; 4 | using ModularMonolithicArch.ImageGame.Application.Category.Repository; 5 | 6 | namespace ModularMonolithicArch.ImageGame.Api.Endpoints.Category; 7 | 8 | public static class CategoryEndpoints 9 | { 10 | public static async Task CreateAsync(CategoryDto categoryDto, IValidator validator, ICategoryService service, CancellationToken cancellationToken) 11 | { 12 | var validationResult = validator.Validate(categoryDto); 13 | 14 | if (!validationResult.IsValid) 15 | return Results.ValidationProblem(validationResult.ToDictionary()); 16 | 17 | int result = await service.CreateAsync(categoryDto, cancellationToken); 18 | return result > 0 ? Results.Ok("Category created successfully") 19 | : Results.UnprocessableEntity("Failed to add category"); 20 | } 21 | 22 | public static async Task?> GetAllAsync(ICategoryService service, CancellationToken cancellationToken) 23 | => await service.GetAllAsync(cancellationToken); 24 | } 25 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Application/Level/Commands/Handlers/UpdateLevelHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.WordGame.Application.Level.Commands.Requests; 3 | using System.Net; 4 | using System.Net.Http.Json; 5 | 6 | namespace ModularMonolithicArch.WordGame.Application.Level.Commands.Handlers; 7 | 8 | public class UpdateLevelHandler(IHttpClientFactory httpClient) : IRequestHandler 9 | { 10 | public async Task Handle(UpdateLevelRequest request, CancellationToken cancellationToken) 11 | { 12 | var response = await httpClient.CreateClient("Client").PutAsJsonAsync(UpdateLevelRequest.route, request, cancellationToken); 13 | return response.StatusCode switch 14 | { 15 | HttpStatusCode.BadRequest => new UpdateLevelRequest.Response(400, "Vlidation failed"), 16 | HttpStatusCode.Conflict => new UpdateLevelRequest.Response(409, "Level already exists"), 17 | HttpStatusCode.UnprocessableEntity => new UpdateLevelRequest.Response(422, "Failed to update level"), 18 | _ => new UpdateLevelRequest.Response(200, "Level updated successfully") 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Application/Image/Commands/Handlers/UploadImageRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using ModularMonolithicArch.ImageGame.Application.Image.Commands.Requests; 3 | 4 | namespace ModularMonolithicArch.ImageGame.Application.Image.Commands.Handlers; 5 | 6 | public class UploadImageRequestHandler(IHttpClientFactory httpClientFactory) : IRequestHandler 7 | { 8 | private const long _maxSize = 1024 * 1024 * 2; 9 | 10 | public async Task Handle(UploadImageRequest request, CancellationToken cancellationToken) 11 | { 12 | var stream = request.File.OpenReadStream(request.File.Size, cancellationToken); 13 | var content = new MultipartFormDataContent { { new StreamContent(stream), "image", request.File.Name } }; 14 | 15 | var route = UploadImageRequest.route.Replace("imageId", request.ImageId.ToString()); 16 | var response = await httpClientFactory.CreateClient("Client").PostAsync(route, content, cancellationToken); 17 | 18 | if (!response.IsSuccessStatusCode) 19 | return new(400, "Failed to upload image."); 20 | 21 | return new(200, "Image created successfully"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Infrastructure/ServiceCollectionExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using ModularMonolithicArch.ImageGame.Application.Category.Repository; 5 | using ModularMonolithicArch.ImageGame.Application.Image.Repository; 6 | using ModularMonolithicArch.ImageGame.Application.Room.Repository; 7 | using ModularMonolithicArch.ImageGame.Infrastructure.Context; 8 | using ModularMonolithicArch.ImageGame.Infrastructure.Services; 9 | 10 | namespace ModularMonolithicArch.ImageGame.Infrastructure; 11 | 12 | public static class ServiceCollectionExtension 13 | { 14 | public static IServiceCollection AddInfraStructure(this IServiceCollection services, IConfiguration configuration) 15 | { 16 | ArgumentNullException.ThrowIfNull(services); 17 | 18 | services.AddDbContextPool(opt => 19 | opt.UseSqlServer(configuration.GetConnectionString("DbGame"), opt => opt.EnableRetryOnFailure(3))); 20 | 21 | services.AddScoped(); 22 | services.AddScoped(); 23 | services.AddScoped(); 24 | 25 | return services; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web/Configuration/BlazorExtension.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.Web.Components; 2 | 3 | namespace ModularMonolithicArch.Web.Configuration; 4 | 5 | public static class BlazorExtension 6 | { 7 | public static IServiceCollection ConfigureBlazor(this IServiceCollection services) 8 | { 9 | ArgumentNullException.ThrowIfNull(services); 10 | 11 | services.AddRazorComponents() 12 | .AddInteractiveServerComponents() 13 | .AddInteractiveWebAssemblyComponents(); 14 | 15 | return services; 16 | } 17 | 18 | public static IEndpointRouteBuilder ConfigureBlazorMiddleWares(this IEndpointRouteBuilder app) 19 | { 20 | ArgumentNullException.ThrowIfNull(app); 21 | 22 | app.MapRazorComponents() 23 | .AddInteractiveServerRenderMode() 24 | .AddInteractiveWebAssemblyRenderMode() 25 | .AddAdditionalAssemblies( 26 | typeof(Client._Imports).Assembly, 27 | typeof(WordGame.Presentation._Imports).Assembly, 28 | typeof(User.Presentation._Imports).Assembly, 29 | typeof(ImageGame.Presentation._Imports).Assembly, 30 | typeof(TypeGame.Presentation._Imports).Assembly); 31 | 32 | return app; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ModularMonolithicArch.UserModule/Integrations/Commands/IncreaseUsersHealthRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using Microsoft.AspNetCore.Identity; 3 | using Microsoft.EntityFrameworkCore; 4 | using ModularMonolithicArch.User.Contract.Messages.Commands; 5 | using ModularMonolithicArch.UserModule.Shared.Domain.Entity; 6 | using ModularMonolithicArch.UserModule.Shared.Persistence; 7 | 8 | namespace ModularMonolithicArch.UserModule.Integrations.Commands; 9 | 10 | public class IncreaseUsersHealthRequestHandler(UserContext db, UserManager userManager) : IRequestHandler 11 | { 12 | private readonly UserContext db = db; 13 | private readonly UserManager userManager = userManager; 14 | 15 | public async Task Handle(IncreaseUsersHealthRequest request, CancellationToken cancellationToken) 16 | { 17 | try 18 | { 19 | await userManager.Users 20 | .Where(u => u.Health < 3) 21 | .ForEachAsync(u => u.Health++, cancellationToken); 22 | 23 | await db.SaveChangesAsync(cancellationToken); 24 | 25 | return 1; 26 | } 27 | catch (Exception ex) 28 | { 29 | Console.WriteLine(ex.Message); 30 | return -1; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Presentation/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Authorization 2 | @using Microsoft.AspNetCore.Components.Authorization 3 | @using Microsoft.AspNetCore.Components.QuickGrid 4 | @using Microsoft.AspNetCore.Components.Web 5 | @using static Microsoft.AspNetCore.Components.Web.RenderMode 6 | @using Microsoft.AspNetCore.Components.Forms 7 | @using ModularMonolithicArch.Shared.Sections 8 | @using ModularMonolithicArch.Shared.Modal 9 | @using ModularMonolithicArch.WordGame.Application.Category.Dto 10 | @using ModularMonolithicArch.WordGame.Application.Category.Command.Requests 11 | @using ModularMonolithicArch.WordGame.Application.Level.Dto 12 | @using ModularMonolithicArch.WordGame.Application.Category.Queries.Requests 13 | @using ModularMonolithicArch.WordGame.Application.Level.Commands.Requests 14 | @using ModularMonolithicArch.WordGame.Application.Category.Repository 15 | @using ModularMonolithicArch.WordGame.Domain.Enums 16 | @using ModularMonolithicArch.WordGame.Presentation.Sections 17 | @using ModularMonolithicArch.WordGame.Application.Level.Repository 18 | @using ModularMonolithicArch.WordGame.Application.Level.Queries.Requests 19 | @using ModularMonolithicArch.WordGame.Domain.Repository 20 | @using ModularMonolithicArch.WordGame.Domain.Core 21 | @using Blazored.FluentValidation 22 | @using Blazored.LocalStorage 23 | @using MediatR 24 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web/Components/Pages/Error.razor: -------------------------------------------------------------------------------- 1 | @page "/Error" 2 | @using System.Diagnostics 3 | 4 | Error 5 | 6 |

Error.

7 |

An error occurred while processing your request.

8 | 9 | @if (ShowRequestId) 10 | { 11 |

12 | Request ID: @RequestId 13 |

14 | } 15 | 16 |

Development Mode

17 |

18 | Swapping to Development environment will display more detailed information about the error that occurred. 19 |

20 |

21 | The Development environment shouldn't be enabled for deployed applications. 22 | It can result in displaying sensitive information from exceptions to end users. 23 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 24 | and restarting the app. 25 |

26 | 27 | @code{ 28 | [CascadingParameter] 29 | private HttpContext? HttpContext { get; set; } 30 | 31 | private string? RequestId { get; set; } 32 | private bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 33 | 34 | protected override void OnInitialized() => 35 | RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; 36 | } 37 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Infrastructure/ModularMonolithicArch.ImageGame.Infrastructure.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | all 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Infrastructure/ServiceCollectionExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using ModularMonolithicArch.WordGame.Application.Category.Repository; 5 | using ModularMonolithicArch.WordGame.Application.Level.Repository; 6 | using ModularMonolithicArch.WordGame.Infrastructure.BackroundTasks; 7 | using ModularMonolithicArch.WordGame.Infrastructure.Context; 8 | using ModularMonolithicArch.WordGame.Infrastructure.Services; 9 | 10 | namespace ModularMonolithicArch.WordGame.Infrastructure; 11 | 12 | public static class ServiceCollectionExtension 13 | { 14 | public static IServiceCollection AddInfraStructure(this IServiceCollection services, IConfiguration configuration) 15 | { 16 | ArgumentNullException.ThrowIfNull(services); 17 | 18 | services.AddDbContextPool(opt => 19 | opt.UseSqlServer(configuration.GetConnectionString("DbGame"), 20 | opt => opt.EnableRetryOnFailure(3))); 21 | 22 | services.AddMediatR(conf => conf.RegisterServicesFromAssembly(typeof(ServiceCollectionExtension).Assembly)); 23 | 24 | services.AddScoped(); 25 | services.AddScoped(); 26 | 27 | services.AddHostedService(); 28 | 29 | return services; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Domain/Services/WordGameService.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.WordGame.Domain.Core; 2 | using ModularMonolithicArch.WordGame.Domain.Repository; 3 | 4 | namespace ModularMonolithicArch.WordGame.Domain.Services; 5 | 6 | public class WordGameService(IWordService wordService) : IWordGameService 7 | { 8 | private List _randomIndices = []; 9 | private Word _word = new(); 10 | private bool _isGuideActive; 11 | private string _hint = string.Empty; 12 | 13 | public List RandomIndices => _randomIndices; 14 | public bool IsGuideActive => _isGuideActive; 15 | public string Hint => _hint; 16 | 17 | public void Initialize(Word word, string hint, int percentageOfCharsToHide) 18 | { 19 | _word = word; 20 | _word.Letters = word.Value.Select(v => new Letter { Value = v }).ToList(); 21 | _hint = hint; 22 | 23 | _randomIndices = wordService.RemoveRandomLetters(word, percentageOfCharsToHide); 24 | wordService.DisableRemainingLetters(word, _randomIndices); 25 | } 26 | 27 | public bool IsAnswerCorrect() 28 | { 29 | string insertedLetter = string.Join("", _word.Letters.Select(l => l.Value)); 30 | 31 | if (IsGuideActive) 32 | wordService.MakeLettersGreen(_word, insertedLetter, _randomIndices); 33 | 34 | return Equals(insertedLetter, _word.Value); 35 | } 36 | 37 | public void EnableGuide() 38 | => _isGuideActive = true; 39 | } 40 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Infrastructure/Migrations/20240907110201_modify-room.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace ModularMonolithicArch.ImageGame.Infrastructure.Migrations 6 | { 7 | /// 8 | public partial class modifyroom : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.AddColumn( 14 | name: "CreatorUserName", 15 | schema: "imageGame", 16 | table: "Rooms", 17 | type: "nvarchar(max)", 18 | nullable: false, 19 | defaultValue: ""); 20 | 21 | migrationBuilder.AddColumn( 22 | name: "GuestId", 23 | schema: "imageGame", 24 | table: "Rooms", 25 | type: "nvarchar(max)", 26 | nullable: true); 27 | } 28 | 29 | /// 30 | protected override void Down(MigrationBuilder migrationBuilder) 31 | { 32 | migrationBuilder.DropColumn( 33 | name: "CreatorUserName", 34 | schema: "imageGame", 35 | table: "Rooms"); 36 | 37 | migrationBuilder.DropColumn( 38 | name: "GuestId", 39 | schema: "imageGame", 40 | table: "Rooms"); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ModularMonolithicArch.UserModule/Features/Account/Register.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using Microsoft.AspNetCore.Identity; 3 | using ModularMonolithicArch.UserModule.Shared.Domain.Entity; 4 | using System.ComponentModel.DataAnnotations; 5 | 6 | namespace ModularMonolithicArch.UserModule.Features.Account; 7 | 8 | public static class Register 9 | { 10 | public class RegisterDto 11 | { 12 | [Required] 13 | [EmailAddress] 14 | public string Email { get; set; } = default!; 15 | 16 | [Required] public string Password { get; set; } = default!; 17 | 18 | [Required] 19 | [Compare(nameof(Password))] 20 | public string ConfirmPassword { get; set; } = default!; 21 | } 22 | 23 | public record RegisterRequest(RegisterDto RegisterDto) : IRequest; 24 | 25 | internal class RegisterRequestHandler(UserManager userManager) : IRequestHandler 26 | { 27 | private readonly UserManager userManager = userManager; 28 | 29 | public async Task Handle(RegisterRequest request, CancellationToken cancellationToken) 30 | { 31 | var user = new ApplicationUser 32 | { 33 | Email = request.RegisterDto.Email, 34 | UserName = request.RegisterDto.Email, 35 | EmailConfirmed = true 36 | }; 37 | 38 | return await userManager.CreateAsync(user, request.RegisterDto.Password); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web.Client/Configuration/PersistentAuthenticationStateProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components.Authorization; 2 | using Microsoft.AspNetCore.Components; 3 | using System.Security.Claims; 4 | 5 | namespace ModularMonolithicArch.Web.Client.Configuration; 6 | 7 | public class PersistentAuthenticationStateProvider : AuthenticationStateProvider 8 | { 9 | private static readonly Task defaultUnauthenticatedTask = 10 | Task.FromResult(new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()))); 11 | 12 | private readonly Task authenticationStateTask = defaultUnauthenticatedTask; 13 | 14 | public PersistentAuthenticationStateProvider(PersistentComponentState state) 15 | { 16 | if (!state.TryTakeFromJson(nameof(UserInfo), out var userInfo) || userInfo is null) 17 | { 18 | return; 19 | } 20 | 21 | Claim[] claims = [ 22 | new Claim(ClaimTypes.NameIdentifier, userInfo.UserId), 23 | new Claim(ClaimTypes.Name, userInfo.Email), 24 | new Claim(ClaimTypes.Email, userInfo.Email) ]; 25 | 26 | authenticationStateTask = Task.FromResult( 27 | new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(claims, 28 | authenticationType: nameof(PersistentAuthenticationStateProvider))))); 29 | } 30 | 31 | public override Task GetAuthenticationStateAsync() => authenticationStateTask; 32 | } 33 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Api/Endpoints/Level/LevelEndpoints.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using Microsoft.AspNetCore.Http; 3 | using ModularMonolithicArch.WordGame.Application.Level.Commands.Requests; 4 | using ModularMonolithicArch.WordGame.Application.Level.Dto; 5 | using ModularMonolithicArch.WordGame.Application.Level.Repository; 6 | 7 | namespace ModularMonolithicArch.WordGame.Api.Endpoints.Level; 8 | 9 | public class LevelEndpoints 10 | { 11 | public static async Task CreateAsync(AddLevelRequest request, ILevelService levelService, IValidator validator, CancellationToken cancellationToken) 12 | { 13 | var validationResult = validator.Validate(request.LevelDto); 14 | 15 | if (!validationResult.IsValid) 16 | return Results.ValidationProblem(validationResult.ToDictionary()); 17 | 18 | int createResult = await levelService.CreateAsync(request.LevelDto, cancellationToken); 19 | 20 | return createResult > 0 ? Results.Ok("Level created successfully") : createResult is -1 ? Results.Conflict("Level already exist") : Results.UnprocessableEntity("Failed to add level"); 21 | } 22 | 23 | public static async Task GetAsync(int id, ILevelService levelService, CancellationToken cancellationToken) 24 | => await levelService.GetAsync(id, cancellationToken); 25 | 26 | public static async Task GetNextLevelAsync(int id, int categoryId, ILevelService levelService, CancellationToken cancellationToken) 27 | => await levelService.GetNextAsync(id, categoryId, cancellationToken); 28 | } 29 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Shared/Sections/CountDownSection.razor: -------------------------------------------------------------------------------- 1 | @implements IDisposable 2 | @using System.Timers 3 | 4 |
5 |

@Time

6 |
7 | 8 | @code { 9 | private Timer? _timer = null; 10 | private int _secondsToRun = 0; 11 | 12 | protected string Time { get; set; } = "00:00"; 13 | 14 | [Parameter] 15 | public EventCallback TimerOut { get; set; } 16 | 17 | public void Start(int secondsToRun) 18 | { 19 | _secondsToRun = secondsToRun; 20 | 21 | if (_secondsToRun > 0) 22 | { 23 | Time = TimeSpan.FromSeconds(_secondsToRun).ToString(@"mm\:ss"); 24 | StateHasChanged(); 25 | _timer?.Start(); 26 | } 27 | } 28 | 29 | public void Stop() 30 | { 31 | _timer?.Stop(); 32 | } 33 | 34 | protected override void OnInitialized() 35 | { 36 | _timer = new Timer(1000); 37 | _timer.Elapsed += OnTimedEvent; 38 | _timer.AutoReset = true; 39 | } 40 | 41 | private async void OnTimedEvent(object? sender, ElapsedEventArgs e) 42 | { 43 | _secondsToRun--; 44 | 45 | await InvokeAsync(() => 46 | { 47 | Time = TimeSpan.FromSeconds(_secondsToRun).ToString(@"mm\:ss"); 48 | StateHasChanged(); 49 | }); 50 | 51 | if (_secondsToRun <= 0) 52 | { 53 | _timer?.Stop(); 54 | await InvokeAsync(async () => await TimerOut.InvokeAsync()); 55 | } 56 | } 57 | 58 | void IDisposable.Dispose() 59 | { 60 | _timer?.Dispose(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Presentation/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Components.Web 2 | @using Microsoft.AspNetCore.Authorization 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.QuickGrid 5 | @using Microsoft.AspNetCore.SignalR.Client 6 | @using Microsoft.AspNetCore.Components.Authorization 7 | @using static Microsoft.AspNetCore.Components.Web.RenderMode 8 | @using ModularMonolithicArch.Shared.Sections 9 | @using ModularMonolithicArch.ImageGame.Application.Category.Repository 10 | @using ModularMonolithicArch.Shared.Modal 11 | @using ModularMonolithicArch.ImageGame.Application.Category.Dto 12 | @using ModularMonolithicArch.ImageGame.Application.Category.Commands.Requests 13 | @using ModularMonolithicArch.ImageGame.Application.Image.Dto 14 | @using ModularMonolithicArch.ImageGame.Application.Category.Queries.Requests 15 | @using ModularMonolithicArch.ImageGame.Application.Image.Commands.Requests 16 | @using ModularMonolithicArch.ImageGame.Application.Image.Repository 17 | @using ModularMonolithicArch.ImageGame.Application.Room.Queries.Requests 18 | @using ModularMonolithicArch.ImageGame.Application.Room.Dto 19 | @using ModularMonolithicArch.ImageGame.Application.Image.Queries.Requests 20 | @using ModularMonolithicArch.ImageGame.Application.Room.Commands.Requests 21 | @using ModularMonolithicArch.ImageGame.Application.Room.Repository 22 | @using ModularMonolithicArch.ImageGame.Domain.Enums 23 | @using ModularMonolithicArch.ImageGame.Domain.Entities 24 | @using ModularMonolithicArch.ImageGame.Domain.Services 25 | @using Blazored.FluentValidation 26 | @using MediatR 27 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Presentation/Sections/LevelCardSection.razor: -------------------------------------------------------------------------------- 1 | 14 | 15 | @code { 16 | private string levelColor = string.Empty; 17 | 18 | [Parameter, EditorRequired] 19 | public LevelDto LevelDto { get; set; } = default!; 20 | [Parameter, EditorRequired] 21 | public EventCallback OnNav { get; set; } 22 | 23 | protected override void OnParametersSet() 24 | { 25 | levelColor = LevelDto.LevelStatus switch 26 | { 27 | LevelStatus.Easy => "border-success btn-outline-success", 28 | LevelStatus.Medium => "border-warning btn-outline-warning", 29 | LevelStatus.Hard => "border-danger btn-outline-danger", 30 | _ => throw new ArgumentOutOfRangeException(nameof(LevelDto.LevelStatus), $"Not expected difficulty value: {LevelDto.LevelStatus}") 31 | }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:45857", 8 | "sslPort": 44327 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 17 | "applicationUrl": "http://localhost:5274", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "https": { 23 | "commandName": "Project", 24 | "dotnetRunMessages": true, 25 | "launchBrowser": true, 26 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 27 | "applicationUrl": "https://localhost:7197;http://localhost:5274", 28 | "environmentVariables": { 29 | "ASPNETCORE_ENVIRONMENT": "Development" 30 | } 31 | }, 32 | "IIS Express": { 33 | "commandName": "IISExpress", 34 | "launchBrowser": true, 35 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 36 | "environmentVariables": { 37 | "ASPNETCORE_ENVIRONMENT": "Development" 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web.Client/ModularMonolithicArch.Web.Client.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | true 8 | Default 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Infrastructure/Services/CategoryService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using ModularMonolithicArch.ImageGame.Application.Category.Dto; 3 | using ModularMonolithicArch.ImageGame.Application.Category.Mapper; 4 | using ModularMonolithicArch.ImageGame.Application.Category.Repository; 5 | using ModularMonolithicArch.ImageGame.Infrastructure.Context; 6 | 7 | namespace ModularMonolithicArch.ImageGame.Infrastructure.Services; 8 | 9 | public class CategoryService(ImageGameContext db, ICategoryMapper mapper) : ICategoryService 10 | { 11 | public async Task CreateAsync(CategoryDto categoryDto, CancellationToken cancellationToken) 12 | { 13 | var category = mapper.Map(categoryDto); 14 | 15 | db.Add(category); 16 | await db.SaveChangesAsync(cancellationToken); 17 | 18 | return category.Id; 19 | } 20 | 21 | public async Task DeleteAsync(int id, CancellationToken cancellationToken) 22 | => await db.Categories 23 | .Where(c => c.Id == id) 24 | .ExecuteDeleteAsync(cancellationToken); 25 | 26 | public async Task?> GetAllAsync(CancellationToken cancellationToken) 27 | => await db.Categories 28 | .AsNoTracking() 29 | .Select(c => new CategoryDto() 30 | { 31 | Id = c.Id, 32 | Name = c.Name, 33 | }) 34 | .ToListAsync(cancellationToken); 35 | 36 | public async Task UpdateAsync(CategoryDto categoryDto, CancellationToken cancellationToken) 37 | => await db.Categories 38 | .Where(c => c.Id == categoryDto.Id) 39 | .ExecuteUpdateAsync(c => c 40 | .SetProperty(p => p.Name, categoryDto.Name), cancellationToken: cancellationToken); 41 | } 42 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web/ModularMonolithicArch.Web.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8f3e4af5-38ac-496a-921d-7471aac44989 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | all 22 | runtime; build; native; contentfiles; analyzers; buildtransitive 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.FileProviders; 2 | using ModularMonolithicArch.ImageGame.Api; 3 | using ModularMonolithicArch.User.Infrastructure; 4 | using ModularMonolithicArch.Web.Client; 5 | using ModularMonolithicArch.Web.Configuration; 6 | using ModularMonolithicArch.WordGame.Infrastructure; 7 | 8 | var builder = WebApplication.CreateBuilder(args); 9 | 10 | builder.Services.ConfigureBlazor(); 11 | 12 | builder.Services.ConfigureSharedWebClient(builder.Configuration); 13 | builder.Services.ConfigureIdentity(); 14 | builder.Services.ConfigureSignalR(); 15 | builder.Services.ConfigureSwagger(); 16 | 17 | var app = builder.Build(); 18 | await app.ConfigureRole(); 19 | 20 | if (app.Environment.IsDevelopment()) 21 | { 22 | app.UseWebAssemblyDebugging(); 23 | app.UseSwagger().UseSwaggerUI(c => 24 | { 25 | c.SwaggerEndpoint($"/swagger/v1.0/swagger.json", "Version 1.0"); 26 | c.SwaggerEndpoint($"/swagger/v2.0/swagger.json", "Version 2.0"); 27 | }); 28 | } 29 | else 30 | { 31 | app.UseExceptionHandler("/Error", createScopeForErrors: true); 32 | app.UseHsts(); 33 | } 34 | 35 | app.UseResponseCompression(); 36 | app.ConfigureExceptionsHandlerMiddlewares(); 37 | app.UseHttpsRedirection(); 38 | app.UseStaticFiles(); 39 | app.UseStaticFiles(new StaticFileOptions 40 | { 41 | FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot\\Images")), 42 | RequestPath = "/Images" 43 | }); 44 | app.ConfigureIdentityMiddlewares(); 45 | app.UseAntiforgery(); 46 | 47 | app.ConfigureImageGameModuleEndpoints(); 48 | app.ConfigureUserModuleEndpoints(); 49 | app.ConfigureWordGameModuleEndpoints(); 50 | app.ConfigureSignalRMiddleware(); 51 | 52 | app.ConfigureBlazorMiddleWares(); 53 | 54 | app.Run(); 55 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Api/Endpoints/Category/CategoryEndpoints.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using MediatR; 3 | using Microsoft.AspNetCore.Http; 4 | using ModularMonolithicArch.User.Contract.Messages.Commands; 5 | using ModularMonolithicArch.WordGame.Application.Category.Command.Requests; 6 | using ModularMonolithicArch.WordGame.Application.Category.Dto; 7 | using ModularMonolithicArch.WordGame.Application.Category.Repository; 8 | 9 | namespace ModularMonolithicArch.WordGame.Api.Endpoints.Category; 10 | 11 | public static class CategoryEndpoints 12 | { 13 | public static async Task CreateAsync(AddCategoryRequest request, ICategoryService categoryService, IValidator validator, CancellationToken cancellationToken) 14 | { 15 | var validationResult = validator.Validate(request.CategoryDto); 16 | 17 | if (!validationResult.IsValid) 18 | return Results.ValidationProblem(validationResult.ToDictionary()); 19 | 20 | int createResult = await categoryService.CreateAsync(request.CategoryDto, cancellationToken); 21 | 22 | return createResult > 0 ? Results.Ok("Category created successfully") : createResult is -1 ? Results.Conflict("Category already exist") : Results.UnprocessableEntity("Failed to add category"); 23 | } 24 | 25 | 26 | public static async Task?> GetCategoriesAsync(ICategoryService categoryService, CancellationToken cancellationToken) 27 | => await categoryService.GetAllAsync(cancellationToken); 28 | 29 | 30 | public static async Task DecreaseHealthAsync(string userName, IMediator mediator, CancellationToken cancellationToken) 31 | { 32 | bool isSuccess = await mediator.Send(new DecreaseUserHealthRequest(userName), cancellationToken); 33 | return isSuccess ? Results.Ok() : Results.BadRequest("Can't update user health."); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Presentation/Pages/AddCategoryPage.razor: -------------------------------------------------------------------------------- 1 | @page "/imageGame/add-category" 2 | @rendermode @(new InteractiveWebAssemblyRenderMode(false)) 3 | @attribute [Authorize(Roles = "Admin")] 4 | 5 | @inject IMediator Mediator 6 | @inject ModalService ModalService 7 | 8 | Add new category 9 | 10 |

Add new category

11 | @if (!string.IsNullOrEmpty(message)) 12 | { 13 | 14 | } 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | @code { 29 | [SupplyParameterFromForm] 30 | private CategoryDto CategoryDto { get; set; } = new(); 31 | 32 | private string message = string.Empty; 33 | private string status = string.Empty; 34 | 35 | private async void OnSubmit() 36 | { 37 | var result = await ModalService.FireAsync(new ModalOptions { Text = "Are you sure?", ShowConfirmButton = true, ShowDenyButton = true }); 38 | 39 | if (result.IsConfirmed) 40 | { 41 | var response = await Mediator.Send(new AddCategoryRequest(CategoryDto)); 42 | 43 | if (response.StatusCode == 200) 44 | { 45 | CategoryDto = new(); 46 | status = "success"; 47 | } 48 | else 49 | { 50 | status = "danger"; 51 | } 52 | message = response.Message!; 53 | StateHasChanged(); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Shared/Sections/HealthSection.razor: -------------------------------------------------------------------------------- 1 | @inject ILocalStorageService LocalStorageService 2 | @inject IMediator Mediator 3 | 4 |
5 | ❤️: @Value 6 |
7 | 8 | @code { 9 | public int Value { get; private set; } 10 | public int MaxLocalHealth => 3; 11 | 12 | public async Task GetProfileHealthAsync(string userName) 13 | { 14 | await LocalStorageService.RemoveItemAsync("Health"); 15 | var response = await Mediator.Send(new GetUserHealthRequest(userName)); 16 | Value = response.Health; 17 | StateHasChanged(); 18 | 19 | return response.Health; 20 | } 21 | 22 | public async Task GetLocalHealthAsync() 23 | { 24 | int? localHealth = await LocalStorageService.GetItemAsync("Health"); 25 | if (localHealth is null) 26 | { 27 | Value = MaxLocalHealth; 28 | await LocalStorageService.SetItemAsync("Health", Value); 29 | } 30 | if (localHealth.HasValue && localHealth.Value != 0) 31 | { 32 | Value = localHealth.Value; 33 | } 34 | StateHasChanged(); 35 | 36 | return localHealth; 37 | } 38 | 39 | public async Task PreventChangeLocalHealthAsync() 40 | { 41 | Value = MaxLocalHealth; 42 | StateHasChanged(); 43 | await LocalStorageService.SetItemAsync("Health", 2); 44 | } 45 | 46 | public async Task ReduceFromProfileAsync(string userName) 47 | { 48 | Value--; 49 | StateHasChanged(); 50 | await Mediator.Send(new DecreaseUserHealthRequest(userName!)); 51 | } 52 | 53 | public async Task ReduceFromLocalAsync() 54 | { 55 | Value--; 56 | StateHasChanged(); 57 | await LocalStorageService.SetItemAsync("Health", Value); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Presentation/Pages/AddCategoryPage.razor: -------------------------------------------------------------------------------- 1 | @page "/wordGame/add-category" 2 | @rendermode @(new InteractiveWebAssemblyRenderMode(false)) 3 | @attribute [Authorize(Roles = "Admin")] 4 | 5 | @inject IMediator Mediator 6 | @inject ModalService ModalService 7 | 8 | Add new category 9 | 10 |

Add new category

11 | @if (!string.IsNullOrEmpty(message)) 12 | { 13 | 14 | } 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | @code { 29 | [SupplyParameterFromForm] 30 | private CategoryDto CategoryDto { get; set; } = new(); 31 | 32 | private string message = string.Empty; 33 | private string status = string.Empty; 34 | 35 | 36 | private async void OnSubmit() 37 | { 38 | var result = await ModalService.FireAsync(new ModalOptions { Text = "Are you sure?", ShowConfirmButton = true, ShowDenyButton = true }); 39 | 40 | if (result.IsConfirmed) 41 | { 42 | var response = await Mediator.Send(new AddCategoryRequest(CategoryDto)); 43 | 44 | if (response.StatusCode == 200) 45 | { 46 | CategoryDto = new(); 47 | status = "success"; 48 | } 49 | else 50 | { 51 | status = "danger"; 52 | } 53 | message = response.Message!; 54 | StateHasChanged(); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Shared/Modal/ModalService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.JSInterop; 2 | 3 | namespace ModularMonolithicArch.Shared.Modal; 4 | 5 | public class ModalService(IJSRuntime jSRuntime) : IAsyncDisposable 6 | { 7 | private IJSObjectReference jsObjectReference = default!; 8 | private TaskCompletionSource tcs = default!; 9 | private readonly ModalResult modalResult = new(); 10 | public ModalOptions modalOptions = new(); 11 | public event EventHandler? OnFire; 12 | 13 | public async Task InitializeAsync() 14 | => jsObjectReference = await jSRuntime.InvokeAsync("import", "./_content/ModularMonolithicArch.Shared/CustomJs/Modal.js"); 15 | 16 | public async Task FireAsync(ModalOptions configure) 17 | { 18 | await Task.Delay(250); 19 | modalOptions = configure; 20 | OnFire?.Invoke(this, new EventArgs()); 21 | await Show(); 22 | 23 | return modalResult; 24 | } 25 | 26 | public async void ConfirmButton() 27 | { 28 | modalResult.IsConfirmed = true; 29 | await Hide(); 30 | modalResult.IsConfirmed = false; 31 | } 32 | 33 | public async void DenyButton() 34 | { 35 | modalResult.IsDenied = true; 36 | await Hide(); 37 | modalResult.IsDenied = false; 38 | } 39 | 40 | private async Task Show() 41 | { 42 | tcs = new TaskCompletionSource(); 43 | await jsObjectReference.InvokeVoidAsync("Show"); 44 | await tcs.Task; 45 | Console.WriteLine("Show called."); 46 | } 47 | 48 | private async Task Hide() 49 | { 50 | await jsObjectReference.InvokeVoidAsync("Hide"); 51 | tcs.SetResult(true); 52 | } 53 | 54 | public async ValueTask DisposeAsync() 55 | { 56 | if (jsObjectReference is not null) 57 | { 58 | await jsObjectReference.DisposeAsync(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Domain/Services/WordService.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.WordGame.Domain.Core; 2 | using ModularMonolithicArch.WordGame.Domain.Repository; 3 | 4 | namespace ModularMonolithicArch.WordGame.Domain.Services; 5 | 6 | public class WordService(ILetterService letterService) : IWordService 7 | { 8 | public List RemoveRandomLetters(Word word, int percentageOfCharsToHide) 9 | { 10 | int deletedLettersCount = word.Value.Length * percentageOfCharsToHide / 100; 11 | 12 | List randomIndices = GetRandomIndices(word.Value.Length, deletedLettersCount); 13 | 14 | randomIndices.ForEach(r => 15 | { 16 | word.Letters[r].Value = '\0'; 17 | }); 18 | 19 | return randomIndices; 20 | } 21 | 22 | 23 | private static List GetRandomIndices(int length, int deletedLettersCount) 24 | => Enumerable 25 | .Range(0, length - 1) 26 | .OrderBy(x => Random.Shared.Next()) 27 | .Take(deletedLettersCount) 28 | .ToList(); 29 | 30 | 31 | public void DisableRemainingLetters(Word word, List randomIndices) 32 | => word.Letters.Where((letter, index) => !randomIndices.Contains(index)) 33 | .ToList() 34 | .ForEach(letterService.MarkAsDisable); 35 | 36 | 37 | public void ClearLettersAtRandomIndices(Word word, List randomIndices) 38 | { 39 | randomIndices.ForEach(r => 40 | { 41 | word.Letters[r].Value = '\0'; 42 | }); 43 | } 44 | 45 | 46 | public void MakeLettersGreen(Word word, string insertedLetter, List randomIndices) 47 | { 48 | randomIndices.ForEach(r => 49 | { 50 | if (Equals(insertedLetter[r], word.Value[r])) 51 | { 52 | letterService.MarkAsCorrect(word.Letters[r]); 53 | } 54 | }); 55 | } 56 | 57 | public bool IsEmptyLetterExist(Word word) 58 | => word.Letters.Any(l => l.Value == '\0'); 59 | } 60 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Api/Endpoints/Image/ImageEndpoints.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using Microsoft.AspNetCore.Http; 3 | using ModularMonolithicArch.ImageGame.Application.Image.Dto; 4 | using ModularMonolithicArch.ImageGame.Application.Image.Repository; 5 | 6 | namespace ModularMonolithicArch.ImageGame.Api.Endpoints.Image 7 | { 8 | public static class ImageEndpoints 9 | { 10 | public static async Task CreateAsync(ImageDto imageDto, IValidator validator, IImageService service, CancellationToken cancellationToken) 11 | { 12 | var validationResult = validator.Validate(imageDto); 13 | if (!validationResult.IsValid) 14 | return Results.ValidationProblem(validationResult.ToDictionary()); 15 | 16 | int result = await service.CreateAsync(imageDto, cancellationToken); 17 | return result > 0 ? Results.Ok(result) 18 | : Results.UnprocessableEntity("Failed to add Image"); 19 | } 20 | 21 | public static async Task UploadImageAsync(int imageId, HttpRequest httpRequest, IValidator validator, IImageService service, CancellationToken cancellationToken) 22 | { 23 | var file = httpRequest.Form.Files[0]; 24 | 25 | var validationResult = validator.Validate(file); 26 | if (!validationResult.IsValid) 27 | Results.ValidationProblem(validationResult.ToDictionary()); 28 | 29 | int result = await service.UploadImageAsync(imageId, file, cancellationToken); 30 | return result > 0 ? Results.Ok("Image uploaded successfuly.") 31 | : Results.UnprocessableEntity("Failed to upload Image"); 32 | } 33 | 34 | public static async Task?> GetRandomAsync(int categoryId, int boardSize, int seed, IImageService service, CancellationToken cancellationToken) 35 | => await service.GetRandomAsync(categoryId, boardSize, seed, cancellationToken); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web/Components/Layout/Shared/DropDownSection.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | 16 | 27 |
28 |
29 |
30 |
31 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Infrastructure/Services/CategoryService.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using Microsoft.EntityFrameworkCore; 3 | using ModularMonolithicArch.WordGame.Application.Category.Dto; 4 | using ModularMonolithicArch.WordGame.Application.Category.Mapper; 5 | using ModularMonolithicArch.WordGame.Application.Category.Repository; 6 | using ModularMonolithicArch.WordGame.Infrastructure.Context; 7 | 8 | namespace ModularMonolithicArch.WordGame.Infrastructure.Services; 9 | 10 | public class CategoryService(WordGameContext db, IValidator validator, ICategoryMapper mapper) : ICategoryService 11 | { 12 | public async Task CreateAsync(CategoryDto categoryDto, CancellationToken cancellationToken) 13 | { 14 | var category = mapper.Map(categoryDto); 15 | 16 | db.Add(category); 17 | await db.SaveChangesAsync(cancellationToken); 18 | 19 | return category.Id; 20 | } 21 | 22 | public async Task DeleteAsync(int id, CancellationToken cancellationToken) 23 | { 24 | return await db.Categories 25 | .Where(l => l.Id == id) 26 | .ExecuteDeleteAsync(cancellationToken); 27 | } 28 | 29 | public async Task UpdateAsync(CategoryDto categoryDto, CancellationToken cancellationToken) 30 | { 31 | var validationResult = validator.Validate(categoryDto); 32 | 33 | if (!validationResult.IsValid) 34 | return -1; 35 | 36 | return await db.Categories 37 | .Where(l => l.Id == categoryDto.Id) 38 | .ExecuteUpdateAsync(l => l 39 | .SetProperty(p => p.Name, categoryDto.Name), cancellationToken); 40 | } 41 | 42 | public async Task?> GetAllAsync(CancellationToken cancellationToken) 43 | { 44 | return await db.Categories 45 | .AsNoTracking() 46 | .OrderBy(c => c.Id) 47 | .Select(c => new CategoryDto 48 | { 49 | Id = c.Id, 50 | Name = c.Name 51 | }).ToListAsync(cancellationToken); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Presentation/Pages/CategoriesPage.razor: -------------------------------------------------------------------------------- 1 | @page "/wordGame/categories" 2 | @attribute [StreamRendering] 3 | @rendermode InteractiveServer 4 | 5 | @inject IMediator Mediator 6 | @inject ICategoryService CategoryService 7 | @inject AuthenticationStateProvider Auth 8 | @inject NavigationManager NavManager 9 | 10 |
11 |

Categories

12 | 13 |
14 | @if (categoryDtos!.Any()) 15 | { 16 |
17 | @foreach (var categoryDto in categoryDtos!) 18 | { 19 | 20 | } 21 |
22 | } 23 | else 24 | { 25 |

There is no category to show

26 | } 27 | 28 | @code { 29 | private HealthSection _health = default!; 30 | private List? categoryDtos = new(); 31 | private bool? isAuthenticated; 32 | 33 | protected override async Task OnAfterRenderAsync(bool firstRender) 34 | { 35 | if (firstRender) 36 | { 37 | var cts = new CancellationTokenSource(); 38 | var authState = await ConfigAuthAsync(); 39 | if (isAuthenticated is true) 40 | { 41 | int profileHealth = await _health.GetProfileHealthAsync(authState.User.Identity?.Name!); 42 | } 43 | else 44 | { 45 | int? localHealth = await _health.GetLocalHealthAsync(); 46 | } 47 | categoryDtos = await CategoryService.GetAllAsync(cts.Token); 48 | StateHasChanged(); 49 | } 50 | } 51 | 52 | private async Task ConfigAuthAsync() 53 | { 54 | var authState = await Auth.GetAuthenticationStateAsync(); 55 | isAuthenticated = authState.User.Identity?.IsAuthenticated; 56 | 57 | return authState; 58 | } 59 | 60 | private void NavToLevels(int categoryId, string categoryName) 61 | { 62 | NavManager.NavigateTo($"/wordGame/levels/{categoryName}/{categoryId}"); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web/Components/Layout/MainLayout.razor.css: -------------------------------------------------------------------------------- 1 | .page { 2 | position: relative; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | main { 8 | flex: 1; 9 | } 10 | 11 | .sidebar { 12 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); 13 | } 14 | 15 | .top-row { 16 | background-color: #f7f7f7; 17 | border-bottom: 1px solid #d6d5d5; 18 | justify-content: flex-end; 19 | height: 3.5rem; 20 | display: flex; 21 | align-items: center; 22 | } 23 | 24 | .top-row ::deep a, .top-row ::deep .btn-link { 25 | white-space: nowrap; 26 | margin-left: 1.5rem; 27 | text-decoration: none; 28 | } 29 | 30 | .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { 31 | text-decoration: underline; 32 | } 33 | 34 | .top-row ::deep a:first-child { 35 | overflow: hidden; 36 | text-overflow: ellipsis; 37 | } 38 | 39 | @media (max-width: 640.98px) { 40 | .top-row { 41 | justify-content: space-between; 42 | } 43 | 44 | .top-row ::deep a, .top-row ::deep .btn-link { 45 | margin-left: 0; 46 | } 47 | } 48 | 49 | @media (min-width: 641px) { 50 | .page { 51 | flex-direction: row; 52 | } 53 | 54 | .sidebar { 55 | width: 250px; 56 | height: 100vh; 57 | position: sticky; 58 | top: 0; 59 | } 60 | 61 | .top-row { 62 | position: sticky; 63 | top: 0; 64 | z-index: 1; 65 | } 66 | 67 | .top-row.auth ::deep a:first-child { 68 | flex: 1; 69 | text-align: right; 70 | width: 0; 71 | } 72 | 73 | .top-row, article { 74 | padding-left: 2rem !important; 75 | padding-right: 1.5rem !important; 76 | } 77 | } 78 | 79 | #blazor-error-ui { 80 | background: lightyellow; 81 | bottom: 0; 82 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 83 | display: none; 84 | left: 0; 85 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 86 | position: fixed; 87 | width: 100%; 88 | z-index: 1000; 89 | } 90 | 91 | #blazor-error-ui .dismiss { 92 | cursor: pointer; 93 | position: absolute; 94 | right: 0.75rem; 95 | top: 0.5rem; 96 | } 97 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Api/Endpoints/Room/RoomEndpoints.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using Microsoft.AspNetCore.Http; 3 | using ModularMonolithicArch.ImageGame.Application.Room.Commands.Requests; 4 | using ModularMonolithicArch.ImageGame.Application.Room.Dto; 5 | using ModularMonolithicArch.ImageGame.Application.Room.Repository; 6 | 7 | namespace ModularMonolithicArch.ImageGame.Api.Endpoints.Room; 8 | 9 | public static class RoomEndpoints 10 | { 11 | public static async Task CreateAsync(RoomDto roomDto, IValidator validator, IRoomService service, CancellationToken cancellationToken) 12 | { 13 | var validationResult = validator.Validate(roomDto); 14 | 15 | if (!validationResult.IsValid) 16 | return Results.ValidationProblem(validationResult.ToDictionary()); 17 | 18 | int result = await service.CreateAsync(roomDto, cancellationToken); 19 | return result == 0 ? Results.BadRequest("You already have a room. You should delete it to create a new one.") : result > 0 ? Results.Ok("Room created successfully") 20 | : Results.UnprocessableEntity("Failed to add room"); 21 | } 22 | 23 | public static async Task UpdateWinnerAsync(SetWinnerRequest request, IValidator validator, IRoomService service, CancellationToken cancellationToken) 24 | { 25 | int result = await service.UpdateScoresAsync(request.Id, request.creatorScore, request.guestScore, cancellationToken); 26 | return result > 0 ? Results.Ok("Winner updated successfully") : Results.UnprocessableEntity("Failed to update winner"); 27 | } 28 | 29 | public static async Task DeleteAsync(int id, IRoomService service, CancellationToken cancellationToken) 30 | { 31 | int status = await service.DeleteAsync(id, cancellationToken); 32 | return status > 0 ? Results.BadRequest("Failed to delete room.") : Results.Ok(); 33 | } 34 | 35 | public static async Task?> GetAllAsync(IRoomService service, CancellationToken cancellationToken) 36 | => await service.GetAllAsync(cancellationToken); 37 | 38 | public static async Task GetAsync(int id, IRoomService service, CancellationToken cancellationToken) 39 | => await service.GetAsync(id, cancellationToken); 40 | } 41 | -------------------------------------------------------------------------------- /ModularMonolithicArch.TypeGame.Presentation/Pages/PlayPage.razor.css: -------------------------------------------------------------------------------- 1 | /* ========== Correct Effect Styles ========== */ 2 | .correct-effect { 3 | animation: correctAnimation 0.3s ease-in-out; 4 | color: #1ec0e7; /* Blue color */ 5 | } 6 | 7 | @keyframes correctAnimation { 8 | 0% { 9 | transform: scale(1); 10 | } 11 | 12 | 50% { 13 | transform: scale(1.2); 14 | } 15 | 16 | 100% { 17 | transform: scale(1); 18 | } 19 | } 20 | 21 | /* ========== Wrong Effect Styles ========== */ 22 | .wrong-effect { 23 | animation: shakeEffect 0.3s ease-in-out, wrong-blink 1s step-end infinite; 24 | background-color: #fff6f6; /* Light red background */ 25 | color: #e7246b; /* Red color */ 26 | } 27 | 28 | @keyframes shakeEffect { 29 | 0%, 100% { 30 | transform: rotate(0deg); 31 | } 32 | 33 | 25% { 34 | transform: rotate(-10deg); 35 | } 36 | 37 | 75% { 38 | transform: rotate(10deg); 39 | } 40 | } 41 | 42 | @keyframes wrong-blink { 43 | from, to { 44 | border-color: transparent; 45 | } 46 | 47 | 50% { 48 | border-color: #e7246b; /* Red underline color */ 49 | } 50 | } 51 | 52 | /* ========== Current Position Effect Styles ========== */ 53 | .current-position-effect { 54 | border-bottom: 4px solid #03a9f4; /* Blue underline */ 55 | animation: blink 1s step-end infinite; 56 | background-color: #e8f8ff; /* Light blue background */ 57 | color: #03a9f4; /* Blue color */ 58 | } 59 | 60 | /* Combines current position and wrong effects */ 61 | .current-position-wrong-effect { 62 | border-bottom: 4px solid #e7246b; /* Red underline */ 63 | animation: shakeEffect 0.3s ease-in-out, wrong-blink 1s step-end infinite; 64 | background-color: #fff6f6; /* Light red background */ 65 | color: #e7246b; /* Red text */ 66 | } 67 | 68 | @keyframes blink { 69 | from, to { 70 | border-color: transparent; 71 | } 72 | 73 | 50% { 74 | border-color: #03a9f4; /* Blue underline color */ 75 | } 76 | } 77 | 78 | /* ========== Other Utility Styles ========== */ 79 | .large-text { 80 | font-size: 32px; /* Adjust the size as needed */ 81 | margin-right: 5px; 82 | letter-spacing: 0.1em; 83 | } 84 | 85 | .pre-wrap-text { 86 | white-space: pre-wrap; 87 | } -------------------------------------------------------------------------------- /ModularMonolithicArch.User.Presentation/IdentityRedirectManager.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using Microsoft.AspNetCore.Http; 3 | using System.Diagnostics.CodeAnalysis; 4 | 5 | namespace ModularMonolithicArch.User.Presentation; 6 | 7 | public sealed class IdentityRedirectManager(NavigationManager navigationManager) 8 | { 9 | public const string StatusCookieName = "Identity.StatusMessage"; 10 | 11 | private static readonly CookieBuilder StatusCookieBuilder = new() 12 | { 13 | SameSite = SameSiteMode.Strict, 14 | HttpOnly = true, 15 | IsEssential = true, 16 | MaxAge = TimeSpan.FromSeconds(5), 17 | }; 18 | 19 | [DoesNotReturn] 20 | public void RedirectTo(string? uri) 21 | { 22 | uri ??= ""; 23 | 24 | // Prevent open redirects. 25 | if (!Uri.IsWellFormedUriString(uri, UriKind.Relative)) 26 | { 27 | uri = navigationManager.ToBaseRelativePath(uri); 28 | } 29 | 30 | // During static rendering, NavigateTo throws a NavigationException which is handled by the framework as a redirect. 31 | // So as long as this is called from a statically rendered Identity component, the InvalidOperationException is never thrown. 32 | navigationManager.NavigateTo(uri); 33 | throw new InvalidOperationException($"{nameof(IdentityRedirectManager)} can only be used during static rendering."); 34 | } 35 | 36 | [DoesNotReturn] 37 | public void RedirectTo(string uri, Dictionary queryParameters) 38 | { 39 | var uriWithoutQuery = navigationManager.ToAbsoluteUri(uri).GetLeftPart(UriPartial.Path); 40 | var newUri = navigationManager.GetUriWithQueryParameters(uriWithoutQuery, queryParameters); 41 | RedirectTo(newUri); 42 | } 43 | 44 | [DoesNotReturn] 45 | public void RedirectToWithStatus(string uri, string message, HttpContext context) 46 | { 47 | context.Response.Cookies.Append(StatusCookieName, message, StatusCookieBuilder.Build(context)); 48 | RedirectTo(uri); 49 | } 50 | 51 | private string CurrentPath => navigationManager.ToAbsoluteUri(navigationManager.Uri).GetLeftPart(UriPartial.Path); 52 | 53 | [DoesNotReturn] 54 | public void RedirectToCurrentPage() => RedirectTo(CurrentPath); 55 | 56 | [DoesNotReturn] 57 | public void RedirectToCurrentPageWithStatus(string message, HttpContext context) 58 | => RedirectToWithStatus(CurrentPath, message, context); 59 | } 60 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web/Configuration/IdentityExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components.Authorization; 2 | using Microsoft.AspNetCore.Identity; 3 | using ModularMonolithicArch.User.Presentation; 4 | using ModularMonolithicArch.UserModule.Shared.Domain.Entity; 5 | 6 | namespace ModularMonolithicArch.Web.Configuration; 7 | 8 | public static class IdentityExtension 9 | { 10 | public static IServiceCollection ConfigureIdentity(this IServiceCollection services) 11 | { 12 | ArgumentNullException.ThrowIfNull(services); 13 | 14 | services.AddCascadingAuthenticationState(); 15 | 16 | services.AddAuthentication(options => 17 | { 18 | options.DefaultScheme = IdentityConstants.ApplicationScheme; 19 | options.DefaultSignInScheme = IdentityConstants.ExternalScheme; 20 | }).AddIdentityCookies(); 21 | 22 | services.AddIdentityCore(options => options.SignIn.RequireConfirmedAccount = true) 23 | .AddSignInManager() 24 | .AddDefaultTokenProviders(); 25 | 26 | services.AddScoped(); 27 | services.AddScoped(); 28 | 29 | return services; 30 | } 31 | 32 | public static WebApplication ConfigureIdentityMiddlewares(this WebApplication app) 33 | { 34 | ArgumentNullException.ThrowIfNull(app); 35 | 36 | app.UseAuthentication(); 37 | app.UseAuthorization(); 38 | 39 | return app; 40 | } 41 | 42 | public static async Task InitializeRole(RoleManager roleManager, UserManager userManager) 43 | { 44 | string[] roles = ["Admin"]; 45 | 46 | foreach (string role in roles) 47 | { 48 | var identityRole = new IdentityRole(role); 49 | await roleManager.CreateAsync(identityRole); 50 | } 51 | 52 | var user = await userManager.FindByEmailAsync("admin@gmail.com"); 53 | if (user is null) 54 | { 55 | user = new ApplicationUser 56 | { 57 | Email = "admin@gmail.com", 58 | UserName = "admin@gmail.com", 59 | EmailConfirmed = true, 60 | }; 61 | await userManager.CreateAsync(user, "pP_0987"); 62 | } 63 | 64 | if (!await userManager.IsInRoleAsync(user, "Admin")) 65 | { 66 | var result = await userManager.AddToRoleAsync(user, "Admin"); 67 | 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Shared/Sections/ModalSection.razor: -------------------------------------------------------------------------------- 1 | @implements IDisposable 2 | @inject ModalService ModalService 3 | 4 | 33 | 34 | @code { 35 | protected override async Task OnAfterRenderAsync(bool firstRender) 36 | { 37 | if (firstRender) 38 | { 39 | await ModalService.InitializeAsync(); 40 | } 41 | } 42 | 43 | protected override void OnInitialized() 44 | => ModalService.OnFire += NotifyStateHasChanged; 45 | 46 | private void NotifyStateHasChanged(object? sender, EventArgs e) 47 | => StateHasChanged(); 48 | 49 | public void Dispose() 50 | => ModalService.OnFire -= NotifyStateHasChanged; 51 | } 52 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /ModularMonolithicArch.Web/ModularMonolithicArch.Web/wwwroot/app.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 3 | } 4 | 5 | a, .btn-link { 6 | color: #006bb7; 7 | } 8 | 9 | .btn-primary { 10 | color: #fff; 11 | background-color: #1b6ec2; 12 | border-color: #1861ac; 13 | } 14 | 15 | .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { 16 | box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; 17 | } 18 | 19 | .content { 20 | padding-top: 1.1rem; 21 | } 22 | 23 | h1:focus { 24 | outline: none; 25 | } 26 | 27 | .valid.modified:not([type=checkbox]) { 28 | outline: 1px solid #26b050; 29 | } 30 | 31 | .invalid { 32 | outline: 1px solid #e50000; 33 | } 34 | 35 | .validation-message { 36 | color: #e50000; 37 | } 38 | 39 | .blazor-error-boundary { 40 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; 41 | padding: 1rem 1rem 1rem 3.7rem; 42 | color: white; 43 | } 44 | 45 | .blazor-error-boundary::after { 46 | content: "An error has occurred." 47 | } 48 | 49 | .darker-border-checkbox.form-check-input { 50 | border-color: #929292; 51 | } 52 | -------------------------------------------------------------------------------- /ModularMonolithicArch.User.Presentation/Pages/RegisterPage.razor: -------------------------------------------------------------------------------- 1 | @page "/account/register" 2 | 3 | @inject NavigationManager NavigationManager 4 | @inject IdentityRedirectManager RedirectManager 5 | @inject IMediator Mediator 6 | 7 | Register 8 | 9 |

Register

10 | 11 |
12 |
13 | 14 | 15 |

Create a new account.

16 |
17 |
18 | 19 | 20 | 21 |
22 |
23 | 24 | 25 | 26 |
27 |
28 | 29 | 30 | 31 |
32 | 33 | 34 |
35 |
36 |
37 | 38 | @code { 39 | private IEnumerable? identityErrors; 40 | 41 | [SupplyParameterFromForm] 42 | private Register.RegisterDto RegisterDto { get; set; } = new(); 43 | 44 | [SupplyParameterFromQuery] 45 | private string? ReturnUrl { get; set; } 46 | 47 | private string? Message => identityErrors is null ? null : $"Error: {string.Join(", ", identityErrors.Select(error => error.Description))}"; 48 | 49 | public async Task RegisterUser() 50 | { 51 | var result = await Mediator.Send(new Register.RegisterRequest(RegisterDto)); 52 | if (!result.Succeeded) 53 | { 54 | identityErrors = result.Errors; 55 | return; 56 | } 57 | 58 | var request = new Login.LoginRequest(new Login.LoginDto { Email = RegisterDto.Email, Password = RegisterDto.Password, RememberMe = true }); 59 | await Mediator.Send(request); 60 | RedirectManager.RedirectTo(ReturnUrl); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Presentation/Pages/LevelsPage.razor: -------------------------------------------------------------------------------- 1 | @page "/wordGame/levels/{CategoryName}/{CategoryId:int}" 2 | @rendermode InteractiveServer 3 | @attribute [StreamRendering] 4 | 5 | @inject IMediator Mediator 6 | @inject ILevelService LevelService 7 | @inject AuthenticationStateProvider Auth 8 | @inject NavigationManager NavManager 9 | @inject ModalService ModalService 10 | 11 |
12 |

Levels

13 | 14 |
15 | @if (levelDtos!.Any()) 16 | { 17 |
18 | @foreach (var levelDto in levelDtos!) 19 | { 20 | 21 | } 22 |
23 | } 24 | else 25 | { 26 |

There is no level to show

27 | } 28 | 29 | 30 | @code { 31 | [Parameter, EditorRequired] public int CategoryId { get; set; } 32 | [Parameter, EditorRequired] public string CategoryName { get; set; } = default!; 33 | 34 | private List? levelDtos = new(); 35 | private HealthSection _health = default!; 36 | private string? message; 37 | private bool? isAuthenticated; 38 | 39 | 40 | protected override async Task OnAfterRenderAsync(bool firstRender) 41 | { 42 | if (firstRender) 43 | { 44 | var cts = new CancellationTokenSource(); 45 | var authState = await ConfigAuthAsync(); 46 | if (isAuthenticated is true) 47 | { 48 | int profileHealth = await _health.GetProfileHealthAsync(authState.User.Identity?.Name!); 49 | if (profileHealth == 0) 50 | { 51 | message = "You need more health."; 52 | } 53 | } 54 | else 55 | { 56 | int? localHealth = await _health.GetLocalHealthAsync(); 57 | if (localHealth.HasValue && localHealth.Value == 0) 58 | { 59 | message = "You need to log in to continue."; 60 | } 61 | } 62 | levelDtos = await LevelService.GetAllByIdAsync(CategoryId, cts.Token); 63 | StateHasChanged(); 64 | } 65 | } 66 | 67 | private async Task ConfigAuthAsync() 68 | { 69 | var authState = await Auth.GetAuthenticationStateAsync(); 70 | isAuthenticated = authState.User.Identity?.IsAuthenticated; 71 | 72 | return authState; 73 | } 74 | 75 | private async Task NavToLevel(int levelId) 76 | { 77 | if (_health.Value == 0) 78 | { 79 | await ModalService.FireAsync(new ModalOptions { Text = message, ShowConfirmButton = true, ConfirmButtonText = "ok" }); 80 | } 81 | else 82 | { 83 | NavManager.NavigateTo($"/wordGame/level/{levelId}/{CategoryName}/{CategoryId}"); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /ModularMonolithicArch.ImageGame.Domain/Services/ImageGameService.cs: -------------------------------------------------------------------------------- 1 | using ModularMonolithicArch.ImageGame.Domain.Entities; 2 | using ModularMonolithicArch.ImageGame.Domain.Repository; 3 | 4 | namespace ModularMonolithicArch.ImageGame.Domain.Services; 5 | 6 | public class ImageGameService(IImageService imageService) : IImageGameService 7 | { 8 | private List _selectedItems = []; 9 | private const int _maxSelectedItem = 2; 10 | private int _creatorScore; 11 | private int _guestScore; 12 | private bool _creatorTurn = true; 13 | 14 | public event EventHandler OnEndGame = default!; 15 | 16 | public bool CreatorTurn => _creatorTurn; 17 | public int CreatorScore => _creatorScore; 18 | public int GuestScore => _guestScore; 19 | public List Images { get; set; } = []; 20 | 21 | public void Initialize(List images, int seed) 22 | { 23 | Images = images 24 | .DuplicateImages() 25 | .RandomizeImages(seed) 26 | .ToList(); 27 | } 28 | 29 | public async Task AddSelectedItem(Image image) 30 | { 31 | if (_selectedItems.Count < _maxSelectedItem) 32 | { 33 | _selectedItems.Add(image); 34 | } 35 | 36 | if (_selectedItems.Count is _maxSelectedItem) 37 | { 38 | await CheckAnswer(); 39 | } 40 | } 41 | 42 | private async Task CheckAnswer() 43 | { 44 | if (_selectedItems[0].Name == _selectedItems[1].Name) 45 | { 46 | ManageScores(); 47 | } 48 | else 49 | { 50 | await Task.Delay(500); 51 | Images.Where(i => i.Name == _selectedItems[0].Name || i.Name == _selectedItems[1].Name) 52 | .ToList() 53 | .ForEach(imageService.MarkAsInVisible); 54 | 55 | _creatorTurn = !_creatorTurn; 56 | } 57 | 58 | if (Images.All(i => i.IsVisible == true)) 59 | { 60 | OnEndGame.Invoke(this, new EndGameEventArgs { Message = "Game ended🤩" }); 61 | } 62 | _selectedItems.Clear(); 63 | } 64 | 65 | public void ResetStates() 66 | { 67 | _creatorTurn = true; 68 | _creatorScore = 0; 69 | _guestScore = 0; 70 | } 71 | 72 | private int ManageScores() 73 | => _creatorTurn ? _creatorScore++ : _guestScore++; 74 | } 75 | 76 | public class EndGameEventArgs : EventArgs 77 | { 78 | public string Message { get; init; } = string.Empty; 79 | } 80 | 81 | internal static class ImageGameServiceExtensions 82 | { 83 | private static int _id = 1; 84 | public static IEnumerable DuplicateImages(this IEnumerable source) 85 | => source.Concat(source).Select(image => new Image() { Id = _id++, Name = image.Name }); 86 | 87 | public static IEnumerable RandomizeImages(this IEnumerable source, int seed) 88 | { 89 | var random = new Random(seed); 90 | return source.OrderBy(_ => random.Next()); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /ModularMonolithicArch.User.Presentation/Pages/LoginPage.razor: -------------------------------------------------------------------------------- 1 | @page "/account/login" 2 | 3 | @inject NavigationManager NavigationManager 4 | @inject IdentityRedirectManager RedirectManager 5 | @inject IMediator Mediator 6 | 7 | Login 8 | 9 |

Log in

10 |
11 |
12 |
13 | 14 | 15 |

Use a local account to log in.

16 |
17 |
18 | 19 | 20 | 21 |
22 |
23 | 24 | 25 | 26 |
27 |
28 | 32 |
33 |
34 | 35 |
36 | 41 | 42 |
43 |
44 |
45 |
46 | 47 | @code { 48 | private string? errorMessage; 49 | 50 | [CascadingParameter] 51 | private HttpContext HttpContext { get; set; } = default!; 52 | 53 | [SupplyParameterFromForm] 54 | private Login.LoginDto LoginDto { get; set; } = new(); 55 | 56 | [SupplyParameterFromQuery] 57 | private string? ReturnUrl { get; set; } 58 | 59 | public async Task LoginUser() 60 | { 61 | var result = await Mediator.Send(new Login.LoginRequest(LoginDto)); 62 | if (result.Succeeded) 63 | { 64 | RedirectManager.RedirectTo(ReturnUrl); 65 | } 66 | else 67 | { 68 | errorMessage = "Error: Invalid login attempt."; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /ModularMonolithicArch.WordGame.Infrastructure/Migrations/20240831140842_wordGame.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace ModularMonolithicArch.WordGame.Infrastructure.Migrations 6 | { 7 | /// 8 | public partial class wordGame : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.EnsureSchema( 14 | name: "wordGame"); 15 | 16 | migrationBuilder.CreateTable( 17 | name: "Categories", 18 | schema: "wordGame", 19 | columns: table => new 20 | { 21 | Id = table.Column(type: "int", nullable: false) 22 | .Annotation("SqlServer:Identity", "1, 1"), 23 | Name = table.Column(type: "nvarchar(max)", nullable: false) 24 | }, 25 | constraints: table => 26 | { 27 | table.PrimaryKey("PK_Categories", x => x.Id); 28 | }); 29 | 30 | migrationBuilder.CreateTable( 31 | name: "Levels", 32 | schema: "wordGame", 33 | columns: table => new 34 | { 35 | Id = table.Column(type: "int", nullable: false) 36 | .Annotation("SqlServer:Identity", "1, 1"), 37 | LevelNumber = table.Column(type: "int", nullable: false), 38 | Word = table.Column(type: "nvarchar(max)", nullable: false), 39 | Hint = table.Column(type: "nvarchar(max)", nullable: false), 40 | LevelStatus = table.Column(type: "int", nullable: false), 41 | CategoryId = table.Column(type: "int", nullable: false) 42 | }, 43 | constraints: table => 44 | { 45 | table.PrimaryKey("PK_Levels", x => x.Id); 46 | table.ForeignKey( 47 | name: "FK_Levels_Categories_CategoryId", 48 | column: x => x.CategoryId, 49 | principalSchema: "wordGame", 50 | principalTable: "Categories", 51 | principalColumn: "Id", 52 | onDelete: ReferentialAction.Cascade); 53 | }); 54 | 55 | migrationBuilder.CreateIndex( 56 | name: "IX_Levels_CategoryId", 57 | schema: "wordGame", 58 | table: "Levels", 59 | column: "CategoryId"); 60 | } 61 | 62 | /// 63 | protected override void Down(MigrationBuilder migrationBuilder) 64 | { 65 | migrationBuilder.DropTable( 66 | name: "Levels", 67 | schema: "wordGame"); 68 | 69 | migrationBuilder.DropTable( 70 | name: "Categories", 71 | schema: "wordGame"); 72 | } 73 | } 74 | } 75 | --------------------------------------------------------------------------------