Merge pull request 'Mis en prod' (#213) from dev into main
Reviewed-on: https://10.4.0.131/gitea/DI1-P4-E1/Webzine/pulls/213
This commit is contained in:
500
.editorconfig
500
.editorconfig
@@ -1,64 +1,59 @@
|
|||||||
root = true
|
root = true
|
||||||
|
|
||||||
# All files
|
# ============================================================
|
||||||
|
# ALL FILES
|
||||||
|
# ============================================================
|
||||||
[*]
|
[*]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
|
|
||||||
# Xml files
|
# ============================================================
|
||||||
|
# XML FILES
|
||||||
|
# ============================================================
|
||||||
[*.xml]
|
[*.xml]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
# Xml project files
|
|
||||||
[*.{csproj,fsproj,vbproj,proj,slnx}]
|
[*.{csproj,fsproj,vbproj,proj,slnx}]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
# Xml config files
|
|
||||||
[*.{props,targets,config,nuspec}]
|
[*.{props,targets,config,nuspec}]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# JSON FILES
|
||||||
|
# ============================================================
|
||||||
[*.json]
|
[*.json]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
# C# files
|
# ============================================================
|
||||||
[*.cs]
|
# C# AND VB FILES — SHARED .NET CONVENTIONS
|
||||||
|
# ============================================================
|
||||||
#### Core EditorConfig Options ####
|
|
||||||
|
|
||||||
# Indentation and spacing
|
|
||||||
indent_size = 4
|
|
||||||
tab_width = 4
|
|
||||||
|
|
||||||
# New line preferences
|
|
||||||
insert_final_newline = false
|
|
||||||
|
|
||||||
#### .NET Coding Conventions ####
|
|
||||||
[*.{cs,vb}]
|
[*.{cs,vb}]
|
||||||
|
|
||||||
# Organize usings
|
#### Organize Usings ####
|
||||||
dotnet_separate_import_directive_groups = true
|
dotnet_separate_import_directive_groups = true
|
||||||
dotnet_sort_system_directives_first = true
|
dotnet_sort_system_directives_first = true
|
||||||
file_header_template = unset
|
file_header_template = unset
|
||||||
|
|
||||||
# this. and Me. preferences
|
#### this. and Me. Preferences ####
|
||||||
dotnet_style_qualification_for_event = false:silent
|
dotnet_style_qualification_for_event = false:silent
|
||||||
dotnet_style_qualification_for_field = false:silent
|
dotnet_style_qualification_for_field = false:silent
|
||||||
dotnet_style_qualification_for_method = false:silent
|
dotnet_style_qualification_for_method = false:silent
|
||||||
dotnet_style_qualification_for_property = false:silent
|
dotnet_style_qualification_for_property = false:silent
|
||||||
|
|
||||||
# Language keywords vs BCL types preferences
|
#### Language Keywords vs BCL Types ####
|
||||||
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
|
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
|
||||||
dotnet_style_predefined_type_for_member_access = true:silent
|
dotnet_style_predefined_type_for_member_access = true:silent
|
||||||
|
|
||||||
# Parentheses preferences
|
#### Parentheses Preferences ####
|
||||||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
|
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
|
||||||
dotnet_style_parentheses_in_other_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
|
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
|
||||||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
|
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
|
||||||
|
|
||||||
# Modifier preferences
|
#### Modifier Preferences ####
|
||||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
|
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
|
||||||
|
|
||||||
# Expression-level preferences
|
#### Expression-Level Preferences ####
|
||||||
dotnet_style_coalesce_expression = true:suggestion
|
dotnet_style_coalesce_expression = true:suggestion
|
||||||
dotnet_style_collection_initializer = true:suggestion
|
dotnet_style_collection_initializer = true:suggestion
|
||||||
dotnet_style_explicit_tuple_names = true:suggestion
|
dotnet_style_explicit_tuple_names = true:suggestion
|
||||||
@@ -78,129 +73,30 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggesti
|
|||||||
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
||||||
dotnet_style_prefer_simplified_interpolation = true:suggestion
|
dotnet_style_prefer_simplified_interpolation = true:suggestion
|
||||||
|
|
||||||
# Field preferences
|
#### Field Preferences ####
|
||||||
dotnet_style_readonly_field = true:warning
|
dotnet_style_readonly_field = true:warning
|
||||||
|
|
||||||
# Parameter preferences
|
#### Parameter Preferences ####
|
||||||
dotnet_code_quality_unused_parameters = all:suggestion
|
dotnet_code_quality_unused_parameters = all:suggestion
|
||||||
|
|
||||||
# Suppression preferences
|
#### Suppression Preferences ####
|
||||||
dotnet_remove_unnecessary_suppression_exclusions = none
|
dotnet_remove_unnecessary_suppression_exclusions = none
|
||||||
|
|
||||||
#### C# Coding Conventions ####
|
#### Diagnostics ####
|
||||||
[*.cs]
|
dotnet_diagnostic.IDE0005.severity = warning # Remove unused usings
|
||||||
|
dotnet_diagnostic.IDE0073.severity = none # File header not required
|
||||||
|
dotnet_diagnostic.SA1600.severity = warning # Elements should be documented
|
||||||
|
dotnet_diagnostic.SA1623.severity = none # Property summary text
|
||||||
|
dotnet_diagnostic.SA1624.severity = none # Property summary text
|
||||||
|
dotnet_diagnostic.SA1633.severity = none # File header not required
|
||||||
|
dotnet_diagnostic.SA1642.severity = suggestion # Constructor summary text
|
||||||
|
|
||||||
# var preferences
|
# ============================================================
|
||||||
csharp_style_var_elsewhere = false:silent
|
# NAMING RULES
|
||||||
csharp_style_var_for_built_in_types = false:silent
|
# ============================================================
|
||||||
csharp_style_var_when_type_is_apparent = false:silent
|
|
||||||
|
|
||||||
# Expression-bodied members
|
|
||||||
csharp_style_expression_bodied_accessors = true:silent
|
|
||||||
csharp_style_expression_bodied_constructors = false:silent
|
|
||||||
csharp_style_expression_bodied_indexers = true:silent
|
|
||||||
csharp_style_expression_bodied_lambdas = true:suggestion
|
|
||||||
csharp_style_expression_bodied_local_functions = false:silent
|
|
||||||
csharp_style_expression_bodied_methods = false:silent
|
|
||||||
csharp_style_expression_bodied_operators = false:silent
|
|
||||||
csharp_style_expression_bodied_properties = true:silent
|
|
||||||
|
|
||||||
# Pattern matching preferences
|
|
||||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
|
||||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
|
||||||
csharp_style_prefer_extended_property_pattern = true:suggestion
|
|
||||||
csharp_style_prefer_not_pattern = true:suggestion
|
|
||||||
csharp_style_prefer_pattern_matching = true:silent
|
|
||||||
csharp_style_prefer_switch_expression = true:suggestion
|
|
||||||
|
|
||||||
# Null-checking preferences
|
|
||||||
csharp_style_conditional_delegate_call = true:suggestion
|
|
||||||
|
|
||||||
# Modifier preferences
|
|
||||||
csharp_prefer_static_anonymous_function = true:suggestion
|
|
||||||
csharp_prefer_static_local_function = true:warning
|
|
||||||
csharp_preferred_modifier_order = public,private,protected,internal,file,const,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async:suggestion
|
|
||||||
csharp_style_prefer_readonly_struct = true:suggestion
|
|
||||||
csharp_style_prefer_readonly_struct_member = true:suggestion
|
|
||||||
|
|
||||||
# Code-block preferences
|
|
||||||
csharp_prefer_braces = true:silent
|
|
||||||
csharp_prefer_simple_using_statement = true:suggestion
|
|
||||||
csharp_style_namespace_declarations = file_scoped:suggestion
|
|
||||||
csharp_style_prefer_method_group_conversion = true:silent
|
|
||||||
csharp_style_prefer_primary_constructors = true:suggestion
|
|
||||||
csharp_style_prefer_top_level_statements = true:silent
|
|
||||||
|
|
||||||
# Expression-level preferences
|
|
||||||
csharp_prefer_simple_default_expression = true:suggestion
|
|
||||||
csharp_style_deconstructed_variable_declaration = true:suggestion
|
|
||||||
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
|
|
||||||
csharp_style_inlined_variable_declaration = true:suggestion
|
|
||||||
csharp_style_prefer_index_operator = true:suggestion
|
|
||||||
csharp_style_prefer_local_over_anonymous_function = true:suggestion
|
|
||||||
csharp_style_prefer_null_check_over_type_check = true:suggestion
|
|
||||||
csharp_style_prefer_range_operator = true:suggestion
|
|
||||||
csharp_style_prefer_tuple_swap = true:suggestion
|
|
||||||
csharp_style_prefer_utf8_string_literals = true:suggestion
|
|
||||||
csharp_style_throw_expression = true:suggestion
|
|
||||||
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
|
|
||||||
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
|
|
||||||
|
|
||||||
# 'using' directive preferences
|
|
||||||
csharp_using_directive_placement = outside_namespace:silent
|
|
||||||
|
|
||||||
#### C# Formatting Rules ####
|
|
||||||
|
|
||||||
# New line preferences
|
|
||||||
csharp_new_line_before_catch = true
|
|
||||||
csharp_new_line_before_else = true
|
|
||||||
csharp_new_line_before_finally = true
|
|
||||||
csharp_new_line_before_members_in_anonymous_types = true
|
|
||||||
csharp_new_line_before_members_in_object_initializers = true
|
|
||||||
csharp_new_line_before_open_brace = all
|
|
||||||
csharp_new_line_between_query_expression_clauses = true
|
|
||||||
|
|
||||||
# Indentation preferences
|
|
||||||
csharp_indent_block_contents = true
|
|
||||||
csharp_indent_braces = false
|
|
||||||
csharp_indent_case_contents = true
|
|
||||||
csharp_indent_case_contents_when_block = true
|
|
||||||
csharp_indent_labels = one_less_than_current
|
|
||||||
csharp_indent_switch_labels = true
|
|
||||||
|
|
||||||
# Space preferences
|
|
||||||
csharp_space_after_cast = false
|
|
||||||
csharp_space_after_colon_in_inheritance_clause = true
|
|
||||||
csharp_space_after_comma = true
|
|
||||||
csharp_space_after_dot = false
|
|
||||||
csharp_space_after_keywords_in_control_flow_statements = true
|
|
||||||
csharp_space_after_semicolon_in_for_statement = true
|
|
||||||
csharp_space_around_binary_operators = before_and_after
|
|
||||||
csharp_space_around_declaration_statements = false
|
|
||||||
csharp_space_before_colon_in_inheritance_clause = true
|
|
||||||
csharp_space_before_comma = false
|
|
||||||
csharp_space_before_dot = false
|
|
||||||
csharp_space_before_open_square_brackets = false
|
|
||||||
csharp_space_before_semicolon_in_for_statement = false
|
|
||||||
csharp_space_between_empty_square_brackets = false
|
|
||||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
|
||||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
|
||||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
|
||||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
|
||||||
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
|
||||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
|
||||||
csharp_space_between_parentheses = false
|
|
||||||
csharp_space_between_square_brackets = false
|
|
||||||
|
|
||||||
# Wrapping preferences
|
|
||||||
csharp_preserve_single_line_blocks = true
|
|
||||||
csharp_preserve_single_line_statements = true
|
|
||||||
|
|
||||||
#### Naming styles ####
|
|
||||||
[*.{cs,vb}]
|
[*.{cs,vb}]
|
||||||
|
|
||||||
# Naming rules
|
## Rules ##
|
||||||
|
|
||||||
dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion
|
dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion
|
||||||
dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces
|
dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces
|
||||||
dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase
|
dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase
|
||||||
@@ -225,6 +121,38 @@ dotnet_naming_rule.events_should_be_pascalcase.severity = suggestion
|
|||||||
dotnet_naming_rule.events_should_be_pascalcase.symbols = events
|
dotnet_naming_rule.events_should_be_pascalcase.symbols = events
|
||||||
dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase
|
dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields
|
||||||
|
dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields
|
||||||
|
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.style = pascalcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.symbols = public_static_readonly_fields
|
||||||
|
dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.style = pascalcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields
|
||||||
|
dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields
|
||||||
|
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.style = s_camelcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.private_constant_fields_should_be_pascalcase.symbols = private_constant_fields
|
||||||
|
dotnet_naming_rule.private_constant_fields_should_be_pascalcase.style = pascalcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.symbols = private_static_readonly_fields
|
||||||
|
dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.style = pascalcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.enums_should_be_pascalcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.enums_should_be_pascalcase.symbols = enums
|
||||||
|
dotnet_naming_rule.enums_should_be_pascalcase.style = pascalcase
|
||||||
|
|
||||||
dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion
|
dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion
|
||||||
dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables
|
dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables
|
||||||
dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase
|
dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase
|
||||||
@@ -237,38 +165,6 @@ dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion
|
|||||||
dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters
|
dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters
|
||||||
dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase
|
dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase
|
||||||
|
|
||||||
dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion
|
|
||||||
dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields
|
|
||||||
dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase
|
|
||||||
|
|
||||||
dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion
|
|
||||||
dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields
|
|
||||||
dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase
|
|
||||||
|
|
||||||
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = suggestion
|
|
||||||
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields
|
|
||||||
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.style = s_camelcase
|
|
||||||
|
|
||||||
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = suggestion
|
|
||||||
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields
|
|
||||||
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.style = pascalcase
|
|
||||||
|
|
||||||
dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = suggestion
|
|
||||||
dotnet_naming_rule.private_constant_fields_should_be_pascalcase.symbols = private_constant_fields
|
|
||||||
dotnet_naming_rule.private_constant_fields_should_be_pascalcase.style = pascalcase
|
|
||||||
|
|
||||||
dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = suggestion
|
|
||||||
dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.symbols = public_static_readonly_fields
|
|
||||||
dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.style = pascalcase
|
|
||||||
|
|
||||||
dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = suggestion
|
|
||||||
dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.symbols = private_static_readonly_fields
|
|
||||||
dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.style = pascalcase
|
|
||||||
|
|
||||||
dotnet_naming_rule.enums_should_be_pascalcase.severity = suggestion
|
|
||||||
dotnet_naming_rule.enums_should_be_pascalcase.symbols = enums
|
|
||||||
dotnet_naming_rule.enums_should_be_pascalcase.style = pascalcase
|
|
||||||
|
|
||||||
dotnet_naming_rule.local_functions_should_be_pascalcase.severity = suggestion
|
dotnet_naming_rule.local_functions_should_be_pascalcase.severity = suggestion
|
||||||
dotnet_naming_rule.local_functions_should_be_pascalcase.symbols = local_functions
|
dotnet_naming_rule.local_functions_should_be_pascalcase.symbols = local_functions
|
||||||
dotnet_naming_rule.local_functions_should_be_pascalcase.style = pascalcase
|
dotnet_naming_rule.local_functions_should_be_pascalcase.style = pascalcase
|
||||||
@@ -277,67 +173,35 @@ dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = suggestion
|
|||||||
dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members
|
dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members
|
||||||
dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase
|
dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase
|
||||||
|
|
||||||
# Symbol specifications
|
## Symbol Specifications ##
|
||||||
|
dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum
|
||||||
|
dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.types_and_namespaces.required_modifiers =
|
||||||
|
|
||||||
dotnet_naming_symbols.interfaces.applicable_kinds = interface
|
dotnet_naming_symbols.interfaces.applicable_kinds = interface
|
||||||
dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
dotnet_naming_symbols.interfaces.required_modifiers =
|
dotnet_naming_symbols.interfaces.required_modifiers =
|
||||||
|
|
||||||
dotnet_naming_symbols.enums.applicable_kinds = enum
|
# NOTE: was previously misconfigured as 'namespace' kind — corrected to 'type_parameter'
|
||||||
dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
dotnet_naming_symbols.type_parameters.applicable_kinds = type_parameter
|
||||||
dotnet_naming_symbols.enums.required_modifiers =
|
dotnet_naming_symbols.type_parameters.applicable_accessibilities = *
|
||||||
|
dotnet_naming_symbols.type_parameters.required_modifiers =
|
||||||
dotnet_naming_symbols.events.applicable_kinds = event
|
|
||||||
dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
|
||||||
dotnet_naming_symbols.events.required_modifiers =
|
|
||||||
|
|
||||||
dotnet_naming_symbols.methods.applicable_kinds = method
|
dotnet_naming_symbols.methods.applicable_kinds = method
|
||||||
dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
dotnet_naming_symbols.methods.required_modifiers =
|
dotnet_naming_symbols.methods.required_modifiers =
|
||||||
|
|
||||||
dotnet_naming_symbols.properties.applicable_kinds = property
|
dotnet_naming_symbols.properties.applicable_kinds = property
|
||||||
dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
dotnet_naming_symbols.properties.required_modifiers =
|
dotnet_naming_symbols.properties.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.events.applicable_kinds = event
|
||||||
|
dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.events.required_modifiers =
|
||||||
|
|
||||||
dotnet_naming_symbols.public_fields.applicable_kinds = field
|
dotnet_naming_symbols.public_fields.applicable_kinds = field
|
||||||
dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal
|
dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal
|
||||||
dotnet_naming_symbols.public_fields.required_modifiers =
|
dotnet_naming_symbols.public_fields.required_modifiers =
|
||||||
|
|
||||||
dotnet_naming_symbols.private_fields.applicable_kinds = field
|
|
||||||
dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
|
|
||||||
dotnet_naming_symbols.private_fields.required_modifiers =
|
|
||||||
|
|
||||||
dotnet_naming_symbols.private_static_fields.applicable_kinds = field
|
|
||||||
dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
|
|
||||||
dotnet_naming_symbols.private_static_fields.required_modifiers = static
|
|
||||||
|
|
||||||
dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum
|
|
||||||
dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
|
||||||
dotnet_naming_symbols.types_and_namespaces.required_modifiers =
|
|
||||||
|
|
||||||
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
|
||||||
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
|
||||||
dotnet_naming_symbols.non_field_members.required_modifiers =
|
|
||||||
|
|
||||||
dotnet_naming_symbols.type_parameters.applicable_kinds = namespace
|
|
||||||
dotnet_naming_symbols.type_parameters.applicable_accessibilities = *
|
|
||||||
dotnet_naming_symbols.type_parameters.required_modifiers =
|
|
||||||
|
|
||||||
dotnet_naming_symbols.private_constant_fields.applicable_kinds = field
|
|
||||||
dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
|
|
||||||
dotnet_naming_symbols.private_constant_fields.required_modifiers = const
|
|
||||||
|
|
||||||
dotnet_naming_symbols.local_variables.applicable_kinds = local
|
|
||||||
dotnet_naming_symbols.local_variables.applicable_accessibilities = local
|
|
||||||
dotnet_naming_symbols.local_variables.required_modifiers =
|
|
||||||
|
|
||||||
dotnet_naming_symbols.local_constants.applicable_kinds = local
|
|
||||||
dotnet_naming_symbols.local_constants.applicable_accessibilities = local
|
|
||||||
dotnet_naming_symbols.local_constants.required_modifiers = const
|
|
||||||
|
|
||||||
dotnet_naming_symbols.parameters.applicable_kinds = parameter
|
|
||||||
dotnet_naming_symbols.parameters.applicable_accessibilities = *
|
|
||||||
dotnet_naming_symbols.parameters.required_modifiers =
|
|
||||||
|
|
||||||
dotnet_naming_symbols.public_constant_fields.applicable_kinds = field
|
dotnet_naming_symbols.public_constant_fields.applicable_kinds = field
|
||||||
dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal
|
dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal
|
||||||
@@ -347,57 +211,193 @@ dotnet_naming_symbols.public_static_readonly_fields.applicable_kinds = field
|
|||||||
dotnet_naming_symbols.public_static_readonly_fields.applicable_accessibilities = public, internal
|
dotnet_naming_symbols.public_static_readonly_fields.applicable_accessibilities = public, internal
|
||||||
dotnet_naming_symbols.public_static_readonly_fields.required_modifiers = readonly, static
|
dotnet_naming_symbols.public_static_readonly_fields.required_modifiers = readonly, static
|
||||||
|
|
||||||
|
dotnet_naming_symbols.private_fields.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.private_fields.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.private_static_fields.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.private_static_fields.required_modifiers = static
|
||||||
|
|
||||||
|
dotnet_naming_symbols.private_constant_fields.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.private_constant_fields.required_modifiers = const
|
||||||
|
|
||||||
dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field
|
dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field
|
||||||
dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
|
dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
|
||||||
dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readonly, static
|
dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readonly, static
|
||||||
|
|
||||||
|
dotnet_naming_symbols.enums.applicable_kinds = enum
|
||||||
|
dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.enums.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.local_variables.applicable_kinds = local
|
||||||
|
dotnet_naming_symbols.local_variables.applicable_accessibilities = local
|
||||||
|
dotnet_naming_symbols.local_variables.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.local_constants.applicable_kinds = local
|
||||||
|
dotnet_naming_symbols.local_constants.applicable_accessibilities = local
|
||||||
|
dotnet_naming_symbols.local_constants.required_modifiers = const
|
||||||
|
|
||||||
|
dotnet_naming_symbols.parameters.applicable_kinds = parameter
|
||||||
|
dotnet_naming_symbols.parameters.applicable_accessibilities = *
|
||||||
|
dotnet_naming_symbols.parameters.required_modifiers =
|
||||||
|
|
||||||
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
|
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
|
||||||
dotnet_naming_symbols.local_functions.applicable_accessibilities = *
|
dotnet_naming_symbols.local_functions.applicable_accessibilities = *
|
||||||
dotnet_naming_symbols.local_functions.required_modifiers =
|
dotnet_naming_symbols.local_functions.required_modifiers =
|
||||||
|
|
||||||
# Naming styles
|
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||||
|
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.non_field_members.required_modifiers =
|
||||||
|
|
||||||
dotnet_naming_style.pascalcase.required_prefix =
|
## Naming Styles ##
|
||||||
dotnet_naming_style.pascalcase.required_suffix =
|
|
||||||
dotnet_naming_style.pascalcase.word_separator =
|
|
||||||
dotnet_naming_style.pascalcase.capitalization = pascal_case
|
dotnet_naming_style.pascalcase.capitalization = pascal_case
|
||||||
|
dotnet_naming_style.pascalcase.required_prefix =
|
||||||
|
dotnet_naming_style.pascalcase.required_suffix =
|
||||||
|
dotnet_naming_style.pascalcase.word_separator =
|
||||||
|
|
||||||
dotnet_naming_style.ipascalcase.required_prefix = I
|
|
||||||
dotnet_naming_style.ipascalcase.required_suffix =
|
|
||||||
dotnet_naming_style.ipascalcase.word_separator =
|
|
||||||
dotnet_naming_style.ipascalcase.capitalization = pascal_case
|
dotnet_naming_style.ipascalcase.capitalization = pascal_case
|
||||||
|
dotnet_naming_style.ipascalcase.required_prefix = I
|
||||||
|
dotnet_naming_style.ipascalcase.required_suffix =
|
||||||
|
dotnet_naming_style.ipascalcase.word_separator =
|
||||||
|
|
||||||
dotnet_naming_style.tpascalcase.required_prefix = T
|
|
||||||
dotnet_naming_style.tpascalcase.required_suffix =
|
|
||||||
dotnet_naming_style.tpascalcase.word_separator =
|
|
||||||
dotnet_naming_style.tpascalcase.capitalization = pascal_case
|
dotnet_naming_style.tpascalcase.capitalization = pascal_case
|
||||||
|
dotnet_naming_style.tpascalcase.required_prefix = T
|
||||||
|
dotnet_naming_style.tpascalcase.required_suffix =
|
||||||
|
dotnet_naming_style.tpascalcase.word_separator =
|
||||||
|
|
||||||
dotnet_naming_style._camelcase.required_prefix = _
|
|
||||||
dotnet_naming_style._camelcase.required_suffix =
|
|
||||||
dotnet_naming_style._camelcase.word_separator =
|
|
||||||
dotnet_naming_style._camelcase.capitalization = camel_case
|
|
||||||
|
|
||||||
dotnet_naming_style.camelcase.required_prefix =
|
|
||||||
dotnet_naming_style.camelcase.required_suffix =
|
|
||||||
dotnet_naming_style.camelcase.word_separator =
|
|
||||||
dotnet_naming_style.camelcase.capitalization = camel_case
|
dotnet_naming_style.camelcase.capitalization = camel_case
|
||||||
|
dotnet_naming_style.camelcase.required_prefix =
|
||||||
|
dotnet_naming_style.camelcase.required_suffix =
|
||||||
|
dotnet_naming_style.camelcase.word_separator =
|
||||||
|
|
||||||
|
dotnet_naming_style._camelcase.capitalization = camel_case
|
||||||
|
dotnet_naming_style._camelcase.required_prefix = _
|
||||||
|
dotnet_naming_style._camelcase.required_suffix =
|
||||||
|
dotnet_naming_style._camelcase.word_separator =
|
||||||
|
|
||||||
dotnet_naming_style.s_camelcase.required_prefix = s_
|
|
||||||
dotnet_naming_style.s_camelcase.required_suffix =
|
|
||||||
dotnet_naming_style.s_camelcase.word_separator =
|
|
||||||
dotnet_naming_style.s_camelcase.capitalization = camel_case
|
dotnet_naming_style.s_camelcase.capitalization = camel_case
|
||||||
|
dotnet_naming_style.s_camelcase.required_prefix = s_
|
||||||
|
dotnet_naming_style.s_camelcase.required_suffix =
|
||||||
|
dotnet_naming_style.s_camelcase.word_separator =
|
||||||
|
|
||||||
dotnet_diagnostic.SA1623.severity = none
|
# ============================================================
|
||||||
dotnet_diagnostic.SA1624.severity = none
|
# C# FILES ONLY
|
||||||
|
# ============================================================
|
||||||
dotnet_diagnostic.SA1600.severity = suggestion
|
|
||||||
|
|
||||||
[*.cs]
|
[*.cs]
|
||||||
# 1. Tell the .NET Formatter to stop injecting a header
|
|
||||||
file_header_template = unset
|
|
||||||
|
|
||||||
# 2. Tell StyleCop to stop requiring a file header (SA1633)
|
#### Core EditorConfig Options ####
|
||||||
dotnet_diagnostic.SA1633.severity = none
|
indent_size = 4
|
||||||
|
tab_width = 4
|
||||||
|
insert_final_newline = false
|
||||||
|
|
||||||
# 3. Tell the IDE to stop requiring a file header (IDE0073)
|
#### var Preferences ####
|
||||||
dotnet_diagnostic.IDE0073.severity = none
|
csharp_style_var_elsewhere = false:silent
|
||||||
|
csharp_style_var_for_built_in_types = false:silent
|
||||||
|
csharp_style_var_when_type_is_apparent = false:silent
|
||||||
|
|
||||||
|
#### Expression-Bodied Members ####
|
||||||
|
csharp_style_expression_bodied_accessors = true:silent
|
||||||
|
csharp_style_expression_bodied_constructors = false:silent
|
||||||
|
csharp_style_expression_bodied_indexers = true:silent
|
||||||
|
csharp_style_expression_bodied_lambdas = true:suggestion
|
||||||
|
csharp_style_expression_bodied_local_functions = false:silent
|
||||||
|
csharp_style_expression_bodied_methods = false:silent
|
||||||
|
csharp_style_expression_bodied_operators = false:silent
|
||||||
|
csharp_style_expression_bodied_properties = true:silent
|
||||||
|
|
||||||
|
#### Pattern Matching Preferences ####
|
||||||
|
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||||
|
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||||
|
csharp_style_prefer_extended_property_pattern = true:suggestion
|
||||||
|
csharp_style_prefer_not_pattern = true:suggestion
|
||||||
|
csharp_style_prefer_pattern_matching = true:silent
|
||||||
|
csharp_style_prefer_switch_expression = true:suggestion
|
||||||
|
|
||||||
|
#### Null-Checking Preferences ####
|
||||||
|
csharp_style_conditional_delegate_call = true:suggestion
|
||||||
|
|
||||||
|
#### Modifier Preferences ####
|
||||||
|
csharp_prefer_static_anonymous_function = true:suggestion
|
||||||
|
csharp_prefer_static_local_function = true:warning
|
||||||
|
csharp_preferred_modifier_order = public,private,protected,internal,file,const,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async:suggestion
|
||||||
|
csharp_style_prefer_readonly_struct = true:suggestion
|
||||||
|
csharp_style_prefer_readonly_struct_member = true:suggestion
|
||||||
|
|
||||||
|
#### Code-Block Preferences ####
|
||||||
|
csharp_prefer_braces = true:silent
|
||||||
|
csharp_prefer_simple_using_statement = true:suggestion
|
||||||
|
csharp_style_namespace_declarations = file_scoped:suggestion
|
||||||
|
csharp_style_prefer_method_group_conversion = true:silent
|
||||||
|
csharp_style_prefer_primary_constructors = true:suggestion
|
||||||
|
csharp_style_prefer_top_level_statements = true:silent
|
||||||
|
|
||||||
|
#### Expression-Level Preferences ####
|
||||||
|
csharp_prefer_simple_default_expression = true:suggestion
|
||||||
|
csharp_style_deconstructed_variable_declaration = true:suggestion
|
||||||
|
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
|
||||||
|
csharp_style_inlined_variable_declaration = true:suggestion
|
||||||
|
csharp_style_prefer_index_operator = true:suggestion
|
||||||
|
csharp_style_prefer_local_over_anonymous_function = true:suggestion
|
||||||
|
csharp_style_prefer_null_check_over_type_check = true:suggestion
|
||||||
|
csharp_style_prefer_range_operator = true:suggestion
|
||||||
|
csharp_style_prefer_tuple_swap = true:suggestion
|
||||||
|
csharp_style_prefer_utf8_string_literals = true:suggestion
|
||||||
|
csharp_style_throw_expression = true:suggestion
|
||||||
|
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
|
||||||
|
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
|
||||||
|
|
||||||
|
#### Using Directive Preferences ####
|
||||||
|
csharp_using_directive_placement = inside_namespace:warning
|
||||||
|
|
||||||
|
#### Formatting — New Line Preferences ####
|
||||||
|
csharp_new_line_before_catch = true
|
||||||
|
csharp_new_line_before_else = true
|
||||||
|
csharp_new_line_before_finally = true
|
||||||
|
csharp_new_line_before_members_in_anonymous_types = true
|
||||||
|
csharp_new_line_before_members_in_object_initializers = true
|
||||||
|
csharp_new_line_before_open_brace = all
|
||||||
|
csharp_new_line_between_query_expression_clauses = true
|
||||||
|
|
||||||
|
#### Formatting — Indentation Preferences ####
|
||||||
|
csharp_indent_block_contents = true
|
||||||
|
csharp_indent_braces = false
|
||||||
|
csharp_indent_case_contents = true
|
||||||
|
csharp_indent_case_contents_when_block = true
|
||||||
|
csharp_indent_labels = one_less_than_current
|
||||||
|
csharp_indent_switch_labels = true
|
||||||
|
|
||||||
|
#### Formatting — Space Preferences ####
|
||||||
|
csharp_space_after_cast = false
|
||||||
|
csharp_space_after_colon_in_inheritance_clause = true
|
||||||
|
csharp_space_after_comma = true
|
||||||
|
csharp_space_after_dot = false
|
||||||
|
csharp_space_after_keywords_in_control_flow_statements = true
|
||||||
|
csharp_space_after_semicolon_in_for_statement = true
|
||||||
|
csharp_space_around_binary_operators = before_and_after
|
||||||
|
csharp_space_around_declaration_statements = false
|
||||||
|
csharp_space_before_colon_in_inheritance_clause = true
|
||||||
|
csharp_space_before_comma = false
|
||||||
|
csharp_space_before_dot = false
|
||||||
|
csharp_space_before_open_square_brackets = false
|
||||||
|
csharp_space_before_semicolon_in_for_statement = false
|
||||||
|
csharp_space_between_empty_square_brackets = false
|
||||||
|
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||||
|
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||||
|
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_parentheses = false
|
||||||
|
csharp_space_between_square_brackets = false
|
||||||
|
|
||||||
|
#### Formatting — Wrapping Preferences ####
|
||||||
|
csharp_preserve_single_line_blocks = true
|
||||||
|
csharp_preserve_single_line_statements = true
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# TEST PROJECTS — RELAXED RULES
|
||||||
|
# ============================================================
|
||||||
|
[*.Tests/**/*.cs]
|
||||||
|
dotnet_diagnostic.SA1600.severity = none
|
||||||
26
.gitea/workflows/deploy-prod.yaml
Normal file
26
.gitea/workflows/deploy-prod.yaml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: Deploiement API Prod Docker
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
name: Build et Déploiement
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 📥 Récupération du code source
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: 🔐 Injection des variables d'environnement
|
||||||
|
run: |
|
||||||
|
echo "PGSQL_CONNECTION=${{ secrets.PGSQL_CONNECTION }}" > .env
|
||||||
|
|
||||||
|
- name: 🐳 Redémarrage Docker
|
||||||
|
run: |
|
||||||
|
echo "🚀 Démarrage du déploiement Docker sur api-prod..."
|
||||||
|
docker compose down
|
||||||
|
docker compose up -d --build
|
||||||
|
echo "✅ Déploiement terminé !"
|
||||||
@@ -13,19 +13,29 @@ jobs:
|
|||||||
|
|
||||||
- name: Configure appsettings for CI
|
- name: Configure appsettings for CI
|
||||||
run: |
|
run: |
|
||||||
# Find the appsettings.json file
|
|
||||||
APPSETTINGS_PATH="Webzine.WebApplication/appsettings.json"
|
APPSETTINGS_PATH="Webzine.WebApplication/appsettings.json"
|
||||||
|
|
||||||
# Backup original file
|
|
||||||
cp $APPSETTINGS_PATH $APPSETTINGS_PATH.bak
|
cp $APPSETTINGS_PATH $APPSETTINGS_PATH.bak
|
||||||
|
|
||||||
# Use jq to modify the JSON
|
|
||||||
jq '.UseDatabase = true | .IsSQLite = true' $APPSETTINGS_PATH > $APPSETTINGS_PATH.tmp
|
jq '.UseDatabase = true | .IsSQLite = true' $APPSETTINGS_PATH > $APPSETTINGS_PATH.tmp
|
||||||
mv $APPSETTINGS_PATH.tmp $APPSETTINGS_PATH
|
mv $APPSETTINGS_PATH.tmp $APPSETTINGS_PATH
|
||||||
|
|
||||||
echo "Updated appsettings.json:"
|
echo "Updated appsettings.json:"
|
||||||
cat $APPSETTINGS_PATH
|
cat $APPSETTINGS_PATH
|
||||||
|
|
||||||
|
- name: Cache .NET SDK
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.dotnet
|
||||||
|
/usr/share/dotnet
|
||||||
|
key: ${{ runner.os }}-dotnet-10.0.x
|
||||||
|
|
||||||
|
- name: Cache NuGet packages
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ~/.nuget/packages
|
||||||
|
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.sln') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-nuget-
|
||||||
|
|
||||||
- name: Setup .NET 10
|
- name: Setup .NET 10
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
@@ -64,25 +74,22 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
chmod +x scripts/test-endpoints.sh
|
chmod +x scripts/test-endpoints.sh
|
||||||
bash scripts/test-endpoints.sh http://localhost:5038 1000 2>&1 | tee /tmp/webzine_endpoint_output.txt
|
bash scripts/test-endpoints.sh http://localhost:5038 1000 2>&1 | tee /tmp/webzine_endpoint_output.txt
|
||||||
EXIT_CODE=${PIPESTATUS[0]}
|
|
||||||
|
|
||||||
# Count failures
|
|
||||||
FAIL_COUNT=$(grep -cE "^\[ÉCHEC\]" /tmp/webzine_endpoint_output.txt 2>/dev/null || echo 0)
|
FAIL_COUNT=$(grep -cE "^\[ÉCHEC\]" /tmp/webzine_endpoint_output.txt 2>/dev/null || echo 0)
|
||||||
SLOW_COUNT=$(grep -cE "^\[LENT\]" /tmp/webzine_endpoint_output.txt 2>/dev/null || echo 0)
|
SLOW_COUNT=$(grep -cE "^\[LENT\]" /tmp/webzine_endpoint_output.txt 2>/dev/null || echo 0)
|
||||||
|
|
||||||
echo "failed=$FAIL_COUNT" >> "$GITHUB_OUTPUT"
|
echo "failed=$FAIL_COUNT" >> "$GITHUB_OUTPUT"
|
||||||
echo "slow=$SLOW_COUNT" >> "$GITHUB_OUTPUT"
|
echo "slow=$SLOW_COUNT" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
# Échoue s’il y a DES problèmes (échecs OU lents)
|
|
||||||
if [ $FAIL_COUNT -gt 0 ] || [ $SLOW_COUNT -gt 0 ]; then
|
if [ $FAIL_COUNT -gt 0 ] || [ $SLOW_COUNT -gt 0 ]; then
|
||||||
echo "❌ Performance check failed: $FAIL_COUNT endpoint(s) failed, $SLOW_COUNT endpoint(s) exceeded threshold (1000ms)"
|
echo "❌ Performance check failed: $FAIL_COUNT endpoint(s) failed, $SLOW_COUNT endpoint(s) exceeded threshold (1000ms)"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "✅ All endpoints passed performance check (< 1000ms)"
|
echo "✅ All endpoints passed performance check (< 1000ms)"
|
||||||
|
|
||||||
- name: Post performance report as PR comment
|
- name: Post performance report as PR comment
|
||||||
if: always() # Always post comment, even on failure
|
if: always()
|
||||||
env:
|
env:
|
||||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
GITEA_SERVER_URL: ${{ gitea.server_url }}
|
GITEA_SERVER_URL: ${{ gitea.server_url }}
|
||||||
@@ -92,20 +99,15 @@ jobs:
|
|||||||
RAW_REPORT=$(cat /tmp/webzine_endpoint_output.txt 2>/dev/null || echo "Aucune sortie capturée.")
|
RAW_REPORT=$(cat /tmp/webzine_endpoint_output.txt 2>/dev/null || echo "Aucune sortie capturée.")
|
||||||
FAILED_COUNT="${{ steps.perf_test.outputs.failed }}"
|
FAILED_COUNT="${{ steps.perf_test.outputs.failed }}"
|
||||||
SLOW_COUNT="${{ steps.perf_test.outputs.slow }}"
|
SLOW_COUNT="${{ steps.perf_test.outputs.slow }}"
|
||||||
|
|
||||||
# Determine if the check passed or failed
|
|
||||||
if [ "$FAILED_COUNT" -gt 0 ] || [ "$SLOW_COUNT" -gt 0 ]; then
|
if [ "$FAILED_COUNT" -gt 0 ] || [ "$SLOW_COUNT" -gt 0 ]; then
|
||||||
STATUS_HEADER="❌ **PERFORMANCE CHECK FAILED**"
|
STATUS_HEADER="❌ **PERFORMANCE CHECK FAILED**"
|
||||||
STATUS_ICON="❌"
|
|
||||||
else
|
else
|
||||||
STATUS_HEADER="✅ **PERFORMANCE CHECK PASSED**"
|
STATUS_HEADER="✅ **PERFORMANCE CHECK PASSED**"
|
||||||
STATUS_ICON="✅"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Convert report to Markdown with colors
|
|
||||||
CLEAN_REPORT=$(echo "$RAW_REPORT" | sed 's/\x1b\[[0-9;]*m//g')
|
CLEAN_REPORT=$(echo "$RAW_REPORT" | sed 's/\x1b\[[0-9;]*m//g')
|
||||||
|
|
||||||
# Generate formatted report
|
|
||||||
FORMATTED_REPORT=$(echo "$CLEAN_REPORT" | sed \
|
FORMATTED_REPORT=$(echo "$CLEAN_REPORT" | sed \
|
||||||
-e 's/^\[OK\] /✅ /' \
|
-e 's/^\[OK\] /✅ /' \
|
||||||
-e 's/^\[LENT\] /⚠️ /' \
|
-e 's/^\[LENT\] /⚠️ /' \
|
||||||
@@ -120,31 +122,18 @@ jobs:
|
|||||||
-e 's/^\(⚠️ ENDPOINTS LENTS.*\)$/\n### \1/' \
|
-e 's/^\(⚠️ ENDPOINTS LENTS.*\)$/\n### \1/' \
|
||||||
-e 's/^\(❌ ENDPOINTS EN ÉCHEC.*\)$/\n### \1/' \
|
-e 's/^\(❌ ENDPOINTS EN ÉCHEC.*\)$/\n### \1/' \
|
||||||
-e 's/^\(La PR doit.*\)$/\n**❌ \1**/')
|
-e 's/^\(La PR doit.*\)$/\n**❌ \1**/')
|
||||||
|
|
||||||
BODY=$(cat <<EOF
|
BODY=$(cat <<EOF
|
||||||
$STATUS_HEADER
|
$STATUS_HEADER
|
||||||
|
|
||||||
$FORMATTED_REPORT
|
$FORMATTED_REPORT
|
||||||
|
|
||||||
---
|
|
||||||
**Seuil**: 1000ms
|
|
||||||
**Statistiques**:
|
|
||||||
- ✅ Endpoints rapides: \$(grep -c "^\[OK\]" /tmp/webzine_endpoint_output.txt 2>/dev/null || echo 0)
|
|
||||||
- ⚠️ Endpoints lents (>1000ms): $SLOW_COUNT
|
|
||||||
- ❌ Endpoints en échec: $FAILED_COUNT
|
|
||||||
|
|
||||||
**Vérifié par**: Workflow PR Endpoint Performance
|
**Vérifié par**: Workflow PR Endpoint Performance
|
||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
|
|
||||||
curl -s -X POST \
|
curl -s -X POST \
|
||||||
-H "Authorization: token $GITEA_TOKEN" \
|
-H "Authorization: token $GITEA_TOKEN" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "$(jq -n --arg body "$BODY" '{body: $body}')" \
|
-d "$(jq -n --arg body "$BODY" '{body: $body}')" \
|
||||||
"$GITEA_SERVER_URL/api/v1/repos/$REPO/issues/$PR_NUMBER/comments"
|
"$GITEA_SERVER_URL/api/v1/repos/$REPO/issues/$PR_NUMBER/comments"
|
||||||
|
|
||||||
- name: Fail job if performance issues detected
|
|
||||||
if: steps.perf_test.outputs.failed > 0 || steps.perf_test.outputs.slow > 0
|
|
||||||
run: |
|
|
||||||
echo "❌ Job failed due to performance issues"
|
|
||||||
exit 1
|
|
||||||
@@ -7,7 +7,7 @@ EXPOSE 8081
|
|||||||
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||||
ARG BUILD_CONFIGURATION=Release
|
ARG BUILD_CONFIGURATION=Release
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY ["Webzine.WebApplication/Webzine.WebApplication.csproj", "Webzine.WebApplication/"]
|
COPY ["./Webzine.WebApplication/Webzine.WebApplication.csproj", "Webzine.WebApplication/"]
|
||||||
RUN dotnet restore "Webzine.WebApplication/Webzine.WebApplication.csproj"
|
RUN dotnet restore "Webzine.WebApplication/Webzine.WebApplication.csproj"
|
||||||
COPY . .
|
COPY . .
|
||||||
WORKDIR "/src/Webzine.WebApplication"
|
WORKDIR "/src/Webzine.WebApplication"
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
namespace Webzine.WebApplication.Areas.Administration.ViewModels;
|
namespace Webzine.Business.Contracts.Dto;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ViewModel pour le tableau de bord de l'administration du webzine.
|
/// DTO pour le tableau de bord de l'administration du webzine.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DashboardViewModel
|
public class DashboardDTO
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Définit le nombre total d'artistes chroniqués dans le webzine.
|
/// Définit le nombre total d'artistes chroniqués dans le webzine.
|
||||||
57
Webzine.Business.Contracts/Dto/TitreAdminDTO.cs
Normal file
57
Webzine.Business.Contracts/Dto/TitreAdminDTO.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
namespace Webzine.Business.Contracts.Dto;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dto transportant les données métier d'un titre saisi en administration.
|
||||||
|
/// </summary>
|
||||||
|
public class TitreAdminDTO
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Identifiant du titre (0 lors d'une création).
|
||||||
|
/// </summary>
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Identifiant de l'artiste sélectionné.
|
||||||
|
/// </summary>
|
||||||
|
public int IdArtiste { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Libellé du titre.
|
||||||
|
/// </summary>
|
||||||
|
public string Libelle { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Nom de l'album.
|
||||||
|
/// </summary>
|
||||||
|
public string Album { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Texte de la chronique.
|
||||||
|
/// </summary>
|
||||||
|
public string Chronique { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Date de sortie du titre.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime DateSortie { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Durée en secondes.
|
||||||
|
/// </summary>
|
||||||
|
public int Duree { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// URL de la jaquette.
|
||||||
|
/// </summary>
|
||||||
|
public string UrlJaquette { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// URL d'écoute.
|
||||||
|
/// </summary>
|
||||||
|
public string? UrlEcoute { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Identifiants des styles sélectionnés.
|
||||||
|
/// </summary>
|
||||||
|
public List<int> Styles { get; set; } = new ();
|
||||||
|
}
|
||||||
16
Webzine.Business.Contracts/IDashboardService.cs
Normal file
16
Webzine.Business.Contracts/IDashboardService.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
namespace Webzine.Business.Contracts;
|
||||||
|
|
||||||
|
using Webzine.Business.Contracts.Dto;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Service responsable du calcul des statistiques affichées sur le tableau de bord d'administration.
|
||||||
|
/// Agrège les données provenant de plusieurs repositories pour produire un résumé cohérent.
|
||||||
|
/// </summary>
|
||||||
|
public interface IDashboardService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Calcule et retourne toutes les statistiques du tableau de bord en une seule passe.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Un <see cref="DashboardDTO"/> contenant les agrégats calculés.</returns>
|
||||||
|
DashboardDTO GetDashboardData();
|
||||||
|
}
|
||||||
22
Webzine.Business.Contracts/ITitreAdminService.cs
Normal file
22
Webzine.Business.Contracts/ITitreAdminService.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
namespace Webzine.Business.Contracts;
|
||||||
|
|
||||||
|
using Webzine.Business.Contracts.Dto;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Service responsable des opérations d'administration sur les titres.
|
||||||
|
/// Orchestre la résolution des dépendances (artiste, styles) et la persistance.
|
||||||
|
/// </summary>
|
||||||
|
public interface ITitreAdminService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Crée un nouveau titre à partir des données du formulaire d'administration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="commande">Les données saisies dans le formulaire de création.</param>
|
||||||
|
void CreerTitre(TitreAdminDTO commande);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Met à jour un titre existant à partir des données du formulaire d'administration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="commande">Les données saisies dans le formulaire de modification.</param>
|
||||||
|
void ModifierTitre(TitreAdminDTO commande);
|
||||||
|
}
|
||||||
34
Webzine.Business/DTOs/Spotify/SpotifyAlbumDto.cs
Normal file
34
Webzine.Business/DTOs/Spotify/SpotifyAlbumDto.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
namespace Webzine.Business.DTOs.Spotify
|
||||||
|
{
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Objet album de spotify.
|
||||||
|
/// </summary>
|
||||||
|
public class SpotifyAlbumDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Id de l'album.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("id")]
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Nom de l'album.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Date de sortie.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("release_date")]
|
||||||
|
public string? ReleaseDate { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Urls de la jaquette.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("images")]
|
||||||
|
public List<SpotifyImageDto> Images { get; set; } = new ();
|
||||||
|
}
|
||||||
|
}
|
||||||
16
Webzine.Business/DTOs/Spotify/SpotifyAlbumsResponseDto.cs
Normal file
16
Webzine.Business/DTOs/Spotify/SpotifyAlbumsResponseDto.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
namespace Webzine.Business.DTOs.Spotify
|
||||||
|
{
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Objet Spotify qui contient une liste d'album.
|
||||||
|
/// </summary>
|
||||||
|
public class SpotifyAlbumsResponseDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Container de plusieurs albums spotify.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("items")]
|
||||||
|
public List<SpotifyAlbumDto> Items { get; set; } = new ();
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Webzine.Business/DTOs/Spotify/SpotifyArtistDto.cs
Normal file
28
Webzine.Business/DTOs/Spotify/SpotifyArtistDto.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
namespace Webzine.Business.DTOs.Spotify
|
||||||
|
{
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Objet artiste retourne depuis spotify.
|
||||||
|
/// </summary>
|
||||||
|
public class SpotifyArtistDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Id de l'artiste.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("id")]
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Nom de l'artiste.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Genre musical de l'artiste.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("genres")]
|
||||||
|
public List<string> Genres { get; set; } = new ();
|
||||||
|
}
|
||||||
|
}
|
||||||
16
Webzine.Business/DTOs/Spotify/SpotifyArtistsContainerDto.cs
Normal file
16
Webzine.Business/DTOs/Spotify/SpotifyArtistsContainerDto.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
namespace Webzine.Business.DTOs.Spotify
|
||||||
|
{
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Container d'artistes spotify.
|
||||||
|
/// </summary>
|
||||||
|
public class SpotifyArtistsContainerDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Liste d'artiste spotify.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("items")]
|
||||||
|
public List<SpotifyArtistDto> Items { get; set; } = new ();
|
||||||
|
}
|
||||||
|
}
|
||||||
9
Webzine.Business/DTOs/Spotify/SpotifyExternalUrlsDto.cs
Normal file
9
Webzine.Business/DTOs/Spotify/SpotifyExternalUrlsDto.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Webzine.Business.DTOs.Spotify;
|
||||||
|
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
public class SpotifyExternalUrlsDto
|
||||||
|
{
|
||||||
|
[JsonPropertyName("spotify")]
|
||||||
|
public string? Spotify { get; set; }
|
||||||
|
}
|
||||||
9
Webzine.Business/DTOs/Spotify/SpotifyImageDto.cs
Normal file
9
Webzine.Business/DTOs/Spotify/SpotifyImageDto.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Webzine.Business.DTOs.Spotify;
|
||||||
|
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
public class SpotifyImageDto
|
||||||
|
{
|
||||||
|
[JsonPropertyName("url")]
|
||||||
|
public string? Url { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
namespace Webzine.Business.DTOs.Spotify
|
||||||
|
{
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resultat d'une recherche spotify avec un objet artist.
|
||||||
|
/// </summary>
|
||||||
|
public class SpotifySearchArtistsResponseDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Liste d'artistes.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("artists")]
|
||||||
|
public SpotifyArtistsContainerDto? Artists { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
16
Webzine.Business/DTOs/Spotify/SpotifyTokenResponseDto.cs
Normal file
16
Webzine.Business/DTOs/Spotify/SpotifyTokenResponseDto.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
namespace Webzine.Business.DTOs.Spotify
|
||||||
|
{
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recuperation Token Bearer de spotify.
|
||||||
|
/// </summary>
|
||||||
|
public class SpotifyTokenResponseDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Jeton d'acces.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("access_token")]
|
||||||
|
public string? AccessToken { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
34
Webzine.Business/DTOs/Spotify/SpotifyTrackDto.cs
Normal file
34
Webzine.Business/DTOs/Spotify/SpotifyTrackDto.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
namespace Webzine.Business.DTOs.Spotify
|
||||||
|
{
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Objet track de spotify.
|
||||||
|
/// </summary>
|
||||||
|
public class SpotifyTrackDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Id de la track.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("id")]
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Nom de la musique.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Duree de la musique en millieseconde.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("duration_ms")]
|
||||||
|
public int DurationMs { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// urls spotify.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("external_urls")]
|
||||||
|
public SpotifyExternalUrlsDto? ExternalUrls { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
16
Webzine.Business/DTOs/Spotify/SpotifyTracksResponseDto.cs
Normal file
16
Webzine.Business/DTOs/Spotify/SpotifyTracksResponseDto.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
namespace Webzine.Business.DTOs.Spotify
|
||||||
|
{
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reponse spotify qui contient une liste de tracks.
|
||||||
|
/// </summary>
|
||||||
|
public class SpotifyTracksResponseDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Container qui contient plusieurs tracks.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("items")]
|
||||||
|
public List<SpotifyTrackDto> Items { get; set; } = new ();
|
||||||
|
}
|
||||||
|
}
|
||||||
54
Webzine.Business/DashboardService.cs
Normal file
54
Webzine.Business/DashboardService.cs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
namespace Webzine.Business;
|
||||||
|
|
||||||
|
using Webzine.Business.Contracts;
|
||||||
|
using Webzine.Business.Contracts.Dto;
|
||||||
|
using Webzine.Repository.Contracts;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implémentation de <see cref="IDashboardService"/>.
|
||||||
|
/// Orchestre plusieurs appels aux repositories pour produire les statistiques du tableau de bord.
|
||||||
|
/// </summary>
|
||||||
|
public class DashboardService : IDashboardService
|
||||||
|
{
|
||||||
|
private readonly IArtisteRepository artisteRepository;
|
||||||
|
private readonly ITitreRepository titreRepository;
|
||||||
|
private readonly IStyleRepository styleRepository;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DashboardService"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="artisteRepository">Repository des artistes.</param>
|
||||||
|
/// <param name="titreRepository">Repository des titres.</param>
|
||||||
|
/// <param name="styleRepository">Repository des styles.</param>
|
||||||
|
public DashboardService(
|
||||||
|
IArtisteRepository artisteRepository,
|
||||||
|
ITitreRepository titreRepository,
|
||||||
|
IStyleRepository styleRepository)
|
||||||
|
{
|
||||||
|
this.artisteRepository = artisteRepository;
|
||||||
|
this.titreRepository = titreRepository;
|
||||||
|
this.styleRepository = styleRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public DashboardDTO GetDashboardData()
|
||||||
|
{
|
||||||
|
string artisteLePlusChronique = this.titreRepository.FindMostReviewedArtistName() ?? string.Empty;
|
||||||
|
string albumLePlusChronique = this.titreRepository.FindArtistNameWithMostReviewedAlbums() ?? string.Empty;
|
||||||
|
var musiqueLaPlusJouee = this.titreRepository.FindMostPlayedTitle();
|
||||||
|
|
||||||
|
return new DashboardDTO
|
||||||
|
{
|
||||||
|
NombreArtistes = this.artisteRepository.Count(),
|
||||||
|
ArtisteLePlusChronique = artisteLePlusChronique,
|
||||||
|
AlbumLePlusChronique = albumLePlusChronique,
|
||||||
|
NombreBiographies = this.artisteRepository.CountWithBiography(),
|
||||||
|
IdMusiqueLaPlusJouee = musiqueLaPlusJouee?.IdTitre ?? 0,
|
||||||
|
MusiqueLaPlusJouee = musiqueLaPlusJouee?.Libelle ?? string.Empty,
|
||||||
|
NombreTitres = this.titreRepository.Count(),
|
||||||
|
NombreGenres = this.styleRepository.Count(),
|
||||||
|
NombreLectures = this.titreRepository.CountLecture(),
|
||||||
|
NombreLikes = this.titreRepository.CountLike(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
128
Webzine.Business/Mappers/Spotify/SpotifyMapper.cs
Normal file
128
Webzine.Business/Mappers/Spotify/SpotifyMapper.cs
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
namespace Webzine.Business.Mappers.Spotify
|
||||||
|
{
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
using Webzine.Business.DTOs.Spotify;
|
||||||
|
using Webzine.Entity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mapper pour transformer les objets de Spotify (DTOs) en Entity.
|
||||||
|
/// </summary>
|
||||||
|
public static class SpotifyMapper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Permet d'ajouter ou de creer un style.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="styles">Dictionnaire string, Style.</param>
|
||||||
|
/// <param name="genre">Genre.</param>
|
||||||
|
/// <param name="nextStyleId">Id du style.</param>
|
||||||
|
/// <returns>Le style.</returns>
|
||||||
|
public static Style GetOrCreateStyle(Dictionary<string, Style> styles, string genre, ref int nextStyleId)
|
||||||
|
{
|
||||||
|
// On verifie si le genre est présent dans la liste de styles.
|
||||||
|
if (!styles.TryGetValue(genre, out var style))
|
||||||
|
{
|
||||||
|
// Creation d'un nouveau style.
|
||||||
|
style = new Style
|
||||||
|
{
|
||||||
|
IdStyle = nextStyleId++,
|
||||||
|
Libelle = genre,
|
||||||
|
Titres = new List<Titre>(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ajout dans la liste.
|
||||||
|
styles.Add(style.Libelle, style);
|
||||||
|
}
|
||||||
|
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creation d'un nouvel artiste a l'aide des infos Spotify.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="artisteSpotify">Artiste spotify.</param>
|
||||||
|
/// <param name="stylesTitre">Style spotify.</param>
|
||||||
|
/// <param name="idArtiste">Id de l'artiste.</param>
|
||||||
|
/// <returns>Artiste.</returns>
|
||||||
|
public static Artiste ToArtiste(SpotifyArtistDto artisteSpotify, List<Style> stylesTitre, int idArtiste)
|
||||||
|
{
|
||||||
|
// Spotify ne possède pas de Biographie pour les artistes.
|
||||||
|
// On affiche donc son nom, et les styles qui lui sont associés.
|
||||||
|
return new Artiste
|
||||||
|
{
|
||||||
|
IdArtiste = idArtiste,
|
||||||
|
Nom = artisteSpotify.Name,
|
||||||
|
Biographie = $"{artisteSpotify.Name} est un artiste present sur Spotify, associe aux styles {string.Join(", ", stylesTitre.Select(s => s.Libelle))}.",
|
||||||
|
Titres = new List<Titre>(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Permet de creer un titre depuis les donn<6E>es de Spotify.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="track">Titre de spotify.</param>
|
||||||
|
/// <param name="album">Album de spotify.</param>
|
||||||
|
/// <param name="artiste">Artiste mappe.</param>
|
||||||
|
/// <param name="stylesTitre">Style du titre.</param>
|
||||||
|
/// <param name="idTitre">Id du titre.</param>
|
||||||
|
/// <returns>Nouveau Titre.</returns>
|
||||||
|
public static Titre ToTitre(SpotifyTrackDto track, SpotifyAlbumDto album, Artiste artiste, List<Style> stylesTitre, int idTitre)
|
||||||
|
{
|
||||||
|
// Spotify ne fournit pas les elements suivants : Chronique, DateCreation, NbLectures, NbLikes.
|
||||||
|
return new Titre
|
||||||
|
{
|
||||||
|
IdTitre = idTitre,
|
||||||
|
IdArtiste = artiste.IdArtiste,
|
||||||
|
Artiste = artiste,
|
||||||
|
Libelle = track.Name,
|
||||||
|
Chronique = $"{track.Name} est un titre de {artiste.Nom}, issu de l'album {album.Name}. Cette fiche a ete generee depuis Spotify.",
|
||||||
|
DateCreation = DateTime.UtcNow,
|
||||||
|
DateSortie = ParseDate(album.ReleaseDate),
|
||||||
|
Duree = Math.Max(1, track.DurationMs / 1000),
|
||||||
|
UrlJaquette = album.Images.FirstOrDefault()?.Url,
|
||||||
|
UrlEcoute = Trim(track.ExternalUrls?.Spotify ?? $"https://open.spotify.com/track/{track.Id}", 250, string.Empty),
|
||||||
|
NbLectures = Random.Shared.Next(500, 50000),
|
||||||
|
NbLikes = Random.Shared.Next(50, 5000),
|
||||||
|
Album = album.Name,
|
||||||
|
Commentaires = new List<Commentaire>(),
|
||||||
|
Styles = new List<Style>(stylesTitre),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Trim(string? value, int maxLength, string fallback)
|
||||||
|
{
|
||||||
|
var result = string.IsNullOrWhiteSpace(value) ? fallback : value.Trim();
|
||||||
|
return result.Length > maxLength ? result[..maxLength] : result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Permet de transformer une date recu en chaine de caractere en type DateTime.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Date en chaine de caractere.</param>
|
||||||
|
/// <returns>DateTime.</returns>
|
||||||
|
private static DateTime ParseDate(string? value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DateTime.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var date))
|
||||||
|
{
|
||||||
|
return DateTime.SpecifyKind(date, DateTimeKind.Utc);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DateTime.TryParseExact(value, "yyyy-MM", CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
|
||||||
|
{
|
||||||
|
return DateTime.SpecifyKind(date, DateTimeKind.Utc);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DateTime.TryParseExact(value, "yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
|
||||||
|
{
|
||||||
|
return DateTime.SpecifyKind(date, DateTimeKind.Utc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
162
Webzine.Business/Seeders/SeedDataSpotify.cs
Normal file
162
Webzine.Business/Seeders/SeedDataSpotify.cs
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
namespace Webzine.Business.Seeders;
|
||||||
|
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
using Webzine.Business.DTOs.Spotify;
|
||||||
|
using Webzine.Business.Mappers.Spotify;
|
||||||
|
using Webzine.Entity;
|
||||||
|
using Webzine.Entity.Fixtures;
|
||||||
|
|
||||||
|
public class SeedDataSpotify
|
||||||
|
{
|
||||||
|
private readonly HttpClient httpClient;
|
||||||
|
private readonly SpotifySeederOptions options;
|
||||||
|
|
||||||
|
public SeedDataSpotify(HttpClient httpClient, IOptions<SpotifySeederOptions> optionsAccessor)
|
||||||
|
{
|
||||||
|
this.httpClient = httpClient;
|
||||||
|
this.options = optionsAccessor.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Générer les données de la base a l'aide des données de spotify.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken">Cancellation token.</param>
|
||||||
|
/// <returns>Jeu de données.</returns>
|
||||||
|
/// <exception cref="InvalidOperationException">Erreur de connexion a spotify.</exception>
|
||||||
|
public async Task<SeedDataSet> GenererJeuDeDonneesAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
// Verification des parametres pour l'acces a Spotify.
|
||||||
|
if (string.IsNullOrWhiteSpace(this.options.ClientId) || string.IsNullOrWhiteSpace(this.options.ClientSecret))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Renseignez SpotifySeeder:ClientId et SpotifySeeder:ClientSecret.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var token = await this.GetTokenAsync(cancellationToken);
|
||||||
|
var styles = new Dictionary<string, Style>();
|
||||||
|
var artistes = new List<Artiste>();
|
||||||
|
var titres = new List<Titre>();
|
||||||
|
var commentaires = new List<Commentaire>();
|
||||||
|
var artistIds = new HashSet<string>();
|
||||||
|
int nextArtistId = 1;
|
||||||
|
int nextStyleId = 1;
|
||||||
|
int nextTitreId = 1;
|
||||||
|
int nextCommentaireId = 1;
|
||||||
|
|
||||||
|
foreach (var genre in this.options.Genres)
|
||||||
|
{
|
||||||
|
var artistesSpotify = await this.GetAsync<SpotifySearchArtistsResponseDto>(
|
||||||
|
$"https://api.spotify.com/v1/search?q={Uri.EscapeDataString($"genre:\"{genre}\"")}&type=artist&market={this.options.Market}&limit={this.options.ArtistsPerGenre}",
|
||||||
|
token,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
foreach (var artisteSpotify in artistesSpotify?.Artists?.Items ??[])
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(artisteSpotify.Id) || !artistIds.Add(artisteSpotify.Id))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var stylesTitre = (artisteSpotify.Genres.Count > 0 ? artisteSpotify.Genres :[genre])
|
||||||
|
.Select(g => SpotifyMapper.GetOrCreateStyle(styles, g, ref nextStyleId))
|
||||||
|
.DistinctBy(s => s.IdStyle)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var artiste = SpotifyMapper.ToArtiste(artisteSpotify, stylesTitre, nextArtistId++);
|
||||||
|
artistes.Add(artiste);
|
||||||
|
|
||||||
|
var albums = await this.GetAsync<SpotifyAlbumsResponseDto>(
|
||||||
|
$"https://api.spotify.com/v1/artists/{artisteSpotify.Id}/albums?include_groups=album,single&market={this.options.Market}",
|
||||||
|
token,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
foreach (var album in albums?.Items.GroupBy(a => a.Name).Select(g => g.First()) ??[])
|
||||||
|
{
|
||||||
|
var tracks = await this.GetAsync<SpotifyTracksResponseDto>(
|
||||||
|
$"https://api.spotify.com/v1/albums/{album.Id}/tracks?market={this.options.Market}&limit={Math.Clamp(this.options.TracksPerAlbum, 1, 10)}",
|
||||||
|
token,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
foreach (var track in tracks?.Items ??[])
|
||||||
|
{
|
||||||
|
var titre = SpotifyMapper.ToTitre(track, album, artiste, stylesTitre, nextTitreId++);
|
||||||
|
|
||||||
|
var commentairesTitre = SeedDataLocal.GenererListeCommentaire(
|
||||||
|
titre,
|
||||||
|
0,
|
||||||
|
Math.Clamp(this.options.MaxCommentsPerTrack, 0, 10),
|
||||||
|
nextCommentaireId);
|
||||||
|
|
||||||
|
nextCommentaireId += commentairesTitre.Count;
|
||||||
|
|
||||||
|
titre.Commentaires.AddRange(commentairesTitre);
|
||||||
|
commentaires.AddRange(commentairesTitre);
|
||||||
|
titres.Add(titre);
|
||||||
|
artiste.Titres.Add(titre);
|
||||||
|
|
||||||
|
foreach (var style in titre.Styles)
|
||||||
|
{
|
||||||
|
style.Titres.Add(titre);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SeedDataSet
|
||||||
|
{
|
||||||
|
Artistes = artistes,
|
||||||
|
Styles = styles.Values.OrderBy(s => s.IdStyle).ToList(),
|
||||||
|
Titres = titres,
|
||||||
|
Commentaires = commentaires,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recuperation du token d'access a Spotify.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken">Permet d'annuler la requete HTTP en cas de probleme.</param>
|
||||||
|
/// <returns>Le token d'acces a spotify.</returns>
|
||||||
|
/// <exception cref="InvalidOperationException">Le token spotify est introuvable.</exception>
|
||||||
|
private async Task<string> GetTokenAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
using var request = new HttpRequestMessage(HttpMethod.Post, "https://accounts.spotify.com/api/token");
|
||||||
|
request.Content = new FormUrlEncodedContent(
|
||||||
|
[
|
||||||
|
new KeyValuePair<string, string>("grant_type", "client_credentials"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
var basic = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{this.options.ClientId}:{this.options.ClientSecret}"));
|
||||||
|
request.Headers.Authorization = new AuthenticationHeaderValue("Basic", basic);
|
||||||
|
|
||||||
|
using var response = await this.httpClient.SendAsync(request, cancellationToken);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var payload = await response.Content.ReadFromJsonAsync<SpotifyTokenResponseDto>(cancellationToken: cancellationToken);
|
||||||
|
return payload?.AccessToken ?? throw new InvalidOperationException("Token Spotify introuvable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recuperation d'information depuis spotify, en formattant
|
||||||
|
/// la requete avec le Bearer Token.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">DTOs Spotify.</typeparam>
|
||||||
|
/// <param name="url">URL Spotify.</param>
|
||||||
|
/// <param name="token">Token Bearer pour authorisation d'acces a l'api.</param>
|
||||||
|
/// <param name="cancellationToken">Permet d'annuler la requete.</param>
|
||||||
|
/// <returns>Reponse Spotify deserialiser.</returns>
|
||||||
|
private async Task<T?> GetAsync<T>(string url, string token, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// Formattage de la requete HTTP avec le Bearer token.
|
||||||
|
using var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||||
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||||
|
|
||||||
|
using var response = await this.httpClient.SendAsync(request, cancellationToken);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
return await response.Content.ReadFromJsonAsync<T>(cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Webzine.Business/Seeders/SpotifySeederOptions.cs
Normal file
28
Webzine.Business/Seeders/SpotifySeederOptions.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
namespace Webzine.Business.Seeders;
|
||||||
|
|
||||||
|
public class SpotifySeederOptions
|
||||||
|
{
|
||||||
|
public string ClientId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string ClientSecret { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Market { get; set; } = "FR";
|
||||||
|
|
||||||
|
public List<string> Genres { get; set; } =
|
||||||
|
[
|
||||||
|
"rock",
|
||||||
|
"pop",
|
||||||
|
"jazz",
|
||||||
|
"hip hop",
|
||||||
|
"electronic",
|
||||||
|
"metal",
|
||||||
|
];
|
||||||
|
|
||||||
|
public int ArtistsPerGenre { get; set; } = 4;
|
||||||
|
|
||||||
|
public int AlbumsPerArtist { get; set; } = 2;
|
||||||
|
|
||||||
|
public int TracksPerAlbum { get; set; } = 4;
|
||||||
|
|
||||||
|
public int MaxCommentsPerTrack { get; set; } = 3;
|
||||||
|
}
|
||||||
124
Webzine.Business/TitreAdminService.cs
Normal file
124
Webzine.Business/TitreAdminService.cs
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
namespace Webzine.Business;
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
using Webzine.Business.Contracts;
|
||||||
|
using Webzine.Business.Contracts.Dto;
|
||||||
|
using Webzine.Entity;
|
||||||
|
using Webzine.Repository.Contracts;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implémentation de <see cref="ITitreAdminService"/>.
|
||||||
|
/// Orchestre la résolution des styles, la construction de l'entité
|
||||||
|
/// et la délégation au repository.
|
||||||
|
/// </summary>
|
||||||
|
public class TitreAdminService : ITitreAdminService
|
||||||
|
{
|
||||||
|
private readonly ITitreRepository titreRepository;
|
||||||
|
private readonly IArtisteRepository artisteRepository;
|
||||||
|
private readonly IStyleRepository styleRepository;
|
||||||
|
private readonly ILogger<TitreAdminService> logger;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="TitreAdminService"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="titreRepository">Repository des titres.</param>
|
||||||
|
/// <param name="artisteRepository">Repository des artistes.</param>
|
||||||
|
/// <param name="styleRepository">Repository des styles.</param>
|
||||||
|
/// <param name="logger">Service de journalisation.</param>
|
||||||
|
public TitreAdminService(
|
||||||
|
ITitreRepository titreRepository,
|
||||||
|
IArtisteRepository artisteRepository,
|
||||||
|
IStyleRepository styleRepository,
|
||||||
|
ILogger<TitreAdminService> logger)
|
||||||
|
{
|
||||||
|
this.titreRepository = titreRepository;
|
||||||
|
this.artisteRepository = artisteRepository;
|
||||||
|
this.styleRepository = styleRepository;
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void CreerTitre(TitreAdminDTO commande)
|
||||||
|
{
|
||||||
|
this.logger.LogInformation(
|
||||||
|
"Création d'un nouveau titre '{Libelle}' pour l'artiste ID {IdArtiste}.",
|
||||||
|
commande.Libelle,
|
||||||
|
commande.IdArtiste);
|
||||||
|
|
||||||
|
Artiste artiste = this.artisteRepository.Find(commande.IdArtiste);
|
||||||
|
List<Style> styles = this.ResoudreStyles(commande.Styles);
|
||||||
|
|
||||||
|
var titre = new Titre
|
||||||
|
{
|
||||||
|
IdArtiste = artiste.IdArtiste,
|
||||||
|
Artiste = artiste,
|
||||||
|
Libelle = commande.Libelle,
|
||||||
|
Album = commande.Album,
|
||||||
|
Chronique = commande.Chronique,
|
||||||
|
DateCreation = DateTime.UtcNow,
|
||||||
|
DateSortie = commande.DateSortie,
|
||||||
|
Duree = commande.Duree,
|
||||||
|
UrlJaquette = commande.UrlJaquette,
|
||||||
|
UrlEcoute = commande.UrlEcoute ?? string.Empty,
|
||||||
|
Styles = styles,
|
||||||
|
Commentaires = new List<Commentaire>(),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.titreRepository.Add(titre);
|
||||||
|
|
||||||
|
this.logger.LogInformation("Titre '{Libelle}' créé avec succès (ID {IdTitre}).", titre.Libelle, titre.IdTitre);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void ModifierTitre(TitreAdminDTO commande)
|
||||||
|
{
|
||||||
|
this.logger.LogInformation("Modification du titre ID {Id} ('{Libelle}').", commande.Id, commande.Libelle);
|
||||||
|
|
||||||
|
List<Style> styles = this.ResoudreStyles(commande.Styles);
|
||||||
|
|
||||||
|
// On charge le titre existant pour ne pas écraser NbLectures / NbLikes
|
||||||
|
Titre existant = this.titreRepository.Find(commande.Id);
|
||||||
|
|
||||||
|
existant.IdArtiste = commande.IdArtiste;
|
||||||
|
existant.Libelle = commande.Libelle;
|
||||||
|
existant.Album = commande.Album;
|
||||||
|
existant.Chronique = commande.Chronique;
|
||||||
|
existant.DateSortie = commande.DateSortie;
|
||||||
|
existant.Duree = commande.Duree;
|
||||||
|
existant.UrlJaquette = commande.UrlJaquette;
|
||||||
|
existant.UrlEcoute = commande.UrlEcoute ?? string.Empty;
|
||||||
|
|
||||||
|
existant.Styles.Clear();
|
||||||
|
foreach (var style in styles)
|
||||||
|
{
|
||||||
|
existant.Styles.Add(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.titreRepository.Update(existant);
|
||||||
|
|
||||||
|
this.logger.LogInformation("Titre ID {Id} modifié avec succès.", commande.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Résout les entités <see cref="Style"/> à partir d'une liste d'identifiants.
|
||||||
|
/// Les identifiants introuvables sont ignorés avec un avertissement.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="styleIds">Identifiants des styles sélectionnés.</param>
|
||||||
|
/// <returns>Liste des styles résolus.</returns>
|
||||||
|
private List<Style> ResoudreStyles(List<int> styleIds)
|
||||||
|
{
|
||||||
|
var styles = new List<Style>();
|
||||||
|
|
||||||
|
foreach (int id in styleIds)
|
||||||
|
{
|
||||||
|
Style style = this.styleRepository.Find(id);
|
||||||
|
|
||||||
|
styles.Add(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.LogDebug("{NbResolus}/{NbDemandes} styles résolus avec succès.", styles.Count, styleIds.Count);
|
||||||
|
|
||||||
|
return styles;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Faker.Net" Version="2.0.163" />
|
<PackageReference Include="Faker.Net" Version="2.0.163" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||||
<PackageReference Include="NLog" Version="6.1.1" />
|
<PackageReference Include="NLog" Version="6.1.1" />
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
|
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
@@ -21,4 +22,11 @@
|
|||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Webzine.Business.Contracts\Webzine.Business.Contracts.csproj" />
|
||||||
|
<ProjectReference Include="..\Webzine.Entity\Webzine.Entity.csproj" />
|
||||||
|
<ProjectReference Include="..\Webzine.Repository.Contracts\Webzine.Repository.Contracts.csproj" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.5" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -8,9 +8,10 @@
|
|||||||
|
|
||||||
## Table des modifications
|
## Table des modifications
|
||||||
|
|
||||||
| Date | Auteur |
|
| Date | Auteur |
|
||||||
|------|--------|
|
| ----- | ------------- |
|
||||||
| 25/03 | Clément Bobin |
|
| 25/03 | Clément Bobin |
|
||||||
|
| 31/03 | Joséphine Vetu |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -18,12 +19,12 @@
|
|||||||
|
|
||||||
Avant de lancer l'application, assurez-vous d'avoir installé les outils suivants :
|
Avant de lancer l'application, assurez-vous d'avoir installé les outils suivants :
|
||||||
|
|
||||||
| Outil | Version minimale | Lien |
|
| Outil | Version minimale | Lien |
|
||||||
|-------|-----------------|------|
|
| -------------------------------------- | ---------------------------- | ------------------------------------- |
|
||||||
| .NET SDK | 10.0 | https://dotnet.microsoft.com/download |
|
| .NET SDK | 10.0 | https://dotnet.microsoft.com/download |
|
||||||
| Node.js (optionnel, pour outils front) | 18+ | https://nodejs.org |
|
| Node.js (optionnel, pour outils front) | 18+ | https://nodejs.org |
|
||||||
| Git | 2.x | https://git-scm.com |
|
| Git | 2.x | https://git-scm.com |
|
||||||
| Un IDE | Visual Studio 2022+ ou Rider | — |
|
| Un IDE | Visual Studio 2022+ ou Rider | — |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -65,6 +66,7 @@ dotnet run
|
|||||||
```
|
```
|
||||||
|
|
||||||
L'application est accessible par défaut à :
|
L'application est accessible par défaut à :
|
||||||
|
|
||||||
- **HTTP :** http://localhost:5038
|
- **HTTP :** http://localhost:5038
|
||||||
- **HTTPS :** https://localhost:7095
|
- **HTTPS :** https://localhost:7095
|
||||||
|
|
||||||
@@ -89,10 +91,10 @@ Le fichier principal de configuration est `Webzine.WebApplication/appsettings.js
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
| Propriété | Type | Description | Valeur par défaut |
|
| Propriété | Type | Description | Valeur par défaut |
|
||||||
|-----------|------|-------------|-------------------|
|
| ------------------------- | ---- | -------------------------------------------------------------------------------- | ----------------- |
|
||||||
| `NombreDerniereChronique` | int | Nombre de chroniques affichées sur la page d'accueil (section "Derniers titres") | 3 |
|
| `NombreDerniereChronique` | int | Nombre de chroniques affichées sur la page d'accueil (section "Derniers titres") | 3 |
|
||||||
| `NombreDeTopTitres` | int | Nombre de titres affichés dans le bloc "Titres les plus populaires" | 3 |
|
| `NombreDeTopTitres` | int | Nombre de titres affichés dans le bloc "Titres les plus populaires" | 3 |
|
||||||
|
|
||||||
Ces valeurs sont injectées dans `AccueilController` via `IConfiguration` et peuvent être modifiées sans recompilation.
|
Ces valeurs sont injectées dans `AccueilController` via `IConfiguration` et peuvent être modifiées sans recompilation.
|
||||||
|
|
||||||
@@ -106,14 +108,15 @@ La configuration du logging se trouve dans `Webzine.WebApplication/nlog.config`.
|
|||||||
|
|
||||||
Les logs sont écrits dans le dossier `/Logs/` avec deux fichiers :
|
Les logs sont écrits dans le dossier `/Logs/` avec deux fichiers :
|
||||||
|
|
||||||
| Fichier | Contenu |
|
| Fichier | Contenu |
|
||||||
|---------|---------|
|
| --------------------- | ----------------------------------------------------- |
|
||||||
| `nlog-all-{date}.log` | Tous les logs (Debug et supérieur pour l'application) |
|
| `nlog-all-{date}.log` | Tous les logs (Debug et supérieur pour l'application) |
|
||||||
| `nlog-own-{date}.log` | Logs applicatifs avec URL de la requête |
|
| `nlog-own-{date}.log` | Logs applicatifs avec URL de la requête |
|
||||||
|
|
||||||
Les logs de la console sont également activés, utiles lors du développement.
|
Les logs de la console sont également activés, utiles lors du développement.
|
||||||
|
|
||||||
**Niveaux configurés :**
|
**Niveaux configurés :**
|
||||||
|
|
||||||
- `Webzine.WebApplication.*` → Debug et supérieur
|
- `Webzine.WebApplication.*` → Debug et supérieur
|
||||||
- `Microsoft.*` → Warning et supérieur (pour réduire le bruit)
|
- `Microsoft.*` → Warning et supérieur (pour réduire le bruit)
|
||||||
- `Microsoft.Hosting.Lifetime*` → Info (pour voir le démarrage de l'appli)
|
- `Microsoft.Hosting.Lifetime*` → Info (pour voir le démarrage de l'appli)
|
||||||
@@ -143,18 +146,18 @@ Les tests vérifient les contraintes de validation (annotations DataAnnotations)
|
|||||||
|
|
||||||
## 8. Routes principales
|
## 8. Routes principales
|
||||||
|
|
||||||
| URL | Contrôleur | Description |
|
| URL | Contrôleur | Description |
|
||||||
|-----|-----------|-------------|
|
| ----------------------------- | --------------------------- | ------------------------ |
|
||||||
| `/` | `AccueilController.Index` | Page d'accueil |
|
| `/` | `AccueilController.Index` | Page d'accueil |
|
||||||
| `/artiste/{nom}` | `ArtisteController.Index` | Page d'un artiste |
|
| `/artiste/{nom}` | `ArtisteController.Index` | Page d'un artiste |
|
||||||
| `/titre/{id}` | `TitreController.Details` | Détail d'un titre |
|
| `/titre/{id}` | `TitreController.Details` | Détail d'un titre |
|
||||||
| `/titre/style/{style}` | `TitreController.Style` | Titres par style |
|
| `/titre/style/{style}` | `TitreController.Style` | Titres par style |
|
||||||
| `/recherche` (POST) | `RechercheController.Index` | Résultats de recherche |
|
| `/recherche` (POST) | `RechercheController.Index` | Résultats de recherche |
|
||||||
| `/Administration/Dashboard` | `DashboardController.Index` | Tableau de bord admin |
|
| `/Administration/Dashboard` | `DashboardController.Index` | Tableau de bord admin |
|
||||||
| `/Administration/Artiste` | `ArtisteController` (admin) | Gestion des artistes |
|
| `/Administration/Artiste` | `ArtisteController` (admin) | Gestion des artistes |
|
||||||
| `/Administration/Titre` | `TitreController` (admin) | Gestion des titres |
|
| `/Administration/Titre` | `TitreController` (admin) | Gestion des titres |
|
||||||
| `/Administration/Style` | `StyleController` | Gestion des styles |
|
| `/Administration/Style` | `StyleController` | Gestion des styles |
|
||||||
| `/Administration/Commentaire` | `CommentaireController` | Gestion des commentaires |
|
| `/Administration/Commentaire` | `CommentaireController` | Gestion des commentaires |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -168,4 +171,14 @@ En production, positionner :
|
|||||||
export ASPNETCORE_ENVIRONMENT=Production
|
export ASPNETCORE_ENVIRONMENT=Production
|
||||||
```
|
```
|
||||||
|
|
||||||
Cela désactive les pages d'erreur détaillées et active les optimisations de performance ASP.NET Core.
|
Cela désactive les pages d'erreur détaillées et active les optimisations de performance ASP.NET Core.
|
||||||
|
|
||||||
|
## Ajout du pre-commit
|
||||||
|
|
||||||
|
Les fichiers présents dans le dossier git_hooks sont à copier dans le dossier caché .git.
|
||||||
|
Le pre-commit reformatte le code à chaque commit.
|
||||||
|
Le commit-msg vérifie si le message du commit respecte bien les conventions du cahier des charges:
|
||||||
|
|
||||||
|
- le message référence un ticket
|
||||||
|
- le message termine par un point
|
||||||
|
- le message doit faire au moins 10 caractères
|
||||||
|
|||||||
172
Webzine.Documentation/SpotifySeederRecap.md
Normal file
172
Webzine.Documentation/SpotifySeederRecap.md
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
# Récapitulatif des modifications pour le seeder Spotify
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
Ajout d’un seeder capable de récupérer de vraies données depuis l’API Spotify pour alimenter l’application avec :
|
||||||
|
- des artistes
|
||||||
|
- des styles
|
||||||
|
- des titres
|
||||||
|
- des commentaires générés localement autour de ces vraies données
|
||||||
|
|
||||||
|
## 1. Nouveau seeder Spotify
|
||||||
|
|
||||||
|
### Fichier ajouté
|
||||||
|
[Webzine.Entity/Fixtures/SeedDataSpotify.cs](c:\Users\lmasi\Documents\DIIAGE\P4\Webzine\Webzine.Entity\Fixtures\SeedDataSpotify.cs)
|
||||||
|
|
||||||
|
### Ce qui a été fait
|
||||||
|
- création de la classe `SeedDataSpotify`
|
||||||
|
- ajout de la classe de configuration `SpotifySeederOptions`
|
||||||
|
- appel à l’API Spotify via `HttpClient`
|
||||||
|
- authentification avec le flux OAuth `client_credentials`
|
||||||
|
- récupération d’artistes par genre
|
||||||
|
- récupération des albums de chaque artiste
|
||||||
|
- récupération des titres de chaque album
|
||||||
|
- mapping des données Spotify vers les entités métier :
|
||||||
|
- `Artiste`
|
||||||
|
- `Style`
|
||||||
|
- `Titre`
|
||||||
|
- `Commentaire`
|
||||||
|
- génération de commentaires locaux via `SeedDataLocal`
|
||||||
|
- transformation des genres Spotify en `Style`
|
||||||
|
- génération de valeurs applicatives cohérentes :
|
||||||
|
- `Chronique`
|
||||||
|
- `NbLectures`
|
||||||
|
- `NbLikes`
|
||||||
|
- `DateSortie`
|
||||||
|
- `UrlJaquette`
|
||||||
|
- `UrlEcoute`
|
||||||
|
|
||||||
|
### Comportement
|
||||||
|
Le seeder produit un jeu de données exploitable par :
|
||||||
|
- le mode base de données
|
||||||
|
- le mode mémoire
|
||||||
|
|
||||||
|
## 2. Création d’un conteneur commun pour le seeding
|
||||||
|
|
||||||
|
### Fichier ajouté
|
||||||
|
[Webzine.Entity/Fixtures/SeedDataSet.cs](c:\Users\lmasi\Documents\DIIAGE\P4\Webzine\Webzine.Entity\Fixtures\SeedDataSet.cs)
|
||||||
|
|
||||||
|
### Ce qui a été fait
|
||||||
|
Ajout d’un objet `SeedDataSet` contenant :
|
||||||
|
- `Artistes`
|
||||||
|
- `Styles`
|
||||||
|
- `Titres`
|
||||||
|
- `Commentaires`
|
||||||
|
|
||||||
|
### Pourquoi
|
||||||
|
Cela permet d’unifier le résultat du seeding, qu’il vienne :
|
||||||
|
- du générateur local
|
||||||
|
- de Spotify
|
||||||
|
|
||||||
|
## 3. Adaptation du repository DB
|
||||||
|
|
||||||
|
### Fichier modifié
|
||||||
|
[Webzine.Repository/DbEntityRepository.cs](c:\Users\lmasi\Documents\DIIAGE\P4\Webzine\Webzine.Repository\DbEntityRepository.cs)
|
||||||
|
|
||||||
|
### Ce qui a été fait
|
||||||
|
Modification de `SeedBaseDeDonnees` pour qu’elle puisse :
|
||||||
|
- continuer à générer les données locales comme avant
|
||||||
|
- ou accepter un `SeedDataSet` externe déjà préparé
|
||||||
|
|
||||||
|
### Résultat
|
||||||
|
Le repository peut maintenant insérer en base :
|
||||||
|
- soit les données fake locales
|
||||||
|
- soit les données réelles récupérées depuis Spotify
|
||||||
|
|
||||||
|
## 4. Branchement du seeder dans le démarrage de l’application
|
||||||
|
|
||||||
|
### Fichier modifié
|
||||||
|
[Webzine.WebApplication/Program.cs](c:\Users\lmasi\Documents\DIIAGE\P4\Webzine\Webzine.WebApplication\Program.cs)
|
||||||
|
|
||||||
|
### Ce qui a été fait
|
||||||
|
- enregistrement de la configuration `SpotifySeeder`
|
||||||
|
- enregistrement de `SeedDataSpotify` via `AddHttpClient`
|
||||||
|
- lecture de `SeederType`
|
||||||
|
- ajout de la logique :
|
||||||
|
- si `Seeder = Spotify`, on appelle `SeedDataSpotify`
|
||||||
|
- sinon on garde `SeedDataLocal`
|
||||||
|
|
||||||
|
### Cas couverts
|
||||||
|
- `Repository = Db`
|
||||||
|
- `Repository = Local`
|
||||||
|
|
||||||
|
Donc le mode Spotify fonctionne aussi bien :
|
||||||
|
- avec SQLite/PostgreSQL
|
||||||
|
- qu’avec le store mémoire
|
||||||
|
|
||||||
|
## 5. Ajout de la configuration Spotify
|
||||||
|
|
||||||
|
### Fichiers modifiés
|
||||||
|
- [Webzine.WebApplication/appsettings.json](c:\Users\lmasi\Documents\DIIAGE\P4\Webzine\Webzine.WebApplication\appsettings.json)
|
||||||
|
- [Webzine.WebApplication/appsettings.Development.json](c:\Users\lmasi\Documents\DIIAGE\P4\Webzine\Webzine.WebApplication\appsettings.Development.json)
|
||||||
|
- [Webzine.WebApplication/appsettings.Production.json](c:\Users\lmasi\Documents\DIIAGE\P4\Webzine\Webzine.WebApplication\appsettings.Production.json)
|
||||||
|
|
||||||
|
### Ce qui a été ajouté
|
||||||
|
Section `SpotifySeeder` avec :
|
||||||
|
- `ClientId`
|
||||||
|
- `ClientSecret`
|
||||||
|
- `Market`
|
||||||
|
- `Genres`
|
||||||
|
- `ArtistsPerGenre`
|
||||||
|
- `AlbumsPerArtist`
|
||||||
|
- `TracksPerAlbum`
|
||||||
|
- `MaxCommentsPerTrack`
|
||||||
|
|
||||||
|
### Utilisation
|
||||||
|
Pour activer le seeder Spotify :
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Seeder": "Spotify"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Et renseigner :
|
||||||
|
|
||||||
|
```json
|
||||||
|
"SpotifySeeder": {
|
||||||
|
"ClientId": "...",
|
||||||
|
"ClientSecret": "..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Dépendance ajoutée
|
||||||
|
|
||||||
|
### Fichier modifié
|
||||||
|
[Webzine.Entity/Webzine.Entity.csproj](c:\Users\lmasi\Documents\DIIAGE\P4\Webzine\Webzine.Entity\Webzine.Entity.csproj)
|
||||||
|
|
||||||
|
### Ce qui a été fait
|
||||||
|
Ajout du package :
|
||||||
|
- `Microsoft.Extensions.Options`
|
||||||
|
|
||||||
|
### Pourquoi
|
||||||
|
Pour injecter proprement `SpotifySeederOptions` dans `SeedDataSpotify`
|
||||||
|
|
||||||
|
## 7. Effet sur les données de l’application
|
||||||
|
|
||||||
|
Avec le seeder Spotify, les titres créés utilisent maintenant :
|
||||||
|
- un vrai nom de titre
|
||||||
|
- un vrai artiste
|
||||||
|
- un vrai album
|
||||||
|
- une vraie jaquette Spotify
|
||||||
|
- une vraie URL Spotify
|
||||||
|
- des styles dérivés des genres Spotify
|
||||||
|
|
||||||
|
Les commentaires restent générés localement pour enrichir l’affichage.
|
||||||
|
|
||||||
|
## 8. Point d’attention
|
||||||
|
|
||||||
|
Je n’ai pas pu valider la compilation complète dans cet environnement à cause d’un problème SDK/.NET local lié au workload resolver, pas à une erreur C# remontée sur les fichiers modifiés.
|
||||||
|
|
||||||
|
## 9. Fichiers impactés
|
||||||
|
|
||||||
|
### Ajoutés
|
||||||
|
- [Webzine.Entity/Fixtures/SeedDataSpotify.cs](c:\Users\lmasi\Documents\DIIAGE\P4\Webzine\Webzine.Entity\Fixtures\SeedDataSpotify.cs)
|
||||||
|
- [Webzine.Entity/Fixtures/SeedDataSet.cs](c:\Users\lmasi\Documents\DIIAGE\P4\Webzine\Webzine.Entity\Fixtures\SeedDataSet.cs)
|
||||||
|
|
||||||
|
### Modifiés
|
||||||
|
- [Webzine.Repository/DbEntityRepository.cs](c:\Users\lmasi\Documents\DIIAGE\P4\Webzine\Webzine.Repository\DbEntityRepository.cs)
|
||||||
|
- [Webzine.WebApplication/Program.cs](c:\Users\lmasi\Documents\DIIAGE\P4\Webzine\Webzine.WebApplication\Program.cs)
|
||||||
|
- [Webzine.WebApplication/appsettings.json](c:\Users\lmasi\Documents\DIIAGE\P4\Webzine\Webzine.WebApplication\appsettings.json)
|
||||||
|
- [Webzine.WebApplication/appsettings.Development.json](c:\Users\lmasi\Documents\DIIAGE\P4\Webzine\Webzine.WebApplication\appsettings.Development.json)
|
||||||
|
- [Webzine.WebApplication/appsettings.Production.json](c:\Users\lmasi\Documents\DIIAGE\P4\Webzine\Webzine.WebApplication\appsettings.Production.json)
|
||||||
|
- [Webzine.Entity/Webzine.Entity.csproj](c:\Users\lmasi\Documents\DIIAGE\P4\Webzine\Webzine.Entity\Webzine.Entity.csproj)
|
||||||
@@ -2,14 +2,8 @@
|
|||||||
"$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
|
"$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
|
||||||
"settings": {
|
"settings": {
|
||||||
"documentationRules": {
|
"documentationRules": {
|
||||||
"documentInterfaces": false,
|
"companyName": "Equipe 1 - BOBIN, MASI, NODON, VETU",
|
||||||
"documentInternalElements": false,
|
"documentationCulture": "fr-FR"
|
||||||
"documentExposedElements": false
|
}
|
||||||
},
|
|
||||||
"maintainabilityRules": {
|
|
||||||
"settings": {
|
|
||||||
"commonWords": [ "Obtient", "définit" ]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
29
Webzine.Documentation/git_hooks/commit-msg
Normal file
29
Webzine.Documentation/git_hooks/commit-msg
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Hook: commit-msg
|
||||||
|
# Validates the commit message format.
|
||||||
|
# Git passes the path to the temp message file as $1.
|
||||||
|
|
||||||
|
COMMIT_MSG=$(cat "$1")
|
||||||
|
|
||||||
|
# Skip validation for rebase or CI commits
|
||||||
|
if echo "$COMMIT_MSG" | grep -qiE "(Rebase|rebase|CI|merge|Merge)"; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ${#COMMIT_MSG} -le 10 ]; then
|
||||||
|
echo "❌ Erreur : Le message doit faire plus de 10 caractères."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! echo "$COMMIT_MSG" | grep -q "\.$"; then
|
||||||
|
echo "❌ Erreur : Le commit doit se terminer par un point."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! echo "$COMMIT_MSG" | grep -q "#[0-9]\+"; then
|
||||||
|
echo "❌ Erreur : Vous devez faire référence à un ticket (ex: #123)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
21
Webzine.Documentation/git_hooks/pre-commit
Normal file
21
Webzine.Documentation/git_hooks/pre-commit
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Hook: pre-commit
|
||||||
|
# Runs dotnet format on staged files, then verifies documentation warnings.
|
||||||
|
|
||||||
|
echo "🔧 Running dotnet format..."
|
||||||
|
dotnet format --severity warn
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "❌ Erreur : dotnet format a échoué."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
git add -u
|
||||||
|
|
||||||
|
echo "📄 Vérification de la documentation..."
|
||||||
|
dotnet format style --severity warn --verify-no-changes
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "❌ Erreur : Documentation manquante (classes/méthodes non commentées)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
2
Webzine.Entity.Tests/.editorconfig
Normal file
2
Webzine.Entity.Tests/.editorconfig
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[*.cs]
|
||||||
|
dotnet_diagnostic.SA1600.severity = none
|
||||||
27
Webzine.Entity/Fixtures/SeedDataSet.cs
Normal file
27
Webzine.Entity/Fixtures/SeedDataSet.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
namespace Webzine.Entity.Fixtures;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Représente un jeu de données prêt à être injecté dans les différents stockages de l'application.
|
||||||
|
/// </summary>
|
||||||
|
public class SeedDataSet
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Obtient ou définit les artistes générés.
|
||||||
|
/// </summary>
|
||||||
|
public List<Artiste> Artistes { get; set; } = new ();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtient ou définit les styles générés.
|
||||||
|
/// </summary>
|
||||||
|
public List<Style> Styles { get; set; } = new ();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtient ou définit les titres générés.
|
||||||
|
/// </summary>
|
||||||
|
public List<Titre> Titres { get; set; } = new ();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtient ou définit les commentaires générés.
|
||||||
|
/// </summary>
|
||||||
|
public List<Commentaire> Commentaires { get; set; } = new ();
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
namespace Webzine.Entity.Fixtures;
|
|
||||||
|
|
||||||
public class SeedDataSpotify
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@ namespace Webzine.Repository.Contracts
|
|||||||
using Webzine.Entity;
|
using Webzine.Entity;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Défini une interface <see cref="IArtisteRepository"/> pour gérer les opérations de base de données liées aux artistes.
|
/// Défini une interface <see cref="IArtisteRepository"/> pour gérer les opérations des artistes dans la source de données.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IArtisteRepository
|
public interface IArtisteRepository
|
||||||
{
|
{
|
||||||
@@ -23,7 +23,7 @@ namespace Webzine.Repository.Contracts
|
|||||||
/// Récupère un artiste par son identifiant unique. Si aucun artiste n'est trouvé, retourne null.
|
/// Récupère un artiste par son identifiant unique. Si aucun artiste n'est trouvé, retourne null.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">L'identifiant de l'artiste.</param>
|
/// <param name="id">L'identifiant de l'artiste.</param>
|
||||||
/// <returns></returns>
|
/// <returns>L'artiste trouvé ou null.</returns>
|
||||||
Artiste Find(int id);
|
Artiste Find(int id);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -39,10 +39,44 @@ namespace Webzine.Repository.Contracts
|
|||||||
/// <returns>Retourne une collection d'artistes.</returns>
|
/// <returns>Retourne une collection d'artistes.</returns>
|
||||||
IEnumerable<Artiste> FindAll();
|
IEnumerable<Artiste> FindAll();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Récupère une liste d'artistes pour une page spécifique, en fonction du numéro de page et de la taille de la page.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pageNumber">Le numéro de la page à récupérer.</param>
|
||||||
|
/// <param name="pageSize">La taille de chaque page.</param>
|
||||||
|
/// <returns>Une liste d'artistes pour la page demandée.</returns>
|
||||||
|
IEnumerable<Artiste> FindArtistes(int pageNumber, int pageSize);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Met à jour les informations d'un artiste existant dans la collection.
|
/// Met à jour les informations d'un artiste existant dans la collection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="artiste">L'artiste à mettre à jour.</param>
|
/// <param name="artiste">L'artiste à mettre à jour.</param>
|
||||||
void Update(Artiste artiste);
|
void Update(Artiste artiste);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Récupérer une liste d'artiste à partir d'une recherche.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nom">Nom de l'artiste.</param>
|
||||||
|
/// <returns>IEnumarble.<Artiste> qui contient la chaine de caractere.</returns>
|
||||||
|
IEnumerable<Artiste> Search(string nom);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Récupère le nombre total d'artistes dans la collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Le nombre total d'artistes.</returns>
|
||||||
|
int Count();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Récupère le nombre d'artistes correspondant au prédicat fourni.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="predicate">Le prédicat de filtrage.</param>
|
||||||
|
/// <returns>Le nombre d'artistes correspondants.</returns>
|
||||||
|
int Count(Func<Artiste, bool> predicate);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Récupère le nombre d'artistes ayant une biographie renseignée.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Le nombre d'artistes avec biographie.</returns>
|
||||||
|
int CountWithBiography();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,14 +2,49 @@ namespace Webzine.Repository.Contracts
|
|||||||
{
|
{
|
||||||
using Webzine.Entity;
|
using Webzine.Entity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interface de repository pour les commentaires.
|
||||||
|
/// </summary>
|
||||||
public interface ICommentaireRepository
|
public interface ICommentaireRepository
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Ajoute un commentaire à la source de données.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="commentaire">Commentaire à ajouter.</param>
|
||||||
void Add(Commentaire commentaire);
|
void Add(Commentaire commentaire);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Supprime un commentaire de la source de données.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="commentaire">Commentaire à supprimer.</param>
|
||||||
void Delete(Commentaire commentaire);
|
void Delete(Commentaire commentaire);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Trouve un commentaire par son ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">ID du commentaire à trouver.</param>
|
||||||
|
/// <returns>Le commentaire trouvé, ou null si non trouvé.</returns>
|
||||||
Commentaire Find(int id);
|
Commentaire Find(int id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retourne tous les commentaires de la source de données.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Une collection de commentaires.</returns>
|
||||||
IEnumerable<Commentaire> FindAll();
|
IEnumerable<Commentaire> FindAll();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retourne une collection de commentaires paginée à partir de la source de données.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">Le nombre de commentaires à ignorer avant de commencer à
|
||||||
|
/// récupérer les commentaires.</param>
|
||||||
|
/// <param name="limit">Le nombre maximum de commentaires à récupérer.</param>
|
||||||
|
/// <returns>Une collection de commentaires paginée.</returns>
|
||||||
|
IEnumerable<Commentaire> FindCommentaires(int offset, int limit);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retourne le nombre total de commentaires dans la source de données.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Le nombre total de commentaires.</returns>
|
||||||
|
int Count();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,5 +37,19 @@ namespace Webzine.Repository.Contracts
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="style">L'objet style à mettre à jour.</param
|
/// <param name="style">L'objet style à mettre à jour.</param
|
||||||
void Update(Style style);
|
void Update(Style style);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Récupère le nombre total de styles dans la liste des styles.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Le nombre total de styles présents dans la liste.</returns>
|
||||||
|
int Count();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recherche les styles dans la liste des styles en fonction de l'offset et de la limite spécifiés, permettant ainsi une pagination des résultats.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">Le nombre de styles à ignorer avant de commencer à retourner les résultats.</param>
|
||||||
|
/// <param name="limit">Le nombre maximum de styles à retourner.</param>
|
||||||
|
/// <returns>Une collection de styles correspondant au critère de pagination, triée par libellé.</returns>
|
||||||
|
IEnumerable<Style> FindStyles(int offset, int limit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,5 +77,49 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="titre">L'objet titre à mettre à jour.</param>
|
/// <param name="titre">L'objet titre à mettre à jour.</param>
|
||||||
void Update(Titre titre);
|
void Update(Titre titre);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retourne le nombre total de likes.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Integer.</returns>
|
||||||
|
int CountLike();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retourne le nombre total de lecture.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Integer.</returns>
|
||||||
|
int CountLecture();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retourne le nom de l'artiste ayant le plus de titres chroniqués.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Le nom de l'artiste le plus chroniqué, ou null si aucun titre n'existe.</returns>
|
||||||
|
string? FindMostReviewedArtistName();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retourne le nom de l'artiste ayant le plus d'albums chroniqués.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Le nom de l'artiste concerné, ou null si aucun titre n'existe.</returns>
|
||||||
|
string? FindArtistNameWithMostReviewedAlbums();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retourne l'identifiant et le libellé du titre le plus joué.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Un tuple contenant l'identifiant et le libellé du titre le plus joué, ou null si aucun titre n'existe.</returns>
|
||||||
|
(int IdTitre, string Libelle)? FindMostPlayedTitle();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Récupération des chroniques.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">Nombre d'éléments.</param>
|
||||||
|
/// <param name="limit">Limite d'éléments.</param>
|
||||||
|
/// <returns>Liste des derniers titres chroniqués.</returns>
|
||||||
|
IEnumerable<Titre> DerniereChronique(int offset, int limit);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Récupération du top titre liké.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Liste de titre les plus likés.</returns>
|
||||||
|
IEnumerable<Titre> TopTitre(int offset, int limit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,8 +4,6 @@
|
|||||||
|
|
||||||
namespace Webzine.Repository
|
namespace Webzine.Repository
|
||||||
{
|
{
|
||||||
using System.Data.Common;
|
|
||||||
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
@@ -15,7 +13,6 @@ namespace Webzine.Repository
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initialise une classe <see cref="DbArtisteRepository"/> qui implémente l'interface <see cref="IArtisteRepository"/> pour gérer les opérations de base de données liées aux artistes.
|
/// Initialise une classe <see cref="DbArtisteRepository"/> qui implémente l'interface <see cref="IArtisteRepository"/> pour gérer les opérations de base de données liées aux artistes.
|
||||||
/// Utilise <see cref="IArtisteRepository"/> en injection de dépendances.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DbArtisteRepository : IArtisteRepository
|
public class DbArtisteRepository : IArtisteRepository
|
||||||
{
|
{
|
||||||
@@ -25,8 +22,8 @@ namespace Webzine.Repository
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="DbArtisteRepository"/> class.
|
/// Initializes a new instance of the <see cref="DbArtisteRepository"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context">Le contexte de base de données à utiliser pour accéder aux entités et effectuer des opérations de
|
/// <param name="context">Le contexte de base de données à utiliser pour accéder aux entités et effectuer des opérations de persistance.</param>
|
||||||
/// persistance. Ne peut pas être null.</param>
|
/// <param name="logger">Le service de journalisation.</param>
|
||||||
public DbArtisteRepository(WebzineDbContext context, ILogger<LocalArtisteRepository> logger)
|
public DbArtisteRepository(WebzineDbContext context, ILogger<LocalArtisteRepository> logger)
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
@@ -58,11 +55,6 @@ namespace Webzine.Repository
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (artiste == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(artiste), "L'artiste à supprimer ne peut pas être null.");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.context.Artistes.Remove(artiste);
|
this.context.Artistes.Remove(artiste);
|
||||||
this.context.SaveChanges();
|
this.context.SaveChanges();
|
||||||
this.logger.LogDebug("L'artiste {IdArtiste} a bien été supprimé", artiste.IdArtiste);
|
this.logger.LogDebug("L'artiste {IdArtiste} a bien été supprimé", artiste.IdArtiste);
|
||||||
@@ -85,8 +77,8 @@ namespace Webzine.Repository
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Artiste artiste = this.context.Artistes
|
Artiste artiste = this.context.Artistes
|
||||||
.Include(a => a.Titres)
|
.Include(a => a.Titres)
|
||||||
.First(a => a.IdArtiste == id);
|
.SingleOrDefault(a => a.IdArtiste == id);
|
||||||
return artiste;
|
return artiste;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -120,27 +112,33 @@ namespace Webzine.Repository
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// .AsNoTracking() rend la requête beaucoup plus rapide pour de la lecture
|
// .AsNoTracking() rend la requête beaucoup plus rapide pour de la lecture
|
||||||
var artistes = this.context.Artistes.AsNoTracking().ToList();
|
// Pas besoin de faire un ToList() ici, car on retourne un IEnumerable<Artiste> et EF Core gère l'exécution différée de la requête.
|
||||||
this.logger.LogDebug("{Count} artistes récupérés de la base.", artistes.Count);
|
var artistes = this.context.Artistes
|
||||||
|
.AsNoTracking()
|
||||||
|
.Include(t => t.Titres);
|
||||||
|
|
||||||
|
this.logger.LogDebug("La liste d'artistes a été récupérée de la base.");
|
||||||
return artistes;
|
return artistes;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.logger.LogError(ex, "Erreur lors de la récupération de tous les artistes.");
|
this.logger.LogError(ex, "Erreur lors de la récupération de tous les artistes.");
|
||||||
return Enumerable.Empty<Artiste>(); // Retourne une liste vide au lieu de faire crash l'UI
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Update(Artiste artiste)
|
public void Update(Artiste artiste)
|
||||||
{
|
{
|
||||||
if (artiste == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(artiste));
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
Artiste existingArtiste = this.Find(artiste.IdArtiste); // Vérifie que l'artiste existe avant de tenter de le mettre à jour
|
||||||
|
if (existingArtiste == null)
|
||||||
|
{
|
||||||
|
this.logger.LogWarning("L'artiste {Id} n'a pas été trouvé pour l'update.", artiste.IdArtiste);
|
||||||
|
throw new InvalidOperationException($"L'artiste avec l'ID {artiste.IdArtiste} n'a pas été trouvé pour la mise à jour.");
|
||||||
|
}
|
||||||
|
|
||||||
this.context.Artistes.Update(artiste);
|
this.context.Artistes.Update(artiste);
|
||||||
this.context.SaveChanges();
|
this.context.SaveChanges();
|
||||||
this.logger.LogDebug("Artiste {Id} ({Nom}) mis à jour avec succès.", artiste.IdArtiste, artiste.Nom);
|
this.logger.LogDebug("Artiste {Id} ({Nom}) mis à jour avec succès.", artiste.IdArtiste, artiste.Nom);
|
||||||
@@ -156,5 +154,94 @@ namespace Webzine.Repository
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IEnumerable<Artiste> Search(string mot)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Récupération des artistes et des titres
|
||||||
|
// qui leurs sont associés.
|
||||||
|
// ajout de 'ToLower' pour ne pas être sensible à la casse.
|
||||||
|
// NoTracking car action de lecture seulement.
|
||||||
|
var artiste = this.context.Artistes
|
||||||
|
.Where(a => a.Nom.ToLower().Contains(mot.ToLower()))
|
||||||
|
.Include(t => t.Titres)
|
||||||
|
.AsNoTracking();
|
||||||
|
return artiste;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception("Erreur lors de la recherche d'artiste {error}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int Count()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int count = Enumerable.Count(this.context.Artistes);
|
||||||
|
this.logger.LogDebug("Nombre total d'artistes dans la base: {Count}", count);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.logger.LogError(ex, "Erreur lors du comptage des artistes.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int Count(Func<Artiste, bool> predicate)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int count = this.context.Artistes.Count(predicate);
|
||||||
|
this.logger.LogDebug("Nombre d'artistes (avec prédicat): {Count}", count);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.logger.LogError(ex, "Erreur lors du comptage des artistes avec prédicat.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int CountWithBiography()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int count = this.context.Artistes.Count(a => !string.IsNullOrEmpty(a.Biographie));
|
||||||
|
this.logger.LogDebug("Nombre d'artistes avec biographie: {Count}", count);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.logger.LogError(ex, "Erreur lors du comptage des artistes avec biographie.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IEnumerable<Artiste> FindArtistes(int offset, int limit)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var artistes = this.context.Artistes
|
||||||
|
.AsNoTracking()
|
||||||
|
.OrderBy(a => a.Nom)
|
||||||
|
.Include(t => t.Titres)
|
||||||
|
.Paginate(offset, limit);
|
||||||
|
this.logger.LogDebug("Page {PageNumber} d'artistes récupérée avec {PageSize} artistes par page.", offset, limit);
|
||||||
|
return artistes;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.logger.LogError(ex, "Erreur lors de la pagination des artistes. Page: {PageNumber}, Taille: {PageSize}", offset, limit);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
using Npgsql;
|
||||||
|
|
||||||
using Webzine.EntitiesContext;
|
using Webzine.EntitiesContext;
|
||||||
using Webzine.Entity;
|
using Webzine.Entity;
|
||||||
using Webzine.Repository.Contracts;
|
using Webzine.Repository.Contracts;
|
||||||
@@ -16,10 +18,9 @@ public class DbCommentaireRepository : ICommentaireRepository
|
|||||||
private readonly WebzineDbContext context;
|
private readonly WebzineDbContext context;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="DbCommentaireRepository"/> class.
|
|
||||||
/// Initialisation de <see cref="DbCommentaireRepository"/>.
|
/// Initialisation de <see cref="DbCommentaireRepository"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logger">Le service de journalisation injecté pour suivre les opérations du repository.</param>
|
/// <param name="logger">Le service de journalisation.</param>
|
||||||
/// <param name="context">Le contexte de base de données injecté.</param>
|
/// <param name="context">Le contexte de base de données injecté.</param>
|
||||||
public DbCommentaireRepository(ILogger<DbCommentaireRepository> logger, WebzineDbContext context)
|
public DbCommentaireRepository(ILogger<DbCommentaireRepository> logger, WebzineDbContext context)
|
||||||
{
|
{
|
||||||
@@ -33,14 +34,25 @@ public class DbCommentaireRepository : ICommentaireRepository
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.logger.LogDebug("Ajout d'un nouveau commentaire de l'auteur : {Auteur}", commentaire.Auteur);
|
|
||||||
this.context.Commentaires.Add(commentaire);
|
this.context.Commentaires.Add(commentaire);
|
||||||
this.context.SaveChanges();
|
this.context.SaveChanges();
|
||||||
this.logger.LogDebug("Commentaire ajouté avec l'id : {Id}", commentaire.IdCommentaire);
|
this.logger.LogDebug("Commentaire ajouté avec l'id : {Id}", commentaire.IdCommentaire);
|
||||||
}
|
}
|
||||||
catch (DbUpdateException dbex)
|
catch (DbUpdateException dbex)
|
||||||
{
|
{
|
||||||
this.logger.LogError(dbex, "Erreur de base de données lors de l'ajout du commentaire de l'auteur : {Auteur}", commentaire?.Auteur);
|
PostgresException? postgresException = dbex.InnerException as PostgresException;
|
||||||
|
|
||||||
|
this.logger.LogError(
|
||||||
|
dbex,
|
||||||
|
"Erreur de base de données lors de l'ajout du commentaire. Auteur: {Auteur} | IdTitre: {IdTitre} | DateCreation: {DateCreation:o} | PostgresCode: {PostgresCode} | Detail: {Detail} | Constraint: {Constraint} | Column: {Column} | Table: {Table}",
|
||||||
|
commentaire?.Auteur,
|
||||||
|
commentaire?.IdTitre,
|
||||||
|
commentaire?.DateCreation,
|
||||||
|
postgresException?.SqlState,
|
||||||
|
postgresException?.Detail,
|
||||||
|
postgresException?.ConstraintName,
|
||||||
|
postgresException?.ColumnName,
|
||||||
|
postgresException?.TableName);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -55,11 +67,6 @@ public class DbCommentaireRepository : ICommentaireRepository
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (commentaire == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(commentaire), "Le commentaire à supprimer ne peut pas être null.");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.context.Commentaires.Remove(commentaire);
|
this.context.Commentaires.Remove(commentaire);
|
||||||
this.context.SaveChanges();
|
this.context.SaveChanges();
|
||||||
this.logger.LogDebug("Le commentaire {IdCommentaire} a bien été supprimé", commentaire.IdCommentaire);
|
this.logger.LogDebug("Le commentaire {IdCommentaire} a bien été supprimé", commentaire.IdCommentaire);
|
||||||
@@ -76,14 +83,6 @@ public class DbCommentaireRepository : ICommentaireRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public int Count()
|
|
||||||
{
|
|
||||||
var count = this.context.Commentaires.Count();
|
|
||||||
this.logger.LogDebug("Compte total des commentaires : {Count}", count);
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Commentaire Find(int idCommentaire)
|
public Commentaire Find(int idCommentaire)
|
||||||
{
|
{
|
||||||
@@ -92,50 +91,55 @@ public class DbCommentaireRepository : ICommentaireRepository
|
|||||||
// On inclut le titre car il est souvent affiché avec le commentaire
|
// On inclut le titre car il est souvent affiché avec le commentaire
|
||||||
return this.context.Commentaires
|
return this.context.Commentaires
|
||||||
.Include(c => c.Titre)
|
.Include(c => c.Titre)
|
||||||
.FirstOrDefault(c => c.IdCommentaire == idCommentaire);
|
.SingleOrDefault(c => c.IdCommentaire == idCommentaire);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerable<Commentaire> FindAll()
|
public IEnumerable<Commentaire> FindAll()
|
||||||
{
|
{
|
||||||
this.logger.LogDebug("Récupération de tous les commentaires");
|
|
||||||
|
|
||||||
var commentaires = this.context.Commentaires
|
var commentaires = this.context.Commentaires
|
||||||
|
.AsNoTracking()
|
||||||
.Include(c => c.Titre)
|
.Include(c => c.Titre)
|
||||||
.OrderByDescending(c => c.DateCreation)
|
.OrderByDescending(c => c.DateCreation);
|
||||||
.ToList();
|
|
||||||
|
|
||||||
this.logger.LogDebug("Nombre de commentaires trouvés : {Count}", commentaires.Count);
|
this.logger.LogDebug("La liste de commentaires a été récupérée.");
|
||||||
return commentaires;
|
return commentaires;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerable<Commentaire> FindCommentaires(int offset, int limit)
|
public IEnumerable<Commentaire> FindCommentaires(int offset, int limit)
|
||||||
{
|
{
|
||||||
this.logger.LogDebug("Recherche paginée des commentaires (offset : {Offset}, limit : {Limit})", offset, limit);
|
try
|
||||||
|
{
|
||||||
|
this.logger.LogDebug("Recherche paginée des commentaires (offset : {Offset}, limit : {Limit})", offset, limit);
|
||||||
|
|
||||||
var commentaires = this.context.Commentaires
|
var commentaires = this.context.Commentaires
|
||||||
.Include(c => c.Titre)
|
.AsNoTracking()
|
||||||
.OrderByDescending(c => c.DateCreation)
|
.Include(c => c.Titre)
|
||||||
.Skip(offset)
|
.OrderByDescending(c => c.DateCreation)
|
||||||
.Take(limit)
|
.Paginate(offset, limit);
|
||||||
.ToList();
|
|
||||||
|
|
||||||
this.logger.LogDebug("{Count} commentaires trouvés pour cette page", commentaires.Count);
|
return commentaires;
|
||||||
return commentaires;
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.logger.LogError(ex, "Erreur lors de la pagination des commentaires (offset : {Offset}, limit : {Limit})", offset, limit);
|
||||||
|
throw new Exception("Une erreur est survenue lors de la pagination des commentaires.", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerable<Commentaire> FindByIdTitre(int idTitre)
|
public int Count()
|
||||||
{
|
{
|
||||||
this.logger.LogDebug("Recherche des commentaires pour le titre ID : {IdTitre}", idTitre);
|
try
|
||||||
|
{
|
||||||
var commentaires = this.context.Commentaires
|
this.logger.LogDebug("Comptage du nombre total de commentaires");
|
||||||
.Where(c => c.Titre.IdTitre == idTitre)
|
return Enumerable.Count(this.context.Commentaires);
|
||||||
.OrderByDescending(c => c.DateCreation)
|
}
|
||||||
.ToList();
|
catch (Exception ex)
|
||||||
|
{
|
||||||
this.logger.LogDebug($"{commentaires.Count} commentaires trouvés pour l'ID de titre : {idTitre}");
|
this.logger.LogError(ex, "Erreur lors du comptage des commentaires");
|
||||||
return commentaires;
|
throw new Exception("Une erreur est survenue lors du comptage des commentaires.", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,10 +8,17 @@ namespace Webzine.Repository
|
|||||||
using Webzine.Entity;
|
using Webzine.Entity;
|
||||||
using Webzine.Entity.Fixtures;
|
using Webzine.Entity.Fixtures;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Classe de repository pour les entités de la base de données.
|
||||||
|
/// </summary>
|
||||||
public class DbEntityRepository
|
public class DbEntityRepository
|
||||||
{
|
{
|
||||||
private readonly WebzineDbContext context;
|
private readonly WebzineDbContext context;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructeur de DbEntityRepository.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">DB context.</param>
|
||||||
public DbEntityRepository(WebzineDbContext context)
|
public DbEntityRepository(WebzineDbContext context)
|
||||||
{
|
{
|
||||||
this.context = context;
|
this.context = context;
|
||||||
@@ -25,6 +32,7 @@ namespace Webzine.Repository
|
|||||||
/// <param name="minStyles">Nombre min de style.</param>
|
/// <param name="minStyles">Nombre min de style.</param>
|
||||||
/// <param name="maxStyles">Nombre mac de style.</param>
|
/// <param name="maxStyles">Nombre mac de style.</param>
|
||||||
public void SeedBaseDeDonnees(
|
public void SeedBaseDeDonnees(
|
||||||
|
SeedDataSet? jeuDeDonnees = null,
|
||||||
int nbArtistes = 100,
|
int nbArtistes = 100,
|
||||||
int nbTitres = 500,
|
int nbTitres = 500,
|
||||||
int minStyles = 15,
|
int minStyles = 15,
|
||||||
@@ -38,6 +46,16 @@ namespace Webzine.Repository
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (jeuDeDonnees is not null)
|
||||||
|
{
|
||||||
|
this.context.Artistes.AddRange(jeuDeDonnees.Artistes);
|
||||||
|
this.context.Styles.AddRange(jeuDeDonnees.Styles);
|
||||||
|
this.context.Titres.AddRange(jeuDeDonnees.Titres);
|
||||||
|
this.context.Commentaires.AddRange(jeuDeDonnees.Commentaires);
|
||||||
|
this.context.SaveChanges();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
List<Artiste> artistes = SeedDataLocal.GenererListeArtiste(nbArtistes);
|
List<Artiste> artistes = SeedDataLocal.GenererListeArtiste(nbArtistes);
|
||||||
List<Style> styles = SeedDataLocal.GenererListeStyle(minStyles, maxStyles);
|
List<Style> styles = SeedDataLocal.GenererListeStyle(minStyles, maxStyles);
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
|
namespace Webzine.Repository;
|
||||||
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
using Webzine.EntitiesContext;
|
using Webzine.EntitiesContext;
|
||||||
using Webzine.Entity;
|
using Webzine.Entity;
|
||||||
using Webzine.Repository.Contracts;
|
using Webzine.Repository.Contracts;
|
||||||
|
|
||||||
namespace Webzine.Repository;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Classe qui implémente le repository pour les styles en utilisant une base de données.
|
/// Classe qui implémente le repository pour les styles en utilisant une base de données.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -92,27 +93,17 @@ public class DbStyleRepository : IStyleRepository
|
|||||||
{
|
{
|
||||||
this.logger.LogDebug("Recherche du style avec l'ID: {Id}", id);
|
this.logger.LogDebug("Recherche du style avec l'ID: {Id}", id);
|
||||||
|
|
||||||
if (id <= 0)
|
|
||||||
{
|
|
||||||
this.logger.LogWarning("Tentative de recherche d'un style avec un Id invalide: {Id}", id);
|
|
||||||
return new Style();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.LogDebug("Préparation de la requête avec inclusion des titres");
|
|
||||||
var style = this.context.Styles
|
var style = this.context.Styles
|
||||||
.Include(s => s.Titres)
|
.Include(s => s.Titres)
|
||||||
.FirstOrDefault(s => s.IdStyle == id);
|
.SingleOrDefault(s => s.IdStyle == id);
|
||||||
|
|
||||||
if (style == null)
|
if (style == null)
|
||||||
{
|
{
|
||||||
this.logger.LogWarning("Style avec l'ID {Id} non trouvé", id);
|
this.logger.LogWarning("Style avec l'ID {Id} non trouvé", id);
|
||||||
style = new Style();
|
return null;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.logger.LogDebug("Style trouvé: {Libelle}", style.Libelle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.logger.LogDebug("Style trouvé: {Libelle}", style.Libelle);
|
||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -131,10 +122,9 @@ public class DbStyleRepository : IStyleRepository
|
|||||||
this.logger.LogDebug("Tri des styles par libellé");
|
this.logger.LogDebug("Tri des styles par libellé");
|
||||||
|
|
||||||
var styles = this.context.Styles
|
var styles = this.context.Styles
|
||||||
.OrderBy(s => s.Libelle)
|
.OrderBy(s => s.Libelle);
|
||||||
.ToList();
|
|
||||||
|
|
||||||
this.logger.LogDebug("{Count} styles récupérés", styles.Count);
|
this.logger.LogDebug("La liste de styles a été récupérée.");
|
||||||
return styles;
|
return styles;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -150,18 +140,15 @@ public class DbStyleRepository : IStyleRepository
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.logger.LogInformation("Mise à jour du style avec l'ID: {IdStyle}", style.IdStyle);
|
this.logger.LogInformation("Mise à jour du style avec l'ID: {IdStyle}", style.IdStyle);
|
||||||
this.logger.LogDebug("Recherche du style en base de données");
|
Style existingStyle = this.Find(style.IdStyle); // Vérifie que le style existe avant de tenter de le mettre à jour
|
||||||
|
|
||||||
var existingStyle = this.context.Styles.Find(style.IdStyle);
|
|
||||||
if (existingStyle == null)
|
if (existingStyle == null)
|
||||||
{
|
{
|
||||||
this.logger.LogWarning("Style avec l'ID {IdStyle} non trouvé pour la mise à jour", style.IdStyle);
|
this.logger.LogWarning("Style avec l'ID {IdStyle} non trouvé pour l'update.", style.IdStyle);
|
||||||
throw new InvalidOperationException($"Style avec l'ID {style.IdStyle} non trouvé.");
|
throw new InvalidOperationException($"Style avec l'ID {style.IdStyle} non trouvé pour la mise à jour.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update properties
|
this.context.Styles.Update(style);
|
||||||
this.logger.LogDebug("Style trouvé, mise à jour des propriétés");
|
|
||||||
existingStyle.Libelle = style.Libelle;
|
|
||||||
|
|
||||||
this.context.SaveChanges();
|
this.context.SaveChanges();
|
||||||
this.logger.LogDebug("Style mis à jour avec succès: {IdStyle}", style.IdStyle);
|
this.logger.LogDebug("Style mis à jour avec succès: {IdStyle}", style.IdStyle);
|
||||||
@@ -177,4 +164,42 @@ public class DbStyleRepository : IStyleRepository
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int Count()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int count = Enumerable.Count(this.context.Styles);
|
||||||
|
this.logger.LogDebug("Nombre total de styles: {Count}", count);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.logger.LogError(ex, "Erreur lors du comptage des styles");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IEnumerable<Style> FindStyles(int offset, int limit)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.logger.LogDebug("Récupération paginée des styles (offset: {Offset}, limit: {Limit})", offset, limit);
|
||||||
|
|
||||||
|
var styles = this.context.Styles
|
||||||
|
.AsNoTracking()
|
||||||
|
.OrderBy(s => s.Libelle)
|
||||||
|
.Paginate(offset, limit);
|
||||||
|
|
||||||
|
this.logger.LogDebug("La liste paginée de styles a été récupérée.");
|
||||||
|
return styles;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.logger.LogError(ex, "Erreur lors de la récupération paginée des styles (offset: {Offset}, limit: {Limit})", offset, limit);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
|
namespace Webzine.Repository;
|
||||||
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
using Webzine.EntitiesContext;
|
using Webzine.EntitiesContext;
|
||||||
using Webzine.Entity;
|
using Webzine.Entity;
|
||||||
using Webzine.Repository.Contracts;
|
using Webzine.Repository.Contracts;
|
||||||
|
|
||||||
namespace Webzine.Repository;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Classe qui implémente le repository pour les titres en utilisant une base de données.
|
/// Classe qui implémente le repository pour les titres en utilisant une base de données.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -32,7 +33,6 @@ public class DbTitreRepository : ITitreRepository
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.logger.LogInformation("Ajout d'un nouveau titre: {Libelle}", titre.Libelle);
|
this.logger.LogInformation("Ajout d'un nouveau titre: {Libelle}", titre.Libelle);
|
||||||
this.logger.LogDebug("Début de l'ajout du titre en base de données");
|
|
||||||
|
|
||||||
this.context.Titres.Add(titre);
|
this.context.Titres.Add(titre);
|
||||||
this.context.SaveChanges();
|
this.context.SaveChanges();
|
||||||
@@ -56,7 +56,6 @@ public class DbTitreRepository : ITitreRepository
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.logger.LogDebug("Comptage des titres en base de données");
|
|
||||||
var count = this.context.Titres.Count();
|
var count = this.context.Titres.Count();
|
||||||
this.logger.LogDebug("Nombre total de titres: {Count}", count);
|
this.logger.LogDebug("Nombre total de titres: {Count}", count);
|
||||||
return count;
|
return count;
|
||||||
@@ -74,7 +73,6 @@ public class DbTitreRepository : ITitreRepository
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.logger.LogInformation("Suppression du titre avec l'ID: {IdTitre}", titre.IdTitre);
|
this.logger.LogInformation("Suppression du titre avec l'ID: {IdTitre}", titre.IdTitre);
|
||||||
this.logger.LogDebug("Début de la suppression du titre en base de données");
|
|
||||||
|
|
||||||
this.context.Titres.Remove(titre);
|
this.context.Titres.Remove(titre);
|
||||||
this.context.SaveChanges();
|
this.context.SaveChanges();
|
||||||
@@ -99,17 +97,15 @@ public class DbTitreRepository : ITitreRepository
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.logger.LogDebug("Recherche des titres avec offset: {Offset}, limit: {Limit}", offset, limit);
|
this.logger.LogDebug("Recherche des titres avec offset: {Offset}, limit: {Limit}", offset, limit);
|
||||||
this.logger.LogDebug("Préparation de la requête avec les inclusions Artiste et Styles");
|
|
||||||
|
|
||||||
var titres = this.context.Titres
|
var titres = this.context.Titres
|
||||||
|
.OrderByDescending(t => t.DateCreation)
|
||||||
|
.ThenBy(t => t.Libelle)
|
||||||
.Include(t => t.Artiste)
|
.Include(t => t.Artiste)
|
||||||
.Include(t => t.Styles)
|
.Include(t => t.Styles)
|
||||||
.OrderBy(t => t.Libelle)
|
.Paginate(offset, limit)
|
||||||
.Skip(offset)
|
.AsNoTracking();
|
||||||
.Take(limit)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
this.logger.LogDebug("{Count} titres trouvés", titres.Count);
|
|
||||||
return titres;
|
return titres;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -125,12 +121,10 @@ public class DbTitreRepository : ITitreRepository
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.logger.LogInformation("Incrémentation du nombre de lectures pour le titre ID: {IdTitre}", titre.IdTitre);
|
this.logger.LogInformation("Incrémentation du nombre de lectures pour le titre ID: {IdTitre}", titre.IdTitre);
|
||||||
this.logger.LogDebug("Recherche du titre en base de données");
|
|
||||||
|
|
||||||
var existingTitre = this.context.Titres.Find(titre.IdTitre);
|
var existingTitre = this.context.Titres.Find(titre.IdTitre);
|
||||||
if (existingTitre != null)
|
if (existingTitre != null)
|
||||||
{
|
{
|
||||||
this.logger.LogDebug("Titre trouvé, incrémentation du compteur de lectures");
|
|
||||||
existingTitre.NbLectures++;
|
existingTitre.NbLectures++;
|
||||||
this.context.SaveChanges();
|
this.context.SaveChanges();
|
||||||
this.logger.LogDebug("Nouveau nombre de lectures: {NbLectures}", existingTitre.NbLectures);
|
this.logger.LogDebug("Nouveau nombre de lectures: {NbLectures}", existingTitre.NbLectures);
|
||||||
@@ -158,12 +152,10 @@ public class DbTitreRepository : ITitreRepository
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.logger.LogInformation("Incrémentation du nombre de likes pour le titre ID: {IdTitre}", titre.IdTitre);
|
this.logger.LogInformation("Incrémentation du nombre de likes pour le titre ID: {IdTitre}", titre.IdTitre);
|
||||||
this.logger.LogDebug("Recherche du titre en base de données");
|
|
||||||
|
|
||||||
var existingTitre = this.context.Titres.Find(titre.IdTitre);
|
var existingTitre = this.context.Titres.Find(titre.IdTitre);
|
||||||
if (existingTitre != null)
|
if (existingTitre != null)
|
||||||
{
|
{
|
||||||
this.logger.LogDebug("Titre trouvé, incrémentation du compteur de likes");
|
|
||||||
existingTitre.NbLikes++;
|
existingTitre.NbLikes++;
|
||||||
this.context.SaveChanges();
|
this.context.SaveChanges();
|
||||||
this.logger.LogDebug("Nouveau nombre de likes: {NbLikes}", existingTitre.NbLikes);
|
this.logger.LogDebug("Nouveau nombre de likes: {NbLikes}", existingTitre.NbLikes);
|
||||||
@@ -191,16 +183,13 @@ public class DbTitreRepository : ITitreRepository
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.logger.LogInformation("Mise à jour du titre avec l'ID: {IdTitre}", titre.IdTitre);
|
this.logger.LogInformation("Mise à jour du titre avec l'ID: {IdTitre}", titre.IdTitre);
|
||||||
this.logger.LogDebug("Début de la mise à jour du titre en base de données");
|
|
||||||
|
|
||||||
var existingTitre = this.context.Titres.Find(titre.IdTitre);
|
Titre existingTitre = this.Find(titre.IdTitre);
|
||||||
if (existingTitre != null)
|
if (existingTitre != null)
|
||||||
{
|
{
|
||||||
this.logger.LogDebug("Titre trouvé, mise à jour des propriétés");
|
|
||||||
this.context.Entry(existingTitre).CurrentValues.SetValues(titre);
|
this.context.Entry(existingTitre).CurrentValues.SetValues(titre);
|
||||||
|
|
||||||
// Handle many-to-many relationships
|
// Relation many-to-many
|
||||||
this.logger.LogDebug("Mise à jour des relations many-to-many (Styles)");
|
|
||||||
this.context.Entry(existingTitre).Collection(t => t.Styles).Load();
|
this.context.Entry(existingTitre).Collection(t => t.Styles).Load();
|
||||||
existingTitre.Styles.Clear();
|
existingTitre.Styles.Clear();
|
||||||
foreach (var style in titre.Styles)
|
foreach (var style in titre.Styles)
|
||||||
@@ -235,16 +224,14 @@ public class DbTitreRepository : ITitreRepository
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.logger.LogInformation("Recherche des titres avec le mot-clé: {Mot}", mot);
|
this.logger.LogInformation("Recherche des titres avec le mot-clé: {Mot}", mot);
|
||||||
this.logger.LogDebug("Préparation de la requête de recherche avec les inclusions");
|
|
||||||
|
|
||||||
var titres = this.context.Titres
|
var titres = this.context.Titres
|
||||||
.Include(t => t.Artiste)
|
.Include(t => t.Artiste)
|
||||||
.Include(t => t.Styles)
|
.Include(t => t.Styles)
|
||||||
.Where(t => t.Libelle.ToLower().Contains(mot.ToLower()))
|
.Where(t => t.Libelle.ToLower().Contains(mot.ToLower()))
|
||||||
.OrderBy(t => t.Libelle)
|
.OrderBy(t => t.Libelle)
|
||||||
.ToList();
|
.AsNoTracking();
|
||||||
|
|
||||||
this.logger.LogDebug("{Count} titres trouvés correspondant à '{Mot}'", titres.Count, mot);
|
|
||||||
return titres;
|
return titres;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -260,15 +247,13 @@ public class DbTitreRepository : ITitreRepository
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.logger.LogDebug("Recherche du titre avec l'ID: {IdTitre}", idTitre);
|
this.logger.LogDebug("Recherche du titre avec l'ID: {IdTitre}", idTitre);
|
||||||
this.logger.LogDebug("Préparation de la requête avec les inclusions Artiste, Styles et Commentaires");
|
|
||||||
|
|
||||||
var titre = this.context.Titres
|
var titre = this.context.Titres
|
||||||
.Include(t => t.Artiste)
|
.Include(t => t.Artiste)
|
||||||
.Include(t => t.Styles)
|
.Include(t => t.Styles)
|
||||||
.Include(t => t.Commentaires)
|
.Include(t => t.Commentaires)
|
||||||
.First(t => t.IdTitre == idTitre);
|
.SingleOrDefault(t => t.IdTitre == idTitre);
|
||||||
|
|
||||||
this.logger.LogDebug("Titre trouvé: {Libelle}", titre.Libelle);
|
|
||||||
return titre;
|
return titre;
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException ex)
|
catch (InvalidOperationException ex)
|
||||||
@@ -288,17 +273,14 @@ public class DbTitreRepository : ITitreRepository
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.logger.LogDebug("Récupération de tous les titres");
|
|
||||||
this.logger.LogDebug("Préparation de la requête avec les inclusions Artiste et Styles");
|
|
||||||
|
|
||||||
var titres = this.context.Titres
|
var titres = this.context.Titres
|
||||||
.Include(t => t.Artiste)
|
.Include(t => t.Artiste)
|
||||||
.Include(t => t.Styles)
|
.Include(t => t.Styles)
|
||||||
.Include(t => t.Commentaires)
|
.Include(t => t.Commentaires)
|
||||||
.OrderBy(t => t.Libelle)
|
.OrderBy(t => t.Libelle)
|
||||||
.ToList();
|
.AsNoTracking();
|
||||||
|
|
||||||
this.logger.LogDebug("{Count} titres récupérés", titres.Count);
|
this.logger.LogDebug("{Count} titres récupérés", titres.Count());
|
||||||
return titres;
|
return titres;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -314,16 +296,15 @@ public class DbTitreRepository : ITitreRepository
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.logger.LogInformation("Recherche des titres par style: {Libelle}", libelle);
|
this.logger.LogInformation("Recherche des titres par style: {Libelle}", libelle);
|
||||||
this.logger.LogDebug("Préparation de la requête de recherche par style");
|
|
||||||
|
|
||||||
var titres = this.context.Titres
|
var titres = this.context.Titres
|
||||||
.Include(t => t.Artiste)
|
.Include(t => t.Artiste)
|
||||||
.Include(t => t.Styles)
|
.Include(t => t.Styles)
|
||||||
.Where(t => t.Styles.Any(s => s.Libelle.ToLower() == libelle.ToLower()))
|
.Where(t => t.Styles.Any(s => s.Libelle.ToLower() == libelle.ToLower()))
|
||||||
.OrderBy(t => t.Libelle)
|
.OrderBy(t => t.Libelle)
|
||||||
.ToList();
|
.AsNoTracking();
|
||||||
|
|
||||||
this.logger.LogDebug("{Count} titres trouvés pour le style '{Libelle}'", titres.Count, libelle);
|
this.logger.LogDebug("{Count} titres trouvés pour le style '{Libelle}'", titres.Count(), libelle);
|
||||||
return titres;
|
return titres;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -332,4 +313,142 @@ public class DbTitreRepository : ITitreRepository
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int CountLike()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var likes = this.context.Titres.Sum(t => t.NbLikes);
|
||||||
|
return likes;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.logger.LogError(ex, "Erreur lors de la récupération des likes.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int CountLecture()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var lectures = this.context.Titres.Sum(t => t.NbLectures);
|
||||||
|
return lectures;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.logger.LogError(ex, "Erreur lors de la récupération des lectures.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string? FindMostReviewedArtistName()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return this.context.Titres
|
||||||
|
.AsNoTracking()
|
||||||
|
.GroupBy(t => new { t.IdArtiste, t.Artiste.Nom })
|
||||||
|
.OrderByDescending(g => g.Count())
|
||||||
|
.ThenBy(g => g.Key.Nom)
|
||||||
|
.Select(g => g.Key.Nom)
|
||||||
|
.FirstOrDefault();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.logger.LogError(ex, "Erreur lors de la recherche de l'artiste le plus chroniqué.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string? FindArtistNameWithMostReviewedAlbums()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return this.context.Titres
|
||||||
|
.AsNoTracking()
|
||||||
|
.GroupBy(t => new { t.IdArtiste, t.Artiste.Nom })
|
||||||
|
.Select(g => new
|
||||||
|
{
|
||||||
|
g.Key.Nom,
|
||||||
|
AlbumCount = g.Select(t => t.Album).Distinct().Count(),
|
||||||
|
})
|
||||||
|
.OrderByDescending(x => x.AlbumCount)
|
||||||
|
.ThenBy(x => x.Nom)
|
||||||
|
.Select(x => x.Nom)
|
||||||
|
.FirstOrDefault();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.logger.LogError(ex, "Erreur lors de la recherche de l'artiste avec le plus d'albums chroniqués.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public (int IdTitre, string Libelle)? FindMostPlayedTitle()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = this.context.Titres
|
||||||
|
.AsNoTracking()
|
||||||
|
.OrderByDescending(t => t.NbLectures)
|
||||||
|
.ThenBy(t => t.Libelle)
|
||||||
|
.Select(t => new { t.IdTitre, t.Libelle })
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
return result == null ? null : (result.IdTitre, result.Libelle);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.logger.LogError(ex, "Erreur lors de la recherche du titre le plus joué.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IEnumerable<Titre> DerniereChronique(int offset, int limit)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.logger.LogInformation("Récupération des dernières chroniques.");
|
||||||
|
|
||||||
|
return this.context.Titres
|
||||||
|
.AsNoTracking()
|
||||||
|
.OrderByDescending(t => t.DateCreation)
|
||||||
|
.Paginate(offset, limit)
|
||||||
|
.Include(t => t.Artiste)
|
||||||
|
.Include(a => a.Styles);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.logger.LogError(ex, "Erreur lors de la recuperation des dernieres chroniques.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IEnumerable<Titre> TopTitre(int offset, int limit)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.logger.LogInformation("Récupération du Top Titre.");
|
||||||
|
var titres = this.context.Titres
|
||||||
|
.AsNoTracking()
|
||||||
|
.OrderByDescending(t => t.NbLikes)
|
||||||
|
.Paginate(offset, limit)
|
||||||
|
.Include(a => a.Artiste);
|
||||||
|
|
||||||
|
return titres;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.logger.LogError(ex, "Erreur lors de la récupération du Top Titre.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -10,22 +10,21 @@ namespace Webzine.Repository
|
|||||||
using Webzine.Repository.Contracts;
|
using Webzine.Repository.Contracts;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initialise une classe <see cref="LocalArtisteRepository"/> qui implémente l'interface <see cref="IArtisteRepository"/> pour gérer les opérations de base de données liées aux artistes.
|
/// Initialise une classe <see cref="LocalArtisteRepository"/> qui implémente l'interface <see cref="IArtisteRepository"/>.
|
||||||
/// Utilise <see cref="IArtisteRepository"/> en injection de dépendances.
|
/// Gère les opérations liées aux artistes en utilisant une source de données locale (en mémoire).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class LocalArtisteRepository : IArtisteRepository
|
public class LocalArtisteRepository : IArtisteRepository
|
||||||
{
|
{
|
||||||
private readonly ILogger<LocalArtisteRepository> logger;
|
private readonly ILogger<LocalArtisteRepository> logger;
|
||||||
|
|
||||||
// private readonly List<Artiste> artistes;
|
|
||||||
private readonly InMemoryDataStore dataStore;
|
private readonly InMemoryDataStore dataStore;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="LocalArtisteRepository"/> class.
|
/// Initializes a new instance of the <see cref="LocalArtisteRepository"/> class.
|
||||||
/// Est liéee à une liste d'artistes en local et utilise un logger pour enregistrer les opérations effectuées sur les artistes.
|
/// Est liéee à une liste d'artistes en local et utilise un logger pour enregistrer les opérations effectuées sur les artistes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="artistes">La liste des artistes à initialiser. Ne peut pas être null.</param>
|
|
||||||
/// <param name="logger">Le logger à utiliser pour enregistrer les messages de journalisation. Ne peut pas être null.</param>
|
/// <param name="logger">Le logger à utiliser pour enregistrer les messages de journalisation. Ne peut pas être null.</param>
|
||||||
|
/// <param name="dataStore">Le magasin de données en mémoire.</param>
|
||||||
public LocalArtisteRepository(InMemoryDataStore dataStore, ILogger<LocalArtisteRepository> logger)
|
public LocalArtisteRepository(InMemoryDataStore dataStore, ILogger<LocalArtisteRepository> logger)
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
@@ -37,25 +36,19 @@ namespace Webzine.Repository
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Add(Artiste artiste)
|
public void Add(Artiste artiste)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("Mode Local");
|
this.dataStore.Artistes.Add(artiste);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Delete(Artiste artiste)
|
public void Delete(Artiste artiste)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("Mode Local");
|
this.dataStore.Artistes.Remove(artiste);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Artiste Find(int id)
|
public Artiste Find(int id)
|
||||||
{
|
{
|
||||||
var artiste = this.dataStore.Artistes.First(a => a.IdArtiste == id);
|
return this.dataStore.Artistes.SingleOrDefault(a => a.IdArtiste == id);
|
||||||
if (artiste == null)
|
|
||||||
{
|
|
||||||
return new Artiste();
|
|
||||||
}
|
|
||||||
|
|
||||||
return artiste;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -74,7 +67,6 @@ namespace Webzine.Repository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
/// La liste retournée est une copie de la liste interne, donc elle ne peut être nulle.
|
|
||||||
public IEnumerable<Artiste> FindAll()
|
public IEnumerable<Artiste> FindAll()
|
||||||
{
|
{
|
||||||
return this.dataStore.Artistes;
|
return this.dataStore.Artistes;
|
||||||
@@ -83,7 +75,51 @@ namespace Webzine.Repository
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Update(Artiste artiste)
|
public void Update(Artiste artiste)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("Mode Local");
|
Artiste existingArtiste = this.Find(artiste.IdArtiste);
|
||||||
|
if (existingArtiste == null)
|
||||||
|
{
|
||||||
|
this.logger.LogWarning("L'artiste {Id} n'a pas été trouvé pour l'update.", artiste.IdArtiste);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
existingArtiste.Nom = artiste.Nom;
|
||||||
|
existingArtiste.Biographie = artiste.Biographie;
|
||||||
|
existingArtiste.Titres = artiste.Titres;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IEnumerable<Artiste> Search(string mot)
|
||||||
|
{
|
||||||
|
return this.dataStore.Artistes
|
||||||
|
.Where(a => a.Nom.ToLower().Contains(mot.ToLower()))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int Count()
|
||||||
|
{
|
||||||
|
return this.dataStore.Artistes.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int Count(Func<Artiste, bool> predicate)
|
||||||
|
{
|
||||||
|
return this.dataStore.Artistes.Count(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int CountWithBiography()
|
||||||
|
{
|
||||||
|
return this.dataStore.Artistes.Count(a => !string.IsNullOrEmpty(a.Biographie));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IEnumerable<Artiste> FindArtistes(int offset, int limit)
|
||||||
|
{
|
||||||
|
return this.dataStore.Artistes
|
||||||
|
.OrderBy(a => a.Nom)
|
||||||
|
.Paginate(offset, limit)
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
namespace Webzine.Repository
|
namespace Webzine.Repository
|
||||||
{
|
{
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
@@ -15,7 +14,6 @@ namespace Webzine.Repository
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initialise une classe <see cref="LocalCommentaireRepository"/> qui implémente l'interface <see cref="ICommentaireRepository"/> pour gérer les opérations liées aux commentaires.
|
/// Initialise une classe <see cref="LocalCommentaireRepository"/> qui implémente l'interface <see cref="ICommentaireRepository"/> pour gérer les opérations liées aux commentaires.
|
||||||
/// Utilise <see cref="ICommentaireRepository"/> en injection de dépendances.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class LocalCommentaireRepository : ICommentaireRepository
|
public class LocalCommentaireRepository : ICommentaireRepository
|
||||||
{
|
{
|
||||||
@@ -23,9 +21,8 @@ namespace Webzine.Repository
|
|||||||
private readonly InMemoryDataStore dataStore;
|
private readonly InMemoryDataStore dataStore;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="LocalCommentaireRepository"/> class.
|
|
||||||
/// Initialise une nouvelle instance du <see cref="LocalCommentaireRepository"/> .
|
/// Initialise une nouvelle instance du <see cref="LocalCommentaireRepository"/> .
|
||||||
/// Est liée à un magasin de données en mémoire et utilise un logger pour enregistrer les opérations.
|
/// Gère les opérations liées aux commentaires en utilisant une source de données locale (en mémoire).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dataStore">Le magasin de données en mémoire. Ne peut pas être null.</param>
|
/// <param name="dataStore">Le magasin de données en mémoire. Ne peut pas être null.</param>
|
||||||
/// <param name="logger">Le logger à utiliser pour enregistrer les messages de journalisation. Ne peut pas être null.</param>
|
/// <param name="logger">Le logger à utiliser pour enregistrer les messages de journalisation. Ne peut pas être null.</param>
|
||||||
@@ -38,31 +35,19 @@ namespace Webzine.Repository
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Add(Commentaire commentaire)
|
public void Add(Commentaire commentaire)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("Mode Local");
|
this.dataStore.Commentaires.Add(commentaire);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Delete(Commentaire commentaire)
|
public void Delete(Commentaire commentaire)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("Mode Local");
|
this.dataStore.Commentaires.Remove(commentaire);
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public int Count()
|
|
||||||
{
|
|
||||||
return this.dataStore.Commentaires.Count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Commentaire Find(int idCommentaire)
|
public Commentaire Find(int idCommentaire)
|
||||||
{
|
{
|
||||||
var commentaire = this.dataStore.Commentaires.FirstOrDefault(c => c.IdCommentaire == idCommentaire);
|
return this.dataStore.Commentaires.SingleOrDefault(c => c.IdCommentaire == idCommentaire);
|
||||||
if (commentaire == null)
|
|
||||||
{
|
|
||||||
return new Commentaire();
|
|
||||||
}
|
|
||||||
|
|
||||||
return commentaire;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -76,25 +61,15 @@ namespace Webzine.Repository
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerable<Commentaire> FindCommentaires(int offset, int limit)
|
public IEnumerable<Commentaire> FindCommentaires(int offset, int limit)
|
||||||
{
|
{
|
||||||
if (offset < 0 || limit <= 0)
|
|
||||||
{
|
|
||||||
return Enumerable.Empty<Commentaire>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.dataStore.Commentaires
|
return this.dataStore.Commentaires
|
||||||
.OrderByDescending(c => c.DateCreation)
|
.OrderByDescending(c => c.DateCreation)
|
||||||
.Skip(offset)
|
.Paginate(offset, limit);
|
||||||
.Take(limit)
|
|
||||||
.ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerable<Commentaire> FindByIdTitre(int idTitre)
|
public int Count()
|
||||||
{
|
{
|
||||||
return this.dataStore.Commentaires
|
return this.dataStore.Commentaires.Count;
|
||||||
.Where(c => c.Titre != null && c.Titre.IdTitre == idTitre)
|
|
||||||
.OrderByDescending(c => c.DateCreation)
|
|
||||||
.ToList();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
|
namespace Webzine.Repository;
|
||||||
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
using Webzine.Entity;
|
using Webzine.Entity;
|
||||||
using Webzine.Repository.Contracts;
|
using Webzine.Repository.Contracts;
|
||||||
|
|
||||||
namespace Webzine.Repository;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Classe qui implémente le repository pour les styles en utilisant une liste locale comme source de données.
|
/// Classe qui implémente le repository pour les styles en utilisant une liste locale comme source de données.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -16,9 +17,10 @@ public class LocalStyleRepository : IStyleRepository
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="LocalStyleRepository"/> class.
|
/// Initializes a new instance of the <see cref="LocalStyleRepository"/> class.
|
||||||
|
/// Gère les opérations liées aux styles en utilisant une source de données locale (en mémoire).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logger">Le service de journalisation injecté pour suivre les opérations du repository.</param>
|
/// <param name="logger">Le service de journalisation injecté pour suivre les opérations du repository.</param>
|
||||||
/// <param name="styles">La liste de styles à utiliser comme source de données pour le repository.</param>
|
/// <param name="dataStore">Les données en mémoire.</param>
|
||||||
public LocalStyleRepository(ILogger<LocalStyleRepository> logger, InMemoryDataStore dataStore)
|
public LocalStyleRepository(ILogger<LocalStyleRepository> logger, InMemoryDataStore dataStore)
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
@@ -29,19 +31,19 @@ public class LocalStyleRepository : IStyleRepository
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Add(Style style)
|
public void Add(Style style)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("Mode local");
|
this.dataStore.Styles.Add(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Delete(Style style)
|
public void Delete(Style style)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("Mode local");
|
this.dataStore.Styles.Remove(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Style Find(int id)
|
public Style Find(int id)
|
||||||
{
|
{
|
||||||
return this.dataStore.Styles.Find(s => s.IdStyle == id);
|
return this.dataStore.Styles.SingleOrDefault(s => s.IdStyle == id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -53,6 +55,29 @@ public class LocalStyleRepository : IStyleRepository
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Update(Style style)
|
public void Update(Style style)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("Mode local");
|
Style existingStyle = this.Find(style.IdStyle);
|
||||||
|
if (existingStyle == null)
|
||||||
|
{
|
||||||
|
this.logger.LogWarning("Style with id {IdStyle} not found for update.", style.IdStyle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
existingStyle.Libelle = style.Libelle;
|
||||||
|
existingStyle.Titres = style.Titres;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int Count()
|
||||||
|
{
|
||||||
|
return this.dataStore.Styles.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IEnumerable<Style> FindStyles(int offset, int limit)
|
||||||
|
{
|
||||||
|
return this.dataStore.Styles
|
||||||
|
.OrderBy(s => s.Libelle)
|
||||||
|
.Paginate(offset, limit)
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
|
namespace Webzine.Repository;
|
||||||
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
using Webzine.Entity;
|
using Webzine.Entity;
|
||||||
using Webzine.Repository.Contracts;
|
using Webzine.Repository.Contracts;
|
||||||
|
|
||||||
namespace Webzine.Repository;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Classe qui implémente le repository pour les titres en utilisant une liste locale comme source de données.
|
/// Classe qui implémente le repository pour les titres en utilisant une liste locale comme source de données.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -27,20 +28,20 @@ public class LocalTitreRepository : ITitreRepository
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Add(Titre titre)
|
public void Add(Titre titre)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("Mode local");
|
this.dataStore.Titres.Add(titre);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int Count()
|
public int Count()
|
||||||
{
|
{
|
||||||
var count = this.dataStore.Titres.Count();
|
// On appelle directement LINQ count pour ne pas confondre avec la méthode Count() de l'interface ITitreRepository
|
||||||
return count;
|
return Enumerable.Count(this.dataStore.Titres);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Delete(Titre titre)
|
public void Delete(Titre titre)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("Mode Local");
|
this.dataStore.Titres.Remove(titre);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -48,34 +49,54 @@ public class LocalTitreRepository : ITitreRepository
|
|||||||
{
|
{
|
||||||
return this.dataStore.Titres
|
return this.dataStore.Titres
|
||||||
.OrderByDescending(t => t.DateCreation)
|
.OrderByDescending(t => t.DateCreation)
|
||||||
.Skip(offset)
|
.ThenBy(t => t.Libelle)
|
||||||
.Take(limit);
|
.Paginate(offset, limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void IncrementNbLectures(Titre titre)
|
public void IncrementNbLectures(Titre titre)
|
||||||
{
|
{
|
||||||
titre.NbLectures++;
|
var stored = this.dataStore.Titres.FirstOrDefault(t => t.IdTitre == titre.IdTitre);
|
||||||
|
if (stored == null)
|
||||||
|
{
|
||||||
|
this.logger.LogWarning("Titre avec l'ID {Id} non trouvé pour incrémenter le nombre de lectures.", titre.IdTitre);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stored.NbLectures++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void IncrementNbLikes(Titre titre)
|
public void IncrementNbLikes(Titre titre)
|
||||||
{
|
{
|
||||||
titre.NbLikes++;
|
var stored = this.dataStore.Titres.FirstOrDefault(t => t.IdTitre == titre.IdTitre);
|
||||||
|
if (stored == null)
|
||||||
|
{
|
||||||
|
this.logger.LogWarning("Titre avec l'ID {Id} non trouvé pour incrémenter le nombre de likes.", titre.IdTitre);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stored.NbLikes++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerable<Titre> Search(string mot)
|
public IEnumerable<Titre> Search(string mot)
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(mot))
|
||||||
|
{
|
||||||
|
return Enumerable.Empty<Titre>();
|
||||||
|
}
|
||||||
|
|
||||||
return this.dataStore.Titres
|
return this.dataStore.Titres
|
||||||
.Where(t => t.Libelle != null && t.Libelle.Contains(mot));
|
.Where(t => t.Libelle.ToLower().Contains(mot.ToLower()))
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Titre Find(int idTitre)
|
public Titre Find(int idTitre)
|
||||||
{
|
{
|
||||||
return this.dataStore.Titres
|
return this.dataStore.Titres
|
||||||
.First(t => t.IdTitre == idTitre);
|
.SingleOrDefault(t => t.IdTitre == idTitre);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -94,6 +115,105 @@ public class LocalTitreRepository : ITitreRepository
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Update(Titre titre)
|
public void Update(Titre titre)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("Mode local");
|
// On trouve le titre stocké pour mettre à jour ses propriétés avec la méthode Find du repository
|
||||||
|
// pour éviter la duplication de code.
|
||||||
|
Titre existingTitre = this.Find(titre.IdTitre);
|
||||||
|
if (existingTitre == null)
|
||||||
|
{
|
||||||
|
this.logger.LogWarning("Titre avec l'ID {Id} non trouvé pour mise à jour.", titre.IdTitre);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
existingTitre.Libelle = titre.Libelle;
|
||||||
|
existingTitre.DateCreation = titre.DateCreation;
|
||||||
|
existingTitre.NbLectures = titre.NbLectures;
|
||||||
|
existingTitre.NbLikes = titre.NbLikes;
|
||||||
|
existingTitre.IdArtiste = titre.IdArtiste;
|
||||||
|
existingTitre.Styles = titre.Styles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int CountLike()
|
||||||
|
{
|
||||||
|
return this.dataStore.Titres.Sum(t => t.NbLikes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int CountLecture()
|
||||||
|
{
|
||||||
|
return this.dataStore.Titres.Sum(t => t.NbLectures);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string? FindMostReviewedArtistName()
|
||||||
|
{
|
||||||
|
return this.dataStore.Titres
|
||||||
|
.GroupBy(t => t.Artiste)
|
||||||
|
.OrderByDescending(g => g.Count())
|
||||||
|
.ThenBy(g => g.Key?.Nom)
|
||||||
|
.Select(g => g.Key?.Nom)
|
||||||
|
.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string? FindArtistNameWithMostReviewedAlbums()
|
||||||
|
{
|
||||||
|
return this.dataStore.Titres
|
||||||
|
.GroupBy(t => t.Artiste)
|
||||||
|
.Select(g => new
|
||||||
|
{
|
||||||
|
ArtistName = g.Key?.Nom,
|
||||||
|
AlbumCount = g.Select(t => t.Album).Distinct().Count(),
|
||||||
|
})
|
||||||
|
.OrderByDescending(x => x.AlbumCount)
|
||||||
|
.ThenBy(x => x.ArtistName)
|
||||||
|
.Select(x => x.ArtistName)
|
||||||
|
.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public (int IdTitre, string Libelle)? FindMostPlayedTitle()
|
||||||
|
{
|
||||||
|
Titre? titre = this.dataStore.Titres
|
||||||
|
.OrderByDescending(t => t.NbLectures)
|
||||||
|
.ThenBy(t => t.Libelle)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
return titre == null ? null : (titre.IdTitre, titre.Libelle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IEnumerable<Titre> DerniereChronique(int offset, int limit)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return this.dataStore.Titres
|
||||||
|
.OrderByDescending(t => t.DateCreation)
|
||||||
|
.Paginate(offset, limit);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.logger.LogError(ex, "Erreur lors de la recuperation des dernieres chroniques.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IEnumerable<Titre> TopTitre(int offset, int limit)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.logger.LogInformation("Récupération du Top Titre.");
|
||||||
|
var titres = this.dataStore.Titres
|
||||||
|
.OrderByDescending(t => t.NbLikes)
|
||||||
|
.Paginate(offset, limit);
|
||||||
|
|
||||||
|
return titres;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.logger.LogError(ex, "Erreur lors de la récupération du Top Titre.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
56
Webzine.Repository/RepositoryPaginationExtensions.cs
Normal file
56
Webzine.Repository/RepositoryPaginationExtensions.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
namespace Webzine.Repository;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fournit des méthodes génériques pour paginer des collections dans la couche Repository.
|
||||||
|
/// </summary>
|
||||||
|
public static class RepositoryPaginationExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Retourne une portion paginée d'une requête.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Type des éléments paginés.</typeparam>
|
||||||
|
/// <param name="source">Source à paginer.</param>
|
||||||
|
/// <param name="offset">Nombre d'éléments à ignorer.</param>
|
||||||
|
/// <param name="limit">Nombre maximal d'éléments à retourner.</param>
|
||||||
|
/// <returns>La requête paginée.</returns>
|
||||||
|
public static IQueryable<T> Paginate<T>(this IQueryable<T> source, int offset, int limit)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(source);
|
||||||
|
ValidatePaginationArguments(offset, limit);
|
||||||
|
|
||||||
|
return source
|
||||||
|
.Skip(offset)
|
||||||
|
.Take(limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retourne une portion paginée d'une collection en mémoire.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Type des éléments paginés.</typeparam>
|
||||||
|
/// <param name="source">Source à paginer.</param>
|
||||||
|
/// <param name="offset">Nombre d'éléments à ignorer.</param>
|
||||||
|
/// <param name="limit">Nombre maximal d'éléments à retourner.</param>
|
||||||
|
/// <returns>La collection paginée.</returns>
|
||||||
|
public static IEnumerable<T> Paginate<T>(this IEnumerable<T> source, int offset, int limit)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(source);
|
||||||
|
ValidatePaginationArguments(offset, limit);
|
||||||
|
|
||||||
|
return source
|
||||||
|
.Skip(offset)
|
||||||
|
.Take(limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ValidatePaginationArguments(int offset, int limit)
|
||||||
|
{
|
||||||
|
if (offset < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(offset), "L'offset doit être supérieur ou égal à 0.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (limit <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(limit), "La limite doit être strictement supérieure à 0.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,20 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
namespace Webzine.WebApplication.Areas.Administration.Controllers;
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
using Webzine.Entity;
|
using Webzine.Entity;
|
||||||
using Webzine.Repository.Contracts;
|
using Webzine.Repository.Contracts;
|
||||||
using Webzine.WebApplication.Areas.Administration.ViewModels.Artiste;
|
using Webzine.WebApplication.Areas.Administration.ViewModels.Artiste;
|
||||||
|
|
||||||
namespace Webzine.WebApplication.Areas.Administration.Controllers;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contrôleur pour la gestion des artistes dans l'administration du webzine.
|
/// Contrôleur pour la gestion des artistes dans l'administration du webzine.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Area("Administration")]
|
[Area("Administration")]
|
||||||
public class ArtisteController : Controller
|
public class ArtisteController : Controller
|
||||||
{
|
{
|
||||||
// Injection du logger via le constructeur
|
|
||||||
private readonly ILogger<ArtisteController> logger;
|
private readonly ILogger<ArtisteController> logger;
|
||||||
private readonly IArtisteRepository artisteRepository;
|
private readonly IArtisteRepository artisteRepository;
|
||||||
|
private readonly IConfiguration configuration;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ArtisteController"/> class.
|
/// Initializes a new instance of the <see cref="ArtisteController"/> class.
|
||||||
@@ -21,26 +22,39 @@ public class ArtisteController : Controller
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logger">Logger.</param>
|
/// <param name="logger">Logger.</param>
|
||||||
/// <param name="artisteRepository">Repository pour les artistes.</param>
|
/// <param name="artisteRepository">Repository pour les artistes.</param>
|
||||||
|
/// <param name="configuration">Configuration.</param>
|
||||||
public ArtisteController(
|
public ArtisteController(
|
||||||
ILogger<ArtisteController> logger,
|
ILogger<ArtisteController> logger,
|
||||||
IArtisteRepository artisteRepository)
|
IArtisteRepository artisteRepository,
|
||||||
|
IConfiguration configuration)
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.artisteRepository = artisteRepository;
|
this.artisteRepository = artisteRepository;
|
||||||
|
this.configuration = configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Affiche la liste des artistes. Pour l'instant, les artistes sont générés à partir de noms prédéfinis via la méthode SeedArtisteByName de la classe ArtisteFactory.
|
/// Affiche la liste des artistes paginée.
|
||||||
/// Chaque artiste est ensuite ajouté à une liste d'artistes qui est passée à la vue.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="page">Le numéro de page pour la pagination des artistes (par défaut à 0).</param>
|
||||||
/// <returns>Redirection.</returns>
|
/// <returns>Redirection.</returns>
|
||||||
public IActionResult Index()
|
public IActionResult Index(int page = 0)
|
||||||
{
|
{
|
||||||
IEnumerable<Artiste> artistes = this.artisteRepository.FindAll();
|
int artistes_par_page = this.configuration.GetValue<int>("Webzine:NombreDeLignesAdministration");
|
||||||
|
|
||||||
var artistes_ordre = artistes.OrderBy(t => t.Nom).ToList();
|
IEnumerable<Artiste> artistes = this.artisteRepository.FindArtistes(page * artistes_par_page, artistes_par_page);
|
||||||
|
|
||||||
return this.View(artistes_ordre);
|
int totalArtistes = this.artisteRepository.Count();
|
||||||
|
|
||||||
|
int totalPages = (int)Math.Ceiling((double)totalArtistes / artistes_par_page);
|
||||||
|
var model = new ArtisteIndexViewModel
|
||||||
|
{
|
||||||
|
Artistes = artistes.ToList(),
|
||||||
|
Page = page,
|
||||||
|
TotalPages = totalPages,
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -49,14 +63,29 @@ public class ArtisteController : Controller
|
|||||||
/// <returns>Redirection.</returns>
|
/// <returns>Redirection.</returns>
|
||||||
public IActionResult Create()
|
public IActionResult Create()
|
||||||
{
|
{
|
||||||
var model = new AdminArtisteForm
|
return this.View();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Formulaire de création d'un artiste.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="model">Paramètre nécessaire pour la création d'un artiste.</param>
|
||||||
|
/// <returns>Redirection sur la page Index.</returns>
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult Create(ArtisteCreateViewModel model)
|
||||||
|
{
|
||||||
|
// Créer un objet Artiste avecc les paramètres.
|
||||||
|
var artiste = new Artiste
|
||||||
{
|
{
|
||||||
Id = 0,
|
Nom = model.Nom,
|
||||||
Nom = string.Empty,
|
Biographie = model.Biographie,
|
||||||
Biographie = string.Empty,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.View(model);
|
this.artisteRepository.Add(artiste);
|
||||||
|
this.logger.LogInformation("Création d'un nouvel artiste: {Nom}", artiste.Nom);
|
||||||
|
|
||||||
|
// Renvoyer sur la page Index.
|
||||||
|
return this.RedirectToAction("Index");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -68,14 +97,35 @@ public class ArtisteController : Controller
|
|||||||
{
|
{
|
||||||
var artiste = this.artisteRepository.Find(id);
|
var artiste = this.artisteRepository.Find(id);
|
||||||
|
|
||||||
var model = new AdminArtisteForm
|
if (artiste == null)
|
||||||
{
|
{
|
||||||
Id = artiste.IdArtiste,
|
return this.RedirectToAction("Index");
|
||||||
Nom = artiste.Nom,
|
}
|
||||||
Biographie = artiste.Biographie,
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.View(model);
|
return this.View(artiste);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Traitement du formulaire de modification d'un artiste.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="model">Paramètre d'un artiste.</param>
|
||||||
|
/// <returns>Redirection sur Index.</returns>
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult Edit(ArtisteEditViewModel model)
|
||||||
|
{
|
||||||
|
var artiste = this.artisteRepository.Find(model.Id);
|
||||||
|
|
||||||
|
if (artiste == null)
|
||||||
|
{
|
||||||
|
return this.RedirectToAction("Index");
|
||||||
|
}
|
||||||
|
|
||||||
|
artiste.Nom = model.Nom;
|
||||||
|
artiste.Biographie = model.Biographie;
|
||||||
|
|
||||||
|
this.artisteRepository.Update(artiste);
|
||||||
|
|
||||||
|
return this.RedirectToAction("Index");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -86,6 +136,12 @@ public class ArtisteController : Controller
|
|||||||
public IActionResult Delete(int id)
|
public IActionResult Delete(int id)
|
||||||
{
|
{
|
||||||
var artiste = this.artisteRepository.Find(id);
|
var artiste = this.artisteRepository.Find(id);
|
||||||
|
|
||||||
|
if (artiste == null)
|
||||||
|
{
|
||||||
|
return this.RedirectToAction("Index");
|
||||||
|
}
|
||||||
|
|
||||||
var model = new AdminArtisteForm
|
var model = new AdminArtisteForm
|
||||||
{
|
{
|
||||||
Id = id,
|
Id = id,
|
||||||
@@ -110,7 +166,6 @@ public class ArtisteController : Controller
|
|||||||
this.artisteRepository.Delete(artiste);
|
this.artisteRepository.Delete(artiste);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Redirect back to the list (or wherever you want them to go after)
|
|
||||||
return this.RedirectToAction("Index");
|
return this.RedirectToAction("Index");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,11 +5,15 @@ namespace Webzine.WebApplication.Areas.Administration.Controllers
|
|||||||
using Webzine.Repository.Contracts;
|
using Webzine.Repository.Contracts;
|
||||||
using Webzine.WebApplication.Areas.Administration.ViewModels.Commentaire;
|
using Webzine.WebApplication.Areas.Administration.ViewModels.Commentaire;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contrôleur pour la gestion des commentaires dans l'administration du webzine. Ce contrôleur permet d'afficher la liste des commentaires, de supprimer un commentaire spécifique et de gérer les interactions liées aux commentaires dans l'interface d'administration.
|
||||||
|
/// </summary>
|
||||||
[Area("Administration")]
|
[Area("Administration")]
|
||||||
public class CommentaireController : Controller
|
public class CommentaireController : Controller
|
||||||
{
|
{
|
||||||
private readonly ILogger<CommentaireController> logger;
|
private readonly ILogger<CommentaireController> logger;
|
||||||
private readonly ICommentaireRepository commentaireRepository;
|
private readonly ICommentaireRepository commentaireRepository;
|
||||||
|
private readonly IConfiguration configuration;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="CommentaireController"/> class.
|
/// Initializes a new instance of the <see cref="CommentaireController"/> class.
|
||||||
@@ -18,10 +22,15 @@ namespace Webzine.WebApplication.Areas.Administration.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logger">Service de journalisation injecté.</param>
|
/// <param name="logger">Service de journalisation injecté.</param>
|
||||||
/// <param name="commentaireRepository">Le repository des commentaires injecté.</param>
|
/// <param name="commentaireRepository">Le repository des commentaires injecté.</param>
|
||||||
public CommentaireController(ILogger<CommentaireController> logger, ICommentaireRepository commentaireRepository)
|
/// <param name="configuration">Service de configuration injecté pour accéder aux paramètres de configuration.</param>
|
||||||
|
public CommentaireController(
|
||||||
|
ILogger<CommentaireController> logger,
|
||||||
|
ICommentaireRepository commentaireRepository,
|
||||||
|
IConfiguration configuration)
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.commentaireRepository = commentaireRepository;
|
this.commentaireRepository = commentaireRepository;
|
||||||
|
this.configuration = configuration;
|
||||||
|
|
||||||
this.logger.LogInformation("Initialisation du contrôleur CommentaireController.");
|
this.logger.LogInformation("Initialisation du contrôleur CommentaireController.");
|
||||||
}
|
}
|
||||||
@@ -29,16 +38,23 @@ namespace Webzine.WebApplication.Areas.Administration.Controllers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Affiche la liste des commentaires dans la vue Index.
|
/// Affiche la liste des commentaires dans la vue Index.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="page">Le numéro de page pour la pagination des commentaires (par défaut à 0).</param>
|
||||||
/// <returns>La vue Index avec le ViewModel contenant la liste des commentaires.</returns>
|
/// <returns>La vue Index avec le ViewModel contenant la liste des commentaires.</returns>
|
||||||
public IActionResult Index()
|
public IActionResult Index(int page = 0)
|
||||||
{
|
{
|
||||||
|
int commentaires_par_page = this.configuration.GetValue<int>("Webzine:NombreDeLignesAdministration");
|
||||||
|
|
||||||
// Récupération des commentaires depuis le repository
|
// Récupération des commentaires depuis le repository
|
||||||
var commentaires = this.commentaireRepository.FindAll();
|
var commentaires = this.commentaireRepository.FindCommentaires(page * commentaires_par_page, commentaires_par_page);
|
||||||
|
|
||||||
|
int totalCommentaires = this.commentaireRepository.Count();
|
||||||
|
|
||||||
// Initialisation du ViewModel
|
// Initialisation du ViewModel
|
||||||
var viewModel = new CommentaireViewModel
|
var viewModel = new CommentaireIndexViewModel
|
||||||
{
|
{
|
||||||
Commentaires = commentaires,
|
Commentaires = commentaires,
|
||||||
|
Page = page,
|
||||||
|
TotalPages = (int)Math.Ceiling((double)totalCommentaires / commentaires_par_page),
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.View(viewModel);
|
return this.View(viewModel);
|
||||||
@@ -53,6 +69,11 @@ namespace Webzine.WebApplication.Areas.Administration.Controllers
|
|||||||
{
|
{
|
||||||
var commentaire = this.commentaireRepository.Find(id);
|
var commentaire = this.commentaireRepository.Find(id);
|
||||||
|
|
||||||
|
if (commentaire == null)
|
||||||
|
{
|
||||||
|
return this.RedirectToAction("Index");
|
||||||
|
}
|
||||||
|
|
||||||
var model = new CommentaireDeleteViewModel
|
var model = new CommentaireDeleteViewModel
|
||||||
{
|
{
|
||||||
IdCommentaire = commentaire.IdCommentaire,
|
IdCommentaire = commentaire.IdCommentaire,
|
||||||
|
|||||||
@@ -1,30 +1,29 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Webzine.Repository.Contracts;
|
|
||||||
using Webzine.WebApplication.Areas.Administration.ViewModels;
|
|
||||||
|
|
||||||
namespace Webzine.WebApplication.Areas.Administration.Controllers;
|
namespace Webzine.WebApplication.Areas.Administration.Controllers;
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
using Webzine.Business.Contracts;
|
||||||
|
using Webzine.Business.Contracts.Dto;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contrôleur pour gérer le tableau de bord de l'administration.
|
||||||
|
/// </summary>
|
||||||
[Area("Administration")]
|
[Area("Administration")]
|
||||||
public class DashboardController : Controller
|
public class DashboardController : Controller
|
||||||
{
|
{
|
||||||
private readonly ILogger<DashboardController> logger;
|
private readonly ILogger<DashboardController> logger;
|
||||||
private readonly IStyleRepository styleRepository;
|
private readonly IDashboardService dashboardService;
|
||||||
private readonly IArtisteRepository artisteRepository;
|
|
||||||
private readonly ITitreRepository titreRepository;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="DashboardController"/> class.
|
/// Initializes a new instance of the <see cref="DashboardController"/> class.
|
||||||
/// Initialise une nouvelle instance de la classe <see cref="DashboardController"/>.
|
/// Initialise une nouvelle instance de la classe <see cref="DashboardController"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logger">Service de journalisation injecté.</param>
|
/// <param name="logger">Service de journalisation injecté.</param>
|
||||||
/// <param name="styleRepository">Repository des styles injecté.</param>
|
/// <param name="dashboardService">Service de calcul des statistiques du tableau de bord.</param>
|
||||||
public DashboardController(ILogger<DashboardController> logger, IStyleRepository styleRepository, IArtisteRepository artisteRepository, ITitreRepository titreRepository)
|
public DashboardController(ILogger<DashboardController> logger, IDashboardService dashboardService)
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.styleRepository = styleRepository;
|
this.dashboardService = dashboardService;
|
||||||
this.artisteRepository = artisteRepository;
|
|
||||||
this.titreRepository = titreRepository;
|
|
||||||
|
|
||||||
this.logger.LogInformation("Initialisation du contrôleur TitreController.");
|
this.logger.LogInformation("Initialisation du contrôleur TitreController.");
|
||||||
}
|
}
|
||||||
@@ -35,42 +34,8 @@ public class DashboardController : Controller
|
|||||||
/// <returns>La vue Index du tableau de bord.</returns>
|
/// <returns>La vue Index du tableau de bord.</returns>
|
||||||
public IActionResult Index()
|
public IActionResult Index()
|
||||||
{
|
{
|
||||||
var artisteLePlusChronique = this.titreRepository.FindAll()
|
DashboardDTO data = this.dashboardService.GetDashboardData();
|
||||||
.GroupBy(t => t.Artiste)
|
|
||||||
.OrderByDescending(g => g.Count())
|
|
||||||
.First();
|
|
||||||
|
|
||||||
var albumLePlusChronique = this.titreRepository.FindAll()
|
return this.View(data);
|
||||||
.GroupBy(t => t.Artiste)
|
|
||||||
.OrderByDescending(g => g.Select(t => t.Album).Distinct().Count())
|
|
||||||
.First();
|
|
||||||
|
|
||||||
var musiqueLaPlusJouee = this.titreRepository.FindAll()
|
|
||||||
.OrderByDescending(t => t.NbLectures)
|
|
||||||
.First();
|
|
||||||
|
|
||||||
var model = new DashboardViewModel
|
|
||||||
{
|
|
||||||
NombreArtistes = this.artisteRepository.FindAll().Count(),
|
|
||||||
|
|
||||||
ArtisteLePlusChronique = artisteLePlusChronique.Key.Nom,
|
|
||||||
|
|
||||||
AlbumLePlusChronique = albumLePlusChronique.Key.Nom,
|
|
||||||
|
|
||||||
NombreBiographies = this.artisteRepository.FindAll().Count(a => !string.IsNullOrEmpty(a.Biographie)),
|
|
||||||
|
|
||||||
IdMusiqueLaPlusJouee = musiqueLaPlusJouee.IdTitre,
|
|
||||||
MusiqueLaPlusJouee = musiqueLaPlusJouee.Libelle,
|
|
||||||
|
|
||||||
NombreTitres = this.titreRepository.Count(),
|
|
||||||
|
|
||||||
NombreGenres = this.styleRepository.FindAll().Count(),
|
|
||||||
|
|
||||||
NombreLectures = this.titreRepository.FindAll().Sum(t => t.NbLectures),
|
|
||||||
|
|
||||||
NombreLikes = this.titreRepository.FindAll().Sum(t => t.NbLikes),
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.View(model);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,80 +2,122 @@ namespace Webzine.WebApplication.Areas.Administration.Controllers
|
|||||||
{
|
{
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
using Webzine.Entity;
|
||||||
using Webzine.Repository.Contracts;
|
using Webzine.Repository.Contracts;
|
||||||
using Webzine.WebApplication.Areas.Administration.ViewModels.Style;
|
using Webzine.WebApplication.Areas.Administration.ViewModels.Style;
|
||||||
|
using Webzine.WebApplication.Areas.Administration.ViewModels.Styles;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contrôleur pour la gestion des styles dans l'administration du webzine.
|
/// Controleur pour la gestion des styles dans l'administration du webzine.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Area("Administration")]
|
[Area("Administration")]
|
||||||
public class StyleController : Controller
|
public class StyleController : Controller
|
||||||
{
|
{
|
||||||
private readonly ILogger<StyleController> logger;
|
private readonly ILogger<StyleController> logger;
|
||||||
private readonly IStyleRepository styleRepository;
|
private readonly IStyleRepository styleRepository;
|
||||||
|
private readonly IConfiguration configuration;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="StyleController"/> class.
|
/// Initializes a new instance of the <see cref="StyleController"/> class.
|
||||||
/// Initialise une nouvelle instance de la classe <see cref="StyleController"/>.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logger">Service de journalisation injecté.</param>
|
/// <param name="logger">Service de journalisation injecte.</param>
|
||||||
/// <param name="styles">Repository des styles injecté.</param>
|
/// <param name="styleRepository">Repository des styles injecte.</param>
|
||||||
|
/// <param name="configuration">Service de configuration injecte pour acceder aux parametres de configuration.</param>
|
||||||
public StyleController(
|
public StyleController(
|
||||||
ILogger<StyleController> logger,
|
ILogger<StyleController> logger,
|
||||||
IStyleRepository styleRepository)
|
IStyleRepository styleRepository,
|
||||||
|
IConfiguration configuration)
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
|
|
||||||
this.logger.LogInformation("Initialisation du contrôleur StyleController.");
|
|
||||||
|
|
||||||
this.styleRepository = styleRepository;
|
this.styleRepository = styleRepository;
|
||||||
|
this.configuration = configuration;
|
||||||
|
|
||||||
|
this.logger.LogInformation("Initialisation du controleur StyleController.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Affiche la liste des styles dans la vue Index.
|
/// Affiche la liste des styles dans la vue Index.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>La vue Index avec le ViewModel contenant la liste des styles.</returns>
|
/// <param name="page">Le numero de page pour la pagination des styles (par defaut a 0).</param>
|
||||||
public IActionResult Index()
|
/// <returns>La vue Index avec la liste des styles.</returns>
|
||||||
|
public IActionResult Index(int page = 0)
|
||||||
{
|
{
|
||||||
var listeStyles = this.styleRepository.FindAll().Take(10);
|
int styles_par_page = this.configuration.GetValue<int>("Webzine:NombreDeLignesAdministration");
|
||||||
|
IEnumerable<Style> listeStyles = this.styleRepository.FindStyles(page * styles_par_page, styles_par_page);
|
||||||
|
|
||||||
return this.View(listeStyles);
|
int totalStyles = this.styleRepository.Count();
|
||||||
|
|
||||||
|
var model = new StyleIndexViewModel
|
||||||
|
{
|
||||||
|
Styles = listeStyles,
|
||||||
|
Page = page,
|
||||||
|
TotalPages = (int)Math.Ceiling((double)totalStyles / styles_par_page),
|
||||||
|
};
|
||||||
|
return this.View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Affiche la vue de création d'un nouveau style.
|
/// Affiche la vue de creation d'un nouveau style.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>La vue Create pour ajouter un nouveau style.</returns>
|
/// <returns>La vue Create pour ajouter un nouveau style.</returns>
|
||||||
public IActionResult Create()
|
public IActionResult Create()
|
||||||
{
|
{
|
||||||
return this.View();
|
var model = new StyleCreateViewModel
|
||||||
|
{
|
||||||
|
Libelle = string.Empty,
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Affiche la vue de confirmation de suppression d'un style, en récupérant les détails du style à supprimer à partir de l'identifiant fourni.
|
/// Cree un nouveau style.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">L'identifiant du style à supprimer.</param>
|
/// <param name="model">Nouveau style.</param>
|
||||||
/// <returns>La vue de confirmation de suppression avec le ViewModel contenant les détails du style à supprimer, ou une redirection vers l'index si le style n'existe pas.</returns>
|
/// <returns>IActionResult.</returns>
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult Create(StyleCreateViewModel model)
|
||||||
|
{
|
||||||
|
var style = new Style
|
||||||
|
{
|
||||||
|
Libelle = model.Libelle,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.styleRepository.Add(style);
|
||||||
|
|
||||||
|
return this.RedirectToAction("Index");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Affiche la vue de confirmation de suppression d'un style.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">L'identifiant du style a supprimer.</param>
|
||||||
|
/// <returns>La vue de confirmation ou une redirection vers l'index si le style n'existe pas.</returns>
|
||||||
public IActionResult Delete(int id)
|
public IActionResult Delete(int id)
|
||||||
{
|
{
|
||||||
var style = this.styleRepository.Find(id);
|
var style = this.styleRepository.Find(id);
|
||||||
|
|
||||||
var vm = new StyleDeleteViewModel
|
if (style == null || style.IdStyle == 0)
|
||||||
|
{
|
||||||
|
return this.RedirectToAction("Index");
|
||||||
|
}
|
||||||
|
|
||||||
|
var model = new StyleDeleteViewModel
|
||||||
{
|
{
|
||||||
IdStyle = style.IdStyle,
|
IdStyle = style.IdStyle,
|
||||||
Libelle = style.Libelle,
|
Libelle = style.Libelle,
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.View(vm);
|
return this.View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Méthode POST pour supprimer un style.
|
/// Methode POST pour supprimer un style.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">Le style à supprimer.</param>
|
/// <param name="model">Le style a supprimer.</param>
|
||||||
/// <returns>Redirige vers la page d'index d'admin style.</returns>
|
/// <returns>Redirige vers la page d'index d'admin style.</returns>
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult Delete(StyleEditViewModel model)
|
public IActionResult Delete(StyleDeleteViewModel model)
|
||||||
{
|
{
|
||||||
var style = this.styleRepository.Find(model.IdStyle);
|
var style = this.styleRepository.Find(model.IdStyle);
|
||||||
|
|
||||||
@@ -88,15 +130,19 @@ namespace Webzine.WebApplication.Areas.Administration.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Affiche la vue d'édition d'un style existant, en récupérant les détails du style à éditer à partir de l'identifiant fourni.
|
/// Affiche la vue d'edition d'un style existant.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">L'identifiant du style à éditer.</param>
|
/// <param name="id">L'identifiant du style a editer.</param>
|
||||||
/// <returns>La vue d'édition avec le ViewModel contenant les détails du style à éditer, ou une redirection vers l'index si le style n'existe pas.</returns>
|
/// <returns>La vue d'edition ou une redirection vers l'index si le style n'existe pas.</returns>
|
||||||
[HttpGet]
|
|
||||||
public IActionResult Edit(int id)
|
public IActionResult Edit(int id)
|
||||||
{
|
{
|
||||||
var style = this.styleRepository.Find(id);
|
var style = this.styleRepository.Find(id);
|
||||||
|
|
||||||
|
if (style == null || style.IdStyle == 0)
|
||||||
|
{
|
||||||
|
return this.RedirectToAction("Index");
|
||||||
|
}
|
||||||
|
|
||||||
var model = new StyleEditViewModel
|
var model = new StyleEditViewModel
|
||||||
{
|
{
|
||||||
IdStyle = style.IdStyle,
|
IdStyle = style.IdStyle,
|
||||||
@@ -105,5 +151,25 @@ namespace Webzine.WebApplication.Areas.Administration.Controllers
|
|||||||
|
|
||||||
return this.View(model);
|
return this.View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Met a jour un style existant.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="model">Donnees du style a modifier.</param>
|
||||||
|
/// <returns>Redirige vers la page d'index d'admin style.</returns>
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult Edit(StyleEditViewModel model)
|
||||||
|
{
|
||||||
|
var style = this.styleRepository.Find(model.IdStyle);
|
||||||
|
if (style == null)
|
||||||
|
{
|
||||||
|
return this.RedirectToAction("Index");
|
||||||
|
}
|
||||||
|
|
||||||
|
style.Libelle = model.Libelle;
|
||||||
|
this.styleRepository.Update(style);
|
||||||
|
|
||||||
|
return this.RedirectToAction("Index");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
|
namespace Webzine.WebApplication.Areas.Administration.Controllers;
|
||||||
|
|
||||||
|
using Business.Contracts;
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
|
||||||
|
using Webzine.Business.Contracts.Dto;
|
||||||
using Webzine.Entity;
|
using Webzine.Entity;
|
||||||
using Webzine.Repository.Contracts;
|
using Webzine.Repository.Contracts;
|
||||||
using Webzine.WebApplication.Areas.Administration.ViewModels.Titre;
|
using Webzine.WebApplication.Areas.Administration.ViewModels.Titre;
|
||||||
|
|
||||||
namespace Webzine.WebApplication.Areas.Administration.Controllers;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contrôleur pour la gestion des titres en administration. Ce contrôleur gère les opérations de création, modification, suppression et affichage des titres dans l'interface d'administration du webzine. Chaque action du contrôleur prépare un ViewModel spécifique pour la vue correspondante, permettant ainsi une séparation claire entre la logique métier et la présentation des données.
|
/// Contrôleur pour la gestion des titres en administration. Ce contrôleur gère les opérations de création, modification, suppression et affichage des titres dans l'interface d'administration du webzine. Chaque action du contrôleur prépare un ViewModel spécifique pour la vue correspondante, permettant ainsi une séparation claire entre la logique métier et la présentation des données.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -13,9 +17,11 @@ namespace Webzine.WebApplication.Areas.Administration.Controllers;
|
|||||||
public class TitreController : Controller
|
public class TitreController : Controller
|
||||||
{
|
{
|
||||||
private readonly ILogger<TitreController> logger;
|
private readonly ILogger<TitreController> logger;
|
||||||
|
private readonly IConfiguration configuration;
|
||||||
private readonly ITitreRepository titreRepository;
|
private readonly ITitreRepository titreRepository;
|
||||||
private readonly IArtisteRepository artisteRepository;
|
private readonly IArtisteRepository artisteRepository;
|
||||||
private readonly IStyleRepository styleRepository;
|
private readonly IStyleRepository styleRepository;
|
||||||
|
private readonly ITitreAdminService titreAdminService;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="TitreController"/> class.
|
/// Initializes a new instance of the <see cref="TitreController"/> class.
|
||||||
@@ -25,23 +31,37 @@ public class TitreController : Controller
|
|||||||
/// <param name="titreRepository">Repository des titres injecté pour accéder aux données des titres.</param>
|
/// <param name="titreRepository">Repository des titres injecté pour accéder aux données des titres.</param>
|
||||||
/// <param name="artisteRepository">Repository des artistes injecté pour accéder aux données des artistes, nécessaires pour les associations avec les titres.</param>
|
/// <param name="artisteRepository">Repository des artistes injecté pour accéder aux données des artistes, nécessaires pour les associations avec les titres.</param>
|
||||||
/// <param name="styleRepository">Repository des styles injecté pour accéder aux données des styles, nécessaires pour les associations avec les titres.</param>
|
/// <param name="styleRepository">Repository des styles injecté pour accéder aux données des styles, nécessaires pour les associations avec les titres.</param>
|
||||||
public TitreController(ILogger<TitreController> logger, ITitreRepository titreRepository, IArtisteRepository artisteRepository, IStyleRepository styleRepository)
|
/// <param name="titreAdminService">Service Titre Administration injecté gérant Edit et Crée.</param>
|
||||||
|
/// <param name="configuration">Service de configuration injecté pour accéder aux paramètres de configuration.</param>
|
||||||
|
public TitreController(
|
||||||
|
ILogger<TitreController> logger,
|
||||||
|
IConfiguration configuration,
|
||||||
|
ITitreRepository titreRepository,
|
||||||
|
IArtisteRepository artisteRepository,
|
||||||
|
IStyleRepository styleRepository,
|
||||||
|
ITitreAdminService titreAdminService)
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
|
this.configuration = configuration;
|
||||||
this.titreRepository = titreRepository;
|
this.titreRepository = titreRepository;
|
||||||
this.artisteRepository = artisteRepository;
|
this.artisteRepository = artisteRepository;
|
||||||
this.styleRepository = styleRepository;
|
this.styleRepository = styleRepository;
|
||||||
|
this.titreAdminService = titreAdminService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Affiche la liste des titres dans la vue Index.
|
/// Affiche la liste des titres dans la vue Index.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="page">Le numéro de page pour la pagination des titres (par défaut à 0).</param>
|
||||||
/// <returns>La vue Index avec le ViewModel contenant la liste des titres.</returns>
|
/// <returns>La vue Index avec le ViewModel contenant la liste des titres.</returns>
|
||||||
public IActionResult Index()
|
public IActionResult Index(int page = 0)
|
||||||
{
|
{
|
||||||
IEnumerable<Titre> titres = this.titreRepository.FindAll().Take(10);
|
int titres_par_page = this.configuration.GetValue<int>("Webzine:NombreDeLignesAdministration");
|
||||||
|
IEnumerable<Titre> titres = this.titreRepository.FindTitres(page * titres_par_page, titres_par_page);
|
||||||
|
|
||||||
var model = titres.Select(t => new AdminTitreList
|
int totalTitres = this.titreRepository.Count();
|
||||||
|
|
||||||
|
var adminListeModel = titres.Select(t => new AdminTitreList
|
||||||
{
|
{
|
||||||
Id = t.IdTitre,
|
Id = t.IdTitre,
|
||||||
Nom = t.Artiste.Nom,
|
Nom = t.Artiste.Nom,
|
||||||
@@ -51,9 +71,15 @@ public class TitreController : Controller
|
|||||||
NbLectures = t.NbLectures,
|
NbLectures = t.NbLectures,
|
||||||
NbLikes = t.NbLikes,
|
NbLikes = t.NbLikes,
|
||||||
NbCommentaires = t.Commentaires?.Count ?? 0,
|
NbCommentaires = t.Commentaires?.Count ?? 0,
|
||||||
}).ToList();
|
});
|
||||||
|
|
||||||
return this.View(model);
|
var vm = new TitreIndexViewModel
|
||||||
|
{
|
||||||
|
Titres = adminListeModel.ToList(),
|
||||||
|
Page = page,
|
||||||
|
TotalPages = (int)Math.Ceiling((double)totalTitres / titres_par_page),
|
||||||
|
};
|
||||||
|
return this.View(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -80,6 +106,45 @@ public class TitreController : Controller
|
|||||||
return this.View(model);
|
return this.View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Traite la soumission du formulaire de création d'un titre.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="model">Données saisies dans le formulaire.</param>
|
||||||
|
/// <returns>Redirection vers Index en cas de succès, réaffichage du formulaire sinon.</returns>
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult Create(TitreAdminDTO model)
|
||||||
|
{
|
||||||
|
if (!this.ModelState.IsValid)
|
||||||
|
{
|
||||||
|
var form = new AdminTitreForm
|
||||||
|
{
|
||||||
|
IdArtiste = model.IdArtiste,
|
||||||
|
Libelle = model.Libelle,
|
||||||
|
Album = model.Album,
|
||||||
|
Chronique = model.Chronique,
|
||||||
|
DateSortie = model.DateSortie,
|
||||||
|
Duree = model.Duree,
|
||||||
|
UrlJaquette = model.UrlJaquette,
|
||||||
|
UrlEcoute = model.UrlEcoute,
|
||||||
|
Styles = model.Styles,
|
||||||
|
Artistes = this.artisteRepository.FindAll().Select(a => new SelectListItem
|
||||||
|
{
|
||||||
|
Value = a.IdArtiste.ToString(),
|
||||||
|
Text = a.Nom,
|
||||||
|
}).ToList(),
|
||||||
|
AllStyles = this.styleRepository.FindAll().Select(s => new SelectListItem
|
||||||
|
{
|
||||||
|
Value = s.IdStyle.ToString(),
|
||||||
|
Text = s.Libelle,
|
||||||
|
}).ToList(),
|
||||||
|
};
|
||||||
|
return this.View(form);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.titreAdminService.CreerTitre(model);
|
||||||
|
return this.RedirectToAction("Index");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Affiche le formulaire de modification d'un titre existant dans la vue Edit, en préremplissant les champs avec les données du titre sélectionné. Les listes déroulantes pour les artistes et les styles sont également remplies pour permettre à l'utilisateur de modifier ces associations.
|
/// Affiche le formulaire de modification d'un titre existant dans la vue Edit, en préremplissant les champs avec les données du titre sélectionné. Les listes déroulantes pour les artistes et les styles sont également remplies pour permettre à l'utilisateur de modifier ces associations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -120,6 +185,46 @@ public class TitreController : Controller
|
|||||||
return this.View(model);
|
return this.View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Traite la soumission du formulaire de modification d'un titre.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="model">Données saisies dans le formulaire.</param>
|
||||||
|
/// <returns>Redirection vers Index en cas de succès, réaffichage du formulaire sinon.</returns>
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult Edit(TitreAdminDTO model)
|
||||||
|
{
|
||||||
|
if (!this.ModelState.IsValid)
|
||||||
|
{
|
||||||
|
var form = new AdminTitreForm
|
||||||
|
{
|
||||||
|
Id = model.Id,
|
||||||
|
IdArtiste = model.IdArtiste,
|
||||||
|
Libelle = model.Libelle,
|
||||||
|
Album = model.Album,
|
||||||
|
Chronique = model.Chronique,
|
||||||
|
DateSortie = model.DateSortie,
|
||||||
|
Duree = model.Duree,
|
||||||
|
UrlJaquette = model.UrlJaquette,
|
||||||
|
UrlEcoute = model.UrlEcoute,
|
||||||
|
Styles = model.Styles,
|
||||||
|
Artistes = this.artisteRepository.FindAll().Select(a => new SelectListItem
|
||||||
|
{
|
||||||
|
Value = a.IdArtiste.ToString(),
|
||||||
|
Text = a.Nom,
|
||||||
|
}).ToList(),
|
||||||
|
AllStyles = this.styleRepository.FindAll().Select(s => new SelectListItem
|
||||||
|
{
|
||||||
|
Value = s.IdStyle.ToString(),
|
||||||
|
Text = s.Libelle,
|
||||||
|
}).ToList(),
|
||||||
|
};
|
||||||
|
return this.View(form);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.titreAdminService.ModifierTitre(model);
|
||||||
|
return this.RedirectToAction("Index");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Affiche la vue de confirmation de suppression d'un titre, en récupérant les détails du titre à supprimer à partir de l'identifiant fourni. Le ViewModel contient les informations essentielles du titre, telles que le libellé et le nom de l'artiste, pour permettre à l'utilisateur de confirmer la suppression.
|
/// Affiche la vue de confirmation de suppression d'un titre, en récupérant les détails du titre à supprimer à partir de l'identifiant fourni. Le ViewModel contient les informations essentielles du titre, telles que le libellé et le nom de l'artiste, pour permettre à l'utilisateur de confirmer la suppression.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -129,6 +234,11 @@ public class TitreController : Controller
|
|||||||
{
|
{
|
||||||
var titre = this.titreRepository.Find(id);
|
var titre = this.titreRepository.Find(id);
|
||||||
|
|
||||||
|
if (titre == null)
|
||||||
|
{
|
||||||
|
return this.RedirectToAction("Index");
|
||||||
|
}
|
||||||
|
|
||||||
var model = new AdminTitreDelete
|
var model = new AdminTitreDelete
|
||||||
{
|
{
|
||||||
Id = titre.IdTitre,
|
Id = titre.IdTitre,
|
||||||
@@ -142,12 +252,13 @@ public class TitreController : Controller
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Méthode POST pour supprimer un titre.
|
/// Méthode POST pour supprimer un titre.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">Le titre à supprimer.</param>
|
/// <param name="id">L'identifiant du titre à supprimer, utilisé pour récupérer les données du titre à partir de la liste des titres générés.</param>
|
||||||
/// <returns>Redirige vers la page d'index d'admin titre.</returns>
|
/// <returns>Redirige vers la page d'index d'admin titre.</returns>
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult Delete(AdminTitreDelete model)
|
public IActionResult DeleteTitre(int id)
|
||||||
{
|
{
|
||||||
var titre = this.titreRepository.Find(model.Id);
|
var titre = this.titreRepository.Find(id);
|
||||||
|
|
||||||
if (titre != null)
|
if (titre != null)
|
||||||
{
|
{
|
||||||
this.titreRepository.Delete(titre);
|
this.titreRepository.Delete(titre);
|
||||||
|
|||||||
@@ -14,11 +14,11 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Définit le nom de l'artiste.
|
/// Définit le nom de l'artiste.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Nom { get; set; }
|
public string? Nom { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Définit la biographie de l'artiste.
|
/// Définit la biographie de l'artiste.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Biographie { get; set; }
|
public string? Biographie { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
namespace Webzine.WebApplication.Areas.Administration.ViewModels.Artiste
|
||||||
|
{
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ViewModel qui sert à la création d'un artiste.
|
||||||
|
/// </summary>
|
||||||
|
public class ArtisteCreateViewModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Nom de l'artiste.
|
||||||
|
/// </summary>
|
||||||
|
[Required(ErrorMessage = "Le nom de l'auteur est obligatoire.")]
|
||||||
|
[StringLength(50, ErrorMessage = "Le nom ne doit pas dépasser 50 caractères.")]
|
||||||
|
public string Nom { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Biographie de l'artiste.
|
||||||
|
/// </summary>*
|
||||||
|
[Required(ErrorMessage = "La biographie ne peux pas etre vide.")]
|
||||||
|
|
||||||
|
public string Biographie { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
namespace Webzine.WebApplication.Areas.Administration.ViewModels.Artiste
|
||||||
|
{
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Permet d'éditer un Artiste.
|
||||||
|
/// </summary>
|
||||||
|
public class ArtisteEditViewModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Id de l'artiste.
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Nom de l'artiste.
|
||||||
|
/// </summary>
|
||||||
|
[Required(ErrorMessage = "Le nom de l'auteur est obligatoire.")]
|
||||||
|
[StringLength(50, ErrorMessage = "Le nom ne doit pas dépasser 50 caractères.")]
|
||||||
|
|
||||||
|
public string Nom { get; set; }
|
||||||
|
|
||||||
|
public string Biographie { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
namespace Webzine.WebApplication.Areas.Administration.ViewModels.Artiste
|
||||||
|
{
|
||||||
|
using Webzine.Entity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ViewModel pour la page d'index des artistes dans l'administration, contenant la liste des artistes à afficher et les informations de pagination.
|
||||||
|
/// </summary>
|
||||||
|
public class ArtisteIndexViewModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Définit la liste des artistes à afficher dans la page d'index de l'administration.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<Artiste> Artistes { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Définit le numéro de page pour la pagination des artistes dans l'administration.
|
||||||
|
/// </summary>
|
||||||
|
public int Page { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Définit le nombre total de pages disponibles pour la pagination des artistes dans l'administration.
|
||||||
|
/// </summary>
|
||||||
|
public int TotalPages { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
namespace Webzine.WebApplication.Areas.Administration.ViewModels.Commentaire
|
||||||
|
{
|
||||||
|
using Webzine.Entity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ViewModel pour la page d'index des commentaires dans l'administration, contenant la liste des commentaires à afficher et les informations de pagination.
|
||||||
|
/// </summary>
|
||||||
|
public class CommentaireIndexViewModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Définit la liste des commentaires à afficher dans la page d'index de l'administration.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<Commentaire> Commentaires { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Définit le numéro de page pour la pagination des commentaires dans l'administration.
|
||||||
|
/// </summary>
|
||||||
|
public int Page { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Définit le nombre total de pages disponibles pour la pagination des commentaires dans l'administration.
|
||||||
|
/// </summary>
|
||||||
|
public int TotalPages { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
// <copyright file="CommentaireViewModel.cs" company="PlaceholderCompany">
|
|
||||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
|
||||||
// </copyright>
|
|
||||||
|
|
||||||
namespace Webzine.WebApplication.Areas.Administration.ViewModels.Commentaire
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// ViewModel pour afficher la liste des commentaires en administration.
|
|
||||||
/// </summary>
|
|
||||||
public class CommentaireViewModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Obtient ou définit la liste des commentaires.
|
|
||||||
/// </summary>
|
|
||||||
public IEnumerable<Entity.Commentaire> Commentaires { get; set; } = new List<Entity.Commentaire>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
// <copyright file="StyleCreateViewModel.cs" company="PlaceholderCompany">
|
// <copyright file="StyleCreateViewModel.cs" company="Equipe 1 - BOBIN, MASI, NODON, VETU">
|
||||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
// Copyright (c) Equipe 1 - BOBIN, MASI, NODON, VETU. All rights reserved.
|
||||||
// </copyright>
|
// </copyright>
|
||||||
|
|
||||||
namespace Webzine.WebApplication.Areas.Administration.ViewModels.Style
|
namespace Webzine.WebApplication.Areas.Administration.ViewModels.Styles
|
||||||
{
|
{
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ViewModel pour la création d'un style en administration.
|
/// ViewModel pour la création d'un style en administration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -12,6 +14,7 @@ namespace Webzine.WebApplication.Areas.Administration.ViewModels.Style
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Obtient ou définit le libellé du style.
|
/// Obtient ou définit le libellé du style.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Required(ErrorMessage = "Le libelle du style est obligatoire.")]
|
||||||
public string Libelle { get; set; }
|
public string Libelle { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
// <copyright file="StyleDeleteViewModel.cs" company="PlaceholderCompany">
|
// <copyright file="StyleDeleteViewModel.cs" company="Equipe 1 - BOBIN, MASI, NODON, VETU">
|
||||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
// Copyright (c) Equipe 1 - BOBIN, MASI, NODON, VETU. All rights reserved.
|
||||||
// </copyright>
|
// </copyright>
|
||||||
|
|
||||||
namespace Webzine.WebApplication.Areas.Administration.ViewModels.Style
|
namespace Webzine.WebApplication.Areas.Administration.ViewModels.Styles
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ViewModel pour la suppression d'un style en administration.
|
/// ViewModel pour la suppression d'un style en administration.
|
||||||
@@ -17,6 +17,6 @@ namespace Webzine.WebApplication.Areas.Administration.ViewModels.Style
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Obtient ou définit le libellé du style.
|
/// Obtient ou définit le libellé du style.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Libelle { get; set; }
|
public string? Libelle { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,22 +1,25 @@
|
|||||||
// <copyright file="StyleEditViewModel.cs" company="PlaceholderCompany">
|
// <copyright file="StyleEditViewModel.cs" company="Equipe 1 - BOBIN, MASI, NODON, VETU">
|
||||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
// Copyright (c) Equipe 1 - BOBIN, MASI, NODON, VETU. All rights reserved.
|
||||||
// </copyright>
|
// </copyright>
|
||||||
|
|
||||||
namespace Webzine.WebApplication.Areas.Administration.ViewModels.Style
|
namespace Webzine.WebApplication.Areas.Administration.ViewModels.Styles
|
||||||
{
|
{
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ViewModel pour la modification d'un style en administration.
|
/// ViewModel pour la modification d'un style en administration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class StyleEditViewModel
|
public class StyleEditViewModel
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Obtient ou définit le libellé du style.
|
/// Obtient ou definit l'identifiant du style.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int IdStyle { get; set; }
|
public int IdStyle { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Obtient ou définit le libellé du style.
|
/// Obtient ou definit le libelle du style.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Required(ErrorMessage = "Le libelle du style est obligatoire.")]
|
||||||
public string Libelle { get; set; }
|
public string Libelle { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
namespace Webzine.WebApplication.Areas.Administration.ViewModels.Style
|
||||||
|
{
|
||||||
|
using Webzine.Entity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ViewModel pour la page d'index des styles dans l'administration, contenant la liste des styles à afficher et les informations de pagination.
|
||||||
|
/// </summary>
|
||||||
|
public class StyleIndexViewModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Définit la liste des styles à afficher dans la page d'index de l'administration.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<Style> Styles { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Définit le numéro de page pour la pagination des styles dans l'administration.
|
||||||
|
/// </summary>
|
||||||
|
public int Page { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Définit le nombre total de pages disponibles pour la pagination des styles dans l'administration.
|
||||||
|
/// </summary>
|
||||||
|
public int TotalPages { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
|
||||||
|
|
||||||
namespace Webzine.WebApplication.Areas.Administration.ViewModels.Titre;
|
namespace Webzine.WebApplication.Areas.Administration.ViewModels.Titre;
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ViewModel pour la création et la modification d'un titre dans l'administration.
|
/// ViewModel pour la création et la modification d'un titre dans l'administration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -15,36 +17,47 @@ public class AdminTitreForm
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Définit l'identifiant de l'artiste associé au titre.
|
/// Définit l'identifiant de l'artiste associé au titre.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Required(ErrorMessage = "L'id de l'artiste est obligatoire.")]
|
||||||
|
|
||||||
public int IdArtiste { get; set; }
|
public int IdArtiste { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Définit le titre du titre.
|
/// Définit le titre du titre.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Required(ErrorMessage = "Le labelle est obligatoire.")]
|
||||||
|
|
||||||
public string Libelle { get; set; }
|
public string Libelle { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Définit le nom de l'album associé au titre.
|
/// Définit le nom de l'album associé au titre.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Required(ErrorMessage = "L'album est obligatoire.")]
|
||||||
|
|
||||||
public string Album { get; set; }
|
public string Album { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Définit la chronique du titre, peut-être une critique ou une description du titre.
|
/// Définit la chronique du titre, peut-être une critique ou une description du titre.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Required(ErrorMessage = "La chronique est obligatoire.")]
|
||||||
public string Chronique { get; set; }
|
public string Chronique { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Définit la date de sortie du titre.
|
/// Définit la date de sortie du titre.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Required(ErrorMessage = "La date de est obligatoire.")]
|
||||||
|
|
||||||
public DateTime DateSortie { get; set; }
|
public DateTime DateSortie { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Définit la durée du titre en secondes.
|
/// Définit la durée du titre en secondes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Required(ErrorMessage = "La durée est obligatoire.")]
|
||||||
public int Duree { get; set; }
|
public int Duree { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Définit l'URL de la jaquette de l'album associé au titre.
|
/// Définit l'URL de la jaquette de l'album associé au titre.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Required(ErrorMessage = "L'Url de la jaquette est obligatoire.")]
|
||||||
public string UrlJaquette { get; set; }
|
public string UrlJaquette { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
namespace Webzine.WebApplication.Areas.Administration.ViewModels.Titre
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ViewModel pour la page d'index des titres dans l'administration, contenant la liste des titres à afficher et les informations de pagination.
|
||||||
|
/// </summary>
|
||||||
|
public class TitreIndexViewModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Définit la liste des titres à afficher dans la page d'index de l'administration.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<AdminTitreList> Titres { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Définit le numéro de page pour la pagination des titres dans l'administration.
|
||||||
|
/// </summary>
|
||||||
|
public int Page { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Définit le nombre total de pages disponibles pour la pagination des titres dans l'administration.
|
||||||
|
/// </summary>
|
||||||
|
public int TotalPages { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,45 @@
|
|||||||
@model Webzine.WebApplication.Areas.Administration.ViewModels.Artiste.AdminArtisteForm
|
@model Webzine.WebApplication.Areas.Administration.ViewModels.Artiste.ArtisteCreateViewModel
|
||||||
|
|
||||||
<h1>Créer un artiste</h1>
|
<h1>Créer un artiste</h1>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<form asp-action="Create" method="post">
|
<form asp-action="Create" method="post">
|
||||||
|
<div class="container">
|
||||||
|
<!-- ARTISTE -->
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label class="col-md-3 col-form-label">Nom de l'artiste<span class="text-danger">*</span></label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<input asp-for="Nom" class="form-control" />
|
||||||
|
<span asp-validation-for="Nom" class="text-danger"></span>
|
||||||
|
|
||||||
<partial name="_Form" />
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- BIOGRAPHIE -->
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label class="col-md-3 col-form-label">Biographie</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<textarea asp-for="Biographie" class="form-control" rows="5"></textarea>
|
||||||
|
<span asp-validation-for="Biographie" class="text-danger"></span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- BOUTONS -->
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-md-9 offset-md-3">
|
||||||
|
<button type="submit" class="btn btn-primary me-2">
|
||||||
|
Sauvegarder
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<a asp-action="Index"
|
||||||
|
class="btn text-primary">
|
||||||
|
Retour à l'administration des artistes
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@model Webzine.WebApplication.Areas.Administration.ViewModels.Artiste.AdminArtisteForm
|
@model Webzine.Entity.Artiste
|
||||||
|
|
||||||
<h1>Editer un artiste</h1>
|
<h1>Editer un artiste</h1>
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<form asp-action="Edit" method="post">
|
<form asp-action="Edit" method="post">
|
||||||
|
|
||||||
<input type="hidden" asp-for="Id"/>
|
<input type="hidden" asp-for="IdArtiste"/>
|
||||||
|
|
||||||
<partial name="_Form" />
|
<partial name="_Form" />
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@model IEnumerable<Webzine.Entity.Artiste>
|
@model Webzine.WebApplication.Areas.Administration.ViewModels.Artiste.ArtisteIndexViewModel;
|
||||||
|
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Artiste";
|
ViewData["Title"] = "Artiste";
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
||||||
@foreach (var artiste in Model)
|
@foreach (var artiste in Model.Artistes)
|
||||||
{
|
{
|
||||||
<tr class="align-middle">
|
<tr class="align-middle">
|
||||||
<td class="p-2">
|
<td class="p-2">
|
||||||
@@ -44,5 +44,21 @@
|
|||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="row justify-content-between">
|
||||||
|
@if (Model.Page > 0)
|
||||||
|
{
|
||||||
|
<a asp-action="Index" asp-route-page="@(Model.Page - 1)"
|
||||||
|
class="btn btn-secondary col-auto mt-3">
|
||||||
|
<< Page précédente
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
@if (Model.Page < Model.TotalPages - 1)
|
||||||
|
{
|
||||||
|
<a asp-action="Index" asp-route-page="@(Model.Page + 1)"
|
||||||
|
class="btn btn-secondary col-auto mt-3 ms-auto">
|
||||||
|
Page suivante >>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@model Webzine.WebApplication.Areas.Administration.ViewModels.Artiste.AdminArtisteForm
|
@model Webzine.Entity.Artiste
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<!-- ARTISTE -->
|
<!-- ARTISTE -->
|
||||||
@@ -6,6 +6,8 @@
|
|||||||
<label class="col-md-3 col-form-label">Nom de l'artiste<span class="text-danger">*</span></label>
|
<label class="col-md-3 col-form-label">Nom de l'artiste<span class="text-danger">*</span></label>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<input asp-for="Nom" class="form-control" />
|
<input asp-for="Nom" class="form-control" />
|
||||||
|
<span asp-validation-for="Nom" class="text-danger"></span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -14,11 +16,12 @@
|
|||||||
<label class="col-md-3 col-form-label">Biographie</label>
|
<label class="col-md-3 col-form-label">Biographie</label>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<textarea asp-for="Biographie" class="form-control" rows="5"></textarea>
|
<textarea asp-for="Biographie" class="form-control" rows="5"></textarea>
|
||||||
|
<span asp-validation-for="Biographie" class="text-danger"></span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- BOUTONS -->
|
<!-- BOUTONS -->
|
||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
<div class="col-md-9 offset-md-3">
|
<div class="col-md-9 offset-md-3">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@model Webzine.WebApplication.Areas.Administration.ViewModels.Commentaire.CommentaireViewModel
|
@model Webzine.WebApplication.Areas.Administration.ViewModels.Commentaire.CommentaireIndexViewModel;
|
||||||
|
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Commentaires";
|
ViewData["Title"] = "Commentaires";
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
{
|
{
|
||||||
<tr class="align-middle">
|
<tr class="align-middle">
|
||||||
<td>
|
<td>
|
||||||
<a asp-action="Details" asp-controller="Titre" asp-route-id="@commentaire.Titre.IdTitre">
|
<a asp-controller="Titre" asp-action="Index" asp-route-id="@commentaire.Titre.IdTitre">
|
||||||
@commentaire.Titre.Libelle
|
@commentaire.Titre.Libelle
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
@@ -47,5 +47,21 @@
|
|||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="row justify-content-between">
|
||||||
|
@if (Model.Page > 0)
|
||||||
|
{
|
||||||
|
<a asp-action="Index" asp-route-page="@(Model.Page - 1)"
|
||||||
|
class="btn btn-secondary col-auto mt-3">
|
||||||
|
<< Page précédente
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
@if (Model.Page < Model.TotalPages - 1)
|
||||||
|
{
|
||||||
|
<a asp-action="Index" asp-route-page="@(Model.Page + 1)"
|
||||||
|
class="btn btn-secondary col-auto mt-3 ms-auto">
|
||||||
|
Page suivante >>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
@model Webzine.WebApplication.Areas.Administration.ViewModels.DashboardViewModel
|
@using Webzine.Business.Contracts.Dto
|
||||||
|
@model DashboardDTO
|
||||||
|
|
||||||
<h1 class="mb-4">Tableau de bord</h1>
|
<h1 class="mb-4">Tableau de bord</h1>
|
||||||
|
|
||||||
@@ -94,7 +95,7 @@
|
|||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<a asp-area=""
|
<a asp-area=""
|
||||||
asp-controller="Titre"
|
asp-controller="Titre"
|
||||||
asp-action="Details"
|
asp-action="Index"
|
||||||
asp-route-id="@Model.IdMusiqueLaPlusJouee">
|
asp-route-id="@Model.IdMusiqueLaPlusJouee">
|
||||||
<div class="ratio ratio-4x3">
|
<div class="ratio ratio-4x3">
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@model Webzine.WebApplication.Areas.Administration.ViewModels.Style.StyleCreateViewModel
|
@model Webzine.WebApplication.Areas.Administration.ViewModels.Styles.StyleCreateViewModel
|
||||||
|
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Créer un style";
|
ViewData["Title"] = "Créer un style";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@model Webzine.WebApplication.Areas.Administration.ViewModels.Style.StyleDeleteViewModel
|
@model Webzine.WebApplication.Areas.Administration.ViewModels.Styles.StyleDeleteViewModel
|
||||||
|
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Supprimer un style";
|
ViewData["Title"] = "Supprimer un style";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@model Webzine.WebApplication.Areas.Administration.ViewModels.Style.StyleEditViewModel
|
@model Webzine.WebApplication.Areas.Administration.ViewModels.Styles.StyleEditViewModel
|
||||||
|
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Editer un style";
|
ViewData["Title"] = "Editer un style";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@model IEnumerable<Webzine.Entity.Style>
|
@model Webzine.WebApplication.Areas.Administration.ViewModels.Style.StyleIndexViewModel;
|
||||||
|
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Styles";
|
ViewData["Title"] = "Styles";
|
||||||
@@ -24,9 +24,9 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@if ( Model.Any())
|
@if (Model.Styles.Any())
|
||||||
{
|
{
|
||||||
@foreach (Webzine.Entity.Style style in Model)
|
@foreach (Webzine.Entity.Style style in Model.Styles)
|
||||||
{
|
{
|
||||||
<tr >
|
<tr >
|
||||||
<td class="p-2 w-75">
|
<td class="p-2 w-75">
|
||||||
@@ -52,4 +52,19 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row justify-content-between">
|
||||||
|
@if (Model.Page > 0)
|
||||||
|
{
|
||||||
|
<a asp-action="Index" asp-route-page="@(Model.Page - 1)"
|
||||||
|
class="btn btn-secondary col-auto mt-3">
|
||||||
|
<< Page précédente
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
@if (Model.Page < Model.TotalPages - 1)
|
||||||
|
{
|
||||||
|
<a asp-action="Index" asp-route-page="@(Model.Page + 1)"
|
||||||
|
class="btn btn-secondary col-auto mt-3 ms-auto">
|
||||||
|
Page suivante >>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
@model Webzine.WebApplication.Areas.Administration.ViewModels.Titre.AdminTitreDelete
|
@model Webzine.WebApplication.Areas.Administration.ViewModels.Titre.AdminTitreDelete
|
||||||
|
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Supprimer un titre";
|
||||||
|
}
|
||||||
|
|
||||||
<div class="container mt-4">
|
<div class="container mt-4">
|
||||||
|
|
||||||
<h1 class="mb-3">Supprimer un titre</h1>
|
<h1 class="mb-3">Supprimer un titre</h1>
|
||||||
@@ -13,7 +17,7 @@
|
|||||||
@Model.Artiste ?
|
@Model.Artiste ?
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<form asp-action="Delete" method="post">
|
<form asp-action="DeleteTitre" method="post">
|
||||||
|
|
||||||
<input type="hidden" asp-for="Id"/>
|
<input type="hidden" asp-for="Id"/>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@model IEnumerable<Webzine.WebApplication.Areas.Administration.ViewModels.Titre.AdminTitreList>
|
@model Webzine.WebApplication.Areas.Administration.ViewModels.Titre.TitreIndexViewModel;
|
||||||
|
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Titres";
|
ViewData["Title"] = "Titres";
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
||||||
@foreach (var item in Model)
|
@foreach (var item in Model.Titres)
|
||||||
{
|
{
|
||||||
<tr>
|
<tr>
|
||||||
<td>@item.Nom</td>
|
<td>@item.Nom</td>
|
||||||
@@ -59,5 +59,21 @@
|
|||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row justify-content-between">
|
||||||
|
@if (Model.Page > 0)
|
||||||
|
{
|
||||||
|
<a asp-action="Index" asp-route-page="@(Model.Page - 1)"
|
||||||
|
class="btn btn-secondary col-auto mt-3">
|
||||||
|
<< Page précédente
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
@if (Model.Page < Model.TotalPages - 1)
|
||||||
|
{
|
||||||
|
<a asp-action="Index" asp-route-page="@(Model.Page + 1)"
|
||||||
|
class="btn btn-secondary col-auto mt-3 ms-auto">
|
||||||
|
Page suivante >>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -8,6 +8,8 @@
|
|||||||
<select asp-for="IdArtiste"
|
<select asp-for="IdArtiste"
|
||||||
asp-items="Model.Artistes"
|
asp-items="Model.Artistes"
|
||||||
class="form-select"></select>
|
class="form-select"></select>
|
||||||
|
<span asp-validation-for="IdArtiste" class="text-danger"></span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -16,6 +18,7 @@
|
|||||||
<label class="col-md-3 col-form-label">Titre<span class="text-danger">*</span></label>
|
<label class="col-md-3 col-form-label">Titre<span class="text-danger">*</span></label>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<input asp-for="Libelle" class="form-control"/>
|
<input asp-for="Libelle" class="form-control"/>
|
||||||
|
<span asp-validation-for="Libelle" class="text-danger"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -24,6 +27,7 @@
|
|||||||
<label class="col-md-3 col-form-label">Album<span class="text-danger">*</span></label>
|
<label class="col-md-3 col-form-label">Album<span class="text-danger">*</span></label>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<input asp-for="Album" class="form-control"/>
|
<input asp-for="Album" class="form-control"/>
|
||||||
|
<span asp-validation-for="Album" class="text-danger"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -34,6 +38,8 @@
|
|||||||
<textarea asp-for="Chronique"
|
<textarea asp-for="Chronique"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
rows="5"></textarea>
|
rows="5"></textarea>
|
||||||
|
<span asp-validation-for="Chronique" class="text-danger"></span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -55,6 +61,8 @@
|
|||||||
class="form-control"
|
class="form-control"
|
||||||
type="number"
|
type="number"
|
||||||
min="0" />
|
min="0" />
|
||||||
|
<span asp-validation-for="Duree" class="text-danger"></span>
|
||||||
|
|
||||||
<span class="input-group-text text-muted">seconds</span>
|
<span class="input-group-text text-muted">seconds</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -66,6 +74,8 @@
|
|||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<input asp-for="UrlJaquette"
|
<input asp-for="UrlJaquette"
|
||||||
class="form-control"/>
|
class="form-control"/>
|
||||||
|
<span asp-validation-for="UrlJaquette" class="text-danger"></span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
13
Webzine.WebApplication/Configuration/AppEnums.cs
Normal file
13
Webzine.WebApplication/Configuration/AppEnums.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
namespace Webzine.WebApplication.Configuration;
|
||||||
|
|
||||||
|
public enum SeederType
|
||||||
|
{
|
||||||
|
Local,
|
||||||
|
Spotify,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum RepositoryType
|
||||||
|
{
|
||||||
|
Local,
|
||||||
|
Db,
|
||||||
|
}
|
||||||
13
Webzine.WebApplication/Configuration/Middlewares.cs
Normal file
13
Webzine.WebApplication/Configuration/Middlewares.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
namespace Webzine.WebApplication.Configuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Options de seuil pour la détection des opérations EF Core lentes.
|
||||||
|
/// </summary>
|
||||||
|
public class EfPerformanceOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Obtient ou définit le seuil en millisecondes au-delà duquel une commande SQL est journalisée.
|
||||||
|
/// Valeur par défaut : 200 ms.
|
||||||
|
/// </summary>
|
||||||
|
public int SeuilMs { get; set; } = 200;
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logger">Service de journalisation injecté pour enregistrer les événements et les erreurs.</param>
|
/// <param name="logger">Service de journalisation injecté pour enregistrer les événements et les erreurs.</param>
|
||||||
/// <param name="configuration">Service d'injection de configuration pour accéder aux paramètres de l'application.</param>
|
/// <param name="configuration">Service d'injection de configuration pour accéder aux paramètres de l'application.</param>
|
||||||
|
/// <param name="titreRepository">Service d'injection du dépôt de titres pour accéder aux données des titres.</param>
|
||||||
public AccueilController(
|
public AccueilController(
|
||||||
ILogger<AccueilController> logger,
|
ILogger<AccueilController> logger,
|
||||||
IConfiguration configuration,
|
IConfiguration configuration,
|
||||||
@@ -30,30 +31,31 @@
|
|||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
this.titreRepository = titreRepository;
|
this.titreRepository = titreRepository;
|
||||||
this.logger.LogDebug(1, "initialisation du AccueilController");
|
this.logger.LogDebug(1, "initialisation du AccueilController");
|
||||||
this.titreRepository = titreRepository;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Affiche la page d'accueil du webzine, présentant les derniers titres et les titres les plus populaires.
|
/// Affiche la page d'accueil du webzine, présentant les derniers titres et les titres les plus populaires.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="page">Le numéro de page pour la pagination des titres (par défaut à 0).</param>
|
||||||
/// <returns>La vue Index avec le ViewModel contenant les listes de titres à afficher.</returns>
|
/// <returns>La vue Index avec le ViewModel contenant les listes de titres à afficher.</returns>
|
||||||
public IActionResult Index()
|
public IActionResult Index(int page = 0)
|
||||||
{
|
{
|
||||||
this.logger.LogInformation("Arrivée sur la page d'accueil");
|
this.logger.LogInformation("Arrivée sur la page d'accueil");
|
||||||
|
|
||||||
var derniereChronique = this.configuration.GetValue<int>("Webzine:NombreDerniereChronique");
|
var derniereChronique = this.configuration.GetValue<int>("Webzine:NombreDerniereChronique");
|
||||||
var nbTopTitres = this.configuration.GetValue<int>("Webzine:NombreDeTopTitres");
|
var nbTopTitres = this.configuration.GetValue<int>("Webzine:NombreDeTopTitres");
|
||||||
|
var totalTitres = this.titreRepository.Count();
|
||||||
|
var totalPages = (int)Math.Ceiling((double)totalTitres / derniereChronique);
|
||||||
|
|
||||||
var titres = this.titreRepository.FindAll();
|
var titresPagines = this.titreRepository.DerniereChronique(page * derniereChronique, derniereChronique).ToList();
|
||||||
|
var topTitres = this.titreRepository.TopTitre(nbTopTitres, nbTopTitres).ToList();
|
||||||
|
|
||||||
var vm = new AccueilIndexViewModel
|
var vm = new AccueilIndexViewModel
|
||||||
{
|
{
|
||||||
DerniersTitres = titres.Take(derniereChronique).ToList(),
|
DerniersTitres = titresPagines,
|
||||||
|
TopTitres = topTitres,
|
||||||
TopTitres = titres
|
Page = page,
|
||||||
.OrderByDescending(t => t.NbLikes)
|
TotalPages = totalPages,
|
||||||
.Take(nbTopTitres)
|
|
||||||
.ToList(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.View(vm);
|
return this.View(vm);
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
namespace Webzine.WebApplication.Controllers;
|
namespace Webzine.WebApplication.Controllers;
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Controller de version de l'API.
|
||||||
|
/// </summary>
|
||||||
public class ApiController : ControllerBase
|
public class ApiController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly ILogger<ApiController> logger;
|
private readonly ILogger<ApiController> logger;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ApiController"/> class.
|
|
||||||
/// Initialise une nouvelle instance de la classe <see cref="ApiController"/>.
|
/// Initialise une nouvelle instance de la classe <see cref="ApiController"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logger">Service de journalisation injecté pour enregistrer les événements et les erreurs.</param>
|
/// <param name="logger">Service de journalisation injecté pour enregistrer les événements et les erreurs.</param>
|
||||||
@@ -21,7 +23,6 @@ public class ApiController : ControllerBase
|
|||||||
/// Endpoint de test pour vérifier que l'API fonctionne correctement. Retourne un objet JSON contenant le nom et la version de l'application.
|
/// Endpoint de test pour vérifier que l'API fonctionne correctement. Retourne un objet JSON contenant le nom et la version de l'application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Un objet JSON avec les propriétés "nom" et "version".</returns>
|
/// <returns>Un objet JSON avec les propriétés "nom" et "version".</returns>
|
||||||
[HttpGet]
|
|
||||||
public IActionResult Version()
|
public IActionResult Version()
|
||||||
{
|
{
|
||||||
this.logger.LogInformation("Get Version was called");
|
this.logger.LogInformation("Get Version was called");
|
||||||
@@ -29,7 +30,7 @@ public class ApiController : ControllerBase
|
|||||||
return this.Ok(new
|
return this.Ok(new
|
||||||
{
|
{
|
||||||
nom = "webzine",
|
nom = "webzine",
|
||||||
version = "2.0",
|
version = "3.0",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,9 @@
|
|||||||
using Webzine.Repository.Contracts;
|
using Webzine.Repository.Contracts;
|
||||||
using Webzine.WebApplication.ViewModels.Artiste;
|
using Webzine.WebApplication.ViewModels.Artiste;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contrôleur pour la gestion des artistes dans l'administration du webzine. Ce contrôleur gère les opérations de création, modification, suppression et affichage des artistes dans l'interface d'administration du webzine. Chaque action du contrôleur prépare un ViewModel spécifique pour la vue correspondante, permettant ainsi une séparation claire entre la logique métier et la présentation des données.
|
||||||
|
/// </summary>
|
||||||
public class ArtisteController : Controller
|
public class ArtisteController : Controller
|
||||||
{
|
{
|
||||||
// Injection du logger via le constructeur
|
// Injection du logger via le constructeur
|
||||||
@@ -12,10 +15,10 @@
|
|||||||
private readonly IArtisteRepository artisteRepository;
|
private readonly IArtisteRepository artisteRepository;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ArtisteController"/> class.
|
|
||||||
/// Initialise une nouvelle instance du <see cref="ArtisteController"/>. avec un service de journalisation injecté.
|
/// Initialise une nouvelle instance du <see cref="ArtisteController"/>. avec un service de journalisation injecté.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logger">Service de journalisation injecté pour enregistrer les événements et les erreurs.</param>
|
/// <param name="logger">Service de journalisation injecté pour enregistrer les événements et les erreurs.</param>
|
||||||
|
/// <param name="artisteRepository">Repository pour accéder aux données des artistes, injecté pour permettre les opérations de création, modification, suppression et affichage des artistes.</param>
|
||||||
public ArtisteController(
|
public ArtisteController(
|
||||||
ILogger<ArtisteController> logger,
|
ILogger<ArtisteController> logger,
|
||||||
IArtisteRepository artisteRepository)
|
IArtisteRepository artisteRepository)
|
||||||
@@ -26,11 +29,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Prend en paramètre le nom de l'artiste (ex: "fatal-bazooka"), utilise la factory pour trouver l'artiste correspondant, et affiche sa page dédiée.
|
/// Affiche la liste des artistes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="nom">Le nom de l'artiste à rechercher, formaté en kebab-case (ex: "fatal-bazooka").</param>
|
/// <param name="nom">Le nom de l'artiste à rechercher, formaté en kebab-case (ex: "fatal-bazooka").</param>
|
||||||
/// <returns>La vue de l'artiste avec son ViewModel, ou une redirection vers l'accueil si le nom est vide, ou une erreur 404 si l'artiste n'est pas trouvé.</returns>
|
/// <returns>La vue de l'artiste avec son ViewModel, ou une redirection vers l'accueil si le nom est vide, ou une erreur 404 si l'artiste n'est pas trouvé.</returns>
|
||||||
[HttpGet("/artiste/{nom}")]
|
|
||||||
public IActionResult Index(string nom)
|
public IActionResult Index(string nom)
|
||||||
{
|
{
|
||||||
this.logger.LogInformation("Tentative d'accès à l'artiste avec le nom : {NomArtiste}", nom);
|
this.logger.LogInformation("Tentative d'accès à l'artiste avec le nom : {NomArtiste}", nom);
|
||||||
@@ -41,14 +43,12 @@
|
|||||||
return this.RedirectToAction("Index", "Accueil");
|
return this.RedirectToAction("Index", "Accueil");
|
||||||
}
|
}
|
||||||
|
|
||||||
// On transforme "fatal-bazooka" en "Fatal Bazooka" pour la factory
|
// On transforme "fatal-bazooka" en "Fatal Bazooka"
|
||||||
string nomPropre = System.Globalization.CultureInfo.CurrentCulture.TextInfo
|
string nomPropre = System.Globalization.CultureInfo.CurrentCulture.TextInfo
|
||||||
.ToTitleCase(nom.Replace("-", " "));
|
.ToTitleCase(nom.Replace("-", " "));
|
||||||
|
|
||||||
// On appelle la factory pour obtenir l'artiste unique
|
|
||||||
var artiste = this.artisteRepository.FindByName(nomPropre);
|
var artiste = this.artisteRepository.FindByName(nomPropre);
|
||||||
|
|
||||||
// Check if artiste was found
|
|
||||||
if (artiste == null)
|
if (artiste == null)
|
||||||
{
|
{
|
||||||
this.logger.LogWarning("Artiste non trouvé avec le nom : {NomArtiste}", nomPropre);
|
this.logger.LogWarning("Artiste non trouvé avec le nom : {NomArtiste}", nomPropre);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// <copyright file="RechercheController.cs" company="PlaceholderCompany">
|
// <copyright file="RechercheController.cs" company="Equipe 1 - BOBIN, MASI, NODON, VETU">
|
||||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
// Copyright (c) Equipe 1 - BOBIN, MASI, NODON, VETU. All rights reserved.
|
||||||
// </copyright>
|
// </copyright>
|
||||||
|
|
||||||
namespace Webzine.WebApplication.Controllers
|
namespace Webzine.WebApplication.Controllers
|
||||||
@@ -8,54 +8,49 @@ namespace Webzine.WebApplication.Controllers
|
|||||||
|
|
||||||
using Webzine.Repository.Contracts;
|
using Webzine.Repository.Contracts;
|
||||||
using Webzine.WebApplication.ViewModels.Recherche;
|
using Webzine.WebApplication.ViewModels.Recherche;
|
||||||
using Webzine.WebApplication.ViewModels.Titre;
|
|
||||||
|
|
||||||
[Route("recherche")]
|
/// <summary>
|
||||||
|
/// Controller de la page de recherche d'artistes et de titres.
|
||||||
|
/// </summary>
|
||||||
public class RechercheController : Controller
|
public class RechercheController : Controller
|
||||||
{
|
{
|
||||||
private readonly ILogger<RechercheController> logger;
|
private readonly ILogger<RechercheController> logger;
|
||||||
private readonly ITitreRepository titreRepository;
|
private readonly ITitreRepository titreRepository;
|
||||||
|
private readonly IArtisteRepository artisteRepository;
|
||||||
|
|
||||||
public RechercheController(ILogger<RechercheController> logger, ITitreRepository titreRepository)
|
/// <summary>
|
||||||
|
/// Constructeur du controller de la page de recherche d'artistes et de titres.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger">Le logger pour enregistrer les événements.</param>
|
||||||
|
/// <param name="titreRepository">Le repository pour gérer les opérations sur les titres.</param>
|
||||||
|
/// <param name="artisteRepository">Le repository pour gérer les opérations sur les artistes.</param>
|
||||||
|
public RechercheController(
|
||||||
|
ILogger<RechercheController> logger,
|
||||||
|
ITitreRepository titreRepository,
|
||||||
|
IArtisteRepository artisteRepository)
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.titreRepository = titreRepository;
|
this.titreRepository = titreRepository;
|
||||||
|
this.artisteRepository = artisteRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("")]
|
/// <summary>
|
||||||
|
/// Affichage de la page Recherche depuis le header de l'app.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mot">Nom d'artiste ou de titre.</param>
|
||||||
|
/// <returns>Page de recherche avec les r<>sultats.</returns>
|
||||||
public IActionResult Index(string mot)
|
public IActionResult Index(string mot)
|
||||||
{
|
{
|
||||||
|
// Logger la recherche.
|
||||||
this.logger.LogInformation("Recherche artistes/titres pour le mot : {Mot}.", mot);
|
this.logger.LogInformation("Recherche artistes/titres pour le mot : {Mot}.", mot);
|
||||||
|
|
||||||
var titres = this.titreRepository.Search(mot)
|
// Recherche des titres.
|
||||||
.Concat(this.titreRepository.SearchByStyle(mot))
|
var titres = this.titreRepository.Search(mot);
|
||||||
.DistinctBy(t => t.IdTitre)
|
|
||||||
.OrderBy(t => t.Libelle)
|
|
||||||
.Select(t => new TitreStyleItem
|
|
||||||
{
|
|
||||||
IdTitre = t.IdTitre,
|
|
||||||
Libelle = t.Libelle,
|
|
||||||
ArtisteNom = t.Artiste?.Nom,
|
|
||||||
UrlJaquette = t.UrlJaquette,
|
|
||||||
Duree = t.Duree,
|
|
||||||
})
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var artistes = this.titreRepository.FindAll()
|
// Recherche des artistes.
|
||||||
.Select(t => t.Artiste)
|
var artistes = this.artisteRepository.Search(mot);
|
||||||
.Where(a => a != null
|
|
||||||
&& !string.IsNullOrWhiteSpace(a.Nom)
|
|
||||||
&& !string.IsNullOrWhiteSpace(mot)
|
|
||||||
&& a.Nom.Contains(mot, StringComparison.OrdinalIgnoreCase))
|
|
||||||
.DistinctBy(a => a!.IdArtiste)
|
|
||||||
.OrderBy(a => a!.Nom)
|
|
||||||
.Select(a => new RechercheArtisteItem
|
|
||||||
{
|
|
||||||
Nom = a!.Nom,
|
|
||||||
NombreDeTitres = a.Titres?.Count ?? 0,
|
|
||||||
})
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
|
// Param<61>tres a retourner <20> la vue.
|
||||||
var vm = new RechercheIndexViewModel
|
var vm = new RechercheIndexViewModel
|
||||||
{
|
{
|
||||||
Mot = mot,
|
Mot = mot,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// <copyright file="TitreController.cs" company="PlaceholderCompany">
|
// <copyright file="TitreController.cs" company="Equipe 1 - BOBIN, MASI, NODON, VETU">
|
||||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
// Copyright (c) Equipe 1 - BOBIN, MASI, NODON, VETU. All rights reserved.
|
||||||
// </copyright>
|
// </copyright>
|
||||||
|
|
||||||
namespace Webzine.WebApplication.Controllers
|
namespace Webzine.WebApplication.Controllers
|
||||||
@@ -15,22 +15,28 @@ namespace Webzine.WebApplication.Controllers
|
|||||||
/// affichage des details, filtrage par style,
|
/// affichage des details, filtrage par style,
|
||||||
/// ajout de likes, commentaires et recherche.
|
/// ajout de likes, commentaires et recherche.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Route("titre")]
|
|
||||||
public class TitreController : Controller
|
public class TitreController : Controller
|
||||||
{
|
{
|
||||||
private readonly ILogger<TitreController> logger;
|
private readonly ILogger<TitreController> logger;
|
||||||
private readonly ITitreRepository titreRepository;
|
private readonly ITitreRepository titreRepository;
|
||||||
|
|
||||||
|
// Pour les commentaires.
|
||||||
|
private readonly ICommentaireRepository commentaireRepository;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="TitreController"/> class.
|
/// Initializes a new instance of the <see cref="TitreController"/> class.
|
||||||
/// Initialise une nouvelle instance de la classe <see cref="TitreController"/>.
|
/// Initialise une nouvelle instance de la classe <see cref="TitreController"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logger">Service de journalisation injecte.</param>
|
/// <param name="logger">Service de journalisation injecte.</param>
|
||||||
/// <param name="titreRepository">Repository des titres injecte.</param>
|
/// <param name="titreRepository">Repository des titres injecte.</param>
|
||||||
public TitreController(ILogger<TitreController> logger, ITitreRepository titreRepository)
|
public TitreController(
|
||||||
|
ILogger<TitreController> logger,
|
||||||
|
ITitreRepository titreRepository,
|
||||||
|
ICommentaireRepository commentaireRepository)
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.titreRepository = titreRepository;
|
this.titreRepository = titreRepository;
|
||||||
|
this.commentaireRepository = commentaireRepository;
|
||||||
|
|
||||||
this.logger.LogInformation("Initialisation du controleur TitreController.");
|
this.logger.LogInformation("Initialisation du controleur TitreController.");
|
||||||
}
|
}
|
||||||
@@ -39,9 +45,9 @@ namespace Webzine.WebApplication.Controllers
|
|||||||
/// Affiche le detail d'un titre specifique.
|
/// Affiche le detail d'un titre specifique.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">Identifiant du titre.</param>
|
/// <param name="id">Identifiant du titre.</param>
|
||||||
|
/// <param name="model">Model de donnée pour un commentaire.</param>
|
||||||
/// <returns>Vue des details ou 404 si introuvable.</returns>
|
/// <returns>Vue des details ou 404 si introuvable.</returns>
|
||||||
[HttpGet("{id}")]
|
public IActionResult Index(int id)
|
||||||
public IActionResult Details(int id)
|
|
||||||
{
|
{
|
||||||
this.logger.LogInformation("Demande d'affichage du detail pour le titre ID {Id}.", id);
|
this.logger.LogInformation("Demande d'affichage du detail pour le titre ID {Id}.", id);
|
||||||
|
|
||||||
@@ -50,31 +56,11 @@ namespace Webzine.WebApplication.Controllers
|
|||||||
if (titre == null)
|
if (titre == null)
|
||||||
{
|
{
|
||||||
this.logger.LogWarning("Titre avec ID {Id} introuvable.", id);
|
this.logger.LogWarning("Titre avec ID {Id} introuvable.", id);
|
||||||
return this.RedirectToAction("Index");
|
return this.RedirectToAction("Index", "Accueil");
|
||||||
}
|
}
|
||||||
|
|
||||||
var vm = new TitreDetail
|
this.titreRepository.IncrementNbLectures(titre);
|
||||||
{
|
return this.View(this.BuildTitreDetailViewModel(titre));
|
||||||
Details = new TitreContent
|
|
||||||
{
|
|
||||||
IdTitre = titre.IdTitre,
|
|
||||||
Libelle = titre.Libelle,
|
|
||||||
Chronique = titre.Chronique,
|
|
||||||
DateSortie = titre.DateSortie,
|
|
||||||
NbLikes = titre.NbLikes,
|
|
||||||
UrlJaquette = titre.UrlJaquette,
|
|
||||||
UrlEcoute = titre.UrlEcoute,
|
|
||||||
ArtisteNom = titre.Artiste.Nom,
|
|
||||||
Styles = titre.Styles,
|
|
||||||
Commentaires = titre.Commentaires,
|
|
||||||
},
|
|
||||||
CommentForm = new TitreComment
|
|
||||||
{
|
|
||||||
IdTitre = titre.IdTitre,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.View(vm);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -82,7 +68,6 @@ namespace Webzine.WebApplication.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="style">Nom du style musical.</param>
|
/// <param name="style">Nom du style musical.</param>
|
||||||
/// <returns>Vue contenant la liste filtree.</returns>
|
/// <returns>Vue contenant la liste filtree.</returns>
|
||||||
[HttpGet("style/{style}")]
|
|
||||||
public IActionResult Style(string style)
|
public IActionResult Style(string style)
|
||||||
{
|
{
|
||||||
this.logger.LogInformation("Recherche des titres pour le style : {Style}.", style);
|
this.logger.LogInformation("Recherche des titres pour le style : {Style}.", style);
|
||||||
@@ -101,24 +86,18 @@ namespace Webzine.WebApplication.Controllers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ajoute un like a un titre.
|
/// Ajoute un like a un titre.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">Modele contenant l'identifiant du titre.</param>
|
/// <param name="id">Identifiant du titre a liker.</param>
|
||||||
/// <returns>Redirection vers la page detail.</returns>
|
/// <returns>Redirection vers la page detail.</returns>
|
||||||
[HttpPost("like")]
|
[HttpPost]
|
||||||
public IActionResult Like(TitreLike model)
|
public IActionResult Like(int id)
|
||||||
{
|
{
|
||||||
this.logger.LogInformation("Ajout d'un like pour le titre ID {Id}.", model.IdTitre);
|
var titre = this.titreRepository.Find(id);
|
||||||
|
if (titre != null)
|
||||||
var titre = this.titreRepository.Find(model.IdTitre);
|
|
||||||
|
|
||||||
if (titre == null)
|
|
||||||
{
|
{
|
||||||
this.logger.LogWarning("Impossible d'ajouter un like. Titre ID {Id} introuvable.", model.IdTitre);
|
this.titreRepository.IncrementNbLikes(titre);
|
||||||
return this.RedirectToAction("Index");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
titre.NbLikes++;
|
return this.RedirectToAction("Index", new { id });
|
||||||
|
|
||||||
return this.RedirectToAction("Details", new { id = model.IdTitre });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -126,38 +105,68 @@ namespace Webzine.WebApplication.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">Donnees du commentaire.</param>
|
/// <param name="model">Donnees du commentaire.</param>
|
||||||
/// <returns>Redirection vers la page detail.</returns>
|
/// <returns>Redirection vers la page detail.</returns>
|
||||||
[HttpPost("comment")]
|
[HttpPost]
|
||||||
public IActionResult Comment(TitreComment model)
|
public IActionResult Comment([Bind(Prefix = "CommentForm")] TitreComment model)
|
||||||
{
|
{
|
||||||
|
var titreToUpdate = this.titreRepository.Find(model.IdTitre);
|
||||||
|
|
||||||
if (!this.ModelState.IsValid)
|
if (!this.ModelState.IsValid)
|
||||||
{
|
{
|
||||||
this.logger.LogWarning("Echec de validation du modele de commentaire pour le titre ID {Id}.", model.IdTitre);
|
if (titreToUpdate == null)
|
||||||
return this.RedirectToAction("Details", new { id = model.IdTitre });
|
{
|
||||||
|
this.logger.LogWarning("Titre avec ID {Id} introuvable pour ajout de commentaire.", model.IdTitre);
|
||||||
|
return this.RedirectToAction("Index", "Accueil");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.View("Index", this.BuildTitreDetailViewModel(titreToUpdate, model));
|
||||||
}
|
}
|
||||||
|
|
||||||
var titre = this.titreRepository.Find(model.IdTitre);
|
if (titreToUpdate != null)
|
||||||
|
|
||||||
if (titre == null)
|
|
||||||
{
|
{
|
||||||
this.logger.LogWarning("Impossible d'ajouter le commentaire. Titre ID {Id} introuvable.", model.IdTitre);
|
var commentaire = new Commentaire
|
||||||
return this.RedirectToAction("Index");
|
{
|
||||||
|
Auteur = model.Auteur,
|
||||||
|
Contenu = model.Contenu,
|
||||||
|
DateCreation = DateTime.UtcNow,
|
||||||
|
IdTitre = model.IdTitre,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.commentaireRepository.Add(commentaire);
|
||||||
}
|
}
|
||||||
|
|
||||||
var commentaire = new Commentaire
|
return this.RedirectToAction("Index", new { id = model.IdTitre });
|
||||||
{
|
|
||||||
Auteur = model.Auteur,
|
|
||||||
Contenu = model.Contenu,
|
|
||||||
DateCreation = DateTime.Now,
|
|
||||||
IdTitre = model.IdTitre,
|
|
||||||
};
|
|
||||||
|
|
||||||
titre.Commentaires.Add(commentaire);
|
|
||||||
|
|
||||||
this.logger.LogInformation("Commentaire ajoute avec succes au titre ID {Id}.", model.IdTitre);
|
|
||||||
|
|
||||||
return this.RedirectToAction("Details", new { id = model.IdTitre });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private TitreDetail BuildTitreDetailViewModel(Titre titre, TitreComment? commentForm = null)
|
||||||
|
{
|
||||||
|
return new TitreDetail
|
||||||
|
{
|
||||||
|
Details = new TitreContent
|
||||||
|
{
|
||||||
|
IdTitre = titre.IdTitre,
|
||||||
|
Libelle = titre.Libelle,
|
||||||
|
Chronique = titre.Chronique,
|
||||||
|
DateSortie = titre.DateSortie,
|
||||||
|
NbLikes = titre.NbLikes,
|
||||||
|
UrlJaquette = titre.UrlJaquette,
|
||||||
|
UrlEcoute = titre.UrlEcoute,
|
||||||
|
UrlEmbedEcoute = BuildSpotifyEmbedUrl(titre.UrlEcoute),
|
||||||
|
ArtisteNom = titre.Artiste.Nom,
|
||||||
|
Styles = titre.Styles,
|
||||||
|
Commentaires = titre.Commentaires,
|
||||||
|
},
|
||||||
|
CommentForm = commentForm ?? new TitreComment
|
||||||
|
{
|
||||||
|
IdTitre = titre.IdTitre,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mappe une entite Titre vers un item de la liste de titres pour l'affichage dans la vue de style.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="titre">Le titre à mapper.</param>
|
||||||
|
/// <returns>L'item de la liste de titres.</returns>
|
||||||
private static TitreStyleItem MapTitreItem(Titre titre)
|
private static TitreStyleItem MapTitreItem(Titre titre)
|
||||||
{
|
{
|
||||||
return new TitreStyleItem
|
return new TitreStyleItem
|
||||||
@@ -169,5 +178,23 @@ namespace Webzine.WebApplication.Controllers
|
|||||||
Duree = titre.Duree,
|
Duree = titre.Duree,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Construit une URL d'intégration Spotify à partir de l'URL d'écoute d'un titre.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="urlEcoute">L'URL d'écoute du titre.</param>
|
||||||
|
/// <returns>L'URL d'intégration Spotify ou null si l'URL n'est pas valide.</returns>
|
||||||
|
private static string? BuildSpotifyEmbedUrl(string? urlEcoute)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(urlEcoute))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var trackId = urlEcoute.Split('/').LastOrDefault();
|
||||||
|
return string.IsNullOrWhiteSpace(trackId)
|
||||||
|
? null
|
||||||
|
: $"https://open.spotify.com/embed/track/{trackId}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,47 @@ public static class RouteConfiguration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static void MapCustomRoutes(this IEndpointRouteBuilder endpoints)
|
public static void MapCustomRoutes(this IEndpointRouteBuilder endpoints)
|
||||||
{
|
{
|
||||||
|
// ----------- TITRE -----------
|
||||||
|
endpoints.MapControllerRoute(
|
||||||
|
name: "TitreStyle",
|
||||||
|
pattern: "titres/style/{style}",
|
||||||
|
defaults: new { controller = "Titre", action = "Style" });
|
||||||
|
|
||||||
|
endpoints.MapControllerRoute(
|
||||||
|
name: "TitreIndex",
|
||||||
|
pattern: "titre/{id}",
|
||||||
|
defaults: new { controller = "Titre", action = "Index" });
|
||||||
|
|
||||||
|
endpoints.MapControllerRoute(
|
||||||
|
name: "ArtisteIndex",
|
||||||
|
pattern: "artiste/{nom}",
|
||||||
|
defaults: new { controller = "Artiste", action = "Index" });
|
||||||
|
|
||||||
|
endpoints.MapControllerRoute(
|
||||||
|
name: "TitreLike",
|
||||||
|
pattern: "titre/{id}/like",
|
||||||
|
defaults: new { controller = "Titre", action = "Like" });
|
||||||
|
|
||||||
|
endpoints.MapControllerRoute(
|
||||||
|
name: "TitreComment",
|
||||||
|
pattern: "titre/{id}/comment",
|
||||||
|
defaults: new { controller = "Titre", action = "Comment" });
|
||||||
|
|
||||||
|
// ----------- ADMIN -----------
|
||||||
|
var adminRoutes = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "artistes", "Artiste" }, { "commentaires", "Commentaire" }, { "styles", "Style" }, { "titres", "Titre" },
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var route in adminRoutes)
|
||||||
|
{
|
||||||
|
endpoints.MapControllerRoute(
|
||||||
|
name: $"Admin{route.Value}Index",
|
||||||
|
pattern: $"administration/{route.Key}",
|
||||||
|
defaults: new { area = "Administration", controller = route.Value, action = "Index" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- AUTRES ROUTES ---
|
||||||
endpoints.MapControllerRoute(
|
endpoints.MapControllerRoute(
|
||||||
name: "areas",
|
name: "areas",
|
||||||
pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
|
pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
|
||||||
@@ -15,4 +56,4 @@ public static class RouteConfiguration
|
|||||||
name: "default",
|
name: "default",
|
||||||
pattern: "{controller=Accueil}/{action=Index}/{id?}");
|
pattern: "{controller=Accueil}/{action=Index}/{id?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
59
Webzine.WebApplication/Filters/GlobalExceptionFilter.cs
Normal file
59
Webzine.WebApplication/Filters/GlobalExceptionFilter.cs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
namespace Webzine.WebApplication.Filters;
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Filtre d'exception global qui intercepte toute exception non geree et la journalise automatiquement.
|
||||||
|
/// </summary>
|
||||||
|
public class GlobalExceptionFilter : IExceptionFilter
|
||||||
|
{
|
||||||
|
private readonly ILogger<GlobalExceptionFilter> logger;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="GlobalExceptionFilter"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger">Service de journalisation injecte.</param>
|
||||||
|
public GlobalExceptionFilter(ILogger<GlobalExceptionFilter> logger)
|
||||||
|
{
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void OnException(ExceptionContext context)
|
||||||
|
{
|
||||||
|
string detail = BuildExceptionDetail(context.Exception);
|
||||||
|
|
||||||
|
this.logger.LogError(
|
||||||
|
context.Exception,
|
||||||
|
"Erreur non geree dans {Action} : {Message} | Details: {Details}",
|
||||||
|
context.ActionDescriptor.DisplayName,
|
||||||
|
context.Exception.Message,
|
||||||
|
detail);
|
||||||
|
|
||||||
|
context.Result = new ObjectResult(new
|
||||||
|
{
|
||||||
|
erreur = "Une erreur inattendue est survenue.",
|
||||||
|
detail = context.Exception.Message,
|
||||||
|
})
|
||||||
|
{
|
||||||
|
StatusCode = StatusCodes.Status500InternalServerError,
|
||||||
|
};
|
||||||
|
|
||||||
|
context.ExceptionHandled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildExceptionDetail(Exception exception)
|
||||||
|
{
|
||||||
|
var messages = new List<string>();
|
||||||
|
Exception? current = exception;
|
||||||
|
|
||||||
|
while (current != null)
|
||||||
|
{
|
||||||
|
messages.Add($"{current.GetType().Name}: {current.Message}");
|
||||||
|
current = current.InnerException;
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Join(" --> ", messages);
|
||||||
|
}
|
||||||
|
}
|
||||||
80
Webzine.WebApplication/Filters/ValidationActionFilter.cs
Normal file
80
Webzine.WebApplication/Filters/ValidationActionFilter.cs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
namespace Webzine.WebApplication.Filters;
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Filtre d'action qui valide automatiquement le ModelState avant l'exécution du contrôleur.
|
||||||
|
/// Mesure également le temps d'exécution de chaque action (niveau Trace).
|
||||||
|
/// </summary>
|
||||||
|
public class ValidationActionFilter : IActionFilter
|
||||||
|
{
|
||||||
|
private readonly ILogger<ValidationActionFilter> logger;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ValidationActionFilter"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger">Service de journalisation injecté.</param>
|
||||||
|
public ValidationActionFilter(ILogger<ValidationActionFilter> logger)
|
||||||
|
{
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void OnActionExecuting(ActionExecutingContext context)
|
||||||
|
{
|
||||||
|
string controllerName = context.RouteData.Values["controller"]?.ToString() ?? string.Empty;
|
||||||
|
string actionName = context.RouteData.Values["action"]?.ToString() ?? string.Empty;
|
||||||
|
|
||||||
|
if (controllerName.Equals("Titre", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& actionName.Equals("Comment", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!context.ModelState.IsValid)
|
||||||
|
{
|
||||||
|
var erreurs = context.ModelState
|
||||||
|
.Where(e => e.Value?.Errors.Count > 0)
|
||||||
|
.Select(e => $"{e.Key}: {string.Join(", ", e.Value!.Errors.Select(err => err.ErrorMessage))}")
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
this.logger.LogWarning(
|
||||||
|
"Validation échouée pour {Action} : {Erreurs}",
|
||||||
|
context.ActionDescriptor.DisplayName,
|
||||||
|
string.Join(" | ", erreurs));
|
||||||
|
|
||||||
|
// cas spécial: titre details
|
||||||
|
if (actionName.Equals("Index", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
context.Result = new RedirectResult("/");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupère le modèle soumis (premier argument de l'action, s'il existe)
|
||||||
|
object? model = context.ActionArguments.Values.FirstOrDefault();
|
||||||
|
|
||||||
|
if (context.Controller is Controller controller)
|
||||||
|
{
|
||||||
|
context.Result = new ViewResult
|
||||||
|
{
|
||||||
|
ViewName = actionName,
|
||||||
|
ViewData = new Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary(
|
||||||
|
controller.ViewData)
|
||||||
|
{
|
||||||
|
Model = model,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.Result = new BadRequestObjectResult(context.ModelState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void OnActionExecuted(ActionExecutedContext context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
146
Webzine.WebApplication/Interceptors/EfSlowQueryInterceptor.cs
Normal file
146
Webzine.WebApplication/Interceptors/EfSlowQueryInterceptor.cs
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
namespace Webzine.WebApplication.Interceptors;
|
||||||
|
|
||||||
|
using System.Data.Common;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
using Configuration;
|
||||||
|
|
||||||
|
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Intercepteur EF Core qui journalise uniquement les commandes SQL dépassant le seuil configuré.
|
||||||
|
/// Remonte la pile d'appels pour identifier la méthode repository (<c>Webzine.Repository.*</c>) à l'origine de la requête.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// <b>Références :</b>
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>
|
||||||
|
/// EF Core interceptors (doc officielle) :
|
||||||
|
/// <see href="https://learn.microsoft.com/en-us/ef/core/logging-events-diagnostics/interceptors"/>
|
||||||
|
/// </item>
|
||||||
|
/// <item>
|
||||||
|
/// <see cref="DbCommandInterceptor"/> API :
|
||||||
|
/// <see href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.diagnostics.dbcommandinterceptor"/>
|
||||||
|
/// </item>
|
||||||
|
/// <item>
|
||||||
|
/// Exemple de slow-query interceptor (SO) :
|
||||||
|
/// <see href="https://medium.com/@sudipdevdev/how-to-detect-and-log-slow-queries-in-entity-framework-core-e2ab71024849"/>
|
||||||
|
/// </item>
|
||||||
|
/// <item>
|
||||||
|
/// <see cref="System.Diagnostics.StackTrace"/> pour remonter l'appelant :
|
||||||
|
/// <see href="https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.stacktrace"/>
|
||||||
|
/// </item>
|
||||||
|
/// <item>
|
||||||
|
/// Enregistrement via <c>AddInterceptors</c> :
|
||||||
|
/// <see href="https://learn.microsoft.com/en-us/ef/core/logging-events-diagnostics/interceptors#registering-interceptors"/>
|
||||||
|
/// </item>
|
||||||
|
/// </list>
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public class EfSlowQueryInterceptor : DbCommandInterceptor
|
||||||
|
{
|
||||||
|
private readonly ILogger<EfSlowQueryInterceptor> logger;
|
||||||
|
private readonly int seuilMs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="EfSlowQueryInterceptor"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger">Le service de journalisation injecté pour suivre les opérations de l'intercepteur.</param>
|
||||||
|
/// <param name="options">Les options de performance EF injectées pour récupérer le seuil de lenteur configuré.</param>
|
||||||
|
public EfSlowQueryInterceptor(ILogger<EfSlowQueryInterceptor> logger, IOptions<EfPerformanceOptions> options)
|
||||||
|
{
|
||||||
|
this.logger = logger;
|
||||||
|
this.seuilMs = options.Value.SeuilMs;
|
||||||
|
|
||||||
|
this.logger.LogDebug("[EfSlowQueryInterceptor] Constructeur appelé — seuil : {SeuilMs} ms.", this.seuilMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override DbDataReader ReaderExecuted(DbCommand command, CommandExecutedEventData eventData, DbDataReader result)
|
||||||
|
{
|
||||||
|
this.JournaliserSiLent(eventData.Duration);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override ValueTask<DbDataReader> ReaderExecutedAsync(DbCommand command, CommandExecutedEventData eventData, DbDataReader result, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
this.JournaliserSiLent(eventData.Duration);
|
||||||
|
return ValueTask.FromResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int NonQueryExecuted(DbCommand command, CommandExecutedEventData eventData, int result)
|
||||||
|
{
|
||||||
|
this.JournaliserSiLent(eventData.Duration);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override ValueTask<int> NonQueryExecutedAsync(DbCommand command, CommandExecutedEventData eventData, int result, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
this.JournaliserSiLent(eventData.Duration);
|
||||||
|
return ValueTask.FromResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override object? ScalarExecuted(DbCommand command, CommandExecutedEventData eventData, object? result)
|
||||||
|
{
|
||||||
|
this.JournaliserSiLent(eventData.Duration);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override ValueTask<object?> ScalarExecutedAsync(DbCommand command, CommandExecutedEventData eventData, object? result, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
this.JournaliserSiLent(eventData.Duration);
|
||||||
|
return ValueTask.FromResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remonte la pile d'appels pour trouver la première méthode dans <c>Webzine.Repository</c>.
|
||||||
|
/// Toutes les requêtes EF Core du projet transitent par ce namespace, ce qui garantit
|
||||||
|
/// un résultat pertinent sans parcourir l'intégralité de la stack.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Chaîne <c>Classe.Méthode</c> ou <c>"inconnu"</c> si rien trouvé.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// <see cref="StackTrace"/> est instancié uniquement quand le seuil est dépassé,
|
||||||
|
/// ce qui évite tout impact sur le chemin nominal.
|
||||||
|
/// Ref : <see href="https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.stacktrace"/>.
|
||||||
|
/// </remarks>
|
||||||
|
private static string TrouverAppelantRepository()
|
||||||
|
{
|
||||||
|
// skipFrames: 1 pour sauter TrouverAppelantRepository elle-même
|
||||||
|
// fNeedFileInfo: false — on ne veut pas les numéros de ligne (coût supplémentaire inutile)
|
||||||
|
var frames = new StackTrace(skipFrames: 1, fNeedFileInfo: false).GetFrames();
|
||||||
|
|
||||||
|
foreach (var frame in frames)
|
||||||
|
{
|
||||||
|
var methode = frame.GetMethod();
|
||||||
|
if (methode?.DeclaringType?.Namespace?.StartsWith("Webzine.Repository", StringComparison.Ordinal) == true)
|
||||||
|
{
|
||||||
|
return $"{methode.DeclaringType.Name}.{methode.Name}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "inconnu";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void JournaliserSiLent(TimeSpan duree)
|
||||||
|
{
|
||||||
|
if (duree.TotalMilliseconds > this.seuilMs)
|
||||||
|
{
|
||||||
|
var appelant = TrouverAppelantRepository();
|
||||||
|
|
||||||
|
this.logger.LogWarning(
|
||||||
|
"[EfSlowQueryInterceptor] Opération EF Core lente détectée — durée réelle : {DureeMs} ms — seuil : {SeuilMs} ms — dépassement : +{Depassement} ms.{NewLine}Appelant : {Appelant}",
|
||||||
|
duree.TotalMilliseconds.ToString("F2"),
|
||||||
|
this.seuilMs,
|
||||||
|
(duree.TotalMilliseconds - this.seuilMs).ToString("F2"),
|
||||||
|
Environment.NewLine,
|
||||||
|
appelant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
namespace Webzine.WebApplication.Middlewares
|
||||||
|
{
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
public class LogTempsExecutionMiddleware
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// log à chaque requete http.
|
||||||
|
/// </summary>
|
||||||
|
// _next représente le maillon suivant dans la chaîne (le prochain middleware ou le contrôleur)
|
||||||
|
private readonly RequestDelegate next;
|
||||||
|
private readonly ILogger<LogTempsExecutionMiddleware> logger;
|
||||||
|
|
||||||
|
// Le constructeur récupère "_next" et le Logger
|
||||||
|
public LogTempsExecutionMiddleware(RequestDelegate next, ILogger<LogTempsExecutionMiddleware> logger)
|
||||||
|
{
|
||||||
|
this.next = next;
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
// méthode appelée à chaque requête HTTP
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Middleware chargé de journaliser le cycle de vie d'une requête HTTP (entrée, exécution, sortie et temps de réponse).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">Le contexte HTTP encapsulant toutes les informations de la requête et de la réponse.</param>
|
||||||
|
/// <returns>Une tâche (<see cref="Task"/>) représentant l'opération asynchrone.</returns>
|
||||||
|
public async Task InvokeAsync(HttpContext context)
|
||||||
|
{
|
||||||
|
// (Avant le contrôleur)
|
||||||
|
var chronometre = Stopwatch.StartNew(); // lance le chrono
|
||||||
|
|
||||||
|
// --- IN ---
|
||||||
|
var methode = context.Request.Method;
|
||||||
|
var endpoint = context.Request.Path;
|
||||||
|
var traceId = context.TraceIdentifier; // Identifiant unique généré par .NET
|
||||||
|
|
||||||
|
this.logger.LogInformation("[IN] TraceId: {traceId} | Méthode: {methode} | Endpoint: {endpoint}", traceId, methode, endpoint);
|
||||||
|
|
||||||
|
await this.next(context);
|
||||||
|
|
||||||
|
// (Après le contrôleur)
|
||||||
|
chronometre.Stop(); // arrête le chrono
|
||||||
|
var tempsEcoule = chronometre.ElapsedMilliseconds;
|
||||||
|
|
||||||
|
var httpCode = context.Response.StatusCode; // exemple: 200, 404, 500
|
||||||
|
|
||||||
|
// --- OUT ---
|
||||||
|
if (httpCode >= 500)
|
||||||
|
{
|
||||||
|
this.logger.LogError("[OUT] TraceId: {traceId} | HTTP {httpCode} | Temps: {tempsEcoule} ms | Endpoint: {endpoint}", traceId, httpCode, tempsEcoule, endpoint);
|
||||||
|
}
|
||||||
|
else if (httpCode >= 400)
|
||||||
|
{
|
||||||
|
this.logger.LogWarning("[OUT] TraceId: {traceId} | HTTP {httpCode} | Temps: {tempsEcoule} ms | Endpoint: {endpoint}", traceId, httpCode, tempsEcoule, endpoint);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.logger.LogInformation("[OUT] TraceId: {traceId} | HTTP {httpCode} | Temps: {tempsEcoule} ms | Endpoint: {endpoint}", traceId, httpCode, tempsEcoule, endpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,23 @@
|
|||||||
|
// L'erreur SA1200 (ordre des using directives) est desactivee pour Program.cs
|
||||||
|
#pragma warning disable SA1200
|
||||||
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
using NLog;
|
using NLog;
|
||||||
using NLog.Web;
|
using NLog.Web;
|
||||||
|
|
||||||
|
using Webzine.Business;
|
||||||
|
using Webzine.Business.Contracts;
|
||||||
|
using Webzine.Business.Seeders;
|
||||||
using Webzine.EntitiesContext;
|
using Webzine.EntitiesContext;
|
||||||
using Webzine.Entity;
|
using Webzine.Entity;
|
||||||
using Webzine.Entity.Fixtures;
|
using Webzine.Entity.Fixtures;
|
||||||
using Webzine.Repository;
|
using Webzine.Repository;
|
||||||
using Webzine.Repository.Contracts;
|
using Webzine.Repository.Contracts;
|
||||||
|
using Webzine.WebApplication.Configuration;
|
||||||
using Webzine.WebApplication.Extensions;
|
using Webzine.WebApplication.Extensions;
|
||||||
|
using Webzine.WebApplication.Filters;
|
||||||
|
using Webzine.WebApplication.Interceptors;
|
||||||
|
|
||||||
// Initiation du logger NLog pour la classe courante afin de pouvoir l'utiliser pour logger des messages d'information, d'erreur, etc avant la construction de l'application.
|
// Initiation du logger NLog pour la classe courante afin de pouvoir l'utiliser pour logger des messages d'information, d'erreur, etc avant la construction de l'application.
|
||||||
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
|
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
|
||||||
@@ -20,6 +29,14 @@ try
|
|||||||
|
|
||||||
// Ajoute les services necessaires pour permettre l'utilisation des
|
// Ajoute les services necessaires pour permettre l'utilisation des
|
||||||
// controllers avec des vues.
|
// controllers avec des vues.
|
||||||
|
builder.Services.AddControllersWithViews(options =>
|
||||||
|
{
|
||||||
|
// options.Filters.Add<GlobalExceptionFilter>();
|
||||||
|
options.Filters.Add<ValidationActionFilter>();
|
||||||
|
options.Filters.Add<GlobalExceptionFilter>();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ajoute les services necessaires pour permettre l'utilisation des controllers avec des vues.
|
||||||
builder.Services.AddControllersWithViews()
|
builder.Services.AddControllersWithViews()
|
||||||
|
|
||||||
// Ajoute la compilation des vues lors de l'execution de l'application.
|
// Ajoute la compilation des vues lors de l'execution de l'application.
|
||||||
@@ -30,21 +47,40 @@ try
|
|||||||
// NLog: Setup NLog for Dependency injection
|
// NLog: Setup NLog for Dependency injection
|
||||||
builder.Logging.ClearProviders();
|
builder.Logging.ClearProviders();
|
||||||
builder.Host.UseNLog();
|
builder.Host.UseNLog();
|
||||||
|
builder.Services.Configure<SpotifySeederOptions>(builder.Configuration.GetSection("SpotifySeeder"));
|
||||||
|
builder.Services.AddHttpClient<SeedDataSpotify>();
|
||||||
|
|
||||||
|
builder.Services.Configure<EfPerformanceOptions>(options =>
|
||||||
|
{
|
||||||
|
options.SeuilMs = builder.Configuration.GetValue<int>("EfPerformance:SeuilMs");
|
||||||
|
});
|
||||||
|
builder.Services.AddSingleton<EfSlowQueryInterceptor>();
|
||||||
|
|
||||||
// En fonction de la configuration, utilise soit les repositories basés sur une base de données, soit les repositories basés sur des listes locales.
|
// En fonction de la configuration, utilise soit les repositories basés sur une base de données, soit les repositories basés sur des listes locales.
|
||||||
bool useDatabase = builder.Configuration.GetValue<bool>("UseDatabase");
|
// En fonction de la configuration, utilise soit les repositories bases sur une base de donnees, soit les repositories bases sur des listes locales.
|
||||||
bool isSQLite = builder.Configuration.GetValue<bool>("IsSQLite");
|
var repositoryType = builder.Configuration.GetValue<RepositoryType>("Repository");
|
||||||
if (useDatabase)
|
var seederType = builder.Configuration.GetValue<SeederType>("Seeder");
|
||||||
|
var shouldSeed = args.Contains("--seed");
|
||||||
|
|
||||||
|
if (repositoryType == RepositoryType.Db)
|
||||||
{
|
{
|
||||||
if (isSQLite)
|
if (builder.Environment.IsProduction())
|
||||||
{
|
{
|
||||||
builder.Services.AddDbContext<WebzineDbContext>(options =>
|
builder.Services.AddDbContext<WebzineDbContext>((serviceProvider, options) =>
|
||||||
options.UseSqlite(builder.Configuration.GetConnectionString("SqliteConnection")));
|
{
|
||||||
|
options
|
||||||
|
.UseNpgsql(builder.Configuration.GetConnectionString("PostGreSQLConnection"))
|
||||||
|
.AddInterceptors(serviceProvider.GetRequiredService<EfSlowQueryInterceptor>());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
builder.Services.AddDbContext<WebzineDbContext>(options =>
|
builder.Services.AddDbContext<WebzineDbContext>((serviceProvider, options) =>
|
||||||
options.UseNpgsql(builder.Configuration.GetConnectionString("PostGreSQLConnection")));
|
{
|
||||||
|
options
|
||||||
|
.UseSqlite(builder.Configuration.GetConnectionString("SqliteConnection"))
|
||||||
|
.AddInterceptors(serviceProvider.GetRequiredService<EfSlowQueryInterceptor>());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Services.AddScoped<DbEntityRepository>();
|
builder.Services.AddScoped<DbEntityRepository>();
|
||||||
@@ -62,35 +98,43 @@ try
|
|||||||
builder.Services.AddSingleton<InMemoryDataStore>();
|
builder.Services.AddSingleton<InMemoryDataStore>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
builder.Services.AddScoped<IDashboardService, DashboardService>();
|
||||||
|
builder.Services.AddScoped<ITitreAdminService, TitreAdminService>();
|
||||||
|
|
||||||
|
builder.Services.AddScoped<ValidationActionFilter>();
|
||||||
|
builder.Services.AddScoped<GlobalExceptionFilter>();
|
||||||
|
|
||||||
// https://learn.microsoft.com/fr-fr/aspnet/core/performance/response-compression?view=aspnetcore-10.0#configuration
|
// https://learn.microsoft.com/fr-fr/aspnet/core/performance/response-compression?view=aspnetcore-10.0#configuration
|
||||||
// Ajoute le service de compression des réponses HTTP pour réduire la taille des données envoyées au client et améliorer les performances de l'application.
|
// Ajoute le service de compression des reponses HTTP pour reduire la taille des donnees envoyees au client et ameliorer les performances de l'application.
|
||||||
builder.Services.AddResponseCompression();
|
builder.Services.AddResponseCompression();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
if (useDatabase)
|
app.UseMiddleware<Webzine.WebApplication.Middlewares.LogTempsExecutionMiddleware>();
|
||||||
|
|
||||||
|
if (repositoryType == RepositoryType.Db)
|
||||||
{
|
{
|
||||||
if (isSQLite)
|
using (var scope = app.Services.CreateScope())
|
||||||
{
|
{
|
||||||
using (var scope = app.Services.CreateScope())
|
var db = scope.ServiceProvider.GetRequiredService<WebzineDbContext>();
|
||||||
|
db.Database.EnsureCreated();
|
||||||
|
|
||||||
|
if (shouldSeed)
|
||||||
{
|
{
|
||||||
var db = scope.ServiceProvider.GetRequiredService<WebzineDbContext>();
|
|
||||||
db.Database.EnsureDeleted();
|
db.Database.EnsureDeleted();
|
||||||
db.Database.EnsureCreated();
|
db.Database.EnsureCreated();
|
||||||
var repo = scope.ServiceProvider.GetRequiredService<DbEntityRepository>();
|
var repo = scope.ServiceProvider.GetRequiredService<DbEntityRepository>();
|
||||||
repo.SeedBaseDeDonnees();
|
|
||||||
}
|
if (seederType == SeederType.Local)
|
||||||
}
|
{
|
||||||
else
|
repo.SeedBaseDeDonnees(nbArtistes: 1000, nbTitres: 50000, maxStyles: 50);
|
||||||
{
|
}
|
||||||
using (var scope = app.Services.CreateScope())
|
else if (seederType == SeederType.Spotify)
|
||||||
{
|
{
|
||||||
// TODO : A modifier pour ne pas supprimer la base de donnée en prod
|
var spotifySeeder = scope.ServiceProvider.GetRequiredService<SeedDataSpotify>();
|
||||||
var db = scope.ServiceProvider.GetRequiredService<WebzineDbContext>();
|
var jeuDeDonnees = await spotifySeeder.GenererJeuDeDonneesAsync();
|
||||||
db.Database.EnsureDeleted();
|
repo.SeedBaseDeDonnees(jeuDeDonnees);
|
||||||
db.Database.EnsureCreated();
|
}
|
||||||
var repo = scope.ServiceProvider.GetRequiredService<DbEntityRepository>();
|
|
||||||
repo.SeedBaseDeDonnees();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,11 +150,13 @@ try
|
|||||||
var commentaires = new List<Commentaire>();
|
var commentaires = new List<Commentaire>();
|
||||||
var titres = SeedDataLocal.GenererListeTitre(500, artistes, styles, albums);
|
var titres = SeedDataLocal.GenererListeTitre(500, artistes, styles, albums);
|
||||||
|
|
||||||
|
int commentaireIdStart = 1;
|
||||||
foreach (var titre in titres)
|
foreach (var titre in titres)
|
||||||
{
|
{
|
||||||
var commentairesForTitre = SeedDataLocal.GenererListeCommentaire(titre, 0, 5);
|
var commentairesForTitre = SeedDataLocal.GenererListeCommentaire(titre, 0, 5, commentaireIdStart);
|
||||||
titre.Commentaires.AddRange(commentairesForTitre);
|
titre.Commentaires.AddRange(commentairesForTitre);
|
||||||
commentaires.AddRange(commentairesForTitre);
|
commentaires.AddRange(commentairesForTitre);
|
||||||
|
commentaireIdStart += commentairesForTitre.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
store.Artistes.AddRange(artistes);
|
store.Artistes.AddRange(artistes);
|
||||||
@@ -122,8 +168,7 @@ try
|
|||||||
|
|
||||||
app.UseResponseCompression();
|
app.UseResponseCompression();
|
||||||
|
|
||||||
// Active la possibilité de servir des fichiers statiques presents dans
|
// Active la possibilite de servir des fichiers statiques presents dans le dossier wwwroot.
|
||||||
// le dossier wwwroot.
|
|
||||||
app.UseStaticFiles(new StaticFileOptions
|
app.UseStaticFiles(new StaticFileOptions
|
||||||
{
|
{
|
||||||
OnPrepareResponse = ctx =>
|
OnPrepareResponse = ctx =>
|
||||||
@@ -136,7 +181,7 @@ try
|
|||||||
// Active le middleware permettant le routage des requetes entrantes.
|
// Active le middleware permettant le routage des requetes entrantes.
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
|
|
||||||
// Appelle les routes définies dans le dossier Extensions.
|
// Appelle les routes definies dans le dossier Extensions.
|
||||||
app.MapCustomRoutes();
|
app.MapCustomRoutes();
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|||||||
@@ -18,6 +18,26 @@
|
|||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"http-seed": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": true,
|
||||||
|
"applicationUrl": "http://localhost:5038",
|
||||||
|
"commandLineArgs": "--seed",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"https-seed": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": true,
|
||||||
|
"applicationUrl": "https://localhost:7095;http://localhost:5038",
|
||||||
|
"commandLineArgs": "--seed",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,13 @@
|
|||||||
// public int NombreDeTitre { get; set; } = 0;
|
// public int NombreDeTitre { get; set; } = 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Obtient ou définit le nombre de titre paginé.
|
/// Obtient ou définit le numéro de page pour la pagination des titres affichés sur la page d'accueil.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// public int Pagination { get; set; } = 0;
|
public int Page { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Nombre total de page.
|
||||||
|
/// </summary>
|
||||||
|
public int TotalPages { get; set; } = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
namespace Webzine.WebApplication.ViewModels.Recherche;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// ViewModel pour afficher un artiste dans les resultats de recherche.
|
|
||||||
/// </summary>
|
|
||||||
public class RechercheArtisteItem
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Nom de l'artiste.
|
|
||||||
/// </summary>
|
|
||||||
public string? Nom { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Nombre de titres associes a l'artiste.
|
|
||||||
/// </summary>
|
|
||||||
public int NombreDeTitres { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,24 +1,25 @@
|
|||||||
using Webzine.WebApplication.ViewModels.Titre;
|
namespace Webzine.WebApplication.ViewModels.Recherche
|
||||||
|
|
||||||
namespace Webzine.WebApplication.ViewModels.Recherche;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// ViewModel pour afficher les resultats de recherche d'artistes et de titres.
|
|
||||||
/// </summary>
|
|
||||||
public class RechercheIndexViewModel
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
using Webzine.Entity;
|
||||||
/// Mot saisi dans le formulaire.
|
|
||||||
/// </summary>
|
|
||||||
public string? Mot { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Artistes trouves.
|
/// ViewModel pour afficher les resultats de recherche d'artistes et de titres.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<RechercheArtisteItem> Artistes { get; set; } = new ();
|
public class RechercheIndexViewModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Mot saisi dans le formulaire.
|
||||||
|
/// </summary>
|
||||||
|
public string? Mot { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Titres trouves.
|
/// Artistes trouves.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<TitreStyleItem> Titres { get; set; } = new ();
|
public IEnumerable<Artiste> Artistes { get; set; } = new List<Artiste>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Titres trouves.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<Titre> Titres { get; set; } = new List<Titre>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
|
|
||||||
namespace Webzine.WebApplication.ViewModels.Titre;
|
namespace Webzine.WebApplication.ViewModels.Titre;
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Classe représentant un commentaire sur un titre, utilisée pour la validation des données lors de la soumission d'un commentaire.
|
/// Classe représentant un commentaire sur un titre, utilisée pour la validation des données lors de la soumission d'un commentaire.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using Webzine.Entity;
|
|
||||||
|
|
||||||
namespace Webzine.WebApplication.ViewModels.Titre;
|
namespace Webzine.WebApplication.ViewModels.Titre;
|
||||||
|
|
||||||
|
using Webzine.Entity;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contient les détails d'un titre, ainsi que les commentaires associés.
|
/// Contient les détails d'un titre, ainsi que les commentaires associés.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -42,6 +42,11 @@ public class TitreContent
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string UrlEcoute { get; set; }
|
public string UrlEcoute { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Définit l'url du lecteur embarqué du titre.
|
||||||
|
/// </summary>
|
||||||
|
public string? UrlEmbedEcoute { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Définit le nom de l'artiste associé au titre.
|
/// Définit le nom de l'artiste associé au titre.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -25,5 +25,8 @@ public class TitreStyleItem
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? UrlJaquette { get; set; }
|
public string? UrlJaquette { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Définit la durée du titre en secondes.
|
||||||
|
/// </summary>
|
||||||
public int Duree { get; set; }
|
public int Duree { get; set; }
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
@titre.Artiste.Nom
|
@titre.Artiste.Nom
|
||||||
</a>
|
</a>
|
||||||
-
|
-
|
||||||
<a asp-action="Details"
|
<a asp-action="Index"
|
||||||
asp-controller="Titre"
|
asp-controller="Titre"
|
||||||
asp-route-id="@titre.IdTitre">
|
asp-route-id="@titre.IdTitre">
|
||||||
@titre.Libelle
|
@titre.Libelle
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
|
|
||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
<div class="d-flex flex-wrap align-items-center gap-3">
|
<div class="d-flex flex-wrap align-items-center gap-3">
|
||||||
<a asp-action="Details"
|
<a asp-action="Index"
|
||||||
asp-controller="Titre"
|
asp-controller="Titre"
|
||||||
asp-route-id="@titre.IdTitre"
|
asp-route-id="@titre.IdTitre"
|
||||||
class="btn btn-primary btn-sm">
|
class="btn btn-primary btn-sm">
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
|
|
||||||
@foreach (var style in titre.Styles)
|
@foreach (var style in titre.Styles)
|
||||||
{
|
{
|
||||||
<a asp-controller="Titre"
|
<a asp-controller="Titres"
|
||||||
asp-action="Style"
|
asp-action="Style"
|
||||||
asp-route-id="@style.Libelle"
|
asp-route-id="@style.Libelle"
|
||||||
class="text-decoration-none me-1">
|
class="text-decoration-none me-1">
|
||||||
@@ -73,10 +73,21 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<!-- Bouton -->
|
<!-- Bouton -->
|
||||||
<div class="row justify-content-end">
|
<div class="row justify-content-between">
|
||||||
<button class="btn btn-secondary col-auto mt-3">
|
@if (Model.Page > 0)
|
||||||
Titres plus anciens >>
|
{
|
||||||
</button>
|
<a asp-action="Index" asp-route-page="@(Model.Page - 1)"
|
||||||
|
class="btn btn-secondary col-auto mt-3">
|
||||||
|
<< Titre plus récent
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
@if (Model.Page < Model.TotalPages - 1)
|
||||||
|
{
|
||||||
|
<a asp-action="Index" asp-route-page="@(Model.Page + 1)"
|
||||||
|
class="btn btn-secondary col-auto mt-3 ms-auto">
|
||||||
|
Titre plus anciens >>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -90,7 +101,7 @@
|
|||||||
<img class="card-img-top" src="@titre.UrlJaquette" alt="@titre.Album" loading="lazy" />
|
<img class="card-img-top" src="@titre.UrlJaquette" alt="@titre.Album" loading="lazy" />
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<a asp-controller="Titre" asp-action="Details" asp-route-id="@titre.IdTitre" class="card-link">
|
<a asp-controller="Titre" asp-action="Index" asp-route-id="@titre.IdTitre" class="card-link">
|
||||||
@titre.Libelle
|
@titre.Libelle
|
||||||
</a>
|
</a>
|
||||||
<br />
|
<br />
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ else
|
|||||||
<td class="text-secondary font-monospace">@dureeFormatee</td>
|
<td class="text-secondary font-monospace">@dureeFormatee</td>
|
||||||
<td>
|
<td>
|
||||||
<a asp-controller="Titre"
|
<a asp-controller="Titre"
|
||||||
asp-action="Details"
|
asp-action="Index"
|
||||||
asp-route-id="@titre.IdTitre"
|
asp-route-id="@titre.IdTitre"
|
||||||
class="text-primary">
|
class="text-primary">
|
||||||
@titre.Libelle
|
@titre.Libelle
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class ="my-2">
|
<div class ="my-2">
|
||||||
<i class="fa-solid fa-phone"></i> Phone : 03 80 40 50 60<br />
|
<a href="tel:+0380405060"><i class="fa-solid fa-phone"></i> Phone : 03 80 40 50 60</a><br />
|
||||||
<i class="fa-solid fa-envelope"></i> <span class="text-primary">secretariat@cucdb.fr</span>
|
<a href="mailto:secretariat@cucdb.fr"><i class="fa-solid fa-envelope"></i> <span class="text-primary">secretariat@cucdb.fr</span></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -22,35 +22,35 @@
|
|||||||
<h2>Suivez-nous</h2>
|
<h2>Suivez-nous</h2>
|
||||||
<div class="row g-4 text-center">
|
<div class="row g-4 text-center">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<a href="#" class="card h-100 p-4 border-0 bg-light">
|
<a href="https://diiage.cucdb.fr/" class="card h-100 p-4 border-0 bg-light">
|
||||||
<i class="fa-solid fa-link fa-3x text-primary mb-3 align-self-center"></i>
|
<i class="fa-solid fa-link fa-3x text-primary mb-3 align-self-center"></i>
|
||||||
<div class="fw-bold text-primary">Site officiel du DIIAGE</div>
|
<div class="fw-bold text-primary">Site officiel du DIIAGE</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<a href="#" class="card h-100 p-4 border-0 bg-light">
|
<a href="https://www.facebook.com/diiage" class="card h-100 p-4 border-0 bg-light">
|
||||||
<i class="fa-brands fa-facebook fa-3x text-primary mb-3 align-self-center"></i>
|
<i class="fa-brands fa-facebook fa-3x text-primary mb-3 align-self-center"></i>
|
||||||
<div class="fw-bold text-primary">Facebook</div>
|
<div class="fw-bold text-primary">Facebook</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<a href="#" class="card h-100 p-4 border-0 bg-light">
|
<a href="https://www.instagram.com/diiage.cucdb/" class="card h-100 p-4 border-0 bg-light">
|
||||||
<i class="fa-brands fa-instagram fa-3x text-primary mb-3 align-self-center"></i>
|
<i class="fa-brands fa-instagram fa-3x text-primary mb-3 align-self-center"></i>
|
||||||
<div class="fw-bold text-primary">Instagram</div>
|
<div class="fw-bold text-primary">Instagram</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<a href="#" class="card h-100 p-4 border-0 bg-light">
|
<a href="https://www.linkedin.com/company/diiage/posts/?feedView=all" class="card h-100 p-4 border-0 bg-light">
|
||||||
<i class="fa-brands fa-linkedin fa-3x text-primary mb-3 align-self-center"></i>
|
<i class="fa-brands fa-linkedin fa-3x text-primary mb-3 align-self-center"></i>
|
||||||
<div class="fw-bold text-primary">LinkedIn</div>
|
<div class="fw-bold text-primary">LinkedIn</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<a href="#" class="card h-100 p-4 border-0 bg-light">
|
<a href="https://www.google.com/maps/place/C.U.C.D.B.+Centre+Universitaire+Catholique+De+Bourgogne/%4047.3348851,5.0514267,695m/data=!3m1!1e3!4m6!3m5!1s0x47f29dfee14743ed:0x7d1348ede68455cb!8m2!3d47.3348851!4d5.0540016!16s%2Fg%2F1td04cy3?entry=ttu&g_ep=EgoyMDI2MDQwMS4wIKXMDSoASAFQAw%3D%3D" class="card h-100 p-4 border-0 bg-light">
|
||||||
<i class="fa-solid fa-map fa-3x text-primary mb-3 align-self-center"></i>
|
<i class="fa-solid fa-map fa-3x text-primary mb-3 align-self-center"></i>
|
||||||
<div class="fw-bold text-primary">Google Maps</div>
|
<div class="fw-bold text-primary">Google Maps</div>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -10,74 +10,65 @@
|
|||||||
<h1 class="mb-4">Resultats pour "@Model.Mot"</h1>
|
<h1 class="mb-4">Resultats pour "@Model.Mot"</h1>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
<h2 class="h4 mt-4">Artistes</h2>
|
||||||
|
|
||||||
@if (string.IsNullOrWhiteSpace(Model.Mot))
|
@if (!Model.Artistes.Any())
|
||||||
{
|
{
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
Saisissez un mot-cle pour lancer une recherche.
|
<p>Aucun artiste n'a été trouvé.</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else if (!Model.Artistes.Any() && !Model.Titres.Any())
|
|
||||||
|
@foreach (var artiste in Model.Artistes)
|
||||||
|
{
|
||||||
|
<div class="my-3">
|
||||||
|
<a asp-controller="Artiste"
|
||||||
|
asp-action="Index"
|
||||||
|
asp-route-nom="@artiste.Nom">
|
||||||
|
@artiste.Nom
|
||||||
|
</a>
|
||||||
|
<span class="text-muted">(@artiste.Titres.Count titre(s))</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<h2 class="h4 mt-4">Titres</h2>
|
||||||
|
|
||||||
|
@if (!Model.Titres.Any())
|
||||||
{
|
{
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
Aucun artiste ni titre ne correspond a votre recherche.
|
<p>Aucun titre n'a été trouvé.</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
@if (Model.Artistes.Any())
|
|
||||||
{
|
|
||||||
<h2 class="h4 mt-4">Artistes</h2>
|
|
||||||
|
|
||||||
@foreach (var artiste in Model.Artistes)
|
@foreach (var titre in Model.Titres)
|
||||||
{
|
{
|
||||||
<div class="my-3">
|
<div class="d-flex align-items-start my-3">
|
||||||
|
<a asp-controller="Titre"
|
||||||
|
asp-action="Index"
|
||||||
|
asp-route-id="@titre.IdTitre"
|
||||||
|
class="me-3 text-black">
|
||||||
|
<img src="@titre.UrlJaquette" alt="@titre.Libelle" width="70" height="70" class="object-fit-cover" loading="lazy" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="justify-content-center d-flex flex-column">
|
||||||
|
<div>
|
||||||
<a asp-controller="Artiste"
|
<a asp-controller="Artiste"
|
||||||
asp-action="Index"
|
asp-action="Index"
|
||||||
asp-route-nom="@artiste.Nom">
|
asp-route-nom="@titre.Artiste.Nom">
|
||||||
@artiste.Nom
|
@titre.Artiste.Nom
|
||||||
</a>
|
</a>
|
||||||
<span class="text-muted">(@artiste.NombreDeTitres titre(s))</span>
|
-
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (Model.Titres.Any())
|
|
||||||
{
|
|
||||||
<h2 class="h4 mt-4">Titres</h2>
|
|
||||||
|
|
||||||
@foreach (var titre in Model.Titres)
|
|
||||||
{
|
|
||||||
<div class="d-flex align-items-start my-3">
|
|
||||||
<a asp-controller="Titre"
|
<a asp-controller="Titre"
|
||||||
asp-action="Details"
|
asp-action="Index"
|
||||||
asp-route-id="@titre.IdTitre"
|
asp-route-id="@titre.IdTitre">
|
||||||
class="me-3 text-black">
|
@titre.Libelle
|
||||||
<img src="@titre.UrlJaquette" alt="@titre.Libelle" width="70" height="70" class="object-fit-cover" loading="lazy"/>
|
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="justify-content-center d-flex flex-column">
|
|
||||||
<div>
|
|
||||||
<a asp-controller="Artiste"
|
|
||||||
asp-action="Index"
|
|
||||||
asp-route-nom="@titre.ArtisteNom">
|
|
||||||
@titre.ArtisteNom
|
|
||||||
</a>
|
|
||||||
-
|
|
||||||
<a asp-controller="Titre"
|
|
||||||
asp-action="Details"
|
|
||||||
asp-route-id="@titre.IdTitre">
|
|
||||||
@titre.Libelle
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
Duree : @TimeSpan.FromSeconds(titre.Duree).ToString(@"mm\:ss")
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
}
|
<div>
|
||||||
|
Duree : @TimeSpan.FromSeconds(titre.Duree).ToString(@"mm\:ss")
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user