Compare commits
220 Commits
af19a0ba00
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 9acd4a59ce | |||
| 0adcd6c08a | |||
| be57ac7b65 | |||
| b0519e81cf | |||
| b546d8946e | |||
| cac83e38a8 | |||
| de904e0a28 | |||
| 5bcca4aafe | |||
| 6a24ce43cc | |||
| ef5c0476b0 | |||
| 936c5c0428 | |||
| 247fbd6ae9 | |||
| 7661e9afd3 | |||
| 78dbfa20f1 | |||
| 9d90a54830 | |||
| a0e9e83118 | |||
| 3ae229b4f3 | |||
| 0f274613de | |||
| 17726a4b63 | |||
| febcdc1ced | |||
| 40690304fa | |||
| e468a47782 | |||
| 075aefc97d | |||
| c20ed984c2 | |||
| 28e60b79fa | |||
| 294f601085 | |||
| e1dd694fb7 | |||
| ba61988840 | |||
| 9ab1efde0e | |||
| fcf03d0f62 | |||
| 967542a0f1 | |||
| c9f3f2b343 | |||
| bb049e674a | |||
| 92c85da0c8 | |||
| 006ffc5ba3 | |||
| 258ffb782f | |||
| 6aeb7edc2b | |||
| 2af526d623 | |||
| c503e008a7 | |||
| 907e19e398 | |||
| 5c9f6c8120 | |||
| f2bb057a9b | |||
| 6290ceaaf0 | |||
| dba048620b | |||
| b4b8062b13 | |||
| 3b1d9fa4bc | |||
| 8fe20c32db | |||
| f2b978f35f | |||
| ba0e35e526 | |||
| 203b8db180 | |||
| 5a5b3c5d87 | |||
| 963d9f4ab1 | |||
| b21f4ea9cd | |||
| b1a0a41202 | |||
| 69affbdaed | |||
| 222afcd84f | |||
| 6600c9ab13 | |||
| e59b11b32f | |||
| 924b896a9a | |||
| f89cf27be4 | |||
| 06ef8927a7 | |||
| 0c3b7380c5 | |||
| 8d9eb4b364 | |||
| dca567ba9f | |||
| 385be506a7 | |||
| 00cd006bf8 | |||
| 9c9253c94a | |||
| 3fe1948867 | |||
| a5b70532e6 | |||
| da2fd1e61d | |||
| 1443284236 | |||
| e470dd11bb | |||
| bfbfd34624 | |||
| aa6f27383f | |||
| 325120e5ec | |||
| a57ebd4507 | |||
| 1c47b770b4 | |||
| ff08b82611 | |||
| 98dd9a049b | |||
| 27d4fd5368 | |||
| 7f6998c2de | |||
| 03932b380d | |||
| a7cafb479b | |||
| 12b217ea59 | |||
| 7bf43b1855 | |||
| 070df12049 | |||
| d172642b1e | |||
| abb1d774fe | |||
| 7168d93621 | |||
| a0d05dcdc2 | |||
| 4928a3cc7d | |||
| dc9a1f1b9c | |||
| 402f5f2fe9 | |||
| 0ff81912a9 | |||
| a0044a8d30 | |||
| a2e50a0195 | |||
| 1e6d354343 | |||
| a7a5650046 | |||
| 594315c473 | |||
| 1a91ec38dd | |||
| a76f757081 | |||
| 080c4eda78 | |||
| 0d57d40651 | |||
| d131ec1d16 | |||
| 000cead877 | |||
| 7976545da6 | |||
| ca15b69677 | |||
| 8b3489cf63 | |||
| 9e7ff56099 | |||
| 61f3e27738 | |||
| a8fdca4871 | |||
| 327ba7b1cc | |||
| 3ca5b35cfe | |||
| 12aab06874 | |||
| 5848697dec | |||
| 93162f3a4b | |||
| c001fee09d | |||
| d3a74196f2 | |||
| be211b12c6 | |||
| 8dc53201ce | |||
| aa5cc3d683 | |||
| e6a9e92748 | |||
| 9c1584d621 | |||
| 2f879d987b | |||
| bcc0a60669 | |||
| e169e94c8e | |||
| cc30f5b149 | |||
| 7035a9b64d | |||
| 146cdf9bff | |||
| 0d1f18ecf6 | |||
| 0859e3aa2b | |||
| 1bdb14e9d3 | |||
| 059e93cb86 | |||
| 32aaec7d65 | |||
| 825818645a | |||
| d2560e2971 | |||
| 0fb9333b82 | |||
| 52b4e68fc6 | |||
| 12752c4834 | |||
| 16a9d10844 | |||
| 323af8d6f0 | |||
| 1239f31673 | |||
| 5f92625c16 | |||
| cc590fa65c | |||
| 71c9f27d64 | |||
| cbd2ba7dfb | |||
| 9ed8158135 | |||
| 35f2d1675d | |||
| 4eaf03698c | |||
| df146a24b0 | |||
| 0d6b9eb294 | |||
| 71ac7eb03f | |||
| 556b22b8df | |||
| c225eedc8b | |||
| c07cdf7eae | |||
| 032e2caf1b | |||
| b7706de30c | |||
| 4b084eea98 | |||
| 1110c74234 | |||
| b8814ce708 | |||
| eb6d82a979 | |||
| c8ff0dd9dd | |||
| 3ec9d4be79 | |||
| 2f33951c4e | |||
| 84c75098cf | |||
| 71ebfcf4be | |||
| 7d11eaf7ef | |||
| f64f1bf1ee | |||
| 603dc4a2c6 | |||
| 127a60b621 | |||
| d248ccb1db | |||
| 928743303c | |||
| 36bca4d658 | |||
| 712b561d12 | |||
| ddd0cad36b | |||
| 31fbafe806 | |||
| d4bcf7980b | |||
| ffebc376e1 | |||
| e438e8a114 | |||
| c7543f8a49 | |||
| 1fcf1a1c26 | |||
| 2acb4b4117 | |||
| c48d1bb5b2 | |||
| b2ba51d297 | |||
| afde272d8a | |||
| a4c8edb572 | |||
| 5c94402c2f | |||
| 41f41ce3fb | |||
| 02533e9bfb | |||
| b9d0bf2170 | |||
| 29faf5a99e | |||
| ce2a5aceb2 | |||
| 906d26c2ec | |||
| 5622e55441 | |||
| 1a8a0524a6 | |||
| d1b8c3c7e0 | |||
| c498e23a07 | |||
| a91ca743ab | |||
| dfd2d2ee3f | |||
| 6125eb82ee | |||
| d53729f798 | |||
| 45702aa4f2 | |||
| 36e107d48e | |||
| 449533cf0b | |||
| 6affee7b8c | |||
| 32bc018c75 | |||
| b06e1b27dd | |||
| f1af18f4fb | |||
| 08c567b9ba | |||
| 2802030de1 | |||
| 53bf98ece6 | |||
| 1f2344eabf | |||
| 6e9ce70355 | |||
| b80a402d15 | |||
| 9ac2428a1d | |||
| 87b0a9993c | |||
| a6e4353e97 | |||
| 84a9cec80f | |||
| 59626f852e | |||
| 70df3c5aab |
+152
@@ -0,0 +1,152 @@
|
||||
# To learn more about .editorconfig see https://aka.ms/editorconfigdocs
|
||||
###############################
|
||||
# Core EditorConfig Options #
|
||||
###############################
|
||||
root = true
|
||||
# All files
|
||||
[*]
|
||||
indent_style = space
|
||||
# Code files
|
||||
[*.{cs,csx,vb,vbx}]
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
charset = utf-8-bom
|
||||
###############################
|
||||
# .NET Coding Conventions #
|
||||
###############################
|
||||
[*.{cs,vb}]
|
||||
# Organize usings
|
||||
dotnet_sort_system_directives_first = true
|
||||
# this. preferences
|
||||
dotnet_style_qualification_for_field = false:warning
|
||||
dotnet_style_qualification_for_property = false:warning
|
||||
dotnet_style_qualification_for_method = false:warning
|
||||
dotnet_style_qualification_for_event = false:warning
|
||||
# Language keywords vs BCL types preferences
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
|
||||
dotnet_style_predefined_type_for_member_access = true:silent
|
||||
# Parentheses preferences
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
|
||||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
|
||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
|
||||
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
|
||||
# Modifier preferences
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning
|
||||
dotnet_style_readonly_field = true:suggestion
|
||||
# Expression-level preferences
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
dotnet_style_collection_initializer = true:suggestion
|
||||
dotnet_style_explicit_tuple_names = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
|
||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||
dotnet_style_prefer_auto_properties = true:silent
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||
# SA1201: Elements should appear in the correct order
|
||||
dotnet_diagnostic.SA1201.severity = silent
|
||||
###############################
|
||||
# Naming Conventions #
|
||||
###############################
|
||||
# Style Definitions
|
||||
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
|
||||
# Use PascalCase for constant fields
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
|
||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
|
||||
dotnet_naming_symbols.constant_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.constant_fields.required_modifiers = const
|
||||
###############################
|
||||
# C# Coding Conventions #
|
||||
###############################
|
||||
[*.cs]
|
||||
# var preferences
|
||||
csharp_style_var_for_built_in_types = true:warning
|
||||
csharp_style_var_when_type_is_apparent = true:warning
|
||||
csharp_style_var_elsewhere = true:warning
|
||||
# Expression-bodied members
|
||||
csharp_style_expression_bodied_methods = false:silent
|
||||
csharp_style_expression_bodied_constructors = false:silent
|
||||
csharp_style_expression_bodied_operators = false:silent
|
||||
csharp_style_expression_bodied_properties = true:silent
|
||||
csharp_style_expression_bodied_indexers = true:silent
|
||||
csharp_style_expression_bodied_accessors = true:silent
|
||||
# Pattern matching preferences
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
# Null-checking preferences
|
||||
csharp_style_throw_expression = true:suggestion
|
||||
csharp_style_conditional_delegate_call = true:suggestion
|
||||
# Modifier preferences
|
||||
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
|
||||
# Expression-level preferences
|
||||
csharp_prefer_braces = true:silent
|
||||
csharp_style_deconstructed_variable_declaration = true:suggestion
|
||||
csharp_prefer_simple_default_expression = true:suggestion
|
||||
csharp_style_pattern_local_over_anonymous_function = true:suggestion
|
||||
csharp_style_inlined_variable_declaration = true:suggestion
|
||||
###############################
|
||||
# C# Formatting Rules #
|
||||
###############################
|
||||
# New line preferences
|
||||
csharp_new_line_before_open_brace = all
|
||||
csharp_new_line_before_else = true
|
||||
csharp_new_line_before_catch = true
|
||||
csharp_new_line_before_finally = true
|
||||
csharp_new_line_before_members_in_object_initializers = true
|
||||
csharp_new_line_before_members_in_anonymous_types = true
|
||||
csharp_new_line_between_query_expression_clauses = true
|
||||
# Indentation preferences
|
||||
csharp_indent_case_contents = true
|
||||
csharp_indent_switch_labels = true
|
||||
csharp_indent_labels = flush_left
|
||||
# Space preferences
|
||||
csharp_space_after_cast = false
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
csharp_space_between_parentheses = false
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
# Wrapping preferences
|
||||
csharp_preserve_single_line_statements = true
|
||||
csharp_preserve_single_line_blocks = true
|
||||
|
||||
# SA1601: Partial elements should be documented
|
||||
dotnet_diagnostic.SA1601.severity = none
|
||||
|
||||
# Default severity for analyzer diagnostics with category 'StyleCop.CSharp.DocumentationRules'
|
||||
dotnet_analyzer_diagnostic.category-StyleCop.CSharp.DocumentationRules.severity = none
|
||||
|
||||
# SA1516: Elements should be separated by blank line
|
||||
dotnet_diagnostic.SA1516.severity = error
|
||||
|
||||
# SA1649: File name should match first type name
|
||||
dotnet_diagnostic.SA1649.severity = none
|
||||
|
||||
# CA1062: Validate arguments of public methods
|
||||
dotnet_diagnostic.CA1062.severity = error
|
||||
|
||||
# SA0001: XML comment analysis is disabled
|
||||
dotnet_diagnostic.SA0001.severity = none
|
||||
|
||||
# SA1309: Make sure variables with private access modifier are allowed to start with "_"
|
||||
dotnet_diagnostic.SA1309.severity = none
|
||||
|
||||
# SA1309: Make sure class members are allowed to call without "this" prefix
|
||||
dotnet_diagnostic.SA1101.severity = none
|
||||
# SA1118: parameter spans multiple lines
|
||||
dotnet_diagnostic.SA1118.severity = none
|
||||
# SA1011: closing square bracket spacing
|
||||
dotnet_diagnostic.SA1011.severity = none
|
||||
# SA1517: no blank lines at start of file
|
||||
dotnet_diagnostic.SA1517.severity = none
|
||||
# SA1200: using directive should appear within a namespace
|
||||
dotnet_diagnostic.SA1200.severity = none
|
||||
+1
-3
@@ -1,4 +1,3 @@
|
||||
# ---> VisualStudio
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
@@ -83,8 +82,6 @@ StyleCopReport.xml
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
# but not Directory.Build.rsp, as it configures directory-level build defaults
|
||||
!Directory.Build.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
@@ -399,4 +396,5 @@ FodyWeavers.xsd
|
||||
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
src/Francesco.Recipes.World.xml
|
||||
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
COPY ["Francesco.Recipes.World/Francesco.Recipes.World.csproj", "Francesco.Recipes.World/"]
|
||||
RUN dotnet restore "Francesco.Recipes.World/Francesco.Recipes.World.csproj"
|
||||
|
||||
COPY . .
|
||||
WORKDIR "/src/Francesco.Recipes.World"
|
||||
RUN dotnet publish "Francesco.Recipes.World.csproj" -c Release -o /app/publish
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/publish .
|
||||
ENTRYPOINT ["dotnet", "Francesco.Recipes.World.dll"]
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RuleSet Name="Microsoft Managed Recommended Rules" Description="These rules focus on the most critical problems in your code, including potential security holes, application crashes, and other important logic and design errors. It is recommended to include this rule set in any custom rule set you create for your projects." ToolsVersion="16.0">
|
||||
<Localization ResourceAssembly="Microsoft.VisualStudio.CodeAnalysis.RuleSets.Strings.dll" ResourceBaseName="Microsoft.VisualStudio.CodeAnalysis.RuleSets.Strings.Localized">
|
||||
<Name Resource="MinimumRecommendedRules_Name" />
|
||||
<Description Resource="MinimumRecommendedRules_Description" />
|
||||
</Localization>
|
||||
<Rules AnalyzerId="Microsoft.Analyzers.ManagedCodeAnalysis" RuleNamespace="Microsoft.Rules.Managed">
|
||||
<Rule Id="CA1001" Action="Warning" />
|
||||
<Rule Id="CA1009" Action="Warning" />
|
||||
<Rule Id="CA1016" Action="Warning" />
|
||||
<Rule Id="CA1033" Action="Warning" />
|
||||
<Rule Id="CA1049" Action="Warning" />
|
||||
<Rule Id="CA1060" Action="Warning" />
|
||||
<Rule Id="CA1061" Action="Warning" />
|
||||
<Rule Id="CA1063" Action="Warning" />
|
||||
<Rule Id="CA1065" Action="Warning" />
|
||||
<Rule Id="CA1301" Action="Warning" />
|
||||
<Rule Id="CA1400" Action="Warning" />
|
||||
<Rule Id="CA1401" Action="Warning" />
|
||||
<Rule Id="CA1403" Action="Warning" />
|
||||
<Rule Id="CA1404" Action="Warning" />
|
||||
<Rule Id="CA1405" Action="Warning" />
|
||||
<Rule Id="CA1410" Action="Warning" />
|
||||
<Rule Id="CA1415" Action="Warning" />
|
||||
<Rule Id="CA1821" Action="Warning" />
|
||||
<Rule Id="CA1900" Action="Warning" />
|
||||
<Rule Id="CA1901" Action="Warning" />
|
||||
<Rule Id="CA2002" Action="Warning" />
|
||||
<Rule Id="CA2100" Action="Warning" />
|
||||
<Rule Id="CA2101" Action="Warning" />
|
||||
<Rule Id="CA2108" Action="Warning" />
|
||||
<Rule Id="CA2111" Action="Warning" />
|
||||
<Rule Id="CA2112" Action="Warning" />
|
||||
<Rule Id="CA2114" Action="Warning" />
|
||||
<Rule Id="CA2116" Action="Warning" />
|
||||
<Rule Id="CA2117" Action="Warning" />
|
||||
<Rule Id="CA2122" Action="Warning" />
|
||||
<Rule Id="CA2123" Action="Warning" />
|
||||
<Rule Id="CA2124" Action="Warning" />
|
||||
<Rule Id="CA2126" Action="Warning" />
|
||||
<Rule Id="CA2131" Action="Warning" />
|
||||
<Rule Id="CA2132" Action="Warning" />
|
||||
<Rule Id="CA2133" Action="Warning" />
|
||||
<Rule Id="CA2134" Action="Warning" />
|
||||
<Rule Id="CA2137" Action="Warning" />
|
||||
<Rule Id="CA2138" Action="Warning" />
|
||||
<Rule Id="CA2140" Action="Warning" />
|
||||
<Rule Id="CA2141" Action="Warning" />
|
||||
<Rule Id="CA2146" Action="Warning" />
|
||||
<Rule Id="CA2147" Action="Warning" />
|
||||
<Rule Id="CA2149" Action="Warning" />
|
||||
<Rule Id="CA2200" Action="Warning" />
|
||||
<Rule Id="CA2202" Action="Warning" />
|
||||
<Rule Id="CA2207" Action="Warning" />
|
||||
<Rule Id="CA2212" Action="Warning" />
|
||||
<Rule Id="CA2213" Action="Warning" />
|
||||
<Rule Id="CA2214" Action="Warning" />
|
||||
<Rule Id="CA2216" Action="Warning" />
|
||||
<Rule Id="CA2220" Action="Warning" />
|
||||
<Rule Id="CA2229" Action="Warning" />
|
||||
<Rule Id="CA2231" Action="Warning" />
|
||||
<Rule Id="CA2232" Action="Warning" />
|
||||
<Rule Id="CA2235" Action="Warning" />
|
||||
<Rule Id="CA2236" Action="Warning" />
|
||||
<Rule Id="CA2237" Action="Warning" />
|
||||
<Rule Id="CA2238" Action="Warning" />
|
||||
<Rule Id="CA2240" Action="Warning" />
|
||||
<Rule Id="CA2241" Action="Warning" />
|
||||
<Rule Id="CA2242" Action="Warning" />
|
||||
</Rules>
|
||||
<Rules AnalyzerId="Microsoft.CodeAnalysis.CSharp" RuleNamespace="Microsoft.CodeAnalysis.CSharp">
|
||||
<Rule Id="CS0618" Action="Info" />
|
||||
<Rule Id="CS8019" Action="Warning" />
|
||||
<Rule Id="CS8020" Action="Info" />
|
||||
</Rules>
|
||||
<Rules AnalyzerId="Microsoft.CodeQuality.Analyzers" RuleNamespace="Microsoft.CodeQuality.Analyzers">
|
||||
<Rule Id="CA1716" Action="Info" />
|
||||
<Rule Id="CA1724" Action="Info" />
|
||||
<Rule Id="CA2007" Action="Hidden" />
|
||||
<Rule Id="CA2227" Action="None" />
|
||||
</Rules>
|
||||
<Rules AnalyzerId="Microsoft.NetCore.Analyzers" RuleNamespace="Microsoft.NetCore.Analyzers">
|
||||
<Rule Id="CA1303" Action="None" />
|
||||
</Rules>
|
||||
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
|
||||
<Rule Id="SA0001" Action="None" />
|
||||
<Rule Id="SA1101" Action="None" />
|
||||
<Rule Id="SA1309" Action="None" />
|
||||
<Rule Id="SA1600" Action="None" />
|
||||
<Rule Id="SA1602" Action="None" />
|
||||
<Rule Id="SA1633" Action="None" />
|
||||
</Rules>
|
||||
</RuleSet>
|
||||
@@ -0,0 +1,31 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.11.35222.181
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Francesco.Recipes.World", "Francesco.Recipes.World\Francesco.Recipes.World.csproj", "{4D1BBCF4-8E06-4584-A383-B14BEC558408}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrancescosRecipeWorld Mock", "FrancescosRecipeWorld Mock\FrancescosRecipeWorld Mock.csproj", "{04FD3555-3497-4F6B-B2C3-1B22EEFA676B}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{4D1BBCF4-8E06-4584-A383-B14BEC558408}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4D1BBCF4-8E06-4584-A383-B14BEC558408}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4D1BBCF4-8E06-4584-A383-B14BEC558408}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4D1BBCF4-8E06-4584-A383-B14BEC558408}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{04FD3555-3497-4F6B-B2C3-1B22EEFA676B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{04FD3555-3497-4F6B-B2C3-1B22EEFA676B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{04FD3555-3497-4F6B-B2C3-1B22EEFA676B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{04FD3555-3497-4F6B-B2C3-1B22EEFA676B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {537F0DB8-C26E-4753-917D-0E9D6ED5FB3A}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Francesco.Recipes.World.Constants
|
||||
{
|
||||
public class ContentType
|
||||
{
|
||||
public const string Image = "image/";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Francesco.Recipes.World.Constants
|
||||
{
|
||||
public class SortOrders
|
||||
{
|
||||
public const string Newest = "newest";
|
||||
public const string Oldest = "oldest";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
namespace Francesco.Recipes.World.Controller.Category
|
||||
{
|
||||
using Francesco.Recipes.World.Repositories.Category;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
public class CategoryController : Controller
|
||||
{
|
||||
private readonly ICategoryRepository _categoryRepository;
|
||||
|
||||
public CategoryController(ICategoryRepository categoryRepository)
|
||||
{
|
||||
_categoryRepository = categoryRepository;
|
||||
}
|
||||
|
||||
// GET: /Category
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
var categories = await _categoryRepository.GetAllCategoriesAsync();
|
||||
return View(categories);
|
||||
}
|
||||
|
||||
// GET: /Category/{id}
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<IActionResult> Details(Guid id)
|
||||
{
|
||||
var category = await _categoryRepository.GetCategoryByIdAsync(id);
|
||||
return View(category);
|
||||
}
|
||||
|
||||
// GET: /Category/{id}/recipes
|
||||
[HttpGet("{id:guid}/recipes")]
|
||||
public async Task<IActionResult> GetRecipesByCategory(Guid id)
|
||||
{
|
||||
var recipes = await _categoryRepository.GetRecipesByCategoryAsync(id);
|
||||
return Ok(recipes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
namespace Francesco.Recipes.World.Controller.Favorite
|
||||
{
|
||||
using Francesco.Recipes.World.Constants;
|
||||
using Francesco.Recipes.World.Models;
|
||||
using Francesco.Recipes.World.Repositories.Favorit;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
public class FavoriteController : Controller
|
||||
{
|
||||
private readonly IFavoriteRepository _favoriteRepository;
|
||||
|
||||
public FavoriteController(IFavoriteRepository favoriteRepository)
|
||||
{
|
||||
_favoriteRepository = favoriteRepository;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Index(string sortOrder = SortOrders.Newest)
|
||||
{
|
||||
var favoriteRecipes = await _favoriteRepository.GetFavoriteRecipesAsync();
|
||||
|
||||
var sortedRecipes = sortOrder == SortOrders.Oldest
|
||||
? favoriteRecipes.OrderBy(r => r.Favorite.CreatedAt)
|
||||
: favoriteRecipes.OrderByDescending(r => r.Favorite.CreatedAt);
|
||||
|
||||
var viewModel = new FavoriteViewModel
|
||||
{
|
||||
FavoriteRecipes = sortedRecipes,
|
||||
SortOrder = sortOrder,
|
||||
};
|
||||
|
||||
return View(viewModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace Francesco.Recipes.World.Controller.HomeController
|
||||
{
|
||||
using Francesco.Recipes.World.Repositories.Category;
|
||||
using Francesco.Recipes.World.Repositories.Recipe;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
public class HomeController : Controller
|
||||
{
|
||||
private readonly IRecipeRepository _recipeRepository;
|
||||
private readonly ICategoryRepository _categoryRepository;
|
||||
|
||||
public HomeController(ICategoryRepository categoryRepository, IRecipeRepository recipeRepository)
|
||||
{
|
||||
_recipeRepository = recipeRepository;
|
||||
_categoryRepository = categoryRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
var categoriesWithRecipes = await _categoryRepository.GetAllCategoriesWithRecipesViewModelAsync();
|
||||
return View(categoriesWithRecipes);
|
||||
}
|
||||
|
||||
[HttpGet("/Home/Search")]
|
||||
public async Task<IActionResult> Search(string term)
|
||||
{
|
||||
var recipes = await _recipeRepository.SearchInRecipesAndIngredients(term);
|
||||
return PartialView("_SearchResultsPartial", recipes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Francesco.Recipes.World.Controller.Ingredient
|
||||
{
|
||||
public class IngredientController
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
namespace Francesco.Recipes.World.Controller.Instruction
|
||||
{
|
||||
using Francesco.Recipes.World.Models;
|
||||
using Francesco.Recipes.World.Repositories.Instruction;
|
||||
using Francesco.Recipes.World.Services.Instruction;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
public class InstructionController : Controller
|
||||
{
|
||||
private readonly IInstructionService _instructionService;
|
||||
private readonly IInstructionRepository _instructionRepository;
|
||||
|
||||
public InstructionController(IInstructionService instructionService, IInstructionRepository instructionRepository)
|
||||
{
|
||||
_instructionService = instructionService;
|
||||
_instructionRepository = instructionRepository;
|
||||
}
|
||||
|
||||
[HttpPost("Recipe/{recipeId}/Instruction/{instructionId}/move-up")]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> MoveUp(Guid recipeId, Guid instructionId)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _instructionService.MoveInstructionUpAsync(recipeId, instructionId);
|
||||
return Ok(new { Message = "Instruction moved up successfully." });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new { Error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("Recipe/{recipeId}/Instruction/{instructionId}/move-down")]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> MoveDown(Guid recipeId, Guid instructionId)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _instructionService.MoveInstructionDownAsync(recipeId, instructionId);
|
||||
return Ok(new { Message = "Instruction moved down successfully." });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new { Error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("Recipe/{recipeId}/Instructions")]
|
||||
public async Task<IActionResult> GetInstructions(Guid recipeId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sortedInstructions = await _instructionService.GetSortedInstructionsAsync(recipeId);
|
||||
var viewModel = new InstructionViewModel
|
||||
{
|
||||
RecipeId = recipeId,
|
||||
Instructions = sortedInstructions,
|
||||
};
|
||||
|
||||
return View("~/Views/Shared/_GetInstructions.cshtml", viewModel);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new { Error = ex.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
namespace Francesco.Recipes.World.Controller.MediaFile
|
||||
{
|
||||
using Francesco.Recipes.World.Repositories.MediaFile;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
[Route("Category/{categoryId}/Recipe")]
|
||||
public class MediaFileController : Controller
|
||||
{
|
||||
private readonly IMediaFileRepository _mediaFileRepository;
|
||||
|
||||
public MediaFileController(IMediaFileRepository mediaFileRepository)
|
||||
{
|
||||
_mediaFileRepository = mediaFileRepository;
|
||||
}
|
||||
|
||||
// POST: /UploadImage
|
||||
[HttpPost("UploadImage")]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> UploadImage(Guid recipeId, IFormFile? mediaFile)
|
||||
{
|
||||
if (mediaFile is null)
|
||||
{
|
||||
return BadRequest("Photo is required.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _mediaFileRepository.UploadRecipeMediaAsync(recipeId, mediaFile);
|
||||
return Ok("Image uploaded successfully.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, $"Internal server error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// GET: /UploadImage
|
||||
[HttpGet("UploadImage")]
|
||||
public IActionResult UploadImageView()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpPost("ReplaceInstructionImage")]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> ReplaceInstructionImage(Guid instructionId, Guid mediaFileIdToReplace, IFormFile? newPhoto)
|
||||
{
|
||||
if (newPhoto is null)
|
||||
{
|
||||
return BadRequest("Photo is required.");
|
||||
}
|
||||
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
await newPhoto.CopyToAsync(memoryStream);
|
||||
|
||||
var newMediaData = memoryStream.ToArray();
|
||||
|
||||
try
|
||||
{
|
||||
await _mediaFileRepository.ReplaceInstructionImageAsync(instructionId, mediaFileIdToReplace, newPhoto.FileName, newPhoto.ContentType, newMediaData);
|
||||
return Ok("Image replaced successfully.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, $"Internal server error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("Recipe/{recipeId}/Instruction/{instructionId}/UploadImage")]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> UploadInstructionImage(Guid recipeId, Guid instructionId, IFormFile? photo)
|
||||
{
|
||||
if (recipeId == Guid.Empty)
|
||||
{
|
||||
return BadRequest("Recipe ID is required.");
|
||||
}
|
||||
|
||||
if (photo == null)
|
||||
{
|
||||
return BadRequest("Photo is required.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _mediaFileRepository.UploadInstructionImageAsync(instructionId, photo);
|
||||
|
||||
return RedirectToAction("GetInstructions", "Instruction", new { recipeId });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, $"Internal server error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,523 @@
|
||||
namespace Francesco.Recipes.World.Controller.Recipe
|
||||
{
|
||||
using Francesco.Recipes.World.Data;
|
||||
using Francesco.Recipes.World.Models;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Ingredient;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Instruction;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Recipe;
|
||||
using Francesco.Recipes.World.Repositories.Category;
|
||||
using Francesco.Recipes.World.Repositories.Favorit;
|
||||
using Francesco.Recipes.World.Repositories.Ingredient;
|
||||
using Francesco.Recipes.World.Repositories.Instruction;
|
||||
using Francesco.Recipes.World.Repositories.MediaFile;
|
||||
using Francesco.Recipes.World.Repositories.Recipe;
|
||||
using Francesco.Recipes.World.Repositories.Unit;
|
||||
using Francesco.Recipes.World.Views.Recipe;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
|
||||
public class RecipeController : Controller
|
||||
{
|
||||
private readonly IRecipeRepository _recipeRepository;
|
||||
private readonly IUnitRepository _unitRepository;
|
||||
private readonly ICategoryRepository _categoryRepository;
|
||||
private readonly IIngredientRepository _ingredientRepository;
|
||||
private readonly IMediaFileRepository _mediaFileRepository;
|
||||
private readonly IInstructionRepository _instructionRepository;
|
||||
private readonly IFavoriteRepository _favoriteRepository;
|
||||
private readonly FrancescosRecipesWorldDbContext _context;
|
||||
|
||||
public IReadOnlyCollection<Recipe> Recipes { get; set; }
|
||||
|
||||
public RecipeController(
|
||||
IRecipeRepository recipeRepository,
|
||||
IUnitRepository unitRepository,
|
||||
ICategoryRepository categoryRepository,
|
||||
IIngredientRepository ingredientRepository,
|
||||
IMediaFileRepository mediaFileRepository,
|
||||
IInstructionRepository instructionRepository,
|
||||
IFavoriteRepository favoriteRepository,
|
||||
FrancescosRecipesWorldDbContext context)
|
||||
{
|
||||
_recipeRepository = recipeRepository;
|
||||
_unitRepository = unitRepository;
|
||||
_categoryRepository = categoryRepository;
|
||||
_ingredientRepository = ingredientRepository;
|
||||
Recipes = new List<Recipe>();
|
||||
_mediaFileRepository = mediaFileRepository;
|
||||
_instructionRepository = instructionRepository;
|
||||
_favoriteRepository = favoriteRepository;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
// GET: /Recipe/{recipeId}/AddOrCreateIngredient
|
||||
[HttpGet("{recipeId}/AddOrCreateIngredient")]
|
||||
public async Task<IActionResult> AddOrCreateIngredient(Guid recipeId)
|
||||
{
|
||||
if (recipeId == Guid.Empty)
|
||||
{
|
||||
return BadRequest("Recipe ID cannot be empty.");
|
||||
}
|
||||
|
||||
var units = await _unitRepository.GetAllUnitsAsync();
|
||||
var recipeIngredients = await _ingredientRepository.GetIngredientsByRecipeIdAsync(recipeId);
|
||||
var ingredients = recipeIngredients.Select(ri => ri.Ingredient).ToList();
|
||||
|
||||
var viewModel = new IngredientViewModel
|
||||
{
|
||||
RecipeId = recipeId,
|
||||
Ingredients = ingredients,
|
||||
Units = units.ToList(),
|
||||
};
|
||||
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
// GET: /Recipe/Details/{recipeId}
|
||||
[HttpGet("Details/{recipeId}")]
|
||||
public async Task<IActionResult> Details(Guid recipeId)
|
||||
{
|
||||
var recipe = await _recipeRepository.GetRecipeByIdAsync(recipeId);
|
||||
if (recipe == null)
|
||||
{
|
||||
return NotFound("Recipe not found.");
|
||||
}
|
||||
|
||||
return View(recipe);
|
||||
}
|
||||
|
||||
// GET: /Recipe/CategoryRecipes
|
||||
[HttpGet("CategoryRecipes")]
|
||||
public async Task<IActionResult> CategoryRecipes()
|
||||
{
|
||||
var categoryViewModels = await _categoryRepository.GetAllCategoriesWithRecipesAsync();
|
||||
return View(categoryViewModels);
|
||||
}
|
||||
|
||||
// POST: /Recipe/{recipeId}/AddOrCreateIngredient
|
||||
[HttpPost("{recipeId}/AddOrCreateIngredient")]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> AddOrCreateIngredient(Guid recipeId, string ingredientName, int quantity, Guid unitId)
|
||||
{
|
||||
if (recipeId == Guid.Empty)
|
||||
{
|
||||
return BadRequest("Recipe ID cannot be empty.");
|
||||
}
|
||||
|
||||
if (quantity <= 0)
|
||||
{
|
||||
ModelState.AddModelError(nameof(quantity), "Die Menge muss größer als 0 sein.");
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
var units = await _unitRepository.GetAllUnitsAsync();
|
||||
ViewBag.Units = new SelectList(units, "Id", "Name");
|
||||
return View();
|
||||
}
|
||||
|
||||
await _recipeRepository.CreateRecipeIngredientAsync(recipeId, ingredientName, quantity, unitId);
|
||||
return RedirectToAction("Details", new { id = recipeId });
|
||||
}
|
||||
|
||||
// GET: /Recipe/Create/{categoryId}
|
||||
[HttpGet("Create/{categoryId}")]
|
||||
public async Task<IActionResult> Create(Guid categoryId)
|
||||
{
|
||||
var category = await _categoryRepository.GetCategoryByIdAsync(categoryId);
|
||||
if (category == null)
|
||||
{
|
||||
return NotFound($"Kategorie mit ID {categoryId} wurde nicht gefunden.");
|
||||
}
|
||||
|
||||
var units = await _unitRepository.GetAllUnitsAsync();
|
||||
|
||||
var viewModel = new CreateRecipeViewModel
|
||||
{
|
||||
CategoryId = categoryId,
|
||||
CategoryName = category.Name,
|
||||
IngredientViewModel = new IngredientViewModel
|
||||
{
|
||||
RecipeId = Guid.Empty,
|
||||
Ingredients = new List<Ingredient>(),
|
||||
Units = units.ToList(),
|
||||
},
|
||||
InstructionViewModel = new InstructionViewModel
|
||||
{
|
||||
RecipeId = Guid.Empty,
|
||||
Instructions = new List<Instruction>(),
|
||||
},
|
||||
};
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
// POST: /Recipe/Create/{categoryId}
|
||||
[HttpPost("Create/{categoryId}")]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Create(Guid categoryId, CreateRecipeViewModel model)
|
||||
{
|
||||
if (model == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(model), "CreateRecipeViewModel cannot be null.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(model.Name))
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.Name), "Name darf nicht leer sein.");
|
||||
}
|
||||
|
||||
if (model.Servings <= 0)
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.Servings), "Anzahl der Portionen muss größer als 0 sein.");
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
var category = await _categoryRepository.GetCategoryByIdAsync(categoryId);
|
||||
if (category == null)
|
||||
{
|
||||
return NotFound($"Kategorie mit ID {categoryId} wurde nicht gefunden.");
|
||||
}
|
||||
|
||||
var units = await _unitRepository.GetAllUnitsAsync();
|
||||
|
||||
if (model.IngredientViewModel == null)
|
||||
{
|
||||
model.IngredientViewModel = new IngredientViewModel
|
||||
{
|
||||
RecipeId = Guid.Empty,
|
||||
Ingredients = new List<Ingredient>(),
|
||||
Units = units.ToList(),
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
model.IngredientViewModel.Units = units.ToList();
|
||||
}
|
||||
|
||||
model.CategoryName = category.Name;
|
||||
return View(model);
|
||||
}
|
||||
|
||||
using var transaction = await _context.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
var categoryEntity = await _categoryRepository.GetCategoryByIdAsync(categoryId);
|
||||
if (categoryEntity == null)
|
||||
{
|
||||
return NotFound($"Kategorie mit ID {categoryId} wurde nicht gefunden.");
|
||||
}
|
||||
|
||||
model.PreparationTime = new TimeSpan(model.PrepHours, model.PrepMinutes, 0);
|
||||
model.CookingTime = new TimeSpan(model.CookHours, model.CookMinutes, 0);
|
||||
|
||||
var recipe = await _recipeRepository.CreateRecipeForCategoryAsync(
|
||||
categoryEntity,
|
||||
model.Name,
|
||||
model.Description ?? string.Empty,
|
||||
model.Difficulty,
|
||||
model.Servings,
|
||||
model.PreparationTime,
|
||||
model.CookingTime);
|
||||
|
||||
if (model.Photo != null)
|
||||
{
|
||||
await _mediaFileRepository.UploadRecipeMediaAsync(recipe.Id, model.Photo);
|
||||
}
|
||||
|
||||
if (model.Video != null)
|
||||
{
|
||||
await _mediaFileRepository.UploadRecipeMediaAsync(recipe.Id, model.Video);
|
||||
}
|
||||
|
||||
if (model.IngredientViewModel?.Ingredients != null)
|
||||
{
|
||||
foreach (var ingredient in model.IngredientViewModel.Ingredients)
|
||||
{
|
||||
var ri = ingredient.RecipeIngredients?.FirstOrDefault();
|
||||
if (!string.IsNullOrWhiteSpace(ingredient.Name) && ri?.Quantity > 0 && ri?.Unit?.Id != null)
|
||||
{
|
||||
await _recipeRepository.CreateRecipeIngredientAsync(
|
||||
recipe.Id,
|
||||
ingredient.Name,
|
||||
ri.Quantity,
|
||||
ri.Unit.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (model.InstructionViewModel?.Instructions != null)
|
||||
{
|
||||
for (var i = 0; i < model.InstructionViewModel.Instructions.Count; i++)
|
||||
{
|
||||
var instruction = model.InstructionViewModel.Instructions[i];
|
||||
if (!string.IsNullOrWhiteSpace(instruction.Description))
|
||||
{
|
||||
var fileKey = $"InstructionViewModel.Instructions[{i}].MediaFile";
|
||||
IFormFile? imageFile = null;
|
||||
|
||||
if (Request.Form.Files.Any(f => f.Name == fileKey))
|
||||
{
|
||||
imageFile = Request.Form.Files[fileKey];
|
||||
}
|
||||
|
||||
await _instructionRepository.CreateInstructionAsync(
|
||||
recipe.Id,
|
||||
instruction.Description,
|
||||
imageFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await transaction.CommitAsync();
|
||||
return RedirectToAction("Details", new { recipeId = recipe.Id });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await transaction.RollbackAsync();
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
// GET: /Recipe/{recipeId}/RemoveIngredient/{ingredientId}
|
||||
[HttpGet("{recipeId}/RemoveIngredient/{ingredientId}")]
|
||||
public async Task<IActionResult> RemoveIngredient(Guid recipeId, Guid ingredientId)
|
||||
{
|
||||
var recipe = await _recipeRepository.GetRecipeAsync(recipeId);
|
||||
var ingredient = await _ingredientRepository.GetIngredientByIdAsync(ingredientId);
|
||||
|
||||
if (recipe == null || ingredient == null)
|
||||
{
|
||||
return NotFound("Recipe or Ingredient not found.");
|
||||
}
|
||||
|
||||
ViewBag.RecipeId = recipeId;
|
||||
ViewBag.IngredientId = ingredientId;
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
// DELETE: /Recipe/{recipeId}/RemoveIngredient/{ingredientId}
|
||||
[HttpDelete("{recipeId}/RemoveIngredient/{ingredientId}")]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> RemoveIngredientConfirmed(Guid recipeId, Guid ingredientId)
|
||||
{
|
||||
await _recipeRepository.RemoveIngredientFromRecipeAsync(recipeId, ingredientId);
|
||||
TempData["SuccessMessage"] = "Ingredient removed successfully.";
|
||||
return RedirectToAction("Details", new { id = recipeId });
|
||||
}
|
||||
|
||||
// GET: /Recipe/{recipeId}/RemoveInstruction/{instructionId}
|
||||
[HttpGet("{recipeId}/RemoveInstruction/{instructionId}")]
|
||||
public async Task<IActionResult> RemoveInstruction(Guid recipeId, Guid instructionId)
|
||||
{
|
||||
var recipe = await _recipeRepository.GetRecipeAsync(recipeId);
|
||||
|
||||
if (recipe == null)
|
||||
{
|
||||
return NotFound("Recipe not found.");
|
||||
}
|
||||
|
||||
var instruction = recipe.Instructions?.FirstOrDefault(i => i.Id == instructionId);
|
||||
if (instruction == null)
|
||||
{
|
||||
return NotFound("Instruction not found in the specified recipe.");
|
||||
}
|
||||
|
||||
ViewBag.RecipeId = recipeId;
|
||||
ViewBag.InstructionId = instructionId;
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
// DELETE: /Recipe/{recipeId}/RemoveInstruction/{instructionId}
|
||||
[HttpDelete("{recipeId}/RemoveInstruction/{instructionId}")]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> RemoveInstructionConfirmed(Guid recipeId, Guid instructionId)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _instructionRepository.RemoveInstructionFromRecipeAsync(recipeId, instructionId);
|
||||
TempData["SuccessMessage"] = "Instruction removed successfully.";
|
||||
return RedirectToAction("Details", new { recipeId });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TempData["ErrorMessage"] = $"An error occurred while removing the instruction: {ex.Message}";
|
||||
return RedirectToAction("Details", new { recipeId });
|
||||
}
|
||||
}
|
||||
|
||||
// GET: /Recipe/FilterByDifficulty
|
||||
[HttpGet("FilterByDifficulty")]
|
||||
public async Task<IActionResult> FilterByDifficulty(Difficulty? selectedDifficulty)
|
||||
{
|
||||
var recipes = await _recipeRepository.GetRecipesByDifficultyAsync(selectedDifficulty);
|
||||
var viewModel = new FilterByDifficultyViewModel
|
||||
{
|
||||
SelectedDifficulty = selectedDifficulty,
|
||||
Recipes = recipes,
|
||||
};
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
// GET: /Recipe/{recipeId}/AddInstruction
|
||||
[HttpGet("{recipeId}/AddInstruction")]
|
||||
public async Task<IActionResult> AddInstruction(Guid recipeId)
|
||||
{
|
||||
var recipe = await _recipeRepository.GetRecipeByIdAsync(recipeId);
|
||||
if (recipe == null)
|
||||
{
|
||||
return NotFound("Recipe not found.");
|
||||
}
|
||||
|
||||
var instructions = await _instructionRepository.GetInstructionsOfRecipeAsync(recipeId);
|
||||
|
||||
var viewModel = new InstructionViewModel
|
||||
{
|
||||
RecipeId = recipeId,
|
||||
Instructions = instructions,
|
||||
};
|
||||
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
// POST: /Recipe/{recipeId}/AddInstruction
|
||||
[HttpPost("{recipeId}/AddInstruction")]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> AddInstruction(Guid recipeId, string description, IFormFile? image)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _instructionRepository.CreateInstructionAsync(recipeId, description, image);
|
||||
|
||||
var recipe = await _recipeRepository.GetRecipeByIdAsync(recipeId);
|
||||
var instructions = recipe?.Instructions?.ToList() ?? new List<Instruction>();
|
||||
|
||||
var viewModel = new InstructionViewModel
|
||||
{
|
||||
RecipeId = recipeId,
|
||||
Instructions = instructions,
|
||||
};
|
||||
|
||||
return RedirectToAction("AddInstruction", new { recipeId });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
// GET: /Recipe/Favorites
|
||||
[HttpGet("Favorites")]
|
||||
public async Task<IActionResult> Favorites()
|
||||
{
|
||||
var favoriteRecipes = await _favoriteRepository.GetFavoriteRecipesAsync();
|
||||
return View(favoriteRecipes);
|
||||
}
|
||||
|
||||
[HttpPost("AddFavorite")]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> AddFavorite(Guid recipeId)
|
||||
{
|
||||
await _favoriteRepository.AddFavoriteAsync(recipeId);
|
||||
|
||||
// We only need to know the Id and IsFavorite status
|
||||
var viewModel = new FavoritButtonViewModel
|
||||
{
|
||||
Id = recipeId,
|
||||
IsFavorite = true,
|
||||
};
|
||||
|
||||
return PartialView("_FavoriteButton", viewModel);
|
||||
}
|
||||
|
||||
[HttpPost("RemoveFavorite")]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> RemoveFavorite(Guid recipeId)
|
||||
{
|
||||
await _favoriteRepository.RemoveFavoriteAsync(recipeId);
|
||||
|
||||
var viewModel = new FavoritButtonViewModel
|
||||
{
|
||||
Id = recipeId,
|
||||
IsFavorite = false,
|
||||
};
|
||||
|
||||
return PartialView("_FavoriteButton", viewModel);
|
||||
}
|
||||
|
||||
// GET: /Recipe/{recipeId}/GetIngredients
|
||||
[HttpGet("{recipeId}/GetIngredients")]
|
||||
public async Task<IActionResult> GetIngredients(Guid recipeId)
|
||||
{
|
||||
var recipeIngredients = (await _ingredientRepository.GetIngredientsByRecipeIdAsync(recipeId)).Select(ri => ri.Ingredient).ToList();
|
||||
var units = await _unitRepository.GetAllUnitsAsync();
|
||||
|
||||
var viewModel = new IngredientViewModel
|
||||
{
|
||||
RecipeId = recipeId,
|
||||
Ingredients = recipeIngredients,
|
||||
Units = units,
|
||||
};
|
||||
|
||||
return PartialView("_IngredientsPartial", viewModel);
|
||||
}
|
||||
|
||||
// GET: /Recipe/{recipeId}/AdjustableIngredients
|
||||
[HttpGet("{recipeId}/AdjustableIngredients")]
|
||||
public async Task<IActionResult> GetAdjustableIngredients(Guid recipeId)
|
||||
{
|
||||
var recipe = await _recipeRepository.GetRecipeByIdAsync(recipeId);
|
||||
if (recipe == null)
|
||||
{
|
||||
return NotFound("Recipe not found.");
|
||||
}
|
||||
|
||||
return PartialView("_AdjustableIngredientsPartial", recipe);
|
||||
}
|
||||
|
||||
// GET: /Recipe/{recipeId}/Delete
|
||||
[HttpGet("{recipeId}/Delete")]
|
||||
public async Task<IActionResult> Delete(Guid recipeId)
|
||||
{
|
||||
var recipe = await _recipeRepository.GetRecipeByIdAsync(recipeId);
|
||||
if (recipe == null)
|
||||
{
|
||||
return NotFound("Recipe not found.");
|
||||
}
|
||||
|
||||
return View(recipe);
|
||||
}
|
||||
|
||||
[HttpDelete("{recipeId}/Delete")]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> DeleteConfirmed(Guid recipeId)
|
||||
{
|
||||
var deleted = await _recipeRepository.DeleteRecipeAsync(recipeId);
|
||||
|
||||
if (deleted)
|
||||
{
|
||||
if (Request.Headers.ContainsKey("HX-Request"))
|
||||
{
|
||||
Response.Headers["HX-Redirect"] = Url.Action("Index", "Home");
|
||||
return Ok();
|
||||
}
|
||||
|
||||
return RedirectToAction("Index", "Home");
|
||||
}
|
||||
else
|
||||
{
|
||||
TempData["ErrorMessage"] = "Rezept nicht gefunden oder konnte nicht gelöscht werden.";
|
||||
|
||||
if (Request.Headers.ContainsKey("HX-Request"))
|
||||
{
|
||||
Response.Headers["HX-Redirect"] = Url.Action("Details", new { recipeId });
|
||||
return Ok();
|
||||
}
|
||||
|
||||
return RedirectToAction("Details", new { recipeId });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
namespace Francesco.Recipes.World.Controllers
|
||||
{
|
||||
using Francesco.Recipes.World.Data;
|
||||
using Francesco.Recipes.World.Models;
|
||||
using Francesco.Recipes.World.Repositories.ShoppingList;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
[Route("ShoppingList")]
|
||||
public class ShoppingListController : Controller
|
||||
{
|
||||
private readonly IShoppingListRepository _shoppingListRepository;
|
||||
private readonly FrancescosRecipesWorldDbContext _context;
|
||||
|
||||
public ShoppingListController(IShoppingListRepository shoppingListRepository, FrancescosRecipesWorldDbContext context)
|
||||
{
|
||||
_shoppingListRepository = shoppingListRepository;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
[HttpPost("CreateOrAddIngredients")]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> CreateOrAddIngredients([FromBody] CreateOrAddIngredientRequestModel request)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
if (request == null || request.IngredientIds == null || !request.IngredientIds.Any())
|
||||
{
|
||||
return BadRequest("No ingredients provided.");
|
||||
}
|
||||
|
||||
// Compute duplicates for this recipe in the current shopping list
|
||||
var existingShoppingList = await _context.ShoppingLists
|
||||
.Include(sl => sl.RecipeShoppingList)
|
||||
.ThenInclude(rsl => rsl.SelectedIngredients)
|
||||
.ThenInclude(si => si.RecipeIngredient)
|
||||
.Include(sl => sl.RecipeShoppingList)
|
||||
.ThenInclude(rsl => rsl.Recipe)
|
||||
.FirstOrDefaultAsync(sl => sl.RecipeShoppingList.Any(rsl => rsl.Recipe.Id == request.RecipeId));
|
||||
|
||||
var existingForRecipe = existingShoppingList?
|
||||
.RecipeShoppingList.FirstOrDefault(rsl => rsl.Recipe.Id == request.RecipeId);
|
||||
|
||||
var existingIds = existingForRecipe?.SelectedIngredients
|
||||
.Where(si => si.RecipeIngredient != null)
|
||||
.Select(si => si.RecipeIngredient.Id)
|
||||
.ToHashSet() ?? new HashSet<Guid>();
|
||||
|
||||
var skippedExisting = request.IngredientIds.Where(id => existingIds.Contains(id)).Distinct().ToList();
|
||||
var ingredientIdsToAdd = request.IngredientIds.Except(skippedExisting).Distinct().ToList();
|
||||
|
||||
if (ingredientIdsToAdd.Count == 0)
|
||||
{
|
||||
return Json(new
|
||||
{
|
||||
shoppingListId = existingShoppingList?.Id,
|
||||
skippedExisting,
|
||||
addedCount = 0,
|
||||
skippedCount = skippedExisting.Count,
|
||||
noAdditions = true,
|
||||
});
|
||||
}
|
||||
|
||||
var shoppingList = await _shoppingListRepository.AddIngredientsToShoppingListAsync(request.RecipeId, ingredientIdsToAdd);
|
||||
|
||||
if (shoppingList == null)
|
||||
{
|
||||
return BadRequest("Error creating shopping list.");
|
||||
}
|
||||
|
||||
return Json(new
|
||||
{
|
||||
shoppingListId = shoppingList.Id,
|
||||
skippedExisting,
|
||||
addedCount = ingredientIdsToAdd.Count,
|
||||
skippedCount = skippedExisting.Count,
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("RecipeCount")]
|
||||
public async Task<IActionResult> RecipeCount()
|
||||
{
|
||||
var count = await _shoppingListRepository.CountAllRecipeShoppinglistsAsync();
|
||||
return Json(new { count });
|
||||
}
|
||||
|
||||
// GET: /ShoppingList/Details
|
||||
[HttpGet("Details")]
|
||||
public async Task<IActionResult> Details()
|
||||
{
|
||||
var recipeCount = await _shoppingListRepository.CountAllRecipeShoppinglistsAsync();
|
||||
|
||||
var recipeIngredientToShoppingListMap = new Dictionary<Guid, Guid>();
|
||||
var allEntries = await _context.RecipeIngredientsShoppingLists
|
||||
.Where(risl => risl.RecipeIngredient != null)
|
||||
.Select(risl => new
|
||||
{
|
||||
RecipeIngredientId = risl.RecipeIngredient.Id,
|
||||
ShoppingListId = risl.Id,
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var entry in allEntries)
|
||||
{
|
||||
if (entry.RecipeIngredientId != Guid.Empty &&
|
||||
!recipeIngredientToShoppingListMap.ContainsKey(entry.RecipeIngredientId))
|
||||
{
|
||||
recipeIngredientToShoppingListMap.Add(entry.RecipeIngredientId, entry.ShoppingListId);
|
||||
}
|
||||
}
|
||||
|
||||
var recipeInAnyShoppingList = (await _shoppingListRepository.GetAllShoppingListsAsync())
|
||||
.SelectMany(sl => sl.RecipeShoppingList.Select(rsl => rsl.Recipe))
|
||||
.ToList();
|
||||
|
||||
var viewModel = new ShoppingListDetailsViewModel
|
||||
{
|
||||
RecipeCount = recipeCount,
|
||||
RecipesInAnyShoppingList = recipeInAnyShoppingList,
|
||||
RecipeIngredientToShoppingListMap = recipeIngredientToShoppingListMap,
|
||||
};
|
||||
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
[HttpDelete("RemoveIngredients")]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> RemoveIngredients([FromBody] List<Guid> recipeIngredientShoppingListIds)
|
||||
{
|
||||
if (recipeIngredientShoppingListIds == null || !recipeIngredientShoppingListIds.Any())
|
||||
{
|
||||
return BadRequest("Keine Zutaten zum Entfernen angegeben.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var affectedRecipeIds = await _context.RecipeIngredientsShoppingLists
|
||||
.Where(risl => recipeIngredientShoppingListIds.Contains(risl.Id))
|
||||
.Select(risl => risl.RecipeShoppingList.Recipe.Id)
|
||||
.Distinct()
|
||||
.ToListAsync();
|
||||
|
||||
await _shoppingListRepository.RemoveIngredientsFromShoppingListAsync(recipeIngredientShoppingListIds);
|
||||
|
||||
var remainingRecipeIds = await _context.RecipeShoppingLists
|
||||
.Select(rsl => rsl.Recipe.Id)
|
||||
.ToListAsync();
|
||||
|
||||
var removedRecipeIds = affectedRecipeIds
|
||||
.Where(id => !remainingRecipeIds.Contains(id))
|
||||
.ToList();
|
||||
|
||||
return Json(new { success = true, removedRecipeIds });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("RemoveRecipeFromList/{recipeId}")]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> RemoveRecipeFromList(Guid recipeId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var recipeShoppingLists = await _context.RecipeShoppingLists
|
||||
.Where(rsl => rsl.Recipe.Id == recipeId)
|
||||
.ToListAsync();
|
||||
|
||||
if (!recipeShoppingLists.Any())
|
||||
{
|
||||
return NotFound($"Kein Einkaufslisten-Eintrag für Rezept mit ID {recipeId} gefunden.");
|
||||
}
|
||||
|
||||
foreach (var recipeShoppingList in recipeShoppingLists)
|
||||
{
|
||||
await _shoppingListRepository.RemoveRecipeFromShoppingListAsync(recipeShoppingList.Id);
|
||||
}
|
||||
|
||||
return Ok(new { success = true });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new { success = false, error = ex.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace Francesco.Recipes.World.Controller.Unit
|
||||
{
|
||||
using Francesco.Recipes.World.Repositories.Unit;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
[Route("Unit")]
|
||||
public class UnitController : Controller
|
||||
{
|
||||
private readonly IUnitRepository _unitRepository;
|
||||
|
||||
public UnitController(IUnitRepository unitRepository)
|
||||
{
|
||||
_unitRepository = unitRepository;
|
||||
}
|
||||
|
||||
[HttpGet("GetAllUnits")]
|
||||
public async Task<IActionResult> GetAllUnits()
|
||||
{
|
||||
var units = (await _unitRepository.GetAllUnitsAsync())
|
||||
.Select(u => new { id = u.Id, name = u.Name })
|
||||
.ToList();
|
||||
return Json(units);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
namespace Francesco.Recipes.World.Data;
|
||||
|
||||
using Francesco.Recipes.World.Models.BackendModels;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Category;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Favorit;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Ingredient;
|
||||
using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Instruction;
|
||||
using Francesco.Recipes.World.Models.BackendModels.MediaFile;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Recipe;
|
||||
using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient;
|
||||
using Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Shoppinglist;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Unit;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
public class FrancescosRecipesWorldDbContext : DbContext
|
||||
{
|
||||
public FrancescosRecipesWorldDbContext(DbContextOptions<FrancescosRecipesWorldDbContext> options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
public DbSet<Category> Categories => Set<Category>();
|
||||
|
||||
public DbSet<Ingredient> Ingredients => Set<Ingredient>();
|
||||
|
||||
public DbSet<Instruction> Instructions => Set<Instruction>();
|
||||
|
||||
public DbSet<Recipe> Recipes => Set<Recipe>();
|
||||
|
||||
public DbSet<RecipeIngredient> RecipeIngredients => Set<RecipeIngredient>();
|
||||
|
||||
public DbSet<Unit> Units => Set<Unit>();
|
||||
|
||||
public DbSet<Favorit> Favorits => Set<Favorit>();
|
||||
|
||||
public DbSet<RecipeIngredientShoppingList> RecipeIngredientsShoppingLists => Set<RecipeIngredientShoppingList>();
|
||||
|
||||
public DbSet<RecipeShoppingList> RecipeShoppingLists => Set<RecipeShoppingList>();
|
||||
|
||||
public DbSet<ShoppingList> ShoppingLists => Set<ShoppingList>();
|
||||
|
||||
public DbSet<MediaFile> MediaFiles => Set<MediaFile>();
|
||||
|
||||
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
foreach (var entry in ChangeTracker.Entries())
|
||||
{
|
||||
if (entry.Entity is ITimeStampedEntity timeStampedEntity)
|
||||
{
|
||||
switch (entry.State)
|
||||
{
|
||||
case EntityState.Added:
|
||||
timeStampedEntity.CreatedAt = DateTime.UtcNow;
|
||||
break;
|
||||
case EntityState.Modified:
|
||||
timeStampedEntity.ModifiedAt = DateTime.UtcNow;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return base.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(builder);
|
||||
|
||||
SeedData(builder);
|
||||
|
||||
base.OnModelCreating(builder);
|
||||
}
|
||||
|
||||
private static void SeedData(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Category>()
|
||||
.HasData(GetCategories());
|
||||
|
||||
modelBuilder.Entity<Unit>()
|
||||
.HasData(GetUnits());
|
||||
}
|
||||
|
||||
private static IEnumerable<Category> GetCategories()
|
||||
{
|
||||
return
|
||||
[
|
||||
new Category { Id = Guid.Parse("b248244f-f21c-4555-a14d-5dd49a2717cf"), Name = "Vorspeisen & Snacks" },
|
||||
new Category { Id = Guid.Parse("5186c5d6-5aff-4a6e-baf4-b61b72d889fe"), Name = "Erste Gänge" },
|
||||
new Category { Id = Guid.Parse("abbc6bb0-97ea-49f5-b31e-6507eb784fd1"), Name = "Hauptgerichte" },
|
||||
new Category { Id = Guid.Parse("90deec39-dcd0-422d-9018-ac8389e332e1"), Name = "Desserts & Süßspeisen" },
|
||||
new Category { Id = Guid.Parse("28e39168-701a-4084-81da-d96c987c462f"), Name = "Beilagen & Salate" },
|
||||
new Category { Id = Guid.Parse("20585b74-4805-4aff-a6df-aa6b7af04ff1"), Name = "Kuchen" },
|
||||
new Category { Id = Guid.Parse("adfb75ce-3ef3-428b-a5ca-b0c4c619d5ec"), Name = "Hefegebäck & Brot" },
|
||||
new Category { Id = Guid.Parse("d332f88d-d241-48c5-a2f2-bfd124eada7e"), Name = "Soßen & Saucen" },
|
||||
new Category { Id = Guid.Parse("23b1c740-e427-44f9-a6ea-d33d3f30f05a"), Name = "Marmeladen & Eingemachtes" },
|
||||
new Category { Id = Guid.Parse("0a91a200-dc76-4e00-b38c-b38cab5b69d7"), Name = "Getränke" },
|
||||
];
|
||||
}
|
||||
|
||||
private static IEnumerable<Unit> GetUnits()
|
||||
{
|
||||
return
|
||||
[
|
||||
new Unit { Id = Guid.Parse("62eb2c11-8768-46fb-9db0-c77f940bb4aa"), Name = "liter", Symbol = "l" },
|
||||
new Unit { Id = Guid.Parse("6c41f7e6-ca75-49cc-8541-cebd5a9c560b"), Name = "gramm", Symbol = "g" },
|
||||
new Unit { Id = Guid.Parse("7e3d1b86-ac48-45d6-814e-d3492a86db1d"), Name = "kilogramm", Symbol = "kg" },
|
||||
new Unit { Id = Guid.Parse("0c3648ec-4981-42c8-abf8-18bf1a2ff4c2"), Name = "stücke", Symbol = "stk" },
|
||||
new Unit { Id = Guid.Parse("d82f4abd-e4e4-4104-a9c0-1acdeaa701f5"), Name = "blatt", Symbol = "blatt" },
|
||||
new Unit { Id = Guid.Parse("df5cb4c3-4de6-4c6f-be8c-da41b2986408"), Name = "messerspitze", Symbol = "msp" },
|
||||
new Unit { Id = Guid.Parse("e45a3af2-2ed6-4ac4-b06f-b4175663a7be"), Name = "stange", Symbol = "stange" },
|
||||
new Unit { Id = Guid.Parse("7ea2f51d-7493-4f19-a663-1f309186d3ae"), Name = "bund", Symbol = "bund" },
|
||||
new Unit { Id = Guid.Parse("66556e0e-eb2b-4bc3-9a56-135dd508ed09"), Name = "zehe", Symbol = "zehe" },
|
||||
new Unit { Id = Guid.Parse("2e9894ac-14fa-43fd-ba07-35cdd8ebd461"), Name = "teelöffel", Symbol = "TL" },
|
||||
new Unit { Id = Guid.Parse("24a91b89-3389-4465-89e4-2f70d2ea6fd7"), Name = "esslöffel", Symbol = "EL" },
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UserSecretsId>be6a70eb-b657-40b2-b4a1-418e5c6ec131</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<CodeAnalysisRuleSet>$(SolutionDir)Francesco.Recipes.World.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.11" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.11" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.11" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.11" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.11">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.7" />
|
||||
<PackageReference Include="SecurityCodeScan.VS2019" Version="5.6.7">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Sentry.Serilog" Version="4.13.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
|
||||
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.4" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include=".\stylecop.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Services\Category\" />
|
||||
<Folder Include="Services\MediaFile\" />
|
||||
<Folder Include="Services\Ingredient\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
+513
@@ -0,0 +1,513 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Francesco.Recipes.World.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Francesco.Recipes.World.Migrations
|
||||
{
|
||||
[DbContext(typeof(FrancescosRecipesWorldDbContext))]
|
||||
[Migration("20250318150949_InitialMIgration")]
|
||||
partial class InitialMIgration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.11")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Categories");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = new Guid("b248244f-f21c-4555-a14d-5dd49a2717cf"),
|
||||
Name = "Vorspeisen & Snacks"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("5186c5d6-5aff-4a6e-baf4-b61b72d889fe"),
|
||||
Name = "Erste Gänge"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("abbc6bb0-97ea-49f5-b31e-6507eb784fd1"),
|
||||
Name = "Hauptgerichte"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("90deec39-dcd0-422d-9018-ac8389e332e1"),
|
||||
Name = "Desserts & Süßspeisen"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("28e39168-701a-4084-81da-d96c987c462f"),
|
||||
Name = "Beilagen & Salate"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20585b74-4805-4aff-a6df-aa6b7af04ff1"),
|
||||
Name = "Kuchen"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("adfb75ce-3ef3-428b-a5ca-b0c4c619d5ec"),
|
||||
Name = "Hefegebäck & Brot"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("d332f88d-d241-48c5-a2f2-bfd124eada7e"),
|
||||
Name = "Soßen & Saucen"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("23b1c740-e427-44f9-a6ea-d33d3f30f05a"),
|
||||
Name = "Marmeladen & Eingemachtes"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("0a91a200-dc76-4e00-b38c-b38cab5b69d7"),
|
||||
Name = "Getränke"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Favorits");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Ingredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.IngredientsShoppingList", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("IngredientId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("ShoppinglistId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IngredientId");
|
||||
|
||||
b.HasIndex("ShoppinglistId");
|
||||
|
||||
b.ToTable("IngredientsShoppingLists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Number")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<Guid>("RecipeId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipeId");
|
||||
|
||||
b.ToTable("Instructions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<byte[]>("Data")
|
||||
.HasColumnType("varbinary(max)");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<Guid>("InstructionId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("MimeType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<Guid?>("RecipeId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InstructionId");
|
||||
|
||||
b.HasIndex("RecipeId");
|
||||
|
||||
b.ToTable("MediaFiles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid?>("CategoryId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<TimeSpan>("CookingTime")
|
||||
.HasColumnType("time");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("Difficulty")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("FavoritId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("IsFavorite")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("ModifiedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<TimeSpan>("PreparationTime")
|
||||
.HasColumnType("time");
|
||||
|
||||
b.Property<int>("Servings")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CategoryId");
|
||||
|
||||
b.HasIndex("FavoritId");
|
||||
|
||||
b.ToTable("Recipes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("IngredientId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int>("Quantity")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("RecipeId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("UnitId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IngredientId");
|
||||
|
||||
b.HasIndex("RecipeId");
|
||||
|
||||
b.HasIndex("UnitId");
|
||||
|
||||
b.ToTable("RecipeIngredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime?>("ModifiedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ShoppingLists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Symbol")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Units");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = new Guid("62eb2c11-8768-46fb-9db0-c77f940bb4aa"),
|
||||
Name = "liter",
|
||||
Symbol = "l"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("6c41f7e6-ca75-49cc-8541-cebd5a9c560b"),
|
||||
Name = "gramm",
|
||||
Symbol = "g"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("7e3d1b86-ac48-45d6-814e-d3492a86db1d"),
|
||||
Name = "kilogramm",
|
||||
Symbol = "kg"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("0c3648ec-4981-42c8-abf8-18bf1a2ff4c2"),
|
||||
Name = "stücke",
|
||||
Symbol = "stk"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("d82f4abd-e4e4-4104-a9c0-1acdeaa701f5"),
|
||||
Name = "blatt",
|
||||
Symbol = "blatt"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("df5cb4c3-4de6-4c6f-be8c-da41b2986408"),
|
||||
Name = "messerspitze",
|
||||
Symbol = "msp"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("e45a3af2-2ed6-4ac4-b06f-b4175663a7be"),
|
||||
Name = "stange",
|
||||
Symbol = "stange"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("7ea2f51d-7493-4f19-a663-1f309186d3ae"),
|
||||
Name = "bund",
|
||||
Symbol = "bund"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("66556e0e-eb2b-4bc3-9a56-135dd508ed09"),
|
||||
Name = "zehe",
|
||||
Symbol = "zehe"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("2e9894ac-14fa-43fd-ba07-35cdd8ebd461"),
|
||||
Name = "teelöffel",
|
||||
Symbol = "TL"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("24a91b89-3389-4465-89e4-2f70d2ea6fd7"),
|
||||
Name = "esslöffel",
|
||||
Symbol = "EL"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.IngredientsShoppingList", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", "Ingredient")
|
||||
.WithMany("IngredientShoppingLists")
|
||||
.HasForeignKey("IngredientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "Shoppinglist")
|
||||
.WithMany("IngredientsShoppingLists")
|
||||
.HasForeignKey("ShoppinglistId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Ingredient");
|
||||
|
||||
b.Navigation("Shoppinglist");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
|
||||
.WithMany("Instructions")
|
||||
.HasForeignKey("RecipeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Recipe");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", "Instruction")
|
||||
.WithMany("MediaFiles")
|
||||
.HasForeignKey("InstructionId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
|
||||
.WithMany("MediaFiles")
|
||||
.HasForeignKey("RecipeId");
|
||||
|
||||
b.Navigation("Instruction");
|
||||
|
||||
b.Navigation("Recipe");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Category.Category", null)
|
||||
.WithMany("Recipes")
|
||||
.HasForeignKey("CategoryId");
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", "Favorit")
|
||||
.WithMany("Recipe")
|
||||
.HasForeignKey("FavoritId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Favorit");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", "Ingredient")
|
||||
.WithMany("RecipeIngredients")
|
||||
.HasForeignKey("IngredientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
|
||||
.WithMany("RecipeIngredients")
|
||||
.HasForeignKey("RecipeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", "Unit")
|
||||
.WithMany("RecipeIngredient")
|
||||
.HasForeignKey("UnitId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Ingredient");
|
||||
|
||||
b.Navigation("Recipe");
|
||||
|
||||
b.Navigation("Unit");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b =>
|
||||
{
|
||||
b.Navigation("Recipes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b =>
|
||||
{
|
||||
b.Navigation("Recipe");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b =>
|
||||
{
|
||||
b.Navigation("IngredientShoppingLists");
|
||||
|
||||
b.Navigation("RecipeIngredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
|
||||
{
|
||||
b.Navigation("MediaFiles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
|
||||
{
|
||||
b.Navigation("Instructions");
|
||||
|
||||
b.Navigation("MediaFiles");
|
||||
|
||||
b.Navigation("RecipeIngredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b =>
|
||||
{
|
||||
b.Navigation("IngredientsShoppingLists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b =>
|
||||
{
|
||||
b.Navigation("RecipeIngredient");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
|
||||
|
||||
namespace Francesco.Recipes.World.Migrations
|
||||
{
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
/// <inheritdoc />
|
||||
public partial class InitialMIgration : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
if (migrationBuilder is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(migrationBuilder));
|
||||
}
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Categories",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Categories", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Favorits",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Favorits", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Ingredients",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Ingredients", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ShoppingLists",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
ModifiedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ShoppingLists", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Units",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
Symbol = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Units", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Recipes",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
Description = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
Difficulty = table.Column<int>(type: "int", nullable: false),
|
||||
Servings = table.Column<int>(type: "int", nullable: false),
|
||||
PreparationTime = table.Column<TimeSpan>(type: "time", nullable: false),
|
||||
CookingTime = table.Column<TimeSpan>(type: "time", nullable: false),
|
||||
IsFavorite = table.Column<bool>(type: "bit", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
ModifiedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
FavoritId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
CategoryId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Recipes", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Recipes_Categories_CategoryId",
|
||||
column: x => x.CategoryId,
|
||||
principalTable: "Categories",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_Recipes_Favorits_FavoritId",
|
||||
column: x => x.FavoritId,
|
||||
principalTable: "Favorits",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "IngredientsShoppingLists",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
ShoppinglistId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
IngredientId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_IngredientsShoppingLists", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_IngredientsShoppingLists_Ingredients_IngredientId",
|
||||
column: x => x.IngredientId,
|
||||
principalTable: "Ingredients",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_IngredientsShoppingLists_ShoppingLists_ShoppinglistId",
|
||||
column: x => x.ShoppinglistId,
|
||||
principalTable: "ShoppingLists",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Instructions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
Description = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
Number = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
RecipeId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Instructions", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Instructions_Recipes_RecipeId",
|
||||
column: x => x.RecipeId,
|
||||
principalTable: "Recipes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "RecipeIngredients",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
RecipeId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
IngredientId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
UnitId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
Quantity = table.Column<int>(type: "int", nullable: false),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_RecipeIngredients", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_RecipeIngredients_Ingredients_IngredientId",
|
||||
column: x => x.IngredientId,
|
||||
principalTable: "Ingredients",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_RecipeIngredients_Recipes_RecipeId",
|
||||
column: x => x.RecipeId,
|
||||
principalTable: "Recipes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_RecipeIngredients_Units_UnitId",
|
||||
column: x => x.UnitId,
|
||||
principalTable: "Units",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "MediaFiles",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
FileName = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
MimeType = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
Data = table.Column<byte[]>(type: "varbinary(max)", nullable: true),
|
||||
RecipeId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
InstructionId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_MediaFiles", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_MediaFiles_Instructions_InstructionId",
|
||||
column: x => x.InstructionId,
|
||||
principalTable: "Instructions",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_MediaFiles_Recipes_RecipeId",
|
||||
column: x => x.RecipeId,
|
||||
principalTable: "Recipes",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "Categories",
|
||||
columns: new[] { "Id", "Name" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("0a91a200-dc76-4e00-b38c-b38cab5b69d7"), "Getränke" },
|
||||
{ new Guid("20585b74-4805-4aff-a6df-aa6b7af04ff1"), "Kuchen" },
|
||||
{ new Guid("23b1c740-e427-44f9-a6ea-d33d3f30f05a"), "Marmeladen & Eingemachtes" },
|
||||
{ new Guid("28e39168-701a-4084-81da-d96c987c462f"), "Beilagen & Salate" },
|
||||
{ new Guid("5186c5d6-5aff-4a6e-baf4-b61b72d889fe"), "Erste Gänge" },
|
||||
{ new Guid("90deec39-dcd0-422d-9018-ac8389e332e1"), "Desserts & Süßspeisen" },
|
||||
{ new Guid("abbc6bb0-97ea-49f5-b31e-6507eb784fd1"), "Hauptgerichte" },
|
||||
{ new Guid("adfb75ce-3ef3-428b-a5ca-b0c4c619d5ec"), "Hefegebäck & Brot" },
|
||||
{ new Guid("b248244f-f21c-4555-a14d-5dd49a2717cf"), "Vorspeisen & Snacks" },
|
||||
{ new Guid("d332f88d-d241-48c5-a2f2-bfd124eada7e"), "Soßen & Saucen" },
|
||||
});
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "Units",
|
||||
columns: new[] { "Id", "Name", "Symbol" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("0c3648ec-4981-42c8-abf8-18bf1a2ff4c2"), "stücke", "stk" },
|
||||
{ new Guid("24a91b89-3389-4465-89e4-2f70d2ea6fd7"), "esslöffel", "EL" },
|
||||
{ new Guid("2e9894ac-14fa-43fd-ba07-35cdd8ebd461"), "teelöffel", "TL" },
|
||||
{ new Guid("62eb2c11-8768-46fb-9db0-c77f940bb4aa"), "liter", "l" },
|
||||
{ new Guid("66556e0e-eb2b-4bc3-9a56-135dd508ed09"), "zehe", "zehe" },
|
||||
{ new Guid("6c41f7e6-ca75-49cc-8541-cebd5a9c560b"), "gramm", "g" },
|
||||
{ new Guid("7e3d1b86-ac48-45d6-814e-d3492a86db1d"), "kilogramm", "kg" },
|
||||
{ new Guid("7ea2f51d-7493-4f19-a663-1f309186d3ae"), "bund", "bund" },
|
||||
{ new Guid("d82f4abd-e4e4-4104-a9c0-1acdeaa701f5"), "blatt", "blatt" },
|
||||
{ new Guid("df5cb4c3-4de6-4c6f-be8c-da41b2986408"), "messerspitze", "msp" },
|
||||
{ new Guid("e45a3af2-2ed6-4ac4-b06f-b4175663a7be"), "stange", "stange" },
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_IngredientsShoppingLists_IngredientId",
|
||||
table: "IngredientsShoppingLists",
|
||||
column: "IngredientId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_IngredientsShoppingLists_ShoppinglistId",
|
||||
table: "IngredientsShoppingLists",
|
||||
column: "ShoppinglistId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Instructions_RecipeId",
|
||||
table: "Instructions",
|
||||
column: "RecipeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MediaFiles_InstructionId",
|
||||
table: "MediaFiles",
|
||||
column: "InstructionId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MediaFiles_RecipeId",
|
||||
table: "MediaFiles",
|
||||
column: "RecipeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RecipeIngredients_IngredientId",
|
||||
table: "RecipeIngredients",
|
||||
column: "IngredientId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RecipeIngredients_RecipeId",
|
||||
table: "RecipeIngredients",
|
||||
column: "RecipeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RecipeIngredients_UnitId",
|
||||
table: "RecipeIngredients",
|
||||
column: "UnitId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Recipes_CategoryId",
|
||||
table: "Recipes",
|
||||
column: "CategoryId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Recipes_FavoritId",
|
||||
table: "Recipes",
|
||||
column: "FavoritId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
if (migrationBuilder is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(migrationBuilder));
|
||||
}
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "IngredientsShoppingLists");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "MediaFiles");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "RecipeIngredients");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ShoppingLists");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Instructions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Ingredients");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Units");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Recipes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Categories");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Favorits");
|
||||
}
|
||||
}
|
||||
}
|
||||
+518
@@ -0,0 +1,518 @@
|
||||
// <auto-generated />
|
||||
using Francesco.Recipes.World.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Francesco.Recipes.World.Migrations
|
||||
{
|
||||
[DbContext(typeof(FrancescosRecipesWorldDbContext))]
|
||||
[Migration("20250324124459_CreateNewTableRecipeIngredientShoppinglist")]
|
||||
partial class CreateNewTableRecipeIngredientShoppinglist
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.11")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Categories");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = new Guid("b248244f-f21c-4555-a14d-5dd49a2717cf"),
|
||||
Name = "Vorspeisen & Snacks"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("5186c5d6-5aff-4a6e-baf4-b61b72d889fe"),
|
||||
Name = "Erste Gänge"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("abbc6bb0-97ea-49f5-b31e-6507eb784fd1"),
|
||||
Name = "Hauptgerichte"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("90deec39-dcd0-422d-9018-ac8389e332e1"),
|
||||
Name = "Desserts & Süßspeisen"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("28e39168-701a-4084-81da-d96c987c462f"),
|
||||
Name = "Beilagen & Salate"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20585b74-4805-4aff-a6df-aa6b7af04ff1"),
|
||||
Name = "Kuchen"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("adfb75ce-3ef3-428b-a5ca-b0c4c619d5ec"),
|
||||
Name = "Hefegebäck & Brot"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("d332f88d-d241-48c5-a2f2-bfd124eada7e"),
|
||||
Name = "Soßen & Saucen"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("23b1c740-e427-44f9-a6ea-d33d3f30f05a"),
|
||||
Name = "Marmeladen & Eingemachtes"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("0a91a200-dc76-4e00-b38c-b38cab5b69d7"),
|
||||
Name = "Getränke"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Favorits");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Ingredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid?>("IngredientId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("RecipeIngredientId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("ShoppingListId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IngredientId");
|
||||
|
||||
b.HasIndex("RecipeIngredientId");
|
||||
|
||||
b.HasIndex("ShoppingListId");
|
||||
|
||||
b.ToTable("RecipeIngredientsShoppingLists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("Number")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("RecipeId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipeId");
|
||||
|
||||
b.ToTable("Instructions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<byte[]>("Data")
|
||||
.HasColumnType("varbinary(max)");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<Guid>("InstructionId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("MimeType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<Guid?>("RecipeId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InstructionId");
|
||||
|
||||
b.HasIndex("RecipeId");
|
||||
|
||||
b.ToTable("MediaFiles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid?>("CategoryId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<TimeSpan>("CookingTime")
|
||||
.HasColumnType("time");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("Difficulty")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("FavoritId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("IsFavorite")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("ModifiedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<TimeSpan>("PreparationTime")
|
||||
.HasColumnType("time");
|
||||
|
||||
b.Property<int>("Servings")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CategoryId");
|
||||
|
||||
b.HasIndex("FavoritId");
|
||||
|
||||
b.ToTable("Recipes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("IngredientId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int>("Quantity")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("RecipeId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("UnitId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IngredientId");
|
||||
|
||||
b.HasIndex("RecipeId");
|
||||
|
||||
b.HasIndex("UnitId");
|
||||
|
||||
b.ToTable("RecipeIngredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime?>("ModifiedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ShoppingLists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Symbol")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Units");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = new Guid("62eb2c11-8768-46fb-9db0-c77f940bb4aa"),
|
||||
Name = "liter",
|
||||
Symbol = "l"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("6c41f7e6-ca75-49cc-8541-cebd5a9c560b"),
|
||||
Name = "gramm",
|
||||
Symbol = "g"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("7e3d1b86-ac48-45d6-814e-d3492a86db1d"),
|
||||
Name = "kilogramm",
|
||||
Symbol = "kg"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("0c3648ec-4981-42c8-abf8-18bf1a2ff4c2"),
|
||||
Name = "stücke",
|
||||
Symbol = "stk"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("d82f4abd-e4e4-4104-a9c0-1acdeaa701f5"),
|
||||
Name = "blatt",
|
||||
Symbol = "blatt"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("df5cb4c3-4de6-4c6f-be8c-da41b2986408"),
|
||||
Name = "messerspitze",
|
||||
Symbol = "msp"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("e45a3af2-2ed6-4ac4-b06f-b4175663a7be"),
|
||||
Name = "stange",
|
||||
Symbol = "stange"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("7ea2f51d-7493-4f19-a663-1f309186d3ae"),
|
||||
Name = "bund",
|
||||
Symbol = "bund"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("66556e0e-eb2b-4bc3-9a56-135dd508ed09"),
|
||||
Name = "zehe",
|
||||
Symbol = "zehe"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("2e9894ac-14fa-43fd-ba07-35cdd8ebd461"),
|
||||
Name = "teelöffel",
|
||||
Symbol = "TL"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("24a91b89-3389-4465-89e4-2f70d2ea6fd7"),
|
||||
Name = "esslöffel",
|
||||
Symbol = "EL"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", null)
|
||||
.WithMany("IngredientShoppingLists")
|
||||
.HasForeignKey("IngredientId");
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", "RecipeIngredient")
|
||||
.WithMany()
|
||||
.HasForeignKey("RecipeIngredientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "ShoppingList")
|
||||
.WithMany("RecipeIngredientShoppingLists")
|
||||
.HasForeignKey("ShoppingListId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("RecipeIngredient");
|
||||
|
||||
b.Navigation("ShoppingList");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
|
||||
.WithMany("Instructions")
|
||||
.HasForeignKey("RecipeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Recipe");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", "Instruction")
|
||||
.WithMany("MediaFiles")
|
||||
.HasForeignKey("InstructionId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
|
||||
.WithMany("MediaFiles")
|
||||
.HasForeignKey("RecipeId");
|
||||
|
||||
b.Navigation("Instruction");
|
||||
|
||||
b.Navigation("Recipe");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Category.Category", null)
|
||||
.WithMany("Recipes")
|
||||
.HasForeignKey("CategoryId");
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", "Favorit")
|
||||
.WithMany("Recipe")
|
||||
.HasForeignKey("FavoritId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Favorit");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", "Ingredient")
|
||||
.WithMany("RecipeIngredients")
|
||||
.HasForeignKey("IngredientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
|
||||
.WithMany("RecipeIngredients")
|
||||
.HasForeignKey("RecipeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", "Unit")
|
||||
.WithMany("RecipeIngredient")
|
||||
.HasForeignKey("UnitId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Ingredient");
|
||||
|
||||
b.Navigation("Recipe");
|
||||
|
||||
b.Navigation("Unit");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b =>
|
||||
{
|
||||
b.Navigation("Recipes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b =>
|
||||
{
|
||||
b.Navigation("Recipe");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b =>
|
||||
{
|
||||
b.Navigation("IngredientShoppingLists");
|
||||
|
||||
b.Navigation("RecipeIngredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
|
||||
{
|
||||
b.Navigation("MediaFiles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
|
||||
{
|
||||
b.Navigation("Instructions");
|
||||
|
||||
b.Navigation("MediaFiles");
|
||||
|
||||
b.Navigation("RecipeIngredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b =>
|
||||
{
|
||||
b.Navigation("RecipeIngredientShoppingLists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b =>
|
||||
{
|
||||
b.Navigation("RecipeIngredient");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Francesco.Recipes.World.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class CreateNewTableRecipeIngredientShoppinglist : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
if (migrationBuilder is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(migrationBuilder));
|
||||
}
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "IngredientsShoppingLists");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "Number",
|
||||
table: "Instructions",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "nvarchar(max)");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "RecipeIngredientsShoppingLists",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
ShoppingListId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
RecipeIngredientId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
IngredientId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_RecipeIngredientsShoppingLists", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_RecipeIngredientsShoppingLists_Ingredients_IngredientId",
|
||||
column: x => x.IngredientId,
|
||||
principalTable: "Ingredients",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_RecipeIngredientsShoppingLists_RecipeIngredients_RecipeIngredientId",
|
||||
column: x => x.RecipeIngredientId,
|
||||
principalTable: "RecipeIngredients",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_RecipeIngredientsShoppingLists_ShoppingLists_ShoppingListId",
|
||||
column: x => x.ShoppingListId,
|
||||
principalTable: "ShoppingLists",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RecipeIngredientsShoppingLists_IngredientId",
|
||||
table: "RecipeIngredientsShoppingLists",
|
||||
column: "IngredientId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RecipeIngredientsShoppingLists_RecipeIngredientId",
|
||||
table: "RecipeIngredientsShoppingLists",
|
||||
column: "RecipeIngredientId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RecipeIngredientsShoppingLists_ShoppingListId",
|
||||
table: "RecipeIngredientsShoppingLists",
|
||||
column: "ShoppingListId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
if (migrationBuilder is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(migrationBuilder));
|
||||
}
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "RecipeIngredientsShoppingLists");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Number",
|
||||
table: "Instructions",
|
||||
type: "nvarchar(max)",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "IngredientsShoppingLists",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
IngredientId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
ShoppinglistId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_IngredientsShoppingLists", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_IngredientsShoppingLists_Ingredients_IngredientId",
|
||||
column: x => x.IngredientId,
|
||||
principalTable: "Ingredients",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_IngredientsShoppingLists_ShoppingLists_ShoppinglistId",
|
||||
column: x => x.ShoppinglistId,
|
||||
principalTable: "ShoppingLists",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_IngredientsShoppingLists_IngredientId",
|
||||
table: "IngredientsShoppingLists",
|
||||
column: "IngredientId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_IngredientsShoppingLists_ShoppinglistId",
|
||||
table: "IngredientsShoppingLists",
|
||||
column: "ShoppinglistId");
|
||||
}
|
||||
}
|
||||
}
|
||||
+572
@@ -0,0 +1,572 @@
|
||||
// <auto-generated />
|
||||
|
||||
using Francesco.Recipes.World.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Francesco.Recipes.World.Migrations
|
||||
{
|
||||
[DbContext(typeof(FrancescosRecipesWorldDbContext))]
|
||||
[Migration("20250402120842_UpdateShoppingLIstLogic")]
|
||||
partial class UpdateShoppingLIstLogic
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.11")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Categories");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = new Guid("b248244f-f21c-4555-a14d-5dd49a2717cf"),
|
||||
Name = "Vorspeisen & Snacks"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("5186c5d6-5aff-4a6e-baf4-b61b72d889fe"),
|
||||
Name = "Erste Gänge"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("abbc6bb0-97ea-49f5-b31e-6507eb784fd1"),
|
||||
Name = "Hauptgerichte"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("90deec39-dcd0-422d-9018-ac8389e332e1"),
|
||||
Name = "Desserts & Süßspeisen"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("28e39168-701a-4084-81da-d96c987c462f"),
|
||||
Name = "Beilagen & Salate"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20585b74-4805-4aff-a6df-aa6b7af04ff1"),
|
||||
Name = "Kuchen"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("adfb75ce-3ef3-428b-a5ca-b0c4c619d5ec"),
|
||||
Name = "Hefegebäck & Brot"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("d332f88d-d241-48c5-a2f2-bfd124eada7e"),
|
||||
Name = "Soßen & Saucen"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("23b1c740-e427-44f9-a6ea-d33d3f30f05a"),
|
||||
Name = "Marmeladen & Eingemachtes"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("0a91a200-dc76-4e00-b38c-b38cab5b69d7"),
|
||||
Name = "Getränke"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Favorits");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Ingredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid?>("IngredientId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("IsChecked")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<Guid>("RecipeIngredientId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("RecipeShoppingListId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IngredientId");
|
||||
|
||||
b.HasIndex("RecipeIngredientId");
|
||||
|
||||
b.HasIndex("RecipeShoppingListId");
|
||||
|
||||
b.ToTable("RecipeIngredientsShoppingLists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("Number")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("RecipeId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipeId");
|
||||
|
||||
b.ToTable("Instructions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<byte[]>("Data")
|
||||
.HasColumnType("varbinary(max)");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<Guid>("InstructionId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("MimeType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<Guid?>("RecipeId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InstructionId");
|
||||
|
||||
b.HasIndex("RecipeId");
|
||||
|
||||
b.ToTable("MediaFiles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("CategoryId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<TimeSpan>("CookingTime")
|
||||
.HasColumnType("time");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("Difficulty")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("FavoritId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("IsFavorite")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("ModifiedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<TimeSpan>("PreparationTime")
|
||||
.HasColumnType("time");
|
||||
|
||||
b.Property<int>("Servings")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CategoryId");
|
||||
|
||||
b.HasIndex("FavoritId");
|
||||
|
||||
b.ToTable("Recipes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("IngredientId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int>("Quantity")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("RecipeId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("UnitId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IngredientId");
|
||||
|
||||
b.HasIndex("RecipeId");
|
||||
|
||||
b.HasIndex("UnitId");
|
||||
|
||||
b.ToTable("RecipeIngredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("RecipeId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("ShoppingListId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipeId");
|
||||
|
||||
b.HasIndex("ShoppingListId");
|
||||
|
||||
b.ToTable("RecipeShoppingLists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime?>("ModifiedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ShoppingLists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Symbol")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Units");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = new Guid("62eb2c11-8768-46fb-9db0-c77f940bb4aa"),
|
||||
Name = "liter",
|
||||
Symbol = "l"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("6c41f7e6-ca75-49cc-8541-cebd5a9c560b"),
|
||||
Name = "gramm",
|
||||
Symbol = "g"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("7e3d1b86-ac48-45d6-814e-d3492a86db1d"),
|
||||
Name = "kilogramm",
|
||||
Symbol = "kg"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("0c3648ec-4981-42c8-abf8-18bf1a2ff4c2"),
|
||||
Name = "stücke",
|
||||
Symbol = "stk"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("d82f4abd-e4e4-4104-a9c0-1acdeaa701f5"),
|
||||
Name = "blatt",
|
||||
Symbol = "blatt"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("df5cb4c3-4de6-4c6f-be8c-da41b2986408"),
|
||||
Name = "messerspitze",
|
||||
Symbol = "msp"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("e45a3af2-2ed6-4ac4-b06f-b4175663a7be"),
|
||||
Name = "stange",
|
||||
Symbol = "stange"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("7ea2f51d-7493-4f19-a663-1f309186d3ae"),
|
||||
Name = "bund",
|
||||
Symbol = "bund"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("66556e0e-eb2b-4bc3-9a56-135dd508ed09"),
|
||||
Name = "zehe",
|
||||
Symbol = "zehe"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("2e9894ac-14fa-43fd-ba07-35cdd8ebd461"),
|
||||
Name = "teelöffel",
|
||||
Symbol = "TL"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("24a91b89-3389-4465-89e4-2f70d2ea6fd7"),
|
||||
Name = "esslöffel",
|
||||
Symbol = "EL"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", null)
|
||||
.WithMany("IngredientShoppingLists")
|
||||
.HasForeignKey("IngredientId");
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", "RecipeIngredient")
|
||||
.WithMany()
|
||||
.HasForeignKey("RecipeIngredientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", "RecipeShoppingList")
|
||||
.WithMany("SelectedIngredients")
|
||||
.HasForeignKey("RecipeShoppingListId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("RecipeIngredient");
|
||||
|
||||
b.Navigation("RecipeShoppingList");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
|
||||
.WithMany("Instructions")
|
||||
.HasForeignKey("RecipeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Recipe");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", "Instruction")
|
||||
.WithMany("MediaFiles")
|
||||
.HasForeignKey("InstructionId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
|
||||
.WithMany("MediaFiles")
|
||||
.HasForeignKey("RecipeId");
|
||||
|
||||
b.Navigation("Instruction");
|
||||
|
||||
b.Navigation("Recipe");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Category.Category", "Category")
|
||||
.WithMany("Recipes")
|
||||
.HasForeignKey("CategoryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", "Favorit")
|
||||
.WithMany("Recipe")
|
||||
.HasForeignKey("FavoritId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Category");
|
||||
|
||||
b.Navigation("Favorit");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", "Ingredient")
|
||||
.WithMany("RecipeIngredients")
|
||||
.HasForeignKey("IngredientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
|
||||
.WithMany("RecipeIngredients")
|
||||
.HasForeignKey("RecipeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", "Unit")
|
||||
.WithMany("RecipeIngredient")
|
||||
.HasForeignKey("UnitId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Ingredient");
|
||||
|
||||
b.Navigation("Recipe");
|
||||
|
||||
b.Navigation("Unit");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
|
||||
.WithMany()
|
||||
.HasForeignKey("RecipeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "ShoppingList")
|
||||
.WithMany("RecipeIngredientShoppingLists")
|
||||
.HasForeignKey("ShoppingListId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Recipe");
|
||||
|
||||
b.Navigation("ShoppingList");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b =>
|
||||
{
|
||||
b.Navigation("Recipes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b =>
|
||||
{
|
||||
b.Navigation("Recipe");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b =>
|
||||
{
|
||||
b.Navigation("IngredientShoppingLists");
|
||||
|
||||
b.Navigation("RecipeIngredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
|
||||
{
|
||||
b.Navigation("MediaFiles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
|
||||
{
|
||||
b.Navigation("Instructions");
|
||||
|
||||
b.Navigation("MediaFiles");
|
||||
|
||||
b.Navigation("RecipeIngredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b =>
|
||||
{
|
||||
b.Navigation("SelectedIngredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b =>
|
||||
{
|
||||
b.Navigation("RecipeIngredientShoppingLists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b =>
|
||||
{
|
||||
b.Navigation("RecipeIngredient");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Francesco.Recipes.World.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class UpdateShoppingLIstLogic : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
if (migrationBuilder is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(migrationBuilder));
|
||||
}
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_RecipeIngredientsShoppingLists_ShoppingLists_ShoppingListId",
|
||||
table: "RecipeIngredientsShoppingLists");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Recipes_Categories_CategoryId",
|
||||
table: "Recipes");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "ShoppingListId",
|
||||
table: "RecipeIngredientsShoppingLists",
|
||||
newName: "RecipeShoppingListId");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_RecipeIngredientsShoppingLists_ShoppingListId",
|
||||
table: "RecipeIngredientsShoppingLists",
|
||||
newName: "IX_RecipeIngredientsShoppingLists_RecipeShoppingListId");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "CategoryId",
|
||||
table: "Recipes",
|
||||
type: "uniqueidentifier",
|
||||
nullable: false,
|
||||
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uniqueidentifier",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsChecked",
|
||||
table: "RecipeIngredientsShoppingLists",
|
||||
type: "bit",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "RecipeShoppingLists",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
ShoppingListId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
RecipeId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_RecipeShoppingLists", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_RecipeShoppingLists_Recipes_RecipeId",
|
||||
column: x => x.RecipeId,
|
||||
principalTable: "Recipes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_RecipeShoppingLists_ShoppingLists_ShoppingListId",
|
||||
column: x => x.ShoppingListId,
|
||||
principalTable: "ShoppingLists",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RecipeShoppingLists_RecipeId",
|
||||
table: "RecipeShoppingLists",
|
||||
column: "RecipeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RecipeShoppingLists_ShoppingListId",
|
||||
table: "RecipeShoppingLists",
|
||||
column: "ShoppingListId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_RecipeIngredientsShoppingLists_RecipeShoppingLists_RecipeShoppingListId",
|
||||
table: "RecipeIngredientsShoppingLists",
|
||||
column: "RecipeShoppingListId",
|
||||
principalTable: "RecipeShoppingLists",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.NoAction);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Recipes_Categories_CategoryId",
|
||||
table: "Recipes",
|
||||
column: "CategoryId",
|
||||
principalTable: "Categories",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
if (migrationBuilder is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(migrationBuilder));
|
||||
}
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_RecipeIngredientsShoppingLists_RecipeShoppingLists_RecipeShoppingListId",
|
||||
table: "RecipeIngredientsShoppingLists");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Recipes_Categories_CategoryId",
|
||||
table: "Recipes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "RecipeShoppingLists");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsChecked",
|
||||
table: "RecipeIngredientsShoppingLists");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "RecipeShoppingListId",
|
||||
table: "RecipeIngredientsShoppingLists",
|
||||
newName: "ShoppingListId");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_RecipeIngredientsShoppingLists_RecipeShoppingListId",
|
||||
table: "RecipeIngredientsShoppingLists",
|
||||
newName: "IX_RecipeIngredientsShoppingLists_ShoppingListId");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "CategoryId",
|
||||
table: "Recipes",
|
||||
type: "uniqueidentifier",
|
||||
nullable: true,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uniqueidentifier");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_RecipeIngredientsShoppingLists_ShoppingLists_ShoppingListId",
|
||||
table: "RecipeIngredientsShoppingLists",
|
||||
column: "ShoppingListId",
|
||||
principalTable: "ShoppingLists",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Recipes_Categories_CategoryId",
|
||||
table: "Recipes",
|
||||
column: "CategoryId",
|
||||
principalTable: "Categories",
|
||||
principalColumn: "Id");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+571
@@ -0,0 +1,571 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Francesco.Recipes.World.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Francesco.Recipes.World.Migrations
|
||||
{
|
||||
[DbContext(typeof(FrancescosRecipesWorldDbContext))]
|
||||
[Migration("20250430094852_MakeInstructionOptionalInMediaFile")]
|
||||
partial class MakeInstructionOptionalInMediaFile
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.11")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Categories");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = new Guid("b248244f-f21c-4555-a14d-5dd49a2717cf"),
|
||||
Name = "Vorspeisen & Snacks"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("5186c5d6-5aff-4a6e-baf4-b61b72d889fe"),
|
||||
Name = "Erste Gänge"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("abbc6bb0-97ea-49f5-b31e-6507eb784fd1"),
|
||||
Name = "Hauptgerichte"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("90deec39-dcd0-422d-9018-ac8389e332e1"),
|
||||
Name = "Desserts & Süßspeisen"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("28e39168-701a-4084-81da-d96c987c462f"),
|
||||
Name = "Beilagen & Salate"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20585b74-4805-4aff-a6df-aa6b7af04ff1"),
|
||||
Name = "Kuchen"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("adfb75ce-3ef3-428b-a5ca-b0c4c619d5ec"),
|
||||
Name = "Hefegebäck & Brot"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("d332f88d-d241-48c5-a2f2-bfd124eada7e"),
|
||||
Name = "Soßen & Saucen"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("23b1c740-e427-44f9-a6ea-d33d3f30f05a"),
|
||||
Name = "Marmeladen & Eingemachtes"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("0a91a200-dc76-4e00-b38c-b38cab5b69d7"),
|
||||
Name = "Getränke"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Favorits");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Ingredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid?>("IngredientId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("IsChecked")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<Guid>("RecipeIngredientId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("RecipeShoppingListId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IngredientId");
|
||||
|
||||
b.HasIndex("RecipeIngredientId");
|
||||
|
||||
b.HasIndex("RecipeShoppingListId");
|
||||
|
||||
b.ToTable("RecipeIngredientsShoppingLists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("Number")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("RecipeId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipeId");
|
||||
|
||||
b.ToTable("Instructions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<byte[]>("Data")
|
||||
.HasColumnType("varbinary(max)");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<Guid?>("InstructionId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("MimeType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<Guid?>("RecipeId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InstructionId");
|
||||
|
||||
b.HasIndex("RecipeId");
|
||||
|
||||
b.ToTable("MediaFiles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("CategoryId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<TimeSpan>("CookingTime")
|
||||
.HasColumnType("time");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("Difficulty")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("FavoritId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("IsFavorite")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("ModifiedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<TimeSpan>("PreparationTime")
|
||||
.HasColumnType("time");
|
||||
|
||||
b.Property<int>("Servings")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CategoryId");
|
||||
|
||||
b.HasIndex("FavoritId");
|
||||
|
||||
b.ToTable("Recipes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("IngredientId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int>("Quantity")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("RecipeId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("UnitId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IngredientId");
|
||||
|
||||
b.HasIndex("RecipeId");
|
||||
|
||||
b.HasIndex("UnitId");
|
||||
|
||||
b.ToTable("RecipeIngredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("RecipeId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("ShoppingListId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipeId");
|
||||
|
||||
b.HasIndex("ShoppingListId");
|
||||
|
||||
b.ToTable("RecipeShoppingLists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime?>("ModifiedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ShoppingLists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Symbol")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Units");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = new Guid("62eb2c11-8768-46fb-9db0-c77f940bb4aa"),
|
||||
Name = "liter",
|
||||
Symbol = "l"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("6c41f7e6-ca75-49cc-8541-cebd5a9c560b"),
|
||||
Name = "gramm",
|
||||
Symbol = "g"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("7e3d1b86-ac48-45d6-814e-d3492a86db1d"),
|
||||
Name = "kilogramm",
|
||||
Symbol = "kg"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("0c3648ec-4981-42c8-abf8-18bf1a2ff4c2"),
|
||||
Name = "stücke",
|
||||
Symbol = "stk"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("d82f4abd-e4e4-4104-a9c0-1acdeaa701f5"),
|
||||
Name = "blatt",
|
||||
Symbol = "blatt"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("df5cb4c3-4de6-4c6f-be8c-da41b2986408"),
|
||||
Name = "messerspitze",
|
||||
Symbol = "msp"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("e45a3af2-2ed6-4ac4-b06f-b4175663a7be"),
|
||||
Name = "stange",
|
||||
Symbol = "stange"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("7ea2f51d-7493-4f19-a663-1f309186d3ae"),
|
||||
Name = "bund",
|
||||
Symbol = "bund"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("66556e0e-eb2b-4bc3-9a56-135dd508ed09"),
|
||||
Name = "zehe",
|
||||
Symbol = "zehe"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("2e9894ac-14fa-43fd-ba07-35cdd8ebd461"),
|
||||
Name = "teelöffel",
|
||||
Symbol = "TL"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("24a91b89-3389-4465-89e4-2f70d2ea6fd7"),
|
||||
Name = "esslöffel",
|
||||
Symbol = "EL"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", null)
|
||||
.WithMany("IngredientShoppingLists")
|
||||
.HasForeignKey("IngredientId");
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", "RecipeIngredient")
|
||||
.WithMany()
|
||||
.HasForeignKey("RecipeIngredientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", "RecipeShoppingList")
|
||||
.WithMany("SelectedIngredients")
|
||||
.HasForeignKey("RecipeShoppingListId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("RecipeIngredient");
|
||||
|
||||
b.Navigation("RecipeShoppingList");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
|
||||
.WithMany("Instructions")
|
||||
.HasForeignKey("RecipeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Recipe");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", "Instruction")
|
||||
.WithMany("MediaFiles")
|
||||
.HasForeignKey("InstructionId");
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
|
||||
.WithMany("MediaFiles")
|
||||
.HasForeignKey("RecipeId");
|
||||
|
||||
b.Navigation("Instruction");
|
||||
|
||||
b.Navigation("Recipe");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Category.Category", "Category")
|
||||
.WithMany("Recipes")
|
||||
.HasForeignKey("CategoryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", "Favorit")
|
||||
.WithMany("Recipe")
|
||||
.HasForeignKey("FavoritId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Category");
|
||||
|
||||
b.Navigation("Favorit");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", "Ingredient")
|
||||
.WithMany("RecipeIngredients")
|
||||
.HasForeignKey("IngredientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
|
||||
.WithMany("RecipeIngredients")
|
||||
.HasForeignKey("RecipeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", "Unit")
|
||||
.WithMany("RecipeIngredient")
|
||||
.HasForeignKey("UnitId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Ingredient");
|
||||
|
||||
b.Navigation("Recipe");
|
||||
|
||||
b.Navigation("Unit");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
|
||||
.WithMany()
|
||||
.HasForeignKey("RecipeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "ShoppingList")
|
||||
.WithMany("RecipeShoppingList")
|
||||
.HasForeignKey("ShoppingListId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Recipe");
|
||||
|
||||
b.Navigation("ShoppingList");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b =>
|
||||
{
|
||||
b.Navigation("Recipes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b =>
|
||||
{
|
||||
b.Navigation("Recipe");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b =>
|
||||
{
|
||||
b.Navigation("IngredientShoppingLists");
|
||||
|
||||
b.Navigation("RecipeIngredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
|
||||
{
|
||||
b.Navigation("MediaFiles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
|
||||
{
|
||||
b.Navigation("Instructions");
|
||||
|
||||
b.Navigation("MediaFiles");
|
||||
|
||||
b.Navigation("RecipeIngredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b =>
|
||||
{
|
||||
b.Navigation("SelectedIngredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b =>
|
||||
{
|
||||
b.Navigation("RecipeShoppingList");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b =>
|
||||
{
|
||||
b.Navigation("RecipeIngredient");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Francesco.Recipes.World.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class MakeInstructionOptionalInMediaFile : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
if (migrationBuilder is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(migrationBuilder));
|
||||
}
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_MediaFiles_Instructions_InstructionId",
|
||||
table: "MediaFiles");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "InstructionId",
|
||||
table: "MediaFiles",
|
||||
type: "uniqueidentifier",
|
||||
nullable: true,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uniqueidentifier");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_MediaFiles_Instructions_InstructionId",
|
||||
table: "MediaFiles",
|
||||
column: "InstructionId",
|
||||
principalTable: "Instructions",
|
||||
principalColumn: "Id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
if (migrationBuilder is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(migrationBuilder));
|
||||
}
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_MediaFiles_Instructions_InstructionId",
|
||||
table: "MediaFiles");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "InstructionId",
|
||||
table: "MediaFiles",
|
||||
type: "uniqueidentifier",
|
||||
nullable: false,
|
||||
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "uniqueidentifier",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_MediaFiles_Instructions_InstructionId",
|
||||
table: "MediaFiles",
|
||||
column: "InstructionId",
|
||||
principalTable: "Instructions",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
+571
@@ -0,0 +1,571 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Francesco.Recipes.World.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Francesco.Recipes.World.Migrations
|
||||
{
|
||||
[DbContext(typeof(FrancescosRecipesWorldDbContext))]
|
||||
[Migration("20250602133408_RenameFavoriteColumn")]
|
||||
partial class RenameFavoriteColumn
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.11")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Categories");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = new Guid("b248244f-f21c-4555-a14d-5dd49a2717cf"),
|
||||
Name = "Vorspeisen & Snacks"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("5186c5d6-5aff-4a6e-baf4-b61b72d889fe"),
|
||||
Name = "Erste Gänge"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("abbc6bb0-97ea-49f5-b31e-6507eb784fd1"),
|
||||
Name = "Hauptgerichte"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("90deec39-dcd0-422d-9018-ac8389e332e1"),
|
||||
Name = "Desserts & Süßspeisen"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("28e39168-701a-4084-81da-d96c987c462f"),
|
||||
Name = "Beilagen & Salate"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20585b74-4805-4aff-a6df-aa6b7af04ff1"),
|
||||
Name = "Kuchen"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("adfb75ce-3ef3-428b-a5ca-b0c4c619d5ec"),
|
||||
Name = "Hefegebäck & Brot"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("d332f88d-d241-48c5-a2f2-bfd124eada7e"),
|
||||
Name = "Soßen & Saucen"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("23b1c740-e427-44f9-a6ea-d33d3f30f05a"),
|
||||
Name = "Marmeladen & Eingemachtes"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("0a91a200-dc76-4e00-b38c-b38cab5b69d7"),
|
||||
Name = "Getränke"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Favorits");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Ingredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid?>("IngredientId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("IsChecked")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<Guid>("RecipeIngredientId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("RecipeShoppingListId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IngredientId");
|
||||
|
||||
b.HasIndex("RecipeIngredientId");
|
||||
|
||||
b.HasIndex("RecipeShoppingListId");
|
||||
|
||||
b.ToTable("RecipeIngredientsShoppingLists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("Number")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("RecipeId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipeId");
|
||||
|
||||
b.ToTable("Instructions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<byte[]>("Data")
|
||||
.HasColumnType("varbinary(max)");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<Guid?>("InstructionId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("MimeType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<Guid?>("RecipeId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InstructionId");
|
||||
|
||||
b.HasIndex("RecipeId");
|
||||
|
||||
b.ToTable("MediaFiles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("CategoryId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<TimeSpan>("CookingTime")
|
||||
.HasColumnType("time");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("Difficulty")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("FavoriteId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("IsFavorite")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("ModifiedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<TimeSpan>("PreparationTime")
|
||||
.HasColumnType("time");
|
||||
|
||||
b.Property<int>("Servings")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CategoryId");
|
||||
|
||||
b.HasIndex("FavoriteId");
|
||||
|
||||
b.ToTable("Recipes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("IngredientId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int>("Quantity")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("RecipeId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("UnitId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IngredientId");
|
||||
|
||||
b.HasIndex("RecipeId");
|
||||
|
||||
b.HasIndex("UnitId");
|
||||
|
||||
b.ToTable("RecipeIngredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("RecipeId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("ShoppingListId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipeId");
|
||||
|
||||
b.HasIndex("ShoppingListId");
|
||||
|
||||
b.ToTable("RecipeShoppingLists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime?>("ModifiedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ShoppingLists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Symbol")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Units");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = new Guid("62eb2c11-8768-46fb-9db0-c77f940bb4aa"),
|
||||
Name = "liter",
|
||||
Symbol = "l"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("6c41f7e6-ca75-49cc-8541-cebd5a9c560b"),
|
||||
Name = "gramm",
|
||||
Symbol = "g"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("7e3d1b86-ac48-45d6-814e-d3492a86db1d"),
|
||||
Name = "kilogramm",
|
||||
Symbol = "kg"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("0c3648ec-4981-42c8-abf8-18bf1a2ff4c2"),
|
||||
Name = "stücke",
|
||||
Symbol = "stk"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("d82f4abd-e4e4-4104-a9c0-1acdeaa701f5"),
|
||||
Name = "blatt",
|
||||
Symbol = "blatt"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("df5cb4c3-4de6-4c6f-be8c-da41b2986408"),
|
||||
Name = "messerspitze",
|
||||
Symbol = "msp"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("e45a3af2-2ed6-4ac4-b06f-b4175663a7be"),
|
||||
Name = "stange",
|
||||
Symbol = "stange"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("7ea2f51d-7493-4f19-a663-1f309186d3ae"),
|
||||
Name = "bund",
|
||||
Symbol = "bund"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("66556e0e-eb2b-4bc3-9a56-135dd508ed09"),
|
||||
Name = "zehe",
|
||||
Symbol = "zehe"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("2e9894ac-14fa-43fd-ba07-35cdd8ebd461"),
|
||||
Name = "teelöffel",
|
||||
Symbol = "TL"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("24a91b89-3389-4465-89e4-2f70d2ea6fd7"),
|
||||
Name = "esslöffel",
|
||||
Symbol = "EL"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", null)
|
||||
.WithMany("IngredientShoppingLists")
|
||||
.HasForeignKey("IngredientId");
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", "RecipeIngredient")
|
||||
.WithMany()
|
||||
.HasForeignKey("RecipeIngredientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", "RecipeShoppingList")
|
||||
.WithMany("SelectedIngredients")
|
||||
.HasForeignKey("RecipeShoppingListId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("RecipeIngredient");
|
||||
|
||||
b.Navigation("RecipeShoppingList");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
|
||||
.WithMany("Instructions")
|
||||
.HasForeignKey("RecipeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Recipe");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", "Instruction")
|
||||
.WithMany("MediaFiles")
|
||||
.HasForeignKey("InstructionId");
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
|
||||
.WithMany("MediaFiles")
|
||||
.HasForeignKey("RecipeId");
|
||||
|
||||
b.Navigation("Instruction");
|
||||
|
||||
b.Navigation("Recipe");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Category.Category", "Category")
|
||||
.WithMany("Recipes")
|
||||
.HasForeignKey("CategoryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", "Favorite")
|
||||
.WithMany("Recipe")
|
||||
.HasForeignKey("FavoriteId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Category");
|
||||
|
||||
b.Navigation("Favorite");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", "Ingredient")
|
||||
.WithMany("RecipeIngredients")
|
||||
.HasForeignKey("IngredientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
|
||||
.WithMany("RecipeIngredients")
|
||||
.HasForeignKey("RecipeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", "Unit")
|
||||
.WithMany("RecipeIngredient")
|
||||
.HasForeignKey("UnitId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Ingredient");
|
||||
|
||||
b.Navigation("Recipe");
|
||||
|
||||
b.Navigation("Unit");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
|
||||
.WithMany()
|
||||
.HasForeignKey("RecipeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "ShoppingList")
|
||||
.WithMany("RecipeShoppingList")
|
||||
.HasForeignKey("ShoppingListId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Recipe");
|
||||
|
||||
b.Navigation("ShoppingList");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b =>
|
||||
{
|
||||
b.Navigation("Recipes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b =>
|
||||
{
|
||||
b.Navigation("Recipe");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b =>
|
||||
{
|
||||
b.Navigation("IngredientShoppingLists");
|
||||
|
||||
b.Navigation("RecipeIngredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
|
||||
{
|
||||
b.Navigation("MediaFiles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
|
||||
{
|
||||
b.Navigation("Instructions");
|
||||
|
||||
b.Navigation("MediaFiles");
|
||||
|
||||
b.Navigation("RecipeIngredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b =>
|
||||
{
|
||||
b.Navigation("SelectedIngredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b =>
|
||||
{
|
||||
b.Navigation("RecipeShoppingList");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b =>
|
||||
{
|
||||
b.Navigation("RecipeIngredient");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Francesco.Recipes.World.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RenameFavoriteColumn : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
if (migrationBuilder is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(migrationBuilder));
|
||||
}
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Recipes_Favorits_FavoritId",
|
||||
table: "Recipes");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "FavoritId",
|
||||
table: "Recipes",
|
||||
newName: "FavoriteId");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_Recipes_FavoritId",
|
||||
table: "Recipes",
|
||||
newName: "IX_Recipes_FavoriteId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Recipes_Favorits_FavoriteId",
|
||||
table: "Recipes",
|
||||
column: "FavoriteId",
|
||||
principalTable: "Favorits",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
if (migrationBuilder is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(migrationBuilder));
|
||||
}
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Recipes_Favorits_FavoriteId",
|
||||
table: "Recipes");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "FavoriteId",
|
||||
table: "Recipes",
|
||||
newName: "FavoritId");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_Recipes_FavoriteId",
|
||||
table: "Recipes",
|
||||
newName: "IX_Recipes_FavoritId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Recipes_Favorits_FavoritId",
|
||||
table: "Recipes",
|
||||
column: "FavoritId",
|
||||
principalTable: "Favorits",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,568 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Francesco.Recipes.World.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Francesco.Recipes.World.Migrations
|
||||
{
|
||||
[DbContext(typeof(FrancescosRecipesWorldDbContext))]
|
||||
partial class FrancescosRecipesWorldDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.11")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Categories");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = new Guid("b248244f-f21c-4555-a14d-5dd49a2717cf"),
|
||||
Name = "Vorspeisen & Snacks"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("5186c5d6-5aff-4a6e-baf4-b61b72d889fe"),
|
||||
Name = "Erste Gänge"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("abbc6bb0-97ea-49f5-b31e-6507eb784fd1"),
|
||||
Name = "Hauptgerichte"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("90deec39-dcd0-422d-9018-ac8389e332e1"),
|
||||
Name = "Desserts & Süßspeisen"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("28e39168-701a-4084-81da-d96c987c462f"),
|
||||
Name = "Beilagen & Salate"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20585b74-4805-4aff-a6df-aa6b7af04ff1"),
|
||||
Name = "Kuchen"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("adfb75ce-3ef3-428b-a5ca-b0c4c619d5ec"),
|
||||
Name = "Hefegebäck & Brot"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("d332f88d-d241-48c5-a2f2-bfd124eada7e"),
|
||||
Name = "Soßen & Saucen"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("23b1c740-e427-44f9-a6ea-d33d3f30f05a"),
|
||||
Name = "Marmeladen & Eingemachtes"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("0a91a200-dc76-4e00-b38c-b38cab5b69d7"),
|
||||
Name = "Getränke"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Favorits");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Ingredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid?>("IngredientId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("IsChecked")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<Guid>("RecipeIngredientId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("RecipeShoppingListId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IngredientId");
|
||||
|
||||
b.HasIndex("RecipeIngredientId");
|
||||
|
||||
b.HasIndex("RecipeShoppingListId");
|
||||
|
||||
b.ToTable("RecipeIngredientsShoppingLists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("Number")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("RecipeId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipeId");
|
||||
|
||||
b.ToTable("Instructions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<byte[]>("Data")
|
||||
.HasColumnType("varbinary(max)");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<Guid?>("InstructionId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("MimeType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<Guid?>("RecipeId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InstructionId");
|
||||
|
||||
b.HasIndex("RecipeId");
|
||||
|
||||
b.ToTable("MediaFiles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("CategoryId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<TimeSpan>("CookingTime")
|
||||
.HasColumnType("time");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("Difficulty")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("FavoriteId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("IsFavorite")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTime?>("ModifiedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<TimeSpan>("PreparationTime")
|
||||
.HasColumnType("time");
|
||||
|
||||
b.Property<int>("Servings")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CategoryId");
|
||||
|
||||
b.HasIndex("FavoriteId");
|
||||
|
||||
b.ToTable("Recipes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("IngredientId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int>("Quantity")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("RecipeId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("UnitId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IngredientId");
|
||||
|
||||
b.HasIndex("RecipeId");
|
||||
|
||||
b.HasIndex("UnitId");
|
||||
|
||||
b.ToTable("RecipeIngredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("RecipeId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("ShoppingListId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipeId");
|
||||
|
||||
b.HasIndex("ShoppingListId");
|
||||
|
||||
b.ToTable("RecipeShoppingLists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime?>("ModifiedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ShoppingLists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Symbol")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Units");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = new Guid("62eb2c11-8768-46fb-9db0-c77f940bb4aa"),
|
||||
Name = "liter",
|
||||
Symbol = "l"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("6c41f7e6-ca75-49cc-8541-cebd5a9c560b"),
|
||||
Name = "gramm",
|
||||
Symbol = "g"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("7e3d1b86-ac48-45d6-814e-d3492a86db1d"),
|
||||
Name = "kilogramm",
|
||||
Symbol = "kg"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("0c3648ec-4981-42c8-abf8-18bf1a2ff4c2"),
|
||||
Name = "stücke",
|
||||
Symbol = "stk"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("d82f4abd-e4e4-4104-a9c0-1acdeaa701f5"),
|
||||
Name = "blatt",
|
||||
Symbol = "blatt"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("df5cb4c3-4de6-4c6f-be8c-da41b2986408"),
|
||||
Name = "messerspitze",
|
||||
Symbol = "msp"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("e45a3af2-2ed6-4ac4-b06f-b4175663a7be"),
|
||||
Name = "stange",
|
||||
Symbol = "stange"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("7ea2f51d-7493-4f19-a663-1f309186d3ae"),
|
||||
Name = "bund",
|
||||
Symbol = "bund"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("66556e0e-eb2b-4bc3-9a56-135dd508ed09"),
|
||||
Name = "zehe",
|
||||
Symbol = "zehe"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("2e9894ac-14fa-43fd-ba07-35cdd8ebd461"),
|
||||
Name = "teelöffel",
|
||||
Symbol = "TL"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("24a91b89-3389-4465-89e4-2f70d2ea6fd7"),
|
||||
Name = "esslöffel",
|
||||
Symbol = "EL"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList.RecipeIngredientShoppingList", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", null)
|
||||
.WithMany("IngredientShoppingLists")
|
||||
.HasForeignKey("IngredientId");
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", "RecipeIngredient")
|
||||
.WithMany()
|
||||
.HasForeignKey("RecipeIngredientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", "RecipeShoppingList")
|
||||
.WithMany("SelectedIngredients")
|
||||
.HasForeignKey("RecipeShoppingListId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("RecipeIngredient");
|
||||
|
||||
b.Navigation("RecipeShoppingList");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
|
||||
.WithMany("Instructions")
|
||||
.HasForeignKey("RecipeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Recipe");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.MediaFile.MediaFile", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", "Instruction")
|
||||
.WithMany("MediaFiles")
|
||||
.HasForeignKey("InstructionId");
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
|
||||
.WithMany("MediaFiles")
|
||||
.HasForeignKey("RecipeId");
|
||||
|
||||
b.Navigation("Instruction");
|
||||
|
||||
b.Navigation("Recipe");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Category.Category", "Category")
|
||||
.WithMany("Recipes")
|
||||
.HasForeignKey("CategoryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", "Favorite")
|
||||
.WithMany("Recipe")
|
||||
.HasForeignKey("FavoriteId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Category");
|
||||
|
||||
b.Navigation("Favorite");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", "Ingredient")
|
||||
.WithMany("RecipeIngredients")
|
||||
.HasForeignKey("IngredientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
|
||||
.WithMany("RecipeIngredients")
|
||||
.HasForeignKey("RecipeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", "Unit")
|
||||
.WithMany("RecipeIngredient")
|
||||
.HasForeignKey("UnitId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Ingredient");
|
||||
|
||||
b.Navigation("Recipe");
|
||||
|
||||
b.Navigation("Unit");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b =>
|
||||
{
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", "Recipe")
|
||||
.WithMany()
|
||||
.HasForeignKey("RecipeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", "ShoppingList")
|
||||
.WithMany("RecipeShoppingList")
|
||||
.HasForeignKey("ShoppingListId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Recipe");
|
||||
|
||||
b.Navigation("ShoppingList");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Category.Category", b =>
|
||||
{
|
||||
b.Navigation("Recipes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Favorit.Favorit", b =>
|
||||
{
|
||||
b.Navigation("Recipe");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient", b =>
|
||||
{
|
||||
b.Navigation("IngredientShoppingLists");
|
||||
|
||||
b.Navigation("RecipeIngredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction", b =>
|
||||
{
|
||||
b.Navigation("MediaFiles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe", b =>
|
||||
{
|
||||
b.Navigation("Instructions");
|
||||
|
||||
b.Navigation("MediaFiles");
|
||||
|
||||
b.Navigation("RecipeIngredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList.RecipeShoppingList", b =>
|
||||
{
|
||||
b.Navigation("SelectedIngredients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Shoppinglist.ShoppingList", b =>
|
||||
{
|
||||
b.Navigation("RecipeShoppingList");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Francesco.Recipes.World.Models.BackendModels.Unit.Unit", b =>
|
||||
{
|
||||
b.Navigation("RecipeIngredient");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Francesco.Recipes.World.Models.BackendModels.Category
|
||||
{
|
||||
using Francesco.Recipes.World.Models.BackendModels.Recipe;
|
||||
|
||||
public class Category
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public virtual ICollection<Recipe> Recipes { get; set; } = new List<Recipe>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Francesco.Recipes.World.Models.BackendModels.Favorit
|
||||
{
|
||||
using Francesco.Recipes.World.Models.BackendModels.Recipe;
|
||||
|
||||
public class Favorit
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
public virtual ICollection<Recipe> Recipe { get; set; } = new List<Recipe>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Francesco.Recipes.World.Models.BackendModels
|
||||
{
|
||||
public interface ITimeStampedEntity
|
||||
{
|
||||
DateTime CreatedAt { get; set; }
|
||||
|
||||
DateTime? ModifiedAt { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace Francesco.Recipes.World.Models.BackendModels.Ingredient
|
||||
{
|
||||
using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList;
|
||||
using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient;
|
||||
|
||||
public class Ingredient
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public virtual ICollection<RecipeIngredient> RecipeIngredients { get; set; } = new List<RecipeIngredient>();
|
||||
|
||||
public virtual ICollection<RecipeIngredientShoppingList> IngredientShoppingLists { get; set; } = new List<RecipeIngredientShoppingList>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace Francesco.Recipes.World.Models.BackendModels.Instruction
|
||||
{
|
||||
using Francesco.Recipes.World.Models.BackendModels.MediaFile;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Recipe;
|
||||
|
||||
public class Instruction
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
public int Number { get; set; }
|
||||
|
||||
public virtual Recipe Recipe { get; set; } = new ();
|
||||
|
||||
public virtual ICollection<MediaFile> MediaFiles { get; set; } = new List<MediaFile>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace Francesco.Recipes.World.Models.BackendModels.MediaFile
|
||||
{
|
||||
using Francesco.Recipes.World.Models.BackendModels.Instruction;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Recipe;
|
||||
|
||||
public class MediaFile
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
public string? FileName { get; set; }
|
||||
|
||||
public string? MimeType { get; set; }
|
||||
|
||||
public byte[]? Data { get; set; }
|
||||
|
||||
public virtual Recipe? Recipe { get; set; } = null;
|
||||
|
||||
public virtual Instruction? Instruction { get; set; } = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Francesco.Recipes.World.Models.BackendModels.Recipe
|
||||
{
|
||||
public enum Difficulty
|
||||
{
|
||||
[Display(Name = "Sehr einfach")]
|
||||
VeryEasy = 0,
|
||||
|
||||
[Display(Name = "Einfach")]
|
||||
Easy = 1,
|
||||
|
||||
[Display(Name = "Mittel")]
|
||||
Medium = 2,
|
||||
|
||||
[Display(Name = "Schwer")]
|
||||
Hard = 3,
|
||||
|
||||
[Display(Name = "Experte")]
|
||||
Expert = 4,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
namespace Francesco.Recipes.World.Models.BackendModels.Recipe;
|
||||
|
||||
using Francesco.Recipes.World.Models.BackendModels.Category;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Favorit;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Instruction;
|
||||
using Francesco.Recipes.World.Models.BackendModels.MediaFile;
|
||||
using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient;
|
||||
|
||||
public class Recipe : ITimeStampedEntity, IFavoritable
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public string? Description { get; set; }
|
||||
|
||||
public Difficulty Difficulty { get; set; }
|
||||
|
||||
public int Servings { get; set; }
|
||||
|
||||
public TimeSpan PreparationTime { get; set; }
|
||||
|
||||
public TimeSpan CookingTime { get; set; }
|
||||
|
||||
public bool IsFavorite { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
public DateTime? ModifiedAt { get; set; }
|
||||
|
||||
public virtual ICollection<RecipeIngredient> RecipeIngredients { get; set; } = new List<RecipeIngredient>();
|
||||
|
||||
public virtual ICollection<Instruction> Instructions { get; set; } = new List<Instruction>();
|
||||
|
||||
public virtual ICollection<MediaFile> MediaFiles { get; set; } = new List<MediaFile>();
|
||||
|
||||
public virtual Favorit Favorite { get; set; } = new ();
|
||||
|
||||
public virtual Category Category { get; set; } = new ();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Francesco.Recipes.World.Models.BackendModels.RecipeIngredient
|
||||
{
|
||||
using Francesco.Recipes.World.Models.BackendModels.Ingredient;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Recipe;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Unit;
|
||||
|
||||
public class RecipeIngredient
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public virtual Recipe Recipe { get; set; } = new ();
|
||||
|
||||
public virtual Ingredient Ingredient { get; set; } = new ();
|
||||
|
||||
public virtual Unit Unit { get; set; } = new ();
|
||||
|
||||
public int Quantity { get; set; }
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
namespace Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList
|
||||
{
|
||||
using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient;
|
||||
using Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList;
|
||||
|
||||
public class RecipeIngredientShoppingList
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public virtual RecipeShoppingList RecipeShoppingList { get; set; } = new ();
|
||||
|
||||
public virtual RecipeIngredient RecipeIngredient { get; set; } = new ();
|
||||
|
||||
public bool IsChecked { get; set; } = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList
|
||||
{
|
||||
using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Recipe;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Shoppinglist;
|
||||
|
||||
public class RecipeShoppingList
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public virtual ShoppingList ShoppingList { get; set; } = new ShoppingList();
|
||||
|
||||
public virtual Recipe Recipe { get; set; } = new Recipe();
|
||||
|
||||
public virtual ICollection<RecipeIngredientShoppingList> SelectedIngredients { get; set; } = new List<RecipeIngredientShoppingList>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Francesco.Recipes.World.Models.BackendModels.Shoppinglist
|
||||
{
|
||||
using Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList;
|
||||
|
||||
public class ShoppingList : ITimeStampedEntity
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
public DateTime? ModifiedAt { get; set; }
|
||||
|
||||
public virtual ICollection<RecipeShoppingList> RecipeShoppingList { get; set; } = new List<RecipeShoppingList>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Francesco.Recipes.World.Models.BackendModels.Unit
|
||||
{
|
||||
using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient;
|
||||
|
||||
public class Unit
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public string Symbol { get; set; } = string.Empty;
|
||||
|
||||
public virtual ICollection<RecipeIngredient> RecipeIngredient { get; set; } = new List<RecipeIngredient>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Francesco.Recipes.World.Models
|
||||
{
|
||||
public class CreateOrAddIngredientRequestModel
|
||||
{
|
||||
public Guid RecipeId { get; set; }
|
||||
|
||||
public List<Guid> IngredientIds { get; set; } = new ();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using Francesco.Recipes.World.Models.BackendModels.Recipe;
|
||||
|
||||
namespace Francesco.Recipes.World.Models
|
||||
{
|
||||
public class CreateRecipeViewModel
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public string? Description { get; set; }
|
||||
|
||||
public Difficulty Difficulty { get; set; }
|
||||
|
||||
public int Servings { get; set; }
|
||||
|
||||
public TimeSpan PreparationTime { get; set; }
|
||||
|
||||
public TimeSpan CookingTime { get; set; }
|
||||
|
||||
public int PrepHours { get; set; }
|
||||
|
||||
public int PrepMinutes { get; set; }
|
||||
|
||||
public int CookHours { get; set; }
|
||||
|
||||
public int CookMinutes { get; set; }
|
||||
|
||||
public Guid CategoryId { get; set; }
|
||||
|
||||
public string? CategoryName { get; set; }
|
||||
|
||||
public IFormFile? Photo { get; set; }
|
||||
|
||||
public IFormFile? Video { get; set; }
|
||||
|
||||
public IngredientViewModel? IngredientViewModel { get; set; }
|
||||
|
||||
public InstructionViewModel? InstructionViewModel { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Francesco.Recipes.World.Models
|
||||
{
|
||||
public class ErrorViewModel
|
||||
{
|
||||
public string? RequestId { get; set; }
|
||||
|
||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Francesco.Recipes.World.Models
|
||||
{
|
||||
public class FavoritButtonViewModel : IFavoritable
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public bool IsFavorite { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Francesco.Recipes.World.Constants;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Recipe;
|
||||
|
||||
namespace Francesco.Recipes.World.Models
|
||||
{
|
||||
public class FavoriteViewModel
|
||||
{
|
||||
public IEnumerable<Recipe> FavoriteRecipes { get; set; } = new List<Recipe>();
|
||||
|
||||
public string SortOrder { get; set; } = SortOrders.Newest;
|
||||
|
||||
public bool HasFavorites => FavoriteRecipes.Any();
|
||||
|
||||
public string SortOrderDisplayText => SortOrder == SortOrders.Oldest ? "Älteste Favorits" : "Neueste Favorits";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Francesco.Recipes.World.Models
|
||||
{
|
||||
using Francesco.Recipes.World.Models.BackendModels.Ingredient;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Unit;
|
||||
|
||||
public class IngredientViewModel
|
||||
{
|
||||
public Guid RecipeId { get; set; }
|
||||
|
||||
public List<Ingredient> Ingredients { get; set; } = new List<Ingredient>();
|
||||
|
||||
public List<Unit> Units { get; set; } = new List<Unit>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Francesco.Recipes.World.Models.BackendModels.Instruction;
|
||||
|
||||
namespace Francesco.Recipes.World.Models
|
||||
{
|
||||
public class InstructionViewModel
|
||||
{
|
||||
public Guid RecipeId { get; set; }
|
||||
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
public List<Instruction> Instructions { get; set; } = new List<Instruction>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Francesco.Recipes.World.Models
|
||||
{
|
||||
public interface IFavoritable
|
||||
{
|
||||
Guid Id { get; set; }
|
||||
|
||||
bool IsFavorite { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace Francesco.Recipes.World.Models
|
||||
{
|
||||
public class RecipeCardViewModel : IFavoritable
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public TimeSpan CookingTime { get; set; }
|
||||
|
||||
public bool IsFavorite { get; set; }
|
||||
|
||||
public byte[]? ImageData { get; set; }
|
||||
|
||||
public string? MimeType { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace Francesco.Recipes.World.Models
|
||||
{
|
||||
public class SearchViewModel
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public string? Description { get; set; }
|
||||
|
||||
public bool IsFavorite { get; set; }
|
||||
|
||||
public byte[]? ImageData { get; set; }
|
||||
|
||||
public string? MimeType { get; set; }
|
||||
|
||||
public List<string> Ingredients { get; set; } = new ();
|
||||
|
||||
public TimeSpan TotalTime { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Francesco.Recipes.World.Models.BackendModels.Recipe;
|
||||
|
||||
namespace Francesco.Recipes.World.Models
|
||||
{
|
||||
public class ShoppingListDetailsViewModel
|
||||
{
|
||||
public int RecipeCount { get; set; }
|
||||
|
||||
public List<Recipe> RecipesInAnyShoppingList { get; set; } = new List<Recipe>();
|
||||
|
||||
public Dictionary<Guid, Guid> RecipeIngredientToShoppingListMap { get; set; } = new Dictionary<Guid, Guid>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using Francesco.Recipes.World.Data;
|
||||
using Francesco.Recipes.World.Repositories.Category;
|
||||
using Francesco.Recipes.World.Repositories.Favorit;
|
||||
using Francesco.Recipes.World.Repositories.Ingredient;
|
||||
using Francesco.Recipes.World.Repositories.Instruction;
|
||||
using Francesco.Recipes.World.Repositories.MediaFile;
|
||||
using Francesco.Recipes.World.Repositories.Recipe;
|
||||
using Francesco.Recipes.World.Repositories.ShoppingList;
|
||||
using Francesco.Recipes.World.Repositories.Unit;
|
||||
using Francesco.Recipes.World.Services.Instruction;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
var services = builder.Services;
|
||||
|
||||
var configuration = builder.Configuration;
|
||||
|
||||
var connectionString = builder.Configuration.GetConnectionString("FrancescosRecipesWorldDbContextConnection")
|
||||
?? throw new InvalidOperationException("Connection string 'FrancescosRecipesWorldDbContextConnection' not found.");
|
||||
|
||||
services.AddDbContext<FrancescosRecipesWorldDbContext>(options =>
|
||||
options.UseSqlServer(connectionString, sqlOptions =>
|
||||
sqlOptions.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery)));
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddControllersWithViews();
|
||||
|
||||
builder.Services.AddScoped<IRecipeRepository, RecipeRepository>();
|
||||
|
||||
builder.Services.AddScoped<IIngredientRepository, IngredientRepository>();
|
||||
|
||||
builder.Services.AddScoped<IUnitRepository, UnitRepository>();
|
||||
|
||||
builder.Services.AddScoped<ICategoryRepository, CategoryRepository>();
|
||||
|
||||
builder.Services.AddScoped<IShoppingListRepository, ShoppingListRepository>();
|
||||
|
||||
builder.Services.AddScoped<IMediaFileRepository, MediaFileRepository>();
|
||||
|
||||
builder.Services.AddScoped<IInstructionRepository, InstructionRepository>();
|
||||
|
||||
builder.Services.AddScoped<IFavoriteRepository, FavoritRepository>();
|
||||
|
||||
builder.Services.AddScoped<IInstructionService, InstructionService>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseExceptionHandler("/Home/Error");
|
||||
|
||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
Console.WriteLine("Standard Numeric Format Specifiers");
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.MapDefaultControllerRoute();
|
||||
|
||||
app.MapControllerRoute(
|
||||
name: "default",
|
||||
pattern: "{controller=Category}/{action=Index}/{id?}");
|
||||
|
||||
app.Run();
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:54294",
|
||||
"sslPort": 44322
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:5103",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:7171;http://localhost:5103",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
namespace Francesco.Recipes.World.Repositories.Category
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Francesco.Recipes.World.Data;
|
||||
using Francesco.Recipes.World.Models;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Category;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Recipe;
|
||||
using Francesco.Recipes.World.Views.Category;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
public class CategoryRepository : ICategoryRepository
|
||||
{
|
||||
private readonly FrancescosRecipesWorldDbContext _context;
|
||||
|
||||
public CategoryRepository(FrancescosRecipesWorldDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<Category> GetCategoryByIdAsync(Guid categoryId)
|
||||
{
|
||||
var category = await _context.Categories.FindAsync(categoryId);
|
||||
return category ?? throw new InvalidDataException($"Category {categoryId} not found.");
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Category>> GetAllCategoriesAsync()
|
||||
{
|
||||
return await _context.Categories.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Recipe>> GetRecipesByCategoryAsync(Guid categoryId)
|
||||
{
|
||||
var category = await _context.Categories
|
||||
.Include(c => c.Recipes)
|
||||
.FirstOrDefaultAsync(c => c.Id == categoryId);
|
||||
|
||||
if (category == null)
|
||||
{
|
||||
throw new InvalidDataException($"Category {categoryId} not found.");
|
||||
}
|
||||
|
||||
return category.Recipes;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Category>> GetAllCategoriesWithRecipesAsync()
|
||||
{
|
||||
var categories = await _context.Categories
|
||||
.Include(c => c.Recipes.OrderByDescending(r => r.CreatedAt).Take(3))
|
||||
.ThenInclude(r => r.MediaFiles.Take(2))
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
|
||||
return categories;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CategoryRecipesViewModel>> GetAllCategoriesWithRecipesViewModelAsync()
|
||||
{
|
||||
var categories = await _context.Categories
|
||||
.Select(c => new CategoryRecipesViewModel
|
||||
{
|
||||
Category = c,
|
||||
Recipes = c.Recipes
|
||||
.OrderByDescending(r => r.CreatedAt)
|
||||
.Take(3)
|
||||
.Select(r => new RecipeCardViewModel
|
||||
{
|
||||
Id = r.Id,
|
||||
Name = r.Name,
|
||||
CookingTime = r.CookingTime,
|
||||
IsFavorite = r.IsFavorite,
|
||||
ImageData = r.MediaFiles
|
||||
.OrderBy(m => m.Id)
|
||||
.Select(m => m.Data)
|
||||
.FirstOrDefault(),
|
||||
MimeType = r.MediaFiles
|
||||
.OrderBy(m => m.Id)
|
||||
.Select(m => m.MimeType)
|
||||
.FirstOrDefault(),
|
||||
}),
|
||||
})
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
|
||||
return categories;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Francesco.Recipes.World.Repositories.Category
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Category;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Recipe;
|
||||
using Francesco.Recipes.World.Views.Category;
|
||||
|
||||
public interface ICategoryRepository
|
||||
{
|
||||
Task<Category> GetCategoryByIdAsync(Guid categoryId);
|
||||
|
||||
Task<IEnumerable<Category>> GetAllCategoriesAsync();
|
||||
|
||||
Task<IEnumerable<Recipe>> GetRecipesByCategoryAsync(Guid categoryId);
|
||||
|
||||
Task<IEnumerable<Category>> GetAllCategoriesWithRecipesAsync();
|
||||
|
||||
Task<IEnumerable<CategoryRecipesViewModel>> GetAllCategoriesWithRecipesViewModelAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
namespace Francesco.Recipes.World.Repositories.Favorit
|
||||
{
|
||||
using Francesco.Recipes.World.Data;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Recipe;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
public class FavoritRepository : IFavoriteRepository
|
||||
{
|
||||
private readonly FrancescosRecipesWorldDbContext _context;
|
||||
|
||||
public FavoritRepository(FrancescosRecipesWorldDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Recipe>> GetFavoriteRecipesAsync()
|
||||
{
|
||||
return await _context.Recipes
|
||||
.Where(r => r.IsFavorite)
|
||||
.Include(r => r.Favorite)
|
||||
.Include(r => r.MediaFiles)
|
||||
.Take(6)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> IsFavoriteAsync(Guid recipeId)
|
||||
{
|
||||
return await _context.Recipes
|
||||
.AnyAsync(r => r.Id == recipeId && r.IsFavorite);
|
||||
}
|
||||
|
||||
public async Task AddFavoriteAsync(Guid recipeId)
|
||||
{
|
||||
var recipe = await _context.Recipes
|
||||
.Include(r => r.Favorite)
|
||||
.FirstOrDefaultAsync(r => r.Id == recipeId);
|
||||
|
||||
if (recipe == null)
|
||||
{
|
||||
throw new InvalidOperationException("Recipe not found.");
|
||||
}
|
||||
|
||||
if (recipe.IsFavorite)
|
||||
{
|
||||
throw new InvalidOperationException("Recipe is already a favorite.");
|
||||
}
|
||||
|
||||
recipe.IsFavorite = true;
|
||||
|
||||
if (recipe.Favorite == null || recipe.Favorite.Id == Guid.Empty)
|
||||
{
|
||||
recipe.Favorite = new Models.BackendModels.Favorit.Favorit
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
CreatedAt = DateTime.Now,
|
||||
};
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task RemoveFavoriteAsync(Guid recipeId)
|
||||
{
|
||||
var recipe = await _context.Recipes.FindAsync(recipeId);
|
||||
if (recipe != null && recipe.IsFavorite)
|
||||
{
|
||||
recipe.IsFavorite = false;
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Francesco.Recipes.World.Repositories.Favorit
|
||||
{
|
||||
using Francesco.Recipes.World.Models.BackendModels.Recipe;
|
||||
|
||||
public interface IFavoriteRepository
|
||||
{
|
||||
Task<IEnumerable<Recipe>> GetFavoriteRecipesAsync();
|
||||
|
||||
Task<bool> IsFavoriteAsync(Guid recipeId);
|
||||
|
||||
Task AddFavoriteAsync(Guid recipeId);
|
||||
|
||||
Task RemoveFavoriteAsync(Guid recipeId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace Francesco.Recipes.World.Repositories.Ingredient
|
||||
{
|
||||
using Francesco.Recipes.World.Models.BackendModels.Ingredient;
|
||||
using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient;
|
||||
|
||||
public interface IIngredientRepository
|
||||
{
|
||||
Task UpdateIngredientAsync(Ingredient ingredient);
|
||||
|
||||
Task<List<RecipeIngredient>> GetIngredientsByRecipeIdAsync(Guid recipeId);
|
||||
|
||||
Task<List<Ingredient>> GetIngredientsByNameAsync(string name);
|
||||
|
||||
Task<Ingredient> GetIngredientByIdAsync(Guid ingredientId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
|
||||
namespace Francesco.Recipes.World.Repositories.Ingredient
|
||||
{
|
||||
using Francesco.Recipes.World.Data;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Ingredient;
|
||||
using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
public class IngredientRepository : IIngredientRepository
|
||||
{
|
||||
private readonly FrancescosRecipesWorldDbContext _context;
|
||||
|
||||
public IngredientRepository(
|
||||
FrancescosRecipesWorldDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task UpdateRecipeIngredientAsync(RecipeIngredient recipeIngredient)
|
||||
{
|
||||
if (recipeIngredient == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(recipeIngredient));
|
||||
}
|
||||
|
||||
var existingRecipeIngredient = await _context.RecipeIngredients
|
||||
.Include(ri => ri.Ingredient)
|
||||
.Include(ri => ri.Unit)
|
||||
.FirstOrDefaultAsync(ri => ri.Id == recipeIngredient.Id);
|
||||
|
||||
if (existingRecipeIngredient == null)
|
||||
{
|
||||
throw new InvalidOperationException($"RecipeIngredient with ID {recipeIngredient.Id} not found.");
|
||||
}
|
||||
|
||||
existingRecipeIngredient.Quantity = recipeIngredient.Quantity;
|
||||
existingRecipeIngredient.Ingredient = recipeIngredient.Ingredient;
|
||||
existingRecipeIngredient.Unit = recipeIngredient.Unit;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<List<Ingredient>> GetIngredientsByNameAsync(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return await _context.Ingredients.ToListAsync();
|
||||
}
|
||||
|
||||
return await _context.Ingredients
|
||||
.Where(i => i.Name.ToLower().Contains(name.ToLower()))
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<List<RecipeIngredient>> GetIngredientsByRecipeIdAsync(Guid recipeId)
|
||||
{
|
||||
return await _context.RecipeIngredients
|
||||
.Include(ri => ri.Ingredient)
|
||||
.Include(ri => ri.Unit)
|
||||
.Where(ri => ri.Recipe.Id == recipeId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task UpdateIngredientAsync(Ingredient ingredient)
|
||||
{
|
||||
if (ingredient == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(ingredient));
|
||||
}
|
||||
|
||||
var existingIngredient = await _context.Ingredients.FindAsync(ingredient.Id);
|
||||
if (existingIngredient == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Ingredient with ID {ingredient.Id} not found.");
|
||||
}
|
||||
|
||||
existingIngredient.Name = ingredient.Name;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<Ingredient> GetIngredientByIdAsync(Guid ingredientId)
|
||||
{
|
||||
var ingredient = await _context.Ingredients.FindAsync(ingredientId);
|
||||
return ingredient ?? throw new InvalidDataException($"Address {ingredientId} not found.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Francesco.Recipes.World.Repositories.Instruction
|
||||
{
|
||||
using Francesco.Recipes.World.Models.BackendModels.Instruction;
|
||||
|
||||
public interface IInstructionRepository
|
||||
{
|
||||
Task<Instruction> GetInstructionAsync(Guid instructionId);
|
||||
|
||||
Task<Instruction> CreateInstructionAsync(Guid recipeId, string description, IFormFile? photo);
|
||||
|
||||
Task RemoveInstructionFromRecipeAsync(Guid recipeId, Guid instructionId);
|
||||
|
||||
Task SwapInstructionNumbersAsync(Instruction a, Instruction b);
|
||||
|
||||
Task<List<Instruction>> GetInstructionsOfRecipeAsync(Guid recipeId);
|
||||
|
||||
Task RenumberInstructionsAsync(Guid recipeId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
namespace Francesco.Recipes.World.Repositories.Instruction
|
||||
{
|
||||
using Francesco.Recipes.World.Data;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Instruction;
|
||||
using Francesco.Recipes.World.Models.BackendModels.MediaFile;
|
||||
using Francesco.Recipes.World.Repositories.Recipe;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
public class InstructionRepository : IInstructionRepository
|
||||
{
|
||||
private readonly FrancescosRecipesWorldDbContext _context;
|
||||
private readonly IRecipeRepository _recipeRepository;
|
||||
|
||||
public InstructionRepository(FrancescosRecipesWorldDbContext context, IRecipeRepository recipeRepository)
|
||||
{
|
||||
_recipeRepository = recipeRepository;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<Instruction> GetInstructionAsync(Guid instructionId)
|
||||
{
|
||||
var instruction = await _context.Instructions.FindAsync(instructionId);
|
||||
return instruction ?? throw new InvalidDataException($"Instruction {instructionId} not found.");
|
||||
}
|
||||
|
||||
public async Task<Instruction> CreateInstructionAsync(Guid recipeId, string description, IFormFile? photo)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(description))
|
||||
{
|
||||
throw new ArgumentException("Description cannot be empty", nameof(description));
|
||||
}
|
||||
|
||||
var recipe = await _context.Recipes
|
||||
.Include(r => r.Instructions)
|
||||
.FirstOrDefaultAsync(r => r.Id == recipeId);
|
||||
|
||||
if (recipe == null)
|
||||
{
|
||||
throw new ArgumentException("Recipe not found.", nameof(recipeId));
|
||||
}
|
||||
|
||||
var nextNumber = 1;
|
||||
if (recipe.Instructions != null && recipe.Instructions.Any())
|
||||
{
|
||||
nextNumber = recipe.Instructions.Max(i => i.Number) + 1;
|
||||
}
|
||||
|
||||
var newInstruction = new Instruction
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Description = description,
|
||||
Number = nextNumber,
|
||||
Recipe = recipe,
|
||||
};
|
||||
|
||||
_context.Instructions.Add(newInstruction);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
if (photo != null && photo.Length > 0)
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
await photo.CopyToAsync(memoryStream);
|
||||
|
||||
var instructionImage = new MediaFile
|
||||
{
|
||||
FileName = photo.FileName,
|
||||
MimeType = photo.ContentType,
|
||||
Data = memoryStream.ToArray(),
|
||||
Instruction = newInstruction,
|
||||
Recipe = null,
|
||||
};
|
||||
|
||||
_context.Add(instructionImage);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return newInstruction;
|
||||
}
|
||||
|
||||
public async Task RemoveInstructionFromRecipeAsync(Guid recipeId, Guid instructionId)
|
||||
{
|
||||
var recipe = await _recipeRepository.GetRecipeAsync(recipeId);
|
||||
|
||||
if (recipe.Instructions == null || !recipe.Instructions.Any())
|
||||
{
|
||||
await _context.Entry(recipe)
|
||||
.Collection(r => r.Instructions)
|
||||
.LoadAsync();
|
||||
}
|
||||
|
||||
var instructionToRemove = recipe.Instructions?.FirstOrDefault(i => i.Id == instructionId);
|
||||
|
||||
if (instructionToRemove != null)
|
||||
{
|
||||
await _context.Entry(instructionToRemove)
|
||||
.Collection(i => i.MediaFiles)
|
||||
.LoadAsync();
|
||||
|
||||
if (instructionToRemove.MediaFiles != null && instructionToRemove.MediaFiles.Any())
|
||||
{
|
||||
_context.MediaFiles.RemoveRange(instructionToRemove.MediaFiles);
|
||||
}
|
||||
|
||||
recipe.Instructions?.Remove(instructionToRemove);
|
||||
await _context.SaveChangesAsync();
|
||||
await RenumberInstructionsAsync(recipeId);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<Instruction>> GetInstructionsOfRecipeAsync(Guid recipeId)
|
||||
{
|
||||
var instructions = await _context.Instructions
|
||||
.Include(i => i.MediaFiles)
|
||||
.Where(i => i.Recipe.Id == recipeId)
|
||||
.OrderBy(i => i.Number)
|
||||
.ToListAsync();
|
||||
|
||||
if (!instructions.Any())
|
||||
{
|
||||
throw new InvalidDataException($"No instructions found for Recipe ID {recipeId}.");
|
||||
}
|
||||
|
||||
return instructions;
|
||||
}
|
||||
|
||||
public async Task RenumberInstructionsAsync(Guid recipeId)
|
||||
{
|
||||
var instructions = await _context.Instructions
|
||||
.Where(i => i.Recipe.Id == recipeId)
|
||||
.OrderBy(i => i.Number)
|
||||
.ToListAsync();
|
||||
|
||||
for (var i = 0; i < instructions.Count; i++)
|
||||
{
|
||||
instructions[i].Number = i + 1;
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task SwapInstructionNumbersAsync(Instruction a, Instruction b)
|
||||
{
|
||||
if (a == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(a), "Instruction 'a' cannot be null.");
|
||||
}
|
||||
|
||||
if (b == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(b), "Instruction 'b' cannot be null.");
|
||||
}
|
||||
|
||||
var temp = a.Number;
|
||||
a.Number = b.Number;
|
||||
b.Number = temp;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Francesco.Recipes.World.Repositories.MediaFile
|
||||
{
|
||||
public interface IMediaFileRepository
|
||||
{
|
||||
Task ReplaceInstructionImageAsync(Guid instructionId, Guid mediaFileIdToReplace, string fileName, string mimeType, byte[] newMediaData);
|
||||
|
||||
Task UploadInstructionImageAsync(Guid instructionId, IFormFile? photo);
|
||||
|
||||
Task UploadRecipeMediaAsync(Guid recipeId, IFormFile mediaFile);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
namespace Francesco.Recipes.World.Repositories.MediaFile
|
||||
{
|
||||
using Francesco.Recipes.World.Constants;
|
||||
using Francesco.Recipes.World.Data;
|
||||
using Francesco.Recipes.World.Models.BackendModels.MediaFile;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Recipe;
|
||||
using Francesco.Recipes.World.Repositories.Instruction;
|
||||
using Francesco.Recipes.World.Repositories.Recipe;
|
||||
|
||||
public class MediaFileRepository : IMediaFileRepository
|
||||
{
|
||||
private readonly IInstructionRepository _instructionRepository;
|
||||
private readonly FrancescosRecipesWorldDbContext _context;
|
||||
private readonly IRecipeRepository _recipeRepository;
|
||||
|
||||
public MediaFileRepository(IInstructionRepository instructionRepository, FrancescosRecipesWorldDbContext context, IRecipeRepository recipeRepository)
|
||||
{
|
||||
_instructionRepository = instructionRepository;
|
||||
_context = context;
|
||||
_recipeRepository = recipeRepository;
|
||||
}
|
||||
|
||||
public async Task ReplaceInstructionImageAsync(Guid instructionId, Guid mediaFileIdToReplace, string fileName, string mimeType, byte[] newMediaData)
|
||||
{
|
||||
var instruction = await _instructionRepository.GetInstructionAsync(instructionId);
|
||||
|
||||
var mediaToReplace = instruction.MediaFiles.FirstOrDefault(m => m.Id == mediaFileIdToReplace);
|
||||
if (mediaToReplace == null)
|
||||
{
|
||||
throw new InvalidOperationException("The specified media file does not exist.");
|
||||
}
|
||||
|
||||
_context.MediaFiles.Remove(mediaToReplace);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var newMedia = new MediaFile
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
FileName = fileName,
|
||||
MimeType = mimeType,
|
||||
Data = newMediaData,
|
||||
Instruction = instruction,
|
||||
};
|
||||
|
||||
_context.MediaFiles.Add(newMedia);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task UploadInstructionImageAsync(Guid instructionId, IFormFile? photo)
|
||||
{
|
||||
if (photo is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(photo));
|
||||
}
|
||||
|
||||
var instruction = await _instructionRepository.GetInstructionAsync(instructionId);
|
||||
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
await photo.CopyToAsync(memoryStream);
|
||||
|
||||
var instructionImage = new MediaFile
|
||||
{
|
||||
FileName = photo.FileName,
|
||||
MimeType = photo.ContentType,
|
||||
Data = memoryStream.ToArray(),
|
||||
Instruction = instruction,
|
||||
Recipe = null,
|
||||
};
|
||||
|
||||
_context.Add(instructionImage);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UploadRecipeMediaAsync(Guid recipeId, IFormFile mediaFile)
|
||||
{
|
||||
if (mediaFile == null || mediaFile.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var recipe = await _recipeRepository.GetRecipeAsync(recipeId);
|
||||
|
||||
var isImage = mediaFile.ContentType.StartsWith(ContentType.Image);
|
||||
var isVideo = mediaFile.ContentType.StartsWith("video/");
|
||||
|
||||
if (!isImage && !isVideo)
|
||||
{
|
||||
throw new InvalidOperationException("Only image or video files are allowed.");
|
||||
}
|
||||
|
||||
if (isImage)
|
||||
{
|
||||
await RemoveExistingMediaAsync(recipe, ContentType.Image);
|
||||
}
|
||||
else
|
||||
{
|
||||
await RemoveExistingMediaAsync(recipe, "video/");
|
||||
}
|
||||
|
||||
using var memoryStream = new MemoryStream();
|
||||
await mediaFile.CopyToAsync(memoryStream);
|
||||
|
||||
var newMedia = new MediaFile
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
FileName = mediaFile.FileName,
|
||||
MimeType = mediaFile.ContentType,
|
||||
Data = memoryStream.ToArray(),
|
||||
Recipe = recipe,
|
||||
Instruction = null,
|
||||
};
|
||||
|
||||
_context.MediaFiles.Add(newMedia);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException("An error occurred while uploading the media file.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RemoveExistingMediaAsync(Recipe recipe, string mediaTypePrefix)
|
||||
{
|
||||
var existingMedia = recipe.MediaFiles?.FirstOrDefault(m => m.MimeType?.StartsWith(mediaTypePrefix) == true);
|
||||
if (existingMedia != null)
|
||||
{
|
||||
_context.MediaFiles.Remove(existingMedia);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace Francesco.Recipes.World.Repositories.Recipe
|
||||
{
|
||||
using Francesco.Recipes.World.Models;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Category;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Recipe;
|
||||
|
||||
public interface IRecipeRepository
|
||||
{
|
||||
Task<Recipe> GetRecipeAsync(Guid recipeId);
|
||||
|
||||
Task<Recipe?> GetRecipeByIdAsync(Guid id);
|
||||
|
||||
Task CreateRecipeIngredientAsync(Guid recipeId, string ingredientName, int quantity, Guid unitId);
|
||||
|
||||
Task RemoveIngredientFromRecipeAsync(Guid recipeId, Guid ingredientId);
|
||||
|
||||
Task<IReadOnlyCollection<Recipe>> GetRecipesByDifficultyAsync(Difficulty? difficulty);
|
||||
|
||||
Task<Recipe> CreateRecipeForCategoryAsync(Category category, string name, string description, Difficulty difficulty, int servings, TimeSpan preparationTime, TimeSpan cookingTime);
|
||||
|
||||
Task<bool> DeleteRecipeAsync(Guid recipeId);
|
||||
|
||||
Task<IEnumerable<SearchViewModel>> SearchInRecipesAndIngredients(string searchTerm);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
namespace Francesco.Recipes.World.Repositories.Recipe
|
||||
{
|
||||
using System.Linq.Expressions;
|
||||
using Francesco.Recipes.World.Constants;
|
||||
using Francesco.Recipes.World.Data;
|
||||
using Francesco.Recipes.World.Models;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Category;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Ingredient;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Recipe;
|
||||
using Francesco.Recipes.World.Models.BackendModels.RecipeIngredient;
|
||||
using Francesco.Recipes.World.Repositories.Ingredient;
|
||||
using Francesco.Recipes.World.Repositories.Unit;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
public class RecipeRepository : IRecipeRepository
|
||||
{
|
||||
private readonly FrancescosRecipesWorldDbContext _context;
|
||||
private readonly IIngredientRepository _ingredientRepository;
|
||||
private readonly IUnitRepository _unitRepository;
|
||||
|
||||
public RecipeRepository(
|
||||
FrancescosRecipesWorldDbContext context, IIngredientRepository ingredientRepository, IUnitRepository unitRepository)
|
||||
{
|
||||
_context = context;
|
||||
_ingredientRepository = ingredientRepository;
|
||||
_unitRepository = unitRepository;
|
||||
}
|
||||
|
||||
public async Task<Recipe> GetRecipeAsync(Guid recipeId)
|
||||
{
|
||||
var recipe = await _context.Recipes.FindAsync(recipeId);
|
||||
return recipe ?? throw new InvalidDataException($"Address {recipeId} not found.");
|
||||
}
|
||||
|
||||
public async Task<Recipe?> GetRecipeByIdAsync(Guid recipeId)
|
||||
{
|
||||
return await _context.Recipes
|
||||
.Include(r => r.RecipeIngredients)
|
||||
.ThenInclude(ri => ri.Ingredient)
|
||||
.Include(r => r.RecipeIngredients)
|
||||
.ThenInclude(ri => ri.Unit)
|
||||
.Include(r => r.MediaFiles)
|
||||
.Include(r => r.Instructions)
|
||||
.ThenInclude(i => i.MediaFiles)
|
||||
.FirstOrDefaultAsync(r => r.Id == recipeId);
|
||||
}
|
||||
|
||||
public async Task CreateRecipeIngredientAsync(Guid recipeId, string ingredientName, int quantity, Guid unitId)
|
||||
{
|
||||
var recipe = await GetRecipeAsync(recipeId);
|
||||
var unit = await _unitRepository.GetUnitByIdAsync(unitId);
|
||||
|
||||
if (quantity <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(quantity), "Die Menge muss größer als 0 sein.");
|
||||
}
|
||||
|
||||
var ingredients = await _ingredientRepository.GetIngredientsByNameAsync(ingredientName);
|
||||
var exactMatch = ingredients
|
||||
.FirstOrDefault(i => i.Name.Equals(ingredientName, StringComparison.OrdinalIgnoreCase));
|
||||
Ingredient ingredient;
|
||||
|
||||
if (exactMatch == null)
|
||||
{
|
||||
ingredient = new Ingredient
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = ingredientName,
|
||||
};
|
||||
_context.Ingredients.Add(ingredient);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
ingredient = exactMatch;
|
||||
}
|
||||
|
||||
var existingEntry = await _context.RecipeIngredients
|
||||
.FirstOrDefaultAsync(ri => ri.Recipe.Id == recipeId && ri.Ingredient.Id == ingredient.Id);
|
||||
if (existingEntry != null)
|
||||
{
|
||||
throw new InvalidOperationException("Das Rezept enthält diese Zutat bereits.");
|
||||
}
|
||||
|
||||
var recipeIngredient = new RecipeIngredient
|
||||
{
|
||||
Recipe = recipe,
|
||||
Ingredient = ingredient,
|
||||
Unit = unit,
|
||||
Quantity = quantity,
|
||||
};
|
||||
|
||||
_context.Add(recipeIngredient);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<Recipe> CreateRecipeForCategoryAsync(
|
||||
Category category,
|
||||
string name,
|
||||
string description,
|
||||
Difficulty difficulty,
|
||||
int servings,
|
||||
TimeSpan preparationTime,
|
||||
TimeSpan cookingTime)
|
||||
{
|
||||
if (category == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(category), "Category cannot be null.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name), "Name cannot be empty.");
|
||||
}
|
||||
|
||||
if (servings <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(servings), "Servings must be greater than 0.");
|
||||
}
|
||||
|
||||
var recipe = new Recipe
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = name,
|
||||
Description = description,
|
||||
Difficulty = difficulty,
|
||||
Servings = servings,
|
||||
PreparationTime = preparationTime,
|
||||
CookingTime = cookingTime,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
Category = category,
|
||||
};
|
||||
|
||||
_context.Recipes.Add(recipe);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return recipe;
|
||||
}
|
||||
|
||||
public async Task RemoveIngredientFromRecipeAsync(Guid recipeId, Guid ingredientId)
|
||||
{
|
||||
var recipeIngredient = await _context.RecipeIngredients
|
||||
.FirstOrDefaultAsync(ri => ri.Recipe.Id == recipeId && ri.Ingredient.Id == ingredientId);
|
||||
|
||||
if (recipeIngredient == null)
|
||||
{
|
||||
throw new ArgumentException("Diese Zutat ist nicht mit dem Rezept verknüpft.");
|
||||
}
|
||||
|
||||
_context.RecipeIngredients.Remove(recipeIngredient);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SearchViewModel>> SearchInRecipesAndIngredients(string searchTerm)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(searchTerm))
|
||||
{
|
||||
return await _context.Recipes
|
||||
.OrderByDescending(r => r.CreatedAt)
|
||||
.Take(20)
|
||||
.Select(SearchViewModelSelector())
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
var normalizedSearchTerm = searchTerm.ToLower();
|
||||
|
||||
return await ApplyRecipeSearchFilter(_context.Recipes, normalizedSearchTerm)
|
||||
.OrderByDescending(r => r.CreatedAt)
|
||||
.Take(100)
|
||||
.Select(SearchViewModelSelector())
|
||||
.ToListAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error in SearchInRecipesAndIngredientsOptimized: {ex.Message}");
|
||||
return new List<SearchViewModel>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<Recipe>> GetRecipesByDifficultyAsync(Difficulty? difficulty)
|
||||
{
|
||||
if (!difficulty.HasValue)
|
||||
{
|
||||
return await _context.Recipes
|
||||
.Include(r => r.Category)
|
||||
.Include(r => r.RecipeIngredients)
|
||||
.ThenInclude(ri => ri.Ingredient)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
return await _context.Recipes
|
||||
.Where(r => r.Difficulty == difficulty.Value)
|
||||
.Include(r => r.Category)
|
||||
.Include(r => r.RecipeIngredients)
|
||||
.ThenInclude(ri => ri.Ingredient)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteRecipeAsync(Guid recipeId)
|
||||
{
|
||||
var recipe = await GetRecipeByIdAsync(recipeId);
|
||||
|
||||
if (recipe == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (recipe.RecipeIngredients != null && recipe.RecipeIngredients.Any())
|
||||
{
|
||||
_context.RecipeIngredients.RemoveRange(recipe.RecipeIngredients);
|
||||
}
|
||||
|
||||
if (recipe.Instructions != null && recipe.Instructions.Any())
|
||||
{
|
||||
foreach (var instruction in recipe.Instructions)
|
||||
{
|
||||
if (instruction.MediaFiles != null && instruction.MediaFiles.Any())
|
||||
{
|
||||
_context.MediaFiles.RemoveRange(instruction.MediaFiles);
|
||||
}
|
||||
}
|
||||
|
||||
_context.Instructions.RemoveRange(recipe.Instructions);
|
||||
}
|
||||
|
||||
if (recipe.MediaFiles != null && recipe.MediaFiles.Any())
|
||||
{
|
||||
_context.MediaFiles.RemoveRange(recipe.MediaFiles);
|
||||
}
|
||||
|
||||
if (recipe.Favorite != null && recipe.Favorite.Id != Guid.Empty)
|
||||
{
|
||||
_context.Remove(recipe.Favorite);
|
||||
}
|
||||
|
||||
_context.Recipes.Remove(recipe);
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Expression<Func<Recipe, SearchViewModel>> SearchViewModelSelector()
|
||||
{
|
||||
return r => new SearchViewModel
|
||||
{
|
||||
Id = r.Id,
|
||||
Name = r.Name,
|
||||
Description = r.Description,
|
||||
IsFavorite = r.IsFavorite,
|
||||
ImageData = r.MediaFiles
|
||||
.Where(m => m.MimeType != null && m.MimeType.StartsWith(ContentType.Image))
|
||||
.Select(m => m.Data)
|
||||
.FirstOrDefault(),
|
||||
MimeType = r.MediaFiles
|
||||
.Where(m => m.MimeType != null && m.MimeType.StartsWith(ContentType.Image))
|
||||
.Select(m => m.MimeType)
|
||||
.FirstOrDefault(),
|
||||
Ingredients = r.RecipeIngredients.Select(ri => ri.Ingredient.Name).ToList(),
|
||||
TotalTime = r.PreparationTime.Add(r.CookingTime),
|
||||
};
|
||||
}
|
||||
|
||||
private static IQueryable<Recipe> ApplyRecipeSearchFilter(IQueryable<Recipe> query, string normalizedSearchTerm)
|
||||
{
|
||||
return query.Where(r =>
|
||||
EF.Functions.Like(r.Name.ToLower(), $"%{normalizedSearchTerm}%") ||
|
||||
(r.Description != null && EF.Functions.Like(r.Description.ToLower(), $"%{normalizedSearchTerm}%")) ||
|
||||
r.RecipeIngredients.Any(ri => EF.Functions.Like(ri.Ingredient.Name.ToLower(), $"%{normalizedSearchTerm}%")));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Francesco.Recipes.World.Repositories.ShoppingList
|
||||
{
|
||||
using Francesco.Recipes.World.Models.BackendModels.Shoppinglist;
|
||||
|
||||
public interface IShoppingListRepository
|
||||
{
|
||||
Task<ShoppingList> AddIngredientsToShoppingListAsync(Guid recipeId, List<Guid> ingredientIds);
|
||||
|
||||
Task RemoveRecipeIfEmptyAsync(Guid shoppingListRecipeId);
|
||||
|
||||
Task<int> CountAllRecipeShoppinglistsAsync();
|
||||
|
||||
Task<IList<ShoppingList>> GetAllShoppingListsAsync();
|
||||
|
||||
Task RemoveIngredientsFromShoppingListAsync(List<Guid> recipeIngredientShoppngListIds);
|
||||
|
||||
Task RemoveRecipeFromShoppingListAsync(Guid recipeShoppingListId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
namespace Francesco.Recipes.World.Repositories.ShoppingList
|
||||
{
|
||||
using Francesco.Recipes.World.Data;
|
||||
using Francesco.Recipes.World.Models.BackendModels.IngredientShoppingList;
|
||||
using Francesco.Recipes.World.Models.BackendModels.RecipeShoppingList;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Shoppinglist;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
public class ShoppingListRepository : IShoppingListRepository
|
||||
{
|
||||
private readonly FrancescosRecipesWorldDbContext _context;
|
||||
|
||||
public ShoppingListRepository(FrancescosRecipesWorldDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<ShoppingList> AddIngredientsToShoppingListAsync(Guid recipeId, List<Guid> ingredientIds)
|
||||
{
|
||||
if (ingredientIds == null || !ingredientIds.Any())
|
||||
{
|
||||
throw new ArgumentNullException(nameof(ingredientIds));
|
||||
}
|
||||
|
||||
var recipe = await _context.Recipes
|
||||
.Include(r => r.RecipeIngredients)
|
||||
.FirstOrDefaultAsync(r => r.Id == recipeId);
|
||||
|
||||
if (recipe == null)
|
||||
{
|
||||
throw new Exception("Recipe not found.");
|
||||
}
|
||||
|
||||
var shoppingList = await _context.ShoppingLists
|
||||
.Include(sl => sl.RecipeShoppingList)
|
||||
.ThenInclude(rsl => rsl.SelectedIngredients)
|
||||
.Include(sl => sl.RecipeShoppingList)
|
||||
.ThenInclude(rsl => rsl.Recipe)
|
||||
.FirstOrDefaultAsync(sl => sl.RecipeShoppingList.Any(rsl => rsl.Recipe.Id == recipeId));
|
||||
|
||||
var recipeIngredients = await _context.RecipeIngredients
|
||||
.Where(ri => ingredientIds.Contains(ri.Id) && ri.Recipe.Id == recipeId)
|
||||
.ToListAsync();
|
||||
|
||||
if (!recipeIngredients.Any())
|
||||
{
|
||||
throw new Exception("No valid ingredients found.");
|
||||
}
|
||||
|
||||
if (shoppingList == null)
|
||||
{
|
||||
shoppingList = new ShoppingList
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
RecipeShoppingList = new List<RecipeShoppingList>(),
|
||||
};
|
||||
|
||||
var newRecipeList = new RecipeShoppingList
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Recipe = recipe,
|
||||
SelectedIngredients = recipeIngredients.Select(ri => new RecipeIngredientShoppingList
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
RecipeIngredient = ri,
|
||||
IsChecked = false,
|
||||
}).ToList(),
|
||||
};
|
||||
|
||||
shoppingList.RecipeShoppingList.Add(newRecipeList);
|
||||
_context.ShoppingLists.Add(shoppingList);
|
||||
}
|
||||
else
|
||||
{
|
||||
var existingRecipeList = shoppingList.RecipeShoppingList
|
||||
.FirstOrDefault(rsl => rsl.Recipe.Id == recipeId);
|
||||
|
||||
if (existingRecipeList == null)
|
||||
{
|
||||
existingRecipeList = new RecipeShoppingList
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Recipe = recipe,
|
||||
SelectedIngredients = new List<RecipeIngredientShoppingList>(),
|
||||
};
|
||||
|
||||
shoppingList.RecipeShoppingList.Add(existingRecipeList);
|
||||
}
|
||||
|
||||
foreach (var ri in recipeIngredients)
|
||||
{
|
||||
if (!existingRecipeList.SelectedIngredients.Any(si => si.RecipeIngredient.Id == ri.Id))
|
||||
{
|
||||
existingRecipeList.SelectedIngredients.Add(new RecipeIngredientShoppingList
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
RecipeIngredient = ri,
|
||||
IsChecked = false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
shoppingList.ModifiedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
return shoppingList;
|
||||
}
|
||||
|
||||
public async Task<IList<ShoppingList>> GetAllShoppingListsAsync()
|
||||
{
|
||||
return await GetShoppingListQuery()
|
||||
.OrderByDescending(sl => sl.CreatedAt)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task RemoveRecipeIfEmptyAsync(Guid shoppingListRecipeId)
|
||||
{
|
||||
var recipeEntry = await _context.RecipeShoppingLists
|
||||
.Include(r => r.SelectedIngredients)
|
||||
.Include(r => r.ShoppingList)
|
||||
.ThenInclude(sl => sl.RecipeShoppingList)
|
||||
.FirstOrDefaultAsync(r => r.Id == shoppingListRecipeId);
|
||||
|
||||
if (recipeEntry != null && !recipeEntry.SelectedIngredients.Any())
|
||||
{
|
||||
var shoppingList = recipeEntry.ShoppingList;
|
||||
|
||||
_context.RecipeShoppingLists.Remove(recipeEntry);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
if (shoppingList != null && (shoppingList.RecipeShoppingList == null || !shoppingList.RecipeShoppingList.Any()))
|
||||
{
|
||||
_context.ShoppingLists.Remove(shoppingList);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RemoveRecipeFromShoppingListAsync(Guid recipeShoppingListId)
|
||||
{
|
||||
var recipeEntry = await _context.RecipeShoppingLists
|
||||
.Include(r => r.SelectedIngredients)
|
||||
.Include(r => r.ShoppingList)
|
||||
.ThenInclude(sl => sl.RecipeShoppingList)
|
||||
.FirstOrDefaultAsync(r => r.Id == recipeShoppingListId);
|
||||
|
||||
if (recipeEntry != null)
|
||||
{
|
||||
var shoppingList = recipeEntry.ShoppingList;
|
||||
|
||||
_context.RecipeIngredientsShoppingLists.RemoveRange(recipeEntry.SelectedIngredients);
|
||||
|
||||
_context.RecipeShoppingLists.Remove(recipeEntry);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
if (shoppingList != null)
|
||||
{
|
||||
await _context.Entry(shoppingList).Collection(sl => sl.RecipeShoppingList).LoadAsync();
|
||||
|
||||
if (!shoppingList.RecipeShoppingList.Any())
|
||||
{
|
||||
_context.ShoppingLists.Remove(shoppingList);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> CountAllRecipeShoppinglistsAsync()
|
||||
{
|
||||
return await _context.RecipeShoppingLists
|
||||
.CountAsync();
|
||||
}
|
||||
|
||||
public async Task RemoveIngredientsFromShoppingListAsync(List<Guid> recipeIngredientShoppingListIds)
|
||||
{
|
||||
if (recipeIngredientShoppingListIds == null || !recipeIngredientShoppingListIds.Any())
|
||||
{
|
||||
throw new ArgumentNullException(nameof(recipeIngredientShoppingListIds));
|
||||
}
|
||||
|
||||
using var transaction = await _context.Database.BeginTransactionAsync();
|
||||
|
||||
try
|
||||
{
|
||||
var affectedRecipeShoppingListIds = await _context.RecipeIngredientsShoppingLists
|
||||
.Where(risl => recipeIngredientShoppingListIds.Contains(risl.Id))
|
||||
.Select(risl => risl.RecipeShoppingList.Id)
|
||||
.Distinct()
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var id in recipeIngredientShoppingListIds)
|
||||
{
|
||||
var entry = await _context.RecipeIngredientsShoppingLists.FindAsync(id);
|
||||
if (entry != null)
|
||||
{
|
||||
_context.RecipeIngredientsShoppingLists.Remove(entry);
|
||||
}
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
foreach (var recipeShoppingListId in affectedRecipeShoppingListIds)
|
||||
{
|
||||
await RemoveRecipeIfEmptyAsync(recipeShoppingListId);
|
||||
}
|
||||
|
||||
await transaction.CommitAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
await transaction.RollbackAsync();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private IQueryable<ShoppingList> GetShoppingListQuery()
|
||||
{
|
||||
return _context.ShoppingLists
|
||||
.Include(sl => sl.RecipeShoppingList)
|
||||
.ThenInclude(rsl => rsl.Recipe)
|
||||
.ThenInclude(r => r.MediaFiles)
|
||||
.Include(sl => sl.RecipeShoppingList)
|
||||
.ThenInclude(rsl => rsl.SelectedIngredients)
|
||||
.ThenInclude(si => si.RecipeIngredient)
|
||||
.ThenInclude(ri => ri.Ingredient)
|
||||
.Include(sl => sl.RecipeShoppingList)
|
||||
.ThenInclude(rsl => rsl.SelectedIngredients)
|
||||
.ThenInclude(si => si.RecipeIngredient)
|
||||
.ThenInclude(ri => ri.Unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Francesco.Recipes.World.Repositories.Unit
|
||||
{
|
||||
using Francesco.Recipes.World.Models.BackendModels.Unit;
|
||||
|
||||
public interface IUnitRepository
|
||||
{
|
||||
Task<Unit> GetUnitByIdAsync(Guid unitId);
|
||||
|
||||
Task<Unit> AddUnitAsync(string name, string symbol);
|
||||
|
||||
Task<List<Unit>> GetAllUnitsAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
namespace Francesco.Recipes.World.Repositories.Unit
|
||||
{
|
||||
using Francesco.Recipes.World.Data;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Unit;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
public class UnitRepository : IUnitRepository
|
||||
{
|
||||
private readonly FrancescosRecipesWorldDbContext _context;
|
||||
|
||||
public UnitRepository(
|
||||
FrancescosRecipesWorldDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<Unit> GetUnitByIdAsync(Guid unitId)
|
||||
{
|
||||
var unit = await _context.Units.FindAsync(unitId);
|
||||
return unit ?? throw new InvalidDataException($"Address {unitId} not found.");
|
||||
}
|
||||
|
||||
public async Task<Unit> AddUnitAsync(string name, string symbol)
|
||||
{
|
||||
var unit = new Unit
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = name,
|
||||
Symbol = symbol,
|
||||
};
|
||||
|
||||
_context.Units.Add(unit);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return unit;
|
||||
}
|
||||
|
||||
public async Task<List<Unit>> GetAllUnitsAsync()
|
||||
{
|
||||
return await _context.Units.ToListAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Francesco.Recipes.World.Services.Instruction
|
||||
{
|
||||
using Francesco.Recipes.World.Models.BackendModels.Instruction;
|
||||
|
||||
public interface IInstructionService
|
||||
{
|
||||
Task MoveInstructionUpAsync(Guid recipeId, Guid instructionId);
|
||||
|
||||
Task MoveInstructionDownAsync(Guid recipeId, Guid instructionId);
|
||||
|
||||
Task<List<Instruction>> GetSortedInstructionsAsync(Guid recipeId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
namespace Francesco.Recipes.World.Services.Instruction
|
||||
{
|
||||
using Francesco.Recipes.World.Models.BackendModels.Instruction;
|
||||
using Francesco.Recipes.World.Repositories.Instruction;
|
||||
|
||||
public class InstructionService : IInstructionService
|
||||
{
|
||||
private readonly IInstructionRepository _instructionRepository;
|
||||
|
||||
public InstructionService(IInstructionRepository instructionRepository)
|
||||
{
|
||||
_instructionRepository = instructionRepository;
|
||||
}
|
||||
|
||||
public Task MoveInstructionUpAsync(Guid recipeId, Guid instructionId)
|
||||
=> MoveInstructionAsync(recipeId, instructionId, moveUp: true);
|
||||
|
||||
public Task MoveInstructionDownAsync(Guid recipeId, Guid instructionId)
|
||||
=> MoveInstructionAsync(recipeId, instructionId, moveUp: false);
|
||||
|
||||
public async Task<List<Instruction>> GetSortedInstructionsAsync(Guid recipeId)
|
||||
{
|
||||
var instructions = await _instructionRepository.GetInstructionsOfRecipeAsync(recipeId);
|
||||
return instructions.OrderBy(i => i.Number).ToList();
|
||||
}
|
||||
|
||||
private async Task MoveInstructionAsync(Guid recipeId, Guid instructionId, bool moveUp)
|
||||
{
|
||||
var instructions = await _instructionRepository.GetInstructionsOfRecipeAsync(recipeId);
|
||||
|
||||
var instruction = instructions.FirstOrDefault(i => i.Id == instructionId);
|
||||
|
||||
if (instruction == null)
|
||||
{
|
||||
throw new InvalidDataException($"Instruction with ID {instructionId} not found.");
|
||||
}
|
||||
|
||||
var minStep = 1;
|
||||
var maxStep = instructions.Max(i => i.Number);
|
||||
|
||||
if ((moveUp && instruction.Number == minStep) || (!moveUp && instruction.Number >= maxStep))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Instructions are ordered by ascending numbers (1, 2, 3, ...).
|
||||
// Moving up means swapping with the instruction that has one number less (Number - 1).
|
||||
// Moving down means swapping with the instruction that has one number more (Number + 1).
|
||||
var targetNumber = moveUp ? instruction.Number - 1 : instruction.Number + 1;
|
||||
var neighbor = instructions.FirstOrDefault(i => i.Number == targetNumber);
|
||||
|
||||
if (neighbor != null)
|
||||
{
|
||||
await _instructionRepository.SwapInstructionNumbersAsync(instruction, neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Francesco.Recipes.World.Views.Category
|
||||
{
|
||||
using Francesco.Recipes.World.Models;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Category;
|
||||
|
||||
public class CategoryRecipesViewModel
|
||||
{
|
||||
public Category Category { get; set; } = new ();
|
||||
|
||||
public IEnumerable<RecipeCardViewModel> Recipes { get; set; } = new List<RecipeCardViewModel>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
@model Francesco.Recipes.World.Models.BackendModels.Category.Category
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Category Details";
|
||||
}
|
||||
|
||||
<h2>Category Details</h2>
|
||||
|
||||
<div>
|
||||
<h4>Category</h4>
|
||||
<hr />
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">
|
||||
Name
|
||||
</dt>
|
||||
<dd class="col-sm-10">
|
||||
@Model.Name
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div>
|
||||
<a asp-action="Index" class="btn btn-primary">Back to List</a>
|
||||
</div>
|
||||
@@ -0,0 +1,28 @@
|
||||
@model IEnumerable<Francesco.Recipes.World.Models.BackendModels.Category.Category>
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Categories";
|
||||
}
|
||||
|
||||
<h2>Categories</h2>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var category in Model)
|
||||
{
|
||||
<tr>
|
||||
<td>@category.Name</td>
|
||||
<td>
|
||||
<a asp-action="Details" asp-route-id="@category.Id" class="btn btn-primary">Details</a>
|
||||
<a asp-action="Recipes" asp-route-id="@category.Id" class="btn btn-secondary">Recipes</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -0,0 +1,77 @@
|
||||
@model Francesco.Recipes.World.Models.FavoriteViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Favoriten";
|
||||
}
|
||||
|
||||
<h2 class="mb-4">Favoriten</h2>
|
||||
|
||||
<div class="mb-4 text-end">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button" id="sortDropdown"
|
||||
data-bs-toggle="dropdown" aria-expanded="false">
|
||||
@Model.SortOrderDisplayText
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="sortDropdown">
|
||||
<li>
|
||||
<a class="dropdown-item @(Model.SortOrder == "newest" ? "active" : "")"
|
||||
href="@Url.Action("Index", new { sortOrder = "newest" })">Neueste Favorits</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item @(Model.SortOrder == "oldest" ? "active" : "")"
|
||||
href="@Url.Action("Index", new { sortOrder = "oldest" })">Älteste Favorits</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (!Model.HasFavorites)
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
Keine Favoriten vorhanden. Füge Rezepte zu deinen Favoriten hinzu, indem du auf den Stern klickst.
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row row-cols-1 row-cols-md-3 g-4">
|
||||
@foreach (var recipe in Model.FavoriteRecipes)
|
||||
{
|
||||
<div class="col">
|
||||
<div class="card h-100">
|
||||
@{
|
||||
var mediaFile = recipe.MediaFiles?.FirstOrDefault(m => m.MimeType != null && m.MimeType.StartsWith("image/"));
|
||||
var imageData = mediaFile?.Data;
|
||||
var mimeType = mediaFile?.MimeType;
|
||||
}
|
||||
|
||||
<div class="card-img-top text-center bg-light" style="height:200px; display:flex; align-items:center; justify-content:center;">
|
||||
@if (imageData != null && mimeType != null)
|
||||
{
|
||||
<img src="data:@mimeType;base64,@Convert.ToBase64String(imageData)"
|
||||
alt="@recipe.Name" class="img-fluid" style="max-height:100%; max-width:100%; object-fit:contain;" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="text-secondary">Kein Bild</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">@recipe.Name</h5>
|
||||
<div class="d-flex align-items-center mt-3">
|
||||
<span class="me-3">
|
||||
@await Html.PartialAsync("_FavoriteButton", recipe)
|
||||
</span>
|
||||
<span class="text-muted">
|
||||
<i class="bi bi-clock"></i> @(recipe.PreparationTime.TotalMinutes + recipe.CookingTime.TotalMinutes)min
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-white">
|
||||
<a href="@Url.Action("Details", "Recipe", new { recipeId = recipe.Id })"
|
||||
class="btn btn-outline-primary btn-sm w-100">Details</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
@model IEnumerable<Francesco.Recipes.World.Views.Category.CategoryRecipesViewModel>
|
||||
@Html.AntiForgeryToken()
|
||||
|
||||
<div class="welcome-banner text-center mb-4">
|
||||
<img src="/images/banner2.jpg" alt="Willkommen" class="img-fluid" />
|
||||
<h1 class="mt-3">Willkommen in der Rezept-App</h1>
|
||||
</div>
|
||||
|
||||
|
||||
<form hx-get="/Home/Search"
|
||||
hx-target="#search-results"
|
||||
hx-trigger="keyup changed delay:300ms"
|
||||
onsubmit="return false;"
|
||||
class="mb-4">
|
||||
<input type="text" name="term" class="form-control" placeholder="Suchen nach Rezepten oder Zutaten..." autocomplete="off" />
|
||||
</form>
|
||||
|
||||
|
||||
<div id="search-results" class="mt-4"></div>
|
||||
|
||||
|
||||
@foreach (var category in Model)
|
||||
{
|
||||
<div class="category-section mb-5" id="category-@category.Category.Id">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h2>@category.Category.Name</h2>
|
||||
<a href="/Category/Details/@category.Category.Id"
|
||||
class="btn btn-link"
|
||||
hx-get="/Category/Details/@category.Category.Id"
|
||||
hx-target="#category-@category.Category.Id"
|
||||
hx-swap="outerHTML">Alle @category.Category.Name-Rezepte anzeigen</a>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
@foreach (var recipe in category.Recipes)
|
||||
{
|
||||
var imageData = recipe.ImageData;
|
||||
var mimeType = recipe.MimeType;
|
||||
|
||||
<div class="col-md-3 mb-4">
|
||||
<div class="card h-100">
|
||||
<div class="recipe-image">
|
||||
@if (imageData != null && mimeType != null)
|
||||
{
|
||||
<img src="data:@mimeType;base64,@Convert.ToBase64String(imageData)" alt="@recipe.Name" class="card-img-top" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<img src="/images/placeholder.png" alt="@recipe.Name" class="card-img-top" />
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="card-body d-flex flex-column">
|
||||
<h5 class="card-title">@recipe.Name</h5>
|
||||
<p class="card-text text-muted mb-2">@recipe.CookingTime</p>
|
||||
<div id="favorite-button-@recipe.Id">
|
||||
@await Html.PartialAsync("_FavoriteButton", recipe)
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<a href="/Details/@recipe.Id"
|
||||
class="btn btn-primary mt-auto">Details</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
<div class="col-md-3 mb-4">
|
||||
<div class="card h-100 text-center">
|
||||
<div class="card-body d-flex flex-column justify-content-center">
|
||||
<a href="/Create/@category.Category.Id"
|
||||
class="btn btn-outline-primary">Rezept hinzufügen</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
@{
|
||||
ViewData["Title"] = "Privacy Policy";
|
||||
}
|
||||
<h1>@ViewData["Title"]</h1>
|
||||
|
||||
<p>Use this page to detail your site's privacy policy.</p>
|
||||
@@ -0,0 +1,75 @@
|
||||
@model IEnumerable<Francesco.Recipes.World.Models.SearchViewModel>
|
||||
|
||||
@{
|
||||
if (!Model.Any())
|
||||
{
|
||||
<p class="text-muted">Keine Ergebnisse gefunden.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
@foreach (var recipe in Model)
|
||||
{
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="card h-100 shadow-sm">
|
||||
<div class="recipe-image">
|
||||
@if (recipe.ImageData != null && recipe.MimeType != null)
|
||||
{
|
||||
<img src="data:@recipe.MimeType;base64,@Convert.ToBase64String(recipe.ImageData)"
|
||||
alt="@recipe.Name"
|
||||
class="card-img-top" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<img src="~/images/placeholder.png"
|
||||
alt="Platzhalter"
|
||||
class="card-img-top" />
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="card-body d-flex flex-column justify-content-between">
|
||||
<div>
|
||||
<h5 class="card-title">@recipe.Name</h5>
|
||||
<p class="card-text text-muted mb-2">
|
||||
<i class="bi bi-clock"></i>
|
||||
@recipe.TotalTime.Hours h @recipe.TotalTime.Minutes min
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
||||
<div class="favorite-button-container">
|
||||
<form hx-post="@Url.Action(recipe.IsFavorite ? "RemoveFavorite" : "AddFavorite", "Recipe")"
|
||||
hx-target="this"
|
||||
hx-swap="outerHTML">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" name="recipeId" value="@recipe.Id" />
|
||||
<button type="submit" class="btn btn-link p-0" style="width: 40px; height: 40px;">
|
||||
@if (recipe.IsFavorite)
|
||||
{
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#FFD700">
|
||||
<path d="M12 17.27L18.18 21 16.54 13.97
|
||||
22 9.24l-7.19-.62L12 2 9.19 8.62
|
||||
2 9.24l5.46 4.73L5.82 21z" />
|
||||
</svg>
|
||||
}
|
||||
else
|
||||
{
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 17.27L18.18 21 16.54 13.97
|
||||
22 9.24l-7.19-.62L12 2 9.19 8.62
|
||||
2 9.24l5.46 4.73L5.82 21z" />
|
||||
</svg>
|
||||
}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<a href="/Details/@recipe.Id" class="btn btn-primary btn-sm">Details</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
@model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Add Instructions";
|
||||
}
|
||||
|
||||
<h1>Add Instructions to @Model.Name</h1>
|
||||
|
||||
<div class="instructions-list">
|
||||
<h3>Existing Instructions</h3>
|
||||
<ul>
|
||||
@foreach (var instruction in Model.Instructions.OrderBy(i => i.Number))
|
||||
{
|
||||
<li>@instruction.Number. @instruction.Description</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="add-instruction-form">
|
||||
<h3>Add New Instruction</h3>
|
||||
<form id="instruction-form" method="post" asp-action="AddInstruction" asp-controller="Recipe">
|
||||
<input type="hidden" name="recipeId" value="@Model.Id" />
|
||||
<div class="form-group">
|
||||
<label for="description">Description</label>
|
||||
<input type="text" class="form-control" id="description" name="description" required />
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" onclick="addInstructionToRecipe()">Add Instruction</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
async function addInstructionToRecipe() {
|
||||
event.preventDefault();
|
||||
|
||||
var form = document.getElementById('instruction-form');
|
||||
var recipeId = form.querySelector('input[name="recipeId"]').value;
|
||||
var description = form.querySelector('input[name="description"]').value;
|
||||
var token = form.querySelector('input[name="__RequestVerificationToken"]').value;
|
||||
|
||||
var response = await fetch('/' + recipeId + '/AddInstruction', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'RequestVerificationToken': token
|
||||
},
|
||||
body: `recipeId=${encodeURIComponent(recipeId)}&description=${encodeURIComponent(description)}`
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Failed to add instruction.');
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
@model Francesco.Recipes.World.Models.CreateRecipeViewModel
|
||||
@using Francesco.Recipes.World.Models.BackendModels.Recipe
|
||||
@using Francesco.Recipes.World.Models
|
||||
@{
|
||||
ViewData["Title"] = "Erstelle Rezept";
|
||||
}
|
||||
|
||||
<h1 class="mb-4">Erstelle Rezept</h1>
|
||||
|
||||
<form method="post" enctype="multipart/form-data" asp-controller="Recipe" asp-action="Create" asp-route-categoryId="@Model.CategoryId">
|
||||
<div class="mb-4">
|
||||
<h2>Allgemein</h2>
|
||||
<div class="form-group">
|
||||
<label asp-for="Name">Name</label>
|
||||
<input asp-for="Name" class="form-control" required />
|
||||
<span asp-validation-for="Name" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Description">Beschreibung</label>
|
||||
<textarea asp-for="Description" class="form-control"></textarea>
|
||||
<span asp-validation-for="Description" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Difficulty">Schwierigkeit</label>
|
||||
<select asp-for="Difficulty" class="form-control" required>
|
||||
<option value="">Schwierigkeit wählen</option>
|
||||
@foreach (var difficulty in Enum.GetValues(typeof(Difficulty)))
|
||||
{
|
||||
<option value="@difficulty">@difficulty</option>
|
||||
}
|
||||
</select>
|
||||
<span asp-validation-for="Difficulty" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="col-sm-4">
|
||||
<label asp-for="Servings">Portion (pro Person)</label>
|
||||
<input asp-for="Servings" type="number" min="1" class="form-control" required />
|
||||
<span asp-validation-for="Servings" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<label>Vorbereitungszeit</label>
|
||||
<div class="d-flex">
|
||||
<div class="input-group mr-2">
|
||||
<input asp-for="PrepHours" type="number" class="form-control" min="0" value="0" />
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">h</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input asp-for="PrepMinutes" type="number" class="form-control" min="0" max="59" value="0" />
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">min</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<label>Kochzeit</label>
|
||||
<div class="d-flex">
|
||||
<div class="input-group mr-2">
|
||||
<input asp-for="CookHours" type="number" class="form-control" min="0" value="0" />
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">h</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input asp-for="CookMinutes" type="number" class="form-control" min="0" max="59" value="0" />
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">min</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h2>Zutaten</h2>
|
||||
@await Html.PartialAsync("_IngredientsPartial", Model.IngredientViewModel ?? new IngredientViewModel
|
||||
{
|
||||
RecipeId = Model.CategoryId,
|
||||
Ingredients = new List<Francesco.Recipes.World.Models.BackendModels.Ingredient.Ingredient>(),
|
||||
Units = ViewBag.Units ?? new List<Francesco.Recipes.World.Models.BackendModels.Unit.Unit>()
|
||||
})
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h2>Anweisungen</h2>
|
||||
@await Html.PartialAsync("_GetInstructions", Model.InstructionViewModel ?? new InstructionViewModel
|
||||
{
|
||||
RecipeId = Model.CategoryId,
|
||||
Instructions = new List<Francesco.Recipes.World.Models.BackendModels.Instruction.Instruction>()
|
||||
})
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h2>Bild/Video</h2>
|
||||
<div class="form-group">
|
||||
<label asp-for="Photo">Foto</label>
|
||||
<input asp-for="Photo" class="form-control-file" accept="image/*" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Video">Video</label>
|
||||
<input asp-for="Video" class="form-control-file" accept="video/*" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-4">
|
||||
<button type="submit" class="btn btn-success btn-lg">Rezept speichern</button>
|
||||
<a asp-action="Index" asp-controller="Home" class="btn btn-secondary btn-lg ml-2">Abbrechen</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
@model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Recipe Details";
|
||||
}
|
||||
|
||||
<h1>@Model.Name</h1>
|
||||
|
||||
<div class="recipe-details" data-recipe-id="@Model.Id">
|
||||
<div class="recipe-image">
|
||||
@if (Model.MediaFiles.Any() && Model.MediaFiles.First().Data != null)
|
||||
{
|
||||
var mediaFile = Model.MediaFiles.First();
|
||||
if (mediaFile.Data != null)
|
||||
{
|
||||
<img src="data:@mediaFile.MimeType;base64,@Convert.ToBase64String(mediaFile.Data)" alt="@Model.Name" class="img-fluid" />
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<div class="recipe-info">
|
||||
<p><strong>Description:</strong> @Model.Description</p>
|
||||
<p><strong>Difficulty:</strong> @Model.Difficulty</p>
|
||||
<p><strong>Preparation Time:</strong> @Model.PreparationTime</p>
|
||||
<p><strong>Cooking Time:</strong> @Model.CookingTime</p>
|
||||
</div>
|
||||
|
||||
@await Html.PartialAsync("_AdjustableIngredientsPartial", Model)
|
||||
|
||||
@await Html.PartialAsync("_RecipeInstructionGridPartial", new Francesco.Recipes.World.Models.InstructionViewModel
|
||||
{
|
||||
RecipeId = Model.Id,
|
||||
Instructions = Model.Instructions.ToList()
|
||||
})
|
||||
|
||||
<form hx-delete="@($"/{Model.Id}/Delete")"
|
||||
hx-confirm="Sind Sie sicher, dass Sie dieses Rezept löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden."
|
||||
hx-redirect="/">
|
||||
@Html.AntiForgeryToken()
|
||||
<button type="submit" class="btn btn-danger">
|
||||
<i class="bi bi-trash"></i> Rezept löschen
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const recipeId = '@Model.Id';
|
||||
window.recipeId = recipeId;
|
||||
|
||||
const originalServings = @Model.Servings;
|
||||
|
||||
const specialUnits = {
|
||||
discreteUnits: ["stk", "zehe", "blatt", "bund", "stange"],
|
||||
smallUnits: ["msp"],
|
||||
spoonUnits: {
|
||||
"TL": { name: "teelöffel", toMl: 5 },
|
||||
"EL": { name: "esslöffel", toMl: 15 }
|
||||
}
|
||||
};
|
||||
|
||||
const conversionTable = {
|
||||
"knoblauch": {
|
||||
unit: "zehe",
|
||||
smallAmount: 0.5
|
||||
},
|
||||
};
|
||||
|
||||
const savedServings = localStorage.getItem(`recipe_${recipeId}_servings`);
|
||||
if (savedServings) {
|
||||
document.getElementById('servingsInput').value = savedServings;
|
||||
adjustIngredientQuantities(parseInt(savedServings));
|
||||
}
|
||||
|
||||
document.getElementById('decreaseServings').addEventListener('click', function () {
|
||||
const input = document.getElementById('servingsInput');
|
||||
const currentValue = parseInt(input.value);
|
||||
if (currentValue > 1) {
|
||||
input.value = currentValue - 1;
|
||||
adjustIngredientQuantities(currentValue - 1);
|
||||
saveServingsToLocalStorage(currentValue - 1);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('increaseServings').addEventListener('click', function () {
|
||||
const input = document.getElementById('servingsInput');
|
||||
const currentValue = parseInt(input.value);
|
||||
input.value = currentValue + 1;
|
||||
adjustIngredientQuantities(currentValue + 1);
|
||||
saveServingsToLocalStorage(currentValue + 1);
|
||||
});
|
||||
|
||||
document.getElementById('servingsInput').addEventListener('change', function () {
|
||||
const newServings = parseInt(this.value);
|
||||
if (newServings < 1) {
|
||||
this.value = 1;
|
||||
adjustIngredientQuantities(1);
|
||||
saveServingsToLocalStorage(1);
|
||||
} else {
|
||||
adjustIngredientQuantities(newServings);
|
||||
saveServingsToLocalStorage(newServings);
|
||||
}
|
||||
});
|
||||
|
||||
function adjustIngredientQuantities(newServings) {
|
||||
const ingredients = document.querySelectorAll('#adjustable-ingredient-list li');
|
||||
|
||||
ingredients.forEach(ingredient => {
|
||||
const originalQuantity = parseFloat(ingredient.getAttribute('data-original-quantity'));
|
||||
const ingredientName = ingredient.getAttribute('data-ingredient-name').toLowerCase();
|
||||
const unitSymbol = ingredient.getAttribute('data-unit');
|
||||
const unitName = ingredient.getAttribute('data-unit-name');
|
||||
|
||||
let adjustedQuantity = (originalQuantity / originalServings) * newServings;
|
||||
let finalQuantity = adjustedQuantity;
|
||||
let note = "";
|
||||
|
||||
if (specialUnits.discreteUnits.includes(unitSymbol)) {
|
||||
if (adjustedQuantity < 1 && adjustedQuantity > 0) {
|
||||
finalQuantity = Math.ceil(adjustedQuantity);
|
||||
note = " (ggf. eine kleine/halbe nehmen)";
|
||||
} else {
|
||||
finalQuantity = Math.round(adjustedQuantity);
|
||||
}
|
||||
}
|
||||
// Convert small unit "msp" (pinch) to teaspoons (TL) for a better estimate
|
||||
// 4 pinches = about 1 TL → show that as a note, rounded to 1 decimal
|
||||
else if (specialUnits.smallUnits.includes(unitSymbol)) {
|
||||
if (adjustedQuantity > 3) {
|
||||
finalQuantity = Math.round(adjustedQuantity / 4 * 10) / 10;
|
||||
note = ` (ca. ${finalQuantity} TL)`;
|
||||
finalQuantity = adjustedQuantity;
|
||||
} else {
|
||||
finalQuantity = Math.round(adjustedQuantity);
|
||||
}
|
||||
}
|
||||
else if (Object.keys(specialUnits.spoonUnits).includes(unitSymbol)) {
|
||||
if (adjustedQuantity > 4 && unitSymbol === "TL") {
|
||||
const esslöffel = Math.round(adjustedQuantity / 3 * 10) / 10;
|
||||
note = ` (ca. ${esslöffel} EL)`;
|
||||
}
|
||||
finalQuantity = Math.round(adjustedQuantity * 2) / 2;
|
||||
}
|
||||
|
||||
if (ingredientName.includes("knoblauch") && unitSymbol === "zehe" && adjustedQuantity < 1) {
|
||||
finalQuantity = 1;
|
||||
note = " (kleine Zehe)";
|
||||
}
|
||||
|
||||
const formattedQuantity = formatQuantity(finalQuantity);
|
||||
ingredient.querySelector('.ingredient-quantity').textContent = formattedQuantity;
|
||||
|
||||
const noteElement = ingredient.querySelector('.ingredient-note');
|
||||
if (noteElement) {
|
||||
noteElement.textContent = note;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function saveServingsToLocalStorage(servings) {
|
||||
localStorage.setItem(`recipe_${recipeId}_servings`, servings.toString());
|
||||
}
|
||||
|
||||
function formatQuantity(quantity) {
|
||||
if (Number.isInteger(quantity)) {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
if (quantity === 0.5 || quantity === 0.25 || quantity === 0.75) {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
return Math.round(quantity * 10) / 10;
|
||||
}
|
||||
|
||||
window.addSelectedIngredientsToShoppingList = function(recipeIdParam) {
|
||||
const idToUse = recipeIdParam || recipeId;
|
||||
|
||||
var form = document.getElementById('ingredient-form');
|
||||
var formData = new FormData(form);
|
||||
var selectedIngredientIds = formData.getAll('ingredientIds');
|
||||
|
||||
fetch('/ShoppingList/CreateOrAddIngredients', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value
|
||||
},
|
||||
body: JSON.stringify({ recipeId: idToUse, ingredientIds: selectedIngredientIds })
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
throw new Error('Failed to update shopping list');
|
||||
})
|
||||
.then(result => {
|
||||
localStorage.setItem('shoppingListId', result.shoppingListId);
|
||||
const showInfo = (msg) => {
|
||||
if (window.Swal) {
|
||||
Swal.fire({ icon: 'info', title: 'Hinweis', text: msg, timer: 1800, showConfirmButton: false });
|
||||
} else { alert(msg); }
|
||||
};
|
||||
|
||||
if (result.skippedCount && result.skippedCount > 0) {
|
||||
if (result.addedCount && result.addedCount > 0) {
|
||||
showInfo(`Einkaufsliste aktualisiert. ${result.skippedCount} Zutaten waren bereits vorhanden und wurden übersprungen.`);
|
||||
} else {
|
||||
showInfo('Diese Zutaten sind bereits in der Einkaufsliste vorhanden.');
|
||||
}
|
||||
} else {
|
||||
showInfo('Einkaufsliste aktualisiert.');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
if (window.Swal) {
|
||||
Swal.fire({ icon: 'error', title: 'Fehler', text: 'Fehler beim Aktualisieren der Einkaufsliste.' });
|
||||
} else {
|
||||
alert('Fehler beim Aktualisieren der Einkaufsliste.');
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
@using Francesco.Recipes.World.Models.BackendModels.Recipe
|
||||
@model Francesco.Recipes.World.Views.Recipe.FilterByDifficultyViewModel
|
||||
|
||||
<h1>Rezepte nach Schwierigkeitsgrad</h1>
|
||||
|
||||
<div class="mb-3">
|
||||
<a asp-controller="Recipe" asp-action="Create" class="btn btn-primary">Neues Rezept erstellen</a>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<form asp-controller="Recipe" asp-action="FilterByDifficulty" method="get" id="filterForm">
|
||||
<div class="form-group">
|
||||
<label asp-for="SelectedDifficulty" class="form-label">Schwierigkeitsgrad</label>
|
||||
<select asp-for="SelectedDifficulty" asp-items="Html.GetEnumSelectList<Difficulty>()" class="form-select" onchange="submitForm()">
|
||||
<option value="">Alle Schwierigkeitsgrade</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
@if (Model?.Recipes != null && Model.Recipes.Any())
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Beschreibung</th>
|
||||
<th>Schwierigkeitsgrad</th>
|
||||
<th>Portionen</th>
|
||||
<th>Zubereitungszeit</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var recipe in Model.Recipes)
|
||||
{
|
||||
<tr>
|
||||
<td>@recipe.Name</td>
|
||||
<td>@(recipe.Description?.Length > 100 ? recipe.Description.Substring(0, 100) + "..." : recipe.Description)</td>
|
||||
<td>@recipe.?Difficulty</td>
|
||||
<td>@recipe.Servings</td>
|
||||
<td>@($"{recipe.PreparationTime.TotalMinutes} Min.")</td>
|
||||
<td>
|
||||
<a asp-controller="Recipe" asp-action="Details" asp-route-id="@recipe.Id" class="btn btn-sm btn-info">Details</a>
|
||||
<a asp-controller="Recipe" asp-action="Edit" asp-route-id="@recipe.Id" class="btn btn-sm btn-primary">Bearbeiten</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="col-12">
|
||||
<p>Keine Rezepte gefunden.</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
function submitForm() {
|
||||
document.getElementById('filterForm').submit();
|
||||
}
|
||||
</script>
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Francesco.Recipes.World.Views.Recipe
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using Francesco.Recipes.World.Models.BackendModels.Recipe;
|
||||
|
||||
public class FilterByDifficultyViewModel
|
||||
{
|
||||
public Difficulty? SelectedDifficulty { get; set; }
|
||||
|
||||
public IReadOnlyCollection<Recipe> Recipes { get; set; } = new List<Recipe>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
@model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Remove Ingredient";
|
||||
}
|
||||
|
||||
<h1>Remove Ingredient</h1>
|
||||
|
||||
<h3>Are you sure you want to remove the ingredient '@ViewBag.IngredientName' from this recipe?</h3>
|
||||
|
||||
<form asp-action="RemoveIngredientConfirmed" asp-route-categoryId="@ViewBag.CategoryId" asp-route-recipeId="@ViewBag.RecipeId" asp-route-ingredientId="@ViewBag.IngredientId" method="post">
|
||||
<input type="hidden" name="categoryId" value="@ViewBag.CategoryId" />
|
||||
<input type="hidden" name="recipeId" value="@ViewBag.RecipeId" />
|
||||
<input type="hidden" name="ingredientId" value="@ViewBag.IngredientId" />
|
||||
<div class="form-group">
|
||||
<input type="submit" value="Remove" class="btn btn-danger" />
|
||||
<a asp-action="Details" asp-route-id="@ViewBag.RecipeId" class="btn btn-secondary">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
@model ErrorViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Error";
|
||||
}
|
||||
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
@if (Model.ShowRequestId)
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@Model.RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||
</p>
|
||||
<p>
|
||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||
It can result in displaying sensitive information from exceptions to end users.
|
||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||
and restarting the app.
|
||||
</p>
|
||||
@@ -0,0 +1,43 @@
|
||||
@model IEnumerable<Francesco.Recipes.World.Models.BackendModels.RecipeIngredient.RecipeIngredient>
|
||||
|
||||
<div class="recipe-ingredients">
|
||||
<h3>Zutaten</h3>
|
||||
<form id="ingredient-form">
|
||||
<ul id="ingredient-list">
|
||||
@foreach (var ingredient in Model)
|
||||
{
|
||||
<li id="ingredient-@ingredient.Id">
|
||||
<input type="checkbox" name="ingredientIds" value="@ingredient.Id" />
|
||||
@ingredient.Ingredient.Name - @ingredient.Quantity @ingredient.Unit.Symbol
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
<button type="button" class="btn btn-primary" onclick="addSelectedIngredientsToShoppingList()">Ausgewählte zur Einkaufsliste hinzufügen</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
function addSelectedIngredientsToShoppingList() {
|
||||
var form = document.getElementById('ingredient-form');
|
||||
var formData = new FormData(form);
|
||||
var selectedIngredientIds = formData.getAll('ingredientIds');
|
||||
|
||||
fetch('/ShoppingList/AddSelectedIngredients', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ ingredientIds: selectedIngredientIds })
|
||||
}).then(response => {
|
||||
if (response.ok) {
|
||||
alert('Ausgewählte Zutaten wurden zur Einkaufsliste hinzugefügt.');
|
||||
} else {
|
||||
alert('Fehler beim Hinzufügen der ausgewählten Zutaten zur Einkaufsliste.');
|
||||
}
|
||||
}).catch(error => {
|
||||
alert('Ein Fehler ist aufgetreten: ' + error.message);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
@model Francesco.Recipes.World.Models.BackendModels.Recipe.Recipe
|
||||
|
||||
<div class="adjustable-ingredients">
|
||||
<div class="serving-adjustment mb-3" data-original-servings="@Model.Servings">
|
||||
<h3>Ingredients</h3>
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<label for="servingsInput" class="me-2">Adjust servings:</label>
|
||||
<div class="input-group" style="max-width: 150px;">
|
||||
<button type="button" class="btn btn-outline-secondary" id="decreaseServings">-</button>
|
||||
<input type="number" class="form-control text-center" id="servingsInput" value="@Model.Servings" min="1">
|
||||
<button type="button" class="btn btn-outline-secondary" id="increaseServings">+</button>
|
||||
</div>
|
||||
<span class="ms-2 text-muted">(Original: @Model.Servings)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="ingredient-form">
|
||||
<ul id="adjustable-ingredient-list">
|
||||
@foreach (var ingredient in Model.RecipeIngredients)
|
||||
{
|
||||
<li id="ingredient-@ingredient.Id"
|
||||
data-ingredient-id="@ingredient.Id"
|
||||
data-original-quantity="@ingredient.Quantity"
|
||||
data-ingredient-name="@ingredient.Ingredient.Name"
|
||||
data-unit="@(ingredient.Unit?.Symbol ?? string.Empty)">
|
||||
<input type="checkbox" name="ingredientIds" value="@ingredient.Id" />
|
||||
<span class="ingredient-name">@ingredient.Ingredient.Name</span> -
|
||||
<span class="ingredient-quantity">@ingredient.Quantity</span>
|
||||
<span class="ingredient-unit">@(ingredient.Unit != null ? ingredient.Unit.Symbol : string.Empty)</span>
|
||||
<span class="ingredient-note"></span>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
<button type="button" class="btn btn-primary mt-2" onclick="addSelectedIngredientsToShoppingList('@Model.Id')">Add Selected to Shopping List</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -0,0 +1,26 @@
|
||||
@model Francesco.Recipes.World.Models.IFavoritable
|
||||
|
||||
<form hx-post="@Url.Action(Model.IsFavorite ? "RemoveFavorite" : "AddFavorite", "Recipe")"
|
||||
hx-target="this"
|
||||
hx-swap="outerHTML">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" name="recipeId" value="@Model.Id" />
|
||||
<button type="submit" class="btn btn-link p-0" style="width: 40px; height: 40px;">
|
||||
@if (Model.IsFavorite)
|
||||
{
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#FFD700">
|
||||
<path d="M12 17.27L18.18 21 16.54 13.97
|
||||
22 9.24l-7.19-.62L12 2 9.19 8.62
|
||||
2 9.24l5.46 4.73L5.82 21z" />
|
||||
</svg>
|
||||
}
|
||||
else
|
||||
{
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 17.27L18.18 21 16.54 13.97
|
||||
22 9.24l-7.19-.62L12 2 9.19 8.62
|
||||
2 9.24l5.46 4.73L5.82 21z" />
|
||||
</svg>
|
||||
}
|
||||
</button>
|
||||
</form>
|
||||
@@ -0,0 +1,39 @@
|
||||
@model Francesco.Recipes.World.Models.InstructionViewModel
|
||||
@Html.AntiForgeryToken()
|
||||
<div id="instructions-container">
|
||||
@for (int i = 0; i < Model.Instructions.Count; i++)
|
||||
{
|
||||
<div class="instruction-item" id="instruction-@Model.Instructions[i].Id">
|
||||
<div class="instruction-controls">
|
||||
@if (Model.Instructions[i].MediaFiles != null && Model.Instructions[i].MediaFiles.Any())
|
||||
{
|
||||
var mediaFile = Model.Instructions[i].MediaFiles.First();
|
||||
if (mediaFile.Data != null)
|
||||
{
|
||||
<div class="instruction-media">
|
||||
<img src="data:@mediaFile.MimeType;base64,@Convert.ToBase64String(mediaFile.Data)"
|
||||
alt="Instruction Media" class="instruction-media-preview" />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
<input type="file" name="InstructionViewModel.Instructions[@i].MediaFile" class="instruction-file" />
|
||||
<textarea name="InstructionViewModel.Instructions[@i].Description" placeholder="Beschreibung" class="form-control">@Model.Instructions[i].Description</textarea>
|
||||
|
||||
<button type="button" class="btn-delete" onclick="Francesco.removeInstruction('@Model.Instructions[i].Id', '@Model.RecipeId')">🗑️</button>
|
||||
</div>
|
||||
<div class="instruction-actions">
|
||||
<button type="button" class="btn-move-up" onclick="Francesco.moveInstructionUp('@Model.Instructions[i].Id', '@Model.RecipeId')">⬆️</button>
|
||||
<button type="button" class="btn-move-down" onclick="Francesco.moveInstructionDown('@Model.Instructions[i].Id', '@Model.RecipeId')">⬇️</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<button type="button" class="btn-add" onclick="Francesco.addInstruction()">Schritte hinzufügen</button>
|
||||
|
||||
|
||||
|
||||
<script src="~/js/site.js" asp-append-version="true" defer></script>
|
||||
@@ -0,0 +1,29 @@
|
||||
@model Francesco.Recipes.World.Models.IngredientViewModel
|
||||
@Html.AntiForgeryToken()
|
||||
<div id="ingredients-container">
|
||||
@for (int i = 0; i < Model.Ingredients.Count; i++)
|
||||
{
|
||||
<div class="ingredient-item" id="ingredient-@Model.Ingredients[i].Id">
|
||||
<div class="ingredient-controls">
|
||||
<input type="text" name="IngredientViewModel.Ingredients[@i].Name" value="@Model.Ingredients[i].Name" placeholder="Name" class="form-control" />
|
||||
<input type="number" name="IngredientViewModel.Ingredients[@i].RecipeIngredients[0].Quantity" value="@(Model.Ingredients[i].RecipeIngredients.FirstOrDefault()?.Quantity)" placeholder="Menge" class="form-control" />
|
||||
<select name="IngredientViewModel.Ingredients[@i].RecipeIngredients[0].Unit.Id" class="form-control">
|
||||
@foreach (var unit in Model.Units)
|
||||
{
|
||||
@if (Model.Ingredients[i].RecipeIngredients.FirstOrDefault()?.Unit?.Id == unit.Id)
|
||||
{
|
||||
<option value="@unit.Id" selected>@unit.Name (@unit.Symbol)</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@unit.Id">@unit.Name (@unit.Symbol)</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
<button type="button" class="btn-delete" onclick="Francesco.removeIngredient('@Model.Ingredients[i].Id')">🗑️</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn-add" onclick="Francesco.addIngredient()">Zutat hinzufügen</button>
|
||||
@@ -0,0 +1,91 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@ViewData["Title"] - Francesco.Recipes.World</title>
|
||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
||||
<link rel="stylesheet" href="~/Francesco.Recipes.World.styles.css" asp-append-version="true"/>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Francesco.Recipes.World</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
|
||||
aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
|
||||
<ul class="navbar-nav flex-grow-1">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Startseite</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="" asp-controller="ShoppingList" asp-action="Details">Einkaufsliste</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="" asp-controller="Favorite" asp-action="Index">Favoriten</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="d-flex">
|
||||
<button class="btn btn-outline-dark me-2" id="darkModeToggle">
|
||||
<i class="bi bi-moon"></i>
|
||||
</button>
|
||||
<button class="btn btn-outline-light" id="lightModeToggle">
|
||||
<i class="bi bi-sun"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
<div class="container">
|
||||
<main role="main" class="pb-3">
|
||||
@RenderBody()
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="~/lib/jquery/dist/jquery.min.js"></script>
|
||||
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="~/js/site.js" asp-append-version="true"></script>
|
||||
<script src="https://unpkg.com/htmx.org@2.0.4/dist/htmx.js" integrity="sha384-oeUn82QNXPuVkGCkcrInrS1twIxKhkZiFfr2TdiuObZ3n3yIeMiqcRzkIcguaof1" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<script>
|
||||
|
||||
let recipeId;
|
||||
@if (ViewData["RecipeId"] != null)
|
||||
{
|
||||
<text>recipeId = '@ViewData["RecipeId"]';</text>
|
||||
}
|
||||
else if (ViewData["CategoryId"] != null)
|
||||
{
|
||||
<text>recipeId = '@ViewData["CategoryId"]';</text>
|
||||
}
|
||||
|
||||
|
||||
const darkModeToggle = document.getElementById('darkModeToggle');
|
||||
const lightModeToggle = document.getElementById('lightModeToggle');
|
||||
darkModeToggle.addEventListener('click', () => {
|
||||
document.body.classList.add('bg-dark', 'text-white');
|
||||
});
|
||||
lightModeToggle.addEventListener('click', () => {
|
||||
document.body.classList.remove('bg-dark', 'text-white');
|
||||
});
|
||||
|
||||
|
||||
document.body.addEventListener('htmx:configRequest', (event) => {
|
||||
const token = document.querySelector('input[name="__RequestVerificationToken"]')?.value;
|
||||
if (token) {
|
||||
event.detail.headers['RequestVerificationToken'] = token;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
@await RenderSectionAsync("Scripts", required: false)
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
|
||||
for details on configuring this project to bundle and minify static web assets. */
|
||||
|
||||
a.navbar-brand {
|
||||
white-space: normal;
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0077cc;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac;
|
||||
}
|
||||
|
||||
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac;
|
||||
}
|
||||
|
||||
.border-top {
|
||||
border-top: 1px solid #e5e5e5;
|
||||
}
|
||||
.border-bottom {
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.box-shadow {
|
||||
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
|
||||
}
|
||||
|
||||
button.accept-policy {
|
||||
font-size: 1rem;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
line-height: 60px;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
@model Francesco.Recipes.World.Models.InstructionViewModel
|
||||
|
||||
<div class="recipe-instructions-section">
|
||||
<h3>Instructions</h3>
|
||||
|
||||
<div class="instructions-grid">
|
||||
@foreach (var instruction in Model.Instructions.OrderBy(i => i.Number))
|
||||
{
|
||||
<div class="instruction-card">
|
||||
<div class="instruction-image">
|
||||
@if (instruction.MediaFiles != null && instruction.MediaFiles.Any())
|
||||
{
|
||||
var mediaFile = instruction.MediaFiles.First();
|
||||
if (mediaFile.Data != null && mediaFile.Data.Length > 0)
|
||||
{
|
||||
<img src="data:@mediaFile.MimeType;base64,@Convert.ToBase64String(mediaFile.Data)" alt="Step @instruction.Number" />
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="placeholder-image">
|
||||
<i class="bi bi-image"></i>
|
||||
</div>
|
||||
}
|
||||
<span class="step-number">@instruction.Number</span>
|
||||
</div>
|
||||
<p class="instruction-text">@instruction.Description</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,2 @@
|
||||
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
|
||||
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
|
||||
@@ -0,0 +1,107 @@
|
||||
@model Francesco.Recipes.World.Models.ShoppingListDetailsViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Einkaufsliste Details";
|
||||
}
|
||||
|
||||
@using System.Text.Json
|
||||
@{
|
||||
var recipeIngredientsJson = JsonSerializer.Serialize(
|
||||
Model.RecipesInAnyShoppingList.ToDictionary(
|
||||
r => r.Id,
|
||||
r => r.RecipeIngredients.Select(ri => new
|
||||
{
|
||||
id = ri.Id,
|
||||
name = ri.Ingredient.Name,
|
||||
amount = ri.Quantity,
|
||||
unit = ri.Unit.Symbol,
|
||||
shoppingListId = Model.RecipeIngredientToShoppingListMap.ContainsKey(ri.Id)
|
||||
? Model.RecipeIngredientToShoppingListMap[ri.Id]
|
||||
: Guid.Empty
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
<div class="container">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>Einkaufsliste Details</h2>
|
||||
<div style="border:2px solid black; border-radius:30px; padding:10px 30px; display:inline-block;">
|
||||
Anzahl Rezepte: <span id="recipeCount">@Model.RecipeCount</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center mb-4">
|
||||
<button class="btn btn-outline-secondary me-2" type="button" id="carouselLeft">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
</button>
|
||||
<div class="flex-grow-1 overflow-auto" style="white-space:nowrap;" id="recipeCarousel">
|
||||
@foreach (var recipe in Model.RecipesInAnyShoppingList)
|
||||
{
|
||||
var mediaFile = recipe.MediaFiles?.FirstOrDefault();
|
||||
var imageData = mediaFile?.Data;
|
||||
var mimeType = mediaFile?.MimeType;
|
||||
var ingredientCount = recipe.RecipeIngredients?.Count() ?? 0;
|
||||
|
||||
<div class="card d-inline-block mx-2 recipe-card"
|
||||
data-recipe-id="@recipe.Id"
|
||||
style="width: 180px; vertical-align:top; border: 1px solid #000;">
|
||||
<div class="position-relative">
|
||||
<div class="position-absolute top-0 end-0 p-2">
|
||||
<button class="btn btn-sm btn-link text-dark" title="Rezept entfernen"
|
||||
onclick="Francesco.removeRecipe('@recipe.Id');">
|
||||
<i class="bi bi-trash" style="font-size: 1.2rem;"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-center p-2" style="height: 120px; display: flex; align-items: center; justify-content: center;">
|
||||
@if (imageData != null && mimeType != null)
|
||||
{
|
||||
<img src="data:@mimeType;base64,@Convert.ToBase64String(imageData)"
|
||||
alt="@recipe.Name"
|
||||
style="max-height: 100%; max-width: 100%; object-fit: contain;" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<div style="width: 100%; height: 100%; display: flex; align-items: center; justify-content: center;">
|
||||
<i class="bi bi-image" style="font-size: 3rem; color: #ccc;"></i>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="p-2 text-center" style="border-top: 1px solid #000; background-color: #fff;">
|
||||
<div class="recipe-name" style="font-weight: bold; font-size: 0.9rem; white-space: normal; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; height: 40px;">@recipe.Name</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<button class="btn btn-outline-secondary ms-2" type="button" id="carouselRight">
|
||||
<i class="bi bi-arrow-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="ingredientListContainer" class="mt-4">
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h4 class="mb-0">Zutaten</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul id="ingredientList" class="list-group"></ul>
|
||||
<div class="text-center mt-3">
|
||||
<button id="removeSelectedButton" class="btn" onclick="Francesco.removeSelectedIngredients()">
|
||||
ENTFERNE MARKIERTE
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@section Scripts {
|
||||
@Html.AntiForgeryToken()
|
||||
<script>
|
||||
window.recipeIngredients = @Html.Raw(recipeIngredientsJson);
|
||||
</script>
|
||||
<script src="~/js/site.js"></script>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
@using Francesco.Recipes.World
|
||||
@using Francesco.Recipes.World.Models
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@@ -0,0 +1,3 @@
|
||||
@{
|
||||
Layout = "_Layout";
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user