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()
{
Shell shell = new ConnectAppShell(this, Endpoint.AccountManager, Notifier);
Shell shell = new ConnectAppShell(this, Endpoint.AuthService, Notifier);
shell.GoToAsync("//Splash");
MainPage = shell;
}

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

@ -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.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,15 @@
using Endpoint;
using LocalEndpoint.Data;
using Models;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LocalEndpoint
{
internal class AccountRecipesPreferences : IAccountRecipesPreferences
{
//Binds a recipe's id to it's account review ratings.
private readonly Dictionary<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 +19,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);
@ -47,10 +40,12 @@ namespace LocalEndpoint
public ImmutableList<(RecipeInfo, uint)> GetWeeklyList()
{
var weeklyDict = db.GetRecipeListOf(Account.User.Id);
List<(RecipeInfo, uint)> weekly = new List<(RecipeInfo, uint)>();
foreach (Recipe recipe in db.ListAll())
foreach (Recipe recipe in db.ListAllRecipes())
{
if (this.weekly.TryGetValue(recipe.Info.Id, out uint personAmmount))
if (weeklyDict.TryGetValue(recipe.Info.Id, out uint personAmmount))
{
weekly.Add((recipe.Info, personAmmount));
}
@ -59,44 +54,56 @@ namespace LocalEndpoint
}
public AccountRecipeRate GetRate(RecipeInfo info)
public RecipeRate GetRate(RecipeInfo info)
{
AccountRecipeRate rate = null;
RecipeRate rate = null;
var ratings = db.ListRatesOf(Account.User.Id);
if (!ratings.TryGetValue(info.Id, out rate))
{
rate = new AccountRecipeRate();
rate = new RecipeRate();
ratings.Add(info.Id, rate);
}
return rate;
}
public void AddToFavorites(RecipeInfo info)
{
AccountRecipeRate rate = GetRate(info);
ratings[info.Id] = new AccountRecipeRate(true, rate.Rate);
}
public bool AddToWeeklyList(RecipeInfo info, uint persAmount)
{
if (weekly.ContainsKey(info.Id))
var weeklyDict = db.GetRecipeListOf(Account.User.Id);
if (weeklyDict.ContainsKey(info.Id))
return false;
weekly[info.Id] = persAmount;
db.InsertInUserList(Account.User.Id, info.Id, persAmount);
return true;
}
public void AddToFavorites(RecipeInfo info)
{
Guid userId = Account.User.Id;
var ratings = db.ListRatesOf(userId);
RecipeRate rate = GetRate(info);
db.InsertRate(userId, info.Id, new RecipeRate(true, rate.Rate));
}
public void RemoveFromFavorites(RecipeInfo info)
{
AccountRecipeRate rate = GetRate(info);
ratings[info.Id] = new AccountRecipeRate(false, rate.Rate);
Guid userId = Account.User.Id;
var ratings = db.ListRatesOf(userId);
RecipeRate rate = GetRate(info);
db.InsertRate(userId, info.Id, new RecipeRate(false, rate.Rate));
}
public void SetReviewScore(RecipeInfo info, uint score)
{
AccountRecipeRate rate = GetRate(info);
ratings[info.Id] = new AccountRecipeRate(rate.IsFavorite, score);
Guid userId = Account.User.Id;
var ratings = db.ListRatesOf(userId);
RecipeRate rate = GetRate(info);
db.InsertRate(userId, info.Id, new RecipeRate(rate.IsFavorite, score));
}
}
}

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

@ -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>
<ItemGroup>
<ProjectReference Include="..\Endpoint\Endpoint.csproj" />
<ProjectReference Include="..\Services\Services.csproj" />
<ProjectReference Include="..\Models\Models.csproj" />
</ItemGroup>

@ -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,17 +36,16 @@ namespace LocalEndpoint
return GetOrInitData(account).Preferences;
}
private AccountData GetOrInitData(Account account)
private AccountServices GetOrInitData(Account account)
{
AccountData? data;
AccountServices? data;
accountsData.TryGetValue(account, out data);
if (data == null)
{
AccountOwnedRecipes recipes = new AccountOwnedRecipes(account, db);
recipes.UploadRecipe(new Recipe(new RecipeInfo("Cupcake", 500, 12, new Uri("https://www.mycake.fr/wp-content/uploads/2015/12/rs_cupcake_4x3.jpg"), 4.2F, Guid.NewGuid()), account.User, new List<Ingredient> { new Ingredient("Chocolate", 4) }.ToImmutableList(), new List<PreparationStep> { new PreparationStep("Eat Chocolate", "Eat the chocolate") }.ToImmutableList()));
AccountRecipesPreferences preferences = new AccountRecipesPreferences(account, db);
data = new AccountData(recipes, preferences);
data = new AccountServices(recipes, preferences);
accountsData.Add(account, data);
}
return data;

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

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

@ -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>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>

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

@ -1,11 +1,6 @@
using LocalEndpoint;
using Models;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Endpoint
{

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

@ -8,9 +8,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Models", "Models\Models.csproj", "{A9D43E07-345D-4DD4-B4F9-CE69ED569B5F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LocalEndpoint", "LocalEndpoint\LocalEndpoint.csproj", "{57732316-93B9-4DA0-A212-F8892D3D968B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LocalServices", "LocalServices\LocalServices.csproj", "{57732316-93B9-4DA0-A212-F8892D3D968B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Endpoint", "Endpoint\Endpoint.csproj", "{C976BDD8-710D-4162-8A42-973B634491F9}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Services", "Services\Services.csproj", "{C976BDD8-710D-4162-8A42-973B634491F9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

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

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

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

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