diff --git a/App.xaml.cs b/App.xaml.cs index 9c150df..c873ce2 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -25,7 +25,7 @@ public partial class App : Application, ConnectionObserver, IApp public void ForceLogin() { - Shell shell = new ConnectAppShell(this, Endpoint.AccountManager, Notifier); + Shell shell = new ConnectAppShell(this, Endpoint.AuthService, Notifier); shell.GoToAsync("//Splash"); MainPage = shell; } 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/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/Constants.cs b/LocalEndpoint/Constants.cs deleted file mode 100644 index 249a016..0000000 --- a/LocalEndpoint/Constants.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Models; - -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/LocalEndpoint.cs b/LocalEndpoint/LocalEndpoint.cs deleted file mode 100644 index 9b27f1e..0000000 --- a/LocalEndpoint/LocalEndpoint.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Endpoint; -using Models; -using System.Collections.Immutable; - -namespace LocalEndpoint -{ - - /// - /// The local endpoint is an implementation of the Endpoint API definition. - /// - /// - public class LocalEndpoint : IEndpoint - { - private readonly IAccountManager accountManager = new ConnectionManager(); - private readonly IRecipesService recipesService; - - - public LocalEndpoint() - { - RecipesDatabase db = new RecipesDatabase(); - - //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())); - - recipesService = new RecipesService(db); - } - - public IAccountManager AccountManager => accountManager; - - public IRecipesService RecipesService => recipesService; - - } -} \ No newline at end of file diff --git a/LocalEndpoint/RecipesDatabase.cs b/LocalEndpoint/RecipesDatabase.cs deleted file mode 100644 index 511cd80..0000000 --- a/LocalEndpoint/RecipesDatabase.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Models; -using System.Collections.Generic; -using System.Collections.Immutable; - -namespace LocalEndpoint -{ - - //Simple class to simulate a recipe database - internal class RecipesDatabase - { - - private Dictionary recipes = new Dictionary(); - - 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; - } - - public void Remove(Guid id) - { - recipes.Remove(id); - } - - public ImmutableList ListAll() - { - return recipes.Values.ToImmutableList(); - } - - } -} diff --git a/LocalEndpoint/AccountOwnedRecipes.cs b/LocalServices/AccountOwnedRecipes.cs similarity index 73% rename from LocalEndpoint/AccountOwnedRecipes.cs rename to LocalServices/AccountOwnedRecipes.cs index e3d8059..633d96e 100644 --- a/LocalEndpoint/AccountOwnedRecipes.cs +++ b/LocalServices/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/LocalServices/AccountRecipesPreferences.cs similarity index 50% rename from LocalEndpoint/AccountRecipesPreferences.cs rename to LocalServices/AccountRecipesPreferences.cs index ce8f296..a7d848a 100644 --- a/LocalEndpoint/AccountRecipesPreferences.cs +++ b/LocalServices/AccountRecipesPreferences.cs @@ -1,24 +1,15 @@ 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 +19,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); @@ -47,10 +40,12 @@ namespace LocalEndpoint public ImmutableList<(RecipeInfo, uint)> GetWeeklyList() { + var weeklyDict = db.GetRecipeListOf(Account.User.Id); 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)) + if (weeklyDict.TryGetValue(recipe.Info.Id, out uint personAmmount)) { weekly.Add((recipe.Info, personAmmount)); } @@ -59,44 +54,56 @@ 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)) + var weeklyDict = db.GetRecipeListOf(Account.User.Id); + if (weeklyDict.ContainsKey(info.Id)) return false; - weekly[info.Id] = persAmount; + db.InsertInUserList(Account.User.Id, info.Id, persAmount); 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/LocalServices/AccountServices.cs b/LocalServices/AccountServices.cs new file mode 100644 index 0000000..02d4d6a --- /dev/null +++ b/LocalServices/AccountServices.cs @@ -0,0 +1,6 @@ +using Endpoint; + +namespace LocalEndpoint +{ + internal record AccountServices(IAccountOwnedRecipes Recipes, IAccountRecipesPreferences Preferences); +} diff --git a/LocalEndpoint/ConnectionManager.cs b/LocalServices/AuthService.cs similarity index 50% rename from LocalEndpoint/ConnectionManager.cs rename to LocalServices/AuthService.cs index 427814e..47ca4fb 100644 --- a/LocalEndpoint/ConnectionManager.cs +++ b/LocalServices/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/LocalServices/Constants.cs b/LocalServices/Constants.cs new file mode 100644 index 0000000..75f80d1 --- /dev/null +++ b/LocalServices/Constants.cs @@ -0,0 +1,9 @@ +using Models; + +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"); + } +} diff --git a/LocalServices/Data/AccountData.cs b/LocalServices/Data/AccountData.cs new file mode 100644 index 0000000..332c7e9 --- /dev/null +++ b/LocalServices/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/LocalServices/Data/CatastrophicPerformancesDatabase.cs b/LocalServices/Data/CatastrophicPerformancesDatabase.cs new file mode 100644 index 0000000..32c48c1 --- /dev/null +++ b/LocalServices/Data/CatastrophicPerformancesDatabase.cs @@ -0,0 +1,160 @@ +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 InsertInUserList(Guid userId, Guid recipeId, uint persAmount) + { + usersData[userId].RecipesList[recipeId] = persAmount; + Save(USERS_FILENAME, USERS_SERIALIZER, usersData); + } + + public void RemoveFromUserList(Guid userId, Guid recipeId) + { + usersData[userId].RecipesList.Remove(recipeId); + Save(USERS_FILENAME, USERS_SERIALIZER, usersData); + } + + + 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(), 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(); + } + + public ImmutableDictionary GetRecipeListOf(Guid user) + { + return usersData[user].RecipesList.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/LocalServices/Data/Database.cs b/LocalServices/Data/Database.cs new file mode 100644 index 0000000..130093a --- /dev/null +++ b/LocalServices/Data/Database.cs @@ -0,0 +1,36 @@ + +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 InsertInUserList(Guid userId, Guid recipeId, uint persAmount); + public void RemoveFromUserList(Guid userId, Guid recipeId); + 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); + + public ImmutableDictionary GetRecipeListOf(Guid user); + } +} diff --git a/LocalServices/Data/RecipeData.cs b/LocalServices/Data/RecipeData.cs new file mode 100644 index 0000000..431f9b6 --- /dev/null +++ b/LocalServices/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/LocalServices/Data/UserData.cs b/LocalServices/Data/UserData.cs new file mode 100644 index 0000000..847d659 --- /dev/null +++ b/LocalServices/Data/UserData.cs @@ -0,0 +1,14 @@ + + +using Models; +using System.Runtime.Serialization; + +namespace LocalEndpoint.Data +{ + [DataContract] + internal record UserData( + [property: DataMember] User User, + [property: DataMember] Dictionary Rates, + [property: DataMember] Dictionary RecipesList + ); +} diff --git a/LocalServices/LocalEndpoint.cs b/LocalServices/LocalEndpoint.cs new file mode 100644 index 0000000..c8f4533 --- /dev/null +++ b/LocalServices/LocalEndpoint.cs @@ -0,0 +1,65 @@ +using Endpoint; +using LocalEndpoint.Data; +using Models; +using System.Collections.Immutable; + +namespace LocalEndpoint +{ + + /// + /// The local endpoint is an implementation of the Endpoint API definition. + /// + /// + public class LocalEndpoint : IEndpoint + { + private readonly IAuthService authService; + private readonly IRecipesService recipesService; + + + public LocalEndpoint() + { + var db = new CatastrophicPerformancesDatabase(Environment.GetFolderPath(Environment.SpecialFolder.Personal)); + + if (db.IsEmpty()) + PrepareDatabase(db); + + recipesService = new RecipesService(db); + authService = new AuthService(db); + } + + public IAuthService AuthService => 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/LocalEndpoint.csproj b/LocalServices/LocalServices.csproj similarity index 82% rename from LocalEndpoint/LocalEndpoint.csproj rename to LocalServices/LocalServices.csproj index b84a3cb..62f7504 100644 --- a/LocalEndpoint/LocalEndpoint.csproj +++ b/LocalServices/LocalServices.csproj @@ -7,7 +7,7 @@ - + diff --git a/LocalEndpoint/RecipesService.cs b/LocalServices/RecipesService.cs similarity index 54% rename from LocalEndpoint/RecipesService.cs rename to LocalServices/RecipesService.cs index 9684e02..8969e9e 100644 --- a/LocalEndpoint/RecipesService.cs +++ b/LocalServices/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,17 +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); - recipes.UploadRecipe(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())); 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 3a40a75..10e7655 100644 --- a/Models/Ingredient.cs +++ b/Models/Ingredient.cs @@ -1,4 +1,9 @@ -namespace Models +using System.Runtime.Serialization; + +namespace Models { - public record Ingredient(string Name, float Amount); + + + [DataContract] + public record Ingredient([property: DataMember] string Name, [property: DataMember] float Amount); } diff --git a/Models/PreparationStep.cs b/Models/PreparationStep.cs index 40899a7..35e4734 100644 --- a/Models/PreparationStep.cs +++ b/Models/PreparationStep.cs @@ -1,5 +1,7 @@ -namespace Models -{ +using System.Runtime.Serialization; - public record PreparationStep(string Name, string Description); +namespace Models +{ + [DataContract] + public record PreparationStep([property: DataMember] string Name, [property: DataMember] string Description); } diff --git a/Models/Recipe.cs b/Models/Recipe.cs index e077c22..021d99a 100644 --- a/Models/Recipe.cs +++ b/Models/Recipe.cs @@ -1,10 +1,14 @@ using System.Collections.Immutable; +using System.Runtime.Serialization; namespace Models { + [DataContract] public record Recipe( - RecipeInfo Info, - User Owner, - ImmutableList Ingredients, - ImmutableList Steps); + [property: DataMember] RecipeInfo Info, + [property: DataMember] User Owner, + [property: DataMember] ImmutableList Ingredients, + [property: DataMember] ImmutableList Steps + ); + } \ No newline at end of file diff --git a/Models/RecipeInfo.cs b/Models/RecipeInfo.cs index 1265c96..fec0ebb 100644 --- a/Models/RecipeInfo.cs +++ b/Models/RecipeInfo.cs @@ -1,10 +1,14 @@ -namespace Models +using System.Runtime.Serialization; + +namespace Models { + [DataContract] public record RecipeInfo( - string Name, - uint CalPerPers, - uint CookTimeMins, - Uri? Image, - float AverageNote, - Guid Id); + [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 981cef6..b70f7b1 100644 --- a/Models/User.cs +++ b/Models/User.cs @@ -1,4 +1,36 @@ -namespace Models +using System.Runtime.Serialization; + +namespace Models { - public record User(Uri ProfilePicture, string Name); + [DataContract] + public class User + { + [DataMember] + public Uri ProfilePicture { get; init; } + [DataMember] + public string Name { get; init; } + [DataMember] + public Guid Id { get; init; } + + 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); + } + + override public int GetHashCode() + { + return Id.GetHashCode(); + } + } } diff --git a/Platforms/Android/AndroidManifest.xml b/Platforms/Android/AndroidManifest.xml index 60c24e0..7871058 100644 --- a/Platforms/Android/AndroidManifest.xml +++ b/Platforms/Android/AndroidManifest.xml @@ -3,4 +3,5 @@ + \ No newline at end of file diff --git a/Endpoint/IAccountOwnedRecipes.cs b/Services/IAccountOwnedRecipes.cs similarity index 100% rename from Endpoint/IAccountOwnedRecipes.cs rename to Services/IAccountOwnedRecipes.cs diff --git a/Endpoint/IAccountRecipesPreferences.cs b/Services/IAccountRecipesPreferences.cs similarity index 91% rename from Endpoint/IAccountRecipesPreferences.cs rename to Services/IAccountRecipesPreferences.cs index 1a40745..64d4f35 100644 --- a/Endpoint/IAccountRecipesPreferences.cs +++ b/Services/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/Services/IAuthService.cs similarity index 84% rename from Endpoint/IAccountManager.cs rename to Services/IAuthService.cs index c3b697b..0e450e2 100644 --- a/Endpoint/IAccountManager.cs +++ b/Services/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/Services/IEndpoint.cs similarity index 70% rename from Endpoint/IEndpoint.cs rename to Services/IEndpoint.cs index 10906ee..b928557 100644 --- a/Endpoint/IEndpoint.cs +++ b/Services/IEndpoint.cs @@ -4,7 +4,7 @@ namespace Endpoint { public interface IEndpoint { - public IAccountManager AccountManager { get; } + public IAuthService AuthService { get; } public IRecipesService RecipesService { get; } diff --git a/Endpoint/IRecipesService.cs b/Services/IRecipesService.cs similarity index 77% rename from Endpoint/IRecipesService.cs rename to Services/IRecipesService.cs index f9159c2..88e8b65 100644 --- a/Endpoint/IRecipesService.cs +++ b/Services/IRecipesService.cs @@ -1,11 +1,6 @@ using LocalEndpoint; using Models; -using System; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Endpoint { diff --git a/Endpoint/Endpoint.csproj b/Services/Services.csproj similarity index 95% rename from Endpoint/Endpoint.csproj rename to Services/Services.csproj index d24baee..e45d1d7 100644 --- a/Endpoint/Endpoint.csproj +++ b/Services/Services.csproj @@ -1,13 +1,13 @@ - - - - net7.0 - enable - enable - - - - - - - + + + + net7.0 + enable + enable + + + + + + + diff --git a/ShoopNCook.csproj b/ShoopNCook.csproj index 063bc5b..8a80861 100644 --- a/ShoopNCook.csproj +++ b/ShoopNCook.csproj @@ -48,28 +48,28 @@ - - + + - - + + - - + + - - + + - - + + @@ -118,8 +118,8 @@ - - + + diff --git a/ShoopNCook.sln b/ShoopNCook.sln index 6ca3619..3b6c720 100644 --- a/ShoopNCook.sln +++ b/ShoopNCook.sln @@ -8,9 +8,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Models", "Models\Models.csproj", "{A9D43E07-345D-4DD4-B4F9-CE69ED569B5F}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LocalEndpoint", "LocalEndpoint\LocalEndpoint.csproj", "{57732316-93B9-4DA0-A212-F8892D3D968B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LocalServices", "LocalServices\LocalServices.csproj", "{57732316-93B9-4DA0-A212-F8892D3D968B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Endpoint", "Endpoint\Endpoint.csproj", "{C976BDD8-710D-4162-8A42-973B634491F9}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Services", "Services\Services.csproj", "{C976BDD8-710D-4162-8A42-973B634491F9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Views/Components/RecipeView.xaml b/Views/Components/RecipeView.xaml index 32922c3..624860f 100644 --- a/Views/Components/RecipeView.xaml +++ b/Views/Components/RecipeView.xaml @@ -17,6 +17,7 @@ MinimumWidthRequest="150" MaximumWidthRequest="150" RowDefinitions="*, Auto"> + diff --git a/Views/FavoritesPage.xaml.cs b/Views/FavoritesPage.xaml.cs index 8282f86..20b9ef2 100644 --- a/Views/FavoritesPage.xaml.cs +++ b/Views/FavoritesPage.xaml.cs @@ -3,10 +3,8 @@ using Models; namespace ShoopNCook.Pages; using Endpoint; -using LocalEndpoint; using Models; using ShoopNCook.Views; -using System.Security.Principal; public partial class FavoritesPage : ContentPage { diff --git a/Views/RecipePage.xaml b/Views/RecipePage.xaml index 63e2e0f..74fcf55 100644 --- a/Views/RecipePage.xaml +++ b/Views/RecipePage.xaml @@ -14,21 +14,20 @@ + RowDefinitions="Auto, Auto, *, Auto"> + AlignItems="Center" + HeightRequest="60"> - @@ -111,7 +109,8 @@ + Grid.Row="3" + MaximumHeightRequest="45">