From 3c2b7ea89b5105048b6e77cac245f201d7603280 Mon Sep 17 00:00:00 2001 From: rexx07 <56883674+rexx07@users.noreply.github.com> Date: Wed, 1 Mar 2023 11:28:45 -0500 Subject: [PATCH 1/5] initial commit --- .idea/.idea.BankingSoftware/.idea/.gitignore | 13 ++ .../.idea.BankingSoftware/.idea/encodings.xml | 4 + .../.idea/indexLayout.xml | 8 ++ .idea/.idea.BankingSoftware/.idea/vcs.xml | 6 + BankingSoftware.sln | 16 +++ BankingSoftware/BankingSoftware.csproj | 16 +++ BankingSoftware/Common/AddSymbol.cs | 26 ++++ BankingSoftware/Common/Hash.cs | 28 ++++ BankingSoftware/Common/VerifyInput.cs | 129 ++++++++++++++++++ BankingSoftware/Common/WriteFile.cs | 24 ++++ BankingSoftware/Modules/AccountModule.cs | 129 ++++++++++++++++++ BankingSoftware/Modules/AuthModule.cs | 41 ++++++ BankingSoftware/Modules/SignupModule.cs | 93 +++++++++++++ BankingSoftware/Program.cs | 111 +++++++++++++++ .../5FE5F846BD5D98FFF20F1384D0DFC710.txt | 5 + .../2947CB1B92BB13C8F373E78C76FCA765.txt | 8 ++ 16 files changed, 657 insertions(+) create mode 100644 .idea/.idea.BankingSoftware/.idea/.gitignore create mode 100644 .idea/.idea.BankingSoftware/.idea/encodings.xml create mode 100644 .idea/.idea.BankingSoftware/.idea/indexLayout.xml create mode 100644 .idea/.idea.BankingSoftware/.idea/vcs.xml create mode 100644 BankingSoftware.sln create mode 100644 BankingSoftware/BankingSoftware.csproj create mode 100644 BankingSoftware/Common/AddSymbol.cs create mode 100644 BankingSoftware/Common/Hash.cs create mode 100644 BankingSoftware/Common/VerifyInput.cs create mode 100644 BankingSoftware/Common/WriteFile.cs create mode 100644 BankingSoftware/Modules/AccountModule.cs create mode 100644 BankingSoftware/Modules/AuthModule.cs create mode 100644 BankingSoftware/Modules/SignupModule.cs create mode 100644 BankingSoftware/Program.cs create mode 100644 BankingSoftware/Storage/Accounts/5FE5F846BD5D98FFF20F1384D0DFC710.txt create mode 100644 BankingSoftware/Storage/Users/2947CB1B92BB13C8F373E78C76FCA765.txt diff --git a/.idea/.idea.BankingSoftware/.idea/.gitignore b/.idea/.idea.BankingSoftware/.idea/.gitignore new file mode 100644 index 0000000..794932b --- /dev/null +++ b/.idea/.idea.BankingSoftware/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/modules.xml +/contentModel.xml +/projectSettingsUpdater.xml +/.idea.BankingSoftware.iml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.BankingSoftware/.idea/encodings.xml b/.idea/.idea.BankingSoftware/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.BankingSoftware/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.BankingSoftware/.idea/indexLayout.xml b/.idea/.idea.BankingSoftware/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.BankingSoftware/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.BankingSoftware/.idea/vcs.xml b/.idea/.idea.BankingSoftware/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/.idea.BankingSoftware/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/BankingSoftware.sln b/BankingSoftware.sln new file mode 100644 index 0000000..4792473 --- /dev/null +++ b/BankingSoftware.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BankingSoftware", "BankingSoftware\BankingSoftware.csproj", "{335BF223-9311-4DF9-A534-4B3831D2279E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {335BF223-9311-4DF9-A534-4B3831D2279E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {335BF223-9311-4DF9-A534-4B3831D2279E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {335BF223-9311-4DF9-A534-4B3831D2279E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {335BF223-9311-4DF9-A534-4B3831D2279E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/BankingSoftware/BankingSoftware.csproj b/BankingSoftware/BankingSoftware.csproj new file mode 100644 index 0000000..16660e4 --- /dev/null +++ b/BankingSoftware/BankingSoftware.csproj @@ -0,0 +1,16 @@ + + + + Exe + net7.0 + disable + enable + BankingSoftware + + + + + + + + diff --git a/BankingSoftware/Common/AddSymbol.cs b/BankingSoftware/Common/AddSymbol.cs new file mode 100644 index 0000000..1fc6910 --- /dev/null +++ b/BankingSoftware/Common/AddSymbol.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; + +namespace BankingSoftware.Common; + +public static class AddSymbol +{ + public static void AddBreakLine(int symbolLength = 80, string symbol = "-") + { + for (var i = 0; i < symbolLength; i++) + { + Console.Write(symbol); + } + Console.WriteLine(""); + } + + public static void AddBreakLines(int symbolLength = 80, int lineLength = 3, string symbol = "-") + { + for (int i = 0; i < lineLength; i++) + { + AddBreakLine(symbolLength, symbol); + } + + Console.WriteLine("\n"); + } +} \ No newline at end of file diff --git a/BankingSoftware/Common/Hash.cs b/BankingSoftware/Common/Hash.cs new file mode 100644 index 0000000..f118b5c --- /dev/null +++ b/BankingSoftware/Common/Hash.cs @@ -0,0 +1,28 @@ +using System; +using System.Security.Cryptography; +using System.Text; + +namespace BankingSoftware.Common; + +public class Hash +{ + const int keySize = 16; + const int iterations = 350000; + HashAlgorithmName hashAlgorithm = HashAlgorithmName.SHA256; + + public string HashValues(string[] values) + { + var joinedValues = string.Join("", values); + + var salt = new UTF8Encoding(true).GetBytes(joinedValues); + + var hash = Rfc2898DeriveBytes.Pbkdf2( + Encoding.UTF8.GetBytes(joinedValues), + salt, + iterations, + hashAlgorithm, + keySize); + + return Convert.ToHexString(hash); + } +} \ No newline at end of file diff --git a/BankingSoftware/Common/VerifyInput.cs b/BankingSoftware/Common/VerifyInput.cs new file mode 100644 index 0000000..66d9a50 --- /dev/null +++ b/BankingSoftware/Common/VerifyInput.cs @@ -0,0 +1,129 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; + +namespace BankingSoftware.Common; + +public static partial class VerifyInput +{ + public static string VerifyForStringOnly(string input) + { + while ( + string.IsNullOrEmpty(input) + || string.IsNullOrWhiteSpace(input) + || input.Any(char.IsNumber) + || input.Any(c => + { + char.IsNumber(c); + char.IsSymbol(c); + char.IsPunctuation(c); + char.IsSeparator(c); + + return false; + }) + ) + { + Console.WriteLine("Input must not contain any number or symbol"); + Console.Write("Please input new value: "); + input = Console.ReadLine(); + } + + Console.WriteLine("Input is valid"); + return input; + } + + public static string VerifyForStringAndNumber(string stringNumber) + { + while ( + string.IsNullOrEmpty(stringNumber) + || string.IsNullOrWhiteSpace(stringNumber) + || stringNumber.Any(c => + { + char.IsLetter(c); + char.IsPunctuation(c); + char.IsSymbol(c); + + return false; + }) + ) + { + Console.WriteLine("Input must not contain any number or symbol"); + Console.Write("Please input new value: "); + stringNumber = Console.ReadLine(); + } + + Console.WriteLine("Input is valid"); + return stringNumber; + } + + public static int VerifyNumber(int number) + { + while ( + string.IsNullOrEmpty(number.ToString()) + || string.IsNullOrWhiteSpace(number.ToString()) + || number.ToString().Any(c => + { + char.IsSymbol(c); + char.IsPunctuation(c); + char.IsLetter(c); + + + return false; + }) + ) + { + Console.WriteLine("Input must not contain any letter or symbol"); + Console.Write("Please input your value: "); + number = int.Parse(Console.ReadLine()); + } + + Console.WriteLine("Number is valid"); + return number; + } + + public static string VerifyPassword(string password) + { + while ( + string.IsNullOrEmpty(password) + || string.IsNullOrWhiteSpace(password) + ) + { + Console.WriteLine("Input must not contain any space"); + Console.Write("Please input new value: "); + password = Console.ReadLine(); + } + + Console.WriteLine("Password is valid"); + return password; + } + + [GeneratedRegex("^\\(?([0-9]{3})\\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$")] + private static partial Regex MyRegex(); + + public static string VerifyPhoneNumber(string phoneNumber) + { + while (!MyRegex().IsMatch(phoneNumber)) + { + Console.WriteLine("Input must not contain any letter or symbol"); + Console.Write("Please input new value: "); + phoneNumber = Console.ReadLine(); + } + + Console.WriteLine("Phone number is valid"); + return phoneNumber; + } + + public static string VerifyEmail(string email) + { + string regex = @"^[^@\s]+@[^@\s]+\.(com|net|org|gov)$"; + + while (!Regex.IsMatch(email, regex, RegexOptions.IgnoreCase)) + { + Console.Write("Input a valid email: "); + email = Console.ReadLine(); + } + + Console.WriteLine("Email is valid."); + return email; + } +} \ No newline at end of file diff --git a/BankingSoftware/Common/WriteFile.cs b/BankingSoftware/Common/WriteFile.cs new file mode 100644 index 0000000..73df185 --- /dev/null +++ b/BankingSoftware/Common/WriteFile.cs @@ -0,0 +1,24 @@ +using System.IO; +using System.Linq; +using System.Text; + +namespace BankingSoftware.Common; + +public static class WriteFile +{ + public static void WriteLine(string lineText, string fileName) + { + using var fs = File.AppendText(fileName); + fs.Write(lineText); + } + + public static void WriteLines(string[] linesTexts, string fileName) + { + using var fs = File.OpenWrite(fileName); + for (int i = 0; i < linesTexts.Length; i++) + { + byte[] info = new UTF8Encoding(true).GetBytes(linesTexts[i] + "\n"); + fs.Write(info, 0, info.Length); + } + } +} \ No newline at end of file diff --git a/BankingSoftware/Modules/AccountModule.cs b/BankingSoftware/Modules/AccountModule.cs new file mode 100644 index 0000000..42a8e28 --- /dev/null +++ b/BankingSoftware/Modules/AccountModule.cs @@ -0,0 +1,129 @@ +using System; +using System.IO; +using BankingSoftware.Common; + +namespace BankingSoftware.Modules; + +public class AccountModule +{ + private static string GetAccountFile(in string userLogin, string accountStoragePath) + { + var charFileName = userLogin.Split(); + var accountFileName = new Hash().HashValues(charFileName) ; + var accountFilenameWithPath = accountStoragePath + accountFileName + ".txt"; + + return accountFilenameWithPath; + } + + private static string[] GetAccountSummary(string accountFilename) + { + var accountInfo = File.ReadAllLines(accountFilename); + + return accountInfo; + } + + public static void Deposit(in string userLogin, string accountStoragePath) + { + var accountFilename = GetAccountFile(userLogin, accountStoragePath); + + var accountInfo = GetAccountSummary(accountFilename); + foreach (var info in accountInfo) + { + Console.WriteLine(info); + } + + Console.Write("Please enter the amount you want to deposit: "); + var depositAmount = int.Parse(Console.ReadLine()); + + while (depositAmount is string && depositAmount < 0 ) + { + depositAmount = int.Parse(Console.ReadLine()); + } + + var depositAmountLine = accountInfo[2].Split(": $"); + var depositTimeLine = accountInfo[3].Split(": "); + depositAmountLine[1] = depositAmount.ToString(); + depositTimeLine[1] = DateTime.Now.ToString(); + + accountInfo[2] = string.Join(": ", depositAmountLine); + accountInfo[3] = string.Join(": ", depositTimeLine); + + var totalLine = accountInfo[4].Split(": $"); + + var totalAmount = int.Parse(totalLine[1]) + depositAmount; + totalLine[1] = totalAmount.ToString(); + accountInfo[4] = string.Join(": $", totalLine); + + Console.WriteLine("You've deposited ${0} to your account", depositAmount); + + Console.WriteLine("\nYour total amount is ${0}", totalAmount); + + WriteFile.WriteLines(accountInfo, accountFilename); + } + + public static void Withdraw(in string userLogin, string accountStoragePath) + { + var accountFilename = GetAccountFile(userLogin, accountStoragePath); + + var accountInfo = GetAccountSummary(accountFilename); + var totalLine = accountInfo[4].Split(": $"); + + Console.Write("Please enter the amount you want to withdraw: "); + var withdrawAmount = int.Parse(Console.ReadLine()); + var currentTotal = int.Parse(totalLine[1]); + var finalTotalAmount = currentTotal - withdrawAmount; + + while (withdrawAmount is string && withdrawAmount < 0) + { + Console.WriteLine("Your withdraw amount should be greater than Zero"); + withdrawAmount = int.Parse(Console.ReadLine()); + finalTotalAmount = currentTotal - withdrawAmount; + } + + if (finalTotalAmount < 0) + { + Console.WriteLine("Insufficient balance."); + } + else + { + var withdrawAmountLine = accountInfo[0].Split(": "); + var withdrawTimeLine = accountInfo[1].Split(": "); + withdrawAmountLine[1] = withdrawAmount.ToString(); + withdrawTimeLine[1] = DateTime.Now.ToString(); + + accountInfo[0] = string.Join(": ", withdrawAmountLine); + accountInfo[1] = string.Join(": ", withdrawTimeLine); + + totalLine[1] = finalTotalAmount.ToString(); + accountInfo[4] = string.Join(": $", totalLine); + + Console.WriteLine("You've withdrawn ${0} from your account", withdrawAmount); + + Console.WriteLine("\nYour have ${0} left in your account", finalTotalAmount); + + WriteFile.WriteLines(accountInfo, accountFilename); + } + + } + + public static void ViewAccountBalance(in string userLogin, string accountStoragePath) + { + var accountFilename = GetAccountFile(userLogin, accountStoragePath); + + var accountBalance = GetAccountSummary(accountFilename)[4]; + + Console.WriteLine("Your account balance is {0}", accountBalance); + } + + public static void ViewAccountSummary(in string userLogin, string accountStoragePath) + { + var accountFilename = GetAccountFile(userLogin, accountStoragePath); + + var accountInfo = GetAccountSummary(accountFilename); + + foreach (var info in accountInfo) + { + Console.WriteLine(info); + } + } +} \ No newline at end of file diff --git a/BankingSoftware/Modules/AuthModule.cs b/BankingSoftware/Modules/AuthModule.cs new file mode 100644 index 0000000..a3a3723 --- /dev/null +++ b/BankingSoftware/Modules/AuthModule.cs @@ -0,0 +1,41 @@ +using System; +using System.IO; +using BankingSoftware.Common; + +namespace BankingSoftware.Modules; + +public static class AuthModule +{ + public static string SignIn(string userStoragePath) + { + Console.Write("Please enter your username: "); + var username = Console.ReadLine(); + + Console.Write("Please enter your password: "); + var password = Console.ReadLine(); + var hashedPassword = new Hash().HashValues(password.Split()); + + var userLogin = new Hash().HashValues(new string[] { username, hashedPassword }); + var hashedUsername = new Hash().HashValues(username.Split()); + var userExist = File.Exists(userStoragePath + hashedUsername + ".txt"); + + while(!userExist) + { + Console.WriteLine("Incorrect username or password, please try again"); + Console.Write("Please enter your username: "); + username = Console.ReadLine(); + + Console.Write("Please enter your password: "); + password = Console.ReadLine(); + hashedPassword = new Hash().HashValues(password.Split()); + + userLogin = new Hash().HashValues(new string[] { username, hashedPassword }); + hashedUsername = new Hash().HashValues(username.Split()); + userExist = File.Exists(userStoragePath + hashedUsername + ".txt"); + } + + Console.WriteLine("Welcome {0}", username); + + return userLogin; + } +} \ No newline at end of file diff --git a/BankingSoftware/Modules/SignupModule.cs b/BankingSoftware/Modules/SignupModule.cs new file mode 100644 index 0000000..76ee926 --- /dev/null +++ b/BankingSoftware/Modules/SignupModule.cs @@ -0,0 +1,93 @@ +using System; +using System.Linq; +using System.Text; +using BankingSoftware.Common; + +namespace BankingSoftware.Modules; + +public class SignupModule +{ + //const string StoragePath = + + public static string Signup(string userStoragePath) + { + string[] userKeys = { "First Name: ", "Last Name: ", "Email: ", "Username: ", "Age: ", "Phone Number: ", + "HashedPassword: " }; + var userValues = new string[userKeys.Length + 1]; + + Console.WriteLine("Please answer the questions below to signup..."); + AddSymbol.AddBreakLine(symbol:" "); + + Console.Write("Enter your first name: "); + var firstName = Console.ReadLine(); + VerifyInput.VerifyForStringOnly(firstName); + userValues[0] = (userKeys[0] + firstName); + AddSymbol.AddBreakLine(); + + Console.Write("Enter your last name: "); + var lastName = Console.ReadLine(); + VerifyInput.VerifyForStringOnly(lastName); + userValues[1] = (userKeys[1] + lastName); + AddSymbol.AddBreakLine(); + + Console.Write("Enter your email: "); + var email = Console.ReadLine(); + email = VerifyInput.VerifyEmail(email); + userValues[2] = (userKeys[2] + email); + AddSymbol.AddBreakLine(); + + Console.Write("Enter your username: "); + var username = Console.ReadLine(); + username = VerifyInput.VerifyForStringAndNumber(username); + userValues[3] = (userKeys[3] + username); + AddSymbol.AddBreakLine(); + + Console.Write("Enter your age: "); + var age = int.Parse(Console.ReadLine()); + age = VerifyInput.VerifyNumber(age); + while(age < 18 || age > 100) + { + Console.WriteLine("Age should be between 18 and 100"); + Console.Write("Input new value"); + Console.ReadLine(); + + age = int.Parse(Console.ReadLine()); + VerifyInput.VerifyNumber(age); + } + userValues[4] = (userKeys[4] + age); + AddSymbol.AddBreakLine(); + + Console.Write("Enter your phone number: "); + var phoneNumber = Console.ReadLine(); + phoneNumber = VerifyInput.VerifyPhoneNumber(phoneNumber); + userValues[5] = (userKeys[5] + phoneNumber); + AddSymbol.AddBreakLine(); + + Console.Write("Enter your password: "); + var password = Console.ReadLine(); + password = VerifyInput.VerifyPassword(password); + var hashedPassword = new Hash().HashValues(password.Split()); + userValues[6] = (userKeys[6] + hashedPassword); + AddSymbol.AddBreakLines(lineLength: 2); + + var hashedUsername = new Hash().HashValues(username.Split()); + userStoragePath += "\\" + hashedUsername + ".txt"; + WriteFile.WriteLines(userValues, userStoragePath); + + var credentials = new Hash().HashValues(new[] { username, hashedPassword }); + + return credentials; + } + + public static void CreateAccount(in string userLogin, string accountStoragePath) + { + string[] accountValues = { "Last withdrawal amount: $0", "Last withdrawal time: ", "Last deposit amount: $0", + "Last deposit time: " , "Total: $0"}; + + var charFileName = userLogin.Split(); + var hashedFileName = new Hash().HashValues(charFileName) ; + + var accountFilename = accountStoragePath + hashedFileName + ".txt"; + WriteFile.WriteLines(accountValues, accountFilename); + } +} \ No newline at end of file diff --git a/BankingSoftware/Program.cs b/BankingSoftware/Program.cs new file mode 100644 index 0000000..c178dd9 --- /dev/null +++ b/BankingSoftware/Program.cs @@ -0,0 +1,111 @@ +using System; +using BankingSoftware.Common; +using BankingSoftware.Modules; + +namespace BankingSoftware; + +class Program +{ + const string userStoragePath = @"C:\Users\rexxr\Documents\projectss\CodeBenders\BankingSoftware\Storage\Users\"; + + const string accountStoragePath = + @"C:\Users\rexxr\Documents\projectss\CodeBenders\BankingSoftware\Storage\Accounts\"; + + public static void Auth(out string userLogin) + { + userLogin = ""; + Console.WriteLine("\nWelcome to our console banking app"); + AddSymbol.AddBreakLines(symbol: "*"); + + Console.WriteLine("Please login or signup. \n"); + Console.WriteLine("Press 1 to login or 2 to signup"); + Console.Write(">>>>> "); + var loginSignup = int.Parse(Console.ReadLine()); + Console.WriteLine("\n"); + + while (loginSignup is string || loginSignup > 2) + { + Console.WriteLine("Input the correct value..."); + Console.Write(">>>>> "); + loginSignup = int.Parse(Console.ReadLine()); + } + switch (loginSignup) + { + case 1: + userLogin = AuthModule.SignIn(userStoragePath); + break; + case 2: + userLogin = SignupModule.Signup(userStoragePath); + SignupModule.CreateAccount(userLogin, accountStoragePath); + break; + + } + + AddSymbol.AddBreakLine(); + } + + public static void Account(in string userLogin) + { + Console.WriteLine("What action do you want to perform"); + Console.WriteLine("Press 1 to VIEW ACCOUNT SUMMARY"); + Console.WriteLine("Press 2 to VIEW BALANCE"); + Console.WriteLine("Press 3 to DEPOSIT"); + Console.WriteLine("Press 4 to WITHDRAW"); + + Console.Write(">>>>> "); + var action = int.Parse(Console.ReadLine()); + + switch (action) + { + case 1: + AccountModule.ViewAccountSummary(in userLogin, accountStoragePath); + break; + case 2: + AccountModule.ViewAccountBalance(in userLogin, accountStoragePath); + break; + case 3: + AccountModule.Deposit(in userLogin, accountStoragePath); + break; + case 4: + AccountModule.Withdraw(in userLogin, accountStoragePath); + break; + default: + Console.WriteLine("Input the correct value"); + break; + } + + Console.WriteLine("Do you want to perform another transaction"); + Console.WriteLine("............ Press 1 to continue"); + Console.WriteLine("............ Press 2 to quit"); + Console.Write(">>>>> "); + var anotherAction = int.Parse(Console.ReadLine()); + + while (anotherAction > 2) + { + Console.WriteLine("Please input the correct value."); + Console.Write(">>>>> "); + anotherAction = int.Parse(Console.ReadLine()); + } + while (anotherAction == 1) + { + Account(in userLogin); + Console.Write(">>>>> "); + anotherAction = int.Parse(Console.ReadLine()); + } + + if (anotherAction == 2) + { + Console.WriteLine("Thank you for banking with us..."); + Console.WriteLine("Goodbye."); + } + + AddSymbol.AddBreakLines(symbol: "*"); + } + + static void Main() + { + Auth(out var userLogin); + + Account(in userLogin); + } +} \ No newline at end of file diff --git a/BankingSoftware/Storage/Accounts/5FE5F846BD5D98FFF20F1384D0DFC710.txt b/BankingSoftware/Storage/Accounts/5FE5F846BD5D98FFF20F1384D0DFC710.txt new file mode 100644 index 0000000..9f6b71d --- /dev/null +++ b/BankingSoftware/Storage/Accounts/5FE5F846BD5D98FFF20F1384D0DFC710.txt @@ -0,0 +1,5 @@ +Last withdrawal amount: 20000 +Last withdrawal time: 3/1/2023 10:02:54 AM +Last deposit amount: 90000 +Last deposit time: 3/1/2023 10:02:43 AM +Total: $70000 diff --git a/BankingSoftware/Storage/Users/2947CB1B92BB13C8F373E78C76FCA765.txt b/BankingSoftware/Storage/Users/2947CB1B92BB13C8F373E78C76FCA765.txt new file mode 100644 index 0000000..8cb0584 --- /dev/null +++ b/BankingSoftware/Storage/Users/2947CB1B92BB13C8F373E78C76FCA765.txt @@ -0,0 +1,8 @@ +First Name: Ade +Last Name: Bendel +Email: ade@bendel.com +Username: ade +Age: 78 +Phone Number: 9834563247 +HashedPassword: D84BA9FED904447C1BE566032AF633CD + From fc725871f14a686d23297c47df93b7ae69160c1b Mon Sep 17 00:00:00 2001 From: rexx07 <56883674+rexx07@users.noreply.github.com> Date: Thu, 2 Mar 2023 04:25:16 -0500 Subject: [PATCH 2/5] amends --- .gitignore | 3 +++ .idea/.idea.BankingSoftware/.idea/.gitignore | 13 ------------- .idea/.idea.BankingSoftware/.idea/encodings.xml | 4 ---- .idea/.idea.BankingSoftware/.idea/indexLayout.xml | 8 -------- .idea/.idea.BankingSoftware/.idea/vcs.xml | 6 ------ BankingSoftware/Program.cs | 8 +++++--- BankingSoftware.sln => CodeBenders.sln | 0 7 files changed, 8 insertions(+), 34 deletions(-) delete mode 100644 .idea/.idea.BankingSoftware/.idea/.gitignore delete mode 100644 .idea/.idea.BankingSoftware/.idea/encodings.xml delete mode 100644 .idea/.idea.BankingSoftware/.idea/indexLayout.xml delete mode 100644 .idea/.idea.BankingSoftware/.idea/vcs.xml rename BankingSoftware.sln => CodeBenders.sln (100%) diff --git a/.gitignore b/.gitignore index dfcfd56..30fbdce 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,9 @@ *.userosscache *.sln.docstates +# directories +.idea + # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs diff --git a/.idea/.idea.BankingSoftware/.idea/.gitignore b/.idea/.idea.BankingSoftware/.idea/.gitignore deleted file mode 100644 index 794932b..0000000 --- a/.idea/.idea.BankingSoftware/.idea/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Rider ignored files -/modules.xml -/contentModel.xml -/projectSettingsUpdater.xml -/.idea.BankingSoftware.iml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/.idea.BankingSoftware/.idea/encodings.xml b/.idea/.idea.BankingSoftware/.idea/encodings.xml deleted file mode 100644 index df87cf9..0000000 --- a/.idea/.idea.BankingSoftware/.idea/encodings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/.idea.BankingSoftware/.idea/indexLayout.xml b/.idea/.idea.BankingSoftware/.idea/indexLayout.xml deleted file mode 100644 index 7b08163..0000000 --- a/.idea/.idea.BankingSoftware/.idea/indexLayout.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/.idea.BankingSoftware/.idea/vcs.xml b/.idea/.idea.BankingSoftware/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/.idea.BankingSoftware/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/BankingSoftware/Program.cs b/BankingSoftware/Program.cs index c178dd9..1241f82 100644 --- a/BankingSoftware/Program.cs +++ b/BankingSoftware/Program.cs @@ -8,9 +8,8 @@ class Program { const string userStoragePath = @"C:\Users\rexxr\Documents\projectss\CodeBenders\BankingSoftware\Storage\Users\"; - const string accountStoragePath = - @"C:\Users\rexxr\Documents\projectss\CodeBenders\BankingSoftware\Storage\Accounts\"; - + const string accountStoragePath = @"C:\Users\rexxr\Documents\projectss\CodeBenders\BankingSoftware\Storage\Accounts\"; + public static void Auth(out string userLogin) { userLogin = ""; @@ -104,6 +103,9 @@ public static void Account(in string userLogin) static void Main() { + Console.WriteLine(userStoragePath); + Console.WriteLine(accountStoragePath); + Auth(out var userLogin); Account(in userLogin); diff --git a/BankingSoftware.sln b/CodeBenders.sln similarity index 100% rename from BankingSoftware.sln rename to CodeBenders.sln From fbcf1813699a4fe26d762fb139e778fa525c0598 Mon Sep 17 00:00:00 2001 From: SunkanmiKingin <126165259+SunkanmiKingin@users.noreply.github.com> Date: Thu, 2 Mar 2023 04:30:09 -0500 Subject: [PATCH 3/5] Delete .idea/.idea.BankingSoftware/.idea directory --- .idea/.idea.BankingSoftware/.idea/.gitignore | 13 ------------- .idea/.idea.BankingSoftware/.idea/encodings.xml | 4 ---- .idea/.idea.BankingSoftware/.idea/indexLayout.xml | 8 -------- .idea/.idea.BankingSoftware/.idea/vcs.xml | 6 ------ 4 files changed, 31 deletions(-) delete mode 100644 .idea/.idea.BankingSoftware/.idea/.gitignore delete mode 100644 .idea/.idea.BankingSoftware/.idea/encodings.xml delete mode 100644 .idea/.idea.BankingSoftware/.idea/indexLayout.xml delete mode 100644 .idea/.idea.BankingSoftware/.idea/vcs.xml diff --git a/.idea/.idea.BankingSoftware/.idea/.gitignore b/.idea/.idea.BankingSoftware/.idea/.gitignore deleted file mode 100644 index 794932b..0000000 --- a/.idea/.idea.BankingSoftware/.idea/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Rider ignored files -/modules.xml -/contentModel.xml -/projectSettingsUpdater.xml -/.idea.BankingSoftware.iml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/.idea.BankingSoftware/.idea/encodings.xml b/.idea/.idea.BankingSoftware/.idea/encodings.xml deleted file mode 100644 index df87cf9..0000000 --- a/.idea/.idea.BankingSoftware/.idea/encodings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/.idea.BankingSoftware/.idea/indexLayout.xml b/.idea/.idea.BankingSoftware/.idea/indexLayout.xml deleted file mode 100644 index 7b08163..0000000 --- a/.idea/.idea.BankingSoftware/.idea/indexLayout.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/.idea.BankingSoftware/.idea/vcs.xml b/.idea/.idea.BankingSoftware/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/.idea.BankingSoftware/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From 25c7703f8cb55c802884bad5202afb28e583191d Mon Sep 17 00:00:00 2001 From: sunkanmi Date: Sat, 11 Mar 2023 10:07:35 -0500 Subject: [PATCH 4/5] libraryManagementAdded --- LibraryManagementApp/ApiResponse/ApiResult.cs | 152 ++++++++++++++++++ .../RepositoryContracts/IAuthorRepository.cs | 8 + .../RepositoryContracts/IBookRepository.cs | 8 + .../ICategoryRepository.cs | 8 + .../IPublisherRepository.cs | 8 + .../RepositoryContracts/IRepositoryBase.cs | 15 ++ .../RepositoryContracts/IUnitOfWork.cs | 11 ++ .../Controllers/AuthorController.cs | 122 ++++++++++++++ .../Controllers/BookController.cs | 134 +++++++++++++++ .../Controllers/CategoryController.cs | 113 +++++++++++++ LibraryManagementApp/Domain/BaseEntity.cs | 23 +++ LibraryManagementApp/Dtos/AuthorDto.cs | 11 ++ LibraryManagementApp/Dtos/BookDto.cs | 16 ++ LibraryManagementApp/Dtos/CategoryDto.cs | 8 + LibraryManagementApp/Dtos/PublisherDto.cs | 10 ++ .../Persistence/ApplicationDbContext.cs | 26 +++ .../Configurations/AuthorConfiguration.cs | 15 ++ .../Configurations/BookConfiguration.cs | 17 ++ .../Configurations/CategoryConfiguration.cs | 16 ++ .../Configurations/PublisherConfiguration.cs | 16 ++ .../Repository/AuthorRepository.cs | 11 ++ .../Persistence/Repository/BookRepository.cs | 11 ++ .../Repository/CategoryRepository.cs | 11 ++ .../Repository/PublisherRepository.cs | 11 ++ .../Persistence/Repository/RepositoryBase.cs | 45 ++++++ .../Persistence/Repository/UnitOfWork.cs | 28 ++++ 26 files changed, 854 insertions(+) create mode 100644 LibraryManagementApp/ApiResponse/ApiResult.cs create mode 100644 LibraryManagementApp/Contracts/RepositoryContracts/IAuthorRepository.cs create mode 100644 LibraryManagementApp/Contracts/RepositoryContracts/IBookRepository.cs create mode 100644 LibraryManagementApp/Contracts/RepositoryContracts/ICategoryRepository.cs create mode 100644 LibraryManagementApp/Contracts/RepositoryContracts/IPublisherRepository.cs create mode 100644 LibraryManagementApp/Contracts/RepositoryContracts/IRepositoryBase.cs create mode 100644 LibraryManagementApp/Contracts/RepositoryContracts/IUnitOfWork.cs create mode 100644 LibraryManagementApp/Controllers/AuthorController.cs create mode 100644 LibraryManagementApp/Controllers/BookController.cs create mode 100644 LibraryManagementApp/Controllers/CategoryController.cs create mode 100644 LibraryManagementApp/Domain/BaseEntity.cs create mode 100644 LibraryManagementApp/Dtos/AuthorDto.cs create mode 100644 LibraryManagementApp/Dtos/BookDto.cs create mode 100644 LibraryManagementApp/Dtos/CategoryDto.cs create mode 100644 LibraryManagementApp/Dtos/PublisherDto.cs create mode 100644 LibraryManagementApp/Persistence/ApplicationDbContext.cs create mode 100644 LibraryManagementApp/Persistence/Configurations/AuthorConfiguration.cs create mode 100644 LibraryManagementApp/Persistence/Configurations/BookConfiguration.cs create mode 100644 LibraryManagementApp/Persistence/Configurations/CategoryConfiguration.cs create mode 100644 LibraryManagementApp/Persistence/Configurations/PublisherConfiguration.cs create mode 100644 LibraryManagementApp/Persistence/Repository/AuthorRepository.cs create mode 100644 LibraryManagementApp/Persistence/Repository/BookRepository.cs create mode 100644 LibraryManagementApp/Persistence/Repository/CategoryRepository.cs create mode 100644 LibraryManagementApp/Persistence/Repository/PublisherRepository.cs create mode 100644 LibraryManagementApp/Persistence/Repository/RepositoryBase.cs create mode 100644 LibraryManagementApp/Persistence/Repository/UnitOfWork.cs diff --git a/LibraryManagementApp/ApiResponse/ApiResult.cs b/LibraryManagementApp/ApiResponse/ApiResult.cs new file mode 100644 index 0000000..e91825b --- /dev/null +++ b/LibraryManagementApp/ApiResponse/ApiResult.cs @@ -0,0 +1,152 @@ +using System.Linq.Dynamic.Core; +using System.Reflection; +using EFCore.BulkExtensions; +using Microsoft.EntityFrameworkCore; + +namespace LibraryManagementApp.ApiResponse; + +public class ApiResult +{ + /// + /// Private constructor called by the CreateAsync method. + /// + private ApiResult( + List data, + int count, + int pageIndex, + int pageSize, + string? filterQuery) + { + Data = data; + PageIndex = pageIndex; + PageSize = pageSize; + TotalCount = count; + TotalPages = (int)Math.Ceiling(count / (double)pageSize); + FilterQuery = filterQuery; + } + + #region Methods + + /// + /// Pages, sorts and/or filters a IQueryable source. + /// + /// An IQueryable source of generic + /// type + /// Zero-based current page index + /// (0 = first page) + /// The actual size of + /// each page + /// The filtering query (value to + /// lookup) + /// + /// A object containing the IQueryable paged/sorted/filtered + /// result + /// and all the relevant paging/sorting/filtering navigation + /// info. + /// + public static async Task> CreateAsync( + IQueryable source, + int pageIndex, + int pageSize, + string? filterQuery = null) + { + if (!string.IsNullOrEmpty(filterQuery)) + { + source = source.Where(filterQuery); + } + + var count = await source.CountAsync(); + + source = source + .Skip(pageIndex * pageSize) + .Take(pageSize); + +#if DEBUG + // retrieve the SQL query (for debug purposes) + var sql = source.ToParametrizedSql(); +#endif + + var data = await source.ToListAsync(); + + return new ApiResult( + data, + count, + pageIndex, + pageSize, + filterQuery); + } + + /// + /// Checks if the given property name exists + /// to protect against SQL injection attacks + /// + public static bool IsValidProperty( + string propertyName, + bool throwExceptionIfNotFound = true) + { + var prop = typeof(T).GetProperty( + propertyName, + BindingFlags.IgnoreCase | + BindingFlags.Public | + BindingFlags.Static | + BindingFlags.Instance); + if (prop == null && throwExceptionIfNotFound) + throw new NotSupportedException($"ERROR: Property '{propertyName}' does not exist."); + return prop != null; + } + + #endregion + + #region Properties + + /// + /// IQueryable data result to return. + /// + public List Data { get; private set; } + + /// + /// Zero-based index of current page. + /// + public int PageIndex { get; private set; } + + /// + /// Number of items contained in each page. + /// + public int PageSize { get; private set; } + + /// + /// Total items count + /// + public int TotalCount { get; private set; } + + /// + /// Total pages count + /// + public int TotalPages { get; private set; } + + /// + /// TRUE if the current page has a previous page, + /// FALSE otherwise. + /// + public bool HasPreviousPage + { + get { return (PageIndex > 0); } + } + + /// + /// TRUE if the current page has a next page, FALSE otherwise. + /// + public bool HasNextPage + { + get { return ((PageIndex + 1) < TotalPages); } + } + + + /// + /// Filter Query string + /// (to be used within the given FilterColumn) + /// + public string? FilterQuery { get; set; } + + #endregion +} \ No newline at end of file diff --git a/LibraryManagementApp/Contracts/RepositoryContracts/IAuthorRepository.cs b/LibraryManagementApp/Contracts/RepositoryContracts/IAuthorRepository.cs new file mode 100644 index 0000000..2253c59 --- /dev/null +++ b/LibraryManagementApp/Contracts/RepositoryContracts/IAuthorRepository.cs @@ -0,0 +1,8 @@ +using LibraryManagementApp.Domain; + +namespace LibraryManagementApp.Contracts.RepositoryContracts; + +public interface IAuthorRepository: IRepositoryBase +{ + +} \ No newline at end of file diff --git a/LibraryManagementApp/Contracts/RepositoryContracts/IBookRepository.cs b/LibraryManagementApp/Contracts/RepositoryContracts/IBookRepository.cs new file mode 100644 index 0000000..e6bac7d --- /dev/null +++ b/LibraryManagementApp/Contracts/RepositoryContracts/IBookRepository.cs @@ -0,0 +1,8 @@ +using LibraryManagementApp.Domain; + +namespace LibraryManagementApp.Contracts.RepositoryContracts; + +public interface IBookRepository: IRepositoryBase +{ + +} \ No newline at end of file diff --git a/LibraryManagementApp/Contracts/RepositoryContracts/ICategoryRepository.cs b/LibraryManagementApp/Contracts/RepositoryContracts/ICategoryRepository.cs new file mode 100644 index 0000000..9575d32 --- /dev/null +++ b/LibraryManagementApp/Contracts/RepositoryContracts/ICategoryRepository.cs @@ -0,0 +1,8 @@ +using LibraryManagementApp.Domain; + +namespace LibraryManagementApp.Contracts.RepositoryContracts; + +public interface ICategoryRepository: IRepositoryBase +{ + +} \ No newline at end of file diff --git a/LibraryManagementApp/Contracts/RepositoryContracts/IPublisherRepository.cs b/LibraryManagementApp/Contracts/RepositoryContracts/IPublisherRepository.cs new file mode 100644 index 0000000..b7aa2f2 --- /dev/null +++ b/LibraryManagementApp/Contracts/RepositoryContracts/IPublisherRepository.cs @@ -0,0 +1,8 @@ +using LibraryManagementApp.Domain; + +namespace LibraryManagementApp.Contracts.RepositoryContracts; + +public interface IPublisherRepository: IRepositoryBase +{ + +} \ No newline at end of file diff --git a/LibraryManagementApp/Contracts/RepositoryContracts/IRepositoryBase.cs b/LibraryManagementApp/Contracts/RepositoryContracts/IRepositoryBase.cs new file mode 100644 index 0000000..032d48e --- /dev/null +++ b/LibraryManagementApp/Contracts/RepositoryContracts/IRepositoryBase.cs @@ -0,0 +1,15 @@ +using System.Linq.Expressions; + +namespace LibraryManagementApp.Contracts.RepositoryContracts; + +public interface IRepositoryBase where T: class +{ + T FindById(Guid id); + IQueryable FindByCondition(Expression> expression, bool trackChanges); + IQueryable FindAll(bool trackChanges); + void Add(T entity); + void AddRange(IEnumerable entities); + void Update(T entity); + void Remove(T entity); + void RemoveRange(IEnumerable entities); +} \ No newline at end of file diff --git a/LibraryManagementApp/Contracts/RepositoryContracts/IUnitOfWork.cs b/LibraryManagementApp/Contracts/RepositoryContracts/IUnitOfWork.cs new file mode 100644 index 0000000..49bc705 --- /dev/null +++ b/LibraryManagementApp/Contracts/RepositoryContracts/IUnitOfWork.cs @@ -0,0 +1,11 @@ +namespace LibraryManagementApp.Contracts.RepositoryContracts; + +public interface IUnitOfWork: IDisposable +{ + IAuthorRepository Authors { get; } + IBookRepository Books { get; } + IPublisherRepository Publishers { get; } + ICategoryRepository Categories { get; } + + int Complete(); +} \ No newline at end of file diff --git a/LibraryManagementApp/Controllers/AuthorController.cs b/LibraryManagementApp/Controllers/AuthorController.cs new file mode 100644 index 0000000..b39220c --- /dev/null +++ b/LibraryManagementApp/Controllers/AuthorController.cs @@ -0,0 +1,122 @@ +using LibraryManagementApp.ApiResponse; +using LibraryManagementApp.Contracts.RepositoryContracts; +using LibraryManagementApp.Domain; +using LibraryManagementApp.Dtos; +using LibraryManagementApp.Exceptions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LibraryManagementApp.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class AuthorController: ControllerBase +{ + private readonly IUnitOfWork _unitOfWork; + private readonly ILogger _logger; + + public AuthorController(ILogger logger, IUnitOfWork unitOfWork) + { + _logger = logger; + _unitOfWork = unitOfWork; + } + + [HttpGet] + public async Task>> GetAuthors( + bool trackChanges, + int pageIndex = 0, + int pageSize = 10, + string? filterQuery = null) + { + var authors = await ApiResult.CreateAsync( + _unitOfWork.Authors.FindAll(trackChanges).Select( + a => new AuthorDto() + { + Id = a.Id, + FirstName = a.FirstName, + LastName = a.LastName, + PublisherName = a.Publisher.Name, + AvatarUrl = a.AvatarUrl + }), + pageIndex, + pageSize, + filterQuery); + + return authors; + } + + [HttpGet("{id}")] + public async Task> GetAuthorById(Guid id, bool trackChanges) + { + var entity = await _unitOfWork.Authors + .FindByCondition(a => a.Id.Equals(id), trackChanges) + .Select(a => new AuthorDto() + { + Id = a.Id, + FirstName = a.FirstName, + LastName = a.LastName, + PublisherName = a.Publisher.Name, + AvatarUrl = a.AvatarUrl + }) + .FirstOrDefaultAsync(); + + if (entity == null) + throw new NotFoundException(); + + return entity; + } + + [HttpPost] + public ActionResult CreateAuthor(AuthorDto author) + { + var entity = new Author + { + FirstName = author.FirstName, + LastName = author.LastName, + AvatarUrl = author.AvatarUrl, + PublisherId = author.PublisherId + }; + + _unitOfWork.Authors.Add(entity); + _unitOfWork.Complete(); + _unitOfWork.Dispose(); + + return Ok(); + } + + [HttpPut("{id:guid}")] + public ActionResult UpdateAuthor(AuthorDto author, Guid id) + { + var entity = _unitOfWork.Authors.FindById(id); + + if (entity == null) + throw new NotFoundException(); + + entity.FirstName = author.FirstName; + entity.LastName = author.LastName; + entity.AvatarUrl = author.AvatarUrl; + entity.PublisherId = author.PublisherId; + + + _unitOfWork.Authors.Update(entity); + _unitOfWork.Complete(); + _unitOfWork.Dispose(); + + return Ok(); + } + + [HttpDelete("{id:guid}")] + public ActionResult DeleteAuthor(Guid id) + { + var entity = _unitOfWork.Authors.FindById(id); + + if (entity == null) + throw new NotFoundException(); + + _unitOfWork.Authors.Remove(entity); + _unitOfWork.Complete(); + _unitOfWork.Dispose(); + + return Ok(); + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Controllers/BookController.cs b/LibraryManagementApp/Controllers/BookController.cs new file mode 100644 index 0000000..fb3ba4b --- /dev/null +++ b/LibraryManagementApp/Controllers/BookController.cs @@ -0,0 +1,134 @@ +using LibraryManagementApp.ApiResponse; +using LibraryManagementApp.Contracts.RepositoryContracts; +using LibraryManagementApp.Domain; +using LibraryManagementApp.Dtos; +using LibraryManagementApp.Exceptions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LibraryManagementApp.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class BookController : ControllerBase +{ + private readonly IUnitOfWork _unitOfWork; + private readonly ILogger _logger; + + public BookController(ILogger logger, IUnitOfWork unitOfWork) + { + _logger = logger; + _unitOfWork = unitOfWork; + } + + [HttpGet] + public async Task>> GetBooks( + bool trackChanges, + int pageIndex = 0, + int pageSize = 10, + string? filterQuery = null) + { + var books = await ApiResult.CreateAsync( + _unitOfWork.Books.FindAll(trackChanges).Select( + b => new BookDto() + { + Id = b.Id, + Title = b.Title, + CopyrightNotice = b.CopyrightNotice, + ISBN = b.ISBN, + AuthorName = b.Author.FirstName, + PageLength = b.PageLength, + Language = b.Language, + ReleaseYear = b.ReleaseYear + }), + pageIndex, + pageSize, + filterQuery); + + return books; + } + + [HttpGet("{id}")] + public async Task> GetBookById(Guid id, bool trackChanges) + { + var entity = await _unitOfWork.Books + .FindByCondition(a => a.Id.Equals(id), trackChanges) + .Select(b => new BookDto() + { + Id = b.Id, + Title = b.Title, + CopyrightNotice = b.CopyrightNotice, + ISBN = b.ISBN, + AuthorName = b.Author.FirstName, + PageLength = b.PageLength, + Language = b.Language, + ReleaseYear = b.ReleaseYear + }) + .FirstOrDefaultAsync(); + + if (entity == null) + throw new NotFoundException(); + + return entity; + } + + [HttpPost] + public ActionResult CreateBook(BookDto book) + { + var author = _unitOfWork.Authors.FindById(book.AuthourId); + var entity = new Book + { + Title = book.Title, + CopyrightNotice = book.CopyrightNotice, + ISBN = book.ISBN, + AuthorId = author.Id, + PageLength = book.PageLength, + Language = book.Language, + ReleaseYear = book.ReleaseYear + }; + + _unitOfWork.Books.Add(entity); + _unitOfWork.Complete(); + _unitOfWork.Dispose(); + + return Ok(); + } + + [HttpPut("{id:guid}")] + public ActionResult UpdateBook(BookDto book, Guid id) + { + var entity = _unitOfWork.Books.FindById(id); + + if (entity == null) + throw new NotFoundException(); + + entity.Title = book.Title; + entity.CopyrightNotice = book.CopyrightNotice; + entity.ISBN = book.ISBN; + entity.PageLength = book.PageLength; + entity.Language = book.Language; + entity.ReleaseYear = book.ReleaseYear; + + + _unitOfWork.Books.Update(entity); + _unitOfWork.Complete(); + _unitOfWork.Dispose(); + + return Ok(); + } + + [HttpDelete("{id:guid}")] + public ActionResult DeleteBook(Guid id) + { + var entity = _unitOfWork.Books.FindById(id); + + if (entity == null) + throw new NotFoundException(); + + _unitOfWork.Books.Remove(entity); + _unitOfWork.Complete(); + _unitOfWork.Dispose(); + + return Ok(); + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Controllers/CategoryController.cs b/LibraryManagementApp/Controllers/CategoryController.cs new file mode 100644 index 0000000..0392adf --- /dev/null +++ b/LibraryManagementApp/Controllers/CategoryController.cs @@ -0,0 +1,113 @@ +using LibraryManagementApp.ApiResponse; +using LibraryManagementApp.Contracts.RepositoryContracts; +using LibraryManagementApp.Domain; +using LibraryManagementApp.Dtos; +using LibraryManagementApp.Exceptions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LibraryManagementApp.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class CategoryController: ControllerBase +{ + private readonly IUnitOfWork _unitOfWork; + private readonly ILogger _logger; + + public CategoryController(ILogger logger, IUnitOfWork unitOfWork) + { + _logger = logger; + _unitOfWork = unitOfWork; + } + + [HttpGet] + public async Task>> GetPublishers( + bool trackChanges, + int pageIndex = 0, + int pageSize = 10, + string? filterQuery = null) + { + var categories = await ApiResult.CreateAsync( + _unitOfWork.Categories.FindAll(trackChanges).Select( + c => new CategoryDto() + { + Id = c.Id, + Name = c.Name, + Description = c.Description + }), + pageIndex, + pageSize, + filterQuery); + + return categories; + } + + [HttpGet("{id}")] + public async Task> GetCategoryById(Guid id, bool trackChanges) + { + var entity = await _unitOfWork.Categories + .FindByCondition(a => a.Id.Equals(id), trackChanges) + .Select(c => new CategoryDto() + { + Id = c.Id, + Name = c.Name, + Description = c.Description + }) + .FirstOrDefaultAsync(); + + if (entity == null) + throw new NotFoundException(); + + return entity; + } + + [HttpPost] + public ActionResult CreateCategory(CategoryDto category) + { + var entity = new Category + { + Name = category.Name, + Description = category.Description, + }; + + _unitOfWork.Categories.Add(entity); + _unitOfWork.Complete(); + _unitOfWork.Dispose(); + + return Ok(); + } + + [HttpPut("{id:guid}")] + public ActionResult UpdateCategory(CategoryDto category, Guid id) + { + var entity = _unitOfWork.Categories.FindById(id); + + if (entity == null) + throw new NotFoundException(); + + entity.Name = category.Name; + entity.Description = category.Description; + + _unitOfWork.Categories.Update(entity); + _unitOfWork.Complete(); + _unitOfWork.Dispose(); + + return Ok(); + } + + [HttpDelete("{id:guid}")] + public ActionResult DeleteCategory(Guid id) + { + var entity = _unitOfWork.Categories.FindById(id); + + if (entity == null) + throw new NotFoundException(); + + _unitOfWork.Categories.Remove(entity); + _unitOfWork.Complete(); + _unitOfWork.Dispose(); + + return Ok(); + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Domain/BaseEntity.cs b/LibraryManagementApp/Domain/BaseEntity.cs new file mode 100644 index 0000000..5f3c4ea --- /dev/null +++ b/LibraryManagementApp/Domain/BaseEntity.cs @@ -0,0 +1,23 @@ +namespace LibraryManagementApp.Domain; + +public class BaseEntity +{ + public BaseEntity() + { + + } + + public BaseEntity(Guid id, DateTime createdOn) + { + Id = new Guid(); + CreatedOn = createdOn; + } + + public Guid Id { get; init; } + public DateTime CreatedOn { get; init; } + public Guid CreatedBy { get; init; } + public DateTime ModifiedOn { get; set; } + public Guid ModifiedBy { get; set; } + public DateTime DeletedOn { get; init; } + public Guid DeletedBy { get; init; } +} \ No newline at end of file diff --git a/LibraryManagementApp/Dtos/AuthorDto.cs b/LibraryManagementApp/Dtos/AuthorDto.cs new file mode 100644 index 0000000..981bbeb --- /dev/null +++ b/LibraryManagementApp/Dtos/AuthorDto.cs @@ -0,0 +1,11 @@ +namespace LibraryManagementApp.Dtos; + +public record AuthorDto +{ + public Guid Id { get; init; } + public string FirstName{get; init;} + public string LastName{get; init;} + public string AvatarUrl{get; init;} + public string PublisherName{get; init;} + public Guid PublisherId {get; init;} +} \ No newline at end of file diff --git a/LibraryManagementApp/Dtos/BookDto.cs b/LibraryManagementApp/Dtos/BookDto.cs new file mode 100644 index 0000000..f4799a6 --- /dev/null +++ b/LibraryManagementApp/Dtos/BookDto.cs @@ -0,0 +1,16 @@ +using LibraryManagementApp.Domain; + +namespace LibraryManagementApp.Dtos; + +public record BookDto +{ + public Guid Id { get; init; } + public string Title{get; init;} + public string CopyrightNotice{get; init;} + public int ISBN{get; init;} + public string AuthorName { get; init; } + public Guid AuthourId { get; init; } + public int PageLength{get; init;} + public Language Language{get; init;} + public DateOnly ReleaseYear{get; init;} +} \ No newline at end of file diff --git a/LibraryManagementApp/Dtos/CategoryDto.cs b/LibraryManagementApp/Dtos/CategoryDto.cs new file mode 100644 index 0000000..fb156db --- /dev/null +++ b/LibraryManagementApp/Dtos/CategoryDto.cs @@ -0,0 +1,8 @@ +namespace LibraryManagementApp.Dtos; + +public record CategoryDto +{ + public Guid Id { get; init; } + public string Name{get; set;} + public string Description{get; set;} +} \ No newline at end of file diff --git a/LibraryManagementApp/Dtos/PublisherDto.cs b/LibraryManagementApp/Dtos/PublisherDto.cs new file mode 100644 index 0000000..e701323 --- /dev/null +++ b/LibraryManagementApp/Dtos/PublisherDto.cs @@ -0,0 +1,10 @@ +namespace LibraryManagementApp.Dtos; + +public record PublisherDto +{ + public Guid Id { get; init; } + public string Name{get; init;} + public string LogoUrl{get; init;} + public int? TotalAuthors { get; init; } + public int? TotalBooks { get; init; } +} \ No newline at end of file diff --git a/LibraryManagementApp/Persistence/ApplicationDbContext.cs b/LibraryManagementApp/Persistence/ApplicationDbContext.cs new file mode 100644 index 0000000..365bfea --- /dev/null +++ b/LibraryManagementApp/Persistence/ApplicationDbContext.cs @@ -0,0 +1,26 @@ +using LibraryManagementApp.Domain; +using LibraryManagementApp.Persistence.Configurations; +using Microsoft.EntityFrameworkCore; + +namespace LibraryManagementApp.Persistence; + +public class ApplicationDbContext: DbContext +{ + public ApplicationDbContext(DbContextOptions options) : base(options) + { + + } + + public DbSet Authors => Set(); + public DbSet Books => Set(); + public DbSet Categories => Set(); + public DbSet Publishers => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new AuthorConfiguration()); + modelBuilder.ApplyConfiguration(new BookConfiguration()); + modelBuilder.ApplyConfiguration(new CategoryConfiguration()); + modelBuilder.ApplyConfiguration(new PublisherConfiguration()); + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Persistence/Configurations/AuthorConfiguration.cs b/LibraryManagementApp/Persistence/Configurations/AuthorConfiguration.cs new file mode 100644 index 0000000..daa1236 --- /dev/null +++ b/LibraryManagementApp/Persistence/Configurations/AuthorConfiguration.cs @@ -0,0 +1,15 @@ +using LibraryManagementApp.Domain; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace LibraryManagementApp.Persistence.Configurations; + +public class AuthorConfiguration: IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.Property(a => a.Id) + .IsRequired(); + builder.HasMany(a => a.Books); + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Persistence/Configurations/BookConfiguration.cs b/LibraryManagementApp/Persistence/Configurations/BookConfiguration.cs new file mode 100644 index 0000000..64d2bd8 --- /dev/null +++ b/LibraryManagementApp/Persistence/Configurations/BookConfiguration.cs @@ -0,0 +1,17 @@ +using LibraryManagementApp.Domain; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace LibraryManagementApp.Persistence.Configurations; + +public class BookConfiguration: IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + /*builder.Property(b => b.Id) + .IsRequired(); + builder.HasOne(b => b.Author) + .WithMany(a => a.Books) + .HasForeignKey(b => b.AuthorId);*/ + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Persistence/Configurations/CategoryConfiguration.cs b/LibraryManagementApp/Persistence/Configurations/CategoryConfiguration.cs new file mode 100644 index 0000000..30cd565 --- /dev/null +++ b/LibraryManagementApp/Persistence/Configurations/CategoryConfiguration.cs @@ -0,0 +1,16 @@ +using LibraryManagementApp.Domain; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace LibraryManagementApp.Persistence.Configurations; + +public class CategoryConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.Property(b => b.Id) + .IsRequired(); + builder.HasMany(b => b.Books) + .WithMany(); + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Persistence/Configurations/PublisherConfiguration.cs b/LibraryManagementApp/Persistence/Configurations/PublisherConfiguration.cs new file mode 100644 index 0000000..7a1223a --- /dev/null +++ b/LibraryManagementApp/Persistence/Configurations/PublisherConfiguration.cs @@ -0,0 +1,16 @@ +using LibraryManagementApp.Domain; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace LibraryManagementApp.Persistence.Configurations; + +public class PublisherConfiguration: IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.Property(p => p.Id) + .IsRequired(); + builder.HasMany(p => p.Books) + .WithOne(); + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Persistence/Repository/AuthorRepository.cs b/LibraryManagementApp/Persistence/Repository/AuthorRepository.cs new file mode 100644 index 0000000..0884e18 --- /dev/null +++ b/LibraryManagementApp/Persistence/Repository/AuthorRepository.cs @@ -0,0 +1,11 @@ +using LibraryManagementApp.Contracts.RepositoryContracts; +using LibraryManagementApp.Domain; + +namespace LibraryManagementApp.Persistence.Repository; + +public class AuthorRepository: RepositoryBase, IAuthorRepository +{ + public AuthorRepository(ApplicationDbContext context) : base(context) + { + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Persistence/Repository/BookRepository.cs b/LibraryManagementApp/Persistence/Repository/BookRepository.cs new file mode 100644 index 0000000..4e151c0 --- /dev/null +++ b/LibraryManagementApp/Persistence/Repository/BookRepository.cs @@ -0,0 +1,11 @@ +using LibraryManagementApp.Contracts.RepositoryContracts; +using LibraryManagementApp.Domain; + +namespace LibraryManagementApp.Persistence.Repository; + +public class BookRepository: RepositoryBase, IBookRepository +{ + public BookRepository(ApplicationDbContext context) : base(context) + { + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Persistence/Repository/CategoryRepository.cs b/LibraryManagementApp/Persistence/Repository/CategoryRepository.cs new file mode 100644 index 0000000..7d5ed2b --- /dev/null +++ b/LibraryManagementApp/Persistence/Repository/CategoryRepository.cs @@ -0,0 +1,11 @@ +using LibraryManagementApp.Contracts.RepositoryContracts; +using LibraryManagementApp.Domain; + +namespace LibraryManagementApp.Persistence.Repository; + +public class CategoryRepository: RepositoryBase, ICategoryRepository +{ + public CategoryRepository(ApplicationDbContext context) : base(context) + { + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Persistence/Repository/PublisherRepository.cs b/LibraryManagementApp/Persistence/Repository/PublisherRepository.cs new file mode 100644 index 0000000..822a685 --- /dev/null +++ b/LibraryManagementApp/Persistence/Repository/PublisherRepository.cs @@ -0,0 +1,11 @@ +using LibraryManagementApp.Contracts.RepositoryContracts; +using LibraryManagementApp.Domain; + +namespace LibraryManagementApp.Persistence.Repository; + +public class PublisherRepository: RepositoryBase, IPublisherRepository +{ + public PublisherRepository(ApplicationDbContext context) : base(context) + { + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Persistence/Repository/RepositoryBase.cs b/LibraryManagementApp/Persistence/Repository/RepositoryBase.cs new file mode 100644 index 0000000..706136e --- /dev/null +++ b/LibraryManagementApp/Persistence/Repository/RepositoryBase.cs @@ -0,0 +1,45 @@ +using System.Linq.Expressions; +using LibraryManagementApp.Contracts.RepositoryContracts; +using Microsoft.EntityFrameworkCore; + +namespace LibraryManagementApp.Persistence.Repository; + +public class RepositoryBase: IRepositoryBase where T: class +{ + protected readonly ApplicationDbContext _context; + + public RepositoryBase(ApplicationDbContext context) => + _context = context; + + public T FindById(Guid id) => + _context.Set().Find(id); + + public IQueryable FindByCondition(Expression> expression, bool trackChanges) => + !trackChanges ? + _context.Set() + .Where(expression) + .AsNoTracking() : + _context.Set() + .Where(expression); + + public IQueryable FindAll(bool trackChanges) => + !trackChanges ? + _context.Set() + .AsNoTracking() : + _context.Set(); + + public void Add(T entity) => + _context.Set().Add(entity); + + public void AddRange(IEnumerable entities) => + _context.Set().AddRange(entities); + + public void Update(T entity) => + _context.Set().Update(entity); + + public void Remove(T entity) => + _context.Set().Remove(entity); + + public void RemoveRange(IEnumerable entities) => + _context.Set().RemoveRange(entities); +} \ No newline at end of file diff --git a/LibraryManagementApp/Persistence/Repository/UnitOfWork.cs b/LibraryManagementApp/Persistence/Repository/UnitOfWork.cs new file mode 100644 index 0000000..8b64d33 --- /dev/null +++ b/LibraryManagementApp/Persistence/Repository/UnitOfWork.cs @@ -0,0 +1,28 @@ +using LibraryManagementApp.Contracts.RepositoryContracts; + +namespace LibraryManagementApp.Persistence.Repository; + +public class UnitOfWork: IUnitOfWork +{ + private readonly ApplicationDbContext _context; + + public UnitOfWork(ApplicationDbContext context) + { + _context = context; + Authors = new AuthorRepository(_context); + Books = new BookRepository(_context); + Publishers = new PublisherRepository(_context); + Categories = new CategoryRepository(_context); + } + + public IAuthorRepository Authors { get; private set; } + public IBookRepository Books { get; private set; } + public IPublisherRepository Publishers { get; private set; } + public ICategoryRepository Categories { get; private set; } + + public int Complete() => + _context.SaveChanges(); + + public void Dispose() => + _context.Dispose(); +} \ No newline at end of file From cd94afc87195357f38b616348874b6f271183ce4 Mon Sep 17 00:00:00 2001 From: sunkanmi Date: Sat, 11 Mar 2023 10:07:35 -0500 Subject: [PATCH 5/5] libraryManagementAdded --- .../086591F1B63CEC976440DC2952AC4138.txt | 5 + .../0E84027714179DA860EC36BE3ABC2CCE.txt | 5 + .../1BBCFB82D95A66785DABF16FE16DD079.txt | 5 + .../7445BDA0F89FE36B4BC831F76CB0533A.txt | 5 + .../A91CCAE139380AA1368393C8FC2953C1.txt | 5 + .../B56FD7BF4AB39D813239C006C4FF5813.txt | 5 + .../6A3B12DA60987AF16E55F3FCEC2A6EED.txt | 8 + .../7A362B30780DD15E6D710EC44AA0E023.txt | 8 + .../C19291F87C625505E0CB012E128C3E33.txt | 8 + .../FA2111BDFC79125C0CF1D96B53B88FEB.txt | 8 + .../FE6FC17D485932CC797BB376BB501D44.txt | 8 + LibraryManagementApp/ApiResponse/ApiResult.cs | 152 ++++++++++ .../RepositoryContracts/IAuthorRepository.cs | 8 + .../RepositoryContracts/IBookRepository.cs | 8 + .../ICategoryRepository.cs | 8 + .../IPublisherRepository.cs | 8 + .../RepositoryContracts/IRepositoryBase.cs | 15 + .../RepositoryContracts/IUnitOfWork.cs | 11 + .../Controllers/AuthorController.cs | 122 ++++++++ .../Controllers/BookController.cs | 134 +++++++++ .../Controllers/CategoryController.cs | 113 ++++++++ .../Controllers/PublisherController.cs | 114 ++++++++ LibraryManagementApp/Domain/Author.cs | 40 +++ LibraryManagementApp/Domain/BaseEntity.cs | 23 ++ LibraryManagementApp/Domain/Book.cs | 26 ++ LibraryManagementApp/Domain/Category.cs | 24 ++ LibraryManagementApp/Domain/Language.cs | 11 + LibraryManagementApp/Domain/Publisher.cs | 52 ++++ LibraryManagementApp/Dtos/AuthorDto.cs | 11 + LibraryManagementApp/Dtos/BookDto.cs | 16 ++ LibraryManagementApp/Dtos/CategoryDto.cs | 8 + LibraryManagementApp/Dtos/PublisherDto.cs | 10 + .../Exceptions/NotFoundException.cs | 24 ++ .../LibraryManagementApp.csproj | 23 ++ .../20230309042120_Initial.Designer.cs | 262 ++++++++++++++++++ .../Migrations/20230309042120_Initial.cs | 168 +++++++++++ .../ApplicationDbContextModelSnapshot.cs | 259 +++++++++++++++++ .../Persistence/ApplicationDbContext.cs | 26 ++ .../Configurations/AuthorConfiguration.cs | 15 + .../Configurations/BookConfiguration.cs | 17 ++ .../Configurations/CategoryConfiguration.cs | 16 ++ .../Configurations/PublisherConfiguration.cs | 16 ++ .../Repository/AuthorRepository.cs | 11 + .../Persistence/Repository/BookRepository.cs | 11 + .../Repository/CategoryRepository.cs | 11 + .../Repository/PublisherRepository.cs | 11 + .../Persistence/Repository/RepositoryBase.cs | 45 +++ .../Persistence/Repository/UnitOfWork.cs | 28 ++ LibraryManagementApp/Program.cs | 41 +++ .../Properties/launchSettings.json | 41 +++ .../appsettings.Development.json | 8 + LibraryManagementApp/appsettings.json | 12 + 52 files changed, 2029 insertions(+) create mode 100644 BankingSoftware/Storage/Accounts/086591F1B63CEC976440DC2952AC4138.txt create mode 100644 BankingSoftware/Storage/Accounts/0E84027714179DA860EC36BE3ABC2CCE.txt create mode 100644 BankingSoftware/Storage/Accounts/1BBCFB82D95A66785DABF16FE16DD079.txt create mode 100644 BankingSoftware/Storage/Accounts/7445BDA0F89FE36B4BC831F76CB0533A.txt create mode 100644 BankingSoftware/Storage/Accounts/A91CCAE139380AA1368393C8FC2953C1.txt create mode 100644 BankingSoftware/Storage/Accounts/B56FD7BF4AB39D813239C006C4FF5813.txt create mode 100644 BankingSoftware/Storage/Users/6A3B12DA60987AF16E55F3FCEC2A6EED.txt create mode 100644 BankingSoftware/Storage/Users/7A362B30780DD15E6D710EC44AA0E023.txt create mode 100644 BankingSoftware/Storage/Users/C19291F87C625505E0CB012E128C3E33.txt create mode 100644 BankingSoftware/Storage/Users/FA2111BDFC79125C0CF1D96B53B88FEB.txt create mode 100644 BankingSoftware/Storage/Users/FE6FC17D485932CC797BB376BB501D44.txt create mode 100644 LibraryManagementApp/ApiResponse/ApiResult.cs create mode 100644 LibraryManagementApp/Contracts/RepositoryContracts/IAuthorRepository.cs create mode 100644 LibraryManagementApp/Contracts/RepositoryContracts/IBookRepository.cs create mode 100644 LibraryManagementApp/Contracts/RepositoryContracts/ICategoryRepository.cs create mode 100644 LibraryManagementApp/Contracts/RepositoryContracts/IPublisherRepository.cs create mode 100644 LibraryManagementApp/Contracts/RepositoryContracts/IRepositoryBase.cs create mode 100644 LibraryManagementApp/Contracts/RepositoryContracts/IUnitOfWork.cs create mode 100644 LibraryManagementApp/Controllers/AuthorController.cs create mode 100644 LibraryManagementApp/Controllers/BookController.cs create mode 100644 LibraryManagementApp/Controllers/CategoryController.cs create mode 100644 LibraryManagementApp/Controllers/PublisherController.cs create mode 100644 LibraryManagementApp/Domain/Author.cs create mode 100644 LibraryManagementApp/Domain/BaseEntity.cs create mode 100644 LibraryManagementApp/Domain/Book.cs create mode 100644 LibraryManagementApp/Domain/Category.cs create mode 100644 LibraryManagementApp/Domain/Language.cs create mode 100644 LibraryManagementApp/Domain/Publisher.cs create mode 100644 LibraryManagementApp/Dtos/AuthorDto.cs create mode 100644 LibraryManagementApp/Dtos/BookDto.cs create mode 100644 LibraryManagementApp/Dtos/CategoryDto.cs create mode 100644 LibraryManagementApp/Dtos/PublisherDto.cs create mode 100644 LibraryManagementApp/Exceptions/NotFoundException.cs create mode 100644 LibraryManagementApp/LibraryManagementApp.csproj create mode 100644 LibraryManagementApp/Migrations/20230309042120_Initial.Designer.cs create mode 100644 LibraryManagementApp/Migrations/20230309042120_Initial.cs create mode 100644 LibraryManagementApp/Migrations/ApplicationDbContextModelSnapshot.cs create mode 100644 LibraryManagementApp/Persistence/ApplicationDbContext.cs create mode 100644 LibraryManagementApp/Persistence/Configurations/AuthorConfiguration.cs create mode 100644 LibraryManagementApp/Persistence/Configurations/BookConfiguration.cs create mode 100644 LibraryManagementApp/Persistence/Configurations/CategoryConfiguration.cs create mode 100644 LibraryManagementApp/Persistence/Configurations/PublisherConfiguration.cs create mode 100644 LibraryManagementApp/Persistence/Repository/AuthorRepository.cs create mode 100644 LibraryManagementApp/Persistence/Repository/BookRepository.cs create mode 100644 LibraryManagementApp/Persistence/Repository/CategoryRepository.cs create mode 100644 LibraryManagementApp/Persistence/Repository/PublisherRepository.cs create mode 100644 LibraryManagementApp/Persistence/Repository/RepositoryBase.cs create mode 100644 LibraryManagementApp/Persistence/Repository/UnitOfWork.cs create mode 100644 LibraryManagementApp/Program.cs create mode 100644 LibraryManagementApp/Properties/launchSettings.json create mode 100644 LibraryManagementApp/appsettings.Development.json create mode 100644 LibraryManagementApp/appsettings.json diff --git a/BankingSoftware/Storage/Accounts/086591F1B63CEC976440DC2952AC4138.txt b/BankingSoftware/Storage/Accounts/086591F1B63CEC976440DC2952AC4138.txt new file mode 100644 index 0000000..2f3dfb6 --- /dev/null +++ b/BankingSoftware/Storage/Accounts/086591F1B63CEC976440DC2952AC4138.txt @@ -0,0 +1,5 @@ +Last withdrawal amount: $0 +Last withdrawal time: +Last deposit amount: $0 +Last deposit time: +Total: $0 diff --git a/BankingSoftware/Storage/Accounts/0E84027714179DA860EC36BE3ABC2CCE.txt b/BankingSoftware/Storage/Accounts/0E84027714179DA860EC36BE3ABC2CCE.txt new file mode 100644 index 0000000..3df7b09 --- /dev/null +++ b/BankingSoftware/Storage/Accounts/0E84027714179DA860EC36BE3ABC2CCE.txt @@ -0,0 +1,5 @@ +Last withdrawal amount: 20000 +Last withdrawal time: 3/3/2023 9:01:46 AM +Last deposit amount: 60000 +Last deposit time: 3/3/2023 9:01:34 AM +Total: $40000 diff --git a/BankingSoftware/Storage/Accounts/1BBCFB82D95A66785DABF16FE16DD079.txt b/BankingSoftware/Storage/Accounts/1BBCFB82D95A66785DABF16FE16DD079.txt new file mode 100644 index 0000000..f7a2f63 --- /dev/null +++ b/BankingSoftware/Storage/Accounts/1BBCFB82D95A66785DABF16FE16DD079.txt @@ -0,0 +1,5 @@ +Last withdrawal amount: 50000 +Last withdrawal time: 3/3/2023 8:55:01 AM +Last deposit amount: 80000 +Last deposit time: 3/3/2023 8:54:50 AM +Total: $30000 diff --git a/BankingSoftware/Storage/Accounts/7445BDA0F89FE36B4BC831F76CB0533A.txt b/BankingSoftware/Storage/Accounts/7445BDA0F89FE36B4BC831F76CB0533A.txt new file mode 100644 index 0000000..2f3dfb6 --- /dev/null +++ b/BankingSoftware/Storage/Accounts/7445BDA0F89FE36B4BC831F76CB0533A.txt @@ -0,0 +1,5 @@ +Last withdrawal amount: $0 +Last withdrawal time: +Last deposit amount: $0 +Last deposit time: +Total: $0 diff --git a/BankingSoftware/Storage/Accounts/A91CCAE139380AA1368393C8FC2953C1.txt b/BankingSoftware/Storage/Accounts/A91CCAE139380AA1368393C8FC2953C1.txt new file mode 100644 index 0000000..15c2903 --- /dev/null +++ b/BankingSoftware/Storage/Accounts/A91CCAE139380AA1368393C8FC2953C1.txt @@ -0,0 +1,5 @@ +Last withdrawal amount: 20000 +Last withdrawal time: 3/3/2023 8:47:41 AM +Last deposit amount: 80000 +Last deposit time: 3/3/2023 8:47:30 AM +Total: $60000 diff --git a/BankingSoftware/Storage/Accounts/B56FD7BF4AB39D813239C006C4FF5813.txt b/BankingSoftware/Storage/Accounts/B56FD7BF4AB39D813239C006C4FF5813.txt new file mode 100644 index 0000000..4c63b6b --- /dev/null +++ b/BankingSoftware/Storage/Accounts/B56FD7BF4AB39D813239C006C4FF5813.txt @@ -0,0 +1,5 @@ +Last withdrawal amount: 30000 +Last withdrawal time: 3/3/2023 9:09:01 AM +Last deposit amount: $60000 +Last deposit time: 3/3/2023 9:08:49 AM +Total: $30000 diff --git a/BankingSoftware/Storage/Users/6A3B12DA60987AF16E55F3FCEC2A6EED.txt b/BankingSoftware/Storage/Users/6A3B12DA60987AF16E55F3FCEC2A6EED.txt new file mode 100644 index 0000000..a8c4c60 --- /dev/null +++ b/BankingSoftware/Storage/Users/6A3B12DA60987AF16E55F3FCEC2A6EED.txt @@ -0,0 +1,8 @@ +First Name: Kunle +Last Name: Afod +Email: kunle@afod.com +Username: kunle +Age: 67 +Phone Number: 8734652345 +HashedPassword: F1A3AB9E8E8AB970167FEEE7A731F3E9 + diff --git a/BankingSoftware/Storage/Users/7A362B30780DD15E6D710EC44AA0E023.txt b/BankingSoftware/Storage/Users/7A362B30780DD15E6D710EC44AA0E023.txt new file mode 100644 index 0000000..bc3adbb --- /dev/null +++ b/BankingSoftware/Storage/Users/7A362B30780DD15E6D710EC44AA0E023.txt @@ -0,0 +1,8 @@ +First Name: Usher +Last Name: Ray +Email: usher@ray.com +Username: usher +Age: 87 +Phone Number: 8975346523 +HashedPassword: BA4AFECD23BE0B3121EA708831022CEF + diff --git a/BankingSoftware/Storage/Users/C19291F87C625505E0CB012E128C3E33.txt b/BankingSoftware/Storage/Users/C19291F87C625505E0CB012E128C3E33.txt new file mode 100644 index 0000000..738de81 --- /dev/null +++ b/BankingSoftware/Storage/Users/C19291F87C625505E0CB012E128C3E33.txt @@ -0,0 +1,8 @@ +First Name: Hal +Last Name: Finley +Email: hal@finley.com +Username: hal +Age: 87 +Phone Number: 9874523876 +HashedPassword: 8E3F0695D2604D26F5B64C23C485E3BF + diff --git a/BankingSoftware/Storage/Users/FA2111BDFC79125C0CF1D96B53B88FEB.txt b/BankingSoftware/Storage/Users/FA2111BDFC79125C0CF1D96B53B88FEB.txt new file mode 100644 index 0000000..7ebdd13 --- /dev/null +++ b/BankingSoftware/Storage/Users/FA2111BDFC79125C0CF1D96B53B88FEB.txt @@ -0,0 +1,8 @@ +First Name: John +Last Name: Camark +Email: john@camark.com +Username: john +Age: 67 +Phone Number: 9834562346 +HashedPassword: E813ECD4F0196D88949044C1FA1B568D + diff --git a/BankingSoftware/Storage/Users/FE6FC17D485932CC797BB376BB501D44.txt b/BankingSoftware/Storage/Users/FE6FC17D485932CC797BB376BB501D44.txt new file mode 100644 index 0000000..d548215 --- /dev/null +++ b/BankingSoftware/Storage/Users/FE6FC17D485932CC797BB376BB501D44.txt @@ -0,0 +1,8 @@ +First Name: Hailey +Last Name: Bieber +Email: hailey@bieber.com +Username: hailey +Age: 78 +Phone Number: 8777345623 +HashedPassword: 588BE3DDB98C4DB0FB1E484105399596 + diff --git a/LibraryManagementApp/ApiResponse/ApiResult.cs b/LibraryManagementApp/ApiResponse/ApiResult.cs new file mode 100644 index 0000000..e91825b --- /dev/null +++ b/LibraryManagementApp/ApiResponse/ApiResult.cs @@ -0,0 +1,152 @@ +using System.Linq.Dynamic.Core; +using System.Reflection; +using EFCore.BulkExtensions; +using Microsoft.EntityFrameworkCore; + +namespace LibraryManagementApp.ApiResponse; + +public class ApiResult +{ + /// + /// Private constructor called by the CreateAsync method. + /// + private ApiResult( + List data, + int count, + int pageIndex, + int pageSize, + string? filterQuery) + { + Data = data; + PageIndex = pageIndex; + PageSize = pageSize; + TotalCount = count; + TotalPages = (int)Math.Ceiling(count / (double)pageSize); + FilterQuery = filterQuery; + } + + #region Methods + + /// + /// Pages, sorts and/or filters a IQueryable source. + /// + /// An IQueryable source of generic + /// type + /// Zero-based current page index + /// (0 = first page) + /// The actual size of + /// each page + /// The filtering query (value to + /// lookup) + /// + /// A object containing the IQueryable paged/sorted/filtered + /// result + /// and all the relevant paging/sorting/filtering navigation + /// info. + /// + public static async Task> CreateAsync( + IQueryable source, + int pageIndex, + int pageSize, + string? filterQuery = null) + { + if (!string.IsNullOrEmpty(filterQuery)) + { + source = source.Where(filterQuery); + } + + var count = await source.CountAsync(); + + source = source + .Skip(pageIndex * pageSize) + .Take(pageSize); + +#if DEBUG + // retrieve the SQL query (for debug purposes) + var sql = source.ToParametrizedSql(); +#endif + + var data = await source.ToListAsync(); + + return new ApiResult( + data, + count, + pageIndex, + pageSize, + filterQuery); + } + + /// + /// Checks if the given property name exists + /// to protect against SQL injection attacks + /// + public static bool IsValidProperty( + string propertyName, + bool throwExceptionIfNotFound = true) + { + var prop = typeof(T).GetProperty( + propertyName, + BindingFlags.IgnoreCase | + BindingFlags.Public | + BindingFlags.Static | + BindingFlags.Instance); + if (prop == null && throwExceptionIfNotFound) + throw new NotSupportedException($"ERROR: Property '{propertyName}' does not exist."); + return prop != null; + } + + #endregion + + #region Properties + + /// + /// IQueryable data result to return. + /// + public List Data { get; private set; } + + /// + /// Zero-based index of current page. + /// + public int PageIndex { get; private set; } + + /// + /// Number of items contained in each page. + /// + public int PageSize { get; private set; } + + /// + /// Total items count + /// + public int TotalCount { get; private set; } + + /// + /// Total pages count + /// + public int TotalPages { get; private set; } + + /// + /// TRUE if the current page has a previous page, + /// FALSE otherwise. + /// + public bool HasPreviousPage + { + get { return (PageIndex > 0); } + } + + /// + /// TRUE if the current page has a next page, FALSE otherwise. + /// + public bool HasNextPage + { + get { return ((PageIndex + 1) < TotalPages); } + } + + + /// + /// Filter Query string + /// (to be used within the given FilterColumn) + /// + public string? FilterQuery { get; set; } + + #endregion +} \ No newline at end of file diff --git a/LibraryManagementApp/Contracts/RepositoryContracts/IAuthorRepository.cs b/LibraryManagementApp/Contracts/RepositoryContracts/IAuthorRepository.cs new file mode 100644 index 0000000..2253c59 --- /dev/null +++ b/LibraryManagementApp/Contracts/RepositoryContracts/IAuthorRepository.cs @@ -0,0 +1,8 @@ +using LibraryManagementApp.Domain; + +namespace LibraryManagementApp.Contracts.RepositoryContracts; + +public interface IAuthorRepository: IRepositoryBase +{ + +} \ No newline at end of file diff --git a/LibraryManagementApp/Contracts/RepositoryContracts/IBookRepository.cs b/LibraryManagementApp/Contracts/RepositoryContracts/IBookRepository.cs new file mode 100644 index 0000000..e6bac7d --- /dev/null +++ b/LibraryManagementApp/Contracts/RepositoryContracts/IBookRepository.cs @@ -0,0 +1,8 @@ +using LibraryManagementApp.Domain; + +namespace LibraryManagementApp.Contracts.RepositoryContracts; + +public interface IBookRepository: IRepositoryBase +{ + +} \ No newline at end of file diff --git a/LibraryManagementApp/Contracts/RepositoryContracts/ICategoryRepository.cs b/LibraryManagementApp/Contracts/RepositoryContracts/ICategoryRepository.cs new file mode 100644 index 0000000..9575d32 --- /dev/null +++ b/LibraryManagementApp/Contracts/RepositoryContracts/ICategoryRepository.cs @@ -0,0 +1,8 @@ +using LibraryManagementApp.Domain; + +namespace LibraryManagementApp.Contracts.RepositoryContracts; + +public interface ICategoryRepository: IRepositoryBase +{ + +} \ No newline at end of file diff --git a/LibraryManagementApp/Contracts/RepositoryContracts/IPublisherRepository.cs b/LibraryManagementApp/Contracts/RepositoryContracts/IPublisherRepository.cs new file mode 100644 index 0000000..b7aa2f2 --- /dev/null +++ b/LibraryManagementApp/Contracts/RepositoryContracts/IPublisherRepository.cs @@ -0,0 +1,8 @@ +using LibraryManagementApp.Domain; + +namespace LibraryManagementApp.Contracts.RepositoryContracts; + +public interface IPublisherRepository: IRepositoryBase +{ + +} \ No newline at end of file diff --git a/LibraryManagementApp/Contracts/RepositoryContracts/IRepositoryBase.cs b/LibraryManagementApp/Contracts/RepositoryContracts/IRepositoryBase.cs new file mode 100644 index 0000000..032d48e --- /dev/null +++ b/LibraryManagementApp/Contracts/RepositoryContracts/IRepositoryBase.cs @@ -0,0 +1,15 @@ +using System.Linq.Expressions; + +namespace LibraryManagementApp.Contracts.RepositoryContracts; + +public interface IRepositoryBase where T: class +{ + T FindById(Guid id); + IQueryable FindByCondition(Expression> expression, bool trackChanges); + IQueryable FindAll(bool trackChanges); + void Add(T entity); + void AddRange(IEnumerable entities); + void Update(T entity); + void Remove(T entity); + void RemoveRange(IEnumerable entities); +} \ No newline at end of file diff --git a/LibraryManagementApp/Contracts/RepositoryContracts/IUnitOfWork.cs b/LibraryManagementApp/Contracts/RepositoryContracts/IUnitOfWork.cs new file mode 100644 index 0000000..49bc705 --- /dev/null +++ b/LibraryManagementApp/Contracts/RepositoryContracts/IUnitOfWork.cs @@ -0,0 +1,11 @@ +namespace LibraryManagementApp.Contracts.RepositoryContracts; + +public interface IUnitOfWork: IDisposable +{ + IAuthorRepository Authors { get; } + IBookRepository Books { get; } + IPublisherRepository Publishers { get; } + ICategoryRepository Categories { get; } + + int Complete(); +} \ No newline at end of file diff --git a/LibraryManagementApp/Controllers/AuthorController.cs b/LibraryManagementApp/Controllers/AuthorController.cs new file mode 100644 index 0000000..b39220c --- /dev/null +++ b/LibraryManagementApp/Controllers/AuthorController.cs @@ -0,0 +1,122 @@ +using LibraryManagementApp.ApiResponse; +using LibraryManagementApp.Contracts.RepositoryContracts; +using LibraryManagementApp.Domain; +using LibraryManagementApp.Dtos; +using LibraryManagementApp.Exceptions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LibraryManagementApp.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class AuthorController: ControllerBase +{ + private readonly IUnitOfWork _unitOfWork; + private readonly ILogger _logger; + + public AuthorController(ILogger logger, IUnitOfWork unitOfWork) + { + _logger = logger; + _unitOfWork = unitOfWork; + } + + [HttpGet] + public async Task>> GetAuthors( + bool trackChanges, + int pageIndex = 0, + int pageSize = 10, + string? filterQuery = null) + { + var authors = await ApiResult.CreateAsync( + _unitOfWork.Authors.FindAll(trackChanges).Select( + a => new AuthorDto() + { + Id = a.Id, + FirstName = a.FirstName, + LastName = a.LastName, + PublisherName = a.Publisher.Name, + AvatarUrl = a.AvatarUrl + }), + pageIndex, + pageSize, + filterQuery); + + return authors; + } + + [HttpGet("{id}")] + public async Task> GetAuthorById(Guid id, bool trackChanges) + { + var entity = await _unitOfWork.Authors + .FindByCondition(a => a.Id.Equals(id), trackChanges) + .Select(a => new AuthorDto() + { + Id = a.Id, + FirstName = a.FirstName, + LastName = a.LastName, + PublisherName = a.Publisher.Name, + AvatarUrl = a.AvatarUrl + }) + .FirstOrDefaultAsync(); + + if (entity == null) + throw new NotFoundException(); + + return entity; + } + + [HttpPost] + public ActionResult CreateAuthor(AuthorDto author) + { + var entity = new Author + { + FirstName = author.FirstName, + LastName = author.LastName, + AvatarUrl = author.AvatarUrl, + PublisherId = author.PublisherId + }; + + _unitOfWork.Authors.Add(entity); + _unitOfWork.Complete(); + _unitOfWork.Dispose(); + + return Ok(); + } + + [HttpPut("{id:guid}")] + public ActionResult UpdateAuthor(AuthorDto author, Guid id) + { + var entity = _unitOfWork.Authors.FindById(id); + + if (entity == null) + throw new NotFoundException(); + + entity.FirstName = author.FirstName; + entity.LastName = author.LastName; + entity.AvatarUrl = author.AvatarUrl; + entity.PublisherId = author.PublisherId; + + + _unitOfWork.Authors.Update(entity); + _unitOfWork.Complete(); + _unitOfWork.Dispose(); + + return Ok(); + } + + [HttpDelete("{id:guid}")] + public ActionResult DeleteAuthor(Guid id) + { + var entity = _unitOfWork.Authors.FindById(id); + + if (entity == null) + throw new NotFoundException(); + + _unitOfWork.Authors.Remove(entity); + _unitOfWork.Complete(); + _unitOfWork.Dispose(); + + return Ok(); + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Controllers/BookController.cs b/LibraryManagementApp/Controllers/BookController.cs new file mode 100644 index 0000000..fb3ba4b --- /dev/null +++ b/LibraryManagementApp/Controllers/BookController.cs @@ -0,0 +1,134 @@ +using LibraryManagementApp.ApiResponse; +using LibraryManagementApp.Contracts.RepositoryContracts; +using LibraryManagementApp.Domain; +using LibraryManagementApp.Dtos; +using LibraryManagementApp.Exceptions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LibraryManagementApp.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class BookController : ControllerBase +{ + private readonly IUnitOfWork _unitOfWork; + private readonly ILogger _logger; + + public BookController(ILogger logger, IUnitOfWork unitOfWork) + { + _logger = logger; + _unitOfWork = unitOfWork; + } + + [HttpGet] + public async Task>> GetBooks( + bool trackChanges, + int pageIndex = 0, + int pageSize = 10, + string? filterQuery = null) + { + var books = await ApiResult.CreateAsync( + _unitOfWork.Books.FindAll(trackChanges).Select( + b => new BookDto() + { + Id = b.Id, + Title = b.Title, + CopyrightNotice = b.CopyrightNotice, + ISBN = b.ISBN, + AuthorName = b.Author.FirstName, + PageLength = b.PageLength, + Language = b.Language, + ReleaseYear = b.ReleaseYear + }), + pageIndex, + pageSize, + filterQuery); + + return books; + } + + [HttpGet("{id}")] + public async Task> GetBookById(Guid id, bool trackChanges) + { + var entity = await _unitOfWork.Books + .FindByCondition(a => a.Id.Equals(id), trackChanges) + .Select(b => new BookDto() + { + Id = b.Id, + Title = b.Title, + CopyrightNotice = b.CopyrightNotice, + ISBN = b.ISBN, + AuthorName = b.Author.FirstName, + PageLength = b.PageLength, + Language = b.Language, + ReleaseYear = b.ReleaseYear + }) + .FirstOrDefaultAsync(); + + if (entity == null) + throw new NotFoundException(); + + return entity; + } + + [HttpPost] + public ActionResult CreateBook(BookDto book) + { + var author = _unitOfWork.Authors.FindById(book.AuthourId); + var entity = new Book + { + Title = book.Title, + CopyrightNotice = book.CopyrightNotice, + ISBN = book.ISBN, + AuthorId = author.Id, + PageLength = book.PageLength, + Language = book.Language, + ReleaseYear = book.ReleaseYear + }; + + _unitOfWork.Books.Add(entity); + _unitOfWork.Complete(); + _unitOfWork.Dispose(); + + return Ok(); + } + + [HttpPut("{id:guid}")] + public ActionResult UpdateBook(BookDto book, Guid id) + { + var entity = _unitOfWork.Books.FindById(id); + + if (entity == null) + throw new NotFoundException(); + + entity.Title = book.Title; + entity.CopyrightNotice = book.CopyrightNotice; + entity.ISBN = book.ISBN; + entity.PageLength = book.PageLength; + entity.Language = book.Language; + entity.ReleaseYear = book.ReleaseYear; + + + _unitOfWork.Books.Update(entity); + _unitOfWork.Complete(); + _unitOfWork.Dispose(); + + return Ok(); + } + + [HttpDelete("{id:guid}")] + public ActionResult DeleteBook(Guid id) + { + var entity = _unitOfWork.Books.FindById(id); + + if (entity == null) + throw new NotFoundException(); + + _unitOfWork.Books.Remove(entity); + _unitOfWork.Complete(); + _unitOfWork.Dispose(); + + return Ok(); + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Controllers/CategoryController.cs b/LibraryManagementApp/Controllers/CategoryController.cs new file mode 100644 index 0000000..0392adf --- /dev/null +++ b/LibraryManagementApp/Controllers/CategoryController.cs @@ -0,0 +1,113 @@ +using LibraryManagementApp.ApiResponse; +using LibraryManagementApp.Contracts.RepositoryContracts; +using LibraryManagementApp.Domain; +using LibraryManagementApp.Dtos; +using LibraryManagementApp.Exceptions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LibraryManagementApp.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class CategoryController: ControllerBase +{ + private readonly IUnitOfWork _unitOfWork; + private readonly ILogger _logger; + + public CategoryController(ILogger logger, IUnitOfWork unitOfWork) + { + _logger = logger; + _unitOfWork = unitOfWork; + } + + [HttpGet] + public async Task>> GetPublishers( + bool trackChanges, + int pageIndex = 0, + int pageSize = 10, + string? filterQuery = null) + { + var categories = await ApiResult.CreateAsync( + _unitOfWork.Categories.FindAll(trackChanges).Select( + c => new CategoryDto() + { + Id = c.Id, + Name = c.Name, + Description = c.Description + }), + pageIndex, + pageSize, + filterQuery); + + return categories; + } + + [HttpGet("{id}")] + public async Task> GetCategoryById(Guid id, bool trackChanges) + { + var entity = await _unitOfWork.Categories + .FindByCondition(a => a.Id.Equals(id), trackChanges) + .Select(c => new CategoryDto() + { + Id = c.Id, + Name = c.Name, + Description = c.Description + }) + .FirstOrDefaultAsync(); + + if (entity == null) + throw new NotFoundException(); + + return entity; + } + + [HttpPost] + public ActionResult CreateCategory(CategoryDto category) + { + var entity = new Category + { + Name = category.Name, + Description = category.Description, + }; + + _unitOfWork.Categories.Add(entity); + _unitOfWork.Complete(); + _unitOfWork.Dispose(); + + return Ok(); + } + + [HttpPut("{id:guid}")] + public ActionResult UpdateCategory(CategoryDto category, Guid id) + { + var entity = _unitOfWork.Categories.FindById(id); + + if (entity == null) + throw new NotFoundException(); + + entity.Name = category.Name; + entity.Description = category.Description; + + _unitOfWork.Categories.Update(entity); + _unitOfWork.Complete(); + _unitOfWork.Dispose(); + + return Ok(); + } + + [HttpDelete("{id:guid}")] + public ActionResult DeleteCategory(Guid id) + { + var entity = _unitOfWork.Categories.FindById(id); + + if (entity == null) + throw new NotFoundException(); + + _unitOfWork.Categories.Remove(entity); + _unitOfWork.Complete(); + _unitOfWork.Dispose(); + + return Ok(); + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Controllers/PublisherController.cs b/LibraryManagementApp/Controllers/PublisherController.cs new file mode 100644 index 0000000..16c6f78 --- /dev/null +++ b/LibraryManagementApp/Controllers/PublisherController.cs @@ -0,0 +1,114 @@ +using LibraryManagementApp.ApiResponse; +using LibraryManagementApp.Contracts.RepositoryContracts; +using LibraryManagementApp.Domain; +using LibraryManagementApp.Dtos; +using LibraryManagementApp.Exceptions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LibraryManagementApp.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class PublisherController : ControllerBase +{ + private readonly IUnitOfWork _unitOfWork; + private readonly ILogger _logger; + + public PublisherController(ILogger logger, IUnitOfWork unitOfWork) + { + _logger = logger; + _unitOfWork = unitOfWork; + } + + [HttpGet] + public async Task>> GetPublishers( + bool trackChanges, + int pageIndex = 0, + int pageSize = 10, + string? filterQuery = null) + { + var publishers = await ApiResult.CreateAsync( + _unitOfWork.Publishers.FindAll(trackChanges).Select( + p => new PublisherDto() + { + Id = p.Id, + Name = p.Name, + LogoUrl = p.LogoUrl, + }), + pageIndex, + pageSize, + filterQuery); + + return publishers; + } + + [HttpGet("{id}")] + public async Task> GetPublisherById(Guid id, bool trackChanges) + { + var entity = await _unitOfWork.Publishers + .FindByCondition(p => p.Id.Equals(id), trackChanges) + .Select(p => new PublisherDto + { + Name = p.Name, + LogoUrl = p.LogoUrl, + TotalBooks = p.TotBooks, + TotalAuthors = p.TotAuthors + }) + .FirstOrDefaultAsync(); + + if (entity == null) + throw new NotFoundException(); + + return entity; + } + + [HttpPost] + public ActionResult CreatePublisher(PublisherDto publisher) + { + var entity = new Publisher + { + Name = publisher.Name, + LogoUrl = publisher.LogoUrl, + }; + + _unitOfWork.Publishers.Add(entity); + _unitOfWork.Complete(); + _unitOfWork.Dispose(); + + return Ok(); + } + + [HttpPut("{id:guid}")] + public ActionResult UpdatePublisher(PublisherDto publisher, Guid id) + { + var entity = _unitOfWork.Publishers.FindById(id); + + if (entity == null) + throw new NotFoundException(); + + entity.Name = publisher.Name; + entity.LogoUrl = publisher.LogoUrl; + + _unitOfWork.Publishers.Update(entity); + _unitOfWork.Complete(); + _unitOfWork.Dispose(); + + return Ok(); + } + + [HttpDelete("{id:guid}")] + public ActionResult DeletePublisher(Guid id) + { + var entity = _unitOfWork.Publishers.FindById(id); + + if (entity == null) + throw new NotFoundException(); + + _unitOfWork.Publishers.Remove(entity); + _unitOfWork.Complete(); + _unitOfWork.Dispose(); + + return Ok(); + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Domain/Author.cs b/LibraryManagementApp/Domain/Author.cs new file mode 100644 index 0000000..39a2f54 --- /dev/null +++ b/LibraryManagementApp/Domain/Author.cs @@ -0,0 +1,40 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace LibraryManagementApp.Domain +{ + public class Author: BaseEntity + { + public Author() + { + + } + + public Author(string firstName, string lastName, string avatarUrl, Guid publisherId) + { + FirstName = firstName; + LastName = lastName; + AvatarUrl = avatarUrl; + PublisherId = publisherId; + } + + public string FirstName{get; set;} + public string LastName{get; set;} + public string AvatarUrl{get; set;} + public Guid PublisherId{get; set;} + public Publisher Publisher { get; set; } + public ICollection Books{get; set;} + + [NotMapped] + public int TotBooks + { + get + { + return (Books != null) + ? Books.Count + : _TotBooks; + } + set => _TotBooks = value; + } + private int _TotBooks; + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Domain/BaseEntity.cs b/LibraryManagementApp/Domain/BaseEntity.cs new file mode 100644 index 0000000..5f3c4ea --- /dev/null +++ b/LibraryManagementApp/Domain/BaseEntity.cs @@ -0,0 +1,23 @@ +namespace LibraryManagementApp.Domain; + +public class BaseEntity +{ + public BaseEntity() + { + + } + + public BaseEntity(Guid id, DateTime createdOn) + { + Id = new Guid(); + CreatedOn = createdOn; + } + + public Guid Id { get; init; } + public DateTime CreatedOn { get; init; } + public Guid CreatedBy { get; init; } + public DateTime ModifiedOn { get; set; } + public Guid ModifiedBy { get; set; } + public DateTime DeletedOn { get; init; } + public Guid DeletedBy { get; init; } +} \ No newline at end of file diff --git a/LibraryManagementApp/Domain/Book.cs b/LibraryManagementApp/Domain/Book.cs new file mode 100644 index 0000000..a480777 --- /dev/null +++ b/LibraryManagementApp/Domain/Book.cs @@ -0,0 +1,26 @@ +namespace LibraryManagementApp.Domain +{ + public class Book: BaseEntity + { + public Book() + { + + } + + public Book(string title, string copyrightNotice, Guid authorId) + { + Title = title; + CopyrightNotice = copyrightNotice; + AuthorId = authorId; + } + + public string Title{get; set;} + public string CopyrightNotice{get; set;} + public int ISBN{get; set;} + public Guid AuthorId{get; set;} + public Author Author { get; set; } + public int PageLength{get; set;} + public Language Language{get; set;} + public DateOnly ReleaseYear{get; set;} + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Domain/Category.cs b/LibraryManagementApp/Domain/Category.cs new file mode 100644 index 0000000..74672ae --- /dev/null +++ b/LibraryManagementApp/Domain/Category.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace LibraryManagementApp.Domain +{ + public class Category: BaseEntity + { + public Category() + { + + } + public Category(string name, string description) + { + Name = name; + Description = description; + } + + public string Name{get; set;} + public string Description{get; set;} + public ICollection Books{get; set;} + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Domain/Language.cs b/LibraryManagementApp/Domain/Language.cs new file mode 100644 index 0000000..8ad94e9 --- /dev/null +++ b/LibraryManagementApp/Domain/Language.cs @@ -0,0 +1,11 @@ +namespace LibraryManagementApp.Domain +{ + public enum Language + { + English = 10, + Yoruba = 20, + Igbo = 30, + Hausa = 40, + French = 50, + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Domain/Publisher.cs b/LibraryManagementApp/Domain/Publisher.cs new file mode 100644 index 0000000..48105de --- /dev/null +++ b/LibraryManagementApp/Domain/Publisher.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Threading.Tasks; + +namespace LibraryManagementApp.Domain +{ + public class Publisher: BaseEntity + { + public Publisher() + { + + } + public Publisher(string name, string logoUrl) + { + Name = name; + LogoUrl = logoUrl; + } + + public string Name{get; set;} + public string LogoUrl{get; set;} + public ICollection Books { get; set; } + public ICollection Authors { get; set; } + + [NotMapped] + public int TotBooks + { + get + { + return (Books != null) + ? Books.Count + : _TotBooks; + } + set => _TotBooks = value; + } + private int _TotBooks; + + [NotMapped] + public int TotAuthors + { + get + { + return (Books != null) + ? Books.Count + : _TotBooks; + } + set => _TotBooks = value; + } + private int _TotAuthors; + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Dtos/AuthorDto.cs b/LibraryManagementApp/Dtos/AuthorDto.cs new file mode 100644 index 0000000..981bbeb --- /dev/null +++ b/LibraryManagementApp/Dtos/AuthorDto.cs @@ -0,0 +1,11 @@ +namespace LibraryManagementApp.Dtos; + +public record AuthorDto +{ + public Guid Id { get; init; } + public string FirstName{get; init;} + public string LastName{get; init;} + public string AvatarUrl{get; init;} + public string PublisherName{get; init;} + public Guid PublisherId {get; init;} +} \ No newline at end of file diff --git a/LibraryManagementApp/Dtos/BookDto.cs b/LibraryManagementApp/Dtos/BookDto.cs new file mode 100644 index 0000000..f4799a6 --- /dev/null +++ b/LibraryManagementApp/Dtos/BookDto.cs @@ -0,0 +1,16 @@ +using LibraryManagementApp.Domain; + +namespace LibraryManagementApp.Dtos; + +public record BookDto +{ + public Guid Id { get; init; } + public string Title{get; init;} + public string CopyrightNotice{get; init;} + public int ISBN{get; init;} + public string AuthorName { get; init; } + public Guid AuthourId { get; init; } + public int PageLength{get; init;} + public Language Language{get; init;} + public DateOnly ReleaseYear{get; init;} +} \ No newline at end of file diff --git a/LibraryManagementApp/Dtos/CategoryDto.cs b/LibraryManagementApp/Dtos/CategoryDto.cs new file mode 100644 index 0000000..fb156db --- /dev/null +++ b/LibraryManagementApp/Dtos/CategoryDto.cs @@ -0,0 +1,8 @@ +namespace LibraryManagementApp.Dtos; + +public record CategoryDto +{ + public Guid Id { get; init; } + public string Name{get; set;} + public string Description{get; set;} +} \ No newline at end of file diff --git a/LibraryManagementApp/Dtos/PublisherDto.cs b/LibraryManagementApp/Dtos/PublisherDto.cs new file mode 100644 index 0000000..e701323 --- /dev/null +++ b/LibraryManagementApp/Dtos/PublisherDto.cs @@ -0,0 +1,10 @@ +namespace LibraryManagementApp.Dtos; + +public record PublisherDto +{ + public Guid Id { get; init; } + public string Name{get; init;} + public string LogoUrl{get; init;} + public int? TotalAuthors { get; init; } + public int? TotalBooks { get; init; } +} \ No newline at end of file diff --git a/LibraryManagementApp/Exceptions/NotFoundException.cs b/LibraryManagementApp/Exceptions/NotFoundException.cs new file mode 100644 index 0000000..fcdabbe --- /dev/null +++ b/LibraryManagementApp/Exceptions/NotFoundException.cs @@ -0,0 +1,24 @@ +namespace LibraryManagementApp.Exceptions; + +public class NotFoundException : Exception +{ + public NotFoundException() + : base() + { + } + + public NotFoundException(string message) + : base(message) + { + } + + public NotFoundException(string message, Exception innerException) + : base(message, innerException) + { + } + + public NotFoundException(string name, object key) + : base($"Entity \"{name}\" ({key}) was not found.") + { + } +} diff --git a/LibraryManagementApp/LibraryManagementApp.csproj b/LibraryManagementApp/LibraryManagementApp.csproj new file mode 100644 index 0000000..5e13825 --- /dev/null +++ b/LibraryManagementApp/LibraryManagementApp.csproj @@ -0,0 +1,23 @@ + + + + net7.0 + enable + enable + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + diff --git a/LibraryManagementApp/Migrations/20230309042120_Initial.Designer.cs b/LibraryManagementApp/Migrations/20230309042120_Initial.Designer.cs new file mode 100644 index 0000000..bacd982 --- /dev/null +++ b/LibraryManagementApp/Migrations/20230309042120_Initial.Designer.cs @@ -0,0 +1,262 @@ +// +using System; +using LibraryManagementApp.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace LibraryManagementApp.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20230309042120_Initial")] + partial class Initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("BookCategory", b => + { + b.Property("BooksId") + .HasColumnType("uuid"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.HasKey("BooksId", "CategoryId"); + + b.HasIndex("CategoryId"); + + b.ToTable("BookCategory"); + }); + + modelBuilder.Entity("LibraryManagementApp.Domain.Author", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AvatarUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("DeletedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ModifiedBy") + .HasColumnType("uuid"); + + b.Property("ModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("PublisherId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Authors"); + }); + + modelBuilder.Entity("LibraryManagementApp.Domain.Book", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthorId") + .HasColumnType("uuid"); + + b.Property("CopyrightNotice") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("DeletedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ISBN") + .HasColumnType("integer"); + + b.Property("Language") + .HasColumnType("integer"); + + b.Property("ModifiedBy") + .HasColumnType("uuid"); + + b.Property("ModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("PageLength") + .HasColumnType("integer"); + + b.Property("PublisherId") + .HasColumnType("uuid"); + + b.Property("ReleaseYear") + .HasColumnType("date"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("AuthorId"); + + b.HasIndex("PublisherId"); + + b.ToTable("Books"); + }); + + modelBuilder.Entity("LibraryManagementApp.Domain.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("DeletedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ModifiedBy") + .HasColumnType("uuid"); + + b.Property("ModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("LibraryManagementApp.Domain.Publisher", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("DeletedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LogoUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("ModifiedBy") + .HasColumnType("uuid"); + + b.Property("ModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Publishers"); + }); + + modelBuilder.Entity("BookCategory", b => + { + b.HasOne("LibraryManagementApp.Domain.Book", null) + .WithMany() + .HasForeignKey("BooksId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LibraryManagementApp.Domain.Category", null) + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LibraryManagementApp.Domain.Book", b => + { + b.HasOne("LibraryManagementApp.Domain.Author", "Author") + .WithMany("Books") + .HasForeignKey("AuthorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LibraryManagementApp.Domain.Publisher", null) + .WithMany("Books") + .HasForeignKey("PublisherId"); + + b.Navigation("Author"); + }); + + modelBuilder.Entity("LibraryManagementApp.Domain.Author", b => + { + b.Navigation("Books"); + }); + + modelBuilder.Entity("LibraryManagementApp.Domain.Publisher", b => + { + b.Navigation("Books"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/LibraryManagementApp/Migrations/20230309042120_Initial.cs b/LibraryManagementApp/Migrations/20230309042120_Initial.cs new file mode 100644 index 0000000..af9489d --- /dev/null +++ b/LibraryManagementApp/Migrations/20230309042120_Initial.cs @@ -0,0 +1,168 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace LibraryManagementApp.Migrations +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Authors", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + FirstName = table.Column(type: "text", nullable: false), + LastName = table.Column(type: "text", nullable: false), + AvatarUrl = table.Column(type: "text", nullable: false), + PublisherId = table.Column(type: "uuid", nullable: false), + CreatedOn = table.Column(type: "timestamp with time zone", nullable: false), + CreatedBy = table.Column(type: "uuid", nullable: false), + ModifiedOn = table.Column(type: "timestamp with time zone", nullable: false), + ModifiedBy = table.Column(type: "uuid", nullable: false), + DeletedOn = table.Column(type: "timestamp with time zone", nullable: false), + DeletedBy = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Authors", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Categories", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "text", nullable: false), + Description = table.Column(type: "text", nullable: false), + CreatedOn = table.Column(type: "timestamp with time zone", nullable: false), + CreatedBy = table.Column(type: "uuid", nullable: false), + ModifiedOn = table.Column(type: "timestamp with time zone", nullable: false), + ModifiedBy = table.Column(type: "uuid", nullable: false), + DeletedOn = table.Column(type: "timestamp with time zone", nullable: false), + DeletedBy = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Categories", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Publishers", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "text", nullable: false), + LogoUrl = table.Column(type: "text", nullable: false), + CreatedOn = table.Column(type: "timestamp with time zone", nullable: false), + CreatedBy = table.Column(type: "uuid", nullable: false), + ModifiedOn = table.Column(type: "timestamp with time zone", nullable: false), + ModifiedBy = table.Column(type: "uuid", nullable: false), + DeletedOn = table.Column(type: "timestamp with time zone", nullable: false), + DeletedBy = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Publishers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Books", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Title = table.Column(type: "text", nullable: false), + CopyrightNotice = table.Column(type: "text", nullable: false), + ISBN = table.Column(type: "integer", nullable: false), + AuthorId = table.Column(type: "uuid", nullable: false), + PageLength = table.Column(type: "integer", nullable: false), + Language = table.Column(type: "integer", nullable: false), + ReleaseYear = table.Column(type: "date", nullable: false), + PublisherId = table.Column(type: "uuid", nullable: true), + CreatedOn = table.Column(type: "timestamp with time zone", nullable: false), + CreatedBy = table.Column(type: "uuid", nullable: false), + ModifiedOn = table.Column(type: "timestamp with time zone", nullable: false), + ModifiedBy = table.Column(type: "uuid", nullable: false), + DeletedOn = table.Column(type: "timestamp with time zone", nullable: false), + DeletedBy = table.Column(type: "uuid", 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); + table.ForeignKey( + name: "FK_Books_Publishers_PublisherId", + column: x => x.PublisherId, + principalTable: "Publishers", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "BookCategory", + columns: table => new + { + BooksId = table.Column(type: "uuid", nullable: false), + CategoryId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BookCategory", x => new { x.BooksId, x.CategoryId }); + table.ForeignKey( + name: "FK_BookCategory_Books_BooksId", + column: x => x.BooksId, + principalTable: "Books", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_BookCategory_Categories_CategoryId", + column: x => x.CategoryId, + principalTable: "Categories", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_BookCategory_CategoryId", + table: "BookCategory", + column: "CategoryId"); + + migrationBuilder.CreateIndex( + name: "IX_Books_AuthorId", + table: "Books", + column: "AuthorId"); + + migrationBuilder.CreateIndex( + name: "IX_Books_PublisherId", + table: "Books", + column: "PublisherId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BookCategory"); + + migrationBuilder.DropTable( + name: "Books"); + + migrationBuilder.DropTable( + name: "Categories"); + + migrationBuilder.DropTable( + name: "Authors"); + + migrationBuilder.DropTable( + name: "Publishers"); + } + } +} diff --git a/LibraryManagementApp/Migrations/ApplicationDbContextModelSnapshot.cs b/LibraryManagementApp/Migrations/ApplicationDbContextModelSnapshot.cs new file mode 100644 index 0000000..d011046 --- /dev/null +++ b/LibraryManagementApp/Migrations/ApplicationDbContextModelSnapshot.cs @@ -0,0 +1,259 @@ +// +using System; +using LibraryManagementApp.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace LibraryManagementApp.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + partial class ApplicationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("BookCategory", b => + { + b.Property("BooksId") + .HasColumnType("uuid"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.HasKey("BooksId", "CategoryId"); + + b.HasIndex("CategoryId"); + + b.ToTable("BookCategory"); + }); + + modelBuilder.Entity("LibraryManagementApp.Domain.Author", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AvatarUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("DeletedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ModifiedBy") + .HasColumnType("uuid"); + + b.Property("ModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("PublisherId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Authors"); + }); + + modelBuilder.Entity("LibraryManagementApp.Domain.Book", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthorId") + .HasColumnType("uuid"); + + b.Property("CopyrightNotice") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("DeletedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("ISBN") + .HasColumnType("integer"); + + b.Property("Language") + .HasColumnType("integer"); + + b.Property("ModifiedBy") + .HasColumnType("uuid"); + + b.Property("ModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("PageLength") + .HasColumnType("integer"); + + b.Property("PublisherId") + .HasColumnType("uuid"); + + b.Property("ReleaseYear") + .HasColumnType("date"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("AuthorId"); + + b.HasIndex("PublisherId"); + + b.ToTable("Books"); + }); + + modelBuilder.Entity("LibraryManagementApp.Domain.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("DeletedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ModifiedBy") + .HasColumnType("uuid"); + + b.Property("ModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("LibraryManagementApp.Domain.Publisher", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedBy") + .HasColumnType("uuid"); + + b.Property("DeletedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("LogoUrl") + .IsRequired() + .HasColumnType("text"); + + b.Property("ModifiedBy") + .HasColumnType("uuid"); + + b.Property("ModifiedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Publishers"); + }); + + modelBuilder.Entity("BookCategory", b => + { + b.HasOne("LibraryManagementApp.Domain.Book", null) + .WithMany() + .HasForeignKey("BooksId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LibraryManagementApp.Domain.Category", null) + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LibraryManagementApp.Domain.Book", b => + { + b.HasOne("LibraryManagementApp.Domain.Author", "Author") + .WithMany("Books") + .HasForeignKey("AuthorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LibraryManagementApp.Domain.Publisher", null) + .WithMany("Books") + .HasForeignKey("PublisherId"); + + b.Navigation("Author"); + }); + + modelBuilder.Entity("LibraryManagementApp.Domain.Author", b => + { + b.Navigation("Books"); + }); + + modelBuilder.Entity("LibraryManagementApp.Domain.Publisher", b => + { + b.Navigation("Books"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/LibraryManagementApp/Persistence/ApplicationDbContext.cs b/LibraryManagementApp/Persistence/ApplicationDbContext.cs new file mode 100644 index 0000000..365bfea --- /dev/null +++ b/LibraryManagementApp/Persistence/ApplicationDbContext.cs @@ -0,0 +1,26 @@ +using LibraryManagementApp.Domain; +using LibraryManagementApp.Persistence.Configurations; +using Microsoft.EntityFrameworkCore; + +namespace LibraryManagementApp.Persistence; + +public class ApplicationDbContext: DbContext +{ + public ApplicationDbContext(DbContextOptions options) : base(options) + { + + } + + public DbSet Authors => Set(); + public DbSet Books => Set(); + public DbSet Categories => Set(); + public DbSet Publishers => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new AuthorConfiguration()); + modelBuilder.ApplyConfiguration(new BookConfiguration()); + modelBuilder.ApplyConfiguration(new CategoryConfiguration()); + modelBuilder.ApplyConfiguration(new PublisherConfiguration()); + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Persistence/Configurations/AuthorConfiguration.cs b/LibraryManagementApp/Persistence/Configurations/AuthorConfiguration.cs new file mode 100644 index 0000000..daa1236 --- /dev/null +++ b/LibraryManagementApp/Persistence/Configurations/AuthorConfiguration.cs @@ -0,0 +1,15 @@ +using LibraryManagementApp.Domain; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace LibraryManagementApp.Persistence.Configurations; + +public class AuthorConfiguration: IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.Property(a => a.Id) + .IsRequired(); + builder.HasMany(a => a.Books); + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Persistence/Configurations/BookConfiguration.cs b/LibraryManagementApp/Persistence/Configurations/BookConfiguration.cs new file mode 100644 index 0000000..64d2bd8 --- /dev/null +++ b/LibraryManagementApp/Persistence/Configurations/BookConfiguration.cs @@ -0,0 +1,17 @@ +using LibraryManagementApp.Domain; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace LibraryManagementApp.Persistence.Configurations; + +public class BookConfiguration: IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + /*builder.Property(b => b.Id) + .IsRequired(); + builder.HasOne(b => b.Author) + .WithMany(a => a.Books) + .HasForeignKey(b => b.AuthorId);*/ + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Persistence/Configurations/CategoryConfiguration.cs b/LibraryManagementApp/Persistence/Configurations/CategoryConfiguration.cs new file mode 100644 index 0000000..30cd565 --- /dev/null +++ b/LibraryManagementApp/Persistence/Configurations/CategoryConfiguration.cs @@ -0,0 +1,16 @@ +using LibraryManagementApp.Domain; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace LibraryManagementApp.Persistence.Configurations; + +public class CategoryConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.Property(b => b.Id) + .IsRequired(); + builder.HasMany(b => b.Books) + .WithMany(); + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Persistence/Configurations/PublisherConfiguration.cs b/LibraryManagementApp/Persistence/Configurations/PublisherConfiguration.cs new file mode 100644 index 0000000..7a1223a --- /dev/null +++ b/LibraryManagementApp/Persistence/Configurations/PublisherConfiguration.cs @@ -0,0 +1,16 @@ +using LibraryManagementApp.Domain; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace LibraryManagementApp.Persistence.Configurations; + +public class PublisherConfiguration: IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.Property(p => p.Id) + .IsRequired(); + builder.HasMany(p => p.Books) + .WithOne(); + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Persistence/Repository/AuthorRepository.cs b/LibraryManagementApp/Persistence/Repository/AuthorRepository.cs new file mode 100644 index 0000000..0884e18 --- /dev/null +++ b/LibraryManagementApp/Persistence/Repository/AuthorRepository.cs @@ -0,0 +1,11 @@ +using LibraryManagementApp.Contracts.RepositoryContracts; +using LibraryManagementApp.Domain; + +namespace LibraryManagementApp.Persistence.Repository; + +public class AuthorRepository: RepositoryBase, IAuthorRepository +{ + public AuthorRepository(ApplicationDbContext context) : base(context) + { + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Persistence/Repository/BookRepository.cs b/LibraryManagementApp/Persistence/Repository/BookRepository.cs new file mode 100644 index 0000000..4e151c0 --- /dev/null +++ b/LibraryManagementApp/Persistence/Repository/BookRepository.cs @@ -0,0 +1,11 @@ +using LibraryManagementApp.Contracts.RepositoryContracts; +using LibraryManagementApp.Domain; + +namespace LibraryManagementApp.Persistence.Repository; + +public class BookRepository: RepositoryBase, IBookRepository +{ + public BookRepository(ApplicationDbContext context) : base(context) + { + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Persistence/Repository/CategoryRepository.cs b/LibraryManagementApp/Persistence/Repository/CategoryRepository.cs new file mode 100644 index 0000000..7d5ed2b --- /dev/null +++ b/LibraryManagementApp/Persistence/Repository/CategoryRepository.cs @@ -0,0 +1,11 @@ +using LibraryManagementApp.Contracts.RepositoryContracts; +using LibraryManagementApp.Domain; + +namespace LibraryManagementApp.Persistence.Repository; + +public class CategoryRepository: RepositoryBase, ICategoryRepository +{ + public CategoryRepository(ApplicationDbContext context) : base(context) + { + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Persistence/Repository/PublisherRepository.cs b/LibraryManagementApp/Persistence/Repository/PublisherRepository.cs new file mode 100644 index 0000000..822a685 --- /dev/null +++ b/LibraryManagementApp/Persistence/Repository/PublisherRepository.cs @@ -0,0 +1,11 @@ +using LibraryManagementApp.Contracts.RepositoryContracts; +using LibraryManagementApp.Domain; + +namespace LibraryManagementApp.Persistence.Repository; + +public class PublisherRepository: RepositoryBase, IPublisherRepository +{ + public PublisherRepository(ApplicationDbContext context) : base(context) + { + } +} \ No newline at end of file diff --git a/LibraryManagementApp/Persistence/Repository/RepositoryBase.cs b/LibraryManagementApp/Persistence/Repository/RepositoryBase.cs new file mode 100644 index 0000000..706136e --- /dev/null +++ b/LibraryManagementApp/Persistence/Repository/RepositoryBase.cs @@ -0,0 +1,45 @@ +using System.Linq.Expressions; +using LibraryManagementApp.Contracts.RepositoryContracts; +using Microsoft.EntityFrameworkCore; + +namespace LibraryManagementApp.Persistence.Repository; + +public class RepositoryBase: IRepositoryBase where T: class +{ + protected readonly ApplicationDbContext _context; + + public RepositoryBase(ApplicationDbContext context) => + _context = context; + + public T FindById(Guid id) => + _context.Set().Find(id); + + public IQueryable FindByCondition(Expression> expression, bool trackChanges) => + !trackChanges ? + _context.Set() + .Where(expression) + .AsNoTracking() : + _context.Set() + .Where(expression); + + public IQueryable FindAll(bool trackChanges) => + !trackChanges ? + _context.Set() + .AsNoTracking() : + _context.Set(); + + public void Add(T entity) => + _context.Set().Add(entity); + + public void AddRange(IEnumerable entities) => + _context.Set().AddRange(entities); + + public void Update(T entity) => + _context.Set().Update(entity); + + public void Remove(T entity) => + _context.Set().Remove(entity); + + public void RemoveRange(IEnumerable entities) => + _context.Set().RemoveRange(entities); +} \ No newline at end of file diff --git a/LibraryManagementApp/Persistence/Repository/UnitOfWork.cs b/LibraryManagementApp/Persistence/Repository/UnitOfWork.cs new file mode 100644 index 0000000..8b64d33 --- /dev/null +++ b/LibraryManagementApp/Persistence/Repository/UnitOfWork.cs @@ -0,0 +1,28 @@ +using LibraryManagementApp.Contracts.RepositoryContracts; + +namespace LibraryManagementApp.Persistence.Repository; + +public class UnitOfWork: IUnitOfWork +{ + private readonly ApplicationDbContext _context; + + public UnitOfWork(ApplicationDbContext context) + { + _context = context; + Authors = new AuthorRepository(_context); + Books = new BookRepository(_context); + Publishers = new PublisherRepository(_context); + Categories = new CategoryRepository(_context); + } + + public IAuthorRepository Authors { get; private set; } + public IBookRepository Books { get; private set; } + public IPublisherRepository Publishers { get; private set; } + public ICategoryRepository Categories { get; private set; } + + public int Complete() => + _context.SaveChanges(); + + public void Dispose() => + _context.Dispose(); +} \ No newline at end of file diff --git a/LibraryManagementApp/Program.cs b/LibraryManagementApp/Program.cs new file mode 100644 index 0000000..1f7d868 --- /dev/null +++ b/LibraryManagementApp/Program.cs @@ -0,0 +1,41 @@ +using LibraryManagementApp.Contracts.RepositoryContracts; +using LibraryManagementApp.Persistence; +using LibraryManagementApp.Persistence.Repository; +using Microsoft.EntityFrameworkCore; + +var builder = WebApplication.CreateBuilder(args); +var connectionString = builder.Configuration.GetConnectionString("PostgresqlConnectionString"); + +// AddDbContext +builder.Services.AddDbContext(options => + options.UseNpgsql(connectionString)); + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +// Add Services +builder.Services.AddTransient(typeof(IRepositoryBase<>), typeof(RepositoryBase<>)); +builder.Services.AddTransient(); +builder.Services.AddTransient(); +builder.Services.AddTransient(); +builder.Services.AddTransient(); +builder.Services.AddTransient(); + +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/LibraryManagementApp/Properties/launchSettings.json b/LibraryManagementApp/Properties/launchSettings.json new file mode 100644 index 0000000..a7f8d21 --- /dev/null +++ b/LibraryManagementApp/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:20043", + "sslPort": 44351 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5293", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7119;http://localhost:5293", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/LibraryManagementApp/appsettings.Development.json b/LibraryManagementApp/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/LibraryManagementApp/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/LibraryManagementApp/appsettings.json b/LibraryManagementApp/appsettings.json new file mode 100644 index 0000000..9223b41 --- /dev/null +++ b/LibraryManagementApp/appsettings.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + "PostgresqlConnectionString": "User ID =postgres;Password=ologuneru;Host=localhost;Port=5433;Database=librarydb; Integrated Security=true;Pooling=true;" + }, + "AllowedHosts": "*" +}