first commit

This commit is contained in:
2026-03-09 22:09:10 +01:00
commit 7b6b294b0f
269 changed files with 10338 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RiderProjectSettingsUpdater">
<option name="singleClickDiffPreview" value="1" />
<option name="unhandledExceptionsIgnoreList" value="1" />
<option name="vcsConfiguration" value="3" />
</component>
</project>

112
.idea/.idea.BookHive/.idea/workspace.xml generated Normal file
View File

@@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoGeneratedRunConfigurationManager">
<projectFile profileName="http">BookHive/BookHive.csproj</projectFile>
<projectFile profileName="https">BookHive/BookHive.csproj</projectFile>
</component>
<component name="ChangeListManager">
<list default="true" id="4c0ad985-5b6e-4587-85a5-1b48273bdb1d" name="Changes" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="DpaMonitoringSettings">
<option name="firstShow" value="false" />
</component>
<component name="HighlightingSettingsPerFile">
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/93684fd365e5462ea2f83c3be1bf1d6149908/6c/02001336/HttpServiceCollectionExtensions.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/5023b5be698d783ffe9f42b2e944a85a7a66b61bc5e19c76c591036343fd16/RepositoryBaseOfT.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/BookHive/Endpoints/Books/CreateBookEndpoint.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/BookHive/Endpoints/Loans/CreateLoanEndpoint.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/BookHive/Endpoints/Members/CreateMemberEndpoint.cs" root0="FORCE_HIGHLIGHTING" />
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 5
}</component>
<component name="ProjectId" id="3Aiuj7MQNTWLiWp2KXQdFXkcvkz" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
".NET Launch Settings Profile.BookHive: http.executor": "Run",
"ModuleVcsDetector.initialDetectionPerformed": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
"vue.rearranger.settings.migration": "true"
}
}]]></component>
<component name="RunManager" selected=".NET Launch Settings Profile.BookHive: http">
<configuration name="BookHive: http" type="LaunchSettings" factoryName=".NET Launch Settings Profile">
<option name="LAUNCH_PROFILE_PROJECT_FILE_PATH" value="$PROJECT_DIR$/BookHive/BookHive.csproj" />
<option name="LAUNCH_PROFILE_TFM" value="net10.0" />
<option name="LAUNCH_PROFILE_NAME" value="http" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="GENERATE_APPLICATIONHOST_CONFIG" value="1" />
<option name="SHOW_IIS_EXPRESS_OUTPUT" value="0" />
<option name="SEND_DEBUG_REQUEST" value="1" />
<option name="ADDITIONAL_IIS_EXPRESS_ARGUMENTS" value="" />
<option name="AUTO_ATTACH_CHILDREN" value="0" />
<option name="MIXED_MODE_DEBUG" value="0" />
<method v="2">
<option name="Build" />
</method>
</configuration>
<configuration name="BookHive: https" type="LaunchSettings" factoryName=".NET Launch Settings Profile">
<option name="LAUNCH_PROFILE_PROJECT_FILE_PATH" value="$PROJECT_DIR$/BookHive/BookHive.csproj" />
<option name="LAUNCH_PROFILE_TFM" value="net10.0" />
<option name="LAUNCH_PROFILE_NAME" value="https" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="GENERATE_APPLICATIONHOST_CONFIG" value="1" />
<option name="SHOW_IIS_EXPRESS_OUTPUT" value="0" />
<option name="SEND_DEBUG_REQUEST" value="1" />
<option name="ADDITIONAL_IIS_EXPRESS_ARGUMENTS" value="" />
<option name="AUTO_ATTACH_CHILDREN" value="0" />
<option name="MIXED_MODE_DEBUG" value="0" />
<method v="2">
<option name="Build" />
</method>
</configuration>
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="4c0ad985-5b6e-4587-85a5-1b48273bdb1d" name="Changes" comment="" />
<created>1773087844958</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1773087844958</updated>
<workItem from="1773087846465" duration="527000" />
<workItem from="1773088517368" duration="1882000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="UnityProjectConfiguration" hasMinimizedUI="false" />
<component name="VcsManagerConfiguration">
<option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
<breakpoints>
<breakpoint enabled="true" type="DotNet_Exception_Breakpoints">
<properties exception="System.OperationCanceledException" breakIfHandledByOtherCode="false" displayValue="System.OperationCanceledException" />
<option name="timeStamp" value="1" />
</breakpoint>
<breakpoint enabled="true" type="DotNet_Exception_Breakpoints">
<properties exception="System.Threading.Tasks.TaskCanceledException" breakIfHandledByOtherCode="false" displayValue="System.Threading.Tasks.TaskCanceledException" />
<option name="timeStamp" value="2" />
</breakpoint>
<breakpoint enabled="true" type="DotNet_Exception_Breakpoints">
<properties exception="System.Threading.ThreadAbortException" breakIfHandledByOtherCode="false" displayValue="System.Threading.ThreadAbortException" />
<option name="timeStamp" value="3" />
</breakpoint>
</breakpoints>
</breakpoint-manager>
</component>
</project>

16
BookHive.sln Normal file
View File

@@ -0,0 +1,16 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BookHive", "BookHive\BookHive.csproj", "{0897F140-D03F-43C0-BAB2-C5C7F6BAA2D2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0897F140-D03F-43C0-BAB2-C5C7F6BAA2D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0897F140-D03F-43C0-BAB2-C5C7F6BAA2D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0897F140-D03F-43C0-BAB2-C5C7F6BAA2D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0897F140-D03F-43C0-BAB2-C5C7F6BAA2D2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AHttpServiceCollectionExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F93684fd365e5462ea2f83c3be1bf1d6149908_003F6c_003F02001336_003FHttpServiceCollectionExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARepositoryBaseOfT_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F5023b5be698d783ffe9f42b2e944a85a7a66b61bc5e19c76c591036343fd16_003FRepositoryBaseOfT_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>

191
BookHive/.gitignore vendored Normal file
View File

@@ -0,0 +1,191 @@
# Created by https://www.toptal.com/developers/gitignore/api/jetbrains,linux,windows,macos
# Edit at https://www.toptal.com/developers/gitignore?templates=jetbrains,linux,windows,macos
### JetBrains ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### JetBrains Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
# *.iml
# modules.xml
# .idea/misc.xml
# *.ipr
# Sonarlint plugin
# https://plugins.jetbrains.com/plugin/7973-sonarlint
.idea/**/sonarlint/
# SonarQube Plugin
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
.idea/**/sonarIssues.xml
# Markdown Navigator plugin
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
.idea/**/markdown-navigator.xml
.idea/**/markdown-navigator-enh.xml
.idea/**/markdown-navigator/
# Cache file creation bug
# See https://youtrack.jetbrains.com/issue/JBR-2257
.idea/$CACHE_FILE$
# CodeStream plugin
# https://plugins.jetbrains.com/plugin/12206-codestream
.idea/codestream.xml
# Azure Toolkit for IntelliJ plugin
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
.idea/**/azureSettings.xml
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.toptal.com/developers/gitignore/api/jetbrains,linux,windows,macos

26
BookHive/BookHive.csproj Normal file
View File

@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Ardalis.Specification.EntityFrameworkCore" Version="9.3.1" />
<PackageReference Include="AutoMapper" Version="16.1.0" />
<PackageReference Include="AutoMapper.Collection" Version="13.0.0" />
<PackageReference Include="FastEndpoints" Version="8.0.1" />
<PackageReference Include="FastEndpoints.Security" Version="8.0.1" />
<PackageReference Include="FastEndpoints.Swagger" Version="8.0.1" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.3"/>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.3" />
<PackageReference Include="Plainquire.Page" Version="7.1.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,274 @@
using BookHive.Models;
using Microsoft.EntityFrameworkCore;
namespace BookHive;
public class BookHiveDbContext : DbContext
{
public DbSet<Author> Authors { get; set; }
public DbSet<Book> Books { get; set; }
public DbSet<Member> Members { get; set; }
public DbSet<Loan> Loans { get; set; }
public DbSet<Review> Reviews { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
string connectionString =
"Server=romaric-thibault.fr;" + // Serveur SQL
"Database=mathys_BookHive;" + // Nom de la base
"User Id=mathys;" + // Utilisateur
"Password=Onto9-Cage-Afflicted;" + // Mot de passe
"TrustServerCertificate=true;"; // Accepte certificat auto-signé
optionsBuilder.UseSqlServer(connectionString);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Review>()
.HasIndex(x => new { x.BookId, x.MemberId })
.IsUnique();
// -------------------
// AUTHORS
// -------------------
modelBuilder.Entity<Author>().HasData(
new Author
{
Id = 1,
FirstName = "George",
LastName = "Orwell",
Biography = "Auteur britannique connu pour ses romans dystopiques.",
BirthDate = new DateOnly(1903, 6, 25),
Nationality = "Britannique"
},
new Author
{
Id = 2,
FirstName = "Jane",
LastName = "Austen",
Biography = "Romancière anglaise célèbre pour ses romans sur la société.",
BirthDate = new DateOnly(1775, 12, 16),
Nationality = "Britannique"
},
new Author
{
Id = 3,
FirstName = "Victor",
LastName = "Hugo",
Biography = "Poète et écrivain français du XIXe siècle.",
BirthDate = new DateOnly(1802, 2, 26),
Nationality = "Française"
}
);
// -------------------
// BOOKS
// -------------------
modelBuilder.Entity<Book>().HasData(
new Book
{
Id = 1,
Title = "1984",
Isbn = "9780451524935",
Summary = "Roman dystopique sur un régime totalitaire.",
PageCount = 328,
PublishedDate = new DateOnly(1949, 6, 8),
Genre = "Dystopie",
AuthorId = 1
},
new Book
{
Id = 2,
Title = "Animal Farm",
Isbn = "9780451526342",
Summary = "Satire politique sous forme de fable animale.",
PageCount = 112,
PublishedDate = new DateOnly(1945, 8, 17),
Genre = "Satire",
AuthorId = 1
},
new Book
{
Id = 3,
Title = "Pride and Prejudice",
Isbn = "9780141439518",
Summary = "Histoire romantique dans l'Angleterre du XIXe siècle.",
PageCount = 279,
PublishedDate = new DateOnly(1813, 1, 28),
Genre = "Roman",
AuthorId = 2
},
new Book
{
Id = 4,
Title = "Sense and Sensibility",
Isbn = "9780141439662",
Summary = "Roman sur les sœurs Dashwood.",
PageCount = 226,
PublishedDate = new DateOnly(1811, 10, 30),
Genre = "Roman",
AuthorId = 2
},
new Book
{
Id = 5,
Title = "Les Misérables",
Isbn = "9782070409189",
Summary = "Grande fresque sociale sur la misère et la justice.",
PageCount = 1463,
PublishedDate = new DateOnly(1862, 4, 3),
Genre = "Roman historique",
AuthorId = 3
},
new Book
{
Id = 6,
Title = "Notre-Dame de Paris",
Isbn = "9782253004226",
Summary = "Roman historique se déroulant à Paris.",
PageCount = 940,
PublishedDate = new DateOnly(1831, 1, 14),
Genre = "Roman historique",
AuthorId = 3
}
);
// -------------------
// MEMBERS
// -------------------
modelBuilder.Entity<Member>().HasData(
new Member
{
Id = 1,
Email = "alice@example.com",
FirstName = "Alice",
LastName = "Martin",
MembershipDate = new DateOnly(2023, 1, 10),
IsActive = true
},
new Member
{
Id = 2,
Email = "bob@example.com",
FirstName = "Bob",
LastName = "Durand",
MembershipDate = new DateOnly(2023, 5, 12),
IsActive = true
},
new Member
{
Id = 3,
Email = "claire@example.com",
FirstName = "Claire",
LastName = "Petit",
MembershipDate = new DateOnly(2024, 2, 2),
IsActive = true
},
new Member
{
Id = 4,
Email = "david@example.com",
FirstName = "David",
LastName = "Bernard",
MembershipDate = new DateOnly(2024, 6, 15),
IsActive = true
}
);
// -------------------
// LOANS (5 dont 2 en cours)
// -------------------
modelBuilder.Entity<Loan>().HasData(
new Loan
{
Id = 1,
BookId = 1,
MemberId = 1,
LoanDate = new DateOnly(2025, 12, 1),
DueDate = new DateOnly(2025, 12, 15),
ReturnDate = new DateOnly(2025, 12, 10)
},
new Loan
{
Id = 2,
BookId = 2,
MemberId = 2,
LoanDate = new DateOnly(2025, 12, 5),
DueDate = new DateOnly(2025, 12, 20),
ReturnDate = new DateOnly(2025, 12, 18)
},
new Loan
{
Id = 3,
BookId = 3,
MemberId = 3,
LoanDate = new DateOnly(2025, 12, 10),
DueDate = new DateOnly(2025, 12, 25),
ReturnDate = null // en cours
},
new Loan
{
Id = 4,
BookId = 4,
MemberId = 1,
LoanDate = new DateOnly(2025, 12, 12),
DueDate = new DateOnly(2025, 12, 27),
ReturnDate = null // en cours
},
new Loan
{
Id = 5,
BookId = 5,
MemberId = 2,
LoanDate = new DateOnly(2025, 11, 1),
DueDate = new DateOnly(2025, 11, 15),
ReturnDate = new DateOnly(2025, 11, 14)
}
);
// -------------------
// REVIEWS
// -------------------
modelBuilder.Entity<Review>().HasData(
new Review
{
Id = 1,
BookId = 1,
MemberId = 1,
Rating = 5,
Comment = "Un chef-d'œuvre dystopique.",
CreatedAt = new DateTime(2025, 12, 1)
},
new Review
{
Id = 2,
BookId = 3,
MemberId = 2,
Rating = 4,
Comment = "Très bon roman classique.",
CreatedAt = new DateTime(2025, 12, 2)
},
new Review
{
Id = 3,
BookId = 5,
MemberId = 3,
Rating = 5,
Comment = "Incroyable roman historique.",
CreatedAt = new DateTime(2025, 12, 3)
},
new Review
{
Id = 4,
BookId = 2,
MemberId = 4,
Rating = 4,
Comment = "Drôle et intelligent.",
CreatedAt = new DateTime(2025, 12, 4)
}
);
}
}

View File

@@ -0,0 +1,10 @@
namespace BookHive.DTO.Author;
public class CreateAuthorDto
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? Biography { get; set; }
public DateOnly BirthDate { get; set; }
public string? Nationality { get; set; }
}

View File

@@ -0,0 +1,15 @@
using BookHive.DTO.Book;
namespace BookHive.DTO.Author;
public class GetAuthorDetailsDto
{
public int Id { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? Biography { get; set; }
public DateOnly BirthDate { get; set; }
public string? Nationality { get; set; }
public List<GetBookDto>? Books { get; set; }
}

View File

@@ -0,0 +1,8 @@
namespace BookHive.DTO.Author;
public class GetAuthorDto
{
public int Id { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
}

View File

@@ -0,0 +1,11 @@
namespace BookHive.DTO.Author;
public class UpdateAuthorDto
{
public int Id { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? Biography { get; set; }
public DateOnly BirthDate { get; set; }
public string? Nationality { get; set; }
}

View File

@@ -0,0 +1,13 @@
namespace BookHive.DTO.Book;
public class CreateBookDto
{
public string? Title { get; set; }
public string? Isbn { get; set; }
public string? Summary { get; set; }
public int PageCount { get; set; }
public DateOnly PublishedDate { get; set; }
public string? Genre { get; set; }
public int AuthorId { get; set; }
}

View File

@@ -0,0 +1,20 @@
using BookHive.DTO.Author;
using BookHive.DTO.Review;
namespace BookHive.DTO.Book;
public class GetBookDetailsDto
{
public int Id { get; set; }
public string? Title { get; set; }
public string? Isbn { get; set; }
public string? Summary { get; set; }
public int PageCount { get; set; }
public DateOnly PublishedDate { get; set; }
public string? Genre { get; set; }
public int AuthorId { get; set; }
public GetAuthorDto? Author { get; set; }
public List<GetReviewDto>? Reviews { get; set; }
}

View File

@@ -0,0 +1,9 @@
namespace BookHive.DTO.Book;
public class GetBookDto
{
public int Id { get; set; }
public string? Title { get; set; }
public DateOnly PublishedDate { get; set; }
public string? Genre { get; set; }
}

View File

@@ -0,0 +1,14 @@
namespace BookHive.DTO.Book;
public class UpdateBookDto
{
public int Id { get; set; }
public string? Title { get; set; }
public string? Isbn { get; set; }
public string? Summary { get; set; }
public int PageCount { get; set; }
public DateOnly PublishedDate { get; set; }
public string? Genre { get; set; }
public int AuthorId { get; set; }
}

View File

@@ -0,0 +1,10 @@
namespace BookHive.DTO.Loan;
public class CreateLoanDto
{
public int BookId { get; set; }
public int MemberId { get; set; }
public DateOnly LoanDate { get; set; }
public DateOnly DueDate { get; set; }
}

View File

@@ -0,0 +1,19 @@
using BookHive.DTO.Book;
using BookHive.DTO.Member;
namespace BookHive.DTO.Loan;
public class GetLoanDto
{
public int Id { get; set; }
public int BookId { get; set; }
public int MemberId { get; set; }
public DateOnly LoanDate { get; set; }
public DateOnly DueDate { get; set; }
public DateOnly? ReturnDate { get; set; }
public GetBookDto? Book { get; set; }
public GetMemberDto? Member { get; set; }
}

View File

@@ -0,0 +1,13 @@
namespace BookHive.DTO.Loan;
public class UpdateLoanDto
{
public int Id { get; set; }
public int BookId { get; set; }
public int MemberId { get; set; }
public DateOnly LoanDate { get; set; }
public DateOnly DueDate { get; set; }
public DateOnly? ReturnDate { get; set; }
}

View File

@@ -0,0 +1,10 @@
namespace BookHive.DTO.Member;
public class CreateMemberDto
{
public string? Email { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
public DateOnly MembershipDate { get; set; }
}

View File

@@ -0,0 +1,17 @@
using BookHive.DTO.Loan;
using BookHive.DTO.Review;
namespace BookHive.DTO.Member;
public class GetMemberDetailsDto
{
public int Id { get; set; }
public string? Email { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
public DateOnly MembershipDate { get; set; }
public bool IsActive { get; set; }
public List<GetLoanDto>? Loans { get; set; }
public List<GetReviewDto>? Reviews { get; set; }
}

View File

@@ -0,0 +1,10 @@
namespace BookHive.DTO.Member;
public class GetMemberDto
{
public int Id { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
public DateOnly MembershipDate { get; set; }
public bool IsActive { get; set; }
}

View File

@@ -0,0 +1,11 @@
namespace BookHive.DTO.Member;
public class UpdateMemberDto
{
public int Id { get; set; }
public string? Email { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
public DateOnly MembershipDate { get; set; }
public bool IsActive { get; set; }
}

View File

@@ -0,0 +1,11 @@
namespace BookHive.DTO.Review;
public class CreateReviewDto
{
public int BookId { get; set; }
public int MemberId { get; set; }
public int Rating { get; set; }
public string? Comment { get; set; }
}

View File

@@ -0,0 +1,19 @@
using BookHive.DTO.Book;
using BookHive.DTO.Member;
namespace BookHive.DTO.Review;
public class GetReviewDto
{
public int Id { get; set; }
public int BookId { get; set; }
public int MemberId { get; set; }
public int Rating { get; set; }
public string? Comment { get; set; }
public DateTime CreatedAt { get; set; }
public GetBookDto? Book { get; set; }
public GetMemberDto? Member { get; set; }
}

View File

@@ -0,0 +1,12 @@
namespace BookHive.DTO.Review;
public class UpdateReviewDto
{
public int Id { get; set; }
public int BookId { get; set; }
public int MemberId { get; set; }
public int Rating { get; set; }
public string? Comment { get; set; }
}

View File

@@ -0,0 +1,21 @@
using BookHive.DTO.Author;
using BookHive.Models;
using BookHive.Repositories;
using FastEndpoints;
namespace BookHive.Endpoints.Authors;
public class CreateAuthorEndpoint(AuthorRepository authorRepository, AutoMapper.IMapper mapper) : Endpoint<CreateAuthorDto>
{
public override void Configure()
{
Post("/authors/");
AllowAnonymous();
}
public override async Task HandleAsync(CreateAuthorDto req, CancellationToken ct)
{
await authorRepository.AddAsync(mapper.Map<Author>(req), ct);
await Send.OkAsync(cancellation: ct);
}
}

View File

@@ -0,0 +1,34 @@
using BookHive.Models;
using BookHive.Repositories;
using BookHive.Specifications.Authors;
using FastEndpoints;
namespace BookHive.Endpoints.Authors;
public class DeleteAuthorRequest
{
public int Id { get; set; }
}
public class DeleteAuthorEndpoint(AuthorRepository authorRepository) : Endpoint<DeleteAuthorRequest>
{
public override void Configure()
{
Delete("/authors/{@Id}", x => new { x.Id });
AllowAnonymous();
}
public override async Task HandleAsync(DeleteAuthorRequest req, CancellationToken ct)
{
Author? author = await authorRepository.SingleOrDefaultAsync(new GetAuthorByIdSpec(req.Id), ct);
if (author is null)
{
await Send.NotFoundAsync(ct);
return;
}
await authorRepository.DeleteAsync(author, ct);
await Send.OkAsync(cancellation: ct);
}
}

View File

@@ -0,0 +1,34 @@
using BookHive.DTO.Author;
using BookHive.Models;
using BookHive.Repositories;
using BookHive.Specifications.Authors;
using FastEndpoints;
namespace BookHive.Endpoints.Authors;
public class AuthorRequest
{
public int Id { get; set; }
}
public class GetAuthorEndpoint(AuthorRepository authorRepository, AutoMapper.IMapper mapper) : Endpoint<AuthorRequest, GetAuthorDetailsDto>
{
public override void Configure()
{
Get("/authors/{@Id}/", x => new { x.Id });
AllowAnonymous();
}
public override async Task HandleAsync(AuthorRequest req, CancellationToken ct)
{
Author? author = await authorRepository.SingleOrDefaultAsync(new GetAuthorByIdSpec(req.Id), ct);
if (author is null)
{
await Send.NoContentAsync(ct);
return;
}
await Send.OkAsync(mapper.Map<GetAuthorDetailsDto>(author), ct);
}
}

View File

@@ -0,0 +1,19 @@
using BookHive.DTO.Author;
using BookHive.Repositories;
using FastEndpoints;
namespace BookHive.Endpoints.Authors;
public class GetAuthorsEndpoint(AuthorRepository authorRepository) : EndpointWithoutRequest<List<GetAuthorDto>>
{
public override void Configure()
{
Get("/authors/");
AllowAnonymous();
}
public override async Task HandleAsync(CancellationToken ct)
{
await Send.OkAsync(await authorRepository.ProjectToListAsync<GetAuthorDto>(ct), ct);
}
}

View File

@@ -0,0 +1,30 @@
using BookHive.DTO.Author;
using BookHive.Models;
using BookHive.Repositories;
using BookHive.Specifications.Authors;
using FastEndpoints;
namespace BookHive.Endpoints.Authors;
public class UpdateAuthorEndpoint(AuthorRepository authorRepository, AutoMapper.IMapper mapper) : Endpoint<UpdateAuthorDto>
{
public override void Configure()
{
Put("/authors/{@Id}/", x => new { x.Id });
AllowAnonymous();
}
public override async Task HandleAsync(UpdateAuthorDto req, CancellationToken ct)
{
Author? author = await authorRepository.SingleOrDefaultAsync(new GetAuthorByIdSpec(req.Id), ct);
if (author is null)
{
await Send.NotFoundAsync(ct);
return;
}
mapper.Map(req, author);
await authorRepository.SaveChangesAsync(ct);
await Send.OkAsync(cancellation: ct);
}
}

View File

@@ -0,0 +1,22 @@
using BookHive.DTO.Book;
using BookHive.Models;
using BookHive.Repositories;
using FastEndpoints;
namespace BookHive.Endpoints.Books;
public class CreateBookEndpoint(BookRepository bookRepository, AutoMapper.IMapper mapper) : Endpoint<CreateBookDto>
{
public override void Configure()
{
Post("/books/");
AllowAnonymous();
}
public override async Task HandleAsync(CreateBookDto req, CancellationToken ct)
{
await bookRepository.AddAsync(mapper.Map<Book>(req), ct);
await Send.OkAsync(cancellation: ct);
}
}

View File

@@ -0,0 +1,34 @@
using BookHive.Models;
using BookHive.Repositories;
using BookHive.Specifications.Books;
using FastEndpoints;
namespace BookHive.Endpoints.Books;
public class DeleteBookRequest
{
public int Id { get; set; }
}
public class DeleteBookEndpoint(BookRepository bookRepository) : Endpoint<DeleteBookRequest>
{
public override void Configure()
{
Delete("/books/{@Id}", x => new { x.Id });
AllowAnonymous();
}
public override async Task HandleAsync(DeleteBookRequest req, CancellationToken ct)
{
Book? book = await bookRepository.SingleOrDefaultAsync(new GetBookByIdSpec(req.Id), ct);
if (book is null)
{
await Send.NotFoundAsync(ct);
return;
}
await bookRepository.DeleteAsync(book, ct);
await Send.OkAsync(cancellation: ct);
}
}

View File

@@ -0,0 +1,34 @@
using BookHive.DTO.Book;
using BookHive.Models;
using BookHive.Repositories;
using BookHive.Specifications.Books;
using FastEndpoints;
namespace BookHive.Endpoints.Books;
public class BookRequest
{
public int Id { get; set; }
}
public class GetBookEndpoint(BookRepository bookRepository, AutoMapper.IMapper mapper) : Endpoint<BookRequest, GetBookDetailsDto>
{
public override void Configure()
{
Get("/books/{@Id}/", x => new { x.Id });
AllowAnonymous();
}
public override async Task HandleAsync(BookRequest req, CancellationToken ct)
{
Book? book = await bookRepository.SingleOrDefaultAsync(new GetBookByIdSpec(req.Id), ct);
if (book is null)
{
await Send.NoContentAsync(ct);
return;
}
await Send.OkAsync(mapper.Map<GetBookDetailsDto>(book), ct);
}
}

View File

@@ -0,0 +1,19 @@
using BookHive.DTO.Book;
using BookHive.Repositories;
using FastEndpoints;
namespace BookHive.Endpoints.Books;
public class GetBooksEndpoint(BookRepository bookRepository) : EndpointWithoutRequest<List<GetBookDto>>
{
public override void Configure()
{
Get("/books/");
AllowAnonymous();
}
public override async Task HandleAsync(CancellationToken ct)
{
await Send.OkAsync(await bookRepository.ProjectToListAsync<GetBookDto>(ct), ct);
}
}

View File

@@ -0,0 +1,21 @@
using BookHive.DTO.Loan;
using BookHive.Models;
using BookHive.Repositories;
using FastEndpoints;
namespace BookHive.Endpoints.Loans;
public class CreateLoanEndpoint(LoanRepository loanRepository, AutoMapper.IMapper mapper) : Endpoint<CreateLoanDto>
{
public override void Configure()
{
Post("/loans/");
AllowAnonymous();
}
public override async Task HandleAsync(CreateLoanDto req, CancellationToken ct)
{
await loanRepository.AddAsync(mapper.Map<Loan>(req), ct);
await Send.OkAsync(cancellation: ct);
}
}

View File

@@ -0,0 +1,19 @@
using BookHive.DTO.Loan;
using BookHive.Repositories;
using FastEndpoints;
namespace BookHive.Endpoints.Loans;
public class GetLoansEndpoint(LoanRepository loanRepository) : EndpointWithoutRequest<List<GetLoanDto>>
{
public override void Configure()
{
Get("/loans/");
AllowAnonymous();
}
public override async Task HandleAsync(CancellationToken ct)
{
await Send.OkAsync(await loanRepository.ProjectToListAsync<GetLoanDto>(ct), ct);
}
}

View File

@@ -0,0 +1,21 @@
using BookHive.DTO.Member;
using BookHive.Models;
using BookHive.Repositories;
using FastEndpoints;
namespace BookHive.Endpoints.Members;
public class CreateMemberEndpoint(MemberRepository memberRepository, AutoMapper.IMapper mapper) : Endpoint<CreateMemberDto>
{
public override void Configure()
{
Post("/members/");
AllowAnonymous();
}
public override async Task HandleAsync(CreateMemberDto req, CancellationToken ct)
{
await memberRepository.AddAsync(mapper.Map<Member>(req), ct);
await Send.OkAsync(cancellation: ct);
}
}

View File

@@ -0,0 +1,34 @@
using BookHive.DTO.Member;
using BookHive.Models;
using BookHive.Repositories;
using BookHive.Specifications.Members;
using FastEndpoints;
namespace BookHive.Endpoints.Members;
public class MemberRequest
{
public int Id { get; set; }
}
public class GetMemberEndpoint(MemberRepository memberRepository, AutoMapper.IMapper mapper) : Endpoint<MemberRequest, GetMemberDetailsDto>
{
public override void Configure()
{
Get("/members/{@Id}/", x => new { x.Id });
AllowAnonymous();
}
public override async Task HandleAsync(MemberRequest req, CancellationToken ct)
{
Member? member = await memberRepository.SingleOrDefaultAsync(new GetMemberByIdSpec(req.Id), ct);
if (member is null)
{
await Send.NoContentAsync(ct);
return;
}
await Send.OkAsync(mapper.Map<GetMemberDetailsDto>(member), ct);
}
}

View File

@@ -0,0 +1,19 @@
using BookHive.DTO.Member;
using BookHive.Repositories;
using FastEndpoints;
namespace BookHive.Endpoints.Members;
public class GetMembersEndpoint(MemberRepository memberRepository) : EndpointWithoutRequest<List<GetMemberDto>>
{
public override void Configure()
{
Get("/members/");
AllowAnonymous();
}
public override async Task HandleAsync(CancellationToken ct)
{
await Send.OkAsync(await memberRepository.ProjectToListAsync<GetMemberDto>(ct), ct);
}
}

View File

@@ -0,0 +1,34 @@
using BookHive.Models;
using BookHive.Repositories;
using BookHive.Specifications.Reviews;
using FastEndpoints;
namespace BookHive.Endpoints.Reviews;
public class DeleteReviewsRequest
{
public int Id { get; set; }
}
public class DeleteReviewEndpoint(ReviewRepository reviewRepository) : Endpoint<DeleteReviewsRequest>
{
public override void Configure()
{
Delete("/reviews/{@Id}", x => new { x.Id });
AllowAnonymous();
}
public override async Task HandleAsync(DeleteReviewsRequest req, CancellationToken ct)
{
Review? review = await reviewRepository.SingleOrDefaultAsync(new GetReviewByIdSpec(req.Id), ct);
if (review is null)
{
await Send.NotFoundAsync(ct);
return;
}
await reviewRepository.DeleteAsync(review, ct);
await Send.OkAsync(cancellation: ct);
}
}

View File

@@ -0,0 +1,35 @@
using BookHive.DTO.Review;
using BookHive.Models;
using BookHive.Repositories;
using BookHive.Specifications.Books;
using BookHive.Specifications.Reviews;
using FastEndpoints;
namespace BookHive.Endpoints.Reviews;
public class ReviewRequest
{
public int BookId { get; set; }
}
public class GetReviewEndpoint(BookRepository bookRepository, ReviewRepository reviewRepository) : Endpoint<ReviewRequest, List<GetReviewDto>>
{
public override void Configure()
{
Get("/books/{@BookId}/reviews", x => new { x.BookId });
AllowAnonymous();
}
public override async Task HandleAsync(ReviewRequest req, CancellationToken ct)
{
Book? book = await bookRepository.SingleOrDefaultAsync(new GetBookByIdSpec(req.BookId), ct);
if (book is null)
{
await Send.NoContentAsync(ct);
return;
}
await Send.OkAsync(await reviewRepository.ProjectToListAsync<GetReviewDto>(new GetReviewByBookIdSpec(req.BookId), ct), ct);
}
}

View File

@@ -0,0 +1,30 @@
using AutoMapper;
using BookHive.DTO.Author;
using BookHive.DTO.Book;
using BookHive.DTO.Loan;
using BookHive.DTO.Member;
using BookHive.DTO.Review;
using BookHive.Models;
namespace BookHive.MappingProfiles;
public class DtoToEntityMappings : Profile
{
public DtoToEntityMappings()
{
CreateMap<CreateBookDto, Book>();
CreateMap<UpdateBookDto, Book>();
CreateMap<CreateAuthorDto, Author>();
CreateMap<UpdateAuthorDto, Author>();
CreateMap<CreateMemberDto, Member>();
CreateMap<UpdateMemberDto, Member>();
CreateMap<CreateReviewDto, Review>();
CreateMap<UpdateReviewDto, Review>();
CreateMap<CreateLoanDto, Loan>();
CreateMap<UpdateLoanDto, Loan>();
}
}

View File

@@ -0,0 +1,28 @@
using AutoMapper;
using BookHive.DTO.Author;
using BookHive.DTO.Book;
using BookHive.DTO.Loan;
using BookHive.DTO.Member;
using BookHive.DTO.Review;
using BookHive.Models;
namespace BookHive.MappingProfiles;
public class EntityToDtoMappings : Profile
{
public EntityToDtoMappings()
{
CreateMap<Book, GetBookDto>();
CreateMap<Book, GetBookDetailsDto>();
CreateMap<Author, GetAuthorDto>();
CreateMap<Author, GetAuthorDetailsDto>();
CreateMap<Loan, GetLoanDto>();
CreateMap<Member, GetMemberDto>();
CreateMap<Member, GetMemberDetailsDto>();
CreateMap<Review, GetReviewDto>();
}
}

View File

@@ -0,0 +1,274 @@
// <auto-generated />
using System;
using BookHive;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace BookHive.Migrations
{
[DbContext(typeof(BookHiveDbContext))]
[Migration("20260309112221_InitialDatabase")]
partial class InitialDatabase
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.20")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("BookHive.Models.Author", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Biography")
.HasMaxLength(2000)
.HasColumnType("nvarchar(2000)");
b.Property<DateOnly>("BirthDate")
.HasColumnType("date");
b.Property<string>("FirstName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("LastName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("Nationality")
.IsRequired()
.HasMaxLength(60)
.HasColumnType("nvarchar(60)");
b.HasKey("Id");
b.ToTable("Authors");
});
modelBuilder.Entity("BookHive.Models.Book", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("AuthorId")
.HasColumnType("int");
b.Property<string>("Genre")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<string>("Isbn")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("PageCount")
.HasColumnType("int");
b.Property<DateOnly>("PublishedDate")
.HasColumnType("date");
b.Property<string>("Summary")
.HasMaxLength(3000)
.HasColumnType("nvarchar(3000)");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.HasKey("Id");
b.HasIndex("AuthorId");
b.ToTable("Books");
});
modelBuilder.Entity("BookHive.Models.Loan", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("BookId")
.HasColumnType("int");
b.Property<DateOnly>("DueDate")
.HasColumnType("date");
b.Property<DateOnly>("LoanDate")
.HasColumnType("date");
b.Property<int>("MemberId")
.HasColumnType("int");
b.Property<DateOnly?>("ReturnDate")
.HasColumnType("date");
b.HasKey("Id");
b.HasIndex("BookId");
b.HasIndex("MemberId");
b.ToTable("Loans");
});
modelBuilder.Entity("BookHive.Models.Member", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Email")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("FirstName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.Property<string>("LastName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<DateOnly>("MembershipDate")
.HasColumnType("date");
b.HasKey("Id");
b.ToTable("Members");
});
modelBuilder.Entity("BookHive.Models.Review", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("BookId")
.HasColumnType("int");
b.Property<string>("Comment")
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<DateTime?>("CreatedAt")
.IsRequired()
.HasColumnType("datetime2");
b.Property<int>("MemberId")
.HasColumnType("int");
b.Property<int>("Rating")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("BookId");
b.HasIndex("MemberId");
b.ToTable("Reviews");
});
modelBuilder.Entity("BookHive.Models.Book", b =>
{
b.HasOne("BookHive.Models.Author", "Author")
.WithMany("Books")
.HasForeignKey("AuthorId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Author");
});
modelBuilder.Entity("BookHive.Models.Loan", b =>
{
b.HasOne("BookHive.Models.Book", "Book")
.WithMany("Loans")
.HasForeignKey("BookId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("BookHive.Models.Member", "Member")
.WithMany("Loans")
.HasForeignKey("MemberId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Book");
b.Navigation("Member");
});
modelBuilder.Entity("BookHive.Models.Review", b =>
{
b.HasOne("BookHive.Models.Book", "Book")
.WithMany("Reviews")
.HasForeignKey("BookId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("BookHive.Models.Member", "Member")
.WithMany("Reviews")
.HasForeignKey("MemberId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Book");
b.Navigation("Member");
});
modelBuilder.Entity("BookHive.Models.Author", b =>
{
b.Navigation("Books");
});
modelBuilder.Entity("BookHive.Models.Book", b =>
{
b.Navigation("Loans");
b.Navigation("Reviews");
});
modelBuilder.Entity("BookHive.Models.Member", b =>
{
b.Navigation("Loans");
b.Navigation("Reviews");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,176 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace BookHive.Migrations
{
/// <inheritdoc />
public partial class InitialDatabase : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Authors",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
FirstName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
LastName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
Biography = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true),
BirthDate = table.Column<DateOnly>(type: "date", nullable: false),
Nationality = table.Column<string>(type: "nvarchar(60)", maxLength: 60, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Authors", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Members",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Email = table.Column<string>(type: "nvarchar(max)", nullable: false),
FirstName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
LastName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
MembershipDate = table.Column<DateOnly>(type: "date", nullable: false),
IsActive = table.Column<bool>(type: "bit", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Members", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Books",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Title = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
Isbn = table.Column<string>(type: "nvarchar(max)", nullable: false),
Summary = table.Column<string>(type: "nvarchar(3000)", maxLength: 3000, nullable: true),
PageCount = table.Column<int>(type: "int", nullable: false),
PublishedDate = table.Column<DateOnly>(type: "date", nullable: false),
Genre = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
AuthorId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Books", x => x.Id);
table.ForeignKey(
name: "FK_Books_Authors_AuthorId",
column: x => x.AuthorId,
principalTable: "Authors",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Loans",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
BookId = table.Column<int>(type: "int", nullable: false),
MemberId = table.Column<int>(type: "int", nullable: false),
LoanDate = table.Column<DateOnly>(type: "date", nullable: false),
DueDate = table.Column<DateOnly>(type: "date", nullable: false),
ReturnDate = table.Column<DateOnly>(type: "date", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Loans", x => x.Id);
table.ForeignKey(
name: "FK_Loans_Books_BookId",
column: x => x.BookId,
principalTable: "Books",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Loans_Members_MemberId",
column: x => x.MemberId,
principalTable: "Members",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Reviews",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
BookId = table.Column<int>(type: "int", nullable: false),
MemberId = table.Column<int>(type: "int", nullable: false),
Rating = table.Column<int>(type: "int", nullable: false),
Comment = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Reviews", x => x.Id);
table.ForeignKey(
name: "FK_Reviews_Books_BookId",
column: x => x.BookId,
principalTable: "Books",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Reviews_Members_MemberId",
column: x => x.MemberId,
principalTable: "Members",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Books_AuthorId",
table: "Books",
column: "AuthorId");
migrationBuilder.CreateIndex(
name: "IX_Loans_BookId",
table: "Loans",
column: "BookId");
migrationBuilder.CreateIndex(
name: "IX_Loans_MemberId",
table: "Loans",
column: "MemberId");
migrationBuilder.CreateIndex(
name: "IX_Reviews_BookId",
table: "Reviews",
column: "BookId");
migrationBuilder.CreateIndex(
name: "IX_Reviews_MemberId",
table: "Reviews",
column: "MemberId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Loans");
migrationBuilder.DropTable(
name: "Reviews");
migrationBuilder.DropTable(
name: "Books");
migrationBuilder.DropTable(
name: "Members");
migrationBuilder.DropTable(
name: "Authors");
}
}
}

View File

@@ -0,0 +1,275 @@
// <auto-generated />
using System;
using BookHive;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace BookHive.Migrations
{
[DbContext(typeof(BookHiveDbContext))]
[Migration("20260309132259_AddedContraint")]
partial class AddedContraint
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.20")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("BookHive.Models.Author", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Biography")
.HasMaxLength(2000)
.HasColumnType("nvarchar(2000)");
b.Property<DateOnly>("BirthDate")
.HasColumnType("date");
b.Property<string>("FirstName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("LastName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("Nationality")
.IsRequired()
.HasMaxLength(60)
.HasColumnType("nvarchar(60)");
b.HasKey("Id");
b.ToTable("Authors");
});
modelBuilder.Entity("BookHive.Models.Book", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("AuthorId")
.HasColumnType("int");
b.Property<string>("Genre")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<string>("Isbn")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("PageCount")
.HasColumnType("int");
b.Property<DateOnly>("PublishedDate")
.HasColumnType("date");
b.Property<string>("Summary")
.HasMaxLength(3000)
.HasColumnType("nvarchar(3000)");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.HasKey("Id");
b.HasIndex("AuthorId");
b.ToTable("Books");
});
modelBuilder.Entity("BookHive.Models.Loan", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("BookId")
.HasColumnType("int");
b.Property<DateOnly>("DueDate")
.HasColumnType("date");
b.Property<DateOnly>("LoanDate")
.HasColumnType("date");
b.Property<int>("MemberId")
.HasColumnType("int");
b.Property<DateOnly?>("ReturnDate")
.HasColumnType("date");
b.HasKey("Id");
b.HasIndex("BookId");
b.HasIndex("MemberId");
b.ToTable("Loans");
});
modelBuilder.Entity("BookHive.Models.Member", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Email")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("FirstName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.Property<string>("LastName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<DateOnly>("MembershipDate")
.HasColumnType("date");
b.HasKey("Id");
b.ToTable("Members");
});
modelBuilder.Entity("BookHive.Models.Review", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("BookId")
.HasColumnType("int");
b.Property<string>("Comment")
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<DateTime?>("CreatedAt")
.IsRequired()
.HasColumnType("datetime2");
b.Property<int>("MemberId")
.HasColumnType("int");
b.Property<int>("Rating")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("MemberId");
b.HasIndex("BookId", "MemberId")
.IsUnique();
b.ToTable("Reviews");
});
modelBuilder.Entity("BookHive.Models.Book", b =>
{
b.HasOne("BookHive.Models.Author", "Author")
.WithMany("Books")
.HasForeignKey("AuthorId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Author");
});
modelBuilder.Entity("BookHive.Models.Loan", b =>
{
b.HasOne("BookHive.Models.Book", "Book")
.WithMany("Loans")
.HasForeignKey("BookId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("BookHive.Models.Member", "Member")
.WithMany("Loans")
.HasForeignKey("MemberId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Book");
b.Navigation("Member");
});
modelBuilder.Entity("BookHive.Models.Review", b =>
{
b.HasOne("BookHive.Models.Book", "Book")
.WithMany("Reviews")
.HasForeignKey("BookId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("BookHive.Models.Member", "Member")
.WithMany("Reviews")
.HasForeignKey("MemberId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Book");
b.Navigation("Member");
});
modelBuilder.Entity("BookHive.Models.Author", b =>
{
b.Navigation("Books");
});
modelBuilder.Entity("BookHive.Models.Book", b =>
{
b.Navigation("Loans");
b.Navigation("Reviews");
});
modelBuilder.Entity("BookHive.Models.Member", b =>
{
b.Navigation("Loans");
b.Navigation("Reviews");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,37 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace BookHive.Migrations
{
/// <inheritdoc />
public partial class AddedContraint : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_Reviews_BookId",
table: "Reviews");
migrationBuilder.CreateIndex(
name: "IX_Reviews_BookId_MemberId",
table: "Reviews",
columns: new[] { "BookId", "MemberId" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_Reviews_BookId_MemberId",
table: "Reviews");
migrationBuilder.CreateIndex(
name: "IX_Reviews_BookId",
table: "Reviews",
column: "BookId");
}
}
}

View File

@@ -0,0 +1,272 @@
// <auto-generated />
using System;
using BookHive;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace BookHive.Migrations
{
[DbContext(typeof(BookHiveDbContext))]
partial class BookHiveDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.20")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("BookHive.Models.Author", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Biography")
.HasMaxLength(2000)
.HasColumnType("nvarchar(2000)");
b.Property<DateOnly>("BirthDate")
.HasColumnType("date");
b.Property<string>("FirstName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("LastName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("Nationality")
.IsRequired()
.HasMaxLength(60)
.HasColumnType("nvarchar(60)");
b.HasKey("Id");
b.ToTable("Authors");
});
modelBuilder.Entity("BookHive.Models.Book", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("AuthorId")
.HasColumnType("int");
b.Property<string>("Genre")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<string>("Isbn")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("PageCount")
.HasColumnType("int");
b.Property<DateOnly>("PublishedDate")
.HasColumnType("date");
b.Property<string>("Summary")
.HasMaxLength(3000)
.HasColumnType("nvarchar(3000)");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.HasKey("Id");
b.HasIndex("AuthorId");
b.ToTable("Books");
});
modelBuilder.Entity("BookHive.Models.Loan", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("BookId")
.HasColumnType("int");
b.Property<DateOnly>("DueDate")
.HasColumnType("date");
b.Property<DateOnly>("LoanDate")
.HasColumnType("date");
b.Property<int>("MemberId")
.HasColumnType("int");
b.Property<DateOnly?>("ReturnDate")
.HasColumnType("date");
b.HasKey("Id");
b.HasIndex("BookId");
b.HasIndex("MemberId");
b.ToTable("Loans");
});
modelBuilder.Entity("BookHive.Models.Member", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Email")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("FirstName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.Property<string>("LastName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<DateOnly>("MembershipDate")
.HasColumnType("date");
b.HasKey("Id");
b.ToTable("Members");
});
modelBuilder.Entity("BookHive.Models.Review", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("BookId")
.HasColumnType("int");
b.Property<string>("Comment")
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<DateTime?>("CreatedAt")
.IsRequired()
.HasColumnType("datetime2");
b.Property<int>("MemberId")
.HasColumnType("int");
b.Property<int>("Rating")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("MemberId");
b.HasIndex("BookId", "MemberId")
.IsUnique();
b.ToTable("Reviews");
});
modelBuilder.Entity("BookHive.Models.Book", b =>
{
b.HasOne("BookHive.Models.Author", "Author")
.WithMany("Books")
.HasForeignKey("AuthorId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Author");
});
modelBuilder.Entity("BookHive.Models.Loan", b =>
{
b.HasOne("BookHive.Models.Book", "Book")
.WithMany("Loans")
.HasForeignKey("BookId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("BookHive.Models.Member", "Member")
.WithMany("Loans")
.HasForeignKey("MemberId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Book");
b.Navigation("Member");
});
modelBuilder.Entity("BookHive.Models.Review", b =>
{
b.HasOne("BookHive.Models.Book", "Book")
.WithMany("Reviews")
.HasForeignKey("BookId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("BookHive.Models.Member", "Member")
.WithMany("Reviews")
.HasForeignKey("MemberId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Book");
b.Navigation("Member");
});
modelBuilder.Entity("BookHive.Models.Author", b =>
{
b.Navigation("Books");
});
modelBuilder.Entity("BookHive.Models.Book", b =>
{
b.Navigation("Loans");
b.Navigation("Reviews");
});
modelBuilder.Entity("BookHive.Models.Member", b =>
{
b.Navigation("Loans");
b.Navigation("Reviews");
});
#pragma warning restore 612, 618
}
}
}

15
BookHive/Models/Author.cs Normal file
View File

@@ -0,0 +1,15 @@
using System.ComponentModel.DataAnnotations;
namespace BookHive.Models;
public class Author
{
[Key]public int Id { get; set; }
[Required, MaxLength(100)] public string? FirstName { get; set; }
[Required, MaxLength(100)] public string? LastName { get; set; }
[MaxLength(2000)] public string? Biography { get; set; }
[Required] public DateOnly BirthDate { get; set; }
[Required, MaxLength(60)] public string? Nationality { get; set; }
public List<Book>? Books { get; set; }
}

20
BookHive/Models/Book.cs Normal file
View File

@@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
namespace BookHive.Models;
public class Book
{
[Key] public int Id { get; set; }
[Required, MaxLength(200)] public string? Title { get; set; }
[Required] public string? Isbn { get; set; }
[MaxLength(3000)] public string? Summary { get; set; }
[Required] public int PageCount { get; set; }
[Required] public DateOnly PublishedDate { get; set; }
[Required, MaxLength(50)] public string? Genre { get; set; }
public Author? Author { get; set; }
[Required] public int AuthorId { get; set; }
public List<Loan>? Loans { get; set; }
public List<Review>? Reviews { get; set; }
}

18
BookHive/Models/Loan.cs Normal file
View File

@@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;
namespace BookHive.Models;
public class Loan
{
[Key] public int Id { get; set; }
public Book? Book { get; set; }
[Required] public int BookId { get; set; }
public Member? Member { get; set; }
[Required] public int MemberId { get; set; }
[Required] public DateOnly LoanDate { get; set; }
[Required] public DateOnly DueDate { get; set; }
public DateOnly? ReturnDate { get; set; }
}

16
BookHive/Models/Member.cs Normal file
View File

@@ -0,0 +1,16 @@
using System.ComponentModel.DataAnnotations;
namespace BookHive.Models;
public class Member
{
[Key] public int Id { get; set; }
[Required, EmailAddress] public string? Email { get; set; }
[Required, MaxLength(100)] public string? FirstName { get; set; }
[Required, MaxLength(100)] public string? LastName { get; set; }
[Required] public DateOnly MembershipDate { get; set; }
[Required] public bool IsActive { get; set; } = true;
public List<Loan>? Loans { get; set; }
public List<Review>? Reviews { get; set; }
}

18
BookHive/Models/Review.cs Normal file
View File

@@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;
namespace BookHive.Models;
public class Review
{
[Key] public int Id { get; set; }
public Book? Book { get; set; }
[Required] public int BookId { get; set; }
public Member? Member { get; set; }
[Required] public int MemberId { get; set; }
[Required] public int Rating { get; set; }
[MaxLength(1000)] public string? Comment { get; set; }
[Required] public DateTime? CreatedAt { get; set; } = DateTime.Now;
}

66
BookHive/Program.cs Normal file
View File

@@ -0,0 +1,66 @@
using AutoMapper;
using AutoMapper.EquivalencyExpression;
using BookHive;
using BookHive.MappingProfiles;
using BookHive.Repositories;
using FastEndpoints;
using FastEndpoints.Security;
using FastEndpoints.Swagger;
using Microsoft.Net.Http.Headers;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
// On ajoute ici FastEndpoints, un framework REPR et Swagger aux services disponibles dans le projet
builder.Services
.AddAuthenticationJwtBearer(s => s.SigningKey = "ThisIsASuperSecretJwtKeyThatIsAtLeast32CharsLong")
.AddAuthorization()
.AddFastEndpoints()
.AddCors(options =>
{
options.AddDefaultPolicy(policyBuilder =>
{
policyBuilder
.WithOrigins("http://localhost:4200")
.WithMethods("GET", "POST", "PUT", "DELETE", "PATCH")
.AllowAnyHeader()
.WithExposedHeaders(HeaderNames.ContentDisposition);
});
})
.SwaggerDocument(options => { options.ShortSchemaNames = true; });
builder.Services.AddDbContext<BookHiveDbContext>();
builder.Services.AddScoped<AuthorRepository>();
builder.Services.AddScoped<BookRepository>();
builder.Services.AddScoped<MemberRepository>();
builder.Services.AddScoped<ReviewRepository>();
builder.Services.AddScoped<LoanRepository>();
builder.Services.AddHttpContextAccessor();
MapperConfiguration mappingConfig = new(mc =>
{
mc.AddCollectionMappers();
mc.AddProfile(new DtoToEntityMappings());
mc.AddProfile(new EntityToDtoMappings());
}, new LoggerFactory());
AutoMapper.IMapper mapper = mappingConfig.CreateMapper();
builder.Services.AddSingleton(mapper);
WebApplication app = builder.Build();
app.UseAuthentication()
.UseAuthorization()
.UseFastEndpoints(options =>
{
options.Endpoints.ShortNames = true;
options.Endpoints.RoutePrefix = "API";
})
.UseSwaggerGen();
// app.UseHttpsRedirection();
// app.UseCors();
app.Run();

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5278",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://localhost:7103;http://localhost:5278",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,5 @@
using BookHive.Models;
namespace BookHive.Repositories;
public class AuthorRepository(BookHiveDbContext bookHiveDbContext, AutoMapper.IMapper mapper) : BookHiveRepository<Author>(bookHiveDbContext, mapper);

View File

@@ -0,0 +1,328 @@
using System.Linq.Expressions;
using Ardalis.Specification;
using Ardalis.Specification.EntityFrameworkCore;
using AutoMapper.QueryableExtensions;
using Microsoft.EntityFrameworkCore;
using Plainquire.Page;
namespace BookHive.Repositories;
public class BookHiveRepository<T>(DbContext databaseContext, AutoMapper.IMapper mapper) : RepositoryBase<T>(databaseContext) where T : class
{
private readonly DbContext _databaseContext = databaseContext;
/// <summary>
///
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<T?> FirstOrDefaultAsync(CancellationToken cancellationToken = default)
{
return await _databaseContext.Set<T>().AsQueryable().FirstOrDefaultAsync(cancellationToken);
}
/// <summary>
///
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<T> FirstAsync(CancellationToken cancellationToken = default)
{
return await _databaseContext.Set<T>().AsQueryable().FirstAsync(cancellationToken);
}
/// <summary>
///
/// </summary>
/// <param name="specification"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<T> SingleAsync(ISpecification<T> specification, CancellationToken cancellationToken = default)
{
return await ApplySpecification(specification).SingleAsync(cancellationToken);
}
/// <summary>
///
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<T> SingleAsync(CancellationToken cancellationToken = default)
{
return await _databaseContext.Set<T>().AsQueryable().SingleAsync(cancellationToken);
}
/// <summary>
///
/// </summary>
/// <param name="specification"></param>
/// <param name="selector"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<int> SumAsync(
ISpecification<T> specification,
Expression<Func<T, int>> selector,
CancellationToken cancellationToken = default)
{
return await ApplySpecification(specification).SumAsync(selector, cancellationToken);
}
// /// <summary>
// ///
// /// </summary>
// /// <param name="specification"></param>
// /// <param name="selector"></param>
// /// <param name="cancellationToken"></param>
// /// <returns></returns>
// public async Task<int> SumAsync(
// ISpecification<T> specification,
// Expression<Func<T, int>> selector,
// CancellationToken cancellationToken = default)
// {
// return await ApplySpecification(specification).SumAsync(selector, cancellationToken);
// }
/// <summary>
///
/// </summary>
/// <param name="specification"></param>
/// <param name="cancellationToken"></param>
/// <param name="postProcessor"></param>
/// <typeparam name="TResult"></typeparam>
/// <returns></returns>
public async Task<TResult?> ProjectToSingleOrDefaultAsync<TResult>(
ISpecification<T> specification,
CancellationToken cancellationToken = default)
{
return await ApplySpecification(specification)
.ProjectTo<TResult>(mapper.ConfigurationProvider)
.SingleOrDefaultAsync(cancellationToken: cancellationToken);
}
/// <summary>
///
/// </summary>
/// <param name="specification"></param>
/// <param name="cancellationToken"></param>
/// <param name="postProcessor"></param>
/// <typeparam name="TResult"></typeparam>
/// <returns></returns>
public async Task<TResult?> ProjectToSingleOrDefaultAsync<TResult>(
ISpecification<T> specification,
Action<TResult?> postProcessor,
CancellationToken cancellationToken = default)
{
TResult? result = await ApplySpecification(specification)
.ProjectTo<TResult>(mapper.ConfigurationProvider)
.SingleOrDefaultAsync(cancellationToken: cancellationToken);
postProcessor(result);
return result;
}
// /// <summary>
// ///
// /// </summary>
// /// <param name="cancellationToken"></param>
// /// <typeparam name="TResult"></typeparam>
// /// <returns></returns>
// public async Task<TResult?> ProjectToSingleOrDefaultAsync<TResult>(
// CancellationToken cancellationToken = default)
// {
// return await _databaseContext.Set<T>().AsQueryable().ProjectTo<TResult>(mapper.ConfigurationProvider).SingleOrDefaultAsync(cancellationToken: cancellationToken);
// }
/// <summary>
///
/// </summary>
/// <param name="specification"></param>
/// <param name="cancellationToken"></param>
/// <typeparam name="TResult"></typeparam>
/// <returns></returns>
public async Task<TResult> ProjectToSingleAsync<TResult>(
ISpecification<T> specification,
CancellationToken cancellationToken = default)
{
return await ApplySpecification(specification)
.ProjectTo<TResult>(mapper.ConfigurationProvider)
.SingleAsync(cancellationToken: cancellationToken);
}
/// <summary>
///
/// </summary>
/// <param name="specification"></param>
/// <param name="postProcessor"></param>
/// <param name="cancellationToken"></param>
/// <typeparam name="TResult"></typeparam>
/// <returns></returns>
public async Task<TResult> ProjectToSingleAsync<TResult>(
ISpecification<T> specification,
Action<TResult?> postProcessor,
CancellationToken cancellationToken = default)
{
TResult result = await ApplySpecification(specification)
.ProjectTo<TResult>(mapper.ConfigurationProvider)
.SingleAsync(cancellationToken: cancellationToken);
postProcessor(result);
return result;
}
/// <summary>
///
/// </summary>
/// <param name="cancellationToken"></param>
/// <typeparam name="TResult"></typeparam>
/// <returns></returns>
public async Task<TResult> ProjectToSingleAsync<TResult>(
CancellationToken cancellationToken = default)
{
return await _databaseContext.Set<T>()
.AsQueryable()
.ProjectTo<TResult>(mapper.ConfigurationProvider)
.SingleAsync(cancellationToken: cancellationToken);
}
/// <summary>
///
/// </summary>
/// <param name="cancellationToken"></param>
/// <typeparam name="TResult"></typeparam>
/// <returns></returns>
public async Task<List<TResult>> ProjectToListAsync<TResult>(
CancellationToken cancellationToken = default)
{
return await _databaseContext.Set<T>()
.AsQueryable()
.ProjectTo<TResult>(mapper.ConfigurationProvider)
.ToListAsync(cancellationToken: cancellationToken);
}
/// <summary>
///
/// </summary>
/// <param name="postProcessor"></param>
/// <param name="cancellationToken"></param>
/// <typeparam name="TResult"></typeparam>
/// <returns></returns>
public async Task<List<TResult>> ProjectToListAsync<TResult>(
Action<List<TResult>> postProcessor,
CancellationToken cancellationToken = default)
{
List<TResult> results = await _databaseContext.Set<T>()
.AsQueryable()
.ProjectTo<TResult>(mapper.ConfigurationProvider)
.ToListAsync(cancellationToken: cancellationToken);
postProcessor(results);
return results;
}
/// <summary>
///
/// </summary>
/// <param name="specification"></param>
/// <param name="postProcessor"></param>
/// <param name="cancellationToken"></param>
/// <typeparam name="TResult"></typeparam>
/// <returns></returns>
public async Task<List<TResult>> ProjectToListAsync<TResult>(
ISpecification<T> specification,
Action<List<TResult>> postProcessor,
CancellationToken cancellationToken = default)
{
List<TResult> results = await ApplySpecification(specification)
.ProjectTo<TResult>(mapper.ConfigurationProvider)
.ToListAsync(cancellationToken: cancellationToken);
postProcessor(results);
return results;
}
/// <summary>
///
/// </summary>
/// <param name="specification"></param>
/// <param name="cancellationToken"></param>
/// <typeparam name="TResult"></typeparam>
/// <returns></returns>
public async Task<List<TResult>> ProjectToListAsync<TResult>(
ISpecification<T> specification,
CancellationToken cancellationToken = default)
{
return await ApplySpecification(specification)
.ProjectTo<TResult>(mapper.ConfigurationProvider)
.ToListAsync(cancellationToken: cancellationToken);
}
/// <summary>
///
/// </summary>
/// <param name="specification"></param>
/// <param name="page"></param>
/// <param name="postProcessor"></param>
/// <param name="cancellationToken"></param>
/// <typeparam name="TResult"></typeparam>
/// <returns></returns>
public async Task<List<TResult>> ProjectToListAsync<TResult>(
ISpecification<T> specification,
EntityPage page,
Action<List<TResult>> postProcessor,
CancellationToken cancellationToken = default)
{
List<TResult> results = await ApplySpecification(specification)
.ProjectTo<TResult>(mapper.ConfigurationProvider)
.Page(page)
.ToListAsync(cancellationToken: cancellationToken);
postProcessor(results);
return results;
}
/// <summary>
///
/// </summary>
/// <param name="specification"></param>
/// <param name="page"></param>
/// <param name="cancellationToken"></param>
/// <typeparam name="TResult"></typeparam>
/// <returns></returns>
public async Task<List<TResult>> ProjectToListAsync<TResult>(
ISpecification<T> specification,
EntityPage page,
CancellationToken cancellationToken = default)
{
return await ApplySpecification(specification)
.ProjectTo<TResult>(mapper.ConfigurationProvider)
.Page(page)
.ToListAsync(cancellationToken: cancellationToken);
}
/// <summary>
///
/// </summary>
/// <param name="specification"></param>
/// <param name="selector"></param>
/// <param name="cancellationToken"></param>
/// <typeparam name="TResult"></typeparam>
/// <returns></returns>
public async Task<List<TResult>> SelectManyAsync<TResult>(
ISpecification<T> specification,
Expression<Func<T, IEnumerable<TResult>>> selector,
CancellationToken cancellationToken = default)
{
return await ApplySpecification(specification).SelectMany(selector).ToListAsync(cancellationToken: cancellationToken);
}
/// <summary>
///
/// </summary>
/// <param name="specification"></param>
/// <param name="selector"></param>
/// <param name="cancellationToken"></param>
/// <typeparam name="TResult"></typeparam>
/// <returns></returns>
public async Task<List<TResult>> SelectAsync<TResult>(
ISpecification<T> specification,
Expression<Func<T, TResult>> selector,
CancellationToken cancellationToken = default)
{
return await ApplySpecification(specification).Select(selector).ToListAsync(cancellationToken: cancellationToken);
}
}

View File

@@ -0,0 +1,5 @@
using BookHive.Models;
namespace BookHive.Repositories;
public class BookRepository(BookHiveDbContext bookHiveDbContext, AutoMapper.IMapper mapper) : BookHiveRepository<Book>(bookHiveDbContext, mapper);

View File

@@ -0,0 +1,5 @@
using BookHive.Models;
namespace BookHive.Repositories;
public class LoanRepository(BookHiveDbContext bookHiveDbContext, AutoMapper.IMapper mapper) : BookHiveRepository<Loan>(bookHiveDbContext, mapper);

View File

@@ -0,0 +1,5 @@
using BookHive.Models;
namespace BookHive.Repositories;
public class MemberRepository(BookHiveDbContext bookHiveDbContext, AutoMapper.IMapper mapper) : BookHiveRepository<Member>(bookHiveDbContext, mapper);

View File

@@ -0,0 +1,5 @@
using BookHive.Models;
namespace BookHive.Repositories;
public class ReviewRepository(BookHiveDbContext bookHiveDbContext, AutoMapper.IMapper mapper) : BookHiveRepository<Review>(bookHiveDbContext, mapper);

View File

@@ -0,0 +1,13 @@
using Ardalis.Specification;
using BookHive.Models;
namespace BookHive.Specifications.Authors;
public class GetAuthorByIdSpec : SingleResultSpecification<Author>
{
public GetAuthorByIdSpec(int authorId)
{
Query
.Where(x => x.Id == authorId);
}
}

View File

@@ -0,0 +1,13 @@
using Ardalis.Specification;
using BookHive.Models;
namespace BookHive.Specifications.Books;
public class GetBookByIdSpec : SingleResultSpecification<Book>
{
public GetBookByIdSpec(int bookId)
{
Query
.Where(x => x.Id == bookId);
}
}

View File

@@ -0,0 +1,13 @@
using Ardalis.Specification;
using BookHive.Models;
namespace BookHive.Specifications.Loans;
public class GetLoanByIdSpec : SingleResultSpecification<Loan>
{
public GetLoanByIdSpec(int loanId)
{
Query
.Where(x => x.Id == loanId);
}
}

View File

@@ -0,0 +1,13 @@
using Ardalis.Specification;
using BookHive.Models;
namespace BookHive.Specifications.Members;
public class GetMemberByIdSpec : SingleResultSpecification<Member>
{
public GetMemberByIdSpec(int memberId)
{
Query
.Where(x => x.Id == memberId);
}
}

View File

@@ -0,0 +1,13 @@
using Ardalis.Specification;
using BookHive.Models;
namespace BookHive.Specifications.Reviews;
public class GetReviewByBookIdSpec : Specification<Review>
{
public GetReviewByBookIdSpec(int bookId)
{
Query
.Where(x => x.Id == bookId);
}
}

View File

@@ -0,0 +1,13 @@
using Ardalis.Specification;
using BookHive.Models;
namespace BookHive.Specifications.Reviews;
public class GetReviewByIdSpec : SingleResultSpecification<Review>
{
public GetReviewByIdSpec(int reviewId)
{
Query
.Where(x => x.Id == reviewId);
}
}

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,20 @@
{
"runtimeOptions": {
"tfm": "net10.0",
"frameworks": [
{
"name": "Microsoft.NETCore.App",
"version": "10.0.0"
},
{
"name": "Microsoft.AspNetCore.App",
"version": "10.0.0"
}
],
"configProperties": {
"System.GC.Server": true,
"System.Reflection.NullabilityInfoContext.IsSupported": true,
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}

View File

@@ -0,0 +1 @@
{"Version":1,"ManifestType":"Build","Endpoints":[]}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More