add documentation, setup doxygen and drone CI
continuous-integration/drone/push Build is passing Details

master
Maxime BATISTA 2 years ago
parent 18304de6a4
commit 7cb2b30216

@ -3,7 +3,12 @@ type: docker
name: CI Workflow name: CI Workflow
steps: steps:
- name: Build - name: Test
image: hub.codefirst.iut.uca.fr/marc.chevaldonne/codefirst-dotnet7-maui:latest image: hub.codefirst.iut.uca.fr/marc.chevaldonne/codefirst-dotnet7-maui:latest
commands: commands:
- dotnet test --framework net7.0 'Tests\\\\Tests.csproj' - dotnet test --framework net7.0 'Tests\\\\Tests.csproj'
- name: Deploy doc
image: hub.codefirst.iut.uca.fr/maxime.batista/codefirst-docdeployer:latest
commands:
- /entrypoint.sh -t doxygen -l . -d sourcecode_documentation

@ -1,19 +1,23 @@
namespace ShoopNCook; namespace ShoopNCook;
using Models; using Models;
using Endpoint; using Services;
using LocalEndpoint; using LocalServices;
// Classe principale de l'application qui implémente l'interface ConnectionObserver et IApp
public partial class App : Application, ConnectionObserver, IApp public partial class App : Application, ConnectionObserver, IApp
{ {
// Initialisation de l'interface IEndpoint avec une nouvelle instance de LocalEndpoint
private IEndpoint Endpoint = new LocalEndpoint(); private IEndpoint Endpoint = new LocalEndpoint();
public App() public App()
{ {
InitializeComponent(); InitializeComponent();
ForceLogin(); //start in login shell ForceLogin(); // Commencer l'application avec l'écran de connexion
} }
// Méthode appelée lorsque l'utilisateur se connecte avec succès
public void OnAccountConnected(Account account) public void OnAccountConnected(Account account)
{ {
Shell shell = new MainAppShell(account, Endpoint, this); Shell shell = new MainAppShell(account, Endpoint, this);
@ -21,6 +25,7 @@ public partial class App : Application, ConnectionObserver, IApp
MainPage = shell; MainPage = shell;
} }
// Méthode pour forcer l'utilisateur à se connecter
public void ForceLogin() public void ForceLogin()
{ {
Shell shell = new ConnectAppShell(this, Endpoint.AuthService); Shell shell = new ConnectAppShell(this, Endpoint.AuthService);

@ -1,16 +1,23 @@
namespace ShoopNCook; namespace ShoopNCook;
using Microsoft.Maui.Controls; using Microsoft.Maui.Controls;
using Models; using Models;
using Endpoint; using Services;
using ShoopNCook.Controllers; using ShoopNCook.Controllers;
using ShoopNCook.Pages; using ShoopNCook.Pages;
// Shell pour la phase de connexion de l'application
public partial class ConnectAppShell : Shell public partial class ConnectAppShell : Shell
{ {
// Constructeur qui prend un observateur de connexion et un service d'authentification en argument
public ConnectAppShell(ConnectionObserver observer, IAuthService accounts) public ConnectAppShell(ConnectionObserver observer, IAuthService accounts)
{ {
// Création d'un nouveau contrôleur de connexion
ConnectionController controller = new ConnectionController(observer, accounts); ConnectionController controller = new ConnectionController(observer, accounts);
InitializeComponent(); InitializeComponent();
// Initialisation des pages de connexion et d'inscription avec le contrôleur de connexion
LoginPage.ContentTemplate = new DataTemplate(() => new LoginPage(controller)); LoginPage.ContentTemplate = new DataTemplate(() => new LoginPage(controller));
RegisterPage.ContentTemplate = new DataTemplate(() => new RegisterPage(controller)); RegisterPage.ContentTemplate = new DataTemplate(() => new RegisterPage(controller));
} }

@ -2,6 +2,8 @@
namespace ShoopNCook namespace ShoopNCook
{ {
// Interface définissant un observateur de connexion.
// Tout objet implémentant cette interface doit définir la méthode OnAccountConnected().
public interface ConnectionObserver public interface ConnectionObserver
{ {
public void OnAccountConnected(Account account); public void OnAccountConnected(Account account);

@ -1,4 +1,4 @@
using Endpoint; using Services;
using Models; using Models;
namespace ShoopNCook.Controllers namespace ShoopNCook.Controllers

@ -1,4 +1,4 @@
using Endpoint; using Services;
using Models; using Models;
using ShoopNCook.Pages; using ShoopNCook.Pages;

@ -0,0 +1,429 @@
#---------------------------------------------------------------------------
# Project related configuration options
#---------------------------------------------------------------------------
DOXYFILE_ENCODING = UTF-8
PROJECT_NAME = "ShopNCook"
PROJECT_NUMBER = 1.0.0
PROJECT_BRIEF = "A Cook application"
PROJECT_LOGO = Resources/appicon.svg
OUTPUT_DIRECTORY = /docs/doxygen
CREATE_SUBDIRS = NO
ALLOW_UNICODE_NAMES = NO
OUTPUT_LANGUAGE = English
BRIEF_MEMBER_DESC = YES
REPEAT_BRIEF = YES
ABBREVIATE_BRIEF = "The $name class" \
"The $name widget" \
"The $name file" \
is \
provides \
specifies \
contains \
represents \
a \
an \
the
ALWAYS_DETAILED_SEC = NO
INLINE_INHERITED_MEMB = NO
FULL_PATH_NAMES = YES
STRIP_FROM_PATH =
STRIP_FROM_INC_PATH =
SHORT_NAMES = NO
JAVADOC_AUTOBRIEF = NO
JAVADOC_BANNER = NO
QT_AUTOBRIEF = NO
MULTILINE_CPP_IS_BRIEF = NO
PYTHON_DOCSTRING = YES
INHERIT_DOCS = YES
SEPARATE_MEMBER_PAGES = NO
TAB_SIZE = 4
ALIASES =
OPTIMIZE_OUTPUT_FOR_C = NO
# Well... the one for Java looks so similar to the one for C#...
OPTIMIZE_OUTPUT_JAVA = YES
OPTIMIZE_FOR_FORTRAN = NO
OPTIMIZE_OUTPUT_VHDL = NO
OPTIMIZE_OUTPUT_SLICE = NO
EXTENSION_MAPPING =
MARKDOWN_SUPPORT = YES
TOC_INCLUDE_HEADINGS = 5
AUTOLINK_SUPPORT = YES
BUILTIN_STL_SUPPORT = NO
CPP_CLI_SUPPORT = NO
SIP_SUPPORT = NO
IDL_PROPERTY_SUPPORT = YES
DISTRIBUTE_GROUP_DOC = NO
GROUP_NESTED_COMPOUNDS = NO
SUBGROUPING = YES
INLINE_GROUPED_CLASSES = NO
INLINE_SIMPLE_STRUCTS = NO
TYPEDEF_HIDES_STRUCT = NO
LOOKUP_CACHE_SIZE = 0
NUM_PROC_THREADS = 1
#---------------------------------------------------------------------------
# Build related configuration options
#---------------------------------------------------------------------------
EXTRACT_ALL = YES
# I do not like other members to see my private members... but you can set it to YES if you prefer.
EXTRACT_PRIVATE = NO
EXTRACT_PRIV_VIRTUAL = NO
EXTRACT_PACKAGE = NO
EXTRACT_STATIC = YES
EXTRACT_LOCAL_CLASSES = YES
EXTRACT_LOCAL_METHODS = NO
EXTRACT_ANON_NSPACES = NO
RESOLVE_UNNAMED_PARAMS = YES
HIDE_UNDOC_MEMBERS = NO
HIDE_UNDOC_CLASSES = NO
HIDE_FRIEND_COMPOUNDS = NO
HIDE_IN_BODY_DOCS = NO
INTERNAL_DOCS = NO
CASE_SENSE_NAMES = NO
HIDE_SCOPE_NAMES = NO
HIDE_COMPOUND_REFERENCE= NO
SHOW_HEADERFILE = YES
SHOW_INCLUDE_FILES = YES
SHOW_GROUPED_MEMB_INC = NO
FORCE_LOCAL_INCLUDES = NO
INLINE_INFO = YES
SORT_MEMBER_DOCS = NO
SORT_BRIEF_DOCS = NO
SORT_MEMBERS_CTORS_1ST = NO
SORT_GROUP_NAMES = NO
SORT_BY_SCOPE_NAME = NO
STRICT_PROTO_MATCHING = NO
GENERATE_TODOLIST = YES
GENERATE_TESTLIST = YES
GENERATE_BUGLIST = YES
GENERATE_DEPRECATEDLIST= YES
ENABLED_SECTIONS =
MAX_INITIALIZER_LINES = 30
SHOW_USED_FILES = YES
SHOW_FILES = YES
SHOW_NAMESPACES = YES
FILE_VERSION_FILTER =
LAYOUT_FILE =
CITE_BIB_FILES =
#---------------------------------------------------------------------------
# Configuration options related to warning and progress messages
#---------------------------------------------------------------------------
QUIET = NO
WARNINGS = YES
WARN_IF_UNDOCUMENTED = YES
WARN_IF_DOC_ERROR = YES
WARN_IF_INCOMPLETE_DOC = YES
WARN_NO_PARAMDOC = NO
WARN_AS_ERROR = NO
WARN_FORMAT = "$file:$line: $text"
WARN_LOGFILE =
#---------------------------------------------------------------------------
# Configuration options related to the input files
#---------------------------------------------------------------------------
INPUT = .
INPUT_ENCODING = UTF-8
FILE_PATTERNS = *.c \
*.cc \
*.cxx \
*.cpp \
*.c++ \
*.java \
*.ii \
*.ixx \
*.ipp \
*.i++ \
*.inl \
*.idl \
*.ddl \
*.odl \
*.h \
*.hh \
*.hxx \
*.hpp \
*.h++ \
*.l \
*.cs \
*.d \
*.php \
*.php4 \
*.php5 \
*.phtml \
*.inc \
*.m \
*.markdown \
*.md \
*.mm \
*.dox \
*.py \
*.pyw \
*.f90 \
*.f95 \
*.f03 \
*.f08 \
*.f18 \
*.f \
*.for \
*.vhd \
*.vhdl \
*.ucf \
*.qsf \
*.ice
RECURSIVE = YES
EXCLUDE =
EXCLUDE_SYMLINKS = NO
EXCLUDE_PATTERNS = */Tests/*
EXCLUDE_PATTERNS += */bin/*
EXCLUDE_PATTERNS += */obj/*
EXCLUDE_PATTERNS += documentation/*
EXCLUDE_SYMBOLS =
EXAMPLE_PATH =
EXAMPLE_PATTERNS = *
EXAMPLE_RECURSIVE = NO
IMAGE_PATH =
INPUT_FILTER =
FILTER_PATTERNS =
FILTER_SOURCE_FILES = NO
FILTER_SOURCE_PATTERNS =
USE_MDFILE_AS_MAINPAGE =
#---------------------------------------------------------------------------
# Configuration options related to source browsing
#---------------------------------------------------------------------------
SOURCE_BROWSER = NO
INLINE_SOURCES = NO
STRIP_CODE_COMMENTS = YES
REFERENCED_BY_RELATION = NO
REFERENCES_RELATION = NO
REFERENCES_LINK_SOURCE = YES
SOURCE_TOOLTIPS = YES
USE_HTAGS = NO
VERBATIM_HEADERS = YES
CLANG_ASSISTED_PARSING = NO
CLANG_ADD_INC_PATHS = YES
CLANG_OPTIONS =
CLANG_DATABASE_PATH =
#---------------------------------------------------------------------------
# Configuration options related to the alphabetical class index
#---------------------------------------------------------------------------
ALPHABETICAL_INDEX = YES
IGNORE_PREFIX =
#---------------------------------------------------------------------------
# Configuration options related to the HTML output
#---------------------------------------------------------------------------
GENERATE_HTML = YES
HTML_OUTPUT = html
HTML_FILE_EXTENSION = .html
HTML_HEADER =
HTML_STYLESHEET =
HTML_EXTRA_STYLESHEET =
HTML_EXTRA_FILES = images/CodeFirst.png images/clubinfo.png
HTML_COLORSTYLE_HUE = 215
HTML_COLORSTYLE_SAT = 45
HTML_COLORSTYLE_GAMMA = 240
HTML_TIMESTAMP = NO
HTML_DYNAMIC_MENUS = YES
HTML_DYNAMIC_SECTIONS = NO
HTML_INDEX_NUM_ENTRIES = 100
GENERATE_DOCSET = NO
DOCSET_FEEDNAME = "Doxygen generated docs"
DOCSET_FEEDURL =
DOCSET_BUNDLE_ID = org.doxygen.Project
DOCSET_PUBLISHER_ID = org.doxygen.Publisher
DOCSET_PUBLISHER_NAME = Publisher
GENERATE_HTMLHELP = NO
CHM_FILE =
HHC_LOCATION =
GENERATE_CHI = NO
CHM_INDEX_ENCODING =
BINARY_TOC = NO
TOC_EXPAND = NO
GENERATE_QHP = NO
QCH_FILE =
QHP_NAMESPACE = org.doxygen.Project
QHP_VIRTUAL_FOLDER = doc
QHP_CUST_FILTER_NAME =
QHP_CUST_FILTER_ATTRS =
QHP_SECT_FILTER_ATTRS =
QHG_LOCATION =
GENERATE_ECLIPSEHELP = NO
ECLIPSE_DOC_ID = org.doxygen.Project
DISABLE_INDEX = NO
GENERATE_TREEVIEW = NO
FULL_SIDEBAR = NO
ENUM_VALUES_PER_LINE = 4
TREEVIEW_WIDTH = 250
EXT_LINKS_IN_WINDOW = NO
OBFUSCATE_EMAILS = YES
HTML_FORMULA_FORMAT = png
FORMULA_FONTSIZE = 10
FORMULA_TRANSPARENT = YES
FORMULA_MACROFILE =
USE_MATHJAX = NO
MATHJAX_VERSION = MathJax_2
MATHJAX_FORMAT = HTML-CSS
MATHJAX_RELPATH =
MATHJAX_EXTENSIONS =
MATHJAX_CODEFILE =
SEARCHENGINE = YES
SERVER_BASED_SEARCH = NO
EXTERNAL_SEARCH = NO
SEARCHENGINE_URL =
SEARCHDATA_FILE = searchdata.xml
EXTERNAL_SEARCH_ID =
EXTRA_SEARCH_MAPPINGS =
#---------------------------------------------------------------------------
# Configuration options related to the LaTeX output
#---------------------------------------------------------------------------
GENERATE_LATEX = NO
LATEX_OUTPUT = latex
LATEX_CMD_NAME =
MAKEINDEX_CMD_NAME = makeindex
LATEX_MAKEINDEX_CMD = makeindex
COMPACT_LATEX = NO
PAPER_TYPE = a4
EXTRA_PACKAGES =
LATEX_HEADER =
LATEX_FOOTER =
LATEX_EXTRA_STYLESHEET =
LATEX_EXTRA_FILES =
PDF_HYPERLINKS = YES
USE_PDFLATEX = YES
LATEX_BATCHMODE = NO
LATEX_HIDE_INDICES = NO
LATEX_BIB_STYLE = plain
LATEX_TIMESTAMP = NO
LATEX_EMOJI_DIRECTORY =
#---------------------------------------------------------------------------
# Configuration options related to the RTF output
#---------------------------------------------------------------------------
GENERATE_RTF = NO
RTF_OUTPUT = rtf
COMPACT_RTF = NO
RTF_HYPERLINKS = NO
RTF_STYLESHEET_FILE =
RTF_EXTENSIONS_FILE =
#---------------------------------------------------------------------------
# Configuration options related to the man page output
#---------------------------------------------------------------------------
GENERATE_MAN = NO
MAN_OUTPUT = man
MAN_EXTENSION = .3
MAN_SUBDIR =
MAN_LINKS = NO
#---------------------------------------------------------------------------
# Configuration options related to the XML output
#---------------------------------------------------------------------------
GENERATE_XML = NO
XML_OUTPUT = xml
XML_PROGRAMLISTING = YES
XML_NS_MEMB_FILE_SCOPE = NO
#---------------------------------------------------------------------------
# Configuration options related to the DOCBOOK output
#---------------------------------------------------------------------------
GENERATE_DOCBOOK = NO
DOCBOOK_OUTPUT = docbook
#---------------------------------------------------------------------------
# Configuration options for the AutoGen Definitions output
#---------------------------------------------------------------------------
GENERATE_AUTOGEN_DEF = NO
#---------------------------------------------------------------------------
# Configuration options related to Sqlite3 output
#---------------------------------------------------------------------------
#---------------------------------------------------------------------------
# Configuration options related to the Perl module output
#---------------------------------------------------------------------------
GENERATE_PERLMOD = NO
PERLMOD_LATEX = NO
PERLMOD_PRETTY = YES
PERLMOD_MAKEVAR_PREFIX =
#---------------------------------------------------------------------------
# Configuration options related to the preprocessor
#---------------------------------------------------------------------------
ENABLE_PREPROCESSING = YES
MACRO_EXPANSION = NO
EXPAND_ONLY_PREDEF = NO
SEARCH_INCLUDES = YES
INCLUDE_PATH =
INCLUDE_FILE_PATTERNS =
PREDEFINED =
EXPAND_AS_DEFINED =
SKIP_FUNCTION_MACROS = YES
#---------------------------------------------------------------------------
# Configuration options related to external references
#---------------------------------------------------------------------------
TAGFILES =
GENERATE_TAGFILE =
ALLEXTERNALS = NO
EXTERNAL_GROUPS = YES
EXTERNAL_PAGES = YES
#---------------------------------------------------------------------------
# Configuration options related to the dot tool
#---------------------------------------------------------------------------
DIA_PATH =
HIDE_UNDOC_RELATIONS = YES
HAVE_DOT = NO
DOT_NUM_THREADS = 0
DOT_FONTNAME = Helvetica
DOT_FONTSIZE = 10
DOT_FONTPATH =
CLASS_GRAPH = YES
COLLABORATION_GRAPH = YES
GROUP_GRAPHS = YES
UML_LOOK = NO
UML_LIMIT_NUM_FIELDS = 10
DOT_UML_DETAILS = NO
DOT_WRAP_THRESHOLD = 17
TEMPLATE_RELATIONS = NO
INCLUDE_GRAPH = YES
INCLUDED_BY_GRAPH = YES
CALL_GRAPH = NO
CALLER_GRAPH = NO
GRAPHICAL_HIERARCHY = YES
DIRECTORY_GRAPH = YES
DIR_GRAPH_MAX_DEPTH = 1
DOT_IMAGE_FORMAT = png
INTERACTIVE_SVG = NO
DOT_PATH =
DOTFILE_DIRS =
MSCFILE_DIRS =
DIAFILE_DIRS =
PLANTUML_JAR_PATH =
PLANTUML_CFG_FILE =
PLANTUML_INCLUDE_PATH =
DOT_GRAPH_MAX_NODES = 50
MAX_DOT_GRAPH_DEPTH = 0
DOT_TRANSPARENT = NO
DOT_MULTI_TARGETS = NO
GENERATE_LEGEND = YES
DOT_CLEANUP = YES

@ -7,6 +7,8 @@ using System.Threading.Tasks;
namespace ShoopNCook namespace ShoopNCook
{ {
// Interface définissant une application.
// Tout objet implémentant cette interface doit définir la méthode ForceLogin().
public interface IApp public interface IApp
{ {
public void ForceLogin(); public void ForceLogin();

@ -1,11 +1,11 @@
using LocalServices; using Services;
using LocalServices.Data; using LocalServices.Data;
using Models; using Models;
using System.Collections.Immutable; using System.Collections.Immutable;
namespace Endpoint namespace Services
{ {
internal class AccountOwnedRecipes : IAccountOwnedRecipes internal class AccountOwnedRecipes : IAccountOwnedRecipesService
{ {
public Account Account { get; init; } public Account Account { get; init; }

@ -1,11 +1,11 @@
using Endpoint; using Services;
using LocalServices.Data; using LocalServices.Data;
using Models; using Models;
using System.Collections.Immutable; using System.Collections.Immutable;
namespace LocalServices namespace LocalServices
{ {
internal class AccountRecipesPreferences : IAccountRecipesPreferences internal class AccountRecipesPreferences : IAccountRecipesPreferencesService
{ {
private readonly IDatabase db; private readonly IDatabase db;
@ -103,7 +103,7 @@ namespace LocalServices
var ratings = db.ListRatesOf(userId); var ratings = db.ListRatesOf(userId);
RecipeRate rate = GetRate(info); RecipeRate rate = GetRate(info);
db.InsertRate(userId, info.Id, new RecipeRate(rate.IsFavorite, score)); db.InsertRate(userId, info.Id, new RecipeRate(rate.IsFavorite, Math.Min(score, 5)));
} }
} }
} }

@ -1,6 +1,6 @@
using Endpoint; using Services;
namespace LocalServices namespace LocalServices
{ {
internal record AccountServices(IAccountOwnedRecipes Recipes, IAccountRecipesPreferences Preferences); internal record AccountServices(IAccountOwnedRecipesService Recipes, IAccountRecipesPreferencesService Preferences);
} }

@ -1,5 +1,5 @@
using Models; using Models;
using Endpoint; using Services;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;

@ -1,10 +1,4 @@
using Models; using System.Runtime.Serialization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
namespace LocalServices.Data namespace LocalServices.Data
{ {

@ -1,27 +1,17 @@
using Models; using Models;
using System;
using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq;
using System.Runtime.Serialization;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace LocalServices.Data namespace LocalServices.Data
{ {
/// <summary> /// <summary>
/// Database implementation with catastrophic performances. /// 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. /// This database implementation persists data in json and will save all the data in their files for each mutable requests.
/// </summary> /// </summary>
internal class CatastrophicPerformancesDatabase : IDatabase internal class CatastrophicPerformancesDatabase : IDatabase
{ {
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 RECIPES_FILENAME = "recipes_data.xml";
private static readonly string USERS_FILENAME = "users_data.xml"; private static readonly string USERS_FILENAME = "users_data.xml";
private static readonly string ACCOUNTS_FILENAME = "accounts_data.xml"; private static readonly string ACCOUNTS_FILENAME = "accounts_data.xml";
@ -39,9 +29,9 @@ namespace LocalServices.Data
if (!Directory.Exists(folderPath)) if (!Directory.Exists(folderPath))
Directory.CreateDirectory(folderPath); Directory.CreateDirectory(folderPath);
usersData = Load<Guid, UserData>(USERS_FILENAME, USERS_SERIALIZER); usersData = Load<Guid, UserData>(USERS_FILENAME);
recipesData = Load<Guid, RecipeData>(RECIPES_FILENAME, RECIPES_SERIALIZER); recipesData = Load<Guid, RecipeData>(RECIPES_FILENAME);
accountsData = Load<string, AccountData>(ACCOUNTS_FILENAME, ACCOUNTS_SERIALIZER); accountsData = Load<string, AccountData>(ACCOUNTS_FILENAME);
} }
public bool IsEmpty() public bool IsEmpty()
@ -61,7 +51,7 @@ namespace LocalServices.Data
public void InsertAccount(Account account, string passwordHash) public void InsertAccount(Account account, string passwordHash)
{ {
accountsData[account.Email] = new AccountData(account.User.Id, account.Email, passwordHash); accountsData[account.Email] = new AccountData(account.User.Id, account.Email, passwordHash);
Save(ACCOUNTS_FILENAME, ACCOUNTS_SERIALIZER, accountsData); Save(ACCOUNTS_FILENAME, accountsData);
} }
public Recipe? GetRecipe(Guid id) public Recipe? GetRecipe(Guid id)
@ -79,38 +69,38 @@ namespace LocalServices.Data
public void InsertInUserList(Guid userId, Guid recipeId, uint persAmount) public void InsertInUserList(Guid userId, Guid recipeId, uint persAmount)
{ {
usersData[userId].RecipesList[recipeId] = persAmount; usersData[userId].RecipesList[recipeId] = persAmount;
Save(USERS_FILENAME, USERS_SERIALIZER, usersData); Save(USERS_FILENAME, usersData);
} }
public void RemoveFromUserList(Guid userId, Guid recipeId) public void RemoveFromUserList(Guid userId, Guid recipeId)
{ {
usersData[userId].RecipesList.Remove(recipeId); usersData[userId].RecipesList.Remove(recipeId);
Save(USERS_FILENAME, USERS_SERIALIZER, usersData); Save(USERS_FILENAME, usersData);
} }
public void InsertRecipe(Recipe recipe) public void InsertRecipe(Recipe recipe)
{ {
recipesData[recipe.Info.Id] = new RecipeData(recipe.Info, recipe.Owner.Id, recipe.Ingredients, recipe.Steps); recipesData[recipe.Info.Id] = new RecipeData(recipe.Info, recipe.Owner.Id, recipe.Ingredients, recipe.Steps);
Save(RECIPES_FILENAME, RECIPES_SERIALIZER, recipesData); Save(RECIPES_FILENAME, recipesData);
} }
public void InsertUser(User user) public void InsertUser(User user)
{ {
usersData[user.Id] = new UserData(user, new Dictionary<Guid, RecipeRate>(), new Dictionary<Guid, uint>()); usersData[user.Id] = new UserData(user, new Dictionary<Guid, RecipeRate>(), new Dictionary<Guid, uint>());
Save(USERS_FILENAME, USERS_SERIALIZER, usersData); Save(USERS_FILENAME, usersData);
} }
public void InsertRate(Guid userId, Guid recipeId, RecipeRate rate) public void InsertRate(Guid userId, Guid recipeId, RecipeRate rate)
{ {
usersData[userId].Rates[recipeId] = rate; usersData[userId].Rates[recipeId] = rate;
Save(USERS_FILENAME, USERS_SERIALIZER, usersData); Save(USERS_FILENAME, usersData);
} }
public void RemoveRecipe(Guid id) public void RemoveRecipe(Guid id)
{ {
recipesData.Remove(id); recipesData.Remove(id);
Save(RECIPES_FILENAME, RECIPES_SERIALIZER, recipesData); Save(RECIPES_FILENAME, recipesData);
} }
public ImmutableList<Recipe> ListAllRecipes() public ImmutableList<Recipe> ListAllRecipes()
@ -135,7 +125,7 @@ namespace LocalServices.Data
return new Recipe(rd.Info, owner, rd.Ingredients, rd.Steps); return new Recipe(rd.Info, owner, rd.Ingredients, rd.Steps);
} }
private Dictionary<K, V> Load<K, V>(string fileName, DataContractSerializer deserializer) private Dictionary<K, V> Load<K, V>(string fileName)
{ {
var file = dbPath + "/" + fileName; var file = dbPath + "/" + fileName;
var fileInfo = new FileInfo(file); var fileInfo = new FileInfo(file);
@ -151,10 +141,10 @@ namespace LocalServices.Data
return JsonSerializer.Deserialize<Dictionary<K, V>>(text); return JsonSerializer.Deserialize<Dictionary<K, V>>(text);
} }
private async void Save<K, T>(string fileName, DataContractSerializer serializer, Dictionary<K, T> dict) private async void Save<K, T>(string fileName, Dictionary<K, T> dict)
{ {
string json = JsonSerializer.Serialize(dict); string json = JsonSerializer.Serialize(dict);
using (var stream = WaitForFile(fileName, FileMode.Open, FileAccess.Write, FileShare.Write)) using (var stream = WaitForFile(dbPath + "/" + fileName, FileMode.Open, FileAccess.Write, FileShare.Write))
{ {
var bytes = Encoding.ASCII.GetBytes(json); var bytes = Encoding.ASCII.GetBytes(json);
await stream.WriteAsync(bytes, 0, bytes.Length); await stream.WriteAsync(bytes, 0, bytes.Length);
@ -166,7 +156,7 @@ namespace LocalServices.Data
// Simply wait until the file is released and return it. This function will never return until // Simply wait until the file is released and return it. This function will never return until
private static FileStream WaitForFile(string fullPath, FileMode mode, FileAccess access, FileShare share) private static FileStream WaitForFile(string fullPath, FileMode mode, FileAccess access, FileShare share)
{ {
for (int attempt = 0 ; attempt < 20; attempt++) //20 attempts equals to 2 seconds of wait for (int attempt = 0 ; attempt < 40; attempt++)
{ {
FileStream? fs = null; FileStream? fs = null;
try try
@ -174,7 +164,11 @@ namespace LocalServices.Data
fs = new FileStream(fullPath, mode, access, share); fs = new FileStream(fullPath, mode, access, share);
return fs; return fs;
} }
catch (IOException) catch (FileNotFoundException e)
{
throw e;
}
catch (IOException e)
{ {
if (fs != null) if (fs != null)
fs.Dispose(); fs.Dispose();

@ -4,33 +4,100 @@ using System.Collections.Immutable;
namespace LocalServices.Data namespace LocalServices.Data
{ {
/// <summary>
// The database interface defines all the different kinds of requests the LocalEndpoint needs to store and retrieve data. /// The database interface defines all the different kinds of requests the LocalEndpoint needs to store and retrieve data.
/// </summary>
public interface IDatabase public interface IDatabase
{ {
/// <summary>
/// Get a Recipe from its identifier
/// </summary>
/// <param name="id"></param>
/// <returns>The recipe if the identifier is registered in the database</returns>
public Recipe? GetRecipe(Guid id); public Recipe? GetRecipe(Guid id);
/// <summary>
/// Get the rate of a user for a given recipe
/// </summary>
/// <param name="user">The user identifier</param>
/// <param name="recipe">The recipe identifier</param>
/// <returns>Returns a rate</returns>
public RecipeRate GetRecipeRate(Guid user, Guid recipe); public RecipeRate GetRecipeRate(Guid user, Guid recipe);
/// <summary>
/// Gets an account from an email and a password hash
/// </summary>
/// <param name="email"></param>
/// <param name="passwordHash"></param>
/// <returns>some account if the email and the hash is found in the database</returns>
public Account? GetAccount(string email, string passwordHash); public Account? GetAccount(string email, string passwordHash);
/// <summary>
/// Insert a recipe in user's weekly list
/// </summary>
/// <param name="userId"></param>
/// <param name="recipeId"></param>
/// <param name="persAmount"></param>
public void InsertInUserList(Guid userId, Guid recipeId, uint persAmount); public void InsertInUserList(Guid userId, Guid recipeId, uint persAmount);
/// <summary>
/// Remove a recipe from user's weekly list
/// </summary>
/// <param name="userId"></param>
/// <param name="recipeId"></param>
public void RemoveFromUserList(Guid userId, Guid recipeId); public void RemoveFromUserList(Guid userId, Guid recipeId);
/// <summary>
/// Inserts an account in the database, with the given passwordhash
/// </summary>
/// <param name="account"></param>
/// <param name="passwordHash"></param>
public void InsertAccount(Account account, string passwordHash); public void InsertAccount(Account account, string passwordHash);
/// <summary>
/// Inserts a recipe
/// </summary>
/// <param name="recipe"></param>
public void InsertRecipe(Recipe recipe); public void InsertRecipe(Recipe recipe);
/// <summary>
/// Inserts a user
/// </summary>
/// <param name="user"></param>
public void InsertUser(User user); public void InsertUser(User user);
/// <summary>
/// Inserts a user rate over the given recipe identifier
/// </summary>
/// <param name="userId"></param>
/// <param name="recipeId"></param>
/// <param name="rate"></param>
public void InsertRate(Guid userId, Guid recipeId, RecipeRate rate); public void InsertRate(Guid userId, Guid recipeId, RecipeRate rate);
/// <summary>
/// Removes a recipe
/// </summary>
/// <param name="id"></param>
public void RemoveRecipe(Guid id); public void RemoveRecipe(Guid id);
/// <summary>
/// Lists all recipes in the database
/// </summary>
/// <returns></returns>
public ImmutableList<Recipe> ListAllRecipes(); public ImmutableList<Recipe> ListAllRecipes();
/// <summary>
/// List the ratings of an user
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public ImmutableDictionary<Guid, RecipeRate> ListRatesOf(Guid user); public ImmutableDictionary<Guid, RecipeRate> ListRatesOf(Guid user);
/// <summary>
/// Get the weekly list of given user
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public ImmutableDictionary<Guid, uint> GetRecipeListOf(Guid user); public ImmutableDictionary<Guid, uint> GetRecipeListOf(Guid user);
} }
} }

@ -4,6 +4,9 @@ using System.Collections.Immutable;
namespace LocalServices.Data namespace LocalServices.Data
{ {
/// <summary>
/// stub implementation for IDatabase
/// </summary>
internal class StubDatabase : IDatabase internal class StubDatabase : IDatabase
{ {
private readonly User User = new User(new Uri("https://images.pexels.com/photos/37546/woman-portrait-face-studio-37546.jpeg?cs=srgb&dl=beauty-face-headshot-37546.jpg&fm=jpg"), "David", Guid.NewGuid()); private readonly User User = new User(new Uri("https://images.pexels.com/photos/37546/woman-portrait-face-studio-37546.jpeg?cs=srgb&dl=beauty-face-headshot-37546.jpg&fm=jpg"), "David", Guid.NewGuid());

@ -1,4 +1,4 @@
using Endpoint; using Services;
using LocalServices.Data; using LocalServices.Data;
using Models; using Models;
using System.Collections.Immutable; using System.Collections.Immutable;
@ -7,8 +7,8 @@ namespace LocalServices
{ {
/// <summary> /// <summary>
/// The local endpoint is an implementation of the Endpoint API definition. /// The local endpoint is an implementation of the Services API.
/// /// This class is the _entry point_ class of the implementation
/// </summary> /// </summary>
public class LocalEndpoint : IEndpoint public class LocalEndpoint : IEndpoint
{ {
@ -20,6 +20,7 @@ namespace LocalServices
{ {
var db = new CatastrophicPerformancesDatabase(Environment.GetFolderPath(Environment.SpecialFolder.Personal)); var db = new CatastrophicPerformancesDatabase(Environment.GetFolderPath(Environment.SpecialFolder.Personal));
if (db.IsEmpty()) if (db.IsEmpty())
PrepareDatabase(db); PrepareDatabase(db);
@ -31,6 +32,11 @@ namespace LocalServices
public IRecipesService RecipesService => recipesService; public IRecipesService RecipesService => recipesService;
/// <summary>
/// Inserts sample data in the local database
/// </summary>
/// <param name="db"></param>
private static void PrepareDatabase(IDatabase db) private static void PrepareDatabase(IDatabase 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 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));
@ -53,6 +59,11 @@ namespace LocalServices
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())); 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()));
} }
/// <summary>
/// helper function to Create a Guid from a given seed
/// </summary>
/// <param name="seed">the seed to use for the generation</param>
/// <returns></returns>
private static Guid MakeGuid(int seed) private static Guid MakeGuid(int seed)
{ {
var r = new Random(seed); var r = new Random(seed);

@ -1,4 +1,4 @@
using Endpoint; using Services;
using LocalServices.Data; using LocalServices.Data;
using Models; using Models;
using System.Collections.Immutable; using System.Collections.Immutable;
@ -27,11 +27,11 @@ namespace LocalServices
} }
public IAccountOwnedRecipes GetRecipesOf(Account account) public IAccountOwnedRecipesService GetRecipesOf(Account account)
{ {
return GetOrInitData(account).Recipes; return GetOrInitData(account).Recipes;
} }
public IAccountRecipesPreferences GetPreferencesOf(Account account) public IAccountRecipesPreferencesService GetPreferencesOf(Account account)
{ {
return GetOrInitData(account).Preferences; return GetOrInitData(account).Preferences;
} }

@ -3,12 +3,17 @@ using Microsoft.Maui.Controls;
using Models; using Models;
using ShoopNCook.Controllers; using ShoopNCook.Controllers;
using ShoopNCook.Pages; using ShoopNCook.Pages;
using Endpoint; using Services;
// Shell principale de l'application après connexion de l'utilisateur
public partial class MainAppShell : Shell public partial class MainAppShell : Shell
{ {
// Constructeur qui prend en argument un compte, un endpoint et une application
public MainAppShell(Account account, IEndpoint endpoint, IApp app) public MainAppShell(Account account, IEndpoint endpoint, IApp app)
{ {
InitializeComponent(); InitializeComponent();
// Initialisation de chaque onglet avec sa page respective
HomeTab.ContentTemplate = new DataTemplate(() => new HomePage(account, endpoint)); HomeTab.ContentTemplate = new DataTemplate(() => new HomePage(account, endpoint));
FavoritesTab.ContentTemplate = new DataTemplate(() => new FavoritesPage(account, endpoint.RecipesService)); FavoritesTab.ContentTemplate = new DataTemplate(() => new FavoritesPage(account, endpoint.RecipesService));
MyListTab.ContentTemplate = new DataTemplate(() => new MyListPage(account, endpoint.RecipesService)); MyListTab.ContentTemplate = new DataTemplate(() => new MyListPage(account, endpoint.RecipesService));

@ -1,4 +1,9 @@
namespace Models namespace Models
{ {
/// <summary>
/// Contains the informations of an account.
/// </summary>
/// <param name="User">The account's public information</param>
/// <param name="Email">The account's email address</param>
public record Account(User User, string Email); public record Account(User User, string Email);
} }

@ -3,7 +3,11 @@
namespace Models namespace Models
{ {
/// <summary>
/// An ingredient
/// </summary>
/// <param name="Name">The ingredient's name</param>
/// <param name="Amount">The ingredient's amount (in kilograms or liters)</param>
[DataContract] [DataContract]
public record Ingredient([property: DataMember] string Name, [property: DataMember] float Amount); public record Ingredient([property: DataMember] string Name, [property: DataMember] float Amount);
} }

@ -2,6 +2,11 @@
namespace Models namespace Models
{ {
/// <summary>
/// A step of preparation
/// </summary>
/// <param name="Name">The step's name</param>
/// <param name="Description">The step's instructions / description</param>
[DataContract] [DataContract]
public record PreparationStep([property: DataMember] string Name, [property: DataMember] string Description); public record PreparationStep([property: DataMember] string Name, [property: DataMember] string Description);
} }

@ -3,6 +3,13 @@ using System.Runtime.Serialization;
namespace Models namespace Models
{ {
/// <summary>
/// A Recipe
/// </summary>
/// <param name="Info">The essential information of the recipe</param>
/// <param name="Owner">The creator of the recipe</param>
/// <param name="Ingredients">The needed ingredients of the recipe</param>
/// <param name="Steps">The preparation steps</param>
[DataContract] [DataContract]
public record Recipe( public record Recipe(
[property: DataMember] RecipeInfo Info, [property: DataMember] RecipeInfo Info,
@ -10,5 +17,4 @@ namespace Models
[property: DataMember] ImmutableList<Ingredient> Ingredients, [property: DataMember] ImmutableList<Ingredient> Ingredients,
[property: DataMember] ImmutableList<PreparationStep> Steps [property: DataMember] ImmutableList<PreparationStep> Steps
); );
} }

@ -7,6 +7,9 @@ using System.Threading.Tasks;
namespace Models namespace Models
{ {
/// <summary>
/// A Simple builder to create a recipe
/// </summary>
public class RecipeBuilder public class RecipeBuilder
{ {
private readonly string name; private readonly string name;

@ -2,12 +2,21 @@
namespace Models namespace Models
{ {
/// <summary>
/// The essential information about a recipe
/// </summary>
/// <param name="Name">The recipe's name</param>
/// <param name="CalPerPers">The energy input</param>
/// <param name="CookTimeMins">Estimated time of preparation in minutes</param>
/// <param name="Image">An illustrative image of the recipe</param>
/// <param name="AverageNote">The average rate of the recipe</param>
/// <param name="Id">An unique identifier</param>
[DataContract] [DataContract]
public record RecipeInfo( public record RecipeInfo(
[property: DataMember] string Name, [property: DataMember] string Name,
[property: DataMember] uint CalPerPers, [property: DataMember] uint CalPerPers,
[property: DataMember] uint CookTimeMins, [property: DataMember] uint CookTimeMins,
[property: DataMember] Uri? Image, [property: DataMember] Uri Image,
[property: DataMember] float AverageNote, [property: DataMember] float AverageNote,
[property: DataMember] Guid Id [property: DataMember] Guid Id
); );

@ -3,6 +3,11 @@ using System.Runtime.Serialization;
namespace Models namespace Models
{ {
/// <summary>
/// The rate of a recipe, usually, the instances are bound with an account.
/// </summary>
/// <param name="IsFavorite"></param>
/// <param name="Rate">a rate between 0 and 5</param>
[DataContract] [DataContract]
public record RecipeRate( public record RecipeRate(
[property: DataMember] bool IsFavorite = false, [property: DataMember] bool IsFavorite = false,

@ -2,13 +2,25 @@
namespace Models namespace Models
{ {
/// <summary>
/// Publics informations of a user
/// </summary>
[DataContract] [DataContract]
public class User public class User
{ {
/// <summary>
/// The profile picture
/// </summary>
[DataMember] [DataMember]
public Uri ProfilePicture { get; init; } public Uri ProfilePicture { get; init; }
/// <summary>
/// The username
/// </summary>
[DataMember] [DataMember]
public string Name { get; init; } public string Name { get; init; }
/// <summary>
/// An unique identifier
/// </summary>
[DataMember] [DataMember]
public Guid Id { get; init; } public Guid Id { get; init; }

@ -1,23 +0,0 @@
using Models;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LocalEndpoint
{
public interface IAccountOwnedRecipes
{
public Account Account { get; }
public bool UploadRecipe(Recipe recipe);
public bool RemoveRecipe(RecipeInfo info);
public ImmutableList<RecipeInfo> GetAccountRecipes();
}
}

@ -0,0 +1,38 @@
using Models;
using System.Collections.Immutable;
namespace Services
{
/// <summary>
/// This service handles the recipes created by an account
/// </summary>
public interface IAccountOwnedRecipesService
{
/// <summary>
/// This service's bound account
/// </summary>
public Account Account { get; }
/// <summary>
/// Upload a new recipe, ensuring the recipe's owner matches the service's bound account user.
/// </summary>
/// <param name="recipe">The recipe to upload</param>
/// <returns>true if the recipe could be uploaded, false instead</returns>
public bool UploadRecipe(Recipe recipe);
/// <summary>
/// Removes a recipe
/// </summary>
/// <param name="info">The informations about the recipe to remove</param>
/// <returns>true if the recipe could be removed, false instead</returns>
public bool RemoveRecipe(RecipeInfo info);
/// <summary>
/// The living recipes created by this account.
/// If the user removes a recipe (using <see cref="RemoveRecipe(RecipeInfo)"/>) it'll no longer apear in the
/// next invocations of this recipe
/// </summary>
/// <returns>the list of all the living recipes of the account</returns>
public ImmutableList<RecipeInfo> GetAccountRecipes();
}
}

@ -1,25 +0,0 @@
using Models;
using System.Collections.Immutable;
namespace Endpoint
{
public interface IAccountRecipesPreferences
{
public Account Account { get; }
public void AddToFavorites(RecipeInfo info);
public void RemoveFromFavorites(RecipeInfo info);
public void SetReviewScore(RecipeInfo info, uint score);
public bool AddToWeeklyList(RecipeInfo info, uint persAmount);
public RecipeRate GetRate(RecipeInfo info);
public ImmutableList<RecipeInfo> GetFavorites();
public ImmutableList<RecipeInfo> GetRecommendedRecipes();
public ImmutableList<(RecipeInfo, uint)> GetWeeklyList();
}
}

@ -0,0 +1,68 @@
using Models;
using System.Collections.Immutable;
namespace Services
{
/// <summary>
/// This service handles the preferences of a bound account
/// </summary>
public interface IAccountRecipesPreferencesService
{
/// <summary>
/// The bound account
/// </summary>
public Account Account { get; }
/// <summary>
/// Adds a recipe in the favorites of the bound account
/// </summary>
/// <param name="info">The information about the recipe to add in favorites</param>
public void AddToFavorites(RecipeInfo info);
/// <summary>
/// Removes a recipe from the favorites of the bound account
/// </summary>
/// <param name="info">The information about the recipe to remove from favorites</param>
public void RemoveFromFavorites(RecipeInfo info);
/// <summary>
/// Sets a score for the specified recipe
/// </summary>
/// <param name="info">The information about the targeted recipe</param>
/// <param name="score">The score to set</param>
public void SetReviewScore(RecipeInfo info, uint score);
/// <summary>
/// Adds a recipe to the weekly list, specifying the amount of persons that must be fed for the week
/// </summary>
/// <param name="info">The information about the targeted recipe</param>
/// <param name="persAmount">The amount of guests that needs to be fed by the recipe for the week</param>
/// <returns></returns>
public bool AddToWeeklyList(RecipeInfo info, uint persAmount);
/// <summary>
/// Retrieves the rate of the targeted recipe
/// The rate contains the user's score and whether if the recipe is in the favorites list.
/// <see cref="RecipeInfo"/>
/// </summary>
/// <param name="info">The information about the targeted recipe</param>
/// <returns></returns>
public RecipeRate GetRate(RecipeInfo info);
/// <summary>
/// The favorites recipes of the account
/// </summary>
/// <returns>A list containing all the recipe info that are marked as favorite by the bound account</returns>
public ImmutableList<RecipeInfo> GetFavorites();
/// <summary>
/// The recommended recipes for the user based on his preferences.
/// </summary>
/// <returns>A list of the recommended recipes based on the preferences of the bound account</returns>
public ImmutableList<RecipeInfo> GetRecommendedRecipes();
/// <summary>
/// The weekly list of the bound account
/// </summary>
/// <returns>The weekly list of the bound account, containing tuples that binds a recipe to the number of guests to feed for the week</returns>
public ImmutableList<(RecipeInfo, uint)> GetWeeklyList();
}
}

@ -1,10 +1,33 @@
using Models; using Models;
namespace Endpoint
namespace Services
{ {
/// <summary>
/// Service for account authentification
/// Passwords are being passed in this service clearly as the hash function used for passwords is implementation specific
/// </summary>
public interface IAuthService public interface IAuthService
{ {
/// <summary>
/// Tries to login to an account using its mail address and password.
/// </summary>
/// <param name="email"> The mail address which acts as an identifier for the targeted account </param>
/// <param name="password"> The (clear) password used to login.</param>
/// <returns>
/// Returns an instance of Account representing the account that got logged in.
/// If the login credentials are invalid to log in the targeted acccount, this method returns null.
/// </returns>
public Account? Login(string email, string password); public Account? Login(string email, string password);
/// <summary>
/// Tries to register to a new account, defining its mail address, username and password.
/// </summary>
/// <param name="email"> The mail address which acts as an identifier for the targeted account </param>
/// <param name="username"> The username of the account </param>
/// <param name="password"> The (clear) password used to login on next connections attempt.</param>
/// <returns>
/// Returns an instance of Account representing the account that got newly registered.
/// If the register credentials are invalid, or if the account already exists, this method returns null.
/// </returns>
public Account? Register(string email, string username, string password); public Account? Register(string email, string username, string password);
} }
} }

@ -1,7 +1,10 @@
 
namespace Endpoint namespace Services
{ {
/// <summary>
/// The endpoint is the central element of the 'Services' assembly.
/// </summary>
public interface IEndpoint public interface IEndpoint
{ {
public IAuthService AuthService { get; } public IAuthService AuthService { get; }

@ -1,17 +1,41 @@
using LocalEndpoint; using Models;
using Models;
using System.Collections.Immutable; using System.Collections.Immutable;
namespace Endpoint namespace Services
{ {
/// <summary>
/// The services that is in charge of handling the application's recipes.
/// </summary>
public interface IRecipesService public interface IRecipesService
{ {
/// <summary>
///
/// </summary>
/// <returns>A list containg the popular recipes of the week</returns>
public ImmutableList<RecipeInfo> PopularRecipes(); public ImmutableList<RecipeInfo> PopularRecipes();
/// <summary>
/// Retrieves a recipe from given RecipeInfo
/// </summary>
/// <param name="info">the informations about the recipe that we want to retrieve</param>
/// <returns>some recipe if the recipe was found, null else</returns>
public Recipe? GetRecipe(RecipeInfo info); public Recipe? GetRecipe(RecipeInfo info);
public IAccountOwnedRecipes GetRecipesOf(Account account); /// <summary>
public IAccountRecipesPreferences GetPreferencesOf(Account account); /// Gets the service that is in charge of handling the account's owned recipes.
/// The account's owned recipes are the recipes that the account created.
/// </summary>
/// <param name="account">The account logged in</param>
/// <returns>The service that handles the given account's recipes</returns>
public IAccountOwnedRecipesService GetRecipesOf(Account account);
/// <summary>
/// Gets the service that handles all the preferences of the given account
/// </summary>
/// <param name="account">The account logged in</param>
/// <returns>
/// The service that handles the given account's preferences
/// </returns>
public IAccountRecipesPreferencesService GetPreferencesOf(Account account);
} }

@ -6,16 +6,17 @@
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET --> <!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
<!-- <TargetFrameworks>$(TargetFrameworks);net7.0-tizen</TargetFrameworks> --> <!-- <TargetFrameworks>$(TargetFrameworks);net7.0-tizen</TargetFrameworks> -->
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<RootNamespace>ShoopNCook</RootNamespace> <RootNamespace>ShoopNCook</RootNamespace>
<UseMaui>true</UseMaui> <UseMaui>true</UseMaui>
<SingleProject>true</SingleProject> <SingleProject>true</SingleProject>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<!-- Display name --> <!-- Display name -->
<ApplicationTitle>ShoopNCook</ApplicationTitle> <ApplicationTitle>ShopNCook</ApplicationTitle>
<!-- App Identifier --> <!-- App Identifier -->
<ApplicationId>com.companyname.shoopncook</ApplicationId> <ApplicationId>com.companyname.shopncook</ApplicationId>
<ApplicationIdGuid>bf17e1fe-a722-42f6-a24d-3327d351c924</ApplicationIdGuid> <ApplicationIdGuid>bf17e1fe-a722-42f6-a24d-3327d351c924</ApplicationIdGuid>
<!-- Versions --> <!-- Versions -->
@ -48,26 +49,31 @@
<ItemGroup> <ItemGroup>
<AndroidResource Remove="ShopNCookTests\**" /> <AndroidResource Remove="ShopNCookTests\**" />
<AndroidResource Remove="Tests\**" /> <AndroidResource Remove="Tests\**" />
<Compile Remove="Foo\**" />
<Compile Remove="Services\**" /> <Compile Remove="Services\**" />
<Compile Remove="LocalServices\**" /> <Compile Remove="LocalServices\**" />
<Compile Remove="Models\**" /> <Compile Remove="Models\**" />
<Compile Remove="ShopNCookTests\**" /> <Compile Remove="ShopNCookTests\**" />
<Compile Remove="Tests\**" /> <Compile Remove="Tests\**" />
<EmbeddedResource Remove="Foo\**" />
<EmbeddedResource Remove="Services\**" /> <EmbeddedResource Remove="Services\**" />
<EmbeddedResource Remove="LocalServices\**" /> <EmbeddedResource Remove="LocalServices\**" />
<EmbeddedResource Remove="Models\**" /> <EmbeddedResource Remove="Models\**" />
<EmbeddedResource Remove="ShopNCookTests\**" /> <EmbeddedResource Remove="ShopNCookTests\**" />
<EmbeddedResource Remove="Tests\**" /> <EmbeddedResource Remove="Tests\**" />
<MauiCss Remove="Foo\**" />
<MauiCss Remove="Services\**" /> <MauiCss Remove="Services\**" />
<MauiCss Remove="LocalServices\**" /> <MauiCss Remove="LocalServices\**" />
<MauiCss Remove="Models\**" /> <MauiCss Remove="Models\**" />
<MauiCss Remove="ShopNCookTests\**" /> <MauiCss Remove="ShopNCookTests\**" />
<MauiCss Remove="Tests\**" /> <MauiCss Remove="Tests\**" />
<MauiXaml Remove="Foo\**" />
<MauiXaml Remove="Services\**" /> <MauiXaml Remove="Services\**" />
<MauiXaml Remove="LocalServices\**" /> <MauiXaml Remove="LocalServices\**" />
<MauiXaml Remove="Models\**" /> <MauiXaml Remove="Models\**" />
<MauiXaml Remove="ShopNCookTests\**" /> <MauiXaml Remove="ShopNCookTests\**" />
<MauiXaml Remove="Tests\**" /> <MauiXaml Remove="Tests\**" />
<None Remove="Foo\**" />
<None Remove="Services\**" /> <None Remove="Services\**" />
<None Remove="LocalServices\**" /> <None Remove="LocalServices\**" />
<None Remove="Models\**" /> <None Remove="Models\**" />

@ -22,4 +22,10 @@
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LocalServices\LocalServices.csproj" />
<ProjectReference Include="..\Models\Models.csproj" />
<ProjectReference Include="..\Services\Services.csproj" />
</ItemGroup>
</Project> </Project>

@ -1,3 +1,5 @@
using Models;
namespace Tests namespace Tests
{ {
public class UnitTest1 public class UnitTest1
@ -5,7 +7,7 @@ namespace Tests
[Fact] [Fact]
public void Test1() public void Test1()
{ {
new RecipeRate(true, 4);
} }
} }

@ -1,34 +1,40 @@
namespace ShoopNCook.Views; namespace ShoopNCook.Views;
// Classe représentant une vue avec un compteur
public partial class CounterView : ContentView public partial class CounterView : ContentView
{ {
// Propriété liée pour le nombre à afficher
private readonly BindableProperty CountProperty = private readonly BindableProperty CountProperty =
BindableProperty.Create(nameof(CountLabel), typeof(uint), typeof(CounterView), default(uint) + 1); BindableProperty.Create(nameof(CountLabel), typeof(uint), typeof(CounterView), default(uint) + 1);
// Propriété liée pour le texte à afficher
private readonly BindableProperty CounterLabelProperty = private readonly BindableProperty CounterLabelProperty =
BindableProperty.Create(nameof(CounterLabel), typeof(string), typeof(CounterView), default(string)); BindableProperty.Create(nameof(CounterLabel), typeof(string), typeof(CounterView), default(string));
public CounterView() public CounterView()
{ {
InitializeComponent(); InitializeComponent();
// Liaison des propriétés à leurs étiquettes respectives
CountLabel.BindingContext = this; CountLabel.BindingContext = this;
CountLabel.SetBinding(Label.TextProperty, nameof(Count)); CountLabel.SetBinding(Label.TextProperty, nameof(Count));
CounterLabel.BindingContext = this; CounterLabel.BindingContext = this;
CounterLabel.SetBinding(Label.TextProperty, nameof(CounterText)); CounterLabel.SetBinding(Label.TextProperty, nameof(CounterText));
} }
// Accesseurs pour Count
public uint Count public uint Count
{ {
get => (uint)GetValue(CountProperty); get => (uint)GetValue(CountProperty);
set set
{ {
// Assure que la valeur est toujours au minimum 1
SetValue(CountProperty, value <= 1 ? 1 : uint.Parse(value.ToString())); SetValue(CountProperty, value <= 1 ? 1 : uint.Parse(value.ToString()));
OnPropertyChanged(nameof(Count)); OnPropertyChanged(nameof(Count));
} }
} }
// Accesseurs pour CounterText
public string CounterText public string CounterText
{ {
get => (string)GetValue(CounterLabelProperty); get => (string)GetValue(CounterLabelProperty);
@ -39,6 +45,7 @@ public partial class CounterView : ContentView
} }
} }
// Méthodes pour augmenter et diminuer le compteur
private void OnPlus(object o, EventArgs e) private void OnPlus(object o, EventArgs e)
{ {
Count += 1; Count += 1;

@ -1,19 +1,22 @@
namespace ShoopNCook.Views; namespace ShoopNCook.Views;
using Microsoft.Maui.Graphics; using Microsoft.Maui.Graphics;
// Classe représentant un bouton avec une tête (une image en préfixe)
public partial class HeadedButton : ContentView public partial class HeadedButton : ContentView
{ {
// Texte du bouton
public string Text public string Text
{ {
set => BtnLabel.Text = value; set => BtnLabel.Text = value;
} }
// Couleur de l'image en préfixe
public string HeadColor public string HeadColor
{ {
set => PrefixBorder.BackgroundColor = Color.FromArgb(value); set => PrefixBorder.BackgroundColor = Color.FromArgb(value);
} }
// Source de l'image en préfixe
public string HeadSource public string HeadSource
{ {
set => PrefixImage.Source = ImageSource.FromFile(value); set => PrefixImage.Source = ImageSource.FromFile(value);

@ -2,6 +2,7 @@ using Models;
namespace ShoopNCook.Views; namespace ShoopNCook.Views;
// Classe représentant une entrée d'ingrédient
public partial class IngredientEntry : ContentView public partial class IngredientEntry : ContentView
{ {
public IngredientEntry() public IngredientEntry()
@ -9,10 +10,12 @@ public partial class IngredientEntry : ContentView
InitializeComponent(); InitializeComponent();
} }
// Renvoie une nouvelle instance de Ingredient à partir des informations entrées par l'utilisateur
public Ingredient MakeValue() public Ingredient MakeValue()
{ {
float quantity; float quantity;
// Tente de convertir la quantité en float, sinon, attribue une valeur par défaut de 0
if (!float.TryParse(QuantityEntry.Text, out quantity)) if (!float.TryParse(QuantityEntry.Text, out quantity))
{ {
quantity = 0; quantity = 0;

@ -2,9 +2,10 @@ using Models;
namespace ShoopNCook.Views; namespace ShoopNCook.Views;
// Classe représentant une vue d'ingrédient
public partial class IngredientView : ContentView public partial class IngredientView : ContentView
{ {
// Propriétés liées pour le nom, la quantité et l'unité de l'ingrédient
private readonly BindableProperty NameProperty = private readonly BindableProperty NameProperty =
BindableProperty.Create(nameof(Name), typeof(string), typeof(IngredientView), default(string)); BindableProperty.Create(nameof(Name), typeof(string), typeof(IngredientView), default(string));
@ -36,6 +37,7 @@ public partial class IngredientView : ContentView
{ {
InitializeComponent(); InitializeComponent();
// Initialisation des valeurs de l'ingrédient
Name = ingredient.Name; Name = ingredient.Name;
Quantity = ingredient.Amount; Quantity = ingredient.Amount;
//TODO Unit implementation in IngredientView.xaml.cs //TODO Unit implementation in IngredientView.xaml.cs

@ -2,7 +2,7 @@ using Models;
namespace ShoopNCook.Pages; namespace ShoopNCook.Pages;
using Endpoint; using Services;
using Models; using Models;
using ShoopNCook.Views; using ShoopNCook.Views;
@ -23,7 +23,7 @@ public partial class FavoritesPage : ContentPage
private void UpdateFavorites() private void UpdateFavorites()
{ {
IAccountRecipesPreferences preferences = service.GetPreferencesOf(account); IAccountRecipesPreferencesService preferences = service.GetPreferencesOf(account);
RecipeViewLayout.Children.Clear(); RecipeViewLayout.Children.Clear();
preferences.GetFavorites().ForEach(info => preferences.GetFavorites().ForEach(info =>
{ {

@ -1,8 +1,8 @@
namespace ShoopNCook.Pages; namespace ShoopNCook.Pages;
using Models; using Models;
using ShoopNCook.Views; using ShoopNCook.Views;
using Endpoint; using Services;
using LocalEndpoint; using Services;
public partial class HomePage : ContentPage public partial class HomePage : ContentPage
{ {
@ -11,7 +11,7 @@ public partial class HomePage : ContentPage
InitializeComponent(); InitializeComponent();
IRecipesService service = endpoint.RecipesService; IRecipesService service = endpoint.RecipesService;
IAccountRecipesPreferences preferences = service.GetPreferencesOf(account); IAccountRecipesPreferencesService preferences = service.GetPreferencesOf(account);
//TODO this code can be factorised //TODO this code can be factorised

@ -1,4 +1,4 @@
using Endpoint; using Services;
using ShoopNCook.Controllers; using ShoopNCook.Controllers;
namespace ShoopNCook.Pages; namespace ShoopNCook.Pages;

@ -1,5 +1,5 @@
using Endpoint; using Services;
using LocalEndpoint; using Services;
using Models; using Models;
using ShoopNCook.Views; using ShoopNCook.Views;
@ -8,7 +8,7 @@ namespace ShoopNCook.Pages;
public partial class MyListPage : ContentPage public partial class MyListPage : ContentPage
{ {
private readonly IAccountRecipesPreferences preferences; private readonly IAccountRecipesPreferencesService preferences;
private readonly IRecipesService service; private readonly IRecipesService service;
public MyListPage(Account account, IRecipesService service) public MyListPage(Account account, IRecipesService service)

@ -1,5 +1,5 @@
using Endpoint; using Services;
using LocalEndpoint; using Services;
using Models; using Models;
using ShoopNCook.Views; using ShoopNCook.Views;
@ -31,7 +31,7 @@ public partial class MyRecipesPage : ContentPage
RecipesLayout.Children.Add(new OwnedRecipeView(info, () => RecipesLayout.Children.Add(new OwnedRecipeView(info, () =>
{ {
Recipe recipe = service.GetRecipe(info); Recipe recipe = service.GetRecipe(info);
IAccountRecipesPreferences preferences = service.GetPreferencesOf(account); IAccountRecipesPreferencesService preferences = service.GetPreferencesOf(account);
Shell.Current.Navigation.PushAsync(new RecipePage(recipe, preferences, 1)); Shell.Current.Navigation.PushAsync(new RecipePage(recipe, preferences, 1));
}, },
() => RemoveRecipe(info) () => RemoveRecipe(info)
@ -40,7 +40,7 @@ public partial class MyRecipesPage : ContentPage
private void RemoveRecipe(RecipeInfo info) private void RemoveRecipe(RecipeInfo info)
{ {
IAccountOwnedRecipes recipes = service.GetRecipesOf(account); IAccountOwnedRecipesService recipes = service.GetRecipesOf(account);
if (!recipes.RemoveRecipe(info)) if (!recipes.RemoveRecipe(info))
{ {
@ -64,7 +64,7 @@ public partial class MyRecipesPage : ContentPage
} }
private async void OnAddRecipeButtonClicked(object sender, EventArgs e) private async void OnAddRecipeButtonClicked(object sender, EventArgs e)
{ {
IAccountOwnedRecipes recipes = service.GetRecipesOf(account); IAccountOwnedRecipesService recipes = service.GetRecipesOf(account);
var page = new CreateRecipePage(account.User, recipe => var page = new CreateRecipePage(account.User, recipe =>
{ {

@ -1,8 +1,8 @@
using ShoopNCook.Views; using ShoopNCook.Views;
using System.Windows.Input; using System.Windows.Input;
using Models; using Models;
using LocalEndpoint; using Services;
using Endpoint; using Services;
namespace ShoopNCook.Pages; namespace ShoopNCook.Pages;
@ -13,12 +13,12 @@ public partial class RecipePage : ContentPage
private bool isFavorite; private bool isFavorite;
private IAccountRecipesPreferences preferences; private IAccountRecipesPreferencesService preferences;
private RecipeInfo info; private RecipeInfo info;
public ICommand StarCommand => new Command<string>(count => SetNote(uint.Parse(count))); public ICommand StarCommand => new Command<string>(count => SetNote(uint.Parse(count)));
public RecipePage(Recipe recipe, IAccountRecipesPreferences preferences, uint amount) public RecipePage(Recipe recipe, IAccountRecipesPreferencesService preferences, uint amount)
{ {
InitializeComponent(); InitializeComponent();

@ -1,12 +1,9 @@
namespace ShoopNCook.Pages; namespace ShoopNCook.Pages;
using Models;
using ShoopNCook.Views;
public partial class SearchPage : ContentPage public partial class SearchPage : ContentPage
{ {
public SearchPage() public SearchPage()
{ {
InitializeComponent(); InitializeComponent();
//TODO
} }
private async void OnBackButtonClicked(object sender, EventArgs e) private async void OnBackButtonClicked(object sender, EventArgs e)
{ {

Loading…
Cancel
Save