Merge pull request 'Persist all user data, recipes and accounts in a xaml files database' (#52) from local-endpoint/persistance into master
continuous-integration/drone/push Build is passing Details

Reviewed-on: ShopNCook/ShopNCook#52
pull/54/head
Maxime BATISTA 2 years ago
commit 211f313bce

@ -25,7 +25,7 @@ public partial class App : Application, ConnectionObserver, IApp
public void ForceLogin() public void ForceLogin()
{ {
Shell shell = new ConnectAppShell(this, Endpoint.AccountManager, Notifier); Shell shell = new ConnectAppShell(this, Endpoint.AuthService, Notifier);
shell.GoToAsync("//Splash"); shell.GoToAsync("//Splash");
MainPage = shell; MainPage = shell;
} }

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

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

@ -1,37 +0,0 @@
using Endpoint;
using Models;
using System.Collections.Immutable;
namespace LocalEndpoint
{
/// <summary>
/// The local endpoint is an implementation of the Endpoint API definition.
///
/// </summary>
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<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()));
recipesService = new RecipesService(db);
}
public IAccountManager AccountManager => accountManager;
public IRecipesService RecipesService => recipesService;
}
}

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

@ -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,15 @@
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 private readonly Database db;
private readonly Dictionary<Guid, uint> weekly = new Dictionary<Guid, uint>(); public AccountRecipesPreferences(Account account, Database db)
private readonly RecipesDatabase db;
public AccountRecipesPreferences(Account account, RecipesDatabase db)
{ {
Account = account; Account = account;
this.db = db; this.db = db;
@ -28,15 +19,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);
@ -47,10 +40,12 @@ namespace LocalEndpoint
public ImmutableList<(RecipeInfo, uint)> GetWeeklyList() public ImmutableList<(RecipeInfo, uint)> GetWeeklyList()
{ {
var weeklyDict = db.GetRecipeListOf(Account.User.Id);
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 (weeklyDict.TryGetValue(recipe.Info.Id, out uint personAmmount))
{ {
weekly.Add((recipe.Info, 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)) 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)) var weeklyDict = db.GetRecipeListOf(Account.User.Id);
if (weeklyDict.ContainsKey(info.Id))
return false; return false;
weekly[info.Id] = persAmount; db.InsertInUserList(Account.User.Id, info.Id, persAmount);
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;
} }
} }

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

@ -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,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
{
/// <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 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<Guid, RecipeRate>(), new Dictionary<Guid, uint>());
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();
}
public ImmutableDictionary<Guid, uint> 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<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,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<Recipe> ListAllRecipes();
public ImmutableDictionary<Guid, RecipeRate> ListRatesOf(Guid user);
public ImmutableDictionary<Guid, uint> GetRecipeListOf(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,14 @@

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

@ -0,0 +1,65 @@
using Endpoint;
using LocalEndpoint.Data;
using Models;
using System.Collections.Immutable;
namespace LocalEndpoint
{
/// <summary>
/// The local endpoint is an implementation of the Endpoint API definition.
///
/// </summary>
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<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);
}
}
}

@ -7,7 +7,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Endpoint\Endpoint.csproj" /> <ProjectReference Include="..\Services\Services.csproj" />
<ProjectReference Include="..\Models\Models.csproj" /> <ProjectReference Include="..\Models\Models.csproj" />
</ItemGroup> </ItemGroup>

@ -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,17 +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);
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<Ingredient> { new Ingredient("Chocolate", 4) }.ToImmutableList(), new List<PreparationStep> { new PreparationStep("Eat Chocolate", "Eat the chocolate") }.ToImmutableList()));
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);
}

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

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

@ -1,10 +1,14 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Runtime.Serialization;
namespace Models namespace Models
{ {
[DataContract]
public record Recipe( public record Recipe(
RecipeInfo Info, [property: DataMember] RecipeInfo Info,
User Owner, [property: DataMember] User Owner,
ImmutableList<Ingredient> Ingredients, [property: DataMember] ImmutableList<Ingredient> Ingredients,
ImmutableList<PreparationStep> Steps); [property: DataMember] ImmutableList<PreparationStep> Steps
);
} }

@ -1,10 +1,14 @@
namespace Models using System.Runtime.Serialization;
namespace Models
{ {
[DataContract]
public record RecipeInfo( public record RecipeInfo(
string Name, [property: DataMember] string Name,
uint CalPerPers, [property: DataMember] uint CalPerPers,
uint CookTimeMins, [property: DataMember] uint CookTimeMins,
Uri? Image, [property: DataMember] Uri? Image,
float AverageNote, [property: DataMember] float AverageNote,
Guid Id); [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
);
}

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

@ -3,4 +3,5 @@
<application android:allowBackup="true" android:supportsRtl="true"></application> <application android:allowBackup="true" android:supportsRtl="true"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest> </manifest>

@ -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 AuthService { get; }
public IRecipesService RecipesService { get; } public IRecipesService RecipesService { get; }

@ -1,11 +1,6 @@
using LocalEndpoint; using LocalEndpoint;
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 Endpoint namespace Endpoint
{ {

@ -48,28 +48,28 @@
<ItemGroup> <ItemGroup>
<AndroidResource Remove="ShopNCookTests\**" /> <AndroidResource Remove="ShopNCookTests\**" />
<AndroidResource Remove="Tests\**" /> <AndroidResource Remove="Tests\**" />
<Compile Remove="Endpoint\**" /> <Compile Remove="Services\**" />
<Compile Remove="LocalEndpoint\**" /> <Compile Remove="LocalServices\**" />
<Compile Remove="Models\**" /> <Compile Remove="Models\**" />
<Compile Remove="ShopNCookTests\**" /> <Compile Remove="ShopNCookTests\**" />
<Compile Remove="Tests\**" /> <Compile Remove="Tests\**" />
<EmbeddedResource Remove="Endpoint\**" /> <EmbeddedResource Remove="Services\**" />
<EmbeddedResource Remove="LocalEndpoint\**" /> <EmbeddedResource Remove="LocalServices\**" />
<EmbeddedResource Remove="Models\**" /> <EmbeddedResource Remove="Models\**" />
<EmbeddedResource Remove="ShopNCookTests\**" /> <EmbeddedResource Remove="ShopNCookTests\**" />
<EmbeddedResource Remove="Tests\**" /> <EmbeddedResource Remove="Tests\**" />
<MauiCss Remove="Endpoint\**" /> <MauiCss Remove="Services\**" />
<MauiCss Remove="LocalEndpoint\**" /> <MauiCss Remove="LocalServices\**" />
<MauiCss Remove="Models\**" /> <MauiCss Remove="Models\**" />
<MauiCss Remove="ShopNCookTests\**" /> <MauiCss Remove="ShopNCookTests\**" />
<MauiCss Remove="Tests\**" /> <MauiCss Remove="Tests\**" />
<MauiXaml Remove="Endpoint\**" /> <MauiXaml Remove="Services\**" />
<MauiXaml Remove="LocalEndpoint\**" /> <MauiXaml Remove="LocalServices\**" />
<MauiXaml Remove="Models\**" /> <MauiXaml Remove="Models\**" />
<MauiXaml Remove="ShopNCookTests\**" /> <MauiXaml Remove="ShopNCookTests\**" />
<MauiXaml Remove="Tests\**" /> <MauiXaml Remove="Tests\**" />
<None Remove="Endpoint\**" /> <None Remove="Services\**" />
<None Remove="LocalEndpoint\**" /> <None Remove="LocalServices\**" />
<None Remove="Models\**" /> <None Remove="Models\**" />
<None Remove="ShopNCookTests\**" /> <None Remove="ShopNCookTests\**" />
<None Remove="Tests\**" /> <None Remove="Tests\**" />
@ -118,8 +118,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="Endpoint\Endpoint.csproj" /> <ProjectReference Include="Services\Services.csproj" />
<ProjectReference Include="LocalEndpoint\LocalEndpoint.csproj" /> <ProjectReference Include="LocalServices\LocalServices.csproj" />
<ProjectReference Include="Models\Models.csproj" /> <ProjectReference Include="Models\Models.csproj" />
</ItemGroup> </ItemGroup>

@ -8,9 +8,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Models", "Models\Models.csproj", "{A9D43E07-345D-4DD4-B4F9-CE69ED569B5F}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Models", "Models\Models.csproj", "{A9D43E07-345D-4DD4-B4F9-CE69ED569B5F}"
EndProject 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 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 EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution

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

@ -3,10 +3,8 @@ using Models;
namespace ShoopNCook.Pages; namespace ShoopNCook.Pages;
using Endpoint; using Endpoint;
using LocalEndpoint;
using Models; using Models;
using ShoopNCook.Views; using ShoopNCook.Views;
using System.Security.Principal;
public partial class FavoritesPage : ContentPage public partial class FavoritesPage : ContentPage
{ {

@ -14,21 +14,20 @@
<!--Main content--> <!--Main content-->
<ScrollView> <ScrollView>
<Grid <Grid
RowDefinitions="*, Auto, Auto, 0.5*"> RowDefinitions="Auto, Auto, *, Auto">
<!--Header--> <!--Header-->
<FlexLayout <FlexLayout
Grid.Row="0" Grid.Row="0"
Direction="Row" Direction="Row"
JustifyContent="SpaceBetween" JustifyContent="SpaceBetween"
AlignContent="Center" AlignContent="Center"
AlignItems="Center"> AlignItems="Center"
HeightRequest="60">
<ImageButton <ImageButton
Source="arrow_back.svg" Source="arrow_back.svg"
Clicked="OnBackButtonClicked"/> Clicked="OnBackButtonClicked"/>
<Label <Label
Style="{StaticResource h1}" Style="{StaticResource h1}"
x:Name="RecipeName" x:Name="RecipeName"
@ -36,7 +35,6 @@
<ImageButton <ImageButton
x:Name="Favorite" x:Name="Favorite"
Source="hearth_off.svg"
Margin="0, 0, 5, 0" Margin="0, 0, 5, 0"
Clicked="OnFavorite"/> Clicked="OnFavorite"/>
</FlexLayout> </FlexLayout>
@ -111,7 +109,8 @@
<!--Stars--> <!--Stars-->
<HorizontalStackLayout <HorizontalStackLayout
Grid.Row="3"> Grid.Row="3"
MaximumHeightRequest="45">
<HorizontalStackLayout <HorizontalStackLayout
x:Name="Stars" x:Name="Stars"
Spacing="2" Spacing="2"

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