add catastrophic performance persistance system for accounts, users,
continuous-integration/drone/push Build is passing Details

recipes and recipe rates
pull/52/head
maxime.BATISTA@etu.uca.fr 2 years ago
parent 6c47bf5dc9
commit eb4d0cf7ca

@ -7,7 +7,7 @@ using ShoopNCook.Pages;
public partial class ConnectAppShell : Shell 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); ConnectionController controller = new ConnectionController(observer, accounts, notifier);
InitializeComponent(); InitializeComponent();

@ -6,9 +6,9 @@ namespace ShoopNCook.Controllers
public class ConnectionController : LoginController, RegisterController public class ConnectionController : LoginController, RegisterController
{ {
private readonly ConnectionObserver observer; private readonly ConnectionObserver observer;
private readonly IAccountManager accounts; private readonly IAuthService accounts;
private readonly IUserNotifier notifier; private readonly IUserNotifier notifier;
public ConnectionController(ConnectionObserver observer, IAccountManager accounts, IUserNotifier notifier) { public ConnectionController(ConnectionObserver observer, IAuthService accounts, IUserNotifier notifier) {
this.observer = observer; this.observer = observer;
this.accounts = accounts; this.accounts = accounts;
this.notifier = notifier; this.notifier = notifier;

@ -13,7 +13,7 @@ namespace Endpoint
public void SetReviewScore(RecipeInfo info, uint score); public void SetReviewScore(RecipeInfo info, uint score);
public bool AddToWeeklyList(RecipeInfo info, uint persAmount); public bool AddToWeeklyList(RecipeInfo info, uint persAmount);
public AccountRecipeRate GetRate(RecipeInfo info); public RecipeRate GetRate(RecipeInfo info);
public ImmutableList<RecipeInfo> GetFavorites(); public ImmutableList<RecipeInfo> GetFavorites();

@ -1,7 +1,7 @@
using Models; using Models;
namespace Endpoint namespace Endpoint
{ {
public interface IAccountManager public interface IAuthService
{ {
public Account? Login(string email, string password); public Account? Login(string email, string password);

@ -4,7 +4,7 @@ namespace Endpoint
{ {
public interface IEndpoint public interface IEndpoint
{ {
public IAccountManager AccountManager { get; } public IAuthService AccountManager { get; }
public IRecipesService RecipesService { get; } public IRecipesService RecipesService { get; }

@ -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);
}

@ -1,4 +1,5 @@
using LocalEndpoint; using LocalEndpoint;
using LocalEndpoint.Data;
using Models; using Models;
using System.Collections.Immutable; using System.Collections.Immutable;
@ -10,17 +11,18 @@ namespace Endpoint
public Account Account { get; init; } public Account Account { get; init; }
private readonly Dictionary<Guid, Recipe> ownedRecipes = new Dictionary<Guid, Recipe>(); private readonly Dictionary<Guid, Recipe> ownedRecipes = new Dictionary<Guid, Recipe>();
private readonly RecipesDatabase db; private readonly Database db;
public AccountOwnedRecipes(Account account, RecipesDatabase db) public AccountOwnedRecipes(Account account, Database db)
{ {
Account = account; Account = account;
this.db = db; this.db = db;
//Retrieve all owned recipes from database. //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; return false;
} }
db.Insert(recipe); db.InsertRecipe(recipe);
ownedRecipes.Add(id, recipe); ownedRecipes.Add(id, recipe);
return true; return true;
} }
public bool RemoveRecipe(RecipeInfo info) public bool RemoveRecipe(RecipeInfo info)
{ {
db.Remove(info.Id); db.RemoveRecipe(info.Id);
return ownedRecipes.Remove(info.Id); return ownedRecipes.Remove(info.Id);
} }

@ -1,24 +1,18 @@
using Endpoint; using Endpoint;
using LocalEndpoint.Data;
using Models; using Models;
using System;
using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LocalEndpoint namespace LocalEndpoint
{ {
internal class AccountRecipesPreferences : IAccountRecipesPreferences internal class AccountRecipesPreferences : IAccountRecipesPreferences
{ {
//Binds a recipe's id to it's account review ratings.
private readonly Dictionary<Guid, AccountRecipeRate> ratings = new Dictionary<Guid, AccountRecipeRate>();
//Binds a recipe's id to its amount of person stored in the account's weekly list //Binds a recipe's id to its amount of person stored in the account's weekly list
private readonly Dictionary<Guid, uint> weekly = new Dictionary<Guid, uint>(); private readonly Dictionary<Guid, uint> weekly = new Dictionary<Guid, uint>();
private readonly RecipesDatabase db; private readonly Database db;
public AccountRecipesPreferences(Account account, RecipesDatabase db) public AccountRecipesPreferences(Account account, Database db)
{ {
Account = account; Account = account;
this.db = db; this.db = db;
@ -28,15 +22,17 @@ namespace LocalEndpoint
public ImmutableList<RecipeInfo> GetRecommendedRecipes() public ImmutableList<RecipeInfo> GetRecommendedRecipes()
{ {
return db.ListAll().ConvertAll(recipe => recipe.Info); return db.ListAllRecipes().ConvertAll(recipe => recipe.Info);
} }
public ImmutableList<RecipeInfo> GetFavorites() public ImmutableList<RecipeInfo> GetFavorites()
{ {
List<RecipeInfo> favorites = new List<RecipeInfo>(); List<RecipeInfo> favorites = new List<RecipeInfo>();
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) if (rate.IsFavorite)
favorites.Add(recipe.Info); favorites.Add(recipe.Info);
@ -48,7 +44,7 @@ namespace LocalEndpoint
public ImmutableList<(RecipeInfo, uint)> GetWeeklyList() public ImmutableList<(RecipeInfo, uint)> GetWeeklyList()
{ {
List<(RecipeInfo, uint)> weekly = new List<(RecipeInfo, uint)>(); 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 (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)) if (!ratings.TryGetValue(info.Id, out rate))
{ {
rate = new AccountRecipeRate(); rate = new RecipeRate();
ratings.Add(info.Id, rate); ratings.Add(info.Id, rate);
} }
return 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) public bool AddToWeeklyList(RecipeInfo info, uint persAmount)
{ {
if (weekly.ContainsKey(info.Id)) if (weekly.ContainsKey(info.Id))
@ -87,16 +78,32 @@ namespace LocalEndpoint
return true; 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) public void RemoveFromFavorites(RecipeInfo info)
{ {
AccountRecipeRate rate = GetRate(info); Guid userId = Account.User.Id;
ratings[info.Id] = new AccountRecipeRate(false, rate.Rate); 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) public void SetReviewScore(RecipeInfo info, uint score)
{ {
AccountRecipeRate rate = GetRate(info); Guid userId = Account.User.Id;
ratings[info.Id] = new AccountRecipeRate(rate.IsFavorite, score); var ratings = db.ListRatesOf(userId);
RecipeRate rate = GetRate(info);
db.InsertRate(userId, info.Id, new RecipeRate(rate.IsFavorite, score));
} }
} }
} }

@ -0,0 +1,6 @@
using Endpoint;
namespace LocalEndpoint
{
internal record AccountServices(IAccountOwnedRecipes Recipes, IAccountRecipesPreferences Preferences);
}

@ -5,28 +5,33 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using LocalEndpoint.Data;
using System.Security.Cryptography;
namespace LocalEndpoint namespace LocalEndpoint
{ {
internal class ConnectionManager : IAccountManager internal class AuthService : IAuthService
{ {
private Account userAccount = Constants.MAIN_USER_ACCOUNT; private readonly Database db;
private string userPassword = Constants.MAIN_USER_PASSWORD;
public Account? Login(string email, string password)
public AuthService(Database db)
{ {
if (Constants.MAIN_USER_ACCOUNT.Email == email && Constants.MAIN_USER_PASSWORD == password) this.db = db;
return Constants.MAIN_USER_ACCOUNT;
return null;
} }
public Account? Login(string email, string password)
{
return db.GetAccount(email, password);
}
public Account? Register(string email, string username, string password) public Account? Register(string email, string username, string password)
{ {
if (email == null || username == null || password == null) if (email == null || username == null || password == null)
return null; return null;
userAccount = new Account(new User(Constants.DEFAULT_ACCOUNT_IMAGE, username), email); var userAccount = new Account(new User(Constants.DEFAULT_ACCOUNT_IMAGE, username, Guid.NewGuid()), email);
userPassword = password; db.InsertAccount(userAccount, password);
return userAccount; return userAccount;
} }
} }

@ -4,17 +4,6 @@ namespace LocalEndpoint
{ {
internal class Constants 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"); 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");
} }
} }

@ -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
);
}

@ -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
{
/// <summary>
/// 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.
/// </summary>
internal class CatastrophicPerformancesDatabase : Database
{
private static readonly DataContractSerializer RECIPES_SERIALIZER = new DataContractSerializer(typeof(Dictionary<Guid, RecipeData>));
private static readonly DataContractSerializer USERS_SERIALIZER = new DataContractSerializer(typeof(Dictionary<Guid, UserData>));
private static readonly DataContractSerializer ACCOUNTS_SERIALIZER = new DataContractSerializer(typeof(Dictionary<string, AccountData>));
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<Guid, RecipeData> recipesData;
private readonly Dictionary<Guid, UserData> usersData;
private readonly Dictionary<string, AccountData> accountsData;
private readonly string dbPath;
public CatastrophicPerformancesDatabase(string folderPath)
{
dbPath = folderPath;
if (!Directory.Exists(folderPath))
Directory.CreateDirectory(folderPath);
usersData = Load<Guid, UserData>(USERS_FILENAME, USERS_SERIALIZER);
recipesData = Load<Guid, RecipeData>(RECIPES_FILENAME, RECIPES_SERIALIZER);
accountsData = Load<string, AccountData>(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<Guid, RecipeRate>());
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<Recipe> ListAllRecipes()
{
return recipesData.Values.ToImmutableList().ConvertAll(ConvertRecipeDataToRecipe);
}
public ImmutableDictionary<Guid, RecipeRate> 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<K, V> Load<K, V>(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<K, V>(); //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<K, V> ?? throw new Exception("object read from " + file + " is not a dictionnary");
}
private void Save<K, T>(string fileName, DataContractSerializer serializer, Dictionary<K, T> dict)
{
using (var stream = File.OpenWrite(dbPath + "/" + fileName))
{
serializer.WriteObject(stream, dict);
stream.Flush();
}
}
}
}

@ -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<Recipe> ListAllRecipes();
public ImmutableDictionary<Guid, RecipeRate> ListRatesOf(Guid user);
}
}

@ -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<Ingredient> Ingredients,
[property: DataMember] ImmutableList<PreparationStep> Steps
);
}

@ -0,0 +1,13 @@

using Models;
using System.Runtime.Serialization;
namespace LocalEndpoint.Data
{
[DataContract]
internal record UserData(
[property: DataMember] User User,
[property: DataMember] Dictionary<Guid, RecipeRate> Rates
);
}

@ -1,4 +1,5 @@
using Endpoint; using Endpoint;
using LocalEndpoint.Data;
using Models; using Models;
using System.Collections.Immutable; using System.Collections.Immutable;
@ -11,28 +12,54 @@ namespace LocalEndpoint
/// </summary> /// </summary>
public class LocalEndpoint : IEndpoint public class LocalEndpoint : IEndpoint
{ {
private readonly IAccountManager accountManager = new ConnectionManager(); private readonly IAuthService authService;
private readonly IRecipesService recipesService; private readonly IRecipesService recipesService;
public LocalEndpoint() public LocalEndpoint()
{ {
RecipesDatabase db = new RecipesDatabase(Environment.GetFolderPath(Environment.SpecialFolder.Personal) + "/recipes.xaml"); var db = new CatastrophicPerformancesDatabase(Environment.GetFolderPath(Environment.SpecialFolder.Personal));
//miam if (db.IsEmpty())
/*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<Ingredient> { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List<PreparationStep> { new PreparationStep("Step 1", "Bake the eggs") }.ToImmutableList())); PrepareDatabase(db);
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<Ingredient> { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List<PreparationStep> { 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<Ingredient> { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List<PreparationStep> { 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<Ingredient> { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List<PreparationStep> { 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<Ingredient> { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List<PreparationStep> { 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<Ingredient> { new Ingredient("Chocolate", 4) }.ToImmutableList(), new List<PreparationStep> { new PreparationStep("Eat Chocolate", "Eat the chocolate") }.ToImmutableList()));
*/
recipesService = new RecipesService(db); recipesService = new RecipesService(db);
authService = new AuthService(db);
} }
public IAccountManager AccountManager => accountManager; public IAuthService AccountManager => authService;
public IRecipesService RecipesService => recipesService; 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<Ingredient> { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List<PreparationStep> { 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<Ingredient> { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List<PreparationStep> { 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<Ingredient> { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List<PreparationStep> { 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<Ingredient> { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List<PreparationStep> { 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<Ingredient> { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List<PreparationStep> { 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<Ingredient> { new Ingredient("Chocolate", 4) }.ToImmutableList(), new List<PreparationStep> { 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);
}
} }
} }

@ -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<Recipe>));
private readonly Dictionary<Guid, Recipe> recipes = new Dictionary<Guid, Recipe>();
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<Recipe>;
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<Recipe> ListAll()
{
return recipes.Values.ToImmutableList();
}
}
}

@ -1,4 +1,5 @@
using Endpoint; using Endpoint;
using LocalEndpoint.Data;
using Models; using Models;
using System.Collections.Immutable; using System.Collections.Immutable;
@ -7,22 +8,22 @@ namespace LocalEndpoint
internal class RecipesService : IRecipesService internal class RecipesService : IRecipesService
{ {
private readonly RecipesDatabase db; private readonly Database db;
private readonly Dictionary<Account, AccountData> accountsData = new Dictionary<Account, AccountData>(); private readonly Dictionary<Account, AccountServices> accountsData = new Dictionary<Account, AccountServices>();
public RecipesService(RecipesDatabase db) public RecipesService(Database db)
{ {
this.db = db; this.db = db;
} }
public ImmutableList<RecipeInfo> PopularRecipes() public ImmutableList<RecipeInfo> 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) 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; return GetOrInitData(account).Preferences;
} }
private AccountData GetOrInitData(Account account) private AccountServices GetOrInitData(Account account)
{ {
AccountData? data; AccountServices? data;
accountsData.TryGetValue(account, out data); accountsData.TryGetValue(account, out data);
if (data == null) if (data == null)
{ {
AccountOwnedRecipes recipes = new AccountOwnedRecipes(account, db); AccountOwnedRecipes recipes = new AccountOwnedRecipes(account, db);
AccountRecipesPreferences preferences = new AccountRecipesPreferences(account, db); AccountRecipesPreferences preferences = new AccountRecipesPreferences(account, db);
data = new AccountData(recipes, preferences); data = new AccountServices(recipes, preferences);
accountsData.Add(account, data); accountsData.Add(account, data);
} }
return data; return data;

@ -1,5 +0,0 @@

namespace Models
{
public record AccountRecipeRate(bool IsFavorite = false, uint Rate = 0);
}

@ -2,18 +2,8 @@
namespace Models namespace Models
{ {
[DataContract]
public class Ingredient
{
[DataMember]
public string Name { get; init; }
[DataMember]
public float Amount { get; init; }
public Ingredient(string name, float amount)
{ [DataContract]
Name = name; public record Ingredient([property: DataMember] string Name, [property: DataMember] float Amount);
Amount = amount;
}
}
} }

@ -3,17 +3,5 @@
namespace Models namespace Models
{ {
[DataContract] [DataContract]
public record PreparationStep public record PreparationStep([property: DataMember] string Name, [property: DataMember] string Description);
{
[DataMember]
public string Name { get; init; }
[DataMember]
public string Description { get; init; }
public PreparationStep(string name, string description)
{
Name = name;
Description = description;
}
}
} }

@ -4,23 +4,11 @@ using System.Runtime.Serialization;
namespace Models namespace Models
{ {
[DataContract] [DataContract]
public class Recipe public record Recipe(
{ [property: DataMember] RecipeInfo Info,
[DataMember] [property: DataMember] User Owner,
public RecipeInfo Info { get; init; } [property: DataMember] ImmutableList<Ingredient> Ingredients,
[DataMember] [property: DataMember] ImmutableList<PreparationStep> Steps
public User Owner { get; init; } );
[DataMember]
public ImmutableList<Ingredient> Ingredients { get; init; }
[DataMember]
public ImmutableList<PreparationStep> Steps { get; init; }
public Recipe(RecipeInfo info, User owner, ImmutableList<Ingredient> ingredients, ImmutableList<PreparationStep> steps)
{
Info = info;
Owner = owner;
Ingredients = ingredients;
Steps = steps;
}
}
} }

@ -3,29 +3,12 @@
namespace Models namespace Models
{ {
[DataContract] [DataContract]
public class RecipeInfo public record RecipeInfo(
{ [property: DataMember] string Name,
[DataMember] [property: DataMember] uint CalPerPers,
public string Name { get; init; } [property: DataMember] uint CookTimeMins,
[DataMember] [property: DataMember] Uri? Image,
public uint CalPerPers { get; init; } [property: DataMember] float AverageNote,
[DataMember] [property: DataMember] Guid Id
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;
}
}
} }

@ -0,0 +1,11 @@

using System.Runtime.Serialization;
namespace Models
{
[DataContract]
public record RecipeRate(
[property: DataMember] bool IsFavorite = false,
[property: DataMember] uint Rate = 0
);
}

@ -9,11 +9,23 @@ namespace Models
public Uri ProfilePicture { get; init; } public Uri ProfilePicture { get; init; }
[DataMember] [DataMember]
public string Name { get; init; } 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; ProfilePicture = profilePicture;
Name = name; 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);
} }
} }
} }

@ -14,6 +14,7 @@
MinimumHeightRequest="175" MinimumHeightRequest="175"
MinimumWidthRequest="150" MinimumWidthRequest="150"
RowDefinitions="*, Auto"> RowDefinitions="*, Auto">
<Grid.GestureRecognizers> <Grid.GestureRecognizers>
<TapGestureRecognizer Tapped="OnRecipeTapped"/> <TapGestureRecognizer Tapped="OnRecipeTapped"/>
</Grid.GestureRecognizers> </Grid.GestureRecognizers>

@ -27,7 +27,7 @@ public partial class RecipePage : ContentPage
this.notifier = notifier; this.notifier = notifier;
this.info = recipe.Info; this.info = recipe.Info;
AccountRecipeRate rate = preferences.GetRate(recipe.Info); RecipeRate rate = preferences.GetRate(recipe.Info);
note = rate.Rate; note = rate.Rate;
isFavorite = rate.IsFavorite; isFavorite = rate.IsFavorite;
@ -80,6 +80,10 @@ public partial class RecipePage : ContentPage
private void OnFavorite(object o, EventArgs e) private void OnFavorite(object o, EventArgs e)
{ {
SetFavorite(!isFavorite); SetFavorite(!isFavorite);
if (isFavorite)
preferences.AddToFavorites(info);
else
preferences.RemoveFromFavorites(info);
} }
private void OnSubmitReviewClicked(object o, EventArgs e) private void OnSubmitReviewClicked(object o, EventArgs e)
@ -100,19 +104,12 @@ public partial class RecipePage : ContentPage
{ {
this.isFavorite = isFavorite; this.isFavorite = isFavorite;
if (isFavorite) if (isFavorite)
{
Favorite.Source = ImageSource.FromFile("hearth_on.svg"); Favorite.Source = ImageSource.FromFile("hearth_on.svg");
preferences.AddToFavorites(info);
}
else else
{
Favorite.Source = ImageSource.FromFile("hearth_off.svg"); Favorite.Source = ImageSource.FromFile("hearth_off.svg");
preferences.RemoveFromFavorites(info);
}
} }
private void OnBackButtonClicked(object sender, EventArgs e) private void OnBackButtonClicked(object sender, EventArgs e)
{ {
Navigation.PopAsync(); Navigation.PopAsync();
} }

Loading…
Cancel
Save