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 ConnectAppShell(ConnectionObserver observer, IAccountManager accounts, IUserNotifier notifier)
public ConnectAppShell(ConnectionObserver observer, IAuthService accounts, IUserNotifier notifier)
{
ConnectionController controller = new ConnectionController(observer, accounts, notifier);
InitializeComponent();

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

@ -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<RecipeInfo> GetFavorites();

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

@ -4,7 +4,7 @@ namespace Endpoint
{
public interface IEndpoint
{
public IAccountManager AccountManager { get; }
public IAuthService AccountManager { 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.Data;
using Models;
using System.Collections.Immutable;
@ -10,17 +11,18 @@ namespace Endpoint
public Account Account { get; init; }
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;
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);
}

@ -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<Guid, AccountRecipeRate> ratings = new Dictionary<Guid, AccountRecipeRate>();
//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 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<RecipeInfo> GetRecommendedRecipes()
{
return db.ListAll().ConvertAll(recipe => recipe.Info);
return db.ListAllRecipes().ConvertAll(recipe => recipe.Info);
}
public ImmutableList<RecipeInfo> GetFavorites()
{
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)
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));
}
}
}

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

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

@ -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 LocalEndpoint.Data;
using Models;
using System.Collections.Immutable;
@ -11,28 +12,54 @@ namespace LocalEndpoint
/// </summary>
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<Ingredient> { new Ingredient("Ingredient 1", 6) }.ToImmutableList(), new List<PreparationStep> { 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<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()));
*/
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<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 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<Account, AccountData> accountsData = new Dictionary<Account, AccountData>();
private readonly Database db;
private readonly Dictionary<Account, AccountServices> accountsData = new Dictionary<Account, AccountServices>();
public RecipesService(RecipesDatabase db)
public RecipesService(Database db)
{
this.db = db;
}
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)
{
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;

@ -1,5 +0,0 @@

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

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

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

@ -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<Ingredient> Ingredients { get; init; }
[DataMember]
public ImmutableList<PreparationStep> Steps { get; init; }
public record Recipe(
[property: DataMember] RecipeInfo Info,
[property: DataMember] User Owner,
[property: DataMember] ImmutableList<Ingredient> Ingredients,
[property: DataMember] ImmutableList<PreparationStep> Steps
);
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
{
[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
);
}

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

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

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

Loading…
Cancel
Save