diff --git a/ConnectAppShell.xaml.cs b/ConnectAppShell.xaml.cs index 29f3275..dd44352 100644 --- a/ConnectAppShell.xaml.cs +++ b/ConnectAppShell.xaml.cs @@ -7,7 +7,7 @@ using ShoopNCook.Pages; public partial class ConnectAppShell : Shell { - public ConnectAppShell(ConnectionObserver observer, IAccountManager accounts, IUserNotifier notifier) + public ConnectAppShell(ConnectionObserver observer, IAuthService accounts, IUserNotifier notifier) { ConnectionController controller = new ConnectionController(observer, accounts, notifier); InitializeComponent(); diff --git a/Controllers/ConnectionController.cs b/Controllers/ConnectionController.cs index 6ecfd51..0e59bb7 100644 --- a/Controllers/ConnectionController.cs +++ b/Controllers/ConnectionController.cs @@ -6,9 +6,9 @@ namespace ShoopNCook.Controllers public class ConnectionController : LoginController, RegisterController { private readonly ConnectionObserver observer; - private readonly IAccountManager accounts; + private readonly IAuthService accounts; private readonly IUserNotifier notifier; - public ConnectionController(ConnectionObserver observer, IAccountManager accounts, IUserNotifier notifier) { + public ConnectionController(ConnectionObserver observer, IAuthService accounts, IUserNotifier notifier) { this.observer = observer; this.accounts = accounts; this.notifier = notifier; diff --git a/Endpoint/IAccountRecipesPreferences.cs b/Endpoint/IAccountRecipesPreferences.cs index 1a40745..64d4f35 100644 --- a/Endpoint/IAccountRecipesPreferences.cs +++ b/Endpoint/IAccountRecipesPreferences.cs @@ -13,7 +13,7 @@ namespace Endpoint public void SetReviewScore(RecipeInfo info, uint score); public bool AddToWeeklyList(RecipeInfo info, uint persAmount); - public AccountRecipeRate GetRate(RecipeInfo info); + public RecipeRate GetRate(RecipeInfo info); public ImmutableList GetFavorites(); diff --git a/Endpoint/IAccountManager.cs b/Endpoint/IAuthService.cs similarity index 84% rename from Endpoint/IAccountManager.cs rename to Endpoint/IAuthService.cs index c61b79e..2b4fa73 100644 --- a/Endpoint/IAccountManager.cs +++ b/Endpoint/IAuthService.cs @@ -1,7 +1,7 @@ using Models; namespace Endpoint { - public interface IAccountManager + public interface IAuthService { public Account? Login(string email, string password); diff --git a/Endpoint/IEndpoint.cs b/Endpoint/IEndpoint.cs index 10906ee..bd75452 100644 --- a/Endpoint/IEndpoint.cs +++ b/Endpoint/IEndpoint.cs @@ -4,7 +4,7 @@ namespace Endpoint { public interface IEndpoint { - public IAccountManager AccountManager { get; } + public IAuthService AccountManager { get; } public IRecipesService RecipesService { get; } diff --git a/LocalEndpoint/AccountData.cs b/LocalEndpoint/AccountData.cs deleted file mode 100644 index cf84b07..0000000 --- a/LocalEndpoint/AccountData.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Endpoint; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LocalEndpoint -{ - internal record AccountData(IAccountOwnedRecipes Recipes, IAccountRecipesPreferences Preferences); -} diff --git a/LocalEndpoint/AccountOwnedRecipes.cs b/LocalEndpoint/AccountOwnedRecipes.cs index e3d8059..633d96e 100644 --- a/LocalEndpoint/AccountOwnedRecipes.cs +++ b/LocalEndpoint/AccountOwnedRecipes.cs @@ -1,4 +1,5 @@ using LocalEndpoint; +using LocalEndpoint.Data; using Models; using System.Collections.Immutable; @@ -10,17 +11,18 @@ namespace Endpoint public Account Account { get; init; } private readonly Dictionary ownedRecipes = new Dictionary(); - private readonly RecipesDatabase db; + private readonly Database db; - public AccountOwnedRecipes(Account account, RecipesDatabase db) + public AccountOwnedRecipes(Account account, Database db) { Account = account; this.db = db; //Retrieve all owned recipes from database. - db.ListAll().ForEach(recipe => + db.ListAllRecipes().ForEach(recipe => { - if (recipe.Owner == account.User) ownedRecipes[recipe.Info.Id] = recipe; + if (recipe.Owner.Equals(account.User)) + ownedRecipes.Add(recipe.Info.Id, recipe); }); } @@ -31,14 +33,14 @@ namespace Endpoint { return false; } - db.Insert(recipe); + db.InsertRecipe(recipe); ownedRecipes.Add(id, recipe); return true; } public bool RemoveRecipe(RecipeInfo info) { - db.Remove(info.Id); + db.RemoveRecipe(info.Id); return ownedRecipes.Remove(info.Id); } diff --git a/LocalEndpoint/AccountRecipesPreferences.cs b/LocalEndpoint/AccountRecipesPreferences.cs index ce8f296..d260838 100644 --- a/LocalEndpoint/AccountRecipesPreferences.cs +++ b/LocalEndpoint/AccountRecipesPreferences.cs @@ -1,24 +1,18 @@ using Endpoint; +using LocalEndpoint.Data; using Models; -using System; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace LocalEndpoint { internal class AccountRecipesPreferences : IAccountRecipesPreferences { - //Binds a recipe's id to it's account review ratings. - private readonly Dictionary ratings = new Dictionary(); //Binds a recipe's id to its amount of person stored in the account's weekly list private readonly Dictionary weekly = new Dictionary(); - private readonly RecipesDatabase db; - public AccountRecipesPreferences(Account account, RecipesDatabase db) + private readonly Database db; + public AccountRecipesPreferences(Account account, Database db) { Account = account; this.db = db; @@ -28,15 +22,17 @@ namespace LocalEndpoint public ImmutableList GetRecommendedRecipes() { - return db.ListAll().ConvertAll(recipe => recipe.Info); + return db.ListAllRecipes().ConvertAll(recipe => recipe.Info); } public ImmutableList GetFavorites() { List favorites = new List(); - foreach (Recipe recipe in db.ListAll()) + var ratings = db.ListRatesOf(Account.User.Id); + + foreach (Recipe recipe in db.ListAllRecipes()) { - if (ratings.TryGetValue(recipe.Info.Id, out AccountRecipeRate? rate)) + if (ratings.TryGetValue(recipe.Info.Id, out RecipeRate? rate)) { if (rate.IsFavorite) favorites.Add(recipe.Info); @@ -48,7 +44,7 @@ namespace LocalEndpoint public ImmutableList<(RecipeInfo, uint)> GetWeeklyList() { List<(RecipeInfo, uint)> weekly = new List<(RecipeInfo, uint)>(); - foreach (Recipe recipe in db.ListAll()) + foreach (Recipe recipe in db.ListAllRecipes()) { if (this.weekly.TryGetValue(recipe.Info.Id, out uint personAmmount)) { @@ -59,24 +55,19 @@ namespace LocalEndpoint } - public AccountRecipeRate GetRate(RecipeInfo info) + public RecipeRate GetRate(RecipeInfo info) { - AccountRecipeRate rate = null; + RecipeRate rate = null; + var ratings = db.ListRatesOf(Account.User.Id); + if (!ratings.TryGetValue(info.Id, out rate)) { - rate = new AccountRecipeRate(); + rate = new RecipeRate(); ratings.Add(info.Id, rate); } return rate; } - public void AddToFavorites(RecipeInfo info) - { - AccountRecipeRate rate = GetRate(info); - ratings[info.Id] = new AccountRecipeRate(true, rate.Rate); - } - - public bool AddToWeeklyList(RecipeInfo info, uint persAmount) { if (weekly.ContainsKey(info.Id)) @@ -87,16 +78,32 @@ namespace LocalEndpoint return true; } + + public void AddToFavorites(RecipeInfo info) + { + Guid userId = Account.User.Id; + var ratings = db.ListRatesOf(userId); + RecipeRate rate = GetRate(info); + + db.InsertRate(userId, info.Id, new RecipeRate(true, rate.Rate)); + } + public void RemoveFromFavorites(RecipeInfo info) { - AccountRecipeRate rate = GetRate(info); - ratings[info.Id] = new AccountRecipeRate(false, rate.Rate); + Guid userId = Account.User.Id; + var ratings = db.ListRatesOf(userId); + RecipeRate rate = GetRate(info); + + db.InsertRate(userId, info.Id, new RecipeRate(false, rate.Rate)); } public void SetReviewScore(RecipeInfo info, uint score) { - AccountRecipeRate rate = GetRate(info); - ratings[info.Id] = new AccountRecipeRate(rate.IsFavorite, score); + Guid userId = Account.User.Id; + var ratings = db.ListRatesOf(userId); + RecipeRate rate = GetRate(info); + + db.InsertRate(userId, info.Id, new RecipeRate(rate.IsFavorite, score)); } } } diff --git a/LocalEndpoint/AccountServices.cs b/LocalEndpoint/AccountServices.cs new file mode 100644 index 0000000..02d4d6a --- /dev/null +++ b/LocalEndpoint/AccountServices.cs @@ -0,0 +1,6 @@ +using Endpoint; + +namespace LocalEndpoint +{ + internal record AccountServices(IAccountOwnedRecipes Recipes, IAccountRecipesPreferences Preferences); +} diff --git a/LocalEndpoint/ConnectionManager.cs b/LocalEndpoint/AuthService.cs similarity index 50% rename from LocalEndpoint/ConnectionManager.cs rename to LocalEndpoint/AuthService.cs index 427814e..47ca4fb 100644 --- a/LocalEndpoint/ConnectionManager.cs +++ b/LocalEndpoint/AuthService.cs @@ -5,28 +5,33 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using LocalEndpoint.Data; +using System.Security.Cryptography; namespace LocalEndpoint { - internal class ConnectionManager : IAccountManager + internal class AuthService : IAuthService { - private Account userAccount = Constants.MAIN_USER_ACCOUNT; - private string userPassword = Constants.MAIN_USER_PASSWORD; - public Account? Login(string email, string password) + private readonly Database db; + + + public AuthService(Database db) { - if (Constants.MAIN_USER_ACCOUNT.Email == email && Constants.MAIN_USER_PASSWORD == password) - return Constants.MAIN_USER_ACCOUNT; - return null; + this.db = db; } + public Account? Login(string email, string password) + { + return db.GetAccount(email, password); + } public Account? Register(string email, string username, string password) { if (email == null || username == null || password == null) return null; - userAccount = new Account(new User(Constants.DEFAULT_ACCOUNT_IMAGE, username), email); - userPassword = password; + var userAccount = new Account(new User(Constants.DEFAULT_ACCOUNT_IMAGE, username, Guid.NewGuid()), email); + db.InsertAccount(userAccount, password); return userAccount; } } diff --git a/LocalEndpoint/Constants.cs b/LocalEndpoint/Constants.cs index 249a016..75f80d1 100644 --- a/LocalEndpoint/Constants.cs +++ b/LocalEndpoint/Constants.cs @@ -4,17 +4,6 @@ namespace LocalEndpoint { internal class Constants { - public static readonly Uri DEFAULT_ACCOUNT_IMAGE = new Uri("https://www.pngkey.com/png/full/115-1150152_default-profile-picture-avatar-png-green.png"); - - // User account for tests - public static readonly Account MAIN_USER_ACCOUNT = new Account(new User(DEFAULT_ACCOUNT_IMAGE, "Example Account"), "test@example.com"); - public static readonly string MAIN_USER_PASSWORD = "123456"; - - // other user samples - public static readonly User USER1 = new User(new Uri("https://i.ibb.co/L6t6bGR/DALL-E-2023-05-10-20-27-31-cook-looking-at-the-camera-with-a-chef-s-hat-laughing-in-an-exaggerated-w.png"), "The Funny Chief"); - public static readonly User USER2 = new User(DEFAULT_ACCOUNT_IMAGE, "Yanis"); - public static readonly User USER3 = new User(DEFAULT_ACCOUNT_IMAGE, "Leo"); - } } diff --git a/LocalEndpoint/Data/AccountData.cs b/LocalEndpoint/Data/AccountData.cs new file mode 100644 index 0000000..332c7e9 --- /dev/null +++ b/LocalEndpoint/Data/AccountData.cs @@ -0,0 +1,17 @@ +using Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace LocalEndpoint.Data +{ + [DataContract] + internal record AccountData( + [property: DataMember] Guid UserId, + [property: DataMember] string Email, + [property: DataMember] string PasswordHash + ); +} diff --git a/LocalEndpoint/Data/CatastrophicPerformancesDatabase.cs b/LocalEndpoint/Data/CatastrophicPerformancesDatabase.cs new file mode 100644 index 0000000..136b2ab --- /dev/null +++ b/LocalEndpoint/Data/CatastrophicPerformancesDatabase.cs @@ -0,0 +1,143 @@ +using Models; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace LocalEndpoint.Data +{ + /// + /// Database implementation with catastrophic performances. + /// This database implementation persists data in xml and will save all the data in their files on each mutable requests. + /// + internal class CatastrophicPerformancesDatabase : Database + { + + private static readonly DataContractSerializer RECIPES_SERIALIZER = new DataContractSerializer(typeof(Dictionary)); + private static readonly DataContractSerializer USERS_SERIALIZER = new DataContractSerializer(typeof(Dictionary)); + private static readonly DataContractSerializer ACCOUNTS_SERIALIZER = new DataContractSerializer(typeof(Dictionary)); + + private static readonly string RECIPES_FILENAME = "recipes_data.xml"; + private static readonly string USERS_FILENAME = "users_data.xml"; + private static readonly string ACCOUNTS_FILENAME = "accounts_data.xml"; + + private readonly Dictionary recipesData; + private readonly Dictionary usersData; + private readonly Dictionary accountsData; + + private readonly string dbPath; + + + public CatastrophicPerformancesDatabase(string folderPath) + { + dbPath = folderPath; + if (!Directory.Exists(folderPath)) + Directory.CreateDirectory(folderPath); + + usersData = Load(USERS_FILENAME, USERS_SERIALIZER); + recipesData = Load(RECIPES_FILENAME, RECIPES_SERIALIZER); + accountsData = Load(ACCOUNTS_FILENAME, ACCOUNTS_SERIALIZER); + } + + public bool IsEmpty() + { + return recipesData.Count == 0 && usersData.Count == 0 && accountsData.Count == 0; + } + + public Account? GetAccount(string email, string passwordHash) + { + if (!accountsData.TryGetValue(email, out AccountData? data)) + return null; + + if (data.PasswordHash != passwordHash) return null; + return new Account(usersData[data.UserId].User, data.Email); + } + + public void InsertAccount(Account account, string passwordHash) + { + accountsData[account.Email] = new AccountData(account.User.Id, account.Email, passwordHash); + Save(ACCOUNTS_FILENAME, ACCOUNTS_SERIALIZER, accountsData); + } + + public Recipe GetRecipe(Guid id) + { + return ConvertRecipeDataToRecipe(recipesData[id]); + } + + public RecipeRate GetRecipeRate(Guid user, Guid recipe) + { + return usersData[user].Rates[recipe]; + } + + + public void InsertRecipe(Recipe recipe) + { + recipesData[recipe.Info.Id] = new RecipeData(recipe.Info, recipe.Owner.Id, recipe.Ingredients, recipe.Steps); + Save(RECIPES_FILENAME, RECIPES_SERIALIZER, recipesData); + } + + public void InsertUser(User user) + { + usersData[user.Id] = new UserData(user, new Dictionary()); + Save(USERS_FILENAME, USERS_SERIALIZER, usersData); + } + + public void InsertRate(Guid userId, Guid recipeId, RecipeRate rate) + { + usersData[userId].Rates[recipeId] = rate; + Save(USERS_FILENAME, USERS_SERIALIZER, usersData); + } + + public void RemoveRecipe(Guid id) + { + recipesData.Remove(id); + Save(RECIPES_FILENAME, RECIPES_SERIALIZER, recipesData); + } + + public ImmutableList ListAllRecipes() + { + return recipesData.Values.ToImmutableList().ConvertAll(ConvertRecipeDataToRecipe); + } + + + public ImmutableDictionary ListRatesOf(Guid user) + { + return usersData[user].Rates.ToImmutableDictionary(); + } + + private Recipe ConvertRecipeDataToRecipe(RecipeData rd) + { + var owner = usersData[rd.OwnerID].User; + return new Recipe(rd.Info, owner, rd.Ingredients, rd.Steps); + } + + private Dictionary Load(string fileName, DataContractSerializer deserializer) + { + var file = dbPath + "/" + fileName; + var fileInfo = new FileInfo(file); + + if (!fileInfo.Exists) + fileInfo.Create(); + + if (fileInfo.Length == 0) + return new Dictionary(); //file is empty thus there is nothing to deserialize + Console.WriteLine(File.ReadAllText(file)); + + using (var stream = File.OpenRead(file)) + return deserializer.ReadObject(stream) as Dictionary ?? throw new Exception("object read from " + file + " is not a dictionnary"); + } + + private void Save(string fileName, DataContractSerializer serializer, Dictionary dict) + { + using (var stream = File.OpenWrite(dbPath + "/" + fileName)) + { + serializer.WriteObject(stream, dict); + stream.Flush(); + } + } + + } +} diff --git a/LocalEndpoint/Data/Database.cs b/LocalEndpoint/Data/Database.cs new file mode 100644 index 0000000..b27004a --- /dev/null +++ b/LocalEndpoint/Data/Database.cs @@ -0,0 +1,32 @@ + +using Models; +using System.Collections.Immutable; + +namespace LocalEndpoint.Data +{ + + // The database interface defines all the different kinds of requests the LocalEndpoint needs to store and retrieve data. + public interface Database + { + + public Recipe GetRecipe(Guid id); + + public RecipeRate GetRecipeRate(Guid user, Guid recipe); + + public Account? GetAccount(string email, string passwordHash); + + public void InsertAccount(Account account, string passwordHash); + + public void InsertRecipe(Recipe recipe); + + public void InsertUser(User user); + + public void InsertRate(Guid userId, Guid recipeId, RecipeRate rate); + + public void RemoveRecipe(Guid id); + + public ImmutableList ListAllRecipes(); + + public ImmutableDictionary ListRatesOf(Guid user); + } +} diff --git a/LocalEndpoint/Data/RecipeData.cs b/LocalEndpoint/Data/RecipeData.cs new file mode 100644 index 0000000..431f9b6 --- /dev/null +++ b/LocalEndpoint/Data/RecipeData.cs @@ -0,0 +1,14 @@ +using Models; +using System.Collections.Immutable; +using System.Runtime.Serialization; + +namespace LocalEndpoint.Data +{ + [DataContract] + internal record RecipeData( + [property: DataMember] RecipeInfo Info, + [property: DataMember] Guid OwnerID, + [property: DataMember] ImmutableList Ingredients, + [property: DataMember] ImmutableList Steps + ); +} \ No newline at end of file diff --git a/LocalEndpoint/Data/UserData.cs b/LocalEndpoint/Data/UserData.cs new file mode 100644 index 0000000..bad1f5e --- /dev/null +++ b/LocalEndpoint/Data/UserData.cs @@ -0,0 +1,13 @@ + + +using Models; +using System.Runtime.Serialization; + +namespace LocalEndpoint.Data +{ + [DataContract] + internal record UserData( + [property: DataMember] User User, + [property: DataMember] Dictionary Rates + ); +} diff --git a/LocalEndpoint/LocalEndpoint.cs b/LocalEndpoint/LocalEndpoint.cs index 666a9de..ca4875b 100644 --- a/LocalEndpoint/LocalEndpoint.cs +++ b/LocalEndpoint/LocalEndpoint.cs @@ -1,4 +1,5 @@ using Endpoint; +using LocalEndpoint.Data; using Models; using System.Collections.Immutable; @@ -11,28 +12,54 @@ namespace LocalEndpoint /// public class LocalEndpoint : IEndpoint { - private readonly IAccountManager accountManager = new ConnectionManager(); + private readonly IAuthService authService; private readonly IRecipesService recipesService; public LocalEndpoint() { - RecipesDatabase db = new RecipesDatabase(Environment.GetFolderPath(Environment.SpecialFolder.Personal) + "/recipes.xaml"); - - //miam - /*db.Insert(new Recipe(new RecipeInfo("Chicken Salad", 500, 20, new Uri("https://healthyfitnessmeals.com/wp-content/uploads/2021/04/Southwest-chicken-salad-7-500x500.jpg"), 4, Guid.NewGuid()), Constants.USER1, new List { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList())); - db.Insert(new Recipe(new RecipeInfo("Chocolate Cake", 2500, 10, new Uri("https://bakewithshivesh.com/wp-content/uploads/2022/08/IMG_0248-scaled.jpg"), 3, Guid.NewGuid()), Constants.USER2, new List { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList())); - db.Insert(new Recipe(new RecipeInfo("Salmon", 20, 10, new Uri("https://www.wholesomeyum.com/wp-content/uploads/2021/06/wholesomeyum-Pan-Seared-Salmon-Recipe-13.jpg"), 4, Guid.NewGuid()), Constants.MAIN_USER_ACCOUNT.User, new List { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList())); - db.Insert(new Recipe(new RecipeInfo("Fish", 50, 30, new Uri("https://www.ciaanet.org/wp-content/uploads/2022/07/Atlantic-and-Pacific-whole-salmon-1024x683.jpg"), 4.5F, Guid.NewGuid()), Constants.MAIN_USER_ACCOUNT.User, new List { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList())); - db.Insert(new Recipe(new RecipeInfo("Space Cake", 800, 5, new Uri("https://static.youmiam.com/images/recipe/1500x1000/space-cake-22706?placeholder=web_recipe&sig=f14a7a86da837c6b8cc678cde424d6d5902f99ec&v3"), 5, Guid.NewGuid()), Constants.USER3, new List { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList())); - db.Insert(new Recipe(new RecipeInfo("Cupcake", 500, 12, new Uri("https://www.mycake.fr/wp-content/uploads/2015/12/rs_cupcake_4x3.jpg"), 4.2F, Guid.NewGuid()), account.User, new List { new Ingredient("Chocolate", 4) }.ToImmutableList(), new List { new PreparationStep("Eat Chocolate", "Eat the chocolate") }.ToImmutableList())); - */ + var db = new CatastrophicPerformancesDatabase(Environment.GetFolderPath(Environment.SpecialFolder.Personal)); + + if (db.IsEmpty()) + PrepareDatabase(db); + recipesService = new RecipesService(db); + authService = new AuthService(db); } - public IAccountManager AccountManager => accountManager; + public IAuthService AccountManager => authService; public IRecipesService RecipesService => recipesService; + private static void PrepareDatabase(Database db) + { + User USER1 = new User(new Uri("https://i.ibb.co/L6t6bGR/DALL-E-2023-05-10-20-27-31-cook-looking-at-the-camera-with-a-chef-s-hat-laughing-in-an-exaggerated-w.png"), "The Funny Chief", MakeGuid(1)); + User USER2 = new User(Constants.DEFAULT_ACCOUNT_IMAGE, "Yanis", MakeGuid(2)); + User USER3 = new User(Constants.DEFAULT_ACCOUNT_IMAGE, "Leo", MakeGuid(3)); + + db.InsertUser(USER1); + db.InsertUser(USER2); + db.InsertUser(USER3); + + db.InsertAccount(new Account(USER1, "chief@cook.com"), "123456"); + db.InsertAccount(new Account(USER2, "yanis@google.com"), "123456"); + db.InsertAccount(new Account(USER3, "leo@google.com"), "123456"); + + db.InsertRecipe(new Recipe(new RecipeInfo("Chicken Salad", 500, 20, new Uri("https://healthyfitnessmeals.com/wp-content/uploads/2021/04/Southwest-chicken-salad-7-500x500.jpg"), 4, Guid.NewGuid()), USER1, new List { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList())); + db.InsertRecipe(new Recipe(new RecipeInfo("Chocolate Cake", 2500, 10, new Uri("https://bakewithshivesh.com/wp-content/uploads/2022/08/IMG_0248-scaled.jpg"), 3, Guid.NewGuid()), USER2, new List { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList())); + db.InsertRecipe(new Recipe(new RecipeInfo("Salmon", 20, 10, new Uri("https://www.wholesomeyum.com/wp-content/uploads/2021/06/wholesomeyum-Pan-Seared-Salmon-Recipe-13.jpg"), 4, Guid.NewGuid()), USER1, new List { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList())); + db.InsertRecipe(new Recipe(new RecipeInfo("Fish", 50, 30, new Uri("https://www.ciaanet.org/wp-content/uploads/2022/07/Atlantic-and-Pacific-whole-salmon-1024x683.jpg"), 4.5F, Guid.NewGuid()), USER3, new List { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList())); + db.InsertRecipe(new Recipe(new RecipeInfo("Space Cake", 800, 5, new Uri("https://static.youmiam.com/images/recipe/1500x1000/space-cake-22706?placeholder=web_recipe&sig=f14a7a86da837c6b8cc678cde424d6d5902f99ec&v3"), 5, Guid.NewGuid()), USER3, new List { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList())); + db.InsertRecipe(new Recipe(new RecipeInfo("Cupcake", 500, 12, new Uri("https://www.mycake.fr/wp-content/uploads/2015/12/rs_cupcake_4x3.jpg"), 4.2F, Guid.NewGuid()), USER1, new List { new Ingredient("Chocolate", 4) }.ToImmutableList(), new List { new PreparationStep("Eat Chocolate", "Eat the chocolate") }.ToImmutableList())); + } + + private static Guid MakeGuid(int seed) + { + var r = new Random(seed); + var guid = new byte[16]; + r.NextBytes(guid); + + return new Guid(guid); + } } } \ No newline at end of file diff --git a/LocalEndpoint/RecipesDatabase.cs b/LocalEndpoint/RecipesDatabase.cs deleted file mode 100644 index 70aba26..0000000 --- a/LocalEndpoint/RecipesDatabase.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Models; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Runtime.Serialization; - -namespace LocalEndpoint -{ - - internal class RecipesDatabase - { - - private static readonly DataContractSerializer RECIPES_SERIALIZER = new DataContractSerializer(typeof(List)); - - private readonly Dictionary recipes = new Dictionary(); - private readonly string dbPath; - - public RecipesDatabase(string path) - { - dbPath = path; - - - if (!File.Exists(dbPath)) - { - File.Create(dbPath); - } - if (new FileInfo(dbPath).Length == 0) - return; //file is empty thus there is nothing to deserialize - Console.WriteLine(File.ReadAllText(dbPath)); - - using (var stream = File.OpenRead(dbPath)) { - var recipes = RECIPES_SERIALIZER.ReadObject(stream) as List; - recipes.ForEach(recipe => this.recipes.Add(recipe.Info.Id, recipe)); - } - } - - private void SaveAll() - { - using (var stream = File.OpenWrite(dbPath)) - { - RECIPES_SERIALIZER.WriteObject(stream, recipes.Values.ToList()); - } - } - - public Recipe? Lookup(Guid id) - { - Recipe? recipe; - recipes.TryGetValue(id, out recipe); - return recipe; - } - - - public Recipe Get(Guid id) - { - return recipes[id]; - } - - public void Insert(Recipe recipe) - { - recipes[recipe.Info.Id] = recipe; - SaveAll(); - } - - public void Remove(Guid id) - { - recipes.Remove(id); - SaveAll(); - } - - public ImmutableList ListAll() - { - return recipes.Values.ToImmutableList(); - } - - } -} diff --git a/LocalEndpoint/RecipesService.cs b/LocalEndpoint/RecipesService.cs index e8df0c7..8969e9e 100644 --- a/LocalEndpoint/RecipesService.cs +++ b/LocalEndpoint/RecipesService.cs @@ -1,4 +1,5 @@ using Endpoint; +using LocalEndpoint.Data; using Models; using System.Collections.Immutable; @@ -7,22 +8,22 @@ namespace LocalEndpoint internal class RecipesService : IRecipesService { - private readonly RecipesDatabase db; - private readonly Dictionary accountsData = new Dictionary(); + private readonly Database db; + private readonly Dictionary accountsData = new Dictionary(); - public RecipesService(RecipesDatabase db) + public RecipesService(Database db) { this.db = db; } public ImmutableList PopularRecipes() { - return db.ListAll().Take(4).ToImmutableList().ConvertAll(v => v.Info); + return db.ListAllRecipes().Take(4).ToImmutableList().ConvertAll(v => v.Info); } public Recipe GetRecipe(RecipeInfo info) { - return db.Get(info.Id); + return db.GetRecipe(info.Id); } @@ -35,16 +36,16 @@ namespace LocalEndpoint return GetOrInitData(account).Preferences; } - private AccountData GetOrInitData(Account account) + private AccountServices GetOrInitData(Account account) { - AccountData? data; + AccountServices? data; accountsData.TryGetValue(account, out data); if (data == null) { AccountOwnedRecipes recipes = new AccountOwnedRecipes(account, db); AccountRecipesPreferences preferences = new AccountRecipesPreferences(account, db); - data = new AccountData(recipes, preferences); + data = new AccountServices(recipes, preferences); accountsData.Add(account, data); } return data; diff --git a/Models/AccountRecipeRate.cs b/Models/AccountRecipeRate.cs deleted file mode 100644 index caf7658..0000000 --- a/Models/AccountRecipeRate.cs +++ /dev/null @@ -1,5 +0,0 @@ - -namespace Models -{ - public record AccountRecipeRate(bool IsFavorite = false, uint Rate = 0); -} diff --git a/Models/Ingredient.cs b/Models/Ingredient.cs index 7caaa9d..10e7655 100644 --- a/Models/Ingredient.cs +++ b/Models/Ingredient.cs @@ -2,18 +2,8 @@ namespace Models { - [DataContract] - public class Ingredient - { - [DataMember] - public string Name { get; init; } - [DataMember] - public float Amount { get; init; } - public Ingredient(string name, float amount) - { - Name = name; - Amount = amount; - } - } + + [DataContract] + public record Ingredient([property: DataMember] string Name, [property: DataMember] float Amount); } diff --git a/Models/PreparationStep.cs b/Models/PreparationStep.cs index 779f797..35e4734 100644 --- a/Models/PreparationStep.cs +++ b/Models/PreparationStep.cs @@ -3,17 +3,5 @@ namespace Models { [DataContract] - public record PreparationStep - { - [DataMember] - public string Name { get; init; } - [DataMember] - public string Description { get; init; } - - public PreparationStep(string name, string description) - { - Name = name; - Description = description; - } - } + public record PreparationStep([property: DataMember] string Name, [property: DataMember] string Description); } diff --git a/Models/Recipe.cs b/Models/Recipe.cs index f6152bf..021d99a 100644 --- a/Models/Recipe.cs +++ b/Models/Recipe.cs @@ -4,23 +4,11 @@ using System.Runtime.Serialization; namespace Models { [DataContract] - public class Recipe - { - [DataMember] - public RecipeInfo Info { get; init; } - [DataMember] - public User Owner { get; init; } - [DataMember] - public ImmutableList Ingredients { get; init; } - [DataMember] - public ImmutableList Steps { get; init; } + public record Recipe( + [property: DataMember] RecipeInfo Info, + [property: DataMember] User Owner, + [property: DataMember] ImmutableList Ingredients, + [property: DataMember] ImmutableList Steps + ); - public Recipe(RecipeInfo info, User owner, ImmutableList ingredients, ImmutableList steps) - { - Info = info; - Owner = owner; - Ingredients = ingredients; - Steps = steps; - } - } } \ No newline at end of file diff --git a/Models/RecipeInfo.cs b/Models/RecipeInfo.cs index 2dfbbcc..fec0ebb 100644 --- a/Models/RecipeInfo.cs +++ b/Models/RecipeInfo.cs @@ -3,29 +3,12 @@ namespace Models { [DataContract] - public class RecipeInfo - { - [DataMember] - public string Name { get; init; } - [DataMember] - public uint CalPerPers { get; init; } - [DataMember] - public uint CookTimeMins { get; init; } - [DataMember] - public Uri? Image { get; init; } - [DataMember] - public float AverageNote { get; init; } - [DataMember] - public Guid Id { get; init; } - - public RecipeInfo(string name, uint calPerPers, uint cookTimeMins, Uri? image, float averageNote, Guid id) - { - Name = name; - CalPerPers = calPerPers; - CookTimeMins = cookTimeMins; - Image = image; - AverageNote = averageNote; - Id = id; - } - } + public record RecipeInfo( + [property: DataMember] string Name, + [property: DataMember] uint CalPerPers, + [property: DataMember] uint CookTimeMins, + [property: DataMember] Uri? Image, + [property: DataMember] float AverageNote, + [property: DataMember] Guid Id + ); } diff --git a/Models/RecipeRate.cs b/Models/RecipeRate.cs new file mode 100644 index 0000000..b4dc3a3 --- /dev/null +++ b/Models/RecipeRate.cs @@ -0,0 +1,11 @@ + +using System.Runtime.Serialization; + +namespace Models +{ + [DataContract] + public record RecipeRate( + [property: DataMember] bool IsFavorite = false, + [property: DataMember] uint Rate = 0 + ); +} diff --git a/Models/User.cs b/Models/User.cs index 2273a93..21cd678 100644 --- a/Models/User.cs +++ b/Models/User.cs @@ -9,11 +9,23 @@ namespace Models public Uri ProfilePicture { get; init; } [DataMember] public string Name { get; init; } + [DataMember] + public Guid Id { get; init; } - public User(Uri profilePicture, string name) + public User(Uri profilePicture, string name, Guid id) { ProfilePicture = profilePicture; Name = name; + Id = id; + } + + public override bool Equals(object? other) + { + if (this == other) + return true; + + User? otherUser = other as User; + return otherUser != null && Id.Equals(otherUser.Id); } } } diff --git a/Views/Components/RecipeView.xaml b/Views/Components/RecipeView.xaml index 8e794da..7409572 100644 --- a/Views/Components/RecipeView.xaml +++ b/Views/Components/RecipeView.xaml @@ -14,6 +14,7 @@ MinimumHeightRequest="175" MinimumWidthRequest="150" RowDefinitions="*, Auto"> + diff --git a/Views/RecipePage.xaml.cs b/Views/RecipePage.xaml.cs index d40478d..33531f3 100644 --- a/Views/RecipePage.xaml.cs +++ b/Views/RecipePage.xaml.cs @@ -27,7 +27,7 @@ public partial class RecipePage : ContentPage this.notifier = notifier; this.info = recipe.Info; - AccountRecipeRate rate = preferences.GetRate(recipe.Info); + RecipeRate rate = preferences.GetRate(recipe.Info); note = rate.Rate; isFavorite = rate.IsFavorite; @@ -80,6 +80,10 @@ public partial class RecipePage : ContentPage private void OnFavorite(object o, EventArgs e) { SetFavorite(!isFavorite); + if (isFavorite) + preferences.AddToFavorites(info); + else + preferences.RemoveFromFavorites(info); } private void OnSubmitReviewClicked(object o, EventArgs e) @@ -100,19 +104,12 @@ public partial class RecipePage : ContentPage { this.isFavorite = isFavorite; if (isFavorite) - { Favorite.Source = ImageSource.FromFile("hearth_on.svg"); - preferences.AddToFavorites(info); - } else - { Favorite.Source = ImageSource.FromFile("hearth_off.svg"); - preferences.RemoveFromFavorites(info); - } } private void OnBackButtonClicked(object sender, EventArgs e) { - Navigation.PopAsync(); }