From 75624601712a11c679cc91b008bbdc533fb5640b Mon Sep 17 00:00:00 2001 From: Vova Zelenyi Date: Sun, 6 Aug 2023 17:35:10 +0200 Subject: [PATCH 01/23] Init --- .gitignore | 393 ++++++++++++++++++ Films/.dockerignore | 25 ++ Films/Films.API/Controllers/FilmController.cs | 136 ++++++ Films/Films.API/Dockerfile | 20 + Films/Films.API/Films.API.csproj | 33 ++ Films/Films.API/Program.cs | 46 ++ .../Films.API/Properties/launchSettings.json | 31 ++ Films/Films.API/appsettings.Development.json | 8 + Films/Films.API/appsettings.json | 13 + Films/Films.Core/FilmMapper.cs | 20 + Films/Films.Core/FilmService.cs | 133 ++++++ Films/Films.Core/Films.Core.csproj | 20 + Films/Films.Core/GenreMapper.cs | 18 + Films/Films.DTOs/FilmCreateDto.cs | 34 ++ Films/Films.DTOs/FilmDto.cs | 43 ++ Films/Films.DTOs/FilmUpdateDto.cs | 34 ++ Films/Films.DTOs/Films.DTOs.csproj | 9 + Films/Films.DTOs/GenreDto.cs | 18 + Films/Films.Domain/Film.cs | 45 ++ Films/Films.Domain/Films.Domain.csproj | 14 + Films/Films.Domain/Genre.cs | 18 + Films/Films.Infrastructure/FilmDbContext.cs | 30 ++ .../Films.Infrastructure/FilmDbInitialize.cs | 20 + .../Films.Infrastructure.csproj | 18 + Films/Films.sln | 40 ++ Films/global.json | 7 + 26 files changed, 1226 insertions(+) create mode 100644 .gitignore create mode 100644 Films/.dockerignore create mode 100644 Films/Films.API/Controllers/FilmController.cs create mode 100644 Films/Films.API/Dockerfile create mode 100644 Films/Films.API/Films.API.csproj create mode 100644 Films/Films.API/Program.cs create mode 100644 Films/Films.API/Properties/launchSettings.json create mode 100644 Films/Films.API/appsettings.Development.json create mode 100644 Films/Films.API/appsettings.json create mode 100644 Films/Films.Core/FilmMapper.cs create mode 100644 Films/Films.Core/FilmService.cs create mode 100644 Films/Films.Core/Films.Core.csproj create mode 100644 Films/Films.Core/GenreMapper.cs create mode 100644 Films/Films.DTOs/FilmCreateDto.cs create mode 100644 Films/Films.DTOs/FilmDto.cs create mode 100644 Films/Films.DTOs/FilmUpdateDto.cs create mode 100644 Films/Films.DTOs/Films.DTOs.csproj create mode 100644 Films/Films.DTOs/GenreDto.cs create mode 100644 Films/Films.Domain/Film.cs create mode 100644 Films/Films.Domain/Films.Domain.csproj create mode 100644 Films/Films.Domain/Genre.cs create mode 100644 Films/Films.Infrastructure/FilmDbContext.cs create mode 100644 Films/Films.Infrastructure/FilmDbInitialize.cs create mode 100644 Films/Films.Infrastructure/Films.Infrastructure.csproj create mode 100644 Films/Films.sln create mode 100644 Films/global.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c2b29e --- /dev/null +++ b/.gitignore @@ -0,0 +1,393 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Nuget personal access tokens and Credentials +nuget.config + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.idea/ +*.sln.iml + +# sqlite +*.db +*.db-wal +*.db-shm \ No newline at end of file diff --git a/Films/.dockerignore b/Films/.dockerignore new file mode 100644 index 0000000..cd967fc --- /dev/null +++ b/Films/.dockerignore @@ -0,0 +1,25 @@ +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.idea +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file diff --git a/Films/Films.API/Controllers/FilmController.cs b/Films/Films.API/Controllers/FilmController.cs new file mode 100644 index 0000000..2425df2 --- /dev/null +++ b/Films/Films.API/Controllers/FilmController.cs @@ -0,0 +1,136 @@ +using Films.Core; +using Films.DTOs; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Films.API.Controllers +{ + /// + /// Controller for handling film-related operations. + /// + [ApiController] + [Route("api/[controller]")] + public class FilmController : ControllerBase + { + private readonly FilmService _filmService; + + /// + /// Initializes a new instance of the class. + /// + /// The film service to retrieve and manage film data. + public FilmController(FilmService filmService) + { + _filmService = filmService; + } + + /// + /// Searches for films by name. + /// + /// The name of the film to search for. + /// Returns a list of films matching the search criteria. + [HttpGet("search")] + public async Task>> SearchFilm(string filmName) + { + try + { + var films = await _filmService.SearchFilm(filmName); + return Ok(films); + } + catch (FilmService.FilmNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception) + { + // If an unexpected exception occurs return a 500 Internal Server Error response. + return StatusCode(500, "An error occurred while processing your request."); + } + } + + /// + /// Gets all films. + /// + /// Returns a list of all films. + [HttpGet("all")] + public async Task>> GetAllFilms() + { + try + { + var films = await _filmService.GetAllFilms(); + return Ok(films); + } + catch (Exception) + { + return StatusCode(500, "An error occurred while processing your request."); + } + } + + /// + /// Deletes a film by ID. + /// + /// The ID of the film to delete. + /// Returns a status indicating the success of the delete operation. + [HttpDelete("{filmId}")] + public async Task DeleteFilm(int filmId) + { + try + { + await _filmService.DeleteFilm(filmId); + return Ok(); + } + catch (FilmService.FilmNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception) + { + return StatusCode(500, "An error occurred while processing your request."); + } + } + + /// + /// Creates a new film. + /// + /// The DTO containing the film information to create. + /// Returns the ID of the created film. + [HttpPost("create")] + public async Task> CreateFilm(FilmCreateDto filmDto) + { + try + { + var createdFilmId = await _filmService.CreateFilm(filmDto); + return Ok(createdFilmId); + } + catch (Exception) + { + return StatusCode(500, "An error occurred while processing your request."); + } + } + + /// + /// Updates a film by ID. + /// + /// The ID of the film to update. + /// The DTO containing the updated film information. + /// Returns a status indicating the success of the update operation. + [HttpPut("update/{filmId}")] + public async Task UpdateFilm(int filmId, FilmUpdateDto filmDto) + { + try + { + await _filmService.UpdateFilm(filmId, filmDto); + return Ok(); + } + catch (FilmService.FilmNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception) + { + return StatusCode(500, "An error occurred while processing your request."); + } + } + } +} diff --git a/Films/Films.API/Dockerfile b/Films/Films.API/Dockerfile new file mode 100644 index 0000000..37ab475 --- /dev/null +++ b/Films/Films.API/Dockerfile @@ -0,0 +1,20 @@ +FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 + +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +WORKDIR /src +COPY ["Films.API/Films.API.csproj", "Films.API/"] +RUN dotnet restore "Films.API/Films.API.csproj" +COPY . . +WORKDIR "/src/Films.API" +RUN dotnet build "Films.API.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "Films.API.csproj" -c Release -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Films.API.dll"] diff --git a/Films/Films.API/Films.API.csproj b/Films/Films.API/Films.API.csproj new file mode 100644 index 0000000..2d6344e --- /dev/null +++ b/Films/Films.API/Films.API.csproj @@ -0,0 +1,33 @@ + + + + net6.0 + enable + enable + Linux + + + + + + + + + + + + + + + + + .dockerignore + + + + + + + + + diff --git a/Films/Films.API/Program.cs b/Films/Films.API/Program.cs new file mode 100644 index 0000000..b3c8ff8 --- /dev/null +++ b/Films/Films.API/Program.cs @@ -0,0 +1,46 @@ + +using System.Reflection; +using Films.Core; +using Films.Domain; +using Films.Infrastructure; +using Kirel.Repositories; +using Kirel.Repositories.Interfaces; +using Microsoft.EntityFrameworkCore; + +var builder = WebApplication.CreateBuilder(args); + +/*var authOptions = builder.Configuration.GetSection("AuthOptions").Get();*/ + +//taking connection string from appsettings.json +var connectionString = builder.Configuration.GetConnectionString("SqlConnection"); +builder.Services.AddDbContext(options => + options.UseSqlServer(connectionString)); + +// Add services to the container. +builder.Services.AddScoped(); +builder.Services.AddScoped, KirelGenericEntityFrameworkRepository>(); + +//Add AutoMapper + +builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly()); +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); +FilmDbInitialize.Initialize(app.Services.GetRequiredService().CreateScope().ServiceProvider); +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/Films/Films.API/Properties/launchSettings.json b/Films/Films.API/Properties/launchSettings.json new file mode 100644 index 0000000..af866fb --- /dev/null +++ b/Films/Films.API/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:10057", + "sslPort": 44332 + } + }, + "profiles": { + "Films.API": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7016;http://localhost:5261", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Films/Films.API/appsettings.Development.json b/Films/Films.API/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/Films/Films.API/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Films/Films.API/appsettings.json b/Films/Films.API/appsettings.json new file mode 100644 index 0000000..04fb93d --- /dev/null +++ b/Films/Films.API/appsettings.json @@ -0,0 +1,13 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + "PostgreConnection": "Host=localhost;Port=5432;Database=Films;Username=lsodis;Password=6742", + "SqlConnection": "Server=localhost;Database=Films;Trusted_Connection=True;Encrypt=False;" + }, + "AllowedHosts": "*" +} diff --git a/Films/Films.Core/FilmMapper.cs b/Films/Films.Core/FilmMapper.cs new file mode 100644 index 0000000..637e9ab --- /dev/null +++ b/Films/Films.Core/FilmMapper.cs @@ -0,0 +1,20 @@ +using Films.Domain; +using Films.DTOs; +using AutoMapper; +namespace Films.Core; + +/// +/// Mapping Film for FilmDTO +/// +public class FilmMapper : Profile +{ + /// + /// Films constructor + /// + public FilmMapper() + { + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + } +} \ No newline at end of file diff --git a/Films/Films.Core/FilmService.cs b/Films/Films.Core/FilmService.cs new file mode 100644 index 0000000..89f044c --- /dev/null +++ b/Films/Films.Core/FilmService.cs @@ -0,0 +1,133 @@ +using AutoMapper; +using Films.Domain; +using Films.DTOs; +using Kirel.Repositories.Interfaces; +using Kirel.Repositories.Sorts; + +namespace Films.Core +{ + /// + /// Service responsible for film-related operations and logic. + /// + public class FilmService + { + private readonly IKirelGenericEntityRepository _filmRepository; + private readonly IMapper _mapper; + + /// + /// Initializes a new instance of the class. + /// + /// The repository for accessing film data. + public FilmService(IKirelGenericEntityRepository filmRepository, IMapper mapper) + { + this._filmRepository = filmRepository; + _mapper = mapper; + } + + /// + /// Searches for films in the database by name. + /// + /// The name of the film to search for. + /// A list of film DTOs matching the search criteria. + public async Task> SearchFilm(string filmName) + { + // Search for films in the database based on the provided film name. + var existingFilm = await _filmRepository.GetList(m => m.Name != null && m.Name.Contains(filmName)); + + var enumerable = existingFilm.ToList(); + if (existingFilm == null || !enumerable.Any()) + { + throw new FilmNotFoundException($"Film with the title '{filmName}' was not found in the database."); + } + + // Map the retrieved films to DTOs for presentation. + var filmsList = enumerable.Select(film => new FilmDto + { + Id = film.Id, + Name = film.Name, + Rating = film.Rating, + Description = film.Description, + PosterURL = film.PosterUrl, + Genres = film.Genres + }).ToList(); + + return filmsList; + } + /// + /// Creates a new film. + /// + /// The DTO containing the film information to create. + /// The ID of the created film. + public async Task CreateFilm(FilmCreateDto filmDto) + { + // Map the DTO to an entity and insert it into the repository. + var film = _mapper.Map(filmDto); + var createdFilm = await _filmRepository.Insert(film); + return createdFilm.Id; + } + + /// + /// Updates an existing film. + /// + /// The ID of the film to update. + /// The DTO containing the updated film information. + public async Task UpdateFilm(int filmId, FilmUpdateDto filmDto) + { + // Retrieve the existing film by its ID. + var existingFilm = await _filmRepository.GetById(filmId); + if (existingFilm == null) + { + throw new FilmNotFoundException($"Film with ID '{filmId}' was not found in the database."); + } + + // Update the existing film entity with the new data from the DTO. + _mapper.Map(filmDto, existingFilm); + + // Perform the update in the repository. + await _filmRepository.Update(existingFilm); + } + + /// + /// Gets a list of all films. + /// + /// A list of film DTOs representing all films in the database. + public async Task> GetAllFilms() + { + // Get the total count of films in the database. + var allCount = await _filmRepository.Count(expression: null); + + // Retrieve a paginated list of all films and map them to DTOs. + var films = await _filmRepository.GetList(null, "Created", SortDirection.Asc, 1, allCount); + var filmsDto = _mapper.Map>(films); + return filmsDto; + } + + /// + /// Deletes a film from the database. + /// + /// The ID of the film to delete. + public async Task DeleteFilm(int filmId) + { + // Retrieve the film by its ID. + var film = await _filmRepository.GetById(filmId); + + if (film == null) + { + throw new FilmNotFoundException($"Film with ID '{filmId}' was not found in the database."); + } + + // Delete the film from the repository. + await _filmRepository.Delete(filmId); + } + + /// + /// Custom exception class for indicating that a film was not found. + /// + public class FilmNotFoundException : Exception + { + public FilmNotFoundException(string message) : base(message) + { + } + } + } +} diff --git a/Films/Films.Core/Films.Core.csproj b/Films/Films.Core/Films.Core.csproj new file mode 100644 index 0000000..762ad68 --- /dev/null +++ b/Films/Films.Core/Films.Core.csproj @@ -0,0 +1,20 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + + diff --git a/Films/Films.Core/GenreMapper.cs b/Films/Films.Core/GenreMapper.cs new file mode 100644 index 0000000..a844b30 --- /dev/null +++ b/Films/Films.Core/GenreMapper.cs @@ -0,0 +1,18 @@ +using AutoMapper; +using Films.Domain; +using Films.DTOs; + +namespace Films.Core; +/// +/// Mapping Genre for GenreDTO +/// +public class GenreMapper : Profile +{ + /// + /// GenreMapping constructor + /// + public GenreMapper() + { + CreateMap().ReverseMap(); + } +} \ No newline at end of file diff --git a/Films/Films.DTOs/FilmCreateDto.cs b/Films/Films.DTOs/FilmCreateDto.cs new file mode 100644 index 0000000..6864757 --- /dev/null +++ b/Films/Films.DTOs/FilmCreateDto.cs @@ -0,0 +1,34 @@ +namespace Films.DTOs; + +public class FilmCreateDto +{ + /// + /// Gets or sets the name of the film. + /// + public string? Name { get; set; } + + /// + /// Gets or sets the rating of the film. + /// + public int Rating { get; set; } + + /// + /// Gets or sets the description of the film. + /// + public string? Description { get; set; } + + /// + /// Gets or sets the list of genres associated with the film. + /// + public List? Genres { get; set; } + + /// + /// Gets or sets the URL of the film's poster. + /// + public string? PosterURL { get; set; } + + /// + /// Gets or sets the timestamp when the film was created. + /// + public DateTime Created { get; set; } +} \ No newline at end of file diff --git a/Films/Films.DTOs/FilmDto.cs b/Films/Films.DTOs/FilmDto.cs new file mode 100644 index 0000000..5e8371e --- /dev/null +++ b/Films/Films.DTOs/FilmDto.cs @@ -0,0 +1,43 @@ +namespace Films.DTOs +{ + /// + /// Data transfer object (DTO) representing film details. + /// + public class FilmDto + { + /// + /// Gets or sets the unique identifier for the film. + /// + public int Id { get; set; } + + /// + /// Gets or sets the name of the film. + /// + public string? Name { get; set; } + + /// + /// Gets or sets the rating of the film. + /// + public int Rating { get; set; } + + /// + /// Gets or sets the description of the film. + /// + public string? Description { get; set; } + + /// + /// Gets or sets the list of genres associated with the film. + /// + public List? Genres { get; set; } + + /// + /// Gets or sets the URL of the film's poster. + /// + public string? PosterURL { get; set; } + + /// + /// Gets or sets the timestamp when the film was created. + /// + public DateTime Created { get; set; } + } +} \ No newline at end of file diff --git a/Films/Films.DTOs/FilmUpdateDto.cs b/Films/Films.DTOs/FilmUpdateDto.cs new file mode 100644 index 0000000..fb920d3 --- /dev/null +++ b/Films/Films.DTOs/FilmUpdateDto.cs @@ -0,0 +1,34 @@ +namespace Films.DTOs; + +public class FilmUpdateDto +{ + /// + /// Gets or sets the name of the film. + /// + public string? Name { get; set; } + + /// + /// Gets or sets the rating of the film. + /// + public int Rating { get; set; } + + /// + /// Gets or sets the description of the film. + /// + public string? Description { get; set; } + + /// + /// Gets or sets the list of genres associated with the film. + /// + public List? Genres { get; set; } + + /// + /// Gets or sets the URL of the film's poster. + /// + public string? PosterURL { get; set; } + + /// + /// Gets or sets the timestamp when the film was created. + /// + public DateTime Created { get; set; } +} \ No newline at end of file diff --git a/Films/Films.DTOs/Films.DTOs.csproj b/Films/Films.DTOs/Films.DTOs.csproj new file mode 100644 index 0000000..1b1aba9 --- /dev/null +++ b/Films/Films.DTOs/Films.DTOs.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/Films/Films.DTOs/GenreDto.cs b/Films/Films.DTOs/GenreDto.cs new file mode 100644 index 0000000..67c986d --- /dev/null +++ b/Films/Films.DTOs/GenreDto.cs @@ -0,0 +1,18 @@ +namespace Films.DTOs +{ + /// + /// Data transfer object (DTO) representing a film genre. + /// + public class GenreDto + { + /// + /// Gets or sets the unique identifier for the genre. + /// + public int Id { get; set; } + + /// + /// Gets or sets the name of the genre. + /// + public string? Name { get; set; } + } +} \ No newline at end of file diff --git a/Films/Films.Domain/Film.cs b/Films/Films.Domain/Film.cs new file mode 100644 index 0000000..786d6da --- /dev/null +++ b/Films/Films.Domain/Film.cs @@ -0,0 +1,45 @@ +using Kirel.Repositories.Interfaces; +namespace Films.Domain +{ + /// + /// Represents a film entity with details like name, rating, description, and genres. + /// Implements interfaces for creation timestamp tracking and using an integer as the key. + /// + public class Film : ICreatedAtTrackedEntity, IKeyEntity + { + /// + /// Gets or sets the unique identifier for the film. + /// + public int Id { get; set; } + + /// + /// Gets or sets the name of the film. + /// + public string? Name { get; set; } + + /// + /// Gets or sets the rating of the film. + /// + public int Rating { get; set; } + + /// + /// Gets or sets the description of the film. + /// + public string? Description { get; set; } + + /// + /// Gets or sets the list of genres associated with the film. + /// + public List? Genres { get; set; } + + /// + /// Gets or sets the URL of the film's poster. + /// + public string? PosterUrl { get; set; } + + /// + /// Gets or sets the timestamp when the film was created. + /// + public DateTime Created { get; set; } + } +} \ No newline at end of file diff --git a/Films/Films.Domain/Films.Domain.csproj b/Films/Films.Domain/Films.Domain.csproj new file mode 100644 index 0000000..0da779a --- /dev/null +++ b/Films/Films.Domain/Films.Domain.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + enable + + + + + + + + diff --git a/Films/Films.Domain/Genre.cs b/Films/Films.Domain/Genre.cs new file mode 100644 index 0000000..67d229f --- /dev/null +++ b/Films/Films.Domain/Genre.cs @@ -0,0 +1,18 @@ +namespace Films.Domain +{ + /// + /// Represents a genre associated with films. + /// + public class Genre + { + /// + /// Gets or sets the unique identifier for the genre. + /// + public int Id { get; set; } + + /// + /// Gets or sets the name of the genre. + /// + public string? Name { get; set; } + } +} \ No newline at end of file diff --git a/Films/Films.Infrastructure/FilmDbContext.cs b/Films/Films.Infrastructure/FilmDbContext.cs new file mode 100644 index 0000000..7ace614 --- /dev/null +++ b/Films/Films.Infrastructure/FilmDbContext.cs @@ -0,0 +1,30 @@ +using Films.Domain; +using Microsoft.EntityFrameworkCore; + +namespace Films.Infrastructure +{ + /// + /// Represents the database context for managing film-related data. + /// + public class FilmDbContext : DbContext + { + /// + /// Initializes a new instance of the class. + /// + /// The options used to configure the context. + public FilmDbContext(DbContextOptions options) : base(options) + { + + } + + /// + /// Gets or sets the DbSet for films in the database. + /// + private DbSet Films { get; set; } + + /// + /// Gets or sets the DbSet for genres in the database. + /// + private DbSet Genres { get; set; } + } +} \ No newline at end of file diff --git a/Films/Films.Infrastructure/FilmDbInitialize.cs b/Films/Films.Infrastructure/FilmDbInitialize.cs new file mode 100644 index 0000000..631cecf --- /dev/null +++ b/Films/Films.Infrastructure/FilmDbInitialize.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Films.Infrastructure +{ + /// + /// Utility class for initializing the film database and creating tables. + /// + public class FilmDbInitialize : Microsoft.EntityFrameworkCore.DbContext + { + /// + /// Initializes the film database and creates tables if they do not exist. + /// + /// The service provider to retrieve the database context. + public static void Initialize(IServiceProvider serviceProvider) + { + var context = serviceProvider.GetRequiredService(); + context.Database.EnsureCreated(); + } + } +} \ No newline at end of file diff --git a/Films/Films.Infrastructure/Films.Infrastructure.csproj b/Films/Films.Infrastructure/Films.Infrastructure.csproj new file mode 100644 index 0000000..1c75b8c --- /dev/null +++ b/Films/Films.Infrastructure/Films.Infrastructure.csproj @@ -0,0 +1,18 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + diff --git a/Films/Films.sln b/Films/Films.sln new file mode 100644 index 0000000..d8cba02 --- /dev/null +++ b/Films/Films.sln @@ -0,0 +1,40 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Films.API", "Films.API\Films.API.csproj", "{8C91B590-7FD7-46AA-A34A-6A4D09A0BC9C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Films.Infrastructure", "Films.Infrastructure\Films.Infrastructure.csproj", "{7B56086C-9FA8-4B4E-A2B0-B2DC27990C34}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Films.Core", "Films.Core\Films.Core.csproj", "{3DCC9533-8898-4241-B8EC-504F0DBD0B2F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Films.Domain", "Films.Domain\Films.Domain.csproj", "{904C56F6-4160-4A1B-BA15-002D574FAE1F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Films.DTOs", "Films.DTOs\Films.DTOs.csproj", "{5B5FACA7-DABE-472C-8813-85A8C18A6A4D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8C91B590-7FD7-46AA-A34A-6A4D09A0BC9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8C91B590-7FD7-46AA-A34A-6A4D09A0BC9C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C91B590-7FD7-46AA-A34A-6A4D09A0BC9C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8C91B590-7FD7-46AA-A34A-6A4D09A0BC9C}.Release|Any CPU.Build.0 = Release|Any CPU + {7B56086C-9FA8-4B4E-A2B0-B2DC27990C34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7B56086C-9FA8-4B4E-A2B0-B2DC27990C34}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7B56086C-9FA8-4B4E-A2B0-B2DC27990C34}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7B56086C-9FA8-4B4E-A2B0-B2DC27990C34}.Release|Any CPU.Build.0 = Release|Any CPU + {3DCC9533-8898-4241-B8EC-504F0DBD0B2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3DCC9533-8898-4241-B8EC-504F0DBD0B2F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3DCC9533-8898-4241-B8EC-504F0DBD0B2F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3DCC9533-8898-4241-B8EC-504F0DBD0B2F}.Release|Any CPU.Build.0 = Release|Any CPU + {904C56F6-4160-4A1B-BA15-002D574FAE1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {904C56F6-4160-4A1B-BA15-002D574FAE1F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {904C56F6-4160-4A1B-BA15-002D574FAE1F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {904C56F6-4160-4A1B-BA15-002D574FAE1F}.Release|Any CPU.Build.0 = Release|Any CPU + {5B5FACA7-DABE-472C-8813-85A8C18A6A4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B5FACA7-DABE-472C-8813-85A8C18A6A4D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B5FACA7-DABE-472C-8813-85A8C18A6A4D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B5FACA7-DABE-472C-8813-85A8C18A6A4D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Films/global.json b/Films/global.json new file mode 100644 index 0000000..1bcf6c0 --- /dev/null +++ b/Films/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "6.0.0", + "rollForward": "latestMinor", + "allowPrerelease": false + } +} \ No newline at end of file From b7329f3f1b81b15aa8734f7c9fd371d86f1cd3e0 Mon Sep 17 00:00:00 2001 From: Vova Zelenyi Date: Sun, 6 Aug 2023 17:50:22 +0200 Subject: [PATCH 02/23] Add XML comments --- Films/Films.API/Films.API.csproj | 4 +++- Films/Films.Core/FilmService.cs | 6 +++++- Films/Films.Core/Films.Core.csproj | 3 ++- Films/Films.DTOs/FilmCreateDto.cs | 8 +++----- Films/Films.DTOs/FilmDto.cs | 6 ------ Films/Films.DTOs/FilmUpdateDto.cs | 8 +++----- Films/Films.DTOs/Films.DTOs.csproj | 2 ++ Films/Films.DTOs/GenreDto.cs | 1 - Films/Films.Domain/Film.cs | 6 ------ Films/Films.Domain/Films.Domain.csproj | 4 +++- Films/Films.Domain/Genre.cs | 1 - Films/Films.Infrastructure/FilmDbContext.cs | 4 ++-- Films/Films.Infrastructure/Films.Infrastructure.csproj | 6 ++++-- 13 files changed, 27 insertions(+), 32 deletions(-) diff --git a/Films/Films.API/Films.API.csproj b/Films/Films.API/Films.API.csproj index 2d6344e..c468e9d 100644 --- a/Films/Films.API/Films.API.csproj +++ b/Films/Films.API/Films.API.csproj @@ -5,6 +5,8 @@ enable enable Linux + true + true @@ -12,7 +14,7 @@ - + diff --git a/Films/Films.Core/FilmService.cs b/Films/Films.Core/FilmService.cs index 89f044c..6c5a8e0 100644 --- a/Films/Films.Core/FilmService.cs +++ b/Films/Films.Core/FilmService.cs @@ -18,12 +18,12 @@ public class FilmService /// Initializes a new instance of the class. /// /// The repository for accessing film data. + /// AutoMapper instance public FilmService(IKirelGenericEntityRepository filmRepository, IMapper mapper) { this._filmRepository = filmRepository; _mapper = mapper; } - /// /// Searches for films in the database by name. /// @@ -125,6 +125,10 @@ public async Task DeleteFilm(int filmId) /// public class FilmNotFoundException : Exception { + /// + /// Retrieves exception message + /// + /// public FilmNotFoundException(string message) : base(message) { } diff --git a/Films/Films.Core/Films.Core.csproj b/Films/Films.Core/Films.Core.csproj index 762ad68..fa4bd3a 100644 --- a/Films/Films.Core/Films.Core.csproj +++ b/Films/Films.Core/Films.Core.csproj @@ -4,6 +4,8 @@ net6.0 enable enable + true + true @@ -14,7 +16,6 @@ - diff --git a/Films/Films.DTOs/FilmCreateDto.cs b/Films/Films.DTOs/FilmCreateDto.cs index 6864757..2089a41 100644 --- a/Films/Films.DTOs/FilmCreateDto.cs +++ b/Films/Films.DTOs/FilmCreateDto.cs @@ -1,32 +1,30 @@ namespace Films.DTOs; +/// +/// Film CreateDTO +/// public class FilmCreateDto { /// /// Gets or sets the name of the film. /// public string? Name { get; set; } - /// /// Gets or sets the rating of the film. /// public int Rating { get; set; } - /// /// Gets or sets the description of the film. /// public string? Description { get; set; } - /// /// Gets or sets the list of genres associated with the film. /// public List? Genres { get; set; } - /// /// Gets or sets the URL of the film's poster. /// public string? PosterURL { get; set; } - /// /// Gets or sets the timestamp when the film was created. /// diff --git a/Films/Films.DTOs/FilmDto.cs b/Films/Films.DTOs/FilmDto.cs index 5e8371e..a60c388 100644 --- a/Films/Films.DTOs/FilmDto.cs +++ b/Films/Films.DTOs/FilmDto.cs @@ -9,32 +9,26 @@ public class FilmDto /// Gets or sets the unique identifier for the film. /// public int Id { get; set; } - /// /// Gets or sets the name of the film. /// public string? Name { get; set; } - /// /// Gets or sets the rating of the film. /// public int Rating { get; set; } - /// /// Gets or sets the description of the film. /// public string? Description { get; set; } - /// /// Gets or sets the list of genres associated with the film. /// public List? Genres { get; set; } - /// /// Gets or sets the URL of the film's poster. /// public string? PosterURL { get; set; } - /// /// Gets or sets the timestamp when the film was created. /// diff --git a/Films/Films.DTOs/FilmUpdateDto.cs b/Films/Films.DTOs/FilmUpdateDto.cs index fb920d3..f9fd732 100644 --- a/Films/Films.DTOs/FilmUpdateDto.cs +++ b/Films/Films.DTOs/FilmUpdateDto.cs @@ -1,32 +1,30 @@ namespace Films.DTOs; +/// +/// Film UpdateDto +/// public class FilmUpdateDto { /// /// Gets or sets the name of the film. /// public string? Name { get; set; } - /// /// Gets or sets the rating of the film. /// public int Rating { get; set; } - /// /// Gets or sets the description of the film. /// public string? Description { get; set; } - /// /// Gets or sets the list of genres associated with the film. /// public List? Genres { get; set; } - /// /// Gets or sets the URL of the film's poster. /// public string? PosterURL { get; set; } - /// /// Gets or sets the timestamp when the film was created. /// diff --git a/Films/Films.DTOs/Films.DTOs.csproj b/Films/Films.DTOs/Films.DTOs.csproj index 1b1aba9..29e81bb 100644 --- a/Films/Films.DTOs/Films.DTOs.csproj +++ b/Films/Films.DTOs/Films.DTOs.csproj @@ -4,6 +4,8 @@ net6.0 enable enable + true + true diff --git a/Films/Films.DTOs/GenreDto.cs b/Films/Films.DTOs/GenreDto.cs index 67c986d..ffedfc3 100644 --- a/Films/Films.DTOs/GenreDto.cs +++ b/Films/Films.DTOs/GenreDto.cs @@ -9,7 +9,6 @@ public class GenreDto /// Gets or sets the unique identifier for the genre. /// public int Id { get; set; } - /// /// Gets or sets the name of the genre. /// diff --git a/Films/Films.Domain/Film.cs b/Films/Films.Domain/Film.cs index 786d6da..e152e67 100644 --- a/Films/Films.Domain/Film.cs +++ b/Films/Films.Domain/Film.cs @@ -11,32 +11,26 @@ public class Film : ICreatedAtTrackedEntity, IKeyEntity /// Gets or sets the unique identifier for the film. /// public int Id { get; set; } - /// /// Gets or sets the name of the film. /// public string? Name { get; set; } - /// /// Gets or sets the rating of the film. /// public int Rating { get; set; } - /// /// Gets or sets the description of the film. /// public string? Description { get; set; } - /// /// Gets or sets the list of genres associated with the film. /// public List? Genres { get; set; } - /// /// Gets or sets the URL of the film's poster. /// public string? PosterUrl { get; set; } - /// /// Gets or sets the timestamp when the film was created. /// diff --git a/Films/Films.Domain/Films.Domain.csproj b/Films/Films.Domain/Films.Domain.csproj index 0da779a..2dd9ec4 100644 --- a/Films/Films.Domain/Films.Domain.csproj +++ b/Films/Films.Domain/Films.Domain.csproj @@ -4,11 +4,13 @@ net6.0 enable enable + true + true - + diff --git a/Films/Films.Domain/Genre.cs b/Films/Films.Domain/Genre.cs index 67d229f..73ce1a5 100644 --- a/Films/Films.Domain/Genre.cs +++ b/Films/Films.Domain/Genre.cs @@ -9,7 +9,6 @@ public class Genre /// Gets or sets the unique identifier for the genre. /// public int Id { get; set; } - /// /// Gets or sets the name of the genre. /// diff --git a/Films/Films.Infrastructure/FilmDbContext.cs b/Films/Films.Infrastructure/FilmDbContext.cs index 7ace614..2bf5302 100644 --- a/Films/Films.Infrastructure/FilmDbContext.cs +++ b/Films/Films.Infrastructure/FilmDbContext.cs @@ -20,11 +20,11 @@ public FilmDbContext(DbContextOptions options) : base(options) /// /// Gets or sets the DbSet for films in the database. /// - private DbSet Films { get; set; } + private DbSet? Films { get; set; } /// /// Gets or sets the DbSet for genres in the database. /// - private DbSet Genres { get; set; } + private DbSet? Genres { get; set; } } } \ No newline at end of file diff --git a/Films/Films.Infrastructure/Films.Infrastructure.csproj b/Films/Films.Infrastructure/Films.Infrastructure.csproj index 1c75b8c..0d2cbc9 100644 --- a/Films/Films.Infrastructure/Films.Infrastructure.csproj +++ b/Films/Films.Infrastructure/Films.Infrastructure.csproj @@ -4,11 +4,13 @@ net6.0 enable enable + true + true - - + + From a30c53250c68e7d036f33444fb13f4b6de796fc0 Mon Sep 17 00:00:00 2001 From: Vova Zelenyi Date: Sun, 6 Aug 2023 22:09:08 +0200 Subject: [PATCH 03/23] Added paginated request --- Films/Films.API/Controllers/FilmController.cs | 18 +++- Films/Films.API/Program.cs | 7 +- Films/Films.Core/FilmService.cs | 35 +++++-- Films/Films.Domain/PaginatedResult.cs | 92 +++++++++++++++++++ 4 files changed, 136 insertions(+), 16 deletions(-) create mode 100644 Films/Films.Domain/PaginatedResult.cs diff --git a/Films/Films.API/Controllers/FilmController.cs b/Films/Films.API/Controllers/FilmController.cs index 2425df2..a147a68 100644 --- a/Films/Films.API/Controllers/FilmController.cs +++ b/Films/Films.API/Controllers/FilmController.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Films.Domain; +using Kirel.Repositories.Sorts; namespace Films.API.Controllers { @@ -50,15 +52,22 @@ public async Task>> SearchFilm(string filmName) } /// - /// Gets all films. + /// Retrieves a paginated list of films based on the specified parameters. /// - /// Returns a list of all films. + /// Page number of the paginated results. + /// Number of items per page. + /// Field by which the results should be ordered. + /// Sorting direction (ascending or descending). + /// Search term to filter the results. + /// Paginated result containing a list of FilmDto objects. [HttpGet("all")] - public async Task>> GetAllFilms() + public async Task>>> GetAllFilms( + int pageNumber = 0, int pageSize = 10, + string orderBy = "", SortDirection orderDirection = SortDirection.Asc, string search = "") { try { - var films = await _filmService.GetAllFilms(); + var films = await _filmService.GetAllFilmsPaginated(pageNumber, pageSize, orderBy, orderDirection, search); return Ok(films); } catch (Exception) @@ -67,6 +76,7 @@ public async Task>> GetAllFilms() } } + /// /// Deletes a film by ID. /// diff --git a/Films/Films.API/Program.cs b/Films/Films.API/Program.cs index b3c8ff8..6988b3b 100644 --- a/Films/Films.API/Program.cs +++ b/Films/Films.API/Program.cs @@ -1,20 +1,19 @@ - using System.Reflection; using Films.Core; using Films.Domain; using Films.Infrastructure; using Kirel.Repositories; using Kirel.Repositories.Interfaces; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); /*var authOptions = builder.Configuration.GetSection("AuthOptions").Get();*/ //taking connection string from appsettings.json -var connectionString = builder.Configuration.GetConnectionString("SqlConnection"); +var connectionString = builder.Configuration.GetConnectionString("PostgreConnection"); builder.Services.AddDbContext(options => - options.UseSqlServer(connectionString)); + options.UseNpgsql(connectionString)); // Add services to the container. builder.Services.AddScoped(); diff --git a/Films/Films.Core/FilmService.cs b/Films/Films.Core/FilmService.cs index 6c5a8e0..fcc7aa1 100644 --- a/Films/Films.Core/FilmService.cs +++ b/Films/Films.Core/FilmService.cs @@ -88,20 +88,39 @@ public async Task UpdateFilm(int filmId, FilmUpdateDto filmDto) } /// - /// Gets a list of all films. + /// Retrieves a paginated list of all films in the database. /// - /// A list of film DTOs representing all films in the database. - public async Task> GetAllFilms() + /// Page number of the paginated results. + /// Number of items per page. + /// Field by which the results should be ordered. + /// Sorting direction (ascending or descending). + /// Search term to filter the results. + /// Paginated result containing a list of FilmDto objects. + public async Task>> GetAllFilmsPaginated(int pageNumber = 0, int pageSize = 0, + string orderBy = "", SortDirection orderDirection = SortDirection.Asc, string search = "") { - // Get the total count of films in the database. - var allCount = await _filmRepository.Count(expression: null); + // Get the total count of films in the database based on the search criteria. + var totalCount = await _filmRepository.Count(search); - // Retrieve a paginated list of all films and map them to DTOs. - var films = await _filmRepository.GetList(null, "Created", SortDirection.Asc, 1, allCount); + // Generate pagination information. + var pagination = Pagination.Generate(pageNumber, pageSize, totalCount); + + // Retrieve a paginated list of films based on the pagination and sorting parameters. + var films = await _filmRepository.GetList(search, orderBy, orderDirection, pagination.CurrentPage, pagination.PageSize); + + // Map the retrieved films to DTOs. var filmsDto = _mapper.Map>(films); - return filmsDto; + + // Create and return the paginated result. + var result = new PaginatedResult> + { + Pagination = pagination, + Data = filmsDto + }; + return result; } + /// /// Deletes a film from the database. /// diff --git a/Films/Films.Domain/PaginatedResult.cs b/Films/Films.Domain/PaginatedResult.cs new file mode 100644 index 0000000..339ccca --- /dev/null +++ b/Films/Films.Domain/PaginatedResult.cs @@ -0,0 +1,92 @@ +namespace Films.Domain; +/// +/// Pagination class +/// +public class Pagination +{ + /// + /// Total number of pages + /// + public int TotalPages { get; set; } + /// + /// Total number of items + /// + public int TotalCount { get; set; } + /// + /// Current page number + /// + public int CurrentPage { get; set; } + /// + /// Size of the page + /// + public int PageSize { get; set; } + /// + /// Pagination constructor + /// + /// Total number of pages + /// Total number of items + /// Current page number + /// Size of the page + public Pagination(int totalPages = 0, int totalCount = 0, int currentPage = 1, int pageSize = 10) + { + TotalPages = totalPages; + TotalCount = totalCount; + CurrentPage = currentPage; + PageSize = pageSize; + } + /// + /// Generates pagination of entities + /// + /// Page number + /// Page size + /// Total count + /// Pagination + public static Pagination Generate(int pageNumber = 0, int pageSize = 0, int totalCount = 0) + { + var page = pageNumber > 0 ? pageNumber : 1; + var size = pageSize > 0 ? pageSize : 10; + var pagination = new Pagination() + { + CurrentPage = page, + PageSize = size, + TotalCount = totalCount, + TotalPages = (int) Math.Ceiling(totalCount / (double) size) + }; + return pagination; + } +} +/// +/// Class pagination results +/// +/// Entity type +public class PaginatedResult +{ + /// + /// Pagination entity field + /// + public Pagination Pagination { get; set; } + /// + /// A data field with a specific type + /// + public T? Data { get; set; } + /// + /// PaginatedResult constructor + /// + public PaginatedResult() + { + Pagination = new Pagination(); + } + /// + /// PaginatedResult constructor + /// + /// A data with a specific type + /// Current page number + /// Size of the page + /// Total number of pages + /// Total number of items + public PaginatedResult(T data, int currentPage, int pageSize, int totalPages, int totalCount) + { + Pagination = new Pagination(totalPages, totalCount, currentPage: currentPage, pageSize: pageSize); + Data = data; + } +} \ No newline at end of file From eee91a350f0c3d4db1e38d045a6cb34d50cb5bd4 Mon Sep 17 00:00:00 2001 From: Vova Zelenyi Date: Mon, 7 Aug 2023 22:32:46 +0200 Subject: [PATCH 04/23] change history --- Films/Films.API/Controllers/FilmController.cs | 15 ++- Films/Films.API/Films.API.csproj | 3 + Films/Films.API/Program.cs | 19 +++- Films/Films.Core/FilmMapper.cs | 37 ++++--- Films/Films.Core/FilmService.cs | 55 +++++++--- Films/Films.Core/Films.Core.csproj | 4 + Films/Films.Core/GenreMapper.cs | 2 + Films/Films.DTOs/FilmCreateDto.cs | 4 +- Films/Films.DTOs/FilmDto.cs | 2 +- Films/Films.DTOs/FilmUpdateDto.cs | 4 +- Films/Films.DTOs/GenreCreateDto.cs | 13 +++ Films/Films.DTOs/GenreUpdateDto.cs | 17 +++ Films/Films.Domain/Film.cs | 3 +- Films/Films.Domain/Genre.cs | 9 +- Films/Films.Infrastructure/FilmDbContext.cs | 19 +++- .../Films.Infrastructure.csproj | 1 + Films/Films.sln | 39 +++++++ .../Controllers/ExAuthorizedUserController.cs | 22 ++++ .../ExJwtAuthenticationController.cs | 19 ++++ .../Controllers/ExRegistrationController.cs | 18 +++ .../Controllers/ExRolesController.cs | 22 ++++ .../Controllers/ExUsersController.cs | 22 ++++ Films/Identity.API/Dockerfile | 20 ++++ Films/Identity.API/Identity.API.csproj | 28 +++++ Films/Identity.API/Program.cs | 103 ++++++++++++++++++ .../Properties/launchSettings.json | 31 ++++++ .../Identity.API/appsettings.Development.json | 8 ++ Films/Identity.API/appsettings.json | 13 +++ Films/Identity.Core/ClaimMapping.cs | 21 ++++ Films/Identity.Core/ExRoleMapping.cs | 21 ++++ Films/Identity.Core/ExUserMapping.cs | 24 ++++ Films/Identity.Core/Identity.Core.csproj | 20 ++++ .../Services/FilmAuthenticationService.cs | 15 +++ .../Services/FilmAuthorizedUserService.cs | 17 +++ .../Services/FilmJwtTokenService.cs | 15 +++ .../Services/FilmRegistrationService.cs | 16 +++ .../Identity.Core/Services/FilmRoleService.cs | 18 +++ .../Identity.Core/Services/FilmUserService.cs | 17 +++ Films/Identity.DTOs/FilmAuthorizedUserDto.cs | 9 ++ .../FilmAuthorizedUserUpdateDto.cs | 9 ++ Films/Identity.DTOs/FilmRoleCreateDto.cs | 9 ++ Films/Identity.DTOs/FilmRoleDto.cs | 9 ++ Films/Identity.DTOs/FilmRoleUpdateDto.cs | 9 ++ Films/Identity.DTOs/FilmUserCreateDto.cs | 9 ++ Films/Identity.DTOs/FilmUserDto.cs | 9 ++ .../Identity.DTOs/FilmUserRegistrationDto.cs | 9 ++ Films/Identity.DTOs/FilmUserUpdateDto.cs | 9 ++ Films/Identity.DTOs/Identity.DTOs.csproj | 13 +++ Films/Identity.Domain/FilmRole.cs | 9 ++ Films/Identity.Domain/FilmRoleClaim.cs | 9 ++ Films/Identity.Domain/FilmUser.cs | 9 ++ Films/Identity.Domain/FilmUserClaim.cs | 8 ++ Films/Identity.Domain/FilmUserLogin.cs | 9 ++ Films/Identity.Domain/FilmUserRole.cs | 8 ++ Films/Identity.Domain/FilmUserToken.cs | 9 ++ Films/Identity.Domain/Identity.Domain.csproj | 21 ++++ .../DbContext/IdentityContext.cs | 15 +++ .../DbContext/SeedDb.cs | 74 +++++++++++++ .../Identity.Infrastruture.csproj | 31 ++++++ 59 files changed, 984 insertions(+), 48 deletions(-) create mode 100644 Films/Films.DTOs/GenreCreateDto.cs create mode 100644 Films/Films.DTOs/GenreUpdateDto.cs create mode 100644 Films/Identity.API/Controllers/ExAuthorizedUserController.cs create mode 100644 Films/Identity.API/Controllers/ExJwtAuthenticationController.cs create mode 100644 Films/Identity.API/Controllers/ExRegistrationController.cs create mode 100644 Films/Identity.API/Controllers/ExRolesController.cs create mode 100644 Films/Identity.API/Controllers/ExUsersController.cs create mode 100644 Films/Identity.API/Dockerfile create mode 100644 Films/Identity.API/Identity.API.csproj create mode 100644 Films/Identity.API/Program.cs create mode 100644 Films/Identity.API/Properties/launchSettings.json create mode 100644 Films/Identity.API/appsettings.Development.json create mode 100644 Films/Identity.API/appsettings.json create mode 100644 Films/Identity.Core/ClaimMapping.cs create mode 100644 Films/Identity.Core/ExRoleMapping.cs create mode 100644 Films/Identity.Core/ExUserMapping.cs create mode 100644 Films/Identity.Core/Identity.Core.csproj create mode 100644 Films/Identity.Core/Services/FilmAuthenticationService.cs create mode 100644 Films/Identity.Core/Services/FilmAuthorizedUserService.cs create mode 100644 Films/Identity.Core/Services/FilmJwtTokenService.cs create mode 100644 Films/Identity.Core/Services/FilmRegistrationService.cs create mode 100644 Films/Identity.Core/Services/FilmRoleService.cs create mode 100644 Films/Identity.Core/Services/FilmUserService.cs create mode 100644 Films/Identity.DTOs/FilmAuthorizedUserDto.cs create mode 100644 Films/Identity.DTOs/FilmAuthorizedUserUpdateDto.cs create mode 100644 Films/Identity.DTOs/FilmRoleCreateDto.cs create mode 100644 Films/Identity.DTOs/FilmRoleDto.cs create mode 100644 Films/Identity.DTOs/FilmRoleUpdateDto.cs create mode 100644 Films/Identity.DTOs/FilmUserCreateDto.cs create mode 100644 Films/Identity.DTOs/FilmUserDto.cs create mode 100644 Films/Identity.DTOs/FilmUserRegistrationDto.cs create mode 100644 Films/Identity.DTOs/FilmUserUpdateDto.cs create mode 100644 Films/Identity.DTOs/Identity.DTOs.csproj create mode 100644 Films/Identity.Domain/FilmRole.cs create mode 100644 Films/Identity.Domain/FilmRoleClaim.cs create mode 100644 Films/Identity.Domain/FilmUser.cs create mode 100644 Films/Identity.Domain/FilmUserClaim.cs create mode 100644 Films/Identity.Domain/FilmUserLogin.cs create mode 100644 Films/Identity.Domain/FilmUserRole.cs create mode 100644 Films/Identity.Domain/FilmUserToken.cs create mode 100644 Films/Identity.Domain/Identity.Domain.csproj create mode 100644 Films/Identity.Infrastruture/DbContext/IdentityContext.cs create mode 100644 Films/Identity.Infrastruture/DbContext/SeedDb.cs create mode 100644 Films/Identity.Infrastruture/Identity.Infrastruture.csproj diff --git a/Films/Films.API/Controllers/FilmController.cs b/Films/Films.API/Controllers/FilmController.cs index a147a68..d3d6733 100644 --- a/Films/Films.API/Controllers/FilmController.cs +++ b/Films/Films.API/Controllers/FilmController.cs @@ -106,17 +106,20 @@ public async Task DeleteFilm(int filmId) /// The DTO containing the film information to create. /// Returns the ID of the created film. [HttpPost("create")] - public async Task> CreateFilm(FilmCreateDto filmDto) + public async Task> CreateFilm([FromBody] FilmCreateDto filmDto) { - try + if (!ModelState.IsValid) { - var createdFilmId = await _filmService.CreateFilm(filmDto); - return Ok(createdFilmId); + return BadRequest(ModelState); } - catch (Exception) + + int createdFilmId = await _filmService.CreateFilm(filmDto); + return Ok(createdFilmId); + + /*catch (Exception) { return StatusCode(500, "An error occurred while processing your request."); - } + }*/ } /// diff --git a/Films/Films.API/Films.API.csproj b/Films/Films.API/Films.API.csproj index c468e9d..cbf6b2b 100644 --- a/Films/Films.API/Films.API.csproj +++ b/Films/Films.API/Films.API.csproj @@ -12,6 +12,9 @@ + + + diff --git a/Films/Films.API/Program.cs b/Films/Films.API/Program.cs index 6988b3b..49fb2af 100644 --- a/Films/Films.API/Program.cs +++ b/Films/Films.API/Program.cs @@ -1,7 +1,10 @@ using System.Reflection; using Films.Core; using Films.Domain; +using Films.DTOs; using Films.Infrastructure; +using FluentValidation; +using FluentValidation.AspNetCore; using Kirel.Repositories; using Kirel.Repositories.Interfaces; using Microsoft.EntityFrameworkCore; @@ -11,17 +14,27 @@ /*var authOptions = builder.Configuration.GetSection("AuthOptions").Get();*/ //taking connection string from appsettings.json -var connectionString = builder.Configuration.GetConnectionString("PostgreConnection"); +var connectionString = builder.Configuration.GetConnectionString("SqlConnection"); builder.Services.AddDbContext(options => - options.UseNpgsql(connectionString)); + options.UseSqlServer(connectionString)); // Add services to the container. builder.Services.AddScoped(); builder.Services.AddScoped, KirelGenericEntityFrameworkRepository>(); +builder.Services.AddScoped, KirelGenericEntityFrameworkRepository>(); //Add AutoMapper +builder.Services.AddAutoMapper(cfg => +{ + cfg.CreateMap() + .ForMember(dest => dest.Genres, opt => opt.MapFrom(src => src.GenreNames!.Select(g => new Genre { Name = g }))); +}, Assembly.GetExecutingAssembly()); + +/*builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());*/ +builder.Services.AddFluentValidationAutoValidation(); +builder.Services.AddFluentValidationClientsideAdapters(); +builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); -builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly()); builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); diff --git a/Films/Films.Core/FilmMapper.cs b/Films/Films.Core/FilmMapper.cs index 637e9ab..bbd6ead 100644 --- a/Films/Films.Core/FilmMapper.cs +++ b/Films/Films.Core/FilmMapper.cs @@ -1,20 +1,25 @@ -using Films.Domain; -using Films.DTOs; -using AutoMapper; -namespace Films.Core; + using Films.Domain; + using Films.DTOs; + using AutoMapper; + namespace Films.Core; -/// -/// Mapping Film for FilmDTO -/// -public class FilmMapper : Profile -{ /// - /// Films constructor + /// Mapping Film for FilmDTO /// - public FilmMapper() + public class FilmMapper : Profile { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } -} \ No newline at end of file + /// + /// Films constructor + /// + public FilmMapper() + { + CreateMap().ReverseMap(); + CreateMap() + .ForMember(dest => dest.Genres, opt => opt.Ignore()); // Игнорируем маппинг списка жанров здесь + + CreateMap() + .ForMember(dest => dest.GenreNames, opt => opt.MapFrom(src => src.Genres.Select(g => g.Name))); + + CreateMap().ReverseMap(); + } + } \ No newline at end of file diff --git a/Films/Films.Core/FilmService.cs b/Films/Films.Core/FilmService.cs index fcc7aa1..936e0fe 100644 --- a/Films/Films.Core/FilmService.cs +++ b/Films/Films.Core/FilmService.cs @@ -3,6 +3,7 @@ using Films.DTOs; using Kirel.Repositories.Interfaces; using Kirel.Repositories.Sorts; +using Microsoft.Extensions.DependencyInjection; namespace Films.Core { @@ -13,16 +14,19 @@ public class FilmService { private readonly IKirelGenericEntityRepository _filmRepository; private readonly IMapper _mapper; + private readonly IKirelGenericEntityRepository _genreRepository; /// /// Initializes a new instance of the class. /// /// The repository for accessing film data. /// AutoMapper instance - public FilmService(IKirelGenericEntityRepository filmRepository, IMapper mapper) + /// + public FilmService(IKirelGenericEntityRepository filmRepository, IMapper mapper, IKirelGenericEntityRepository genreRepository) { - this._filmRepository = filmRepository; + _filmRepository = filmRepository; _mapper = mapper; + _genreRepository = genreRepository; } /// /// Searches for films in the database by name. @@ -32,39 +36,60 @@ public FilmService(IKirelGenericEntityRepository filmRepository, IMap public async Task> SearchFilm(string filmName) { // Search for films in the database based on the provided film name. - var existingFilm = await _filmRepository.GetList(m => m.Name != null && m.Name.Contains(filmName)); + var existingFilms = await _filmRepository.GetList(m => m.Name != null && m.Name.Contains(filmName)); - var enumerable = existingFilm.ToList(); - if (existingFilm == null || !enumerable.Any()) + if (existingFilms == null || !existingFilms.Any()) { throw new FilmNotFoundException($"Film with the title '{filmName}' was not found in the database."); } // Map the retrieved films to DTOs for presentation. - var filmsList = enumerable.Select(film => new FilmDto + var filmsList = existingFilms.Select(film => new FilmDto { Id = film.Id, Name = film.Name, Rating = film.Rating, Description = film.Description, PosterURL = film.PosterUrl, - Genres = film.Genres + Genres = _mapper.Map>(film.Genres) }).ToList(); return filmsList; } + /// /// Creates a new film. /// - /// The DTO containing the film information to create. + /// The DTO containing the film information to create. /// The ID of the created film. - public async Task CreateFilm(FilmCreateDto filmDto) - { - // Map the DTO to an entity and insert it into the repository. - var film = _mapper.Map(filmDto); - var createdFilm = await _filmRepository.Insert(film); - return createdFilm.Id; - } + public async Task CreateFilm(FilmCreateDto filmCreateDto) + { + Film film = _mapper.Map(filmCreateDto); + + // Добавьте жанры в фильм, если они указаны в filmCreateDto.GenreNames + if (filmCreateDto.GenreNames != null) + { + foreach (string genreName in filmCreateDto.GenreNames) + { + var genre = await _genreRepository.GetList(m => m.Name != null && m.Name.Contains(genreName)); + if (genre.Any()) + { + film.Genres.Add(genre.First()); // Выберите первый подходящий жанр + } + else + { + // Жанр с указанным названием не найден, создание нового жанра + var newGenre = new Genre { Name = genreName }; + film.Genres.Add(newGenre); + } + } + } + + var createdFilm = await _filmRepository.Insert(film); + return createdFilm.Id; + } + + /// /// Updates an existing film. diff --git a/Films/Films.Core/Films.Core.csproj b/Films/Films.Core/Films.Core.csproj index fa4bd3a..8822f85 100644 --- a/Films/Films.Core/Films.Core.csproj +++ b/Films/Films.Core/Films.Core.csproj @@ -18,4 +18,8 @@ + + + + diff --git a/Films/Films.Core/GenreMapper.cs b/Films/Films.Core/GenreMapper.cs index a844b30..c739c02 100644 --- a/Films/Films.Core/GenreMapper.cs +++ b/Films/Films.Core/GenreMapper.cs @@ -14,5 +14,7 @@ public class GenreMapper : Profile public GenreMapper() { CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); } } \ No newline at end of file diff --git a/Films/Films.DTOs/FilmCreateDto.cs b/Films/Films.DTOs/FilmCreateDto.cs index 2089a41..40346ea 100644 --- a/Films/Films.DTOs/FilmCreateDto.cs +++ b/Films/Films.DTOs/FilmCreateDto.cs @@ -20,11 +20,11 @@ public class FilmCreateDto /// /// Gets or sets the list of genres associated with the film. /// - public List? Genres { get; set; } + public List? GenreNames { get; set; } /// /// Gets or sets the URL of the film's poster. /// - public string? PosterURL { get; set; } + public string? PosterUrl { get; set; } /// /// Gets or sets the timestamp when the film was created. /// diff --git a/Films/Films.DTOs/FilmDto.cs b/Films/Films.DTOs/FilmDto.cs index a60c388..2465a0a 100644 --- a/Films/Films.DTOs/FilmDto.cs +++ b/Films/Films.DTOs/FilmDto.cs @@ -24,7 +24,7 @@ public class FilmDto /// /// Gets or sets the list of genres associated with the film. /// - public List? Genres { get; set; } + public List? Genres { get; set; } /// /// Gets or sets the URL of the film's poster. /// diff --git a/Films/Films.DTOs/FilmUpdateDto.cs b/Films/Films.DTOs/FilmUpdateDto.cs index f9fd732..bcfc8ad 100644 --- a/Films/Films.DTOs/FilmUpdateDto.cs +++ b/Films/Films.DTOs/FilmUpdateDto.cs @@ -20,11 +20,11 @@ public class FilmUpdateDto /// /// Gets or sets the list of genres associated with the film. /// - public List? Genres { get; set; } + public List? Genres { get; set; } /// /// Gets or sets the URL of the film's poster. /// - public string? PosterURL { get; set; } + public string? PosterUrl { get; set; } /// /// Gets or sets the timestamp when the film was created. /// diff --git a/Films/Films.DTOs/GenreCreateDto.cs b/Films/Films.DTOs/GenreCreateDto.cs new file mode 100644 index 0000000..befc8d7 --- /dev/null +++ b/Films/Films.DTOs/GenreCreateDto.cs @@ -0,0 +1,13 @@ +namespace Films.DTOs; + +/// +/// Data transfer object (DTO) representing a film genre. +/// +public class GenreCreateDto +{ + + /// + /// Gets or sets the name of the genre. + /// + public string? Name { get; set; } +} \ No newline at end of file diff --git a/Films/Films.DTOs/GenreUpdateDto.cs b/Films/Films.DTOs/GenreUpdateDto.cs new file mode 100644 index 0000000..238339c --- /dev/null +++ b/Films/Films.DTOs/GenreUpdateDto.cs @@ -0,0 +1,17 @@ +namespace Films.DTOs; + +/// +/// Data transfer object (DTO) representing a film genre. +/// +public class GenreUpdateDto +{ + + /// + /// Gets or sets the unique identifier for the genre. + /// + public int Id { get; set; } + /// + /// Gets or sets the name of the genre. + /// + public string? Name { get; set; } +} \ No newline at end of file diff --git a/Films/Films.Domain/Film.cs b/Films/Films.Domain/Film.cs index e152e67..e7352ba 100644 --- a/Films/Films.Domain/Film.cs +++ b/Films/Films.Domain/Film.cs @@ -26,7 +26,8 @@ public class Film : ICreatedAtTrackedEntity, IKeyEntity /// /// Gets or sets the list of genres associated with the film. /// - public List? Genres { get; set; } + public List Genres { get; set; } = new List(); + /// /// Gets or sets the URL of the film's poster. /// diff --git a/Films/Films.Domain/Genre.cs b/Films/Films.Domain/Genre.cs index 73ce1a5..8907a39 100644 --- a/Films/Films.Domain/Genre.cs +++ b/Films/Films.Domain/Genre.cs @@ -1,9 +1,11 @@ +using Kirel.Repositories.Interfaces; + namespace Films.Domain { /// /// Represents a genre associated with films. /// - public class Genre + public class Genre : IKeyEntity, ICreatedAtTrackedEntity { /// /// Gets or sets the unique identifier for the genre. @@ -13,5 +15,10 @@ public class Genre /// Gets or sets the name of the genre. /// public string? Name { get; set; } + + /// + /// + /// + public DateTime Created { get; set; } } } \ No newline at end of file diff --git a/Films/Films.Infrastructure/FilmDbContext.cs b/Films/Films.Infrastructure/FilmDbContext.cs index 2bf5302..c624d54 100644 --- a/Films/Films.Infrastructure/FilmDbContext.cs +++ b/Films/Films.Infrastructure/FilmDbContext.cs @@ -16,7 +16,6 @@ public FilmDbContext(DbContextOptions options) : base(options) { } - /// /// Gets or sets the DbSet for films in the database. /// @@ -26,5 +25,23 @@ public FilmDbContext(DbContextOptions options) : base(options) /// Gets or sets the DbSet for genres in the database. /// private DbSet? Genres { get; set; } + /// + /// + /// + /// + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + modelBuilder.Entity() + .HasMany(f => f.Genres) + .WithMany() + .UsingEntity(j => + { + + j.Property("Id"); + }); + } + + } } \ No newline at end of file diff --git a/Films/Films.Infrastructure/Films.Infrastructure.csproj b/Films/Films.Infrastructure/Films.Infrastructure.csproj index 0d2cbc9..d3a52f2 100644 --- a/Films/Films.Infrastructure/Films.Infrastructure.csproj +++ b/Films/Films.Infrastructure/Films.Infrastructure.csproj @@ -9,6 +9,7 @@ + diff --git a/Films/Films.sln b/Films/Films.sln index d8cba02..874ae61 100644 --- a/Films/Films.sln +++ b/Films/Films.sln @@ -10,6 +10,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Films.Domain", "Films.Domai EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Films.DTOs", "Films.DTOs\Films.DTOs.csproj", "{5B5FACA7-DABE-472C-8813-85A8C18A6A4D}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Identity", "Identity", "{1777E05F-05BC-439B-9848-8494967590E2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Identity.API", "Identity.API\Identity.API.csproj", "{AE26ACCE-6A8C-46E8-8A21-6D0AB454C465}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Identity.Core", "Identity.Core\Identity.Core.csproj", "{7D2ECC79-7725-42F2-856D-939A53230C21}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Identity.Domain", "Identity.Domain\Identity.Domain.csproj", "{56826CEA-DBA9-42AD-ABC1-9D4D0F22E84C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Identity.DTOs", "Identity.DTOs\Identity.DTOs.csproj", "{621C6524-6993-4D9C-B084-A2BFACA0A366}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Identity.Infrastruture", "Identity.Infrastruture\Identity.Infrastruture.csproj", "{B6D90487-5384-48F9-A278-EEE49392B9F2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -36,5 +48,32 @@ Global {5B5FACA7-DABE-472C-8813-85A8C18A6A4D}.Debug|Any CPU.Build.0 = Debug|Any CPU {5B5FACA7-DABE-472C-8813-85A8C18A6A4D}.Release|Any CPU.ActiveCfg = Release|Any CPU {5B5FACA7-DABE-472C-8813-85A8C18A6A4D}.Release|Any CPU.Build.0 = Release|Any CPU + {AE26ACCE-6A8C-46E8-8A21-6D0AB454C465}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE26ACCE-6A8C-46E8-8A21-6D0AB454C465}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE26ACCE-6A8C-46E8-8A21-6D0AB454C465}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE26ACCE-6A8C-46E8-8A21-6D0AB454C465}.Release|Any CPU.Build.0 = Release|Any CPU + {7D2ECC79-7725-42F2-856D-939A53230C21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D2ECC79-7725-42F2-856D-939A53230C21}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D2ECC79-7725-42F2-856D-939A53230C21}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D2ECC79-7725-42F2-856D-939A53230C21}.Release|Any CPU.Build.0 = Release|Any CPU + {56826CEA-DBA9-42AD-ABC1-9D4D0F22E84C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {56826CEA-DBA9-42AD-ABC1-9D4D0F22E84C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {56826CEA-DBA9-42AD-ABC1-9D4D0F22E84C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {56826CEA-DBA9-42AD-ABC1-9D4D0F22E84C}.Release|Any CPU.Build.0 = Release|Any CPU + {621C6524-6993-4D9C-B084-A2BFACA0A366}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {621C6524-6993-4D9C-B084-A2BFACA0A366}.Debug|Any CPU.Build.0 = Debug|Any CPU + {621C6524-6993-4D9C-B084-A2BFACA0A366}.Release|Any CPU.ActiveCfg = Release|Any CPU + {621C6524-6993-4D9C-B084-A2BFACA0A366}.Release|Any CPU.Build.0 = Release|Any CPU + {B6D90487-5384-48F9-A278-EEE49392B9F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B6D90487-5384-48F9-A278-EEE49392B9F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B6D90487-5384-48F9-A278-EEE49392B9F2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B6D90487-5384-48F9-A278-EEE49392B9F2}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {AE26ACCE-6A8C-46E8-8A21-6D0AB454C465} = {1777E05F-05BC-439B-9848-8494967590E2} + {7D2ECC79-7725-42F2-856D-939A53230C21} = {1777E05F-05BC-439B-9848-8494967590E2} + {56826CEA-DBA9-42AD-ABC1-9D4D0F22E84C} = {1777E05F-05BC-439B-9848-8494967590E2} + {621C6524-6993-4D9C-B084-A2BFACA0A366} = {1777E05F-05BC-439B-9848-8494967590E2} + {B6D90487-5384-48F9-A278-EEE49392B9F2} = {1777E05F-05BC-439B-9848-8494967590E2} EndGlobalSection EndGlobal diff --git a/Films/Identity.API/Controllers/ExAuthorizedUserController.cs b/Films/Identity.API/Controllers/ExAuthorizedUserController.cs new file mode 100644 index 0000000..3674692 --- /dev/null +++ b/Films/Identity.API/Controllers/ExAuthorizedUserController.cs @@ -0,0 +1,22 @@ +using AutoMapper; +using Identity.Core.Services; +using Identity.Domain; +using Identity.DTOs; +using Kirel.Identity.Controllers; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Identity.API.Controllers; + +/// +[ApiController] +[Authorize] +[Route("authorized/user")] +public class ExAuthorizedUserController : KirelAuthorizedUserController +{ + /// + public ExAuthorizedUserController(FilmAuthorizedUserService service, IMapper mapper) : base(service, mapper) + { + } +} \ No newline at end of file diff --git a/Films/Identity.API/Controllers/ExJwtAuthenticationController.cs b/Films/Identity.API/Controllers/ExJwtAuthenticationController.cs new file mode 100644 index 0000000..c06089c --- /dev/null +++ b/Films/Identity.API/Controllers/ExJwtAuthenticationController.cs @@ -0,0 +1,19 @@ +using Identity.Core.Services; +using Identity.Domain; +using Identity.DTOs; +using Kirel.Identity.Jwt.Controllers; +using Microsoft.AspNetCore.Mvc; + +namespace Identity.API.Controllers; + +/// +[ApiController] +[Route("authentication/jwt")] +public class ExJwtAuthenticationController : KirelJwtAuthenticationController +{ + /// + public ExJwtAuthenticationController(FilmAuthenticationService authService, FilmJwtTokenService tokenService, FilmAuthorizedUserService authorizedUserservice) : base(authService, tokenService, authorizedUserservice) + { + } +} \ No newline at end of file diff --git a/Films/Identity.API/Controllers/ExRegistrationController.cs b/Films/Identity.API/Controllers/ExRegistrationController.cs new file mode 100644 index 0000000..99d9047 --- /dev/null +++ b/Films/Identity.API/Controllers/ExRegistrationController.cs @@ -0,0 +1,18 @@ +using Identity.Core.Services; +using Identity.Domain; +using Identity.DTOs; +using Kirel.Identity.Controllers; +using Microsoft.AspNetCore.Mvc; + +namespace Identity.API.Controllers; + +/// +[ApiController] +[Route("registration")] +public class ExRegistrationController : KirelRegistrationController +{ + /// + public ExRegistrationController(FilmRegistrationService service) : base(service) + { + } +} \ No newline at end of file diff --git a/Films/Identity.API/Controllers/ExRolesController.cs b/Films/Identity.API/Controllers/ExRolesController.cs new file mode 100644 index 0000000..3f1ff4a --- /dev/null +++ b/Films/Identity.API/Controllers/ExRolesController.cs @@ -0,0 +1,22 @@ +using Identity.Core.Services; +using Identity.Domain; +using Identity.DTOs; +using Kirel.Identity.Controllers; +using Kirel.Identity.DTOs; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Identity.API.Controllers; + +/// +[ApiController] +[Authorize] +[Route("roles")] +public class ExRolesController : KirelRolesController +{ + /// + public ExRolesController(FilmRoleService service) : base(service) + { + } +} \ No newline at end of file diff --git a/Films/Identity.API/Controllers/ExUsersController.cs b/Films/Identity.API/Controllers/ExUsersController.cs new file mode 100644 index 0000000..acfb4fa --- /dev/null +++ b/Films/Identity.API/Controllers/ExUsersController.cs @@ -0,0 +1,22 @@ +using Identity.Core.Services; +using Identity.Domain; +using Identity.DTOs; +using Kirel.Identity.Controllers; +using Kirel.Identity.DTOs; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Identity.API.Controllers; + +/// +[ApiController] +[Authorize] +[Route("users")] +public class ExUsersController : KirelUsersController +{ + /// + public ExUsersController(FilmUserService service) : base(service) + { + } +} \ No newline at end of file diff --git a/Films/Identity.API/Dockerfile b/Films/Identity.API/Dockerfile new file mode 100644 index 0000000..20c6782 --- /dev/null +++ b/Films/Identity.API/Dockerfile @@ -0,0 +1,20 @@ +FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 + +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +WORKDIR /src +COPY ["Identity.API/Identity.API.csproj", "Identity.API/"] +RUN dotnet restore "Identity.API/Identity.API.csproj" +COPY . . +WORKDIR "/src/Identity.API" +RUN dotnet build "Identity.API.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "Identity.API.csproj" -c Release -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Identity.API.dll"] diff --git a/Films/Identity.API/Identity.API.csproj b/Films/Identity.API/Identity.API.csproj new file mode 100644 index 0000000..3bd58b6 --- /dev/null +++ b/Films/Identity.API/Identity.API.csproj @@ -0,0 +1,28 @@ + + + + net6.0 + enable + enable + Linux + + + + + + + + + + + + .dockerignore + + + + + + + + + diff --git a/Films/Identity.API/Program.cs b/Films/Identity.API/Program.cs new file mode 100644 index 0000000..2f0d2b6 --- /dev/null +++ b/Films/Identity.API/Program.cs @@ -0,0 +1,103 @@ +using System.Reflection; +using FluentValidation; +using FluentValidation.AspNetCore; +using Identity.Core.Services; +using Identity.Domain; +using Identity.Infrastruture.DbContext; +using Kirel.Identity.Core.Models; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +var connectionString = builder.Configuration.GetConnectionString("SqlConnection"); +builder.Services.AddDbContext(options => + options.UseSqlServer(connectionString)); +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.AddIdentity().AddEntityFrameworkStores(); +builder.Services.Configure(options => +{ + // Password settings. + options.Password.RequireDigit = true; + options.Password.RequireLowercase = true; + options.Password.RequireUppercase = true; + options.Password.RequireNonAlphanumeric = true; + options.Password.RequiredLength = 8; + // User settings. + options.User.RequireUniqueEmail = true; +}); + +// Add dto validators. Configured in Validators. +builder.Services.AddFluentValidationAutoValidation(); +builder.Services.AddFluentValidationClientsideAdapters(); +builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); + +//Add AutoMapper. Class <--> Dto mappings. Configured in Mappings. +builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly()); + +// Add kirel based identity management services +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + +// add kirel based jwt tokens generation service +builder.Services.AddScoped(); + +// Add kirel authentication/authorization options +var authOptions = new KirelAuthOptions() +{ + AccessLifetime = 5, + Audience = "ExampleClient", + Issuer = "ExampleServer", + Key = "SomeSuperSecretKey123", + RefreshLifetime = 3600 +}; +builder.Services.AddSingleton(authOptions); + +// Add ASP.NET authentication configuration +builder.Services.AddAuthentication(option => + { + // Fixing 404 error when adding an attribute Authorize to controller + option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }) + .AddJwtBearer(options => + { + options.RequireHttpsMetadata = false; + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidIssuer = authOptions.Issuer, + ValidateAudience = true, + ValidAudience = authOptions.Audience, + ValidateLifetime = true, + IssuerSigningKey = authOptions.GetSymmetricSecurityKey(authOptions.Key), + ValidateIssuerSigningKey = true, + }; + }); + + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/Films/Identity.API/Properties/launchSettings.json b/Films/Identity.API/Properties/launchSettings.json new file mode 100644 index 0000000..78d82bf --- /dev/null +++ b/Films/Identity.API/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:10507", + "sslPort": 44382 + } + }, + "profiles": { + "Identity.API": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7012;http://localhost:5050", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Films/Identity.API/appsettings.Development.json b/Films/Identity.API/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/Films/Identity.API/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Films/Identity.API/appsettings.json b/Films/Identity.API/appsettings.json new file mode 100644 index 0000000..9886e45 --- /dev/null +++ b/Films/Identity.API/appsettings.json @@ -0,0 +1,13 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + "PostgreConnection": "Host=localhost;Port=5432;Database=Films;Username=lsodis;Password=6742", + "SqlConnection": "Server=localhost;Database=Identity;Trusted_Connection=True;Encrypt=False;" + }, + "AllowedHosts": "*" +} diff --git a/Films/Identity.Core/ClaimMapping.cs b/Films/Identity.Core/ClaimMapping.cs new file mode 100644 index 0000000..ac71c9b --- /dev/null +++ b/Films/Identity.Core/ClaimMapping.cs @@ -0,0 +1,21 @@ +using System.Security.Claims; +using AutoMapper; +using Kirel.Identity.DTOs; + +namespace Identity.Core; + +/// +/// Mapping profile for claim +/// +public class ClaimMappings : Profile +{ + /// + /// ClaimMappings constructor + /// + public ClaimMappings() + { + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + } +} \ No newline at end of file diff --git a/Films/Identity.Core/ExRoleMapping.cs b/Films/Identity.Core/ExRoleMapping.cs new file mode 100644 index 0000000..32a5fd7 --- /dev/null +++ b/Films/Identity.Core/ExRoleMapping.cs @@ -0,0 +1,21 @@ +using AutoMapper; +using Identity.Domain; +using Identity.DTOs; + +namespace Identity.Core; + +/// +/// Mapping profile for role +/// +public class ExRoleMapping : Profile +{ + /// + /// Role mapping constructor + /// + public ExRoleMapping() + { + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + } +} \ No newline at end of file diff --git a/Films/Identity.Core/ExUserMapping.cs b/Films/Identity.Core/ExUserMapping.cs new file mode 100644 index 0000000..c3f3593 --- /dev/null +++ b/Films/Identity.Core/ExUserMapping.cs @@ -0,0 +1,24 @@ +using AutoMapper; +using Identity.Domain; +using Identity.DTOs; + +namespace Identity.Core; + +/// +/// Mapping profile for user +/// +public class ExUserMapping : Profile +{ + /// + /// UserMapping constructor + /// + public ExUserMapping() + { + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + } +} \ No newline at end of file diff --git a/Films/Identity.Core/Identity.Core.csproj b/Films/Identity.Core/Identity.Core.csproj new file mode 100644 index 0000000..fbaf991 --- /dev/null +++ b/Films/Identity.Core/Identity.Core.csproj @@ -0,0 +1,20 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + + diff --git a/Films/Identity.Core/Services/FilmAuthenticationService.cs b/Films/Identity.Core/Services/FilmAuthenticationService.cs new file mode 100644 index 0000000..ca54332 --- /dev/null +++ b/Films/Identity.Core/Services/FilmAuthenticationService.cs @@ -0,0 +1,15 @@ + +using Identity.Domain; +using Kirel.Identity.Core.Services; +using Microsoft.AspNetCore.Identity; + +namespace Identity.Core.Services; + +/// +public class FilmAuthenticationService : KirelAuthenticationService +{ + /// + public FilmAuthenticationService(UserManager userManager) : base(userManager) + { + } +} \ No newline at end of file diff --git a/Films/Identity.Core/Services/FilmAuthorizedUserService.cs b/Films/Identity.Core/Services/FilmAuthorizedUserService.cs new file mode 100644 index 0000000..53e4ad6 --- /dev/null +++ b/Films/Identity.Core/Services/FilmAuthorizedUserService.cs @@ -0,0 +1,17 @@ +using AutoMapper; +using Identity.Domain; +using Identity.DTOs; +using Kirel.Identity.Core.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; + +namespace Identity.Core.Services; + +/// +public class FilmAuthorizedUserService : KirelAuthorizedUserService +{ + /// + public FilmAuthorizedUserService(IHttpContextAccessor httpContextAccessor, UserManager userManager, IMapper mapper) : base(httpContextAccessor, userManager, mapper) + { + } +} \ No newline at end of file diff --git a/Films/Identity.Core/Services/FilmJwtTokenService.cs b/Films/Identity.Core/Services/FilmJwtTokenService.cs new file mode 100644 index 0000000..acb27d7 --- /dev/null +++ b/Films/Identity.Core/Services/FilmJwtTokenService.cs @@ -0,0 +1,15 @@ +using Identity.Domain; +using Kirel.Identity.Core.Models; +using Kirel.Identity.Jwt.Core.Services; +using Microsoft.AspNetCore.Identity; + +namespace Identity.Core.Services; + +/// +public class FilmJwtTokenService : KirelJwtTokenService +{ + /// + public FilmJwtTokenService(UserManager userManager, RoleManager roleManager, KirelAuthOptions authOptions) : base(userManager, roleManager, authOptions) + { + } +} \ No newline at end of file diff --git a/Films/Identity.Core/Services/FilmRegistrationService.cs b/Films/Identity.Core/Services/FilmRegistrationService.cs new file mode 100644 index 0000000..cad95ad --- /dev/null +++ b/Films/Identity.Core/Services/FilmRegistrationService.cs @@ -0,0 +1,16 @@ +using AutoMapper; +using Identity.Domain; +using Identity.DTOs; +using Kirel.Identity.Core.Services; +using Microsoft.AspNetCore.Identity; + +namespace Identity.Core.Services; + +/// +public class FilmRegistrationService : KirelRegistrationService +{ + /// + public FilmRegistrationService(UserManager userManager, IMapper mapper) : base(userManager, mapper) + { + } +} \ No newline at end of file diff --git a/Films/Identity.Core/Services/FilmRoleService.cs b/Films/Identity.Core/Services/FilmRoleService.cs new file mode 100644 index 0000000..573eaae --- /dev/null +++ b/Films/Identity.Core/Services/FilmRoleService.cs @@ -0,0 +1,18 @@ +using AutoMapper; +using Identity.Domain; +using Identity.DTOs; +using Kirel.Identity.Core.Services; +using Kirel.Identity.DTOs; +using Microsoft.AspNetCore.Identity; + +namespace Identity.Core.Services; + +/// +public class FilmRoleService : KirelRoleService +{ + /// + public FilmRoleService(RoleManager roleManager, IMapper mapper) : base(roleManager, mapper) + { + } +} \ No newline at end of file diff --git a/Films/Identity.Core/Services/FilmUserService.cs b/Films/Identity.Core/Services/FilmUserService.cs new file mode 100644 index 0000000..1b7e1f5 --- /dev/null +++ b/Films/Identity.Core/Services/FilmUserService.cs @@ -0,0 +1,17 @@ +using AutoMapper; +using Identity.Domain; +using Identity.DTOs; +using Kirel.Identity.Core.Services; +using Kirel.Identity.DTOs; +using Microsoft.AspNetCore.Identity; + +namespace Identity.Core.Services; +/// +public class FilmUserService : KirelUserService +{ + /// + public FilmUserService(UserManager userManager, RoleManager roleManager, IMapper mapper) : base(userManager, roleManager, mapper) + { + } +} \ No newline at end of file diff --git a/Films/Identity.DTOs/FilmAuthorizedUserDto.cs b/Films/Identity.DTOs/FilmAuthorizedUserDto.cs new file mode 100644 index 0000000..d1c18a2 --- /dev/null +++ b/Films/Identity.DTOs/FilmAuthorizedUserDto.cs @@ -0,0 +1,9 @@ +using Kirel.Identity.DTOs; + +namespace Identity.DTOs; + +/// +public class FilmAuthorizedUserDto : KirelAuthorizedUserDto +{ + +} \ No newline at end of file diff --git a/Films/Identity.DTOs/FilmAuthorizedUserUpdateDto.cs b/Films/Identity.DTOs/FilmAuthorizedUserUpdateDto.cs new file mode 100644 index 0000000..5036d79 --- /dev/null +++ b/Films/Identity.DTOs/FilmAuthorizedUserUpdateDto.cs @@ -0,0 +1,9 @@ +using Kirel.Identity.DTOs; + +namespace Identity.DTOs; + +/// +public class FilmAuthorizedUserUpdateDto : KirelAuthorizedUserUpdateDto +{ + +} \ No newline at end of file diff --git a/Films/Identity.DTOs/FilmRoleCreateDto.cs b/Films/Identity.DTOs/FilmRoleCreateDto.cs new file mode 100644 index 0000000..d699169 --- /dev/null +++ b/Films/Identity.DTOs/FilmRoleCreateDto.cs @@ -0,0 +1,9 @@ +using Kirel.Identity.DTOs; + +namespace Identity.DTOs; + +/// +public class FilmRoleCreateDto : KirelRoleCreateDto +{ + +} \ No newline at end of file diff --git a/Films/Identity.DTOs/FilmRoleDto.cs b/Films/Identity.DTOs/FilmRoleDto.cs new file mode 100644 index 0000000..a3ba293 --- /dev/null +++ b/Films/Identity.DTOs/FilmRoleDto.cs @@ -0,0 +1,9 @@ +using Kirel.Identity.DTOs; + +namespace Identity.DTOs; + +/// +public class FilmRoleDto : KirelRoleDto +{ + +} \ No newline at end of file diff --git a/Films/Identity.DTOs/FilmRoleUpdateDto.cs b/Films/Identity.DTOs/FilmRoleUpdateDto.cs new file mode 100644 index 0000000..43813a1 --- /dev/null +++ b/Films/Identity.DTOs/FilmRoleUpdateDto.cs @@ -0,0 +1,9 @@ +using Kirel.Identity.DTOs; + +namespace Identity.DTOs; + +/// +public class FilmRoleUpdateDto : KirelRoleUpdateDto +{ + +} \ No newline at end of file diff --git a/Films/Identity.DTOs/FilmUserCreateDto.cs b/Films/Identity.DTOs/FilmUserCreateDto.cs new file mode 100644 index 0000000..fed8497 --- /dev/null +++ b/Films/Identity.DTOs/FilmUserCreateDto.cs @@ -0,0 +1,9 @@ +using Kirel.Identity.DTOs; + +namespace Identity.DTOs; + +/// +public class FilmUserCreateDto : KirelUserCreateDto +{ + +} \ No newline at end of file diff --git a/Films/Identity.DTOs/FilmUserDto.cs b/Films/Identity.DTOs/FilmUserDto.cs new file mode 100644 index 0000000..e30234e --- /dev/null +++ b/Films/Identity.DTOs/FilmUserDto.cs @@ -0,0 +1,9 @@ +using Kirel.Identity.DTOs; + +namespace Identity.DTOs; + +/// +public class FilmUserDto : KirelUserDto +{ + +} \ No newline at end of file diff --git a/Films/Identity.DTOs/FilmUserRegistrationDto.cs b/Films/Identity.DTOs/FilmUserRegistrationDto.cs new file mode 100644 index 0000000..50efabd --- /dev/null +++ b/Films/Identity.DTOs/FilmUserRegistrationDto.cs @@ -0,0 +1,9 @@ +using Kirel.Identity.DTOs; + +namespace Identity.DTOs; + +/// +public class FilmUserRegistrationDto : KirelUserRegistrationDto +{ + +} \ No newline at end of file diff --git a/Films/Identity.DTOs/FilmUserUpdateDto.cs b/Films/Identity.DTOs/FilmUserUpdateDto.cs new file mode 100644 index 0000000..0b3baf6 --- /dev/null +++ b/Films/Identity.DTOs/FilmUserUpdateDto.cs @@ -0,0 +1,9 @@ +using Kirel.Identity.DTOs; + +namespace Identity.DTOs; + +/// +public class FilmUserUpdateDto : KirelUserUpdateDto +{ + +} \ No newline at end of file diff --git a/Films/Identity.DTOs/Identity.DTOs.csproj b/Films/Identity.DTOs/Identity.DTOs.csproj new file mode 100644 index 0000000..22bba45 --- /dev/null +++ b/Films/Identity.DTOs/Identity.DTOs.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/Films/Identity.Domain/FilmRole.cs b/Films/Identity.Domain/FilmRole.cs new file mode 100644 index 0000000..8636315 --- /dev/null +++ b/Films/Identity.Domain/FilmRole.cs @@ -0,0 +1,9 @@ +using Kirel.Identity.Core.Models; + +namespace Identity.Domain; + +/// +public class FilmRole : KirelIdentityRole +{ + +} \ No newline at end of file diff --git a/Films/Identity.Domain/FilmRoleClaim.cs b/Films/Identity.Domain/FilmRoleClaim.cs new file mode 100644 index 0000000..6798950 --- /dev/null +++ b/Films/Identity.Domain/FilmRoleClaim.cs @@ -0,0 +1,9 @@ +using Kirel.Identity.Core.Models; + +namespace Identity.Domain; + +/// +public class FilmRoleClaim : KirelIdentityRoleClaim +{ + +} \ No newline at end of file diff --git a/Films/Identity.Domain/FilmUser.cs b/Films/Identity.Domain/FilmUser.cs new file mode 100644 index 0000000..c005996 --- /dev/null +++ b/Films/Identity.Domain/FilmUser.cs @@ -0,0 +1,9 @@ +using Kirel.Identity.Core.Models; + +namespace Identity.Domain; + +/// +public class FilmUser : KirelIdentityUser +{ + +} \ No newline at end of file diff --git a/Films/Identity.Domain/FilmUserClaim.cs b/Films/Identity.Domain/FilmUserClaim.cs new file mode 100644 index 0000000..abe8e61 --- /dev/null +++ b/Films/Identity.Domain/FilmUserClaim.cs @@ -0,0 +1,8 @@ +using Kirel.Identity.Core.Models; +namespace Identity.Domain; + +/// +public class FilmUserClaim : KirelIdentityUserClaim +{ + +} \ No newline at end of file diff --git a/Films/Identity.Domain/FilmUserLogin.cs b/Films/Identity.Domain/FilmUserLogin.cs new file mode 100644 index 0000000..a59d670 --- /dev/null +++ b/Films/Identity.Domain/FilmUserLogin.cs @@ -0,0 +1,9 @@ +using Kirel.Identity.Core.Models; + +namespace Identity.Domain; + +/// +public class FilmUserLogin : KirelIdentityUserLogin +{ + +} \ No newline at end of file diff --git a/Films/Identity.Domain/FilmUserRole.cs b/Films/Identity.Domain/FilmUserRole.cs new file mode 100644 index 0000000..9a40d80 --- /dev/null +++ b/Films/Identity.Domain/FilmUserRole.cs @@ -0,0 +1,8 @@ +using Kirel.Identity.Core.Models; +namespace Identity.Domain; + +/// +public class FilmUserRole : KirelIdentityUserRole +{ + +} \ No newline at end of file diff --git a/Films/Identity.Domain/FilmUserToken.cs b/Films/Identity.Domain/FilmUserToken.cs new file mode 100644 index 0000000..d055feb --- /dev/null +++ b/Films/Identity.Domain/FilmUserToken.cs @@ -0,0 +1,9 @@ +using Kirel.Identity.Core.Models; + +namespace Identity.Domain; + +/// +public class FilmUserToken : KirelIdentityUserToken +{ + +} \ No newline at end of file diff --git a/Films/Identity.Domain/Identity.Domain.csproj b/Films/Identity.Domain/Identity.Domain.csproj new file mode 100644 index 0000000..b7bad3a --- /dev/null +++ b/Films/Identity.Domain/Identity.Domain.csproj @@ -0,0 +1,21 @@ + + + + net6.0 + enable + enable + + + + + ..\..\..\..\Users\vova2\.nuget\packages\kirel.identity.core\1.0.1\lib\net6.0\Kirel.Identity.Core.dll + + + + + + + + + + diff --git a/Films/Identity.Infrastruture/DbContext/IdentityContext.cs b/Films/Identity.Infrastruture/DbContext/IdentityContext.cs new file mode 100644 index 0000000..2ddd97e --- /dev/null +++ b/Films/Identity.Infrastruture/DbContext/IdentityContext.cs @@ -0,0 +1,15 @@ +using Identity.Domain; +using Kirel.Identity.Core.Context; +using Microsoft.EntityFrameworkCore; + +namespace Identity.Infrastruture.DbContext; + +/// +public class IdentityContext : KirelIdentityContext +{ + /// + public IdentityContext(DbContextOptions options) : base(options) + { + } +} \ No newline at end of file diff --git a/Films/Identity.Infrastruture/DbContext/SeedDb.cs b/Films/Identity.Infrastruture/DbContext/SeedDb.cs new file mode 100644 index 0000000..9052da3 --- /dev/null +++ b/Films/Identity.Infrastruture/DbContext/SeedDb.cs @@ -0,0 +1,74 @@ +using Identity.Domain; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; + +namespace Identity.Infrastruture.DbContext; + +/// +/// Static class for database initialization +/// +public static class SeedDb +{ + private static RoleManager _roleManager; + private static UserManager_userManager; + private static async Task FindOrCreateRole(string roleName) + { + var role = await _roleManager.FindByNameAsync(roleName); + if (role != null) return role; + + role = new FilmRole() { Name = roleName }; + var result = await _roleManager.CreateAsync(role); + return !result.Succeeded ? null : role; + } + private static async Task FindOrCreateUser(string username) + { + var user = await _userManager.FindByNameAsync(username); + if (user != null) return user; + + user = new FilmUser() + { + UserName = username, Email = $"{username}@kirel.com", Name = username, LastName = "Default" + }; + var result = await _userManager.CreateAsync(user, $"{username}@123"); + return !result.Succeeded ? null : user; + } + + private static async Task AddUserToRoleIfNotAlreadyInRole(FilmUser user, string roleName) + { + if(!await _userManager.IsInRoleAsync(user, roleName)) + await _userManager.AddToRoleAsync(user, roleName); + } + + /// + /// Database initialization + /// + /// Service provider + public static async Task Initialize(IServiceProvider serviceProvider) + { + var context = serviceProvider.GetRequiredService(); + context.Database.EnsureCreated(); + + _roleManager = serviceProvider.GetRequiredService>(); + _userManager = serviceProvider.GetRequiredService>(); + + var adminUser = await FindOrCreateUser("Admin"); + var adminRole = await FindOrCreateRole("Admin"); + var userRole = await FindOrCreateRole("User"); + var userUser = await FindOrCreateUser("User"); + + if (adminRole != null && adminUser != null) + { + await AddUserToRoleIfNotAlreadyInRole(adminUser, "Admin"); + } + + if (userRole != null && adminUser != null) + { + await AddUserToRoleIfNotAlreadyInRole(adminUser, "User"); + } + + if (userRole != null && userUser != null) + { + await AddUserToRoleIfNotAlreadyInRole(userUser, "User"); + } + } +} \ No newline at end of file diff --git a/Films/Identity.Infrastruture/Identity.Infrastruture.csproj b/Films/Identity.Infrastruture/Identity.Infrastruture.csproj new file mode 100644 index 0000000..ac679bb --- /dev/null +++ b/Films/Identity.Infrastruture/Identity.Infrastruture.csproj @@ -0,0 +1,31 @@ + + + + net6.0 + enable + enable + + + + + ..\..\..\..\Users\vova2\.nuget\packages\kirel.identity.core\1.0.1\lib\net6.0\Kirel.Identity.Core.dll + + + + + + + + + + + + + + + + + + + + From 31e1b448c68ce815116b29f4fa38a647e7e857f7 Mon Sep 17 00:00:00 2001 From: Vova Zelenyi Date: Sat, 12 Aug 2023 19:19:15 +0200 Subject: [PATCH 05/23] Updated commit history --- .../Authentication.Shared.csproj} | 2 +- .../JwtAuthenticationOptions.cs | 68 +++++++ Films/Films.API/Controllers/FilmController.cs | 149 -------------- .../Extensions/AuthenticationExtension.cs | 41 ++++ .../Films.API/Extensions/SwaggerExtension.cs | 54 ++++++ Films/Films.API/Films.API.csproj | 33 ++-- Films/Films.API/Program.cs | 35 ++-- Films/Films.API/appsettings.Development.json | 7 + Films/Films.API/appsettings.json | 9 +- .../Extensions/AutoMapperExtension.cs | 17 ++ .../Extensions/FilmsServicesExtension.cs | 17 ++ Films/Films.Core/FilmMapper.cs | 25 --- Films/Films.Core/FilmService.cs | 181 ------------------ Films/Films.Core/Films.Core.csproj | 12 +- Films/Films.Core/Mappers/FilmMapper.cs | 22 +++ Films/Films.Core/{ => Mappers}/GenreMapper.cs | 7 +- Films/Films.Core/Services/FilmService.cs | 166 ++++++++++++++++ Films/Films.DTOs/FilmCreateDto.cs | 9 +- Films/Films.DTOs/FilmDto.cs | 71 +++---- Films/Films.DTOs/FilmUpdateDto.cs | 5 + Films/Films.DTOs/GenreCreateDto.cs | 1 - Films/Films.DTOs/GenreDto.cs | 31 +-- Films/Films.DTOs/GenreUpdateDto.cs | 2 +- Films/Films.Domain/Film.cs | 40 ---- Films/Films.Domain/Films.Domain.csproj | 4 +- Films/Films.Domain/Genre.cs | 24 --- Films/Films.Domain/Models/Film.cs | 45 +++++ Films/Films.Domain/Models/Genre.cs | 29 +++ .../{ => Models}/PaginatedResult.cs | 82 ++++---- .../DbContext/FilmDbContext.cs | 39 ++++ .../DbContext/FilmDbInitialize.cs | 20 ++ .../Extentions/FilmsRepositoriesExtension.cs | 25 +++ Films/Films.Infrastructure/FilmDbContext.cs | 47 ----- .../Films.Infrastructure/FilmDbInitialize.cs | 20 -- .../Films.Infrastructure.csproj | 10 +- Films/Films.sln | 41 +--- .../Controllers/ExAuthorizedUserController.cs | 22 --- .../ExJwtAuthenticationController.cs | 19 -- .../Controllers/ExRegistrationController.cs | 18 -- .../Controllers/ExRolesController.cs | 22 --- .../Controllers/ExUsersController.cs | 22 --- Films/Identity.API/Dockerfile | 20 -- Films/Identity.API/Identity.API.csproj | 28 --- Films/Identity.API/Program.cs | 103 ---------- .../Properties/launchSettings.json | 31 --- .../Identity.API/appsettings.Development.json | 8 - Films/Identity.API/appsettings.json | 13 -- Films/Identity.Core/ClaimMapping.cs | 21 -- Films/Identity.Core/ExRoleMapping.cs | 21 -- Films/Identity.Core/ExUserMapping.cs | 24 --- Films/Identity.Core/Identity.Core.csproj | 20 -- .../Services/FilmAuthenticationService.cs | 15 -- .../Services/FilmAuthorizedUserService.cs | 17 -- .../Services/FilmJwtTokenService.cs | 15 -- .../Services/FilmRegistrationService.cs | 16 -- .../Identity.Core/Services/FilmRoleService.cs | 18 -- .../Identity.Core/Services/FilmUserService.cs | 17 -- Films/Identity.DTOs/FilmAuthorizedUserDto.cs | 9 - .../FilmAuthorizedUserUpdateDto.cs | 9 - Films/Identity.DTOs/FilmRoleCreateDto.cs | 9 - Films/Identity.DTOs/FilmRoleDto.cs | 9 - Films/Identity.DTOs/FilmRoleUpdateDto.cs | 9 - Films/Identity.DTOs/FilmUserCreateDto.cs | 9 - Films/Identity.DTOs/FilmUserDto.cs | 9 - .../Identity.DTOs/FilmUserRegistrationDto.cs | 9 - Films/Identity.DTOs/FilmUserUpdateDto.cs | 9 - Films/Identity.Domain/FilmRole.cs | 9 - Films/Identity.Domain/FilmRoleClaim.cs | 9 - Films/Identity.Domain/FilmUser.cs | 9 - Films/Identity.Domain/FilmUserClaim.cs | 8 - Films/Identity.Domain/FilmUserLogin.cs | 9 - Films/Identity.Domain/FilmUserRole.cs | 8 - Films/Identity.Domain/FilmUserToken.cs | 9 - Films/Identity.Domain/Identity.Domain.csproj | 21 -- .../DbContext/IdentityContext.cs | 15 -- .../DbContext/SeedDb.cs | 74 ------- .../Identity.Infrastruture.csproj | 31 --- 77 files changed, 732 insertions(+), 1431 deletions(-) rename Films/{Identity.DTOs/Identity.DTOs.csproj => Authentication.Shared/Authentication.Shared.csproj} (73%) create mode 100644 Films/Authentication.Shared/JwtAuthenticationOptions.cs delete mode 100644 Films/Films.API/Controllers/FilmController.cs create mode 100644 Films/Films.API/Extensions/AuthenticationExtension.cs create mode 100644 Films/Films.API/Extensions/SwaggerExtension.cs create mode 100644 Films/Films.Core/Extensions/AutoMapperExtension.cs create mode 100644 Films/Films.Core/Extensions/FilmsServicesExtension.cs delete mode 100644 Films/Films.Core/FilmMapper.cs delete mode 100644 Films/Films.Core/FilmService.cs create mode 100644 Films/Films.Core/Mappers/FilmMapper.cs rename Films/Films.Core/{ => Mappers}/GenreMapper.cs (87%) create mode 100644 Films/Films.Core/Services/FilmService.cs delete mode 100644 Films/Films.Domain/Film.cs delete mode 100644 Films/Films.Domain/Genre.cs create mode 100644 Films/Films.Domain/Models/Film.cs create mode 100644 Films/Films.Domain/Models/Genre.cs rename Films/Films.Domain/{ => Models}/PaginatedResult.cs (66%) create mode 100644 Films/Films.Infrastructure/DbContext/FilmDbContext.cs create mode 100644 Films/Films.Infrastructure/DbContext/FilmDbInitialize.cs create mode 100644 Films/Films.Infrastructure/Extentions/FilmsRepositoriesExtension.cs delete mode 100644 Films/Films.Infrastructure/FilmDbContext.cs delete mode 100644 Films/Films.Infrastructure/FilmDbInitialize.cs delete mode 100644 Films/Identity.API/Controllers/ExAuthorizedUserController.cs delete mode 100644 Films/Identity.API/Controllers/ExJwtAuthenticationController.cs delete mode 100644 Films/Identity.API/Controllers/ExRegistrationController.cs delete mode 100644 Films/Identity.API/Controllers/ExRolesController.cs delete mode 100644 Films/Identity.API/Controllers/ExUsersController.cs delete mode 100644 Films/Identity.API/Dockerfile delete mode 100644 Films/Identity.API/Identity.API.csproj delete mode 100644 Films/Identity.API/Program.cs delete mode 100644 Films/Identity.API/Properties/launchSettings.json delete mode 100644 Films/Identity.API/appsettings.Development.json delete mode 100644 Films/Identity.API/appsettings.json delete mode 100644 Films/Identity.Core/ClaimMapping.cs delete mode 100644 Films/Identity.Core/ExRoleMapping.cs delete mode 100644 Films/Identity.Core/ExUserMapping.cs delete mode 100644 Films/Identity.Core/Identity.Core.csproj delete mode 100644 Films/Identity.Core/Services/FilmAuthenticationService.cs delete mode 100644 Films/Identity.Core/Services/FilmAuthorizedUserService.cs delete mode 100644 Films/Identity.Core/Services/FilmJwtTokenService.cs delete mode 100644 Films/Identity.Core/Services/FilmRegistrationService.cs delete mode 100644 Films/Identity.Core/Services/FilmRoleService.cs delete mode 100644 Films/Identity.Core/Services/FilmUserService.cs delete mode 100644 Films/Identity.DTOs/FilmAuthorizedUserDto.cs delete mode 100644 Films/Identity.DTOs/FilmAuthorizedUserUpdateDto.cs delete mode 100644 Films/Identity.DTOs/FilmRoleCreateDto.cs delete mode 100644 Films/Identity.DTOs/FilmRoleDto.cs delete mode 100644 Films/Identity.DTOs/FilmRoleUpdateDto.cs delete mode 100644 Films/Identity.DTOs/FilmUserCreateDto.cs delete mode 100644 Films/Identity.DTOs/FilmUserDto.cs delete mode 100644 Films/Identity.DTOs/FilmUserRegistrationDto.cs delete mode 100644 Films/Identity.DTOs/FilmUserUpdateDto.cs delete mode 100644 Films/Identity.Domain/FilmRole.cs delete mode 100644 Films/Identity.Domain/FilmRoleClaim.cs delete mode 100644 Films/Identity.Domain/FilmUser.cs delete mode 100644 Films/Identity.Domain/FilmUserClaim.cs delete mode 100644 Films/Identity.Domain/FilmUserLogin.cs delete mode 100644 Films/Identity.Domain/FilmUserRole.cs delete mode 100644 Films/Identity.Domain/FilmUserToken.cs delete mode 100644 Films/Identity.Domain/Identity.Domain.csproj delete mode 100644 Films/Identity.Infrastruture/DbContext/IdentityContext.cs delete mode 100644 Films/Identity.Infrastruture/DbContext/SeedDb.cs delete mode 100644 Films/Identity.Infrastruture/Identity.Infrastruture.csproj diff --git a/Films/Identity.DTOs/Identity.DTOs.csproj b/Films/Authentication.Shared/Authentication.Shared.csproj similarity index 73% rename from Films/Identity.DTOs/Identity.DTOs.csproj rename to Films/Authentication.Shared/Authentication.Shared.csproj index 22bba45..c52e1b0 100644 --- a/Films/Identity.DTOs/Identity.DTOs.csproj +++ b/Films/Authentication.Shared/Authentication.Shared.csproj @@ -7,7 +7,7 @@ - + diff --git a/Films/Authentication.Shared/JwtAuthenticationOptions.cs b/Films/Authentication.Shared/JwtAuthenticationOptions.cs new file mode 100644 index 0000000..fd1b38f --- /dev/null +++ b/Films/Authentication.Shared/JwtAuthenticationOptions.cs @@ -0,0 +1,68 @@ +using System.Text; +using Microsoft.IdentityModel.Tokens; + +namespace Authentication.Shared; + +/// +/// Represents authorization options +/// +public class JwtAuthenticationOptions +{ + /// + /// JwtAuthenticationOptions constructor + /// + /// Token publisher + /// Token consumer + /// Encryption key + /// Access token lifetime(in minutes) + /// Refresh token lifetime(in minutes) + public JwtAuthenticationOptions(string issuer, string audience, string key, int accessLifetime, int refreshLifetime) + { + Issuer = issuer; + Audience = audience; + Key = key; + AccessLifetime = accessLifetime; + RefreshLifetime = refreshLifetime; + } + + /// + /// JwtAuthenticationOptions constructor + /// + public JwtAuthenticationOptions() + { + } + + /// + /// Token publisher + /// + public string Issuer { get; set; } = ""; + + /// + /// Token consumer + /// + public string Audience { get; set; } = ""; + + /// + /// Encryption key + /// + public string Key { get; set; } = ""; + + /// + /// Access token lifetime(in minutes) + /// + public int AccessLifetime { get; set; } + + /// + /// Refresh token lifetime(in minutes) + /// + public int RefreshLifetime { get; set; } + + /// + /// Method for getting symmetric security key + /// + /// Symmetric security key + public SymmetricSecurityKey GetSymmetricSecurityKey(string key) + { + return new SymmetricSecurityKey(Encoding.ASCII.GetBytes(key)); + } +} \ No newline at end of file diff --git a/Films/Films.API/Controllers/FilmController.cs b/Films/Films.API/Controllers/FilmController.cs deleted file mode 100644 index d3d6733..0000000 --- a/Films/Films.API/Controllers/FilmController.cs +++ /dev/null @@ -1,149 +0,0 @@ -using Films.Core; -using Films.DTOs; -using Microsoft.AspNetCore.Mvc; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Films.Domain; -using Kirel.Repositories.Sorts; - -namespace Films.API.Controllers -{ - /// - /// Controller for handling film-related operations. - /// - [ApiController] - [Route("api/[controller]")] - public class FilmController : ControllerBase - { - private readonly FilmService _filmService; - - /// - /// Initializes a new instance of the class. - /// - /// The film service to retrieve and manage film data. - public FilmController(FilmService filmService) - { - _filmService = filmService; - } - - /// - /// Searches for films by name. - /// - /// The name of the film to search for. - /// Returns a list of films matching the search criteria. - [HttpGet("search")] - public async Task>> SearchFilm(string filmName) - { - try - { - var films = await _filmService.SearchFilm(filmName); - return Ok(films); - } - catch (FilmService.FilmNotFoundException ex) - { - return NotFound(ex.Message); - } - catch (Exception) - { - // If an unexpected exception occurs return a 500 Internal Server Error response. - return StatusCode(500, "An error occurred while processing your request."); - } - } - - /// - /// Retrieves a paginated list of films based on the specified parameters. - /// - /// Page number of the paginated results. - /// Number of items per page. - /// Field by which the results should be ordered. - /// Sorting direction (ascending or descending). - /// Search term to filter the results. - /// Paginated result containing a list of FilmDto objects. - [HttpGet("all")] - public async Task>>> GetAllFilms( - int pageNumber = 0, int pageSize = 10, - string orderBy = "", SortDirection orderDirection = SortDirection.Asc, string search = "") - { - try - { - var films = await _filmService.GetAllFilmsPaginated(pageNumber, pageSize, orderBy, orderDirection, search); - return Ok(films); - } - catch (Exception) - { - return StatusCode(500, "An error occurred while processing your request."); - } - } - - - /// - /// Deletes a film by ID. - /// - /// The ID of the film to delete. - /// Returns a status indicating the success of the delete operation. - [HttpDelete("{filmId}")] - public async Task DeleteFilm(int filmId) - { - try - { - await _filmService.DeleteFilm(filmId); - return Ok(); - } - catch (FilmService.FilmNotFoundException ex) - { - return NotFound(ex.Message); - } - catch (Exception) - { - return StatusCode(500, "An error occurred while processing your request."); - } - } - - /// - /// Creates a new film. - /// - /// The DTO containing the film information to create. - /// Returns the ID of the created film. - [HttpPost("create")] - public async Task> CreateFilm([FromBody] FilmCreateDto filmDto) - { - if (!ModelState.IsValid) - { - return BadRequest(ModelState); - } - - int createdFilmId = await _filmService.CreateFilm(filmDto); - return Ok(createdFilmId); - - /*catch (Exception) - { - return StatusCode(500, "An error occurred while processing your request."); - }*/ - } - - /// - /// Updates a film by ID. - /// - /// The ID of the film to update. - /// The DTO containing the updated film information. - /// Returns a status indicating the success of the update operation. - [HttpPut("update/{filmId}")] - public async Task UpdateFilm(int filmId, FilmUpdateDto filmDto) - { - try - { - await _filmService.UpdateFilm(filmId, filmDto); - return Ok(); - } - catch (FilmService.FilmNotFoundException ex) - { - return NotFound(ex.Message); - } - catch (Exception) - { - return StatusCode(500, "An error occurred while processing your request."); - } - } - } -} diff --git a/Films/Films.API/Extensions/AuthenticationExtension.cs b/Films/Films.API/Extensions/AuthenticationExtension.cs new file mode 100644 index 0000000..36747e4 --- /dev/null +++ b/Films/Films.API/Extensions/AuthenticationExtension.cs @@ -0,0 +1,41 @@ +using Authentication.Shared; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; + +namespace Films.API.Extensions; + +/// +/// Authentication configuration extension +/// +public static class AuthenticationExtension +{ + /// + /// Add authentication configuration to DI + /// + /// services collection + /// JWT Token generation config + public static void AddAuthenticationConfigurations(this IServiceCollection services, + JwtAuthenticationOptions authOptions) + { + services.AddAuthentication(option => + { + // Fixing 404 error when adding an attribute Authorize to controller + option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }) + .AddJwtBearer(options => + { + options.RequireHttpsMetadata = false; + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidIssuer = authOptions.Issuer, + ValidateAudience = true, + ValidAudience = authOptions.Audience, + ValidateLifetime = true, + IssuerSigningKey = authOptions.GetSymmetricSecurityKey(authOptions.Key), + ValidateIssuerSigningKey = true + }; + }); + } +} \ No newline at end of file diff --git a/Films/Films.API/Extensions/SwaggerExtension.cs b/Films/Films.API/Extensions/SwaggerExtension.cs new file mode 100644 index 0000000..fe47ace --- /dev/null +++ b/Films/Films.API/Extensions/SwaggerExtension.cs @@ -0,0 +1,54 @@ +using Microsoft.OpenApi.Models; + +namespace Films.API.Extensions; + +/// +/// Extension that adds swagger documentation support +/// +public static class SwaggerExtension +{ + /// + /// Add swagger documentation stuff to DI + /// + /// + public static void AddSwagger(this IServiceCollection services) + { + // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle + services.AddEndpointsApiExplorer(); + // Configure swagger + services.AddSwaggerGen(c => + { + //Add swagger xml docs + c.SwaggerDoc("v1", new OpenApiInfo { Title = "Films Api", Version = "v1" }); + //Set the comments path for the swagger json and ui. + var xmlFiles = Directory.GetFiles(AppContext.BaseDirectory, "*.xml", SearchOption.TopDirectoryOnly) + .ToList(); + xmlFiles.ForEach(xmlFile => c.IncludeXmlComments(xmlFile)); + // Add JWT token authorization support + c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + Name = "Authorization", + Type = SecuritySchemeType.ApiKey, + Scheme = "Bearer", + BearerFormat = "JWT", + In = ParameterLocation.Header, + Description = + "JWT Authorization header using the Bearer scheme. \r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.\r\n\r\nExample: \"Bearer 12345abcdef\"" + }); + c.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + new string[] { } + } + }); + }); + } +} \ No newline at end of file diff --git a/Films/Films.API/Films.API.csproj b/Films/Films.API/Films.API.csproj index cbf6b2b..a7f9c0a 100644 --- a/Films/Films.API/Films.API.csproj +++ b/Films/Films.API/Films.API.csproj @@ -10,29 +10,28 @@ - - - - - - - - - - - - + + + + + + + + + + - - .dockerignore - + + .dockerignore + - - + + + diff --git a/Films/Films.API/Program.cs b/Films/Films.API/Program.cs index 49fb2af..de2e80e 100644 --- a/Films/Films.API/Program.cs +++ b/Films/Films.API/Program.cs @@ -1,17 +1,16 @@ using System.Reflection; -using Films.Core; -using Films.Domain; -using Films.DTOs; +using Authentication.Shared; +using Films.API.Extensions; +using Films.Core.Extensions; using Films.Infrastructure; +using Films.Infrastructure.Extentions; using FluentValidation; using FluentValidation.AspNetCore; -using Kirel.Repositories; -using Kirel.Repositories.Interfaces; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); -/*var authOptions = builder.Configuration.GetSection("AuthOptions").Get();*/ +var jwtOptions = builder.Configuration.GetSection("JwtAuthenticationOptions").Get(); //taking connection string from appsettings.json var connectionString = builder.Configuration.GetConnectionString("SqlConnection"); @@ -19,22 +18,23 @@ options.UseSqlServer(connectionString)); // Add services to the container. -builder.Services.AddScoped(); -builder.Services.AddScoped, KirelGenericEntityFrameworkRepository>(); -builder.Services.AddScoped, KirelGenericEntityFrameworkRepository>(); - -//Add AutoMapper -builder.Services.AddAutoMapper(cfg => -{ - cfg.CreateMap() - .ForMember(dest => dest.Genres, opt => opt.MapFrom(src => src.GenreNames!.Select(g => new Genre { Name = g }))); -}, Assembly.GetExecutingAssembly()); +builder.Services.AddFilmsServices(); +builder.Services.AddFilmsRepositories(); + + +//Add AutoMapper. Class <--> Dto mappings. Configured in Mappings. +builder.Services.AddMapper(); /*builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());*/ builder.Services.AddFluentValidationAutoValidation(); builder.Services.AddFluentValidationClientsideAdapters(); builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); +//Add authentication +builder.Services.AddAuthentication(); +builder.Services.AddAuthenticationConfigurations(jwtOptions); +// Configure swagger +builder.Services.AddSwagger(); builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); @@ -51,6 +51,7 @@ app.UseHttpsRedirection(); +app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); diff --git a/Films/Films.API/appsettings.Development.json b/Films/Films.API/appsettings.Development.json index 0c208ae..341f582 100644 --- a/Films/Films.API/appsettings.Development.json +++ b/Films/Films.API/appsettings.Development.json @@ -4,5 +4,12 @@ "Default": "Information", "Microsoft.AspNetCore": "Warning" } + }, + "JwtAuthenticationOptions": { + "Issuer": "ExampleServer", + "Audience": "ExampleClient", + "Key": "SomeSuperSecretKey123", + "AccessLifetime": 20, + "RefreshLifetime": 3600 } } diff --git a/Films/Films.API/appsettings.json b/Films/Films.API/appsettings.json index 04fb93d..f1e9f83 100644 --- a/Films/Films.API/appsettings.json +++ b/Films/Films.API/appsettings.json @@ -9,5 +9,12 @@ "PostgreConnection": "Host=localhost;Port=5432;Database=Films;Username=lsodis;Password=6742", "SqlConnection": "Server=localhost;Database=Films;Trusted_Connection=True;Encrypt=False;" }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "JwtAuthenticationOptions": { + "Issuer": "ExampleServer", + "Audience": "ExampleClient", + "Key": "SomeSuperSecretKey123", + "AccessLifetime": 20, + "RefreshLifetime": 3600 + } } diff --git a/Films/Films.Core/Extensions/AutoMapperExtension.cs b/Films/Films.Core/Extensions/AutoMapperExtension.cs new file mode 100644 index 0000000..f51e7d6 --- /dev/null +++ b/Films/Films.Core/Extensions/AutoMapperExtension.cs @@ -0,0 +1,17 @@ +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; + +namespace Films.Core.Extensions; + +/// +/// +public static class AutoMapperExtension +{ + /// + /// + /// + public static void AddMapper(this IServiceCollection services) + { + services.AddAutoMapper(Assembly.GetExecutingAssembly()); + } +} \ No newline at end of file diff --git a/Films/Films.Core/Extensions/FilmsServicesExtension.cs b/Films/Films.Core/Extensions/FilmsServicesExtension.cs new file mode 100644 index 0000000..9b0dac9 --- /dev/null +++ b/Films/Films.Core/Extensions/FilmsServicesExtension.cs @@ -0,0 +1,17 @@ +using Films.Core.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace Films.Core.Extensions; + +/// +/// +public static class FilmsServicesExtension +{ + /// + /// + /// + public static void AddFilmsServices(this IServiceCollection services) + { + services.AddScoped(); + } +} \ No newline at end of file diff --git a/Films/Films.Core/FilmMapper.cs b/Films/Films.Core/FilmMapper.cs deleted file mode 100644 index bbd6ead..0000000 --- a/Films/Films.Core/FilmMapper.cs +++ /dev/null @@ -1,25 +0,0 @@ - using Films.Domain; - using Films.DTOs; - using AutoMapper; - namespace Films.Core; - - /// - /// Mapping Film for FilmDTO - /// - public class FilmMapper : Profile - { - /// - /// Films constructor - /// - public FilmMapper() - { - CreateMap().ReverseMap(); - CreateMap() - .ForMember(dest => dest.Genres, opt => opt.Ignore()); // Игнорируем маппинг списка жанров здесь - - CreateMap() - .ForMember(dest => dest.GenreNames, opt => opt.MapFrom(src => src.Genres.Select(g => g.Name))); - - CreateMap().ReverseMap(); - } - } \ No newline at end of file diff --git a/Films/Films.Core/FilmService.cs b/Films/Films.Core/FilmService.cs deleted file mode 100644 index 936e0fe..0000000 --- a/Films/Films.Core/FilmService.cs +++ /dev/null @@ -1,181 +0,0 @@ -using AutoMapper; -using Films.Domain; -using Films.DTOs; -using Kirel.Repositories.Interfaces; -using Kirel.Repositories.Sorts; -using Microsoft.Extensions.DependencyInjection; - -namespace Films.Core -{ - /// - /// Service responsible for film-related operations and logic. - /// - public class FilmService - { - private readonly IKirelGenericEntityRepository _filmRepository; - private readonly IMapper _mapper; - private readonly IKirelGenericEntityRepository _genreRepository; - - /// - /// Initializes a new instance of the class. - /// - /// The repository for accessing film data. - /// AutoMapper instance - /// - public FilmService(IKirelGenericEntityRepository filmRepository, IMapper mapper, IKirelGenericEntityRepository genreRepository) - { - _filmRepository = filmRepository; - _mapper = mapper; - _genreRepository = genreRepository; - } - /// - /// Searches for films in the database by name. - /// - /// The name of the film to search for. - /// A list of film DTOs matching the search criteria. - public async Task> SearchFilm(string filmName) - { - // Search for films in the database based on the provided film name. - var existingFilms = await _filmRepository.GetList(m => m.Name != null && m.Name.Contains(filmName)); - - if (existingFilms == null || !existingFilms.Any()) - { - throw new FilmNotFoundException($"Film with the title '{filmName}' was not found in the database."); - } - - // Map the retrieved films to DTOs for presentation. - var filmsList = existingFilms.Select(film => new FilmDto - { - Id = film.Id, - Name = film.Name, - Rating = film.Rating, - Description = film.Description, - PosterURL = film.PosterUrl, - Genres = _mapper.Map>(film.Genres) - }).ToList(); - - return filmsList; - } - - /// - /// Creates a new film. - /// - /// The DTO containing the film information to create. - /// The ID of the created film. - public async Task CreateFilm(FilmCreateDto filmCreateDto) - { - Film film = _mapper.Map(filmCreateDto); - - // Добавьте жанры в фильм, если они указаны в filmCreateDto.GenreNames - if (filmCreateDto.GenreNames != null) - { - foreach (string genreName in filmCreateDto.GenreNames) - { - var genre = await _genreRepository.GetList(m => m.Name != null && m.Name.Contains(genreName)); - if (genre.Any()) - { - film.Genres.Add(genre.First()); // Выберите первый подходящий жанр - } - else - { - // Жанр с указанным названием не найден, создание нового жанра - var newGenre = new Genre { Name = genreName }; - film.Genres.Add(newGenre); - } - } - } - - var createdFilm = await _filmRepository.Insert(film); - return createdFilm.Id; - } - - - - /// - /// Updates an existing film. - /// - /// The ID of the film to update. - /// The DTO containing the updated film information. - public async Task UpdateFilm(int filmId, FilmUpdateDto filmDto) - { - // Retrieve the existing film by its ID. - var existingFilm = await _filmRepository.GetById(filmId); - if (existingFilm == null) - { - throw new FilmNotFoundException($"Film with ID '{filmId}' was not found in the database."); - } - - // Update the existing film entity with the new data from the DTO. - _mapper.Map(filmDto, existingFilm); - - // Perform the update in the repository. - await _filmRepository.Update(existingFilm); - } - - /// - /// Retrieves a paginated list of all films in the database. - /// - /// Page number of the paginated results. - /// Number of items per page. - /// Field by which the results should be ordered. - /// Sorting direction (ascending or descending). - /// Search term to filter the results. - /// Paginated result containing a list of FilmDto objects. - public async Task>> GetAllFilmsPaginated(int pageNumber = 0, int pageSize = 0, - string orderBy = "", SortDirection orderDirection = SortDirection.Asc, string search = "") - { - // Get the total count of films in the database based on the search criteria. - var totalCount = await _filmRepository.Count(search); - - // Generate pagination information. - var pagination = Pagination.Generate(pageNumber, pageSize, totalCount); - - // Retrieve a paginated list of films based on the pagination and sorting parameters. - var films = await _filmRepository.GetList(search, orderBy, orderDirection, pagination.CurrentPage, pagination.PageSize); - - // Map the retrieved films to DTOs. - var filmsDto = _mapper.Map>(films); - - // Create and return the paginated result. - var result = new PaginatedResult> - { - Pagination = pagination, - Data = filmsDto - }; - return result; - } - - - /// - /// Deletes a film from the database. - /// - /// The ID of the film to delete. - public async Task DeleteFilm(int filmId) - { - // Retrieve the film by its ID. - var film = await _filmRepository.GetById(filmId); - - if (film == null) - { - throw new FilmNotFoundException($"Film with ID '{filmId}' was not found in the database."); - } - - // Delete the film from the repository. - await _filmRepository.Delete(filmId); - } - - /// - /// Custom exception class for indicating that a film was not found. - /// - public class FilmNotFoundException : Exception - { - /// - /// Retrieves exception message - /// - /// - public FilmNotFoundException(string message) : base(message) - { - } - } - } -} diff --git a/Films/Films.Core/Films.Core.csproj b/Films/Films.Core/Films.Core.csproj index 8822f85..82c6359 100644 --- a/Films/Films.Core/Films.Core.csproj +++ b/Films/Films.Core/Films.Core.csproj @@ -9,17 +9,19 @@ - - + + - - + + + + - + diff --git a/Films/Films.Core/Mappers/FilmMapper.cs b/Films/Films.Core/Mappers/FilmMapper.cs new file mode 100644 index 0000000..7fb0639 --- /dev/null +++ b/Films/Films.Core/Mappers/FilmMapper.cs @@ -0,0 +1,22 @@ +using AutoMapper; +using Films.Domain.Models; +using Films.DTOs; + +namespace Films.Core.Mappers; + +/// +/// Mapping Film for FilmDTO +/// +public class FilmMapper : Profile +{ + /// + /// Films constructor + /// + public FilmMapper() + { + CreateMap().ReverseMap(); + + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + } +} \ No newline at end of file diff --git a/Films/Films.Core/GenreMapper.cs b/Films/Films.Core/Mappers/GenreMapper.cs similarity index 87% rename from Films/Films.Core/GenreMapper.cs rename to Films/Films.Core/Mappers/GenreMapper.cs index c739c02..6b6d592 100644 --- a/Films/Films.Core/GenreMapper.cs +++ b/Films/Films.Core/Mappers/GenreMapper.cs @@ -1,13 +1,14 @@ using AutoMapper; -using Films.Domain; +using Films.Domain.Models; using Films.DTOs; -namespace Films.Core; +namespace Films.Core.Mappers; + /// /// Mapping Genre for GenreDTO /// public class GenreMapper : Profile -{ +{ /// /// GenreMapping constructor /// diff --git a/Films/Films.Core/Services/FilmService.cs b/Films/Films.Core/Services/FilmService.cs new file mode 100644 index 0000000..328745c --- /dev/null +++ b/Films/Films.Core/Services/FilmService.cs @@ -0,0 +1,166 @@ +using AutoMapper; +using Films.Domain.Models; +using Films.DTOs; +using Kirel.Repositories.Core.Interfaces; +using Kirel.Repositories.Core.Models; +using Microsoft.EntityFrameworkCore; + +namespace Films.Core.Services; + +/// +/// Service responsible for film-related operations and logic. +/// +public class FilmService +{ + private readonly IKirelGenericEntityRepository _filmRepository; + private readonly IKirelGenericEntityRepository _genreRepository; + private readonly IMapper _mapper; + + /// + /// Initializes a new instance of the class. + /// + /// The repository for accessing film data. + /// AutoMapper instance + /// The repository to accessing Genre data + public FilmService(IKirelGenericEntityRepository filmRepository, IMapper mapper, + IKirelGenericEntityRepository genreRepository) + { + _filmRepository = filmRepository; + _mapper = mapper; + _genreRepository = genreRepository; + } + + /// + /// Searches for films in the database based on the provided film name. + /// + /// The name of the film to search for. + /// A list of FilmDto objects representing the matching films. + /// Thrown if no films matching the provided name are found. + public async Task> SearchFilms(string filmName) + { + // Search for films in the database based on the provided film name. + var existingFilms = await _filmRepository.GetList( + m => m.Name != null && m.Name.Contains(filmName), + includes: q => q.Include(f => f.Genres) + ); + if (existingFilms == null || !existingFilms.Any()) + throw new FilmNotFoundException($"Film with the title '{filmName}' was not found in the database."); + + // Map the existingFilms to FilmDto objects + var filmDtos = _mapper.Map>(existingFilms); + + return filmDtos; + } + + /// + /// Creates a new film in the database with the provided data and associated genres. + /// + /// Data for the new film. + /// An asynchronous task representing the operation execution. + public async Task CreateFilm(FilmCreateDto filmCreateDto) + { + var film = _mapper.Map(filmCreateDto); + + foreach (var genreName in filmCreateDto.Genres!) + { + var existingGenres = await _genreRepository.GetList(g => g.Name == genreName.ToString()); + + if (!existingGenres.Any()) + { + var newGenre = new Genre { Name = genreName.ToString() }; + await _genreRepository.Insert(newGenre); + + // Add the newly created genre to the list of existing genres + existingGenres = new List { newGenre }; + } + + foreach (var genre in existingGenres) film.Genres.Add(genre); + } + + await _filmRepository.Insert(film); + } + + + /// + /// Updates an existing film. + /// + /// The ID of the film to update. + /// The DTO containing the updated film information. + public async Task UpdateFilm(int filmId, FilmUpdateDto filmDto) + { + // Retrieve the existing film by its ID. + var existingFilm = await _filmRepository.GetById(filmId); + if (existingFilm == null) + throw new FilmNotFoundException($"Film with ID '{filmId}' was not found in the database."); + + // Update the existing film entity with the new data from the DTO. + _mapper.Map(filmDto, existingFilm); + + // Perform the update in the repository. + await _filmRepository.Update(existingFilm); + } + + /// + /// Retrieves a paginated list of all films in the database. + /// + /// Page number of the paginated results. + /// Number of items per page. + /// Field by which the results should be ordered. + /// Sorting direction (ascending or descending). + /// Search term to filter the results. + /// Paginated result containing a list of FilmDto objects. + public async Task>> GetAllFilmsPaginated(int pageNumber = 0, int pageSize = 0, + string orderBy = "", SortDirection orderDirection = SortDirection.Asc, string search = "") + { + // Get the total count of films in the database based on the search criteria. + var totalCount = await _filmRepository.Count(search); + + // Generate pagination information. + var pagination = Pagination.Generate(pageNumber, pageSize, totalCount); + + // Retrieve a paginated list of films based on the pagination and sorting parameters. + var films = await _filmRepository.GetList(search, orderBy, orderDirection, pagination.CurrentPage, + pagination.PageSize); + + // Map the retrieved films to DTOs. + var filmsDto = _mapper.Map>(films); + + // Create and return the paginated result. + var result = new PaginatedResult> + { + Pagination = pagination, + Data = filmsDto + }; + return result; + } + + + /// + /// Deletes a film from the database. + /// + /// The ID of the film to delete. + public async Task DeleteFilm(int filmId) + { + // Retrieve the film by its ID. + var film = await _filmRepository.GetById(filmId); + + if (film == null) throw new FilmNotFoundException($"Film with ID '{filmId}' was not found in the database."); + + // Delete the film from the repository. + await _filmRepository.Delete(filmId); + } + + /// + /// Custom exception class for indicating that a film was not found. + /// + public class FilmNotFoundException : Exception + { + /// + /// Retrieves exception message + /// + /// + public FilmNotFoundException(string message) : base(message) + { + } + } +} \ No newline at end of file diff --git a/Films/Films.DTOs/FilmCreateDto.cs b/Films/Films.DTOs/FilmCreateDto.cs index 40346ea..ab2ad89 100644 --- a/Films/Films.DTOs/FilmCreateDto.cs +++ b/Films/Films.DTOs/FilmCreateDto.cs @@ -1,7 +1,7 @@ namespace Films.DTOs; /// -/// Film CreateDTO +/// Film CreateDTO /// public class FilmCreateDto { @@ -9,22 +9,27 @@ public class FilmCreateDto /// Gets or sets the name of the film. /// public string? Name { get; set; } + /// /// Gets or sets the rating of the film. /// public int Rating { get; set; } + /// /// Gets or sets the description of the film. /// public string? Description { get; set; } + /// /// Gets or sets the list of genres associated with the film. /// - public List? GenreNames { get; set; } + public List? Genres { get; set; } + /// /// Gets or sets the URL of the film's poster. /// public string? PosterUrl { get; set; } + /// /// Gets or sets the timestamp when the film was created. /// diff --git a/Films/Films.DTOs/FilmDto.cs b/Films/Films.DTOs/FilmDto.cs index 2465a0a..d9481e5 100644 --- a/Films/Films.DTOs/FilmDto.cs +++ b/Films/Films.DTOs/FilmDto.cs @@ -1,37 +1,42 @@ -namespace Films.DTOs +namespace Films.DTOs; + +/// +/// Data transfer object (DTO) representing film details. +/// +public class FilmDto { /// - /// Data transfer object (DTO) representing film details. + /// Gets or sets the unique identifier for the film. /// - public class FilmDto - { - /// - /// Gets or sets the unique identifier for the film. - /// - public int Id { get; set; } - /// - /// Gets or sets the name of the film. - /// - public string? Name { get; set; } - /// - /// Gets or sets the rating of the film. - /// - public int Rating { get; set; } - /// - /// Gets or sets the description of the film. - /// - public string? Description { get; set; } - /// - /// Gets or sets the list of genres associated with the film. - /// - public List? Genres { get; set; } - /// - /// Gets or sets the URL of the film's poster. - /// - public string? PosterURL { get; set; } - /// - /// Gets or sets the timestamp when the film was created. - /// - public DateTime Created { get; set; } - } + public int Id { get; set; } + + /// + /// Gets or sets the name of the film. + /// + public string? Name { get; set; } + + /// + /// Gets or sets the rating of the film. + /// + public int Rating { get; set; } + + /// + /// Gets or sets the description of the film. + /// + public string? Description { get; set; } + + /// + /// Gets or sets the list of genres associated with the film. + /// + public List? Genres { get; set; } + + /// + /// Gets or sets the URL of the film's poster. + /// + public string? PosterURL { get; set; } + + /// + /// Gets or sets the timestamp when the film was created. + /// + public DateTime Created { get; set; } } \ No newline at end of file diff --git a/Films/Films.DTOs/FilmUpdateDto.cs b/Films/Films.DTOs/FilmUpdateDto.cs index bcfc8ad..8ae18a0 100644 --- a/Films/Films.DTOs/FilmUpdateDto.cs +++ b/Films/Films.DTOs/FilmUpdateDto.cs @@ -9,22 +9,27 @@ public class FilmUpdateDto /// Gets or sets the name of the film. /// public string? Name { get; set; } + /// /// Gets or sets the rating of the film. /// public int Rating { get; set; } + /// /// Gets or sets the description of the film. /// public string? Description { get; set; } + /// /// Gets or sets the list of genres associated with the film. /// public List? Genres { get; set; } + /// /// Gets or sets the URL of the film's poster. /// public string? PosterUrl { get; set; } + /// /// Gets or sets the timestamp when the film was created. /// diff --git a/Films/Films.DTOs/GenreCreateDto.cs b/Films/Films.DTOs/GenreCreateDto.cs index befc8d7..3937251 100644 --- a/Films/Films.DTOs/GenreCreateDto.cs +++ b/Films/Films.DTOs/GenreCreateDto.cs @@ -5,7 +5,6 @@ namespace Films.DTOs; /// public class GenreCreateDto { - /// /// Gets or sets the name of the genre. /// diff --git a/Films/Films.DTOs/GenreDto.cs b/Films/Films.DTOs/GenreDto.cs index ffedfc3..27bc477 100644 --- a/Films/Films.DTOs/GenreDto.cs +++ b/Films/Films.DTOs/GenreDto.cs @@ -1,17 +1,22 @@ -namespace Films.DTOs +namespace Films.DTOs; + +/// +/// Data transfer object (DTO) representing a film genre. +/// +public class GenreDto { /// - /// Data transfer object (DTO) representing a film genre. + /// Gets or sets the unique identifier for the genre. /// - public class GenreDto - { - /// - /// Gets or sets the unique identifier for the genre. - /// - public int Id { get; set; } - /// - /// Gets or sets the name of the genre. - /// - public string? Name { get; set; } - } + public int Id { get; set; } + + /// + /// Gets or sets the name of the genre. + /// + public string? Name { get; set; } + + /// + /// Gets or sets the Created time of genre + /// + public DateTime Created { get; set; } } \ No newline at end of file diff --git a/Films/Films.DTOs/GenreUpdateDto.cs b/Films/Films.DTOs/GenreUpdateDto.cs index 238339c..a9b2b42 100644 --- a/Films/Films.DTOs/GenreUpdateDto.cs +++ b/Films/Films.DTOs/GenreUpdateDto.cs @@ -5,11 +5,11 @@ namespace Films.DTOs; /// public class GenreUpdateDto { - /// /// Gets or sets the unique identifier for the genre. /// public int Id { get; set; } + /// /// Gets or sets the name of the genre. /// diff --git a/Films/Films.Domain/Film.cs b/Films/Films.Domain/Film.cs deleted file mode 100644 index e7352ba..0000000 --- a/Films/Films.Domain/Film.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Kirel.Repositories.Interfaces; -namespace Films.Domain -{ - /// - /// Represents a film entity with details like name, rating, description, and genres. - /// Implements interfaces for creation timestamp tracking and using an integer as the key. - /// - public class Film : ICreatedAtTrackedEntity, IKeyEntity - { - /// - /// Gets or sets the unique identifier for the film. - /// - public int Id { get; set; } - /// - /// Gets or sets the name of the film. - /// - public string? Name { get; set; } - /// - /// Gets or sets the rating of the film. - /// - public int Rating { get; set; } - /// - /// Gets or sets the description of the film. - /// - public string? Description { get; set; } - /// - /// Gets or sets the list of genres associated with the film. - /// - public List Genres { get; set; } = new List(); - - /// - /// Gets or sets the URL of the film's poster. - /// - public string? PosterUrl { get; set; } - /// - /// Gets or sets the timestamp when the film was created. - /// - public DateTime Created { get; set; } - } -} \ No newline at end of file diff --git a/Films/Films.Domain/Films.Domain.csproj b/Films/Films.Domain/Films.Domain.csproj index 2dd9ec4..a53978b 100644 --- a/Films/Films.Domain/Films.Domain.csproj +++ b/Films/Films.Domain/Films.Domain.csproj @@ -9,8 +9,8 @@ - - + + diff --git a/Films/Films.Domain/Genre.cs b/Films/Films.Domain/Genre.cs deleted file mode 100644 index 8907a39..0000000 --- a/Films/Films.Domain/Genre.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Kirel.Repositories.Interfaces; - -namespace Films.Domain -{ - /// - /// Represents a genre associated with films. - /// - public class Genre : IKeyEntity, ICreatedAtTrackedEntity - { - /// - /// Gets or sets the unique identifier for the genre. - /// - public int Id { get; set; } - /// - /// Gets or sets the name of the genre. - /// - public string? Name { get; set; } - - /// - /// - /// - public DateTime Created { get; set; } - } -} \ No newline at end of file diff --git a/Films/Films.Domain/Models/Film.cs b/Films/Films.Domain/Models/Film.cs new file mode 100644 index 0000000..25bd04c --- /dev/null +++ b/Films/Films.Domain/Models/Film.cs @@ -0,0 +1,45 @@ +using Kirel.Repositories.Core.Interfaces; + +namespace Films.Domain.Models; + +/// +/// Represents a film entity with details like name, rating, description, and genres. +/// Implements interfaces for creation timestamp tracking and using an integer as the key. +/// +public class Film : ICreatedAtTrackedEntity, IKeyEntity +{ + /// + /// Gets or sets the name of the film. + /// + public string? Name { get; set; } + + /// + /// Gets or sets the rating of the film. + /// + public int Rating { get; set; } + + /// + /// Gets or sets the description of the film. + /// + public string? Description { get; set; } + + /// + /// Gets or sets the list of genres associated with the film. + /// + public List Genres { get; set; } = new(); + + /// + /// Gets or sets the URL of the film's poster. + /// + public string? PosterUrl { get; set; } + + /// + /// Gets or sets the timestamp when the film was created. + /// + public DateTime Created { get; set; } + + /// + /// Gets or sets the unique identifier for the film. + /// + public int Id { get; set; } +} \ No newline at end of file diff --git a/Films/Films.Domain/Models/Genre.cs b/Films/Films.Domain/Models/Genre.cs new file mode 100644 index 0000000..984d0a0 --- /dev/null +++ b/Films/Films.Domain/Models/Genre.cs @@ -0,0 +1,29 @@ +using Kirel.Repositories.Core.Interfaces; + +namespace Films.Domain.Models; + +/// +/// Represents a genre associated with films. +/// +public class Genre : IKeyEntity, ICreatedAtTrackedEntity +{ + /// + /// Gets or sets the name of the genre. + /// + public string? Name { get; set; } + + /// + /// reference to film + /// + public ICollection Films { get; set; } = new List(); + + /// + /// Gets or sets the Created time of genre + /// + public DateTime Created { get; set; } + + /// + /// Gets or sets the unique identifier for the genre. + /// + public int Id { get; set; } +} \ No newline at end of file diff --git a/Films/Films.Domain/PaginatedResult.cs b/Films/Films.Domain/Models/PaginatedResult.cs similarity index 66% rename from Films/Films.Domain/PaginatedResult.cs rename to Films/Films.Domain/Models/PaginatedResult.cs index 339ccca..47704bb 100644 --- a/Films/Films.Domain/PaginatedResult.cs +++ b/Films/Films.Domain/Models/PaginatedResult.cs @@ -1,74 +1,73 @@ -namespace Films.Domain; +namespace Films.Domain.Models; + /// /// Pagination class /// public class Pagination { + /// + /// Pagination constructor + /// + /// Total number of pages + /// Total number of items + /// Current page number + /// Size of the page + public Pagination(int totalPages = 0, int totalCount = 0, int currentPage = 1, int pageSize = 10) + { + TotalPages = totalPages; + TotalCount = totalCount; + CurrentPage = currentPage; + PageSize = pageSize; + } + /// /// Total number of pages /// public int TotalPages { get; set; } + /// /// Total number of items /// public int TotalCount { get; set; } + /// /// Current page number /// public int CurrentPage { get; set; } + /// /// Size of the page /// public int PageSize { get; set; } - /// - /// Pagination constructor - /// - /// Total number of pages - /// Total number of items - /// Current page number - /// Size of the page - public Pagination(int totalPages = 0, int totalCount = 0, int currentPage = 1, int pageSize = 10) - { - TotalPages = totalPages; - TotalCount = totalCount; - CurrentPage = currentPage; - PageSize = pageSize; - } + /// /// Generates pagination of entities /// - /// Page number - /// Page size - /// Total count - /// Pagination + /// Page number + /// Page size + /// Total count + /// Pagination public static Pagination Generate(int pageNumber = 0, int pageSize = 0, int totalCount = 0) { var page = pageNumber > 0 ? pageNumber : 1; var size = pageSize > 0 ? pageSize : 10; - var pagination = new Pagination() + var pagination = new Pagination { CurrentPage = page, PageSize = size, TotalCount = totalCount, - TotalPages = (int) Math.Ceiling(totalCount / (double) size) + TotalPages = (int)Math.Ceiling(totalCount / (double)size) }; return pagination; } } + /// /// Class pagination results /// -/// Entity type +/// Entity type public class PaginatedResult { - /// - /// Pagination entity field - /// - public Pagination Pagination { get; set; } - /// - /// A data field with a specific type - /// - public T? Data { get; set; } /// /// PaginatedResult constructor /// @@ -76,17 +75,28 @@ public PaginatedResult() { Pagination = new Pagination(); } + /// /// PaginatedResult constructor /// - /// A data with a specific type - /// Current page number - /// Size of the page - /// Total number of pages - /// Total number of items + /// A data with a specific type + /// Current page number + /// Size of the page + /// Total number of pages + /// Total number of items public PaginatedResult(T data, int currentPage, int pageSize, int totalPages, int totalCount) { - Pagination = new Pagination(totalPages, totalCount, currentPage: currentPage, pageSize: pageSize); + Pagination = new Pagination(totalPages, totalCount, currentPage, pageSize); Data = data; } + + /// + /// Pagination entity field + /// + public Pagination Pagination { get; set; } + + /// + /// A data field with a specific type + /// + public T? Data { get; set; } } \ No newline at end of file diff --git a/Films/Films.Infrastructure/DbContext/FilmDbContext.cs b/Films/Films.Infrastructure/DbContext/FilmDbContext.cs new file mode 100644 index 0000000..749d5c8 --- /dev/null +++ b/Films/Films.Infrastructure/DbContext/FilmDbContext.cs @@ -0,0 +1,39 @@ +using Films.Domain.Models; +using Microsoft.EntityFrameworkCore; + +namespace Films.Infrastructure; + +/// +/// Represents the database context for managing film-related data. +/// +public class FilmDbContext : DbContext +{ + /// + /// Initializes a new instance of the class. + /// + /// The options used to configure the context. + public FilmDbContext(DbContextOptions options) : base(options) + { + } + + /// + /// Gets or sets the DbSet for films in the database. + /// + private DbSet? Films { get; set; } + + /// + /// Gets or sets the DbSet for genres in the database. + /// + private DbSet? Genres { get; set; } + + /// + /// + /// + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasMany(f => f.Genres) + .WithMany(g => g.Films) + .UsingEntity(j => j.ToTable("FilmGenres")); + } +} \ No newline at end of file diff --git a/Films/Films.Infrastructure/DbContext/FilmDbInitialize.cs b/Films/Films.Infrastructure/DbContext/FilmDbInitialize.cs new file mode 100644 index 0000000..66f5ac2 --- /dev/null +++ b/Films/Films.Infrastructure/DbContext/FilmDbInitialize.cs @@ -0,0 +1,20 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace Films.Infrastructure; + +/// +/// Utility class for initializing the film database and creating tables. +/// +public class FilmDbInitialize : DbContext +{ + /// + /// Initializes the film database and creates tables if they do not exist. + /// + /// The service provider to retrieve the database context. + public static void Initialize(IServiceProvider serviceProvider) + { + var context = serviceProvider.GetRequiredService(); + context.Database.EnsureCreated(); + } +} \ No newline at end of file diff --git a/Films/Films.Infrastructure/Extentions/FilmsRepositoriesExtension.cs b/Films/Films.Infrastructure/Extentions/FilmsRepositoriesExtension.cs new file mode 100644 index 0000000..162dfa9 --- /dev/null +++ b/Films/Films.Infrastructure/Extentions/FilmsRepositoriesExtension.cs @@ -0,0 +1,25 @@ +using Films.Domain.Models; +using Kirel.Repositories.Core.Interfaces; +using Kirel.Repositories.EntityFramework; +using Microsoft.Extensions.DependencyInjection; + +namespace Films.Infrastructure.Extentions; + +/// +/// Add db to DI +/// +public static class FilmsRepositoriesExtension +{ + /// + /// + /// Collection services + public static void AddFilmsRepositories(this IServiceCollection services) + { + services + .AddScoped, + KirelGenericEntityFrameworkRepository>(); + services + .AddScoped, + KirelGenericEntityFrameworkRepository>(); + } +} \ No newline at end of file diff --git a/Films/Films.Infrastructure/FilmDbContext.cs b/Films/Films.Infrastructure/FilmDbContext.cs deleted file mode 100644 index c624d54..0000000 --- a/Films/Films.Infrastructure/FilmDbContext.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Films.Domain; -using Microsoft.EntityFrameworkCore; - -namespace Films.Infrastructure -{ - /// - /// Represents the database context for managing film-related data. - /// - public class FilmDbContext : DbContext - { - /// - /// Initializes a new instance of the class. - /// - /// The options used to configure the context. - public FilmDbContext(DbContextOptions options) : base(options) - { - - } - /// - /// Gets or sets the DbSet for films in the database. - /// - private DbSet? Films { get; set; } - - /// - /// Gets or sets the DbSet for genres in the database. - /// - private DbSet? Genres { get; set; } - /// - /// - /// - /// - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - modelBuilder.Entity() - .HasMany(f => f.Genres) - .WithMany() - .UsingEntity(j => - { - - j.Property("Id"); - }); - } - - - } -} \ No newline at end of file diff --git a/Films/Films.Infrastructure/FilmDbInitialize.cs b/Films/Films.Infrastructure/FilmDbInitialize.cs deleted file mode 100644 index 631cecf..0000000 --- a/Films/Films.Infrastructure/FilmDbInitialize.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace Films.Infrastructure -{ - /// - /// Utility class for initializing the film database and creating tables. - /// - public class FilmDbInitialize : Microsoft.EntityFrameworkCore.DbContext - { - /// - /// Initializes the film database and creates tables if they do not exist. - /// - /// The service provider to retrieve the database context. - public static void Initialize(IServiceProvider serviceProvider) - { - var context = serviceProvider.GetRequiredService(); - context.Database.EnsureCreated(); - } - } -} \ No newline at end of file diff --git a/Films/Films.Infrastructure/Films.Infrastructure.csproj b/Films/Films.Infrastructure/Films.Infrastructure.csproj index d3a52f2..798427a 100644 --- a/Films/Films.Infrastructure/Films.Infrastructure.csproj +++ b/Films/Films.Infrastructure/Films.Infrastructure.csproj @@ -9,13 +9,15 @@ - - - + + + + + - + diff --git a/Films/Films.sln b/Films/Films.sln index 874ae61..c0006d6 100644 --- a/Films/Films.sln +++ b/Films/Films.sln @@ -10,17 +10,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Films.Domain", "Films.Domai EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Films.DTOs", "Films.DTOs\Films.DTOs.csproj", "{5B5FACA7-DABE-472C-8813-85A8C18A6A4D}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Identity", "Identity", "{1777E05F-05BC-439B-9848-8494967590E2}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Identity.API", "Identity.API\Identity.API.csproj", "{AE26ACCE-6A8C-46E8-8A21-6D0AB454C465}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Identity.Core", "Identity.Core\Identity.Core.csproj", "{7D2ECC79-7725-42F2-856D-939A53230C21}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Identity.Domain", "Identity.Domain\Identity.Domain.csproj", "{56826CEA-DBA9-42AD-ABC1-9D4D0F22E84C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Identity.DTOs", "Identity.DTOs\Identity.DTOs.csproj", "{621C6524-6993-4D9C-B084-A2BFACA0A366}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Identity.Infrastruture", "Identity.Infrastruture\Identity.Infrastruture.csproj", "{B6D90487-5384-48F9-A278-EEE49392B9F2}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Authentication.Shared", "Authentication.Shared\Authentication.Shared.csproj", "{C145BC0A-17A6-4CB3-A524-8355B41DD2CC}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -48,32 +38,11 @@ Global {5B5FACA7-DABE-472C-8813-85A8C18A6A4D}.Debug|Any CPU.Build.0 = Debug|Any CPU {5B5FACA7-DABE-472C-8813-85A8C18A6A4D}.Release|Any CPU.ActiveCfg = Release|Any CPU {5B5FACA7-DABE-472C-8813-85A8C18A6A4D}.Release|Any CPU.Build.0 = Release|Any CPU - {AE26ACCE-6A8C-46E8-8A21-6D0AB454C465}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AE26ACCE-6A8C-46E8-8A21-6D0AB454C465}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AE26ACCE-6A8C-46E8-8A21-6D0AB454C465}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AE26ACCE-6A8C-46E8-8A21-6D0AB454C465}.Release|Any CPU.Build.0 = Release|Any CPU - {7D2ECC79-7725-42F2-856D-939A53230C21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7D2ECC79-7725-42F2-856D-939A53230C21}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7D2ECC79-7725-42F2-856D-939A53230C21}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7D2ECC79-7725-42F2-856D-939A53230C21}.Release|Any CPU.Build.0 = Release|Any CPU - {56826CEA-DBA9-42AD-ABC1-9D4D0F22E84C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {56826CEA-DBA9-42AD-ABC1-9D4D0F22E84C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {56826CEA-DBA9-42AD-ABC1-9D4D0F22E84C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {56826CEA-DBA9-42AD-ABC1-9D4D0F22E84C}.Release|Any CPU.Build.0 = Release|Any CPU - {621C6524-6993-4D9C-B084-A2BFACA0A366}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {621C6524-6993-4D9C-B084-A2BFACA0A366}.Debug|Any CPU.Build.0 = Debug|Any CPU - {621C6524-6993-4D9C-B084-A2BFACA0A366}.Release|Any CPU.ActiveCfg = Release|Any CPU - {621C6524-6993-4D9C-B084-A2BFACA0A366}.Release|Any CPU.Build.0 = Release|Any CPU - {B6D90487-5384-48F9-A278-EEE49392B9F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B6D90487-5384-48F9-A278-EEE49392B9F2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B6D90487-5384-48F9-A278-EEE49392B9F2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B6D90487-5384-48F9-A278-EEE49392B9F2}.Release|Any CPU.Build.0 = Release|Any CPU + {C145BC0A-17A6-4CB3-A524-8355B41DD2CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C145BC0A-17A6-4CB3-A524-8355B41DD2CC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C145BC0A-17A6-4CB3-A524-8355B41DD2CC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C145BC0A-17A6-4CB3-A524-8355B41DD2CC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution - {AE26ACCE-6A8C-46E8-8A21-6D0AB454C465} = {1777E05F-05BC-439B-9848-8494967590E2} - {7D2ECC79-7725-42F2-856D-939A53230C21} = {1777E05F-05BC-439B-9848-8494967590E2} - {56826CEA-DBA9-42AD-ABC1-9D4D0F22E84C} = {1777E05F-05BC-439B-9848-8494967590E2} - {621C6524-6993-4D9C-B084-A2BFACA0A366} = {1777E05F-05BC-439B-9848-8494967590E2} - {B6D90487-5384-48F9-A278-EEE49392B9F2} = {1777E05F-05BC-439B-9848-8494967590E2} EndGlobalSection EndGlobal diff --git a/Films/Identity.API/Controllers/ExAuthorizedUserController.cs b/Films/Identity.API/Controllers/ExAuthorizedUserController.cs deleted file mode 100644 index 3674692..0000000 --- a/Films/Identity.API/Controllers/ExAuthorizedUserController.cs +++ /dev/null @@ -1,22 +0,0 @@ -using AutoMapper; -using Identity.Core.Services; -using Identity.Domain; -using Identity.DTOs; -using Kirel.Identity.Controllers; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace Identity.API.Controllers; - -/// -[ApiController] -[Authorize] -[Route("authorized/user")] -public class ExAuthorizedUserController : KirelAuthorizedUserController -{ - /// - public ExAuthorizedUserController(FilmAuthorizedUserService service, IMapper mapper) : base(service, mapper) - { - } -} \ No newline at end of file diff --git a/Films/Identity.API/Controllers/ExJwtAuthenticationController.cs b/Films/Identity.API/Controllers/ExJwtAuthenticationController.cs deleted file mode 100644 index c06089c..0000000 --- a/Films/Identity.API/Controllers/ExJwtAuthenticationController.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Identity.Core.Services; -using Identity.Domain; -using Identity.DTOs; -using Kirel.Identity.Jwt.Controllers; -using Microsoft.AspNetCore.Mvc; - -namespace Identity.API.Controllers; - -/// -[ApiController] -[Route("authentication/jwt")] -public class ExJwtAuthenticationController : KirelJwtAuthenticationController -{ - /// - public ExJwtAuthenticationController(FilmAuthenticationService authService, FilmJwtTokenService tokenService, FilmAuthorizedUserService authorizedUserservice) : base(authService, tokenService, authorizedUserservice) - { - } -} \ No newline at end of file diff --git a/Films/Identity.API/Controllers/ExRegistrationController.cs b/Films/Identity.API/Controllers/ExRegistrationController.cs deleted file mode 100644 index 99d9047..0000000 --- a/Films/Identity.API/Controllers/ExRegistrationController.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Identity.Core.Services; -using Identity.Domain; -using Identity.DTOs; -using Kirel.Identity.Controllers; -using Microsoft.AspNetCore.Mvc; - -namespace Identity.API.Controllers; - -/// -[ApiController] -[Route("registration")] -public class ExRegistrationController : KirelRegistrationController -{ - /// - public ExRegistrationController(FilmRegistrationService service) : base(service) - { - } -} \ No newline at end of file diff --git a/Films/Identity.API/Controllers/ExRolesController.cs b/Films/Identity.API/Controllers/ExRolesController.cs deleted file mode 100644 index 3f1ff4a..0000000 --- a/Films/Identity.API/Controllers/ExRolesController.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Identity.Core.Services; -using Identity.Domain; -using Identity.DTOs; -using Kirel.Identity.Controllers; -using Kirel.Identity.DTOs; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace Identity.API.Controllers; - -/// -[ApiController] -[Authorize] -[Route("roles")] -public class ExRolesController : KirelRolesController -{ - /// - public ExRolesController(FilmRoleService service) : base(service) - { - } -} \ No newline at end of file diff --git a/Films/Identity.API/Controllers/ExUsersController.cs b/Films/Identity.API/Controllers/ExUsersController.cs deleted file mode 100644 index acfb4fa..0000000 --- a/Films/Identity.API/Controllers/ExUsersController.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Identity.Core.Services; -using Identity.Domain; -using Identity.DTOs; -using Kirel.Identity.Controllers; -using Kirel.Identity.DTOs; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace Identity.API.Controllers; - -/// -[ApiController] -[Authorize] -[Route("users")] -public class ExUsersController : KirelUsersController -{ - /// - public ExUsersController(FilmUserService service) : base(service) - { - } -} \ No newline at end of file diff --git a/Films/Identity.API/Dockerfile b/Films/Identity.API/Dockerfile deleted file mode 100644 index 20c6782..0000000 --- a/Films/Identity.API/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base -WORKDIR /app -EXPOSE 80 -EXPOSE 443 - -FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build -WORKDIR /src -COPY ["Identity.API/Identity.API.csproj", "Identity.API/"] -RUN dotnet restore "Identity.API/Identity.API.csproj" -COPY . . -WORKDIR "/src/Identity.API" -RUN dotnet build "Identity.API.csproj" -c Release -o /app/build - -FROM build AS publish -RUN dotnet publish "Identity.API.csproj" -c Release -o /app/publish /p:UseAppHost=false - -FROM base AS final -WORKDIR /app -COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "Identity.API.dll"] diff --git a/Films/Identity.API/Identity.API.csproj b/Films/Identity.API/Identity.API.csproj deleted file mode 100644 index 3bd58b6..0000000 --- a/Films/Identity.API/Identity.API.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - net6.0 - enable - enable - Linux - - - - - - - - - - - - .dockerignore - - - - - - - - - diff --git a/Films/Identity.API/Program.cs b/Films/Identity.API/Program.cs deleted file mode 100644 index 2f0d2b6..0000000 --- a/Films/Identity.API/Program.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System.Reflection; -using FluentValidation; -using FluentValidation.AspNetCore; -using Identity.Core.Services; -using Identity.Domain; -using Identity.Infrastruture.DbContext; -using Kirel.Identity.Core.Models; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Identity; -using Microsoft.EntityFrameworkCore; -using Microsoft.IdentityModel.Tokens; - -var builder = WebApplication.CreateBuilder(args); - -// Add services to the container. -var connectionString = builder.Configuration.GetConnectionString("SqlConnection"); -builder.Services.AddDbContext(options => - options.UseSqlServer(connectionString)); -builder.Services.AddControllers(); -// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); -builder.Services.AddIdentity().AddEntityFrameworkStores(); -builder.Services.Configure(options => -{ - // Password settings. - options.Password.RequireDigit = true; - options.Password.RequireLowercase = true; - options.Password.RequireUppercase = true; - options.Password.RequireNonAlphanumeric = true; - options.Password.RequiredLength = 8; - // User settings. - options.User.RequireUniqueEmail = true; -}); - -// Add dto validators. Configured in Validators. -builder.Services.AddFluentValidationAutoValidation(); -builder.Services.AddFluentValidationClientsideAdapters(); -builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); - -//Add AutoMapper. Class <--> Dto mappings. Configured in Mappings. -builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly()); - -// Add kirel based identity management services -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); - -// add kirel based jwt tokens generation service -builder.Services.AddScoped(); - -// Add kirel authentication/authorization options -var authOptions = new KirelAuthOptions() -{ - AccessLifetime = 5, - Audience = "ExampleClient", - Issuer = "ExampleServer", - Key = "SomeSuperSecretKey123", - RefreshLifetime = 3600 -}; -builder.Services.AddSingleton(authOptions); - -// Add ASP.NET authentication configuration -builder.Services.AddAuthentication(option => - { - // Fixing 404 error when adding an attribute Authorize to controller - option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; - }) - .AddJwtBearer(options => - { - options.RequireHttpsMetadata = false; - options.TokenValidationParameters = new TokenValidationParameters - { - ValidateIssuer = true, - ValidIssuer = authOptions.Issuer, - ValidateAudience = true, - ValidAudience = authOptions.Audience, - ValidateLifetime = true, - IssuerSigningKey = authOptions.GetSymmetricSecurityKey(authOptions.Key), - ValidateIssuerSigningKey = true, - }; - }); - - -var app = builder.Build(); - -// Configure the HTTP request pipeline. -if (app.Environment.IsDevelopment()) -{ - app.UseSwagger(); - app.UseSwaggerUI(); -} - -app.UseHttpsRedirection(); - -app.UseAuthorization(); - -app.MapControllers(); - -app.Run(); \ No newline at end of file diff --git a/Films/Identity.API/Properties/launchSettings.json b/Films/Identity.API/Properties/launchSettings.json deleted file mode 100644 index 78d82bf..0000000 --- a/Films/Identity.API/Properties/launchSettings.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:10507", - "sslPort": 44382 - } - }, - "profiles": { - "Identity.API": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "swagger", - "applicationUrl": "https://localhost:7012;http://localhost:5050", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/Films/Identity.API/appsettings.Development.json b/Films/Identity.API/appsettings.Development.json deleted file mode 100644 index 0c208ae..0000000 --- a/Films/Identity.API/appsettings.Development.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - } -} diff --git a/Films/Identity.API/appsettings.json b/Films/Identity.API/appsettings.json deleted file mode 100644 index 9886e45..0000000 --- a/Films/Identity.API/appsettings.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "ConnectionStrings": { - "PostgreConnection": "Host=localhost;Port=5432;Database=Films;Username=lsodis;Password=6742", - "SqlConnection": "Server=localhost;Database=Identity;Trusted_Connection=True;Encrypt=False;" - }, - "AllowedHosts": "*" -} diff --git a/Films/Identity.Core/ClaimMapping.cs b/Films/Identity.Core/ClaimMapping.cs deleted file mode 100644 index ac71c9b..0000000 --- a/Films/Identity.Core/ClaimMapping.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Security.Claims; -using AutoMapper; -using Kirel.Identity.DTOs; - -namespace Identity.Core; - -/// -/// Mapping profile for claim -/// -public class ClaimMappings : Profile -{ - /// - /// ClaimMappings constructor - /// - public ClaimMappings() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } -} \ No newline at end of file diff --git a/Films/Identity.Core/ExRoleMapping.cs b/Films/Identity.Core/ExRoleMapping.cs deleted file mode 100644 index 32a5fd7..0000000 --- a/Films/Identity.Core/ExRoleMapping.cs +++ /dev/null @@ -1,21 +0,0 @@ -using AutoMapper; -using Identity.Domain; -using Identity.DTOs; - -namespace Identity.Core; - -/// -/// Mapping profile for role -/// -public class ExRoleMapping : Profile -{ - /// - /// Role mapping constructor - /// - public ExRoleMapping() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } -} \ No newline at end of file diff --git a/Films/Identity.Core/ExUserMapping.cs b/Films/Identity.Core/ExUserMapping.cs deleted file mode 100644 index c3f3593..0000000 --- a/Films/Identity.Core/ExUserMapping.cs +++ /dev/null @@ -1,24 +0,0 @@ -using AutoMapper; -using Identity.Domain; -using Identity.DTOs; - -namespace Identity.Core; - -/// -/// Mapping profile for user -/// -public class ExUserMapping : Profile -{ - /// - /// UserMapping constructor - /// - public ExUserMapping() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } -} \ No newline at end of file diff --git a/Films/Identity.Core/Identity.Core.csproj b/Films/Identity.Core/Identity.Core.csproj deleted file mode 100644 index fbaf991..0000000 --- a/Films/Identity.Core/Identity.Core.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - net6.0 - enable - enable - - - - - - - - - - - - - - diff --git a/Films/Identity.Core/Services/FilmAuthenticationService.cs b/Films/Identity.Core/Services/FilmAuthenticationService.cs deleted file mode 100644 index ca54332..0000000 --- a/Films/Identity.Core/Services/FilmAuthenticationService.cs +++ /dev/null @@ -1,15 +0,0 @@ - -using Identity.Domain; -using Kirel.Identity.Core.Services; -using Microsoft.AspNetCore.Identity; - -namespace Identity.Core.Services; - -/// -public class FilmAuthenticationService : KirelAuthenticationService -{ - /// - public FilmAuthenticationService(UserManager userManager) : base(userManager) - { - } -} \ No newline at end of file diff --git a/Films/Identity.Core/Services/FilmAuthorizedUserService.cs b/Films/Identity.Core/Services/FilmAuthorizedUserService.cs deleted file mode 100644 index 53e4ad6..0000000 --- a/Films/Identity.Core/Services/FilmAuthorizedUserService.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AutoMapper; -using Identity.Domain; -using Identity.DTOs; -using Kirel.Identity.Core.Services; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Identity; - -namespace Identity.Core.Services; - -/// -public class FilmAuthorizedUserService : KirelAuthorizedUserService -{ - /// - public FilmAuthorizedUserService(IHttpContextAccessor httpContextAccessor, UserManager userManager, IMapper mapper) : base(httpContextAccessor, userManager, mapper) - { - } -} \ No newline at end of file diff --git a/Films/Identity.Core/Services/FilmJwtTokenService.cs b/Films/Identity.Core/Services/FilmJwtTokenService.cs deleted file mode 100644 index acb27d7..0000000 --- a/Films/Identity.Core/Services/FilmJwtTokenService.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Identity.Domain; -using Kirel.Identity.Core.Models; -using Kirel.Identity.Jwt.Core.Services; -using Microsoft.AspNetCore.Identity; - -namespace Identity.Core.Services; - -/// -public class FilmJwtTokenService : KirelJwtTokenService -{ - /// - public FilmJwtTokenService(UserManager userManager, RoleManager roleManager, KirelAuthOptions authOptions) : base(userManager, roleManager, authOptions) - { - } -} \ No newline at end of file diff --git a/Films/Identity.Core/Services/FilmRegistrationService.cs b/Films/Identity.Core/Services/FilmRegistrationService.cs deleted file mode 100644 index cad95ad..0000000 --- a/Films/Identity.Core/Services/FilmRegistrationService.cs +++ /dev/null @@ -1,16 +0,0 @@ -using AutoMapper; -using Identity.Domain; -using Identity.DTOs; -using Kirel.Identity.Core.Services; -using Microsoft.AspNetCore.Identity; - -namespace Identity.Core.Services; - -/// -public class FilmRegistrationService : KirelRegistrationService -{ - /// - public FilmRegistrationService(UserManager userManager, IMapper mapper) : base(userManager, mapper) - { - } -} \ No newline at end of file diff --git a/Films/Identity.Core/Services/FilmRoleService.cs b/Films/Identity.Core/Services/FilmRoleService.cs deleted file mode 100644 index 573eaae..0000000 --- a/Films/Identity.Core/Services/FilmRoleService.cs +++ /dev/null @@ -1,18 +0,0 @@ -using AutoMapper; -using Identity.Domain; -using Identity.DTOs; -using Kirel.Identity.Core.Services; -using Kirel.Identity.DTOs; -using Microsoft.AspNetCore.Identity; - -namespace Identity.Core.Services; - -/// -public class FilmRoleService : KirelRoleService -{ - /// - public FilmRoleService(RoleManager roleManager, IMapper mapper) : base(roleManager, mapper) - { - } -} \ No newline at end of file diff --git a/Films/Identity.Core/Services/FilmUserService.cs b/Films/Identity.Core/Services/FilmUserService.cs deleted file mode 100644 index 1b7e1f5..0000000 --- a/Films/Identity.Core/Services/FilmUserService.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AutoMapper; -using Identity.Domain; -using Identity.DTOs; -using Kirel.Identity.Core.Services; -using Kirel.Identity.DTOs; -using Microsoft.AspNetCore.Identity; - -namespace Identity.Core.Services; -/// -public class FilmUserService : KirelUserService -{ - /// - public FilmUserService(UserManager userManager, RoleManager roleManager, IMapper mapper) : base(userManager, roleManager, mapper) - { - } -} \ No newline at end of file diff --git a/Films/Identity.DTOs/FilmAuthorizedUserDto.cs b/Films/Identity.DTOs/FilmAuthorizedUserDto.cs deleted file mode 100644 index d1c18a2..0000000 --- a/Films/Identity.DTOs/FilmAuthorizedUserDto.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Kirel.Identity.DTOs; - -namespace Identity.DTOs; - -/// -public class FilmAuthorizedUserDto : KirelAuthorizedUserDto -{ - -} \ No newline at end of file diff --git a/Films/Identity.DTOs/FilmAuthorizedUserUpdateDto.cs b/Films/Identity.DTOs/FilmAuthorizedUserUpdateDto.cs deleted file mode 100644 index 5036d79..0000000 --- a/Films/Identity.DTOs/FilmAuthorizedUserUpdateDto.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Kirel.Identity.DTOs; - -namespace Identity.DTOs; - -/// -public class FilmAuthorizedUserUpdateDto : KirelAuthorizedUserUpdateDto -{ - -} \ No newline at end of file diff --git a/Films/Identity.DTOs/FilmRoleCreateDto.cs b/Films/Identity.DTOs/FilmRoleCreateDto.cs deleted file mode 100644 index d699169..0000000 --- a/Films/Identity.DTOs/FilmRoleCreateDto.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Kirel.Identity.DTOs; - -namespace Identity.DTOs; - -/// -public class FilmRoleCreateDto : KirelRoleCreateDto -{ - -} \ No newline at end of file diff --git a/Films/Identity.DTOs/FilmRoleDto.cs b/Films/Identity.DTOs/FilmRoleDto.cs deleted file mode 100644 index a3ba293..0000000 --- a/Films/Identity.DTOs/FilmRoleDto.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Kirel.Identity.DTOs; - -namespace Identity.DTOs; - -/// -public class FilmRoleDto : KirelRoleDto -{ - -} \ No newline at end of file diff --git a/Films/Identity.DTOs/FilmRoleUpdateDto.cs b/Films/Identity.DTOs/FilmRoleUpdateDto.cs deleted file mode 100644 index 43813a1..0000000 --- a/Films/Identity.DTOs/FilmRoleUpdateDto.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Kirel.Identity.DTOs; - -namespace Identity.DTOs; - -/// -public class FilmRoleUpdateDto : KirelRoleUpdateDto -{ - -} \ No newline at end of file diff --git a/Films/Identity.DTOs/FilmUserCreateDto.cs b/Films/Identity.DTOs/FilmUserCreateDto.cs deleted file mode 100644 index fed8497..0000000 --- a/Films/Identity.DTOs/FilmUserCreateDto.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Kirel.Identity.DTOs; - -namespace Identity.DTOs; - -/// -public class FilmUserCreateDto : KirelUserCreateDto -{ - -} \ No newline at end of file diff --git a/Films/Identity.DTOs/FilmUserDto.cs b/Films/Identity.DTOs/FilmUserDto.cs deleted file mode 100644 index e30234e..0000000 --- a/Films/Identity.DTOs/FilmUserDto.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Kirel.Identity.DTOs; - -namespace Identity.DTOs; - -/// -public class FilmUserDto : KirelUserDto -{ - -} \ No newline at end of file diff --git a/Films/Identity.DTOs/FilmUserRegistrationDto.cs b/Films/Identity.DTOs/FilmUserRegistrationDto.cs deleted file mode 100644 index 50efabd..0000000 --- a/Films/Identity.DTOs/FilmUserRegistrationDto.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Kirel.Identity.DTOs; - -namespace Identity.DTOs; - -/// -public class FilmUserRegistrationDto : KirelUserRegistrationDto -{ - -} \ No newline at end of file diff --git a/Films/Identity.DTOs/FilmUserUpdateDto.cs b/Films/Identity.DTOs/FilmUserUpdateDto.cs deleted file mode 100644 index 0b3baf6..0000000 --- a/Films/Identity.DTOs/FilmUserUpdateDto.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Kirel.Identity.DTOs; - -namespace Identity.DTOs; - -/// -public class FilmUserUpdateDto : KirelUserUpdateDto -{ - -} \ No newline at end of file diff --git a/Films/Identity.Domain/FilmRole.cs b/Films/Identity.Domain/FilmRole.cs deleted file mode 100644 index 8636315..0000000 --- a/Films/Identity.Domain/FilmRole.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Kirel.Identity.Core.Models; - -namespace Identity.Domain; - -/// -public class FilmRole : KirelIdentityRole -{ - -} \ No newline at end of file diff --git a/Films/Identity.Domain/FilmRoleClaim.cs b/Films/Identity.Domain/FilmRoleClaim.cs deleted file mode 100644 index 6798950..0000000 --- a/Films/Identity.Domain/FilmRoleClaim.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Kirel.Identity.Core.Models; - -namespace Identity.Domain; - -/// -public class FilmRoleClaim : KirelIdentityRoleClaim -{ - -} \ No newline at end of file diff --git a/Films/Identity.Domain/FilmUser.cs b/Films/Identity.Domain/FilmUser.cs deleted file mode 100644 index c005996..0000000 --- a/Films/Identity.Domain/FilmUser.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Kirel.Identity.Core.Models; - -namespace Identity.Domain; - -/// -public class FilmUser : KirelIdentityUser -{ - -} \ No newline at end of file diff --git a/Films/Identity.Domain/FilmUserClaim.cs b/Films/Identity.Domain/FilmUserClaim.cs deleted file mode 100644 index abe8e61..0000000 --- a/Films/Identity.Domain/FilmUserClaim.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Kirel.Identity.Core.Models; -namespace Identity.Domain; - -/// -public class FilmUserClaim : KirelIdentityUserClaim -{ - -} \ No newline at end of file diff --git a/Films/Identity.Domain/FilmUserLogin.cs b/Films/Identity.Domain/FilmUserLogin.cs deleted file mode 100644 index a59d670..0000000 --- a/Films/Identity.Domain/FilmUserLogin.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Kirel.Identity.Core.Models; - -namespace Identity.Domain; - -/// -public class FilmUserLogin : KirelIdentityUserLogin -{ - -} \ No newline at end of file diff --git a/Films/Identity.Domain/FilmUserRole.cs b/Films/Identity.Domain/FilmUserRole.cs deleted file mode 100644 index 9a40d80..0000000 --- a/Films/Identity.Domain/FilmUserRole.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Kirel.Identity.Core.Models; -namespace Identity.Domain; - -/// -public class FilmUserRole : KirelIdentityUserRole -{ - -} \ No newline at end of file diff --git a/Films/Identity.Domain/FilmUserToken.cs b/Films/Identity.Domain/FilmUserToken.cs deleted file mode 100644 index d055feb..0000000 --- a/Films/Identity.Domain/FilmUserToken.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Kirel.Identity.Core.Models; - -namespace Identity.Domain; - -/// -public class FilmUserToken : KirelIdentityUserToken -{ - -} \ No newline at end of file diff --git a/Films/Identity.Domain/Identity.Domain.csproj b/Films/Identity.Domain/Identity.Domain.csproj deleted file mode 100644 index b7bad3a..0000000 --- a/Films/Identity.Domain/Identity.Domain.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - net6.0 - enable - enable - - - - - ..\..\..\..\Users\vova2\.nuget\packages\kirel.identity.core\1.0.1\lib\net6.0\Kirel.Identity.Core.dll - - - - - - - - - - diff --git a/Films/Identity.Infrastruture/DbContext/IdentityContext.cs b/Films/Identity.Infrastruture/DbContext/IdentityContext.cs deleted file mode 100644 index 2ddd97e..0000000 --- a/Films/Identity.Infrastruture/DbContext/IdentityContext.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Identity.Domain; -using Kirel.Identity.Core.Context; -using Microsoft.EntityFrameworkCore; - -namespace Identity.Infrastruture.DbContext; - -/// -public class IdentityContext : KirelIdentityContext -{ - /// - public IdentityContext(DbContextOptions options) : base(options) - { - } -} \ No newline at end of file diff --git a/Films/Identity.Infrastruture/DbContext/SeedDb.cs b/Films/Identity.Infrastruture/DbContext/SeedDb.cs deleted file mode 100644 index 9052da3..0000000 --- a/Films/Identity.Infrastruture/DbContext/SeedDb.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Identity.Domain; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.DependencyInjection; - -namespace Identity.Infrastruture.DbContext; - -/// -/// Static class for database initialization -/// -public static class SeedDb -{ - private static RoleManager _roleManager; - private static UserManager_userManager; - private static async Task FindOrCreateRole(string roleName) - { - var role = await _roleManager.FindByNameAsync(roleName); - if (role != null) return role; - - role = new FilmRole() { Name = roleName }; - var result = await _roleManager.CreateAsync(role); - return !result.Succeeded ? null : role; - } - private static async Task FindOrCreateUser(string username) - { - var user = await _userManager.FindByNameAsync(username); - if (user != null) return user; - - user = new FilmUser() - { - UserName = username, Email = $"{username}@kirel.com", Name = username, LastName = "Default" - }; - var result = await _userManager.CreateAsync(user, $"{username}@123"); - return !result.Succeeded ? null : user; - } - - private static async Task AddUserToRoleIfNotAlreadyInRole(FilmUser user, string roleName) - { - if(!await _userManager.IsInRoleAsync(user, roleName)) - await _userManager.AddToRoleAsync(user, roleName); - } - - /// - /// Database initialization - /// - /// Service provider - public static async Task Initialize(IServiceProvider serviceProvider) - { - var context = serviceProvider.GetRequiredService(); - context.Database.EnsureCreated(); - - _roleManager = serviceProvider.GetRequiredService>(); - _userManager = serviceProvider.GetRequiredService>(); - - var adminUser = await FindOrCreateUser("Admin"); - var adminRole = await FindOrCreateRole("Admin"); - var userRole = await FindOrCreateRole("User"); - var userUser = await FindOrCreateUser("User"); - - if (adminRole != null && adminUser != null) - { - await AddUserToRoleIfNotAlreadyInRole(adminUser, "Admin"); - } - - if (userRole != null && adminUser != null) - { - await AddUserToRoleIfNotAlreadyInRole(adminUser, "User"); - } - - if (userRole != null && userUser != null) - { - await AddUserToRoleIfNotAlreadyInRole(userUser, "User"); - } - } -} \ No newline at end of file diff --git a/Films/Identity.Infrastruture/Identity.Infrastruture.csproj b/Films/Identity.Infrastruture/Identity.Infrastruture.csproj deleted file mode 100644 index ac679bb..0000000 --- a/Films/Identity.Infrastruture/Identity.Infrastruture.csproj +++ /dev/null @@ -1,31 +0,0 @@ - - - - net6.0 - enable - enable - - - - - ..\..\..\..\Users\vova2\.nuget\packages\kirel.identity.core\1.0.1\lib\net6.0\Kirel.Identity.Core.dll - - - - - - - - - - - - - - - - - - - - From d500ef0426a5ae7831d400a7abc5b19a02d31296 Mon Sep 17 00:00:00 2001 From: Vova Zelenyi Date: Sun, 13 Aug 2023 16:00:20 +0200 Subject: [PATCH 06/23] Add UnitTest for filmsService --- Films/Films.sln | 6 + Films/FilmsTest/FilmsTest.csproj | 28 ++++ Films/FilmsTest/FilmsUnitTest.cs | 212 +++++++++++++++++++++++++++++++ Films/FilmsTest/Usings.cs | 9 ++ 4 files changed, 255 insertions(+) create mode 100644 Films/FilmsTest/FilmsTest.csproj create mode 100644 Films/FilmsTest/FilmsUnitTest.cs create mode 100644 Films/FilmsTest/Usings.cs diff --git a/Films/Films.sln b/Films/Films.sln index c0006d6..fbe3187 100644 --- a/Films/Films.sln +++ b/Films/Films.sln @@ -12,6 +12,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Films.DTOs", "Films.DTOs\Fi EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Authentication.Shared", "Authentication.Shared\Authentication.Shared.csproj", "{C145BC0A-17A6-4CB3-A524-8355B41DD2CC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FilmsTest", "FilmsTest\FilmsTest.csproj", "{DB1F0DF7-8783-4B35-8DC8-184E4DA5A15F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -42,6 +44,10 @@ Global {C145BC0A-17A6-4CB3-A524-8355B41DD2CC}.Debug|Any CPU.Build.0 = Debug|Any CPU {C145BC0A-17A6-4CB3-A524-8355B41DD2CC}.Release|Any CPU.ActiveCfg = Release|Any CPU {C145BC0A-17A6-4CB3-A524-8355B41DD2CC}.Release|Any CPU.Build.0 = Release|Any CPU + {DB1F0DF7-8783-4B35-8DC8-184E4DA5A15F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB1F0DF7-8783-4B35-8DC8-184E4DA5A15F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB1F0DF7-8783-4B35-8DC8-184E4DA5A15F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB1F0DF7-8783-4B35-8DC8-184E4DA5A15F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution EndGlobalSection diff --git a/Films/FilmsTest/FilmsTest.csproj b/Films/FilmsTest/FilmsTest.csproj new file mode 100644 index 0000000..a492f73 --- /dev/null +++ b/Films/FilmsTest/FilmsTest.csproj @@ -0,0 +1,28 @@ + + + + net6.0 + enable + enable + + false + + + + + + + + + + + + + + + + + + + + diff --git a/Films/FilmsTest/FilmsUnitTest.cs b/Films/FilmsTest/FilmsUnitTest.cs new file mode 100644 index 0000000..1849247 --- /dev/null +++ b/Films/FilmsTest/FilmsUnitTest.cs @@ -0,0 +1,212 @@ +using Films.Core.Mappers; +using Kirel.Repositories.Core.Models; +using Assert = NUnit.Framework.Assert; + +namespace FilmsTest +{ + [TestClass] + public class FilmServiceTests + { + private IMapper _mapper; + + [TestInitialize] + public void Initialize() + { + var configuration = new MapperConfiguration(cfg => + { + cfg.AddProfile(); + cfg.AddProfile(); // Регистрация вашего профиля маппинга + // Добавьте другие профили маппинга, если они есть + }); + + _mapper = configuration.CreateMapper(); + } + + [TestMethod] + public async Task CreateFilm_ValidModel_CallsRepositoriesAndService() + { + // Arrange + var mockFilmRepository = new Mock>(); + var mockGenreRepository = new Mock>(); + + var filmService = new FilmService( + mockFilmRepository.Object, + _mapper, // Используем зарегистрированный маппер + mockGenreRepository.Object + ); + + var filmCreateDto = new FilmCreateDto + { + Name = "Test Film", + Rating = 5, + Description = "Test Description", + Genres = new List + { + new() { Name = "Action" }, + new() { Name = "Drama" } + }, + PosterUrl = "http://example.com/test-poster.jpg" + }; + + // Assume that genre "Action" and "Drama" do not exist yet + mockGenreRepository.Setup(repo => repo.GetList( + It.IsAny>>(), + It.IsAny, IOrderedQueryable>>(), + It.IsAny, IQueryable>>(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync(new List()); + + // Act + await filmService.CreateFilm(filmCreateDto); + + // Assert + mockGenreRepository.Verify(m => m.Insert(It.IsAny()), + Times.Exactly(2)); // Verify that Insert is called for each new genre + mockFilmRepository.Verify(m => m.Insert(It.IsAny()), Times.Once); + } + + [TestMethod] + public async Task SearchFilms_FoundFilms_ReturnsFilmDtos() + { + // Arrange + var mockFilmRepository = new Mock>(); + var mockGenreRepository = new Mock>(); + var mockMapper = new Mock(); + + var filmService = new FilmService( + mockFilmRepository.Object, + _mapper, // Используем зарегистрированный маппер + mockGenreRepository.Object + ); + + // Создаем поддельные данные для поиска + var filmName = "Test Film"; + var existingFilms = new List + { + new Film { Name = "Test Film 1" }, + new Film { Name = "Test Film 2" } + }; + + // Ожидаем, что метод GetList будет вызван с правильными параметрами + mockFilmRepository.Setup(repo => repo.GetList( + It.IsAny>>(), + It.IsAny, IOrderedQueryable>>(), + It.IsAny, IQueryable>>(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync(existingFilms); + + // Ожидаем, что маппер будет вызван для маппинга Film в FilmDto + mockMapper.Setup(m => m.Map>(existingFilms)) + .Returns(new List + { + new FilmDto { Name = "Test Film 1" }, + new FilmDto { Name = "Test Film 2" } + }); + + // Act + var result = await filmService.SearchFilms(filmName); + + // Output результатов в консоль + Console.WriteLine("Search Films Result:"); + foreach (var filmDto in result) + { + Console.WriteLine($"Film Name: {filmDto.Name}"); + // Другие поля + } + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(2, result.Count); // Проверяем, что количество Dto соответствует ожидаемому + // Другие проверки по вашим ожиданиям могут быть добавлены здесь + } + + [TestMethod] + public async Task GetAllFilmsPaginated_ReturnsPaginatedResultWithGenres() + { + // Arrange + var mockFilmRepository = new Mock>(); + var mockGenreRepository = new Mock>(); + + var filmService = new FilmService( + mockFilmRepository.Object, + _mapper, // Используем зарегистрированный маппер + mockGenreRepository.Object + ); + + // Создаем поддельные данные для пагинации + var pageNumber = 1; + var pageSize = 10; + var orderBy = "Name"; + var orderDirection = SortDirection.Asc; + var search = "Test"; + + var totalCount = 20; // Общее количество фильмов + + + + var paginatedFilms = new List + { + new Film + { + Name = "Test Film 1", + Genres = new List + { + new Genre { Name = "Action" }, + new Genre { Name = "Drama" } + } + }, + new Film + { + Name = "Test Film 2", + Genres = new List + { + new Genre { Name = "Comedy" } + } + } + // Другие фильмы + }; + + // Ожидаем, что метод Count будет вызван с правильными параметрами и вернет общее количество фильмов + mockFilmRepository.Setup(repo => repo.Count(search)) + .ReturnsAsync(totalCount); + + // Ожидаем, что метод GetList будет вызван с правильными параметрами и вернет пагинированный список фильмов + mockFilmRepository.Setup(repo => repo.GetList( + It.IsAny(), + orderBy, + orderDirection, + pageNumber, + pageSize)) + .ReturnsAsync(paginatedFilms); + + // Act + var result = await filmService.GetAllFilmsPaginated(pageNumber, pageSize, orderBy, orderDirection, search); + Console.WriteLine("Pagination:"); + Console.WriteLine($" Current Page: {result.Pagination.CurrentPage}"); + Console.WriteLine($" Page Size: {result.Pagination.PageSize}"); + Console.WriteLine($" Total Count: {result.Pagination.TotalCount}"); + + foreach (var filmDto in result.Data) + { + Console.WriteLine("Film:"); + Console.WriteLine($" Name: {filmDto.Name}"); + Console.WriteLine($" Genres: {string.Join(", ", filmDto.Genres)}"); + // Другие поля + } + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(pageNumber, result.Pagination.CurrentPage); + Assert.AreEqual(pageSize, result.Pagination.PageSize); + Assert.AreEqual(totalCount, result.Pagination.TotalCount); + Assert.AreEqual(paginatedFilms.Count, result.Data.Count); + + // Проверяем, что жанры также корректно маппируются + Assert.AreEqual(paginatedFilms[0].Genres.Count, result.Data[0].Genres.Count); + Assert.AreEqual(paginatedFilms[1].Genres.Count, result.Data[1].Genres.Count); + // Другие проверки по вашим ожиданиям могут быть добавлены здесь + } + } +} diff --git a/Films/FilmsTest/Usings.cs b/Films/FilmsTest/Usings.cs new file mode 100644 index 0000000..40fd8ea --- /dev/null +++ b/Films/FilmsTest/Usings.cs @@ -0,0 +1,9 @@ +global using NUnit.Framework; +global using System.Linq.Expressions; +global using AutoMapper; +global using Films.Core.Services; +global using Films.Domain.Models; +global using Films.DTOs; +global using Kirel.Repositories.Core.Interfaces; +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using Moq; From 64d29f376e7e5d771a00f9b256f089a900b9952c Mon Sep 17 00:00:00 2001 From: Vova Zelenyi Date: Sun, 13 Aug 2023 16:01:13 +0200 Subject: [PATCH 07/23] Add controller because of the handlessness of the developer --- Films/Films.API/Controllers/FilmController.cs | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 Films/Films.API/Controllers/FilmController.cs diff --git a/Films/Films.API/Controllers/FilmController.cs b/Films/Films.API/Controllers/FilmController.cs new file mode 100644 index 0000000..d35b0d6 --- /dev/null +++ b/Films/Films.API/Controllers/FilmController.cs @@ -0,0 +1,136 @@ +using Films.Core.Services; +using Films.Domain.Models; +using Films.DTOs; +using Kirel.Repositories.Core.Models; +using Microsoft.AspNetCore.Mvc; + +namespace Films.API.Controllers; + +/// +/// Controller for handling film-related operations. +/// +[ApiController] +[Route("api/films")] +public class FilmController : ControllerBase +{ + private readonly FilmService _filmService; + + /// + /// Initializes a new instance of the class. + /// + /// The film service to retrieve and manage film data. + public FilmController(FilmService filmService) + { + _filmService = filmService; + } + + /// + /// Searches for films by name. + /// + /// The name of the film to search for. + /// Returns a list of films matching the search criteria. + [HttpGet("search")] + public async Task>> SearchFilms(string filmName) + { + try + { + var films = await _filmService.SearchFilms(filmName); + return Ok(films); + } + catch (FilmService.FilmNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception) + { + // If an unexpected exception occurs return a 500 Internal Server Error response. + return StatusCode(500, "An error occurred while processing your request."); + } + } + + /// + /// Retrieves a paginated list of films based on the specified parameters. + /// + /// Page number of the paginated results. + /// Number of items per page. + /// Field by which the results should be ordered. + /// Sorting direction (ascending or descending). + /// Search term to filter the results. + /// Paginated result containing a list of FilmDto objects. + [HttpGet("all")] + public async Task>>> GetAllFilms( + int pageNumber = 0, int pageSize = 10, + string orderBy = "", SortDirection orderDirection = SortDirection.Asc, string search = "") + { + try + { + var films = await _filmService.GetAllFilmsPaginated(pageNumber, pageSize, orderBy, orderDirection, search); + return Ok(films); + } + catch (Exception) + { + return StatusCode(500, "An error occurred while processing your request."); + } + } + + + /// Deletes a film by ID. + /// The ID of the film to delete. + /// Returns a status indicating the success of the delete operation. + [HttpDelete("{filmId}")] + public async Task DeleteFilm(int filmId) + { + try + { + await _filmService.DeleteFilm(filmId); + return Ok(); + } + catch (FilmService.FilmNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception) + { + return StatusCode(500, "An error occurred while processing your request."); + } + } + + /// + /// Creates a new film. + /// + /// The DTO containing the film information to create. + /// Returns the ID of the created film. + [HttpPost] + public async Task CreateFilm([FromBody] FilmCreateDto filmCreateDto) + { + if (!ModelState.IsValid) return BadRequest(ModelState); + + await _filmService.CreateFilm(filmCreateDto); + + return Ok(); + } + + /// + /// Updates a film by ID. + /// + /// The ID of the film to update. + /// The DTO containing the updated film information. + /// Returns a status indicating the success of the update operation. + [HttpPut("update/{filmId}")] + public async Task UpdateFilm(int filmId, FilmUpdateDto filmDto) + { + try + { + await _filmService.UpdateFilm(filmId, filmDto); + return Ok(); + } + catch (FilmService.FilmNotFoundException ex) + { + return NotFound(ex.Message); + } + catch (Exception) + { + return StatusCode(500, "An error occurred while processing your request."); + } + } +} \ No newline at end of file From 26b3ae7f7bb665071c9b0df4940914467d713497 Mon Sep 17 00:00:00 2001 From: Vova Zelenyi Date: Sun, 13 Aug 2023 16:01:41 +0200 Subject: [PATCH 08/23] change Icollection to List --- Films/Films.Core/Services/FilmService.cs | 1 + Films/Films.Domain/Models/Genre.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Films/Films.Core/Services/FilmService.cs b/Films/Films.Core/Services/FilmService.cs index 328745c..a5f2aef 100644 --- a/Films/Films.Core/Services/FilmService.cs +++ b/Films/Films.Core/Services/FilmService.cs @@ -133,6 +133,7 @@ public async Task>> GetAllFilmsPaginated(int pageN }; return result; } + /// diff --git a/Films/Films.Domain/Models/Genre.cs b/Films/Films.Domain/Models/Genre.cs index 984d0a0..12d05c9 100644 --- a/Films/Films.Domain/Models/Genre.cs +++ b/Films/Films.Domain/Models/Genre.cs @@ -15,7 +15,7 @@ public class Genre : IKeyEntity, ICreatedAtTrackedEntity /// /// reference to film /// - public ICollection Films { get; set; } = new List(); + public List Films { get; set; } = new List(); /// /// Gets or sets the Created time of genre From 9ba6b330292a6b41cdcd244eef1967ec6f811332 Mon Sep 17 00:00:00 2001 From: Vova Zelenyi Date: Sun, 13 Aug 2023 20:01:31 +0200 Subject: [PATCH 09/23] change to connection many to many --- .../DbContext/FilmDbContext.cs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Films/Films.Infrastructure/DbContext/FilmDbContext.cs b/Films/Films.Infrastructure/DbContext/FilmDbContext.cs index 749d5c8..a6779fb 100644 --- a/Films/Films.Infrastructure/DbContext/FilmDbContext.cs +++ b/Films/Films.Infrastructure/DbContext/FilmDbContext.cs @@ -16,15 +16,7 @@ public FilmDbContext(DbContextOptions options) : base(options) { } - /// - /// Gets or sets the DbSet for films in the database. - /// - private DbSet? Films { get; set; } - - /// - /// Gets or sets the DbSet for genres in the database. - /// - private DbSet? Genres { get; set; } + /// /// @@ -33,7 +25,16 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity() .HasMany(f => f.Genres) - .WithMany(g => g.Films) + .WithMany(g => g.Film) .UsingEntity(j => j.ToTable("FilmGenres")); } + /// + /// Gets or sets the DbSet for films in the database. + /// + private DbSet? Films { get; set; } + + /// + /// Gets or sets the DbSet for genres in the database. + /// + private DbSet? Genres { get; set; } } \ No newline at end of file From 9f367ada7176352b2a239a925e34779c62aa22b6 Mon Sep 17 00:00:00 2001 From: Vova Zelenyi Date: Sun, 13 Aug 2023 20:17:26 +0200 Subject: [PATCH 10/23] Fix duplicates and database --- Films/Films.Core/Services/FilmService.cs | 44 ++++++++++++++++++------ Films/Films.Domain/Models/Film.cs | 2 ++ Films/Films.Domain/Models/Genre.cs | 11 ++++-- 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/Films/Films.Core/Services/FilmService.cs b/Films/Films.Core/Services/FilmService.cs index a5f2aef..679fd98 100644 --- a/Films/Films.Core/Services/FilmService.cs +++ b/Films/Films.Core/Services/FilmService.cs @@ -1,9 +1,12 @@ +using System.Transactions; using AutoMapper; using Films.Domain.Models; using Films.DTOs; using Kirel.Repositories.Core.Interfaces; using Kirel.Repositories.Core.Models; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace Films.Core.Services; @@ -16,6 +19,7 @@ public class FilmService private readonly IKirelGenericEntityRepository _genreRepository; private readonly IMapper _mapper; + /// /// Initializes a new instance of the class. /// @@ -28,6 +32,7 @@ public FilmService(IKirelGenericEntityRepository filmRepository, IMap _filmRepository = filmRepository; _mapper = mapper; _genreRepository = genreRepository; + } /// @@ -61,27 +66,46 @@ public async Task CreateFilm(FilmCreateDto filmCreateDto) { var film = _mapper.Map(filmCreateDto); - foreach (var genreName in filmCreateDto.Genres!) + var existingGenres = await _genreRepository.GetList(orderBy: null, includes: null, page: 0, pageSize: 0); + + if (filmCreateDto.Genres != null) { - var existingGenres = await _genreRepository.GetList(g => g.Name == genreName.ToString()); + var genresToRemove = film.Genres.ToList(); // Создаем список для жанров, которые нужно удалить - if (!existingGenres.Any()) + foreach (var genreDto in filmCreateDto.Genres) { - var newGenre = new Genre { Name = genreName.ToString() }; - await _genreRepository.Insert(newGenre); - - // Add the newly created genre to the list of existing genres - existingGenres = new List { newGenre }; + var existingGenre = existingGenres.FirstOrDefault(g => g.Name == genreDto.Name); + if (existingGenre != null) + { + // Прикрепляем существующий жанр к фильму + film.Genres.Add(existingGenre); + + // Если жанр существует, удаляем его из списка для удаления + genresToRemove.Remove(existingGenre); + } + else + { + // Создаем новый жанр, если он не существует + var newGenre = new Genre { Name = genreDto.Name }; + film.Genres.Add(newGenre); + } } - foreach (var genre in existingGenres) film.Genres.Add(genre); + // Удаляем ненужные жанры из фильма + foreach (var genreToRemove in genresToRemove) + { + film.Genres.Remove(genreToRemove); + } } + // Сохраняем фильм в базе данных await _filmRepository.Insert(film); } - /// + + +/// /// Updates an existing film. /// /// The ID of the film to update. diff --git a/Films/Films.Domain/Models/Film.cs b/Films/Films.Domain/Models/Film.cs index 25bd04c..c6b06d9 100644 --- a/Films/Films.Domain/Models/Film.cs +++ b/Films/Films.Domain/Models/Film.cs @@ -1,4 +1,5 @@ using Kirel.Repositories.Core.Interfaces; +using Microsoft.EntityFrameworkCore; namespace Films.Domain.Models; @@ -6,6 +7,7 @@ namespace Films.Domain.Models; /// Represents a film entity with details like name, rating, description, and genres. /// Implements interfaces for creation timestamp tracking and using an integer as the key. /// + public class Film : ICreatedAtTrackedEntity, IKeyEntity { /// diff --git a/Films/Films.Domain/Models/Genre.cs b/Films/Films.Domain/Models/Genre.cs index 12d05c9..7d16765 100644 --- a/Films/Films.Domain/Models/Genre.cs +++ b/Films/Films.Domain/Models/Genre.cs @@ -1,21 +1,25 @@ +using Films.Domain.Models; using Kirel.Repositories.Core.Interfaces; +using Microsoft.EntityFrameworkCore; namespace Films.Domain.Models; /// /// Represents a genre associated with films. /// +/*[Index(nameof(Name), IsUnique = true)]*/ public class Genre : IKeyEntity, ICreatedAtTrackedEntity { /// /// Gets or sets the name of the genre. /// - public string? Name { get; set; } + + public string? Name { get; set; } /// /// reference to film /// - public List Films { get; set; } = new List(); + public List? Film { get; set; } = new(); /// /// Gets or sets the Created time of genre @@ -26,4 +30,7 @@ public class Genre : IKeyEntity, ICreatedAtTrackedEntity /// Gets or sets the unique identifier for the genre. /// public int Id { get; set; } + + + } \ No newline at end of file From 62dc1cfdb06817ebab512b001e53d83f2871556a Mon Sep 17 00:00:00 2001 From: Vova Zelenyi Date: Sun, 13 Aug 2023 20:38:09 +0200 Subject: [PATCH 11/23] Added findByGenreId --- Films/Films.API/Controllers/FilmController.cs | 12 +++++++++- Films/Films.Core/Services/FilmService.cs | 23 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/Films/Films.API/Controllers/FilmController.cs b/Films/Films.API/Controllers/FilmController.cs index d35b0d6..4f62eb9 100644 --- a/Films/Films.API/Controllers/FilmController.cs +++ b/Films/Films.API/Controllers/FilmController.cs @@ -109,7 +109,17 @@ public async Task CreateFilm([FromBody] FilmCreateDto filmCreateD return Ok(); } - + /// + /// Get all films by genres + /// + /// Ids of genres you need + /// + [HttpGet("films/by-genre-ids")] + public async Task GetFilmsByGenreIds([FromQuery] List genreIds) + { + var filmDtos = await _filmService.GetFilmsByGenreIds(genreIds); + return Ok(filmDtos); + } /// /// Updates a film by ID. /// diff --git a/Films/Films.Core/Services/FilmService.cs b/Films/Films.Core/Services/FilmService.cs index 679fd98..5bde34a 100644 --- a/Films/Films.Core/Services/FilmService.cs +++ b/Films/Films.Core/Services/FilmService.cs @@ -101,6 +101,29 @@ public async Task CreateFilm(FilmCreateDto filmCreateDto) // Сохраняем фильм в базе данных await _filmRepository.Insert(film); } + + /// + /// Searching all films which have this genre Id + /// + /// + /// + public async Task> GetFilmsByGenreIds(List genreIds) + { + var filmDtos = new List(); + + var films = await _filmRepository.GetList( + m => genreIds.Contains(m.Id), // Поиск по идентификаторам жанров + includes: q => q.Include(f => f.Genres) // Включение связанных жанров + ); + + foreach (var film in films) + { + var filmDto = _mapper.Map(film); + filmDtos.Add(filmDto); + } + + return filmDtos; + } From 6557d08fe5822d0d5549a1bb72fe890f2a801f20 Mon Sep 17 00:00:00 2001 From: Vova Zelenyi Date: Sun, 13 Aug 2023 20:50:56 +0200 Subject: [PATCH 12/23] UpdateService --- Films/Films.Core/Services/FilmService.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Films/Films.Core/Services/FilmService.cs b/Films/Films.Core/Services/FilmService.cs index 5bde34a..041d17f 100644 --- a/Films/Films.Core/Services/FilmService.cs +++ b/Films/Films.Core/Services/FilmService.cs @@ -111,15 +111,15 @@ public async Task> GetFilmsByGenreIds(List genreIds) { var filmDtos = new List(); - var films = await _filmRepository.GetList( - m => genreIds.Contains(m.Id), // Поиск по идентификаторам жанров - includes: q => q.Include(f => f.Genres) // Включение связанных жанров - ); - - foreach (var film in films) + foreach (var genreId in genreIds) { - var filmDto = _mapper.Map(film); - filmDtos.Add(filmDto); + var films = await _filmRepository.GetList( + f => f.Genres.Any(g => g.Id == genreId), + includes: q => q.Include(f => f.Genres) + ); + + var genreFilmDtos = _mapper.Map>(films); + filmDtos.AddRange(genreFilmDtos); } return filmDtos; From 61968ce8ec3e4afa9602ebb64f13a7bf9378e8ca Mon Sep 17 00:00:00 2001 From: Vova Zelenyi Date: Mon, 14 Aug 2023 15:00:48 +0200 Subject: [PATCH 13/23] Added class ServiceHelper --- Films/Films.Core/ServiceHelper.cs | 35 +++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 Films/Films.Core/ServiceHelper.cs diff --git a/Films/Films.Core/ServiceHelper.cs b/Films/Films.Core/ServiceHelper.cs new file mode 100644 index 0000000..b3900cd --- /dev/null +++ b/Films/Films.Core/ServiceHelper.cs @@ -0,0 +1,35 @@ +using Kirel.Repositories.Core.Models; +using Kirel.Shared; + +namespace Films.Core; + +/// +/// +/// +public static class ServiceHelper +{ + /// + /// + /// + /// + /// + /// + /// + public static Func, IOrderedQueryable>? GenerateOrderingMethod(string? orderBy, SortDirection orderDirection) + { + Func, IOrderedQueryable>? orderingMethod = null; + if (string.IsNullOrEmpty(orderBy)) return orderingMethod; + var orderExpression = PredicateBuilder.ToLambda(orderBy); + if (orderExpression == null) return orderingMethod; + switch (orderDirection) + { + case SortDirection.Asc: + orderingMethod = o => o.OrderBy(orderExpression); + break; + case SortDirection.Desc: + orderingMethod = o => o.OrderByDescending(orderExpression); + break; + } + return orderingMethod; + } +} \ No newline at end of file From 5cc8308eb12cad3af34a7e60d9acc1a058c90bb5 Mon Sep 17 00:00:00 2001 From: Vova Zelenyi Date: Mon, 14 Aug 2023 16:29:58 +0200 Subject: [PATCH 14/23] delete servicehelper --- Films/Films.Core/ServiceHelper.cs | 35 ------------------------------- 1 file changed, 35 deletions(-) delete mode 100644 Films/Films.Core/ServiceHelper.cs diff --git a/Films/Films.Core/ServiceHelper.cs b/Films/Films.Core/ServiceHelper.cs deleted file mode 100644 index b3900cd..0000000 --- a/Films/Films.Core/ServiceHelper.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Kirel.Repositories.Core.Models; -using Kirel.Shared; - -namespace Films.Core; - -/// -/// -/// -public static class ServiceHelper -{ - /// - /// - /// - /// - /// - /// - /// - public static Func, IOrderedQueryable>? GenerateOrderingMethod(string? orderBy, SortDirection orderDirection) - { - Func, IOrderedQueryable>? orderingMethod = null; - if (string.IsNullOrEmpty(orderBy)) return orderingMethod; - var orderExpression = PredicateBuilder.ToLambda(orderBy); - if (orderExpression == null) return orderingMethod; - switch (orderDirection) - { - case SortDirection.Asc: - orderingMethod = o => o.OrderBy(orderExpression); - break; - case SortDirection.Desc: - orderingMethod = o => o.OrderByDescending(orderExpression); - break; - } - return orderingMethod; - } -} \ No newline at end of file From d2f7a40ed9ad5f9d6cced8e1d6a85e54519435c8 Mon Sep 17 00:00:00 2001 From: Vova Zelenyi Date: Mon, 14 Aug 2023 15:01:33 +0200 Subject: [PATCH 15/23] Change GetAllFilmsPaginated now can find by genres ID I did it with help from Internet --- Films/Films.Core/Services/FilmService.cs | 80 ++++++++++++++++++++---- 1 file changed, 67 insertions(+), 13 deletions(-) diff --git a/Films/Films.Core/Services/FilmService.cs b/Films/Films.Core/Services/FilmService.cs index 041d17f..e2fee77 100644 --- a/Films/Films.Core/Services/FilmService.cs +++ b/Films/Films.Core/Services/FilmService.cs @@ -1,9 +1,11 @@ +using System.Linq.Expressions; using System.Transactions; using AutoMapper; using Films.Domain.Models; using Films.DTOs; using Kirel.Repositories.Core.Interfaces; using Kirel.Repositories.Core.Models; +using Kirel.Shared; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -101,7 +103,7 @@ public async Task CreateFilm(FilmCreateDto filmCreateDto) // Сохраняем фильм в базе данных await _filmRepository.Insert(film); } - + /// /// Searching all films which have this genre Id /// @@ -128,7 +130,7 @@ public async Task> GetFilmsByGenreIds(List genreIds) -/// + /// /// Updates an existing film. /// /// The ID of the film to update. @@ -155,24 +157,45 @@ public async Task UpdateFilm(int filmId, FilmUpdateDto filmDto) /// Field by which the results should be ordered. /// Sorting direction (ascending or descending). /// Search term to filter the results. + /// Id of genre which you want to search for /// Paginated result containing a list of FilmDto objects. - public async Task>> GetAllFilmsPaginated(int pageNumber = 0, int pageSize = 0, - string orderBy = "", SortDirection orderDirection = SortDirection.Asc, string search = "") + public async Task>> GetAllFilmsPaginated( + int pageNumber = 0, int pageSize = 0, + string orderBy = "", SortDirection orderDirection = SortDirection.Asc, + string search = "", List? genreIds = null) { - // Get the total count of films in the database based on the search criteria. - var totalCount = await _filmRepository.Count(search); + Expression> expression = null!; + if (!string.IsNullOrWhiteSpace(search)) + { + Expression> searchExpression = PredicateBuilder.PredicateSearchInAllFields(search); + expression = searchExpression; + } + + if (genreIds != null && genreIds.Any()) + { + Expression> genreExpression = f => f.Genres.Any(g => genreIds.Contains(g.Id)); + if (expression == null) + { + expression = genreExpression; + } + else + { + expression = PredicateBuilder.And(expression, genreExpression); + } + } + + var orderByDelegate = GenerateOrderingMethod(orderBy, orderDirection); + var includesDelegate = GenerateIncludes(); + + var totalCount = await _filmRepository.Count(expression); - // Generate pagination information. var pagination = Pagination.Generate(pageNumber, pageSize, totalCount); - // Retrieve a paginated list of films based on the pagination and sorting parameters. - var films = await _filmRepository.GetList(search, orderBy, orderDirection, pagination.CurrentPage, + var films = await _filmRepository.GetList(expression, orderByDelegate, includesDelegate, pagination.CurrentPage, pagination.PageSize); - // Map the retrieved films to DTOs. var filmsDto = _mapper.Map>(films); - // Create and return the paginated result. var result = new PaginatedResult> { Pagination = pagination, @@ -180,7 +203,8 @@ public async Task>> GetAllFilmsPaginated(int pageN }; return result; } - + + /// @@ -198,6 +222,36 @@ public async Task DeleteFilm(int filmId) await _filmRepository.Delete(filmId); } + + private Func, IOrderedQueryable> GenerateOrderingMethod(string orderBy, + SortDirection orderDirection) + { + Func, IOrderedQueryable> orderingMethod = null!; + if (string.IsNullOrEmpty(orderBy)) return orderingMethod!; + var orderExpression = PredicateBuilder.ToLambda(orderBy); + if (orderExpression == null) return orderingMethod!; + switch (orderDirection) + { + case SortDirection.Asc: + orderingMethod = o => o.OrderBy(orderExpression); + break; + case SortDirection.Desc: + orderingMethod = o => o.OrderByDescending(orderExpression); + break; + } + + return orderingMethod!; + } + + // Генерация делегата для включения связанных данных + private Func, IQueryable> GenerateIncludes() + { + Func, IQueryable>? includesDelegate = null; + + return includesDelegate!; + } + + /// /// Custom exception class for indicating that a film was not found. /// @@ -211,4 +265,4 @@ public FilmNotFoundException(string message) : base(message) { } } -} \ No newline at end of file +} From 19c55d84ab34f052fb03055c56612dbbb03c5857 Mon Sep 17 00:00:00 2001 From: Vova Zelenyi Date: Mon, 14 Aug 2023 15:01:59 +0200 Subject: [PATCH 16/23] Update GetAllFilms to match new service --- Films/Films.API/Controllers/FilmController.cs | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/Films/Films.API/Controllers/FilmController.cs b/Films/Films.API/Controllers/FilmController.cs index 4f62eb9..5828c67 100644 --- a/Films/Films.API/Controllers/FilmController.cs +++ b/Films/Films.API/Controllers/FilmController.cs @@ -56,21 +56,33 @@ public async Task>> SearchFilms(string filmName) /// Field by which the results should be ordered. /// Sorting direction (ascending or descending). /// Search term to filter the results. + /// /// Paginated result containing a list of FilmDto objects. [HttpGet("all")] public async Task>>> GetAllFilms( - int pageNumber = 0, int pageSize = 10, - string orderBy = "", SortDirection orderDirection = SortDirection.Asc, string search = "") + [FromQuery] int pageNumber = 1, + [FromQuery] int pageSize = 10, + [FromQuery] string? orderBy = "", + [FromQuery] string orderDirection = "asc", + [FromQuery] string? search = "", + [FromQuery] List? genreIds = null) { - try - { - var films = await _filmService.GetAllFilmsPaginated(pageNumber, pageSize, orderBy, orderDirection, search); - return Ok(films); - } - catch (Exception) - { - return StatusCode(500, "An error occurred while processing your request."); - } + + SortDirection directionEnum; + if (orderDirection == "asc") + { + directionEnum = SortDirection.Asc; + } + else + { + directionEnum = SortDirection.Desc; + } + + var result = await _filmService.GetAllFilmsPaginated( + pageNumber, pageSize, orderBy!, directionEnum, search!, genreIds); + + return Ok(result); + } From 835190e8703b1bf36556f8897937765ff41503e5 Mon Sep 17 00:00:00 2001 From: Vova Zelenyi Date: Mon, 14 Aug 2023 15:02:14 +0200 Subject: [PATCH 17/23] add new packages --- Films/Films.Core/Films.Core.csproj | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Films/Films.Core/Films.Core.csproj b/Films/Films.Core/Films.Core.csproj index 82c6359..ddfdf1c 100644 --- a/Films/Films.Core/Films.Core.csproj +++ b/Films/Films.Core/Films.Core.csproj @@ -9,19 +9,21 @@ - - + + - - - - + + + + + + - + From 557ce675d3eace63ada829e03c8b0b39a6a48975 Mon Sep 17 00:00:00 2001 From: Vova Zelenyi Date: Mon, 14 Aug 2023 15:17:13 +0200 Subject: [PATCH 18/23] Fix bugs that film retrieves without genres --- Films/Films.Core/Services/FilmService.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Films/Films.Core/Services/FilmService.cs b/Films/Films.Core/Services/FilmService.cs index e2fee77..c5630ba 100644 --- a/Films/Films.Core/Services/FilmService.cs +++ b/Films/Films.Core/Services/FilmService.cs @@ -185,7 +185,7 @@ public async Task>> GetAllFilmsPaginated( } var orderByDelegate = GenerateOrderingMethod(orderBy, orderDirection); - var includesDelegate = GenerateIncludes(); + var includesDelegate = GenerateIncludes(); var totalCount = await _filmRepository.Count(expression); @@ -244,10 +244,13 @@ private Func, IOrderedQueryable> GenerateOrderingMe } // Генерация делегата для включения связанных данных - private Func, IQueryable> GenerateIncludes() + private static Func, IQueryable> GenerateIncludes() { - Func, IQueryable>? includesDelegate = null; - + Func, IQueryable> includesDelegate = query => query.Include(f => f.Genres); + + includesDelegate = query => query.Include(f => f.Genres); + // Add other includes for different entity types as needed. + return includesDelegate!; } From 61863a8f31193bfccf9d2a669845ddd1693c7ee7 Mon Sep 17 00:00:00 2001 From: Vova Zelenyi Date: Mon, 14 Aug 2023 15:01:33 +0200 Subject: [PATCH 19/23] Change GetAllFilmsPaginated now can find by genres ID I did it with help from Internet --- Films/Films.Core/Services/FilmService.cs | 80 ++++++++++++++++++++---- 1 file changed, 67 insertions(+), 13 deletions(-) diff --git a/Films/Films.Core/Services/FilmService.cs b/Films/Films.Core/Services/FilmService.cs index 041d17f..e2fee77 100644 --- a/Films/Films.Core/Services/FilmService.cs +++ b/Films/Films.Core/Services/FilmService.cs @@ -1,9 +1,11 @@ +using System.Linq.Expressions; using System.Transactions; using AutoMapper; using Films.Domain.Models; using Films.DTOs; using Kirel.Repositories.Core.Interfaces; using Kirel.Repositories.Core.Models; +using Kirel.Shared; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -101,7 +103,7 @@ public async Task CreateFilm(FilmCreateDto filmCreateDto) // Сохраняем фильм в базе данных await _filmRepository.Insert(film); } - + /// /// Searching all films which have this genre Id /// @@ -128,7 +130,7 @@ public async Task> GetFilmsByGenreIds(List genreIds) -/// + /// /// Updates an existing film. /// /// The ID of the film to update. @@ -155,24 +157,45 @@ public async Task UpdateFilm(int filmId, FilmUpdateDto filmDto) /// Field by which the results should be ordered. /// Sorting direction (ascending or descending). /// Search term to filter the results. + /// Id of genre which you want to search for /// Paginated result containing a list of FilmDto objects. - public async Task>> GetAllFilmsPaginated(int pageNumber = 0, int pageSize = 0, - string orderBy = "", SortDirection orderDirection = SortDirection.Asc, string search = "") + public async Task>> GetAllFilmsPaginated( + int pageNumber = 0, int pageSize = 0, + string orderBy = "", SortDirection orderDirection = SortDirection.Asc, + string search = "", List? genreIds = null) { - // Get the total count of films in the database based on the search criteria. - var totalCount = await _filmRepository.Count(search); + Expression> expression = null!; + if (!string.IsNullOrWhiteSpace(search)) + { + Expression> searchExpression = PredicateBuilder.PredicateSearchInAllFields(search); + expression = searchExpression; + } + + if (genreIds != null && genreIds.Any()) + { + Expression> genreExpression = f => f.Genres.Any(g => genreIds.Contains(g.Id)); + if (expression == null) + { + expression = genreExpression; + } + else + { + expression = PredicateBuilder.And(expression, genreExpression); + } + } + + var orderByDelegate = GenerateOrderingMethod(orderBy, orderDirection); + var includesDelegate = GenerateIncludes(); + + var totalCount = await _filmRepository.Count(expression); - // Generate pagination information. var pagination = Pagination.Generate(pageNumber, pageSize, totalCount); - // Retrieve a paginated list of films based on the pagination and sorting parameters. - var films = await _filmRepository.GetList(search, orderBy, orderDirection, pagination.CurrentPage, + var films = await _filmRepository.GetList(expression, orderByDelegate, includesDelegate, pagination.CurrentPage, pagination.PageSize); - // Map the retrieved films to DTOs. var filmsDto = _mapper.Map>(films); - // Create and return the paginated result. var result = new PaginatedResult> { Pagination = pagination, @@ -180,7 +203,8 @@ public async Task>> GetAllFilmsPaginated(int pageN }; return result; } - + + /// @@ -198,6 +222,36 @@ public async Task DeleteFilm(int filmId) await _filmRepository.Delete(filmId); } + + private Func, IOrderedQueryable> GenerateOrderingMethod(string orderBy, + SortDirection orderDirection) + { + Func, IOrderedQueryable> orderingMethod = null!; + if (string.IsNullOrEmpty(orderBy)) return orderingMethod!; + var orderExpression = PredicateBuilder.ToLambda(orderBy); + if (orderExpression == null) return orderingMethod!; + switch (orderDirection) + { + case SortDirection.Asc: + orderingMethod = o => o.OrderBy(orderExpression); + break; + case SortDirection.Desc: + orderingMethod = o => o.OrderByDescending(orderExpression); + break; + } + + return orderingMethod!; + } + + // Генерация делегата для включения связанных данных + private Func, IQueryable> GenerateIncludes() + { + Func, IQueryable>? includesDelegate = null; + + return includesDelegate!; + } + + /// /// Custom exception class for indicating that a film was not found. /// @@ -211,4 +265,4 @@ public FilmNotFoundException(string message) : base(message) { } } -} \ No newline at end of file +} From bc4bf6e3636d52431574fbbd917fa7bfddc3aab4 Mon Sep 17 00:00:00 2001 From: Vova Zelenyi Date: Mon, 14 Aug 2023 15:01:59 +0200 Subject: [PATCH 20/23] Update GetAllFilms to match new service --- Films/Films.API/Controllers/FilmController.cs | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/Films/Films.API/Controllers/FilmController.cs b/Films/Films.API/Controllers/FilmController.cs index 4f62eb9..5828c67 100644 --- a/Films/Films.API/Controllers/FilmController.cs +++ b/Films/Films.API/Controllers/FilmController.cs @@ -56,21 +56,33 @@ public async Task>> SearchFilms(string filmName) /// Field by which the results should be ordered. /// Sorting direction (ascending or descending). /// Search term to filter the results. + /// /// Paginated result containing a list of FilmDto objects. [HttpGet("all")] public async Task>>> GetAllFilms( - int pageNumber = 0, int pageSize = 10, - string orderBy = "", SortDirection orderDirection = SortDirection.Asc, string search = "") + [FromQuery] int pageNumber = 1, + [FromQuery] int pageSize = 10, + [FromQuery] string? orderBy = "", + [FromQuery] string orderDirection = "asc", + [FromQuery] string? search = "", + [FromQuery] List? genreIds = null) { - try - { - var films = await _filmService.GetAllFilmsPaginated(pageNumber, pageSize, orderBy, orderDirection, search); - return Ok(films); - } - catch (Exception) - { - return StatusCode(500, "An error occurred while processing your request."); - } + + SortDirection directionEnum; + if (orderDirection == "asc") + { + directionEnum = SortDirection.Asc; + } + else + { + directionEnum = SortDirection.Desc; + } + + var result = await _filmService.GetAllFilmsPaginated( + pageNumber, pageSize, orderBy!, directionEnum, search!, genreIds); + + return Ok(result); + } From cf5021c6424603b8aad1aeb38e25b2ab92a0bae9 Mon Sep 17 00:00:00 2001 From: Vova Zelenyi Date: Mon, 14 Aug 2023 15:02:14 +0200 Subject: [PATCH 21/23] add new packages --- Films/Films.Core/Films.Core.csproj | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Films/Films.Core/Films.Core.csproj b/Films/Films.Core/Films.Core.csproj index 82c6359..ddfdf1c 100644 --- a/Films/Films.Core/Films.Core.csproj +++ b/Films/Films.Core/Films.Core.csproj @@ -9,19 +9,21 @@ - - + + - - - - + + + + + + - + From 3cc7cde9a5056fb74970ff6ae38dfb3a62bca7b4 Mon Sep 17 00:00:00 2001 From: Vova Zelenyi Date: Mon, 14 Aug 2023 15:17:13 +0200 Subject: [PATCH 22/23] Fix bugs that film retrieves without genres --- Films/Films.Core/Services/FilmService.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Films/Films.Core/Services/FilmService.cs b/Films/Films.Core/Services/FilmService.cs index e2fee77..c5630ba 100644 --- a/Films/Films.Core/Services/FilmService.cs +++ b/Films/Films.Core/Services/FilmService.cs @@ -185,7 +185,7 @@ public async Task>> GetAllFilmsPaginated( } var orderByDelegate = GenerateOrderingMethod(orderBy, orderDirection); - var includesDelegate = GenerateIncludes(); + var includesDelegate = GenerateIncludes(); var totalCount = await _filmRepository.Count(expression); @@ -244,10 +244,13 @@ private Func, IOrderedQueryable> GenerateOrderingMe } // Генерация делегата для включения связанных данных - private Func, IQueryable> GenerateIncludes() + private static Func, IQueryable> GenerateIncludes() { - Func, IQueryable>? includesDelegate = null; - + Func, IQueryable> includesDelegate = query => query.Include(f => f.Genres); + + includesDelegate = query => query.Include(f => f.Genres); + // Add other includes for different entity types as needed. + return includesDelegate!; } From ca87f787272c4da5125521344bc60d8eaf54a3b5 Mon Sep 17 00:00:00 2001 From: Vova Zelenyi Date: Mon, 14 Aug 2023 19:01:58 +0200 Subject: [PATCH 23/23] Revert "add new packages" This reverts commit cf5021c6424603b8aad1aeb38e25b2ab92a0bae9. --- Films/Films.Core/Films.Core.csproj | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Films/Films.Core/Films.Core.csproj b/Films/Films.Core/Films.Core.csproj index ddfdf1c..82c6359 100644 --- a/Films/Films.Core/Films.Core.csproj +++ b/Films/Films.Core/Films.Core.csproj @@ -9,21 +9,19 @@ - - + + - - - - - - + + + + - +