diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..b70e488 --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,62 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "altcover.global": { + "version": "8.8.74", + "commands": [ + "altcover" + ], + "rollForward": false + }, + "cake.tool": { + "version": "2.3.0", + "commands": [ + "dotnet-cake" + ], + "rollForward": false + }, + "dotnet-format": { + "version": "5.1.250801", + "commands": [ + "dotnet-format" + ], + "rollForward": false + }, + "csharpier": { + "version": "0.28.2", + "commands": [ + "dotnet-csharpier" + ], + "rollForward": false + }, + "swashbuckle.aspnetcore.cli": { + "version": "6.6.2", + "commands": [ + "swagger" + ], + "rollForward": false + }, + "microsoft.tye": { + "version": "0.11.0-alpha.22111.1", + "commands": [ + "tye" + ], + "rollForward": false + }, + "dotnet-ef": { + "version": "8.0.7", + "commands": [ + "dotnet-ef" + ], + "rollForward": false + }, + "dotnet-outdated-tool": { + "version": "4.6.4", + "commands": [ + "dotnet-outdated" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/.csharpierrc.yaml b/.csharpierrc.yaml new file mode 100644 index 0000000..51dd694 --- /dev/null +++ b/.csharpierrc.yaml @@ -0,0 +1,8 @@ +# https://csharpier.com/docs/Configuration +printWidth: 120 +useTabs: false +tabWidth: 4 +preprocessorSymbolSets: + - "" + - "DEBUG" + - "DEBUG,CODE_STYLE" diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..5b49199 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,7 @@ +# https://containers.dev/guide/dockerfile#dockerfile +# https://github.com/devcontainers/images/tree/main/src/dotnet +# https://github.com/devcontainers/templates/tree/main/src/dotnet + +FROM mcr.microsoft.com/devcontainers/dotnet:latest + +# Add additional commands diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..fdbe284 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,130 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet +// https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration/setting-up-your-dotnet-project-for-codespaces +// https://github.com/microsoft/vscode-remote-try-dotnet +// https://dev.to/this-is-learning/set-up-github-codespaces-for-a-net-8-application-5999 +// https://audacioustux.notion.site/Getting-Started-with-Devcontainer-c727dbf9d56f4d6b9b0ef87b3111693f +{ + "name": "Movie Search Application", + // use existing dev container templates. More info: https://containers.dev/guide/dockerfile, https://containers.dev/templates + //"image": "mcr.microsoft.com/devcontainers/dotnet:1-7.0", + // use a Dockerfile file. More info: https://containers.dev/guide/dockerfile#dockerfile + // "build": { + // // Path is relative to the devcontainer.json file. + // "dockerfile": "Dockerfile" + // }, + // using a Dockerfile with Docker Compose, https://containers.dev/guide/dockerfile#docker-compose-image + "dockerComposeFile": "docker-compose.yaml", + "service": "devcontainer", + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", + // Features to add to the dev container. More info: https://containers.dev/features. + "features": { + // https://github.com/devcontainers/features/tree/main/src/dotnet#dotnet-cli-dotnet + "ghcr.io/devcontainers/features/dotnet:2": { + // this version should be matched with global.json .net version for working vscode IntelliSense correctly + "version": "8.0.303", + "additionalVersions": "latest, 8.0.303", + "aspNetCoreRuntimeVersions": "latest" + }, + // https://github.com/devcontainers/features/tree/main/src/github-cli + "ghcr.io/devcontainers/features/github-cli:1": { + "version": "2" + }, + // https://github.com/devcontainers/features/tree/main/src/powershell + "ghcr.io/devcontainers/features/powershell:1": { + "version": "latest" + }, + // // https://github.com/devcontainers/features/tree/main/src/kubectl-helm-minikube + // "ghcr.io/devcontainers/features/kubectl-helm-minikube:1": {}, + // // https://github.com/devcontainers/features/tree/main/src/terraform + // "ghcr.io/devcontainers/features/terraform:1": {}, + // https://github.com/devcontainers/features/tree/main/src/docker-in-docker + // https://devopscube.com/run-docker-in-docker/ + "ghcr.io/devcontainers/features/docker-in-docker:2": { + "dockerDashComposeVersion": "v2" + }, + // https://github.com/devcontainers/features/tree/main/src/node + "ghcr.io/devcontainers/features/node:1": {}, + "ghcr.io/devcontainers/features/git:1": {}, + // https://github.com/devcontainers/features/tree/main/src/common-utils + "ghcr.io/devcontainers/features/common-utils:2": { + "configureZshAsDefaultShell": true + } + }, + // Configure tool-specific properties. + "customizations": { + // Configure properties specific to VS Code. + "vscode": { + "settings": { + "git.autofetch": true, + "files.autoSave": "onFocusChange", + "editor.formatOnSave": true, + "editor.suggest.snippetsPreventQuickSuggestions": false, + "explorer.autoReveal": true, + "resmon.show.cpufreq": false, + "dotnet.defaultSolution": "movie-search.sln", + "dotnet.server.startTimeout": 60000, + "omnisharp.projectLoadTimeout": 60, + "workbench.colorTheme": "Visual Studio Light", + "workbench.iconTheme": "material-icon-theme", + "editor.minimap.enabled": false, + "editor.fontFamily": "'MesloLGM Nerd Font', 'Droid Sans Mono', 'monospace', 'Droid Sans Fallback', 'Consolas'", + "editor.fontSize": 14, + "explorer.confirmDelete": false, + "terminal.integrated.defaultProfile.windows": "PowerShell", + "terminal.integrated.defaultProfile.linux": "zsh", + "powershell.cwd": "~", + "terminal.external.windowsExec": "%LOCALAPPDATA%\\Microsoft\\WindowsApps\\pwsh.exe", + "editor.defaultFormatter": "esbenp.prettier-vscode", + "[jsonc]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "editor.smoothScrolling": true, + "editor.wordWrap": "on", + "editor.guides.bracketPairs": true, + "explorer.experimental.fileNesting.enabled": true, + "[csharp]": { + "editor.defaultFormatter": "csharpier.csharpier-vscode", + "editor.formatOnSave": true + } + }, + "extensions": [ + "streetsidesoftware.code-spell-checker", + "ms-dotnettools.csdevkit", + "mutantdino.resourcemonitor", + "humao.rest-client", + "dzhavat.bracket-pair-toggler", + "ms-azuretools.vscode-docker", + "vivaxy.vscode-conventional-commits", + "emmanuelbeziat.vscode-great-icons", + "ms-vscode.vs-keybindings", + "GitHub.vscode-github-actions", + "PKief.material-icon-theme", + "EditorConfig.EditorConfig", + "DavidAnson.vscode-markdownlint", + "IBM.output-colorizer", // Colorize your output/test logs + "emmanuelbeziat.vscode-great-icons", + "esbenp.prettier-vscode", + "vscode-icons-team.vscode-icons", + "redhat.vscode-yaml" + ] + } + }, + "hostRequirements": { + "cpus": 4, + "memory": "8gb", + "storage": "32gb" + }, + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [5000, 5001], + // "portsAttributes": { + // "5001": { + // "protocol": "https" + // } + // } + // https://containers.dev/implementors/json_reference/#lifecycle-scripts + "updateContentCommand": "chmod +x .devcontainer/scripts/update.sh", + "postCreateCommand": "chmod +x .devcontainer/scripts/post-create.sh" + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.devcontainer/docker-compose.yaml b/.devcontainer/docker-compose.yaml new file mode 100644 index 0000000..8d8adc2 --- /dev/null +++ b/.devcontainer/docker-compose.yaml @@ -0,0 +1,26 @@ +# https://containers.dev/guide/dockerfile#docker-compose-image +# https://containers.dev/guide/dockerfile#docker-compose-dockerfile +services: + devcontainer: + build: + context: . + volumes: + - ../..:/workspaces:rw,cached + init: true + command: sleep infinity + +# services: +# devcontainer: +# image: mcr.microsoft.com/devcontainers/dotnet:3.1.0 +# volumes: +# - ../..:/workspaces:cached +# network_mode: service:db +# command: sleep infinity + +# db: +# image: postgres:latest +# restart: unless-stopped +# environment: +# POSTGRES_PASSWORD: postgres +# POSTGRES_USER: postgres +# POSTGRES_DB: postgres diff --git a/.devcontainer/scripts/post-create.sh b/.devcontainer/scripts/post-create.sh new file mode 100755 index 0000000..e84ede0 --- /dev/null +++ b/.devcontainer/scripts/post-create.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -eax + +# Run the setup_fonts.sh script +./setup-fonts.sh diff --git a/.devcontainer/scripts/setup-fonts.sh b/.devcontainer/scripts/setup-fonts.sh new file mode 100644 index 0000000..cda8f12 --- /dev/null +++ b/.devcontainer/scripts/setup-fonts.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# Download MesloLGM Nerd Font +wget https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.2/Meslo.zip -O MesloLGM.zip + +# Extract the font files +unzip MesloLGM.zip -d MesloLGM + +# Create the fonts directory if it doesn't exist +mkdir -p ~/.local/share/fonts + +# Move the font files to the fonts directory +mv MesloLGM/*.ttf ~/.local/share/fonts/ + +# Update the font cache +fc-cache -fv + +# Clean up +rm -rf MesloLGM.zip MesloLGM + +# Verify installation +fc-list | grep "MesloLGM" + +echo "Font setup completed." diff --git a/.devcontainer/scripts/update.sh b/.devcontainer/scripts/update.sh new file mode 100755 index 0000000..99a8bb9 --- /dev/null +++ b/.devcontainer/scripts/update.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +set -eax \ No newline at end of file diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..7c322a9 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,28 @@ +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.idea +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/secrets.dev.yaml +**/values.dev.yaml +**/bin/ +**/obj/ +**/out/ +LICENSE +README.md +local/data/ +portainer-data/ diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d238d3a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,555 @@ +# https://editorconfig.org +# https://www.jetbrains.com/help/resharper/Using_EditorConfig.html +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/configuration-files +# When opening a file, EditorConfig plugins look for a file named .editorconfig in the directory of the opened file and in every parent directory. A search for .editorconfig files will stop if the root filepath is reached or an EditorConfig file with `root=true` is found. + +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +################################################################################## +## https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ +## https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ +## Microsoft Rules +## + +# C# files +[*.{cs, cshtml}] +indent_size = 4 + +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_within_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_labels = one_less_than_current + +# avoid this. unless absolutely necessary +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion + +# only use var when it's obvious what the variable type is +# csharp_style_var_for_built_in_types = false:none +# csharp_style_var_when_type_is_apparent = false:none +# csharp_style_var_elsewhere = false:suggestion + +# use language keywords instead of BCL types +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +# name all constant fields using PascalCase +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style + +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.required_modifiers = const + +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static + +dotnet_naming_style.static_prefix_style.required_prefix = s_ +dotnet_naming_style.static_prefix_style.capitalization = camel_case + +# internal and private fields should be _camelCase +dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion +dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields +dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style + +dotnet_naming_symbols.private_internal_fields.applicable_kinds = field +dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal + +dotnet_naming_style.camel_case_underscore_style.required_prefix = _ +dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case + +# Code style defaults +dotnet_sort_system_directives_first = true +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = false + +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion + +# Expression-bodied members +csharp_style_expression_bodied_methods = false:none +csharp_style_expression_bodied_constructors = false:none +csharp_style_expression_bodied_operators = false:none +csharp_style_expression_bodied_properties = true:none +csharp_style_expression_bodied_indexers = true:none +csharp_style_expression_bodied_accessors = true:none + +# Pattern matching +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion + +# Null checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion + +# 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_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 + +# CA1305: Specify IFormatProvider +dotnet_diagnostic.ca1305.severity = None + +# CA1063: Implement IDisposable correctly +dotnet_diagnostic.ca1063.severity = None + +# CA2201: Do not raise reserved exception types +dotnet_diagnostic.ca2201.severity = Suggestion + +# CA1848: Use the LoggerMessage delegates +dotnet_diagnostic.ca1848.severity = Suggestion + +# CA1810: Initialize reference type static fields inline +dotnet_diagnostic.ca1810.severity = Suggestion + +# CA1725: Parameter names should match base declaration +dotnet_diagnostic.ca1725.severity = Suggestion + +# https://csharpier.com/docs/IntegratingWithLinters#code-analysis-rules +dotnet_diagnostic.ide0055.severity = none + +################################################################################## +# https://jetbrains.com.xy2401.com/help/resharper/EditorConfig_Index.html +# https://jetbrains.com.xy2401.com/help/resharper/Reference__Code_Inspections_CSHARP.html +## Resharper + +# ReSharper properties +resharper_align_linq_query = true +resharper_align_multiline_array_and_object_initializer = true +resharper_align_multiline_binary_patterns = true +resharper_align_multiline_expression = true +resharper_align_multiline_extends_list = true +resharper_align_multiline_parameter = true +resharper_align_multiline_property_pattern = true +resharper_align_multiline_switch_expression = true +resharper_align_multiple_declaration = true +resharper_align_multline_type_parameter_constrains = true +resharper_align_multline_type_parameter_list = true +resharper_align_tuple_components = true +resharper_braces_for_for = required_for_multiline +resharper_braces_for_foreach = required_for_multiline +resharper_braces_for_ifelse = required_for_multiline +resharper_csharp_alignment_tab_fill_style = optimal_fill +resharper_csharp_indent_type_constraints = false +resharper_csharp_int_align_fix_in_adjacent = false +resharper_csharp_outdent_commas = true +resharper_csharp_stick_comment = false +resharper_csharp_wrap_after_declaration_lpar = true +resharper_csharp_wrap_after_invocation_lpar = true +resharper_csharp_wrap_arguments_style = chop_if_long +resharper_csharp_wrap_before_declaration_rpar = true +resharper_csharp_wrap_before_first_type_parameter_constraint = true +resharper_csharp_wrap_before_ternary_opsigns = false +resharper_csharp_wrap_extends_list_style = chop_if_long +resharper_csharp_wrap_multiple_declaration_style = wrap_if_long +resharper_csharp_wrap_multiple_type_parameter_constraints_style = chop_always +resharper_csharp_wrap_parameters_style = chop_if_long +resharper_enforce_line_ending_style = true +resharper_indent_anonymous_method_block = true +resharper_indent_braces_inside_statement_conditions = false +resharper_indent_nested_fixed_stmt = true +resharper_indent_nested_foreach_stmt = true +resharper_indent_nested_for_stmt = true +resharper_indent_nested_lock_stmt = true +resharper_indent_nested_usings_stmt = true +resharper_indent_nested_while_stmt = true +resharper_keep_existing_declaration_block_arrangement = true +resharper_keep_existing_declaration_parens_arrangement = false +resharper_keep_existing_embedded_arrangement = false +resharper_keep_existing_embedded_block_arrangement = true +resharper_keep_existing_enum_arrangement = true +resharper_keep_existing_initializer_arrangement = false +resharper_keep_existing_invocation_parens_arrangement = false +resharper_keep_existing_property_patterns_arrangement = false +resharper_keep_existing_switch_expression_arrangement = false +resharper_max_array_initializer_elements_on_line = 700 +resharper_max_formal_parameters_on_line = 500 +resharper_max_invocation_arguments_on_line = 700 +resharper_new_line_before_while = true +resharper_place_attribute_on_same_line = false +resharper_place_linq_into_on_new_line = false +resharper_place_simple_case_statement_on_same_line = if_owner_is_single_line +resharper_place_simple_property_pattern_on_single_line = false +resharper_show_autodetect_configure_formatting_tip = false +resharper_space_within_single_line_array_initializer_braces = false +resharper_trailing_comma_in_multiline_lists = true +resharper_trailing_comma_in_singleline_lists = true +resharper_use_heuristics_for_body_style = false +resharper_use_indent_from_vs = false +resharper_use_roslyn_logic_for_evident_types = true +resharper_wrap_array_initializer_style = chop_always +resharper_wrap_chained_binary_expressions = chop_if_long +resharper_wrap_chained_binary_patterns = chop_if_long +resharper_wrap_chained_method_calls = chop_if_long +resharper_wrap_for_stmt_header_style = wrap_if_long +resharper_wrap_switch_expression = chop_if_long +resharper_wrap_verbatim_interpolated_strings = chop_if_long + +# ReSharper inspection severities +resharper_arrange_accessor_owner_body_highlighting = none +resharper_arrange_redundant_parentheses_highlighting = hint +resharper_arrange_type_member_modifiers_highlighting = hint +resharper_arrange_type_modifiers_highlighting = hint +resharper_check_namespace_highlighting = none +resharper_enforce_if_statement_braces_highlighting = hint +resharper_inconsistent_naming_highlighting = suggestion +resharper_static_member_in_generic_type_highlighting = none +resharper_suggest_var_or_type_built_in_types_highlighting = hint +resharper_suggest_var_or_type_elsewhere_highlighting = hint +resharper_suggest_var_or_type_simple_types_highlighting = hint +resharper_web_config_module_not_resolved_highlighting = warning +resharper_web_config_type_not_resolved_highlighting = warning +resharper_web_config_wrong_module_highlighting = warning + + +################################################################################## +## https://github.com/DotNetAnalyzers/StyleCopAnalyzers/tree/master/documentation +## https://documentation.help/StyleCop/StyleCop.html +## StyleCop.Analyzers +## + +# Using directive should appear within a namespace declaration +dotnet_diagnostic.sa1200.severity = None + +# Generic type parameter documentation should have text. +dotnet_diagnostic.sa1622.severity = None + +# XML comment analysis is disabled due to project configuration +dotnet_diagnostic.sa0001.severity = None + +# The file header is missing or not located at the top of the file +dotnet_diagnostic.sa1633.severity = None + +# Use string.Empty for empty strings +dotnet_diagnostic.sa1122.severity = None + +# Variable '_' should begin with lower-case letter +dotnet_diagnostic.sa1312.severity = None + +# Parameter '_' should begin with lower-case letter +dotnet_diagnostic.sa1313.severity = None + +# Elements should be documented +dotnet_diagnostic.sa1600.severity = None + +# Prefix local calls with this +dotnet_diagnostic.sa1101.severity = None + +# 'public' members should come before 'private' members +dotnet_diagnostic.sa1202.severity = None + +# Comments should contain text +dotnet_diagnostic.sa1120.severity = None + +# Constant fields should appear before non-constant fields +dotnet_diagnostic.sa1203.severity = None + +# Field '_blah' should not begin with an underscore +dotnet_diagnostic.sa1309.severity = None + +# Use trailing comma in multi-line initializers +dotnet_diagnostic.sa1413.severity = None + +# A method should not follow a class +dotnet_diagnostic.sa1201.severity = None + +# Elements should be separated by blank line +dotnet_diagnostic.sa1516.severity = None + +# The parameter spans multiple lines +dotnet_diagnostic.sa1118.severity = None + +# Static members should appear before non-static members +dotnet_diagnostic.sa1204.severity = None + +# Put constructor initializers on their own line +dotnet_diagnostic.sa1128.severity = None + +# Opening braces should not be preceded by blank line +dotnet_diagnostic.sa1509.severity = None + +# The parameter should begin on the line after the previous parameter +dotnet_diagnostic.sa1115.severity = None + +# File name should match first type name +dotnet_diagnostic.sa1649.severity = None + +# File may only contain a single type +dotnet_diagnostic.sa1402.severity = None + +# Enumeration items should be documented +dotnet_diagnostic.sa1602.severity = None + +# Element should not be on a single line +dotnet_diagnostic.sa1502.severity = None + +# Closing parenthesis should not be preceded by a space +dotnet_diagnostic.sa1009.severity = None + +# Closing parenthesis should be on line of last parameter +dotnet_diagnostic.sa1111.severity = None + +# Braces should not be ommitted +dotnet_diagnostic.sa1503.severity = None + +dotnet_diagnostic.sa1401.severity = None + +# The parameters to a C# method or indexer call or declaration are not all on the same line or each on a separate line. +# dotnet_diagnostic.SA1117.severity = Suggestion + +# The parameters to a C# method or indexer call or declaration span across multiple lines, but the first parameter does not start on the line after the opening bracket. +# dotnet_diagnostic.SA1116.severity = Suggestion + +# A C# partial element is missing a documentation header. +dotnet_diagnostic.sa1601.severity = None + +# A tag within a C# element’s documentation header is empty. +dotnet_diagnostic.sa1614.severity = None + +# A C# element is missing documentation for its return value. +dotnet_diagnostic.sa1615.severity = None + +# The tag within a C# element’s documentation header is empty. +dotnet_diagnostic.sa1616.severity = None + +# An opening brace within a C# element is not spaced correctly. +dotnet_diagnostic.sa1012.severity = Suggestion + +# A closing brace within a C# element is not spaced correctly. +dotnet_diagnostic.sa1013.severity = Suggestion + +# A call to an instance member of the local class or a base class is not prefixed with 'this.', within a C# code file. +dotnet_diagnostic.sa1101.severity = None + +# The keywords within the declaration of an element do not follow a standard ordering scheme. +dotnet_diagnostic.SA1206.severity = None + +################################################################################## +## https://github.com/meziantou/Meziantou.Analyzer/tree/main/docs +## Meziantou.Analyzer + +# MA0004: Use Task.ConfigureAwait(false) +dotnet_diagnostic.ma0004.severity = Suggestion + +# MA0049: Type name should not match containing namespace +dotnet_diagnostic.ma0049.severity = Suggestion + +# MA0048: File name must match type name +dotnet_diagnostic.ma0048.severity = Suggestion + +# MA0051: Method is too long +dotnet_diagnostic.ma0051.severity = Suggestion + +# https://www.meziantou.net/string-comparisons-are-harder-than-it-seems.htm +# MA0006 - Use String.Equals instead of equality operator +dotnet_diagnostic.ma0006.severity = Suggestion + +# MA0002 - IEqualityComparer or IComparer is missing +dotnet_diagnostic.ma0002.severity = Suggestion + +# MA0001 - StringComparison is missing +dotnet_diagnostic.ma0001.severity = Suggestion + +# https://cezarypiatek.github.io/post/async-analyzers-p2/#13-pass-cancellation-token +# MA0040: Specify a cancellation token +dotnet_diagnostic.ma0032.severity = Suggestion + +# https://cezarypiatek.github.io/post/async-analyzers-p2/#13-pass-cancellation-token +# MA0040: Flow the cancellation token when available +dotnet_diagnostic.ma0040.severity = Suggestion + +# https://cezarypiatek.github.io/post/async-analyzers-p2/#14-using-cancellation-token-with-iasyncenumerable +# MA0079: Use a cancellation token using .WithCancellation() +dotnet_diagnostic.ma0079.severity = Suggestion + +# https://cezarypiatek.github.io/post/async-analyzers-p2/#14-using-cancellation-token-with-iasyncenumerable +# MA0080: Use a cancellation token using .WithCancellation() +dotnet_diagnostic.ma0080.severity = Suggestion + +# MA0047 - Declare types in namespaces +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0047.md +dotnet_diagnostic.ma0047.severity = Suggestion + +# MA0015 - Specify the parameter name in ArgumentException +dotnet_diagnostic.ma0015.severity = Suggestion + +################################################################################## +## http://pihrt.net/Roslynator/Analyzers +## http://pihrt.net/Roslynator/Refactorings +## https://github.com/JosefPihrt/Roslynator/blob/main/docs/Configuration.md +## Roslynator + +# RCS1036 - Remove redundant empty line. +dotnet_diagnostic.rcs1036.severity = None + +# RCS1037 - Remove trailing white-space. +dotnet_diagnostic.rcs1037.severity = None + +# RCS1194: Implement exception constructors +dotnet_diagnostic.rcs1194.severity = None + +# https://cezarypiatek.github.io/post/async-analyzers-p1/#1-redundant-asyncawait +# RCS1174: Remove redundant async/await. +dotnet_diagnostic.rcs1174.severity = error + +# https://cezarypiatek.github.io/post/async-analyzers-p2/#10-returning-null-from-a-task-returning-method +# RCS1210: Return Task.FromResult instead of returning null. +dotnet_diagnostic.rcs1210.severity = error + +# https://cezarypiatek.github.io/post/async-analyzers-p2/#9-missing-configureawaitbool +# RCS1090: Call 'ConfigureAwait(false)'. +dotnet_diagnostic.rcs1090.severity = Suggestion + +# https://cezarypiatek.github.io/post/async-analyzers-p2/#11-asynchronous-method-names-should-end-with-async +#RCS1046: Asynchronous method name should end with 'Async'. +dotnet_diagnostic.rcs1046.severity = Suggestion + +# https://cezarypiatek.github.io/post/async-analyzers-p2/#12-non-asynchronous-method-names-shouldnt-end-with-async +# RCS1047: Non-asynchronous method name should not end with 'Async'. +dotnet_diagnostic.rcs1047.severity = error + +# https://github.com/JosefPihrt/Roslynator/blob/master/docs/analyzers/RCS1174.md +# RCS1174: Remove redundant async/await +dotnet_diagnostic.rcs1174.severity = Suggestion + + + +################################################################################## +## https://github.com/semihokur/asyncfixer +## AsyncFixer01 +## + +# https://cezarypiatek.github.io/post/async-analyzers-p1/#1-redundant-asyncawait +# AsyncFixer01: Unnecessary async/await usage +dotnet_diagnostic.asyncfixer01.severity = Suggestion + +# https://cezarypiatek.github.io/post/async-analyzers-p1/#2-calling-synchronous-method-inside-the-async-method +# AsyncFixer02: Long-running or blocking operations inside an async method +dotnet_diagnostic.asyncfixer02.severity = error + +# https://cezarypiatek.github.io/post/async-analyzers-p1/#3-async-void-method +# AsyncFixer03: Fire & forget async void methods +dotnet_diagnostic.asyncfixer03.severity = error + +# https://cezarypiatek.github.io/post/async-analyzers-p1/#6-not-awaited-task-inside-the-using-block +# AsyncFixer04: Fire & forget async call inside a using block +dotnet_diagnostic.asyncfixer04.severity = error + + +################################################################################## +## https://github.com/microsoft/vs-threading +## Microsoft.VisualStudio.Threading.Analyzers +## + +# https://cezarypiatek.github.io/post/async-analyzers-p1/#2-calling-synchronous-method-inside-the-async-method +# VSTHRD103: Call async methods when in an async method +dotnet_diagnostic.vsthrd103.severity = Suggestion + + +# https://cezarypiatek.github.io/post/async-analyzers-p1/#3-async-void-method +# VSTHRD100: Avoid async void methods +dotnet_diagnostic.vsthrd100.severity = error + +# https://cezarypiatek.github.io/post/async-analyzers-p1/#4-unsupported-async-delegates +# VSTHRD101: Avoid unsupported async delegates +dotnet_diagnostic.vsthrd101.severity = error + +# https://cezarypiatek.github.io/post/async-analyzers-p1/#5-not-awaited-task-within-using-expression +# VSTHRD107: Await Task within using expression +dotnet_diagnostic.vsthrd107.severity = error + +# https://cezarypiatek.github.io/post/async-analyzers-p1/#7-unobserved-result-of-asynchronous-method +# VSTHRD110: Observe result of async calls +dotnet_diagnostic.vsthrd110.severity = Suggestion + +# https://cezarypiatek.github.io/post/async-analyzers-p2/#8-synchronous-waits +# VSTHRD002: Avoid problematic synchronous waits +dotnet_diagnostic.vsthrd002.severity = Suggestion + +# https://cezarypiatek.github.io/post/async-analyzers-p2/#9-missing-configureawaitbool +# VSTHRD111: Use ConfigureAwait(bool) +dotnet_diagnostic.vsthrd111.severity = Suggestion + +# https://cezarypiatek.github.io/post/async-analyzers-p2/#10-returning-null-from-a-task-returning-method +# VSTHRD114: Avoid returning a null Task +dotnet_diagnostic.vsthrd114.severity = error + +# https://cezarypiatek.github.io/post/async-analyzers-p2/#11-asynchronous-method-names-should-end-with-async +# VSTHRD200: Use "Async" suffix for async methods +dotnet_diagnostic.vsthrd200.severity = Suggestion + +# https://cezarypiatek.github.io/post/async-analyzers-p2/#12-non-asynchronous-method-names-shouldnt-end-with-async +# VSTHRD200: Use "Async" suffix for async methods +dotnet_diagnostic.vsthrd200.severity = Suggestion + + +################################################################################## +## https://github.com/hvanbakel/Asyncify-CSharp +## Asyncify +## + +# https://cezarypiatek.github.io/post/async-analyzers-p2/#8-synchronous-waits +# AsyncifyInvocation: Use Task Async +dotnet_diagnostic.asyncifyinvocation.severity = error + +# https://cezarypiatek.github.io/post/async-analyzers-p2/#8-synchronous-waits +# AsyncifyVariable: Use Task Async +dotnet_diagnostic.asyncifyvariable.severity = error + + +################################################################################## +## https://github.com/cezarypiatek/ExceptionAnalyzer + +dotnet_diagnostic.ex001.severity = Suggestion + +[*.{appxmanifest,asax,ascx,aspx,axaml,build,c,c++,cc,cginc,compute,cp,cpp,cs,cshtml,cu,cuh,cxx,dtd,fs,fsi,fsscript,fsx,fx,fxh,h,hh,hlsl,hlsli,hlslinc,hpp,hxx,inc,inl,ino,ipp,master,ml,mli,mpp,mq4,mq5,mqh,nuspec,paml,razor,resw,resx,shader,skin,tpp,usf,ush,vb,xaml,xamlx,xoml,xsd}] +indent_style = space +indent_size = 4 +tab_width = 4 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.github/workflows/build-dot-net.yml b/.github/workflows/build-dot-net.yml index 10b16eb..2381e1d 100644 --- a/.github/workflows/build-dot-net.yml +++ b/.github/workflows/build-dot-net.yml @@ -2,22 +2,22 @@ name: build-dot-net on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] jobs: build: - runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Setup .NET - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 7.0.x - - name: Restore dependencies - run: dotnet restore - - name: Build - run: dotnet build --no-restore + - uses: actions/checkout@v3 + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: "8.0.x" + + - name: Restore dependencies + run: dotnet restore + - name: Build + run: dotnet build --no-restore diff --git a/.gitignore b/.gitignore index 12f5774..f699e6f 100644 --- a/.gitignore +++ b/.gitignore @@ -23,7 +23,6 @@ mono_crash.* [Rr]eleases/ x64/ x86/ -[Ww][Ii][Nn]32/ [Aa][Rr][Mm]/ [Aa][Rr][Mm]64/ bld/ @@ -62,9 +61,6 @@ project.lock.json project.fragment.lock.json artifacts/ -# ASP.NET Scaffolding -ScaffoldingReadMe.txt - # StyleCop StyleCopReport.xml @@ -90,7 +86,6 @@ StyleCopReport.xml *.tmp_proj *_wpftmp.csproj *.log -*.tlog *.vspscc *.vssscc .builds @@ -142,11 +137,6 @@ _TeamCity* .axoCover/* !.axoCover/settings.json -# Coverlet is a free, cross platform Code Coverage Tool -coverage*.json -coverage*.xml -coverage*.info - # Visual Studio code coverage results *.coverage *.coveragexml @@ -196,7 +186,7 @@ PublishScripts/ *.nupkg # NuGet Symbol Packages *.snupkg -# The packages folder can be ignored because of Package Restore +# The packages folder can be ignored because of Package Reecommerce **/[Pp]ackages/* # except build/, which is used as an MSBuild target. !**/[Pp]ackages/build/ @@ -206,9 +196,6 @@ PublishScripts/ *.nuget.props *.nuget.targets -# Nuget personal access tokens and Credentials -# nuget.config - # Microsoft Azure Build Output csx/ *.build.csdef @@ -217,10 +204,8 @@ csx/ ecf/ rcf/ -# Windows Store app package directories and files AppPackages/ BundleArtifacts/ -Package.StoreAssociation.xml _pkginfo.txt *.appx *.appxbundle @@ -362,112 +347,32 @@ MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ -# Fody - auto-generated XML schema -FodyWeavers.xsd - -# VS Code files for those working on multiple tools -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -*.code-workspace - -# Local History for Visual Studio Code -.history/ - -# Windows Installer files from build outputs -*.cab -*.msi -*.msix -*.msm -*.msp +.tye/ +.logs/ +.Log -# JetBrains Rider +out/ +**/.vagrant/ .idea/ -*.sln.iml - -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -*.code-workspace +local/data/ +portainer-data/ +docker.sock/ +localtime/ +coveragereport/ +CoverageResults/ +coverage.cobertura.xml +coverage.info # Local History for Visual Studio Code .history/ -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# AWS User-specific -.idea/**/aws.xml - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests +# Built Visual Studio Code Extensions +*.vsix -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser +# exclude sensitive env files +.env +.env.dev +.denv.prod +.env.catalogs +.env.customers +.env.identity \ No newline at end of file diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 0000000..c160a77 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +npx --no -- commitlint --edit ${1} diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..001bdf6 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +dotnet csharpier . && git add -A . diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a7330d5 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,33 @@ +{ + "git.autofetch": true, + "files.autoSave": "onFocusChange", + "editor.formatOnSave": true, + "editor.suggest.snippetsPreventQuickSuggestions": false, + "explorer.autoReveal": true, + "resmon.show.cpufreq": false, + "dotnet.defaultSolution": "movie-search.sln", + "dotnet.server.startTimeout": 60000, + "omnisharp.projectLoadTimeout": 60, + "workbench.colorTheme": "Visual Studio Light", + "workbench.iconTheme": "material-icon-theme", + "editor.minimap.enabled": false, + "editor.fontFamily": "'MesloLGM Nerd Font', 'Droid Sans Mono', 'monospace', 'Droid Sans Fallback', 'Consolas'", + "editor.fontSize": 14, + "explorer.confirmDelete": false, + "terminal.integrated.defaultProfile.windows": "PowerShell", + "terminal.integrated.defaultProfile.linux": "zsh", + "powershell.cwd": "~", + "terminal.external.windowsExec": "%LOCALAPPDATA%\\Microsoft\\WindowsApps\\pwsh.exe", + "editor.defaultFormatter": "esbenp.prettier-vscode", + "explorer.experimental.fileNesting.enabled": true, + "[jsonc]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "editor.smoothScrolling": true, + "editor.wordWrap": "on", + "editor.guides.bracketPairs": true, + "[csharp]": { + "editor.defaultFormatter": "ms-dotnettools.csharp", + "editor.formatOnSave": true + } +} diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..69b4242 --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ["@commitlint/config-conventional"], +}; diff --git a/deployments/docker-compose/docker-compose.yaml b/deployments/docker-compose/docker-compose.yaml index 836ed8d..55bab7a 100644 --- a/deployments/docker-compose/docker-compose.yaml +++ b/deployments/docker-compose/docker-compose.yaml @@ -1,4 +1,4 @@ -version: '3' +version: '3.9' services: movie.api: container_name: movie.api diff --git a/global.json b/global.json new file mode 100644 index 0000000..9e5db15 --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "8.0.303", + "rollForward": "latestFeature" + } +} diff --git a/movie-search.sln b/movie-search.sln index a778108..1a3fb60 100644 --- a/movie-search.sln +++ b/movie-search.sln @@ -29,27 +29,42 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MovieSearch.UnitTests", "te EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MovieSearch.EndToEndTests", "tests\MovieSearch.EndToEndTests\MovieSearch.EndToEndTests.csproj", "{13E831B9-FDFB-4873-B0A8-B131CFEB357F}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution items", "solution items", "{B2B37F1B-728F-4C54-9BB9-2B75A138D9F6}" + ProjectSection(SolutionItems) = preProject + .csharpierrc.yaml = .csharpierrc.yaml + .dockerignore = .dockerignore + .editorconfig = .editorconfig + .gitattributes = .gitattributes + .gitignore = .gitignore + commitlint.config.js = commitlint.config.js + global.json = global.json + movie-search.sln = movie-search.sln + nuget.config = nuget.config + package.json = package.json + readme.md = readme.md + stylecop.json = stylecop.json + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "deployments", "deployments", "{FA998ABA-B0B3-40F9-9941-0C3CBA15EADB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docker-compose", "docker-compose", "{ADF1E782-F7A1-46AF-8620-55CC8A82AEBD}" + ProjectSection(SolutionItems) = preProject + deployments\docker-compose\docker-compose.yaml = deployments\docker-compose\docker-compose.yaml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "k8s", "k8s", "{C3F744FD-F978-40A4-A884-F79B0C6B0F59}" + ProjectSection(SolutionItems) = preProject + deployments\k8s\delete-resources.sh = deployments\k8s\delete-resources.sh + deployments\k8s\deploy-local.sh = deployments\k8s\deploy-local.sh + deployments\k8s\movie-search-api.yaml = deployments\k8s\movie-search-api.yaml + deployments\k8s\movie-search-ingress.yaml = deployments\k8s\movie-search-ingress.yaml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {55EED072-1C18-46DA-B491-6971715C3912} = {778BA20A-056F-46DC-AB48-95259206D8BE} - {9F808F73-0D9D-4DC6-96CF-2CC9180ABF1E} = {778BA20A-056F-46DC-AB48-95259206D8BE} - {B40C04AA-D156-4289-8D7F-9463925BC222} = {9F808F73-0D9D-4DC6-96CF-2CC9180ABF1E} - {C0B80C21-9FA9-4C27-A9BB-92497A98307A} = {9F808F73-0D9D-4DC6-96CF-2CC9180ABF1E} - {F4F6EE60-D819-46E9-8CA2-69ECAEE04863} = {9F808F73-0D9D-4DC6-96CF-2CC9180ABF1E} - {4E3C0119-A28C-4FAC-9A72-170BE69B8F95} = {9F808F73-0D9D-4DC6-96CF-2CC9180ABF1E} - {7BC58153-8348-480E-B8BE-4ABFECE38309} = {55EED072-1C18-46DA-B491-6971715C3912} - {10EA36EB-5832-4C67-86BC-54CEF4AE5DD0} = {55EED072-1C18-46DA-B491-6971715C3912} - {B19EA418-80E2-42F8-8FB8-AB5277BB9E70} = {0A7114DC-EDF4-4E51-A0B3-E34FE66A46F6} - {A389E4BB-9E51-4124-8A22-691B12B66C73} = {0A7114DC-EDF4-4E51-A0B3-E34FE66A46F6} - {13E831B9-FDFB-4873-B0A8-B131CFEB357F} = {0A7114DC-EDF4-4E51-A0B3-E34FE66A46F6} - EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {B40C04AA-D156-4289-8D7F-9463925BC222}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B40C04AA-D156-4289-8D7F-9463925BC222}.Debug|Any CPU.Build.0 = Debug|Any CPU @@ -88,4 +103,23 @@ Global {13E831B9-FDFB-4873-B0A8-B131CFEB357F}.Release|Any CPU.ActiveCfg = Release|Any CPU {13E831B9-FDFB-4873-B0A8-B131CFEB357F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {B40C04AA-D156-4289-8D7F-9463925BC222} = {9F808F73-0D9D-4DC6-96CF-2CC9180ABF1E} + {C0B80C21-9FA9-4C27-A9BB-92497A98307A} = {9F808F73-0D9D-4DC6-96CF-2CC9180ABF1E} + {F4F6EE60-D819-46E9-8CA2-69ECAEE04863} = {9F808F73-0D9D-4DC6-96CF-2CC9180ABF1E} + {4E3C0119-A28C-4FAC-9A72-170BE69B8F95} = {9F808F73-0D9D-4DC6-96CF-2CC9180ABF1E} + {55EED072-1C18-46DA-B491-6971715C3912} = {778BA20A-056F-46DC-AB48-95259206D8BE} + {9F808F73-0D9D-4DC6-96CF-2CC9180ABF1E} = {778BA20A-056F-46DC-AB48-95259206D8BE} + {7BC58153-8348-480E-B8BE-4ABFECE38309} = {55EED072-1C18-46DA-B491-6971715C3912} + {10EA36EB-5832-4C67-86BC-54CEF4AE5DD0} = {55EED072-1C18-46DA-B491-6971715C3912} + {B19EA418-80E2-42F8-8FB8-AB5277BB9E70} = {0A7114DC-EDF4-4E51-A0B3-E34FE66A46F6} + {A389E4BB-9E51-4124-8A22-691B12B66C73} = {0A7114DC-EDF4-4E51-A0B3-E34FE66A46F6} + {13E831B9-FDFB-4873-B0A8-B131CFEB357F} = {0A7114DC-EDF4-4E51-A0B3-E34FE66A46F6} + {FA998ABA-B0B3-40F9-9941-0C3CBA15EADB} = {B2B37F1B-728F-4C54-9BB9-2B75A138D9F6} + {ADF1E782-F7A1-46AF-8620-55CC8A82AEBD} = {FA998ABA-B0B3-40F9-9941-0C3CBA15EADB} + {C3F744FD-F978-40A4-A884-F79B0C6B0F59} = {FA998ABA-B0B3-40F9-9941-0C3CBA15EADB} + EndGlobalSection EndGlobal diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..6ce9759 --- /dev/null +++ b/nuget.config @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..da573f6 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "movie-search-application", + "version": "1.0.0", + "description": "movie-search-application", + "scripts": { + "prepare": "husky install && dotnet tool restore", + "install-dev-cert-bash": "curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v vs2019 -l ~/vsdbg" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/mehdihadeli/movie-search-application.git" + }, + "author": "", + "license": "MIT", + "bugs": { + "url": "https://github.com/mehdihadeli/movie-search-application/issues" + }, + "homepage": "https://github.com/mehdihadeli/movie-search-application#readme", + "devDependencies": { + "@commitlint/cli": "^19.2.1", + "@commitlint/config-conventional": "^19.1.0", + "husky": "^9.0.11", + "npm-check-updates": "^16.14.18" + } +} diff --git a/readme.md b/readme.md index 16444e8..66d345c 100644 --- a/readme.md +++ b/readme.md @@ -2,13 +2,15 @@ [![Actions Status](https://github.com/mehdihadeli/movie-search-app/workflows/build-dot-net/badge.svg?branch=main)](https://github.com/mehdihadeli/movie-search-app/actions) +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/mehdihadeli/movie-search-application) + Movies Search Application is a search engine for searching movies and tv-shows with using two popular apis [TMDB](https://developers.themoviedb.org/3) and [Youtube API](https://developers.google.com/youtube/v3) for get all information about the movies and its related trailers with some other features. This app is based on .Net core and vertical slices architecture and I wrote some level of tests like [Unit Testing](./tests/MovieSearch.UnitTests), [Integration Testing](./tests/MovieSearch.IntegrationTests) and [End-To-End Testing](./tests/MovieSearch.EndToEndTests) for this application. ## Application Structure -In this application I used a [mediator pattern](https://dotnetcoretutorials.com/2019/04/30/the-mediator-pattern-in-net-core-part-1-whats-a-mediator/) with using [MediatR](https://github.com/jbogard/MediatR) library in my controllers for a clean and [thin controller](https://codeopinion.com/thin-controllers-cqrs-mediatr/), also instead of using a `application service` class because after some times our controller will depends to different services and this breaks single responsibility principle. We use mediator pattern to manage the delivery of messages to handlers. One of the advantages behind the [mediator pattern](https://lostechies.com/jimmybogard/2014/09/09/tackling-cross-cutting-concerns-with-a-mediator-pipeline/) is that it allows the application code to define a pipeline of activities for requests . For example in our controllers we create a command and send it to mediator and mediator will route our command to a specific command handler in application layer. +In this application I used a [mediator pattern](https://dotnetcoretutorials.com/2019/04/30/the-mediator-pattern-in-net-core-part-1-whats-a-mediator/) with using [MediatR](https://github.com/jbogard/MediatR) library in my controllers for a clean and [thin controller](https://codeopinion.com/thin-controllers-cqrs-mediatr/), also instead of using a `application service` class because after some times our controller will depends to different services and this breaks single responsibility principle. We use mediator pattern to manage the delivery of messages to handlers. One of the advantages behind the [mediator pattern](https://lostechies.com/jimmybogard/2014/09/09/tackling-cross-cutting-concerns-with-a-mediator-pipeline/) is that it allows the application code to define a pipeline of activities for requests . For example in our controllers we create a command and send it to mediator and mediator will route our command to a specific command handler in application layer. To support [Single Responsibility Principle](https://en.wikipedia.org/wiki/Single_responsibility_principle) and [Don't Repeat Yourself principles](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself), the implementation of cross-cutting concerns is done using the mediatr [pipeline behaviors](https://github.com/jbogard/MediatR/wiki/Behaviors) or creating a [mediatr decorators](https://lostechies.com/jimmybogard/2014/09/09/tackling-cross-cutting-concerns-with-a-mediator-pipeline/). @@ -26,23 +28,24 @@ Also here I used cqrs for decompose my features to very small parts that make ou - maximize performance, scalability and simplicity. - adding new feature to this mechanism is very easy without any breaking change in other part of our codes. New features only add code, we're not changing shared code and worrying about side effects. - easy to maintain and any changes only affect on one command or query (or a slice) and avoid any breaking changes on other parts -- it gives us better separation of concerns and cross cutting concern (with help of mediatr behavior pipelines) in our code instead of a big service class for doing a lot of things. +- it gives us better separation of concerns and cross cutting concern (with help of mediatr behavior pipelines) in our code instead of a big service class for doing a lot of things. +With using CQRS pattern, we cut each business functionality into some vertical slices, and inner each of this slices we have [technical folders structure](http://www.kamilgrzybek.com/design/feature-folders/) specific to that feature (command, handlers, infrastructure, repository, controllers, ...). In Our CQRS pattern each command/query handler is a separate slice. This is where you can reduce coupling between layers. Each handler can be a separated code unit, even copy/pasted. Thanks to that, we can tune down the specific method to not follow general conventions (e.g. use custom SQL query or even different storage). In a traditional layered architecture, when we change the core generic mechanism in one layer, it can impact all methods. -With using CQRS pattern, we cut each business functionality into some vertical slices, and inner each of this slices we have [technical folders structure](http://www.kamilgrzybek.com/design/feature-folders/) specific to that feature (command, handlers, infrastructure, repository, controllers, ...). In Our CQRS pattern each command/query handler is a separate slice. This is where you can reduce coupling between layers. Each handler can be a separated code unit, even copy/pasted. Thanks to that, we can tune down the specific method to not follow general conventions (e.g. use custom SQL query or even different storage). In a traditional layered architecture, when we change the core generic mechanism in one layer, it can impact all methods. +For checking `validation rules` we use two type of validation: -For checking `validation rules` we use two type of validation: - [Data Validation](http://www.kamilgrzybek.com/design/rest-api-data-validation/): Data validation verify data items which are coming to our application from external sources and check if theirs values are acceptable but Business rules validation is a more broad concept and more close to how business works and behaves. So it is mainly focused on behavior For implementing data validation I used [FluentValidation](https://github.com/FluentValidation/FluentValidation) library for cleaner validation also better separation of concern in my handlers for preventing mixing validation logic with orchestration logic in my handlers. - [Business Rules validation](http://www.kamilgrzybek.com/design/domain-model-validation/): I explicitly check all of the our business rules, inner my handlers and I will throw a customized exception based on the error that these errors should inherits from [AppException](./src/BuildingBlocks/BulidingBlocks/Exception/AppException.cs) class, because of these exceptions, occurs in application layer and we catch this exceptions in api layer with using [Hellang.Middleware.ProblemDetails](https://www.nuget.org/packages/Hellang.Middleware.ProblemDetails/) middleware and pass a correct status code to client. - + Examples of `data validation` : 1- Input Validation + - We want to ensure our Id is greater than zero. In bellow validator for our query as request we check that Id is greater than zero -``` csharp +```csharp public class FindMovieByIdQueryValidator : AbstractValidator { public FindMovieByIdQueryValidator() @@ -53,9 +56,10 @@ public class FindMovieByIdQueryValidator : AbstractValidator ``` 2- Business Rule Validation + - We want to check our database contains a movie with specific Id and if there is no movie with this Id, we throw a [MovieNotFoundException](./src/MovieSearch.Application/Movies/Exceptions/MovieNotFoundException.cs). -``` csharp +```csharp var movie = await _movieDbServiceClient.GetMovieByIdAsync(query.Id, cancellationToken); if (movie is null) @@ -64,7 +68,7 @@ if (movie is null) Also for handling exceptions and correct status codes in our web api response, I Used [Hellang.Middleware.ProblemDetails](https://www.nuget.org/packages/Hellang.Middleware.ProblemDetails/) package and I config and map all our needed exceptions and their corresponding status code in our Infrastructure layer and [AddInfrastructure](./src/MovieSearch.Infrastructure/Extensions.cs) method. -``` csharp +```csharp services.AddProblemDetails(x => { // Control when an exception is included @@ -98,7 +102,7 @@ In this Project I covered most of important tests like `Unit Testing`, `Integrat In this app for increasing performance we could use caching mechanism simply with implementing a interface `ICachePolicy<,>` and our object for caching, this will handle with a chancing pipeline on mediateR as cross cutting concern with name of `CachingBehavior`. For example for caching our `FindMovieByIdQuery` query we could use bellow code: -``` csharp +```csharp public class FindMovieByIdQuery : IQuery { public int Id { get; init; } @@ -114,6 +118,7 @@ public class FindMovieByIdQuery : IQuery } } ``` + ## Communication with External APIs In this application for communicating with external apis like [TMDB Apis](https://developers.themoviedb.org/3) and [Youtube Apis](https://developers.google.com/youtube/v3) I used a [Anti Corruption Layer](https://deviq.com/domain-driven-design/anti-corruption-layer) as a [mediator](https://dev.to/asarnaout/the-anti-corruption-layer-pattern-pcd) between our system domain model and external systems domain model. The reason why you might use an anti corruption layer is to create a little padding between subsystems so that they do not `leak into each other` too much. @@ -127,8 +132,8 @@ In our application I create an anti corruption class for `TMDB api` with name of In this anti corruption classes we also used some [resiliency mechanisms](https://medium.com/@emanuele.bucarelli/improve-resilience-in-the-net-application-80adda2c7710) like `Retry`, `Circuit-breaker`,`Timeout` and`Bulkhead` patterns for increasing our resiliency in calling third party apis and for doing this I used [Polly](https://github.com/App-vNext/Polly) library. - ## Technologies - Libraries + - ✔️ **[`.NET 6`](https://dotnet.microsoft.com/download)** - .NET Framework and .NET Core, including ASP.NET and ASP.NET Core - ✔️ **[`Newtonsoft.Json`](https://github.com/JamesNK/Newtonsoft.Json)** - Json.NET is a popular high-performance JSON framework for .NET - ✔️ **[`MVC Versioning API`](https://github.com/microsoft/aspnet-api-versioning)** - Set of libraries which add service API versioning to ASP.NET Web API, OData with ASP.NET Web API, and ASP.NET Core @@ -146,14 +151,14 @@ In this anti corruption classes we also used some [resiliency mechanisms](https: - ✔️ **[`NSubstitute`](https://nsubstitute.github.io/)** - A friendly substitute for .NET mocking libraries. - ✔️ **[`FluentAssertions`](https://github.com/fluentassertions/fluentassertions)** - A very extensive set of extension methods that allow you to more naturally specify the expected outcome of a TDD or BDD-style unit tests. - ## Configs + For using this app we need a [YouTube ApiKey](https://developers.google.com/youtube/v3/getting-started) also for using TMDB api we need a [TMDB Api Key](https://www.themoviedb.org/settings/api) (I put a api key for TMDB in setting file for test purpose). you should set your api keys in [appsettings.json](./src/MovieSearch.Api/appsettings.json) file in bellow section: -``` json +```json "TMDBOptions": { "BaseApiAddress": "https://api.themoviedb.org/3", - "ApiKey": "122f0c50ae02fa84601c07025cb6d2f1", + "ApiKey": "122f0c50ae02fa84601c07025cb6d2f1", "Language": "en-US", "Region": "US" }, @@ -162,59 +167,61 @@ For using this app we need a [YouTube ApiKey](https://developers.google.com/yout "SearchPart": "snippet", "SearchType": "video", "Order": 4 - }, + }, ``` -We could also set this YouTube ApiKey setting with using OS environments with using bellow command in cmd: +We could also set this YouTube ApiKey setting with using OS environments with using bellow command in cmd: -``` cmd +```cmd `setx YoutubeVideoOptions__ApiKey "your youtube api key"` ``` -It automatically will replace this env `APiKey` with our appsettings `APIKey`. +It automatically will replace this env `APiKey` with our appsettings `APIKey`. Also for security purpose of our Apis I used [API key Authentication](https://codingsonata.com/secure-asp-net-core-web-api-using-api-key-authentication/) and you have to pas a API key in header of request or in query string of your api url with this key name `X-Api-Key`. For example for url It will be like this: -``` bash +```bash http://localhost:5000/api/v1/movies/150/with-trailers?trailersCount=10&X-Api-Key=C5BFF7F0-B4DF-475E-A331-F737424F013C ``` For setup valid api key there is a class with name [InMemoryGetApiKeyQuery](./src/MovieSearch.Infrastructure/Security/InMemoryGetApiKeyQuery.cs), that this class is a in-memory registry for all valid Api Key. this class implemented `IGetApiKeyQuery` class. you can implement this interface and store your keys in your favorite provider like EF Core Sql Server or a Json file, ... Some of valid keys for test are: -``` bash +```bash C5BFF7F0-B4DF-475E-A331-F737424F013C 5908D47C-85D3-4024-8C2B-6EC9464398AD 06795D9D-A770-44B9-9B27-03C6ABDB1BAE ``` + Also our swagger is fully compatible with this Api key and you can authenticate in swagger UI before making any request. Use one of the above keys in this text box. ![](./assets/API-Key-Auth.png) - ## How to Run ### CMD + For running our Apis we need to run bellow command in shell in root of the project. -``` bash +```bash .\scripts\api.bat ``` + Then after this,our application will be up and running. our API service will be host on http://localhost:5000. -### Docker Compose +### Docker Compose We can run this app on docker with this [docker-compose.yaml](./deployments/docker-compose/docker-compose.yaml) file with bellow command in root of application: -``` bash +```bash docker-compose -f ./deployments/docker-compose/docker-compose.yaml up ``` -Also docker image is available on the docker hub in this address: [https://hub.docker.com/r/mehdihadeli/movie.api](https://hub.docker.com/r/mehdihadeli/movie.api) +Also docker image is available on the docker hub in this address: [https://hub.docker.com/r/mehdihadeli/movie.api](https://hub.docker.com/r/mehdihadeli/movie.api) ### Kubernetes For setup your local environment for using kubernetes you can use different approuch but I personally perfer to use [K3s](https://k3s.io/) from rancher team. -For running our app on kubernetes cluster we should apply [movie-search-api.yaml](./deployments/k8s/movie-search-api.yaml) file with +For running our app on kubernetes cluster we should apply [movie-search-api.yaml](./deployments/k8s/movie-search-api.yaml) file with diff --git a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/BuildingBlocks.IntegrationTest.csproj b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/BuildingBlocks.IntegrationTest.csproj index 19ac9f1..b508d39 100644 --- a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/BuildingBlocks.IntegrationTest.csproj +++ b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/BuildingBlocks.IntegrationTest.csproj @@ -1,32 +1,32 @@ - net7.0 + net8.0 false - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - + diff --git a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Factories/CustomApplicationFactory.cs b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Factories/CustomApplicationFactory.cs index cdf0da3..73757b3 100644 --- a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Factories/CustomApplicationFactory.cs +++ b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Factories/CustomApplicationFactory.cs @@ -19,9 +19,8 @@ namespace BuildingBlocks.Test.Factories; public class CustomApplicationFactory : WebApplicationFactory where TEntryPoint : class { - public CustomApplicationFactory() : this(null) - { - } + public CustomApplicationFactory() + : this(null) { } public CustomApplicationFactory(Action testRegistrationServices = null) { @@ -38,15 +37,18 @@ protected override IHostBuilder CreateHostBuilder() { var builder = base.CreateHostBuilder(); - builder = builder.UseSerilog((_, _, configuration) => - { - if (OutputHelper is not null) - configuration.WriteTo.Xunit(OutputHelper); + builder = builder.UseSerilog( + (_, _, configuration) => + { + if (OutputHelper is not null) + configuration.WriteTo.Xunit(OutputHelper); - configuration.MinimumLevel.Is(LogEventLevel.Information) - .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning) - .WriteTo.Console(); - }); + configuration + .MinimumLevel.Is(LogEventLevel.Information) + .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning) + .WriteTo.Console(); + } + ); return builder; } @@ -77,16 +79,19 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) using var scope = sp.CreateScope(); var seeders = scope.ServiceProvider.GetServices(); - foreach (var seeder in seeders) seeder.SeedAllAsync().GetAwaiter().GetResult(); + foreach (var seeder in seeders) + seeder.SeedAllAsync().GetAwaiter().GetResult(); }); - builder.UseDefaultServiceProvider((env, c) => - { - // Handling Captive Dependency Problem - // https://ankitvijay.net/2020/03/17/net-core-and-di-beware-of-captive-dependency/ - // https://blog.ploeh.dk/2014/06/02/captive-dependency/ - if (env.HostingEnvironment.IsEnvironment("test") || env.HostingEnvironment.IsDevelopment()) - c.ValidateScopes = true; - }); + builder.UseDefaultServiceProvider( + (env, c) => + { + // Handling Captive Dependency Problem + // https://ankitvijay.net/2020/03/17/net-core-and-di-beware-of-captive-dependency/ + // https://blog.ploeh.dk/2014/06/02/captive-dependency/ + if (env.HostingEnvironment.IsEnvironment("test") || env.HostingEnvironment.IsDevelopment()) + c.ValidateScopes = true; + } + ); } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/IntegrationTestFixture.cs b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/IntegrationTestFixture.cs index 1129575..d38c2bd 100644 --- a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/IntegrationTestFixture.cs +++ b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/IntegrationTestFixture.cs @@ -81,12 +81,14 @@ public Task ExecuteDbContextAsync(Func> act return ExecuteScopeAsync(sp => action(sp.GetService(), sp.GetService())); } - public Task InsertAsync(params T[] entities) where T : class, IAggregate + public Task InsertAsync(params T[] entities) + where T : class, IAggregate { return ExecuteDbContextAsync(db => db.GetCollection().InsertManyAsync(entities)); } - public Task InsertAsync(TEntity entity) where TEntity : class, IAggregate + public Task InsertAsync(TEntity entity) + where TEntity : class, IAggregate { return ExecuteDbContextAsync(db => db.GetCollection().InsertOneAsync(entity)); } @@ -117,8 +119,12 @@ public Task InsertAsync(TEntity entity, TEntity2 en }); } - public Task InsertAsync(TEntity entity, TEntity2 entity2, - TEntity3 entity3, TEntity4 entity4) + public Task InsertAsync( + TEntity entity, + TEntity2 entity2, + TEntity3 entity3, + TEntity4 entity4 + ) where TEntity : class, IAggregate where TEntity2 : class, IAggregate where TEntity3 : class, IAggregate @@ -134,10 +140,10 @@ public Task InsertAsync(TEntity entity, T }); } - public Task FindAsync(int id) where T : class, IAggregate + public Task FindAsync(int id) + where T : class, IAggregate { - return ExecuteDbContextAsync(db => - db.GetCollection().AsQueryable().SingleOrDefaultAsync(x => x.Id == id)); + return ExecuteDbContextAsync(db => db.GetCollection().AsQueryable().SingleOrDefaultAsync(x => x.Id == id)); } public override Task InitializeAsync() @@ -161,16 +167,14 @@ public IntegrationTestFixture() { Factory = new CustomApplicationFactory(); } + // protected readonly LaunchSettingsFixture LaunchSettings; protected HttpClient Client => Factory.CreateClient(); + public IHttpClientFactory HttpClientFactory => ServiceProvider.GetRequiredService(); - public IHttpClientFactory HttpClientFactory => - ServiceProvider.GetRequiredService(); - - public IHttpContextAccessor HttpContextAccessor => - ServiceProvider.GetRequiredService(); + public IHttpContextAccessor HttpContextAccessor => ServiceProvider.GetRequiredService(); public IServiceProvider ServiceProvider => Factory.Services; public IConfiguration Configuration => Factory.Configuration; @@ -234,7 +238,8 @@ public Task SendAsync(ICommand request, Cancell }); } - public Task SendAsync(T request, CancellationToken cancellationToken) where T : class, ICommand + public Task SendAsync(T request, CancellationToken cancellationToken) + where T : class, ICommand { return ExecuteScopeAsync(sp => { @@ -254,4 +259,4 @@ public Task QueryAsync(IQuery query, Cancellati return mediator.Send(query, cancellationToken); }); } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/LaunchSettingsFixture.cs b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/LaunchSettingsFixture.cs index 8e441b7..7213b61 100644 --- a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/LaunchSettingsFixture.cs +++ b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/LaunchSettingsFixture.cs @@ -35,4 +35,4 @@ public void Dispose() { // ... clean up } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/Mongo2GoFixture.cs b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/Mongo2GoFixture.cs index c698902..f62d9b9 100644 --- a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/Mongo2GoFixture.cs +++ b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/Mongo2GoFixture.cs @@ -34,4 +34,4 @@ public void Dispose() { _mongoRunner.Dispose(); } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/MongoDbFixture.cs b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/MongoDbFixture.cs index 88bc613..2f968e7 100644 --- a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/MongoDbFixture.cs +++ b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/MongoDbFixture.cs @@ -21,4 +21,4 @@ public void Dispose() var client = new MongoClient(MongoOptions.ConnectionString); client.DropDatabase(MongoOptions.DatabaseName); } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/WebApiTestFixture.cs b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/WebApiTestFixture.cs index f8a68cc..aea6815 100644 --- a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/WebApiTestFixture.cs +++ b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Fixtures/WebApiTestFixture.cs @@ -13,12 +13,13 @@ public abstract class WebApiTestFixture : IntegrationTe where TEntryPoint : class where TDbContext : class, IMongoDbContext { - private static readonly JsonSerializerOptions SerializerOptions = new() - { - PropertyNameCaseInsensitive = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - Converters = {new JsonStringEnumConverter()} - }; + private static readonly JsonSerializerOptions SerializerOptions = + new() + { + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + Converters = { new JsonStringEnumConverter() } + }; private string _route; @@ -30,9 +31,11 @@ protected void SetPath(string route) return; } - if (route.StartsWith("/")) route = route.Substring(1, route.Length - 1); + if (route.StartsWith("/")) + route = route.Substring(1, route.Length - 1); - if (route.EndsWith("/")) route = route.Substring(0, route.Length - 1); + if (route.EndsWith("/")) + route = route.Substring(0, route.Length - 1); _route = $"{route}/"; } @@ -104,19 +107,19 @@ protected static async Task ReadAsync(HttpResponseMessage response) return JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync(), SerializerOptions); } - protected virtual void ConfigureServices(IServiceCollection services) - { - } + protected virtual void ConfigureServices(IServiceCollection services) { } } -public abstract class WebApiTestFixture : IntegrationTestFixture where TEntryPoint : class +public abstract class WebApiTestFixture : IntegrationTestFixture + where TEntryPoint : class { - private static readonly JsonSerializerOptions SerializerOptions = new() - { - PropertyNameCaseInsensitive = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - Converters = {new JsonStringEnumConverter()} - }; + private static readonly JsonSerializerOptions SerializerOptions = + new() + { + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + Converters = { new JsonStringEnumConverter() } + }; private string _route; @@ -128,9 +131,11 @@ protected void SetPath(string route) return; } - if (route.StartsWith("/")) route = route.Substring(1, route.Length - 1); + if (route.StartsWith("/")) + route = route.Substring(1, route.Length - 1); - if (route.EndsWith("/")) route = route.Substring(0, route.Length - 1); + if (route.EndsWith("/")) + route = route.Substring(0, route.Length - 1); _route = $"{route}/"; } @@ -202,7 +207,5 @@ protected static async Task ReadAsync(HttpResponseMessage response) return JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync(), SerializerOptions); } - protected virtual void ConfigureServices(IServiceCollection services) - { - } -} \ No newline at end of file + protected virtual void ConfigureServices(IServiceCollection services) { } +} diff --git a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Helpers/OptionsHelper.cs b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Helpers/OptionsHelper.cs index 2b26461..5ce8e91 100644 --- a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Helpers/OptionsHelper.cs +++ b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Helpers/OptionsHelper.cs @@ -10,18 +10,13 @@ public static TSettings GetOptions(string section, string settingsFil settingsFileName ??= "appsettings.test.json"; var configuration = new TSettings(); - GetConfigurationRoot(settingsFileName) - .GetSection(section) - .Bind(configuration); + GetConfigurationRoot(settingsFileName).GetSection(section).Bind(configuration); return configuration; } private static IConfigurationRoot GetConfigurationRoot(string settingsFileName) { - return new ConfigurationBuilder() - .AddJsonFile(settingsFileName, true) - .AddEnvironmentVariables() - .Build(); + return new ConfigurationBuilder().AddJsonFile(settingsFileName, true).AddEnvironmentVariables().Build(); } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Mock/NullDataSeeder.cs b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Mock/NullDataSeeder.cs index e6ce3aa..4bdd695 100644 --- a/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Mock/NullDataSeeder.cs +++ b/src/BuildingBlocks/BuildingBlocks.IntegrationTest/Mock/NullDataSeeder.cs @@ -9,4 +9,4 @@ public Task SeedAllAsync() { return Task.CompletedTask; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/BuildingBlocks.csproj b/src/BuildingBlocks/BulidingBlocks/BuildingBlocks.csproj index 6cf5b0b..94e257c 100644 --- a/src/BuildingBlocks/BulidingBlocks/BuildingBlocks.csproj +++ b/src/BuildingBlocks/BulidingBlocks/BuildingBlocks.csproj @@ -1,62 +1,60 @@ - net7.0 + net8.0 enable - - - - - - + + + + + - - - - + + + + - - - + + - - - - - - - - + + + + + + + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - + + + + + + - - - - - + + + + + - - - - - - - - + + + + + + + + diff --git a/src/BuildingBlocks/BulidingBlocks/Caching/CacheKey.cs b/src/BuildingBlocks/BulidingBlocks/Caching/CacheKey.cs index e692c06..bde26ec 100644 --- a/src/BuildingBlocks/BulidingBlocks/Caching/CacheKey.cs +++ b/src/BuildingBlocks/BulidingBlocks/Caching/CacheKey.cs @@ -14,4 +14,4 @@ public static string With(Type ownerType, params string[] keys) { return With($"{ownerType.GetCacheKey()}:{string.Join("-", keys)}"); } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Caching/CachingBehavior.cs b/src/BuildingBlocks/BulidingBlocks/Caching/CachingBehavior.cs index 1de6eb6..4e3f8a1 100644 --- a/src/BuildingBlocks/BulidingBlocks/Caching/CachingBehavior.cs +++ b/src/BuildingBlocks/BulidingBlocks/Caching/CachingBehavior.cs @@ -18,18 +18,22 @@ public class CachingBehavior : IPipelineBehavior> _logger; private readonly int defaultCacheExpirationInHours = 1; - - public CachingBehavior(IEasyCachingProviderFactory cachingFactory, + public CachingBehavior( + IEasyCachingProviderFactory cachingFactory, ILogger> logger, - IEnumerable> cachePolicies) + IEnumerable> cachePolicies + ) { _logger = logger; _cachingProvider = cachingFactory.GetCachingProvider("mem"); _cachePolicies = cachePolicies; } - public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken - cancellationToken) + public async Task Handle( + TRequest request, + RequestHandlerDelegate next, + CancellationToken cancellationToken + ) { var cachePolicy = _cachePolicies.FirstOrDefault(); if (cachePolicy == null) @@ -40,20 +44,25 @@ public async Task Handle(TRequest request, RequestHandlerDelegate(cacheKey); if (cachedResponse.Value != null) { - _logger.LogDebug("Response retrieved {TRequest} from cache. CacheKey: {CacheKey}", - typeof(TRequest).FullName, cacheKey); + _logger.LogDebug( + "Response retrieved {TRequest} from cache. CacheKey: {CacheKey}", + typeof(TRequest).FullName, + cacheKey + ); return cachedResponse.Value; } var response = await next(); - var time = cachePolicy.AbsoluteExpirationRelativeToNow ?? - DateTime.Now.AddHours(defaultCacheExpirationInHours); + var time = cachePolicy.AbsoluteExpirationRelativeToNow ?? DateTime.Now.AddHours(defaultCacheExpirationInHours); await _cachingProvider.SetAsync(cacheKey, response, time.TimeOfDay); - _logger.LogDebug("Caching response for {TRequest} with cache key: {CacheKey}", typeof(TRequest).FullName, - cacheKey); + _logger.LogDebug( + "Caching response for {TRequest} with cache key: {CacheKey}", + typeof(TRequest).FullName, + cacheKey + ); return response; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Caching/ExpirationOptions.cs b/src/BuildingBlocks/BulidingBlocks/Caching/ExpirationOptions.cs index 29f2a2d..a3f1d36 100644 --- a/src/BuildingBlocks/BulidingBlocks/Caching/ExpirationOptions.cs +++ b/src/BuildingBlocks/BulidingBlocks/Caching/ExpirationOptions.cs @@ -10,4 +10,4 @@ public ExpirationOptions(DateTimeOffset absoluteExpiration) } public DateTimeOffset AbsoluteExpiration { get; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Caching/Extensions.cs b/src/BuildingBlocks/BulidingBlocks/Caching/Extensions.cs index 8397f50..4abb240 100644 --- a/src/BuildingBlocks/BulidingBlocks/Caching/Extensions.cs +++ b/src/BuildingBlocks/BulidingBlocks/Caching/Extensions.cs @@ -7,25 +7,27 @@ namespace BuildingBlocks.Caching; public static class Extensions { - public static IServiceCollection AddCachingRequestPolicies(this IServiceCollection services, - IList assembliesToScan) + public static IServiceCollection AddCachingRequestPolicies( + this IServiceCollection services, + IList assembliesToScan + ) { // ICachePolicy discovery and registration - services.Scan(scan => scan - .FromAssemblies(assembliesToScan ?? AppDomain.CurrentDomain.GetAssemblies()) - .AddClasses(classes => classes.AssignableTo(typeof(ICachePolicy<,>)), - false) - .AsImplementedInterfaces() - .WithTransientLifetime()); + services.Scan(scan => + scan.FromAssemblies(assembliesToScan ?? AppDomain.CurrentDomain.GetAssemblies()) + .AddClasses(classes => classes.AssignableTo(typeof(ICachePolicy<,>)), false) + .AsImplementedInterfaces() + .WithTransientLifetime() + ); // IInvalidateCachePolicy discovery and registration - services.Scan(scan => scan - .FromAssemblies(assembliesToScan ?? AppDomain.CurrentDomain.GetAssemblies()) - .AddClasses(classes => classes.AssignableTo(typeof(IInvalidateCachePolicy<,>)), - false) - .AsImplementedInterfaces() - .WithTransientLifetime()); + services.Scan(scan => + scan.FromAssemblies(assembliesToScan ?? AppDomain.CurrentDomain.GetAssemblies()) + .AddClasses(classes => classes.AssignableTo(typeof(IInvalidateCachePolicy<,>)), false) + .AsImplementedInterfaces() + .WithTransientLifetime() + ); return services; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Caching/ICachePolicy.cs b/src/BuildingBlocks/BulidingBlocks/Caching/ICachePolicy.cs index 92f3844..0ea4efa 100644 --- a/src/BuildingBlocks/BulidingBlocks/Caching/ICachePolicy.cs +++ b/src/BuildingBlocks/BulidingBlocks/Caching/ICachePolicy.cs @@ -4,14 +4,15 @@ namespace BuildingBlocks.Caching; -public interface ICachePolicy where TRequest : IRequest +public interface ICachePolicy + where TRequest : IRequest { DateTime? AbsoluteExpirationRelativeToNow { get; } string GetCacheKey(TRequest request) { - var r = new {request}; + var r = new { request }; var props = r.request.GetType().GetProperties().Select(pi => $"{pi.Name}:{pi.GetValue(r.request, null)}"); return $"{typeof(TRequest).FullName}{{{string.Join(",", props)}}}"; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Caching/IInvalidateCachePolicy.cs b/src/BuildingBlocks/BulidingBlocks/Caching/IInvalidateCachePolicy.cs index 8a0fa72..0b8e922 100644 --- a/src/BuildingBlocks/BulidingBlocks/Caching/IInvalidateCachePolicy.cs +++ b/src/BuildingBlocks/BulidingBlocks/Caching/IInvalidateCachePolicy.cs @@ -3,17 +3,16 @@ namespace BuildingBlocks.Caching; -public interface IInvalidateCachePolicy where TRequest : IRequest +public interface IInvalidateCachePolicy + where TRequest : IRequest { string GetCacheKey(TRequest request) { - var r = new {request}; + var r = new { request }; var props = r.request.GetType().GetProperties().Select(pi => $"{pi.Name}:{pi.GetValue(r.request, null)}"); return $"{typeof(TRequest).FullName}{{{string.Join(",", props)}}}"; } } public interface IInvalidateCachePolicy : IInvalidateCachePolicy - where TRequest : IRequest -{ -} \ No newline at end of file + where TRequest : IRequest { } diff --git a/src/BuildingBlocks/BulidingBlocks/Caching/InvalidateCachingBehavior.cs b/src/BuildingBlocks/BulidingBlocks/Caching/InvalidateCachingBehavior.cs index 5f3c000..440992f 100644 --- a/src/BuildingBlocks/BulidingBlocks/Caching/InvalidateCachingBehavior.cs +++ b/src/BuildingBlocks/BulidingBlocks/Caching/InvalidateCachingBehavior.cs @@ -16,18 +16,22 @@ public class InvalidateCachingBehavior : IPipelineBehavior< private readonly IEnumerable> _invalidateCachePolicies; private readonly ILogger> _logger; - - public InvalidateCachingBehavior(IEasyCachingProviderFactory cachingFactory, + public InvalidateCachingBehavior( + IEasyCachingProviderFactory cachingFactory, ILogger> logger, - IEnumerable> invalidateCachingPolicies) + IEnumerable> invalidateCachingPolicies + ) { _logger = logger; _cachingProvider = cachingFactory.GetCachingProvider("mem"); _invalidateCachePolicies = invalidateCachingPolicies; } - public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken - cancellationToken) + public async Task Handle( + TRequest request, + RequestHandlerDelegate next, + CancellationToken cancellationToken + ) { var cachePolicy = _invalidateCachePolicies.FirstOrDefault(); if (cachePolicy == null) @@ -43,4 +47,4 @@ public async Task Handle(TRequest request, RequestHandlerDelegate : IRequest where T : notnull -{ -} +public interface ICommand : IRequest + where T : notnull { } -public interface ICommand : IRequest -{ -} +public interface ICommand : IRequest { } public interface IQuery : IRequest - where T : notnull -{ -} + where T : notnull { } public interface ICreateCommand : ICommand, ITxRequest - where TResponse : notnull -{ -} + where TResponse : notnull { } -public interface ICreateCommand : ICommand, ITxRequest -{ -} +public interface ICreateCommand : ICommand, ITxRequest { } -public interface IUpdateCommand : ICommand, ITxRequest -{ -} +public interface IUpdateCommand : ICommand, ITxRequest { } public interface IUpdateCommand : ICommand, ITxRequest - where TResponse : notnull -{ -} + where TResponse : notnull { } public interface IDeleteCommand : ICommand where TId : struct @@ -43,7 +30,8 @@ public interface IDeleteCommand : ICommand public TId Id { get; init; } } -public interface IDeleteCommand : ICommand where TId : struct +public interface IDeleteCommand : ICommand + where TId : struct { public TId Id { get; init; } } @@ -58,9 +46,7 @@ public interface IPageList } public interface IListQuery : IQuery, IPageList - where TResponse : notnull -{ -} + where TResponse : notnull { } public interface IItemQuery : IQuery where TId : struct @@ -72,19 +58,18 @@ public interface IItemQuery : IQuery public record FilterModel(string FieldName, string Comparision, string FieldValue); -public record ListResultModel(List Items, long TotalItems, int Page, int PageSize) where T : notnull +public record ListResultModel(List Items, long TotalItems, int Page, int PageSize) + where T : notnull { public static ListResultModel Empty => new(Enumerable.Empty().ToList(), 0, 0, 0); - public static ListResultModel Create(List items, long totalItems = 0, int page = default, - int pageSize = 20) + public static ListResultModel Create(List items, long totalItems = 0, int page = default, int pageSize = 20) { return new ListResultModel(items, totalItems, page, pageSize); } public ListResultModel Map(Func map) { - return ListResultModel.Create( - Items.Select(map).ToList(), TotalItems, Page, PageSize); + return ListResultModel.Create(Items.Select(map).ToList(), TotalItems, Page, PageSize); } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Domain/IAggregate.cs b/src/BuildingBlocks/BulidingBlocks/Domain/IAggregate.cs index d4a1c6e..d6ba884 100644 --- a/src/BuildingBlocks/BulidingBlocks/Domain/IAggregate.cs +++ b/src/BuildingBlocks/BulidingBlocks/Domain/IAggregate.cs @@ -1,10 +1,8 @@ namespace BuildingBlocks.Domain; -public interface IAggregate : IAggregate -{ -} +public interface IAggregate : IAggregate { } public interface IAggregate { public T Id { get; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Domain/ITxRequest.cs b/src/BuildingBlocks/BulidingBlocks/Domain/ITxRequest.cs index aa2295c..9884ac8 100644 --- a/src/BuildingBlocks/BulidingBlocks/Domain/ITxRequest.cs +++ b/src/BuildingBlocks/BulidingBlocks/Domain/ITxRequest.cs @@ -1,5 +1,3 @@ namespace BuildingBlocks.Domain; -public interface ITxRequest -{ -} \ No newline at end of file +public interface ITxRequest { } diff --git a/src/BuildingBlocks/BulidingBlocks/EFCore/DesignTimeDbContextFactoryBase.cs b/src/BuildingBlocks/BulidingBlocks/EFCore/DesignTimeDbContextFactoryBase.cs index 134a590..9f28424 100644 --- a/src/BuildingBlocks/BulidingBlocks/EFCore/DesignTimeDbContextFactoryBase.cs +++ b/src/BuildingBlocks/BulidingBlocks/EFCore/DesignTimeDbContextFactoryBase.cs @@ -36,17 +36,14 @@ private TContext Create(string basePath, string environmentName) var connstr = config.GetConnectionString("DefaultConnection"); if (string.IsNullOrWhiteSpace(connstr)) - throw new InvalidOperationException( - "Could not find a connection string named 'Default'."); + throw new InvalidOperationException("Could not find a connection string named 'Default'."); return Create(connstr); } private TContext Create(string connectionString) { if (string.IsNullOrEmpty(connectionString)) - throw new ArgumentException( - $"{nameof(connectionString)} is null or empty.", - nameof(connectionString)); + throw new ArgumentException($"{nameof(connectionString)} is null or empty.", nameof(connectionString)); var optionsBuilder = new DbContextOptionsBuilder(); @@ -57,4 +54,4 @@ private TContext Create(string connectionString) var options = optionsBuilder.Options; return CreateNewInstance(options); } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/EFCore/QueryableExtensions.cs b/src/BuildingBlocks/BulidingBlocks/EFCore/QueryableExtensions.cs index 5df3402..9c567ca 100644 --- a/src/BuildingBlocks/BulidingBlocks/EFCore/QueryableExtensions.cs +++ b/src/BuildingBlocks/BulidingBlocks/EFCore/QueryableExtensions.cs @@ -8,24 +8,29 @@ namespace BuildingBlocks.EFCore; public static class QueryableExtensions { - public static async Task> PaginateAsync(this IQueryable collection, - IPageList query) + public static async Task> PaginateAsync(this IQueryable collection, IPageList query) { return await collection.PaginateAsync(query.Page, query.PageSize); } - public static async Task> PaginateAsync(this IQueryable collection, int page = 1, int - pageSize = 10) + public static async Task> PaginateAsync( + this IQueryable collection, + int page = 1, + int pageSize = 10 + ) { - if (page <= 0) page = 1; + if (page <= 0) + page = 1; - if (pageSize <= 0) pageSize = 10; + if (pageSize <= 0) + pageSize = 10; var isEmpty = await collection.AnyAsync() == false; - if (isEmpty) return ListResultModel.Empty; + if (isEmpty) + return ListResultModel.Empty; var totalItems = await collection.CountAsync(); - var totalPages = (int) Math.Ceiling((decimal) totalItems / pageSize); + var totalPages = (int)Math.Ceiling((decimal)totalItems / pageSize); var data = await collection.Limit(page, pageSize).ToListAsync(); return ListResultModel.Create(data, totalItems, page, pageSize); @@ -36,17 +41,17 @@ public static IQueryable Limit(this IQueryable collection, IPageList qu return collection.Limit(query.Page, query.PageSize); } - public static IQueryable Limit(this IQueryable collection, - int page = 1, int resultsPerPage = 10) + public static IQueryable Limit(this IQueryable collection, int page = 1, int resultsPerPage = 10) { - if (page <= 0) page = 1; + if (page <= 0) + page = 1; - if (resultsPerPage <= 0) resultsPerPage = 10; + if (resultsPerPage <= 0) + resultsPerPage = 10; var skip = (page - 1) * resultsPerPage; - var data = collection.Skip(skip) - .Take(resultsPerPage); + var data = collection.Skip(skip).Take(resultsPerPage); return data; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Exception/ApiException.cs b/src/BuildingBlocks/BulidingBlocks/Exception/ApiException.cs index e390fa7..a6b4dec 100644 --- a/src/BuildingBlocks/BulidingBlocks/Exception/ApiException.cs +++ b/src/BuildingBlocks/BulidingBlocks/Exception/ApiException.cs @@ -5,16 +5,11 @@ namespace BuildingBlocks.Exception; public class ApiException : ApplicationException { - public ApiException() - { - } + public ApiException() { } - public ApiException(string message) : base(message) - { - } + public ApiException(string message) + : base(message) { } public ApiException(string message, params object[] args) - : base(string.Format(CultureInfo.CurrentCulture, message, args)) - { - } -} \ No newline at end of file + : base(string.Format(CultureInfo.CurrentCulture, message, args)) { } +} diff --git a/src/BuildingBlocks/BulidingBlocks/Exception/AppException.cs b/src/BuildingBlocks/BulidingBlocks/Exception/AppException.cs index 67629f8..88eac6e 100644 --- a/src/BuildingBlocks/BulidingBlocks/Exception/AppException.cs +++ b/src/BuildingBlocks/BulidingBlocks/Exception/AppException.cs @@ -2,10 +2,11 @@ namespace BuildingBlocks.Exception; public class AppException : System.Exception { - public AppException(string message, string code = default!) : base(message) + public AppException(string message, string code = default!) + : base(message) { Code = code; } public virtual string Code { get; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Exception/BadRequestException.cs b/src/BuildingBlocks/BulidingBlocks/Exception/BadRequestException.cs index 9876e74..52ae106 100644 --- a/src/BuildingBlocks/BulidingBlocks/Exception/BadRequestException.cs +++ b/src/BuildingBlocks/BulidingBlocks/Exception/BadRequestException.cs @@ -4,7 +4,6 @@ namespace BuildingBlocks.Exception; public class BadRequestException : ApplicationException { - public BadRequestException(string message) : base(message) - { - } -} \ No newline at end of file + public BadRequestException(string message) + : base(message) { } +} diff --git a/src/BuildingBlocks/BulidingBlocks/Exception/InvalidCommandException.cs b/src/BuildingBlocks/BulidingBlocks/Exception/InvalidCommandException.cs index 04670a3..673af08 100644 --- a/src/BuildingBlocks/BulidingBlocks/Exception/InvalidCommandException.cs +++ b/src/BuildingBlocks/BulidingBlocks/Exception/InvalidCommandException.cs @@ -10,4 +10,4 @@ public InvalidCommandException(List errors) } public List Errors { get; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Exception/NotFoundException.cs b/src/BuildingBlocks/BulidingBlocks/Exception/NotFoundException.cs index c9a65ae..219e2ea 100644 --- a/src/BuildingBlocks/BulidingBlocks/Exception/NotFoundException.cs +++ b/src/BuildingBlocks/BulidingBlocks/Exception/NotFoundException.cs @@ -2,7 +2,6 @@ namespace BuildingBlocks.Exception; public class NotFoundException : System.Exception { - public NotFoundException(string message) : base(message) - { - } -} \ No newline at end of file + public NotFoundException(string message) + : base(message) { } +} diff --git a/src/BuildingBlocks/BulidingBlocks/Logging/Extensions.cs b/src/BuildingBlocks/BulidingBlocks/Logging/Extensions.cs index b8398a4..4a64ef4 100644 --- a/src/BuildingBlocks/BulidingBlocks/Logging/Extensions.cs +++ b/src/BuildingBlocks/BulidingBlocks/Logging/Extensions.cs @@ -13,57 +13,67 @@ public static class Extensions { public static WebApplicationBuilder AddCustomSerilog( this WebApplicationBuilder builder, - Action? extraConfigure = null) + Action? extraConfigure = null + ) { - builder.Host.UseSerilog((context, serviceProvider, loggerConfiguration) => - { - var loggerOptions = context.Configuration.GetSection(nameof(LoggerOptions)).Get(); + builder.Host.UseSerilog( + (context, serviceProvider, loggerConfiguration) => + { + var loggerOptions = context.Configuration.GetSection(nameof(LoggerOptions)).Get(); - extraConfigure?.Invoke(loggerConfiguration); + extraConfigure?.Invoke(loggerConfiguration); - loggerConfiguration - .ReadFrom.Configuration(context.Configuration) - .ReadFrom.Services(serviceProvider) - .Enrich.WithExceptionDetails() - .Enrich.WithCorrelationId() - .Enrich.FromLogContext(); + loggerConfiguration + .ReadFrom.Configuration(context.Configuration) + .ReadFrom.Services(serviceProvider) + .Enrich.WithExceptionDetails() + .Enrich.WithCorrelationId() + .Enrich.FromLogContext(); - var level = Enum.TryParse(loggerOptions?.Level, true, out var logLevel) - ? logLevel - : LogEventLevel.Information; + var level = Enum.TryParse(loggerOptions?.Level, true, out var logLevel) + ? logLevel + : LogEventLevel.Information; - // https://andrewlock.net/using-serilog-aspnetcore-in-asp-net-core-3-reducing-log-verbosity/ - loggerConfiguration.MinimumLevel.Is(level) - .MinimumLevel.Override("Microsoft", LogEventLevel.Information) - .MinimumLevel - // Filter out ASP.NET Core infrastructure logs that are Information and below - .Override("Microsoft.AspNetCore", LogEventLevel.Warning); + // https://andrewlock.net/using-serilog-aspnetcore-in-asp-net-core-3-reducing-log-verbosity/ + loggerConfiguration + .MinimumLevel.Is(level) + .MinimumLevel.Override("Microsoft", LogEventLevel.Information) + .MinimumLevel + // Filter out ASP.NET Core infrastructure logs that are Information and below + .Override("Microsoft.AspNetCore", LogEventLevel.Warning); - if (context.HostingEnvironment.IsDevelopment()) - { - loggerConfiguration.WriteTo.Async(writeTo => writeTo.SpectreConsole( - loggerOptions?.LogTemplate ?? - "{Timestamp:HH:mm:ss} [{Level:u4}] {Message:lj}{NewLine}{Exception}", - level)); - } - else - { - if (!string.IsNullOrEmpty(loggerOptions?.ElasticSearchUrl)) - loggerConfiguration.WriteTo.Async(writeTo => writeTo.Elasticsearch(loggerOptions.ElasticSearchUrl)); + if (context.HostingEnvironment.IsDevelopment()) + { + loggerConfiguration.WriteTo.Async(writeTo => + writeTo.SpectreConsole( + loggerOptions?.LogTemplate + ?? "{Timestamp:HH:mm:ss} [{Level:u4}] {Message:lj}{NewLine}{Exception}", + level + ) + ); + } + else + { + if (!string.IsNullOrEmpty(loggerOptions?.ElasticSearchUrl)) + loggerConfiguration.WriteTo.Async(writeTo => + writeTo.Elasticsearch(loggerOptions.ElasticSearchUrl) + ); - if (!string.IsNullOrEmpty(loggerOptions?.SeqUrl)) - loggerConfiguration.WriteTo.Async(writeTo => writeTo.Seq(loggerOptions.SeqUrl)); - } + if (!string.IsNullOrEmpty(loggerOptions?.SeqUrl)) + loggerConfiguration.WriteTo.Async(writeTo => writeTo.Seq(loggerOptions.SeqUrl)); + } - if (!string.IsNullOrEmpty(loggerOptions?.LogPath)) - loggerConfiguration.WriteTo.File( - loggerOptions.LogPath, - outputTemplate: loggerOptions.LogTemplate ?? - "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Level} - {Message:lj}{NewLine}{Exception}", - rollingInterval: RollingInterval.Day, - rollOnFileSizeLimit: true); - }); + if (!string.IsNullOrEmpty(loggerOptions?.LogPath)) + loggerConfiguration.WriteTo.File( + loggerOptions.LogPath, + outputTemplate: loggerOptions.LogTemplate + ?? "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Level} - {Message:lj}{NewLine}{Exception}", + rollingInterval: RollingInterval.Day, + rollOnFileSizeLimit: true + ); + } + ); return builder; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Logging/LoggerOptions.cs b/src/BuildingBlocks/BulidingBlocks/Logging/LoggerOptions.cs index 6139181..8edb8af 100644 --- a/src/BuildingBlocks/BulidingBlocks/Logging/LoggerOptions.cs +++ b/src/BuildingBlocks/BulidingBlocks/Logging/LoggerOptions.cs @@ -7,4 +7,4 @@ public class LoggerOptions public string? ElasticSearchUrl { get; set; } public string? LogTemplate { get; set; } public string? LogPath { get; set; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Logging/LoggingBehavior.cs b/src/BuildingBlocks/BulidingBlocks/Logging/LoggingBehavior.cs index 25b01e8..707d269 100644 --- a/src/BuildingBlocks/BulidingBlocks/Logging/LoggingBehavior.cs +++ b/src/BuildingBlocks/BulidingBlocks/Logging/LoggingBehavior.cs @@ -17,13 +17,20 @@ public LoggingBehavior(ILogger> logger) _logger = logger; } - public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken - cancellationToken) + public async Task Handle( + TRequest request, + RequestHandlerDelegate next, + CancellationToken cancellationToken + ) { const string prefix = nameof(LoggingBehavior); - _logger.LogInformation("[{Prefix}] Handle request={X-RequestData} and response={X-ResponseData}", - prefix, typeof(TRequest).Name, typeof(TResponse).Name); + _logger.LogInformation( + "[{Prefix}] Handle request={X-RequestData} and response={X-ResponseData}", + prefix, + typeof(TRequest).Name, + typeof(TResponse).Name + ); var timer = new Stopwatch(); timer.Start(); @@ -33,10 +40,14 @@ public async Task Handle(TRequest request, RequestHandlerDelegate 3) // if the request is greater than 3 seconds, then log the warnings - _logger.LogWarning("[{Perf-Possible}] The request {X-RequestData} took {TimeTaken} seconds", - prefix, typeof(TRequest).Name, timeTaken.Seconds); + _logger.LogWarning( + "[{Perf-Possible}] The request {X-RequestData} took {TimeTaken} seconds", + prefix, + typeof(TRequest).Name, + timeTaken.Seconds + ); _logger.LogInformation("[{Prefix}] Handled {X-RequestData}", prefix, typeof(TRequest).Name); return response; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Mongo/Extensions.cs b/src/BuildingBlocks/BulidingBlocks/Mongo/Extensions.cs index 94beec9..a34013b 100644 --- a/src/BuildingBlocks/BulidingBlocks/Mongo/Extensions.cs +++ b/src/BuildingBlocks/BulidingBlocks/Mongo/Extensions.cs @@ -9,16 +9,22 @@ public static class Extensions private const string SectionName = "Mongo"; public static IServiceCollection AddMongoDbContext( - this IServiceCollection services, IConfiguration configuration, - string sectionName = SectionName, Action optionsAction = null) + this IServiceCollection services, + IConfiguration configuration, + string sectionName = SectionName, + Action optionsAction = null + ) where TContext : MongoDbContext { return services.AddMongoDbContext(configuration, sectionName, optionsAction); } public static IServiceCollection AddMongoDbContext( - this IServiceCollection services, IConfiguration configuration, - string sectionName = SectionName, Action optionsAction = null) + this IServiceCollection services, + IConfiguration configuration, + string sectionName = SectionName, + Action optionsAction = null + ) where TContextService : IMongoDbContext where TContextImplementation : MongoDbContext, TContextService { @@ -37,4 +43,4 @@ public static IServiceCollection AddMongoDbContext GetCollection(); -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Mongo/ImmutablePocoConvention.cs b/src/BuildingBlocks/BulidingBlocks/Mongo/ImmutablePocoConvention.cs index d31410b..a7fad34 100644 --- a/src/BuildingBlocks/BulidingBlocks/Mongo/ImmutablePocoConvention.cs +++ b/src/BuildingBlocks/BulidingBlocks/Mongo/ImmutablePocoConvention.cs @@ -16,9 +16,7 @@ public class ImmutablePocoConvention : ConventionBase, IClassMapConvention private readonly BindingFlags _bindingFlags; public ImmutablePocoConvention() - : this(BindingFlags.Instance | BindingFlags.Public) - { - } + : this(BindingFlags.Instance | BindingFlags.Public) { } public ImmutablePocoConvention(BindingFlags bindingFlags) { @@ -27,7 +25,8 @@ public ImmutablePocoConvention(BindingFlags bindingFlags) public void Apply(BsonClassMap classMap) { - var readOnlyProperties = classMap.ClassType.GetTypeInfo() + var readOnlyProperties = classMap + .ClassType.GetTypeInfo() .GetProperties(_bindingFlags) .Where(p => IsReadOnlyProperty(classMap, p)) .ToList(); @@ -48,8 +47,7 @@ public void Apply(BsonClassMap classMap) } } - private static List GetMatchingProperties(ConstructorInfo constructor, - List properties) + private static List GetMatchingProperties(ConstructorInfo constructor, List properties) { var matchProperties = new List(); @@ -66,11 +64,10 @@ private static List GetMatchingProperties(ConstructorInfo construc return matchProperties; } - private static bool ParameterMatchProperty(ParameterInfo parameter, PropertyInfo property) { return string.Equals(property.Name, parameter.Name, StringComparison.InvariantCultureIgnoreCase) - && parameter.ParameterType == property.PropertyType; + && parameter.ParameterType == property.PropertyType; } private static bool IsReadOnlyProperty(BsonClassMap classMap, PropertyInfo propertyInfo) @@ -94,4 +91,4 @@ private static bool IsReadOnlyProperty(BsonClassMap classMap, PropertyInfo prope return true; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Mongo/MongoDbContext.cs b/src/BuildingBlocks/BulidingBlocks/Mongo/MongoDbContext.cs index 42d0ad8..6c1de0f 100644 --- a/src/BuildingBlocks/BulidingBlocks/Mongo/MongoDbContext.cs +++ b/src/BuildingBlocks/BulidingBlocks/Mongo/MongoDbContext.cs @@ -61,13 +61,17 @@ public async Task RollbackTransactionAsync() private static void RegisterConventions() { - ConventionRegistry.Register("conventions", new ConventionPack - { - new CamelCaseElementNameConvention(), - new IgnoreExtraElementsConvention(true), - new EnumRepresentationConvention(BsonType.String), - new IgnoreIfDefaultConvention(true), - new ImmutablePocoConvention() - }, _ => true); + ConventionRegistry.Register( + "conventions", + new ConventionPack + { + new CamelCaseElementNameConvention(), + new IgnoreExtraElementsConvention(true), + new EnumRepresentationConvention(BsonType.String), + new IgnoreIfDefaultConvention(true), + new ImmutablePocoConvention() + }, + _ => true + ); } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Mongo/MongoOptions.cs b/src/BuildingBlocks/BulidingBlocks/Mongo/MongoOptions.cs index cb70b60..d96f562 100644 --- a/src/BuildingBlocks/BulidingBlocks/Mongo/MongoOptions.cs +++ b/src/BuildingBlocks/BulidingBlocks/Mongo/MongoOptions.cs @@ -7,4 +7,4 @@ public class MongoOptions public string ConnectionString { get; set; } public string DatabaseName { get; set; } public static Guid UniqueId { get; set; } = Guid.NewGuid(); -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Mongo/MongoQueryableExtensions.cs b/src/BuildingBlocks/BulidingBlocks/Mongo/MongoQueryableExtensions.cs index da9a763..7db9601 100644 --- a/src/BuildingBlocks/BulidingBlocks/Mongo/MongoQueryableExtensions.cs +++ b/src/BuildingBlocks/BulidingBlocks/Mongo/MongoQueryableExtensions.cs @@ -8,24 +8,29 @@ namespace MicroBootstrap.Mongo; public static class MongoQueryableExtensions { - public static async Task> PaginateAsync(this IMongoQueryable collection, - IPageList query) + public static async Task> PaginateAsync(this IMongoQueryable collection, IPageList query) { return await collection.PaginateAsync(query.Page, query.PageSize); } - public static async Task> PaginateAsync(this IMongoQueryable collection, int page = 1, - int pageSize = 10) + public static async Task> PaginateAsync( + this IMongoQueryable collection, + int page = 1, + int pageSize = 10 + ) { - if (page <= 0) page = 1; + if (page <= 0) + page = 1; - if (pageSize <= 0) pageSize = 10; + if (pageSize <= 0) + pageSize = 10; var isEmpty = await collection.AnyAsync() == false; - if (isEmpty) return ListResultModel.Empty; + if (isEmpty) + return ListResultModel.Empty; var totalItems = await collection.CountAsync(); - var totalPages = (int) Math.Ceiling((decimal) totalItems / pageSize); + var totalPages = (int)Math.Ceiling((decimal)totalItems / pageSize); var data = await collection.Limit(page, pageSize).ToListAsync(); return ListResultModel.Create(data, totalItems, page, pageSize); @@ -36,17 +41,17 @@ public static IMongoQueryable Limit(this IMongoQueryable collection, IP return collection.Limit(query.Page, query.PageSize); } - public static IMongoQueryable Limit(this IMongoQueryable collection, - int page = 1, int resultsPerPage = 10) + public static IMongoQueryable Limit(this IMongoQueryable collection, int page = 1, int resultsPerPage = 10) { - if (page <= 0) page = 1; + if (page <= 0) + page = 1; - if (resultsPerPage <= 0) resultsPerPage = 10; + if (resultsPerPage <= 0) + resultsPerPage = 10; var skip = (page - 1) * resultsPerPage; - var data = collection.Skip(skip) - .Take(resultsPerPage); + var data = collection.Skip(skip).Take(resultsPerPage); return data; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Mongo/MongoRepository.cs b/src/BuildingBlocks/BulidingBlocks/Mongo/MongoRepository.cs index 5670ef7..b87d35e 100644 --- a/src/BuildingBlocks/BulidingBlocks/Mongo/MongoRepository.cs +++ b/src/BuildingBlocks/BulidingBlocks/Mongo/MongoRepository.cs @@ -33,7 +33,6 @@ public Task GetAsync(Expression> predicate) return DbSet.Find(predicate).SingleOrDefaultAsync(); } - public async Task> FindAsync(Expression> predicate) { return await DbSet.Find(predicate).ToListAsync(); @@ -44,8 +43,8 @@ public IAsyncEnumerable GetAllAsync(Expression> pre return DbSet.AsQueryable().ToAsyncEnumerable(); } - public Task> BrowseAsync(Expression> predicate, - TQuery query) where TQuery : IPageList + public Task> BrowseAsync(Expression> predicate, TQuery query) + where TQuery : IPageList { return DbSet.AsQueryable().Where(predicate).PaginateAsync(query); } @@ -84,4 +83,4 @@ public void Dispose() { _context?.Dispose(); } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Mongo/TxBehavior.cs b/src/BuildingBlocks/BulidingBlocks/Mongo/TxBehavior.cs index f5f232c..0c0ee82 100644 --- a/src/BuildingBlocks/BulidingBlocks/Mongo/TxBehavior.cs +++ b/src/BuildingBlocks/BulidingBlocks/Mongo/TxBehavior.cs @@ -23,17 +23,31 @@ public TxBehavior(IMongoDbContext dbContext, ILogger Handle(TRequest request, RequestHandlerDelegate next, CancellationToken - cancellationToken) + public async Task Handle( + TRequest request, + RequestHandlerDelegate next, + CancellationToken cancellationToken + ) { - if (request is not ITxRequest) return await next(); + if (request is not ITxRequest) + return await next(); - _logger.LogInformation("{Prefix} Handled command {MediatRRequest}", nameof(TxBehavior), - typeof(TRequest).FullName); - _logger.LogDebug("{Prefix} Handled command {MediatRRequest} with content {RequestContent}", - nameof(TxBehavior), typeof(TRequest).FullName, JsonSerializer.Serialize(request)); - _logger.LogInformation("{Prefix} Open the transaction for {MediatRRequest}", - nameof(TxBehavior), typeof(TRequest).FullName); + _logger.LogInformation( + "{Prefix} Handled command {MediatRRequest}", + nameof(TxBehavior), + typeof(TRequest).FullName + ); + _logger.LogDebug( + "{Prefix} Handled command {MediatRRequest} with content {RequestContent}", + nameof(TxBehavior), + typeof(TRequest).FullName, + JsonSerializer.Serialize(request) + ); + _logger.LogInformation( + "{Prefix} Open the transaction for {MediatRRequest}", + nameof(TxBehavior), + typeof(TRequest).FullName + ); try { @@ -41,8 +55,11 @@ public async Task Handle(TRequest request, RequestHandlerDelegate), typeof(TRequest).FullName); + _logger.LogInformation( + "{Prefix} Executed the {MediatRRequest} request", + nameof(TxBehavior), + typeof(TRequest).FullName + ); await _dbContext.CommitTransactionAsync(); @@ -54,4 +71,4 @@ public async Task Handle(TRequest request, RequestHandlerDelegate : IDisposable where TEntity : class, IAggregate +public interface IRepository : IDisposable + where TEntity : class, IAggregate { Task GetAsync(Guid id); Task GetAsync(Expression> predicate); Task> FindAsync(Expression> predicate); IAsyncEnumerable GetAllAsync(Expression> predicate = null); - public Task> BrowseAsync(Expression> predicate, - TQuery query) where TQuery : IPageList; + public Task> BrowseAsync(Expression> predicate, TQuery query) + where TQuery : IPageList; Task AddAsync(TEntity entity); Task UpdateAsync(TEntity entity); @@ -22,4 +23,4 @@ public Task> BrowseAsync(Expression> predicate); Task ExistsAsync(Expression> predicate); -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Persistence/IUnitOfWork.cs b/src/BuildingBlocks/BulidingBlocks/Persistence/IUnitOfWork.cs index 30e0035..1056063 100644 --- a/src/BuildingBlocks/BulidingBlocks/Persistence/IUnitOfWork.cs +++ b/src/BuildingBlocks/BulidingBlocks/Persistence/IUnitOfWork.cs @@ -6,4 +6,4 @@ namespace BuildingBlocks.Persistence; public interface IUnitOfWork : IDisposable { Task CommitAsync(); -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Resiliency/Configs/ICircuitBreakerPolicyConfig.cs b/src/BuildingBlocks/BulidingBlocks/Resiliency/Configs/ICircuitBreakerPolicyConfig.cs index e7d583c..7cf6e1e 100644 --- a/src/BuildingBlocks/BulidingBlocks/Resiliency/Configs/ICircuitBreakerPolicyConfig.cs +++ b/src/BuildingBlocks/BulidingBlocks/Resiliency/Configs/ICircuitBreakerPolicyConfig.cs @@ -4,4 +4,4 @@ public interface ICircuitBreakerPolicyConfig { int RetryCount { get; set; } int BreakDuration { get; set; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Resiliency/Configs/IRetryPolicyConfig.cs b/src/BuildingBlocks/BulidingBlocks/Resiliency/Configs/IRetryPolicyConfig.cs index b4a53e8..4ceeb2c 100644 --- a/src/BuildingBlocks/BulidingBlocks/Resiliency/Configs/IRetryPolicyConfig.cs +++ b/src/BuildingBlocks/BulidingBlocks/Resiliency/Configs/IRetryPolicyConfig.cs @@ -3,4 +3,4 @@ namespace BuildingBlocks.Resiliency.Configs; public interface IRetryPolicyConfig { int RetryCount { get; set; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Resiliency/Configs/ITimeoutPolicy.cs b/src/BuildingBlocks/BulidingBlocks/Resiliency/Configs/ITimeoutPolicy.cs index 7bf1e1e..2ded260 100644 --- a/src/BuildingBlocks/BulidingBlocks/Resiliency/Configs/ITimeoutPolicy.cs +++ b/src/BuildingBlocks/BulidingBlocks/Resiliency/Configs/ITimeoutPolicy.cs @@ -3,4 +3,4 @@ namespace BuildingBlocks.Resiliency.Configs; public interface ITimeoutPolicy { public int TimeOutDuration { get; set; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Resiliency/Configs/PolicyConfig.cs b/src/BuildingBlocks/BulidingBlocks/Resiliency/Configs/PolicyConfig.cs index 6527cf1..375b61e 100644 --- a/src/BuildingBlocks/BulidingBlocks/Resiliency/Configs/PolicyConfig.cs +++ b/src/BuildingBlocks/BulidingBlocks/Resiliency/Configs/PolicyConfig.cs @@ -5,4 +5,4 @@ public class PolicyConfig : ICircuitBreakerPolicyConfig, IRetryPolicyConfig, ITi public int RetryCount { get; set; } public int BreakDuration { get; set; } public int TimeOutDuration { get; set; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Resiliency/Extensions.cs b/src/BuildingBlocks/BulidingBlocks/Resiliency/Extensions.cs index b8a883e..fde3473 100644 --- a/src/BuildingBlocks/BulidingBlocks/Resiliency/Extensions.cs +++ b/src/BuildingBlocks/BulidingBlocks/Resiliency/Extensions.cs @@ -9,33 +9,39 @@ namespace BuildingBlocks.Resiliency; public static class Extensions { - public static IServiceCollection AddMediaterRetryPolicy(IServiceCollection services, - IReadOnlyList assemblies) + public static IServiceCollection AddMediaterRetryPolicy( + IServiceCollection services, + IReadOnlyList assemblies + ) { services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RetryBehavior<,>)); - services.Scan(scan => scan - .FromAssemblies(assemblies) - .AddClasses(classes => classes.AssignableTo(typeof(IRetryableRequest<,>))) - .UsingRegistrationStrategy(RegistrationStrategy.Skip) - .AsImplementedInterfaces() - .WithTransientLifetime()); + services.Scan(scan => + scan.FromAssemblies(assemblies) + .AddClasses(classes => classes.AssignableTo(typeof(IRetryableRequest<,>))) + .UsingRegistrationStrategy(RegistrationStrategy.Skip) + .AsImplementedInterfaces() + .WithTransientLifetime() + ); return services; } - public static IServiceCollection AddMediaterFallbackPolicy(IServiceCollection services, - IReadOnlyList assemblies) + public static IServiceCollection AddMediaterFallbackPolicy( + IServiceCollection services, + IReadOnlyList assemblies + ) { services.AddTransient(typeof(IPipelineBehavior<,>), typeof(FallbackBehavior<,>)); - services.Scan(scan => scan - .FromAssemblies(assemblies) - .AddClasses(classes => classes.AssignableTo(typeof(IFallbackHandler<,>))) - .UsingRegistrationStrategy(RegistrationStrategy.Skip) - .AsImplementedInterfaces() - .WithTransientLifetime()); + services.Scan(scan => + scan.FromAssemblies(assemblies) + .AddClasses(classes => classes.AssignableTo(typeof(IFallbackHandler<,>))) + .UsingRegistrationStrategy(RegistrationStrategy.Skip) + .AsImplementedInterfaces() + .WithTransientLifetime() + ); return services; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Resiliency/Fallback/FallbackBehavior.cs b/src/BuildingBlocks/BulidingBlocks/Resiliency/Fallback/FallbackBehavior.cs index 00b25ef..8775865 100644 --- a/src/BuildingBlocks/BulidingBlocks/Resiliency/Fallback/FallbackBehavior.cs +++ b/src/BuildingBlocks/BulidingBlocks/Resiliency/Fallback/FallbackBehavior.cs @@ -19,15 +19,20 @@ public class FallbackBehavior : IPipelineBehavior> _fallbackHandlers; private readonly ILogger> _logger; - public FallbackBehavior(IEnumerable> fallbackHandlers, - ILogger> logger) + public FallbackBehavior( + IEnumerable> fallbackHandlers, + ILogger> logger + ) { _fallbackHandlers = fallbackHandlers; _logger = logger; } - public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken - cancellationToken) + public async Task Handle( + TRequest request, + RequestHandlerDelegate next, + CancellationToken cancellationToken + ) { var fallbackHandler = _fallbackHandlers.FirstOrDefault(); if (fallbackHandler == null) @@ -38,14 +43,15 @@ public async Task Handle(TRequest request, RequestHandlerDelegate() .FallbackAsync(async cancellationToken => { - _logger.LogDebug("Initial handler failed. Falling back to `{FullName}@HandleFallback`", - fallbackHandler.GetType().FullName); - return await fallbackHandler.HandleFallbackAsync(request, cancellationToken) - .ConfigureAwait(false); + _logger.LogDebug( + "Initial handler failed. Falling back to `{FullName}@HandleFallback`", + fallbackHandler.GetType().FullName + ); + return await fallbackHandler.HandleFallbackAsync(request, cancellationToken).ConfigureAwait(false); }); var response = await fallbackPolicy.ExecuteAsync(async () => await next()); return response; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Resiliency/Fallback/IFallbackHandler.cs b/src/BuildingBlocks/BulidingBlocks/Resiliency/Fallback/IFallbackHandler.cs index c6c37b2..608feca 100644 --- a/src/BuildingBlocks/BulidingBlocks/Resiliency/Fallback/IFallbackHandler.cs +++ b/src/BuildingBlocks/BulidingBlocks/Resiliency/Fallback/IFallbackHandler.cs @@ -4,7 +4,8 @@ namespace BuildingBlocks.Resiliency.Fallback; -public interface IFallbackHandler where TRequest : IRequest +public interface IFallbackHandler + where TRequest : IRequest { Task HandleFallbackAsync(TRequest request, CancellationToken cancellationToken); -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/HttpCircuitBreakerPolicies.cs b/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/HttpCircuitBreakerPolicies.cs index e592965..fc8190e 100644 --- a/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/HttpCircuitBreakerPolicies.cs +++ b/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/HttpCircuitBreakerPolicies.cs @@ -9,24 +9,39 @@ namespace BuildingBlocks.Resiliency; public static class HttpCircuitBreakerPolicies { - public static AsyncCircuitBreakerPolicy GetHttpCircuitBreakerPolicy(ILogger logger, - ICircuitBreakerPolicyConfig circuitBreakerPolicyConfig) + public static AsyncCircuitBreakerPolicy GetHttpCircuitBreakerPolicy( + ILogger logger, + ICircuitBreakerPolicyConfig circuitBreakerPolicyConfig + ) { - return HttpPolicyBuilders.GetBaseBuilder() - .CircuitBreakerAsync(circuitBreakerPolicyConfig.RetryCount + 1, + return HttpPolicyBuilders + .GetBaseBuilder() + .CircuitBreakerAsync( + circuitBreakerPolicyConfig.RetryCount + 1, TimeSpan.FromSeconds(circuitBreakerPolicyConfig.BreakDuration), (result, breakDuration) => { OnHttpBreak(result, breakDuration, circuitBreakerPolicyConfig.RetryCount, logger); }, - () => { OnHttpReset(logger); }); + () => + { + OnHttpReset(logger); + } + ); } - public static void OnHttpBreak(DelegateResult result, TimeSpan breakDuration, - int retryCount, ILogger logger) + public static void OnHttpBreak( + DelegateResult result, + TimeSpan breakDuration, + int retryCount, + ILogger logger + ) { - logger.LogWarning("Service shutdown during {breakDuration} after {DefaultRetryCount} failed retries.", - breakDuration, retryCount); + logger.LogWarning( + "Service shutdown during {breakDuration} after {DefaultRetryCount} failed retries.", + breakDuration, + retryCount + ); throw new BrokenCircuitException("Service inoperative. Please try again later"); } @@ -34,4 +49,4 @@ public static void OnHttpReset(ILogger logger) { logger.LogInformation("Service restarted."); } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/HttpClientBuilderExtensions.cs b/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/HttpClientBuilderExtensions.cs index bcfc7fe..5d0345b 100644 --- a/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/HttpClientBuilderExtensions.cs +++ b/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/HttpClientBuilderExtensions.cs @@ -7,42 +7,56 @@ namespace BuildingBlocks.Resiliency; public static class HttpClientBuilderExtensions { - public static IHttpClientBuilder AddCustomPolicyHandlers(this IHttpClientBuilder httpClientBuilder, - IConfiguration configuration, string policySectionName) + public static IHttpClientBuilder AddCustomPolicyHandlers( + this IHttpClientBuilder httpClientBuilder, + IConfiguration configuration, + string policySectionName + ) { var policyConfig = new PolicyConfig(); configuration.Bind(policySectionName, policyConfig); - var circuitBreakerPolicyConfig = (ICircuitBreakerPolicyConfig) policyConfig; - var retryPolicyConfig = (IRetryPolicyConfig) policyConfig; + var circuitBreakerPolicyConfig = (ICircuitBreakerPolicyConfig)policyConfig; + var retryPolicyConfig = (IRetryPolicyConfig)policyConfig; - return httpClientBuilder.AddRetryPolicyHandler(retryPolicyConfig) + return httpClientBuilder + .AddRetryPolicyHandler(retryPolicyConfig) .AddCircuitBreakerHandler(circuitBreakerPolicyConfig); } - public static IHttpClientBuilder AddRetryPolicyHandler(this IHttpClientBuilder httpClientBuilder, - IRetryPolicyConfig retryPolicyConfig) + public static IHttpClientBuilder AddRetryPolicyHandler( + this IHttpClientBuilder httpClientBuilder, + IRetryPolicyConfig retryPolicyConfig + ) { //https://stackoverflow.com/questions/53604295/logging-polly-wait-and-retry-policy-asp-net-core-2-1 - return httpClientBuilder.AddPolicyHandler((sp, _) => - { - var loggerFactory = sp.GetRequiredService(); - var retryLogger = loggerFactory.CreateLogger("PollyHttpRetryPoliciesLogger"); - - return HttpRetryPolicies.GetHttpRetryPolicy(retryLogger, retryPolicyConfig); - }); + return httpClientBuilder.AddPolicyHandler( + (sp, _) => + { + var loggerFactory = sp.GetRequiredService(); + var retryLogger = loggerFactory.CreateLogger("PollyHttpRetryPoliciesLogger"); + + return HttpRetryPolicies.GetHttpRetryPolicy(retryLogger, retryPolicyConfig); + } + ); } - public static IHttpClientBuilder AddCircuitBreakerHandler(this IHttpClientBuilder httpClientBuilder, - ICircuitBreakerPolicyConfig circuitBreakerPolicyConfig) + public static IHttpClientBuilder AddCircuitBreakerHandler( + this IHttpClientBuilder httpClientBuilder, + ICircuitBreakerPolicyConfig circuitBreakerPolicyConfig + ) { - return httpClientBuilder.AddPolicyHandler((sp, _) => - { - var loggerFactory = sp.GetRequiredService(); - var circuitBreakerLogger = loggerFactory.CreateLogger("PollyHttpCircuitBreakerPoliciesLogger"); - - return HttpCircuitBreakerPolicies.GetHttpCircuitBreakerPolicy(circuitBreakerLogger, - circuitBreakerPolicyConfig); - }); + return httpClientBuilder.AddPolicyHandler( + (sp, _) => + { + var loggerFactory = sp.GetRequiredService(); + var circuitBreakerLogger = loggerFactory.CreateLogger("PollyHttpCircuitBreakerPoliciesLogger"); + + return HttpCircuitBreakerPolicies.GetHttpCircuitBreakerPolicy( + circuitBreakerLogger, + circuitBreakerPolicyConfig + ); + } + ); } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/HttpPolicyBuilders.cs b/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/HttpPolicyBuilders.cs index cc64bd5..319cb4b 100644 --- a/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/HttpPolicyBuilders.cs +++ b/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/HttpPolicyBuilders.cs @@ -10,4 +10,4 @@ public static PolicyBuilder GetBaseBuilder() { return HttpPolicyExtensions.HandleTransientHttpError(); } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/HttpRetryPolicies.cs b/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/HttpRetryPolicies.cs index 8a93059..127db6d 100644 --- a/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/HttpRetryPolicies.cs +++ b/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/HttpRetryPolicies.cs @@ -9,33 +9,48 @@ namespace BuildingBlocks.Resiliency; public static class HttpRetryPolicies { - public static AsyncRetryPolicy GetHttpRetryPolicy(ILogger logger, - IRetryPolicyConfig retryPolicyConfig) + public static AsyncRetryPolicy GetHttpRetryPolicy( + ILogger logger, + IRetryPolicyConfig retryPolicyConfig + ) { - return HttpPolicyBuilders.GetBaseBuilder() - .WaitAndRetryAsync(retryPolicyConfig.RetryCount, + return HttpPolicyBuilders + .GetBaseBuilder() + .WaitAndRetryAsync( + retryPolicyConfig.RetryCount, ComputeDuration, (result, timeSpan, retryCount, context) => { OnHttpRetry(result, timeSpan, retryCount, context, logger); - }); + } + ); } - private static void OnHttpRetry(DelegateResult result, TimeSpan timeSpan, int - retryCount, Context context, ILogger logger) + private static void OnHttpRetry( + DelegateResult result, + TimeSpan timeSpan, + int retryCount, + Context context, + ILogger logger + ) { if (result.Result != null) logger.LogWarning( "Request failed with {StatusCode}. Waiting {timeSpan} before next retry. Retry attempt {retryCount}", - result.Result.StatusCode, timeSpan, retryCount); + result.Result.StatusCode, + timeSpan, + retryCount + ); else logger.LogWarning( "Request failed because network failure. Waiting {timeSpan} before next retry. Retry attempt {retryCount}", - timeSpan, retryCount); + timeSpan, + retryCount + ); } private static TimeSpan ComputeDuration(int input) { return TimeSpan.FromSeconds(Math.Pow(2, input)) + TimeSpan.FromMilliseconds(new Random().Next(0, 100)); } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/IRetryableRequest.cs b/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/IRetryableRequest.cs index 92e6d3d..7235962 100644 --- a/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/IRetryableRequest.cs +++ b/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/IRetryableRequest.cs @@ -2,10 +2,11 @@ namespace BuildingBlocks.Resiliency; -public interface IRetryableRequest where TRequest : IRequest +public interface IRetryableRequest + where TRequest : IRequest { int RetryAttempts => 1; int RetryDelay => 250; bool RetryWithExponentialBackoff => false; int ExceptionsAllowedBeforeCircuitTrip => 1; -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/RetryBehavior.cs b/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/RetryBehavior.cs index 3d0e5a7..1481936 100644 --- a/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/RetryBehavior.cs +++ b/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/RetryBehavior.cs @@ -15,15 +15,20 @@ public class RetryBehavior : IPipelineBehavior> _logger; private readonly IEnumerable> _retryHandlers; - public RetryBehavior(IEnumerable> retryHandlers, - ILogger> logger) + public RetryBehavior( + IEnumerable> retryHandlers, + ILogger> logger + ) { _retryHandlers = retryHandlers; _logger = logger; } - public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken - cancellationToken) + public async Task Handle( + TRequest request, + RequestHandlerDelegate next, + CancellationToken cancellationToken + ) { var retryHandler = _retryHandlers.FirstOrDefault(); //var retryAttr = typeof(TRequest).GetCustomAttribute(); @@ -34,25 +39,34 @@ public async Task Handle(TRequest request, RequestHandlerDelegate .Handle() - .CircuitBreakerAsync(retryHandler.ExceptionsAllowedBeforeCircuitTrip, TimeSpan.FromMilliseconds(5000), - (exception, things) => { _logger.LogDebug("Circuit Tripped!"); }, - () => { }); + .CircuitBreakerAsync( + retryHandler.ExceptionsAllowedBeforeCircuitTrip, + TimeSpan.FromMilliseconds(5000), + (exception, things) => + { + _logger.LogDebug("Circuit Tripped!"); + }, + () => { } + ); var retryPolicy = Policy .Handle() - .WaitAndRetryAsync(retryHandler.RetryAttempts, retryAttempt => - { - var retryDelay = retryHandler.RetryWithExponentialBackoff - ? TimeSpan.FromMilliseconds(Math.Pow(2, retryAttempt) * retryHandler.RetryDelay) - : TimeSpan.FromMilliseconds(retryHandler.RetryDelay); + .WaitAndRetryAsync( + retryHandler.RetryAttempts, + retryAttempt => + { + var retryDelay = retryHandler.RetryWithExponentialBackoff + ? TimeSpan.FromMilliseconds(Math.Pow(2, retryAttempt) * retryHandler.RetryDelay) + : TimeSpan.FromMilliseconds(retryHandler.RetryDelay); - _logger.LogDebug("Retrying, waiting {RetryDelay}...", retryDelay); + _logger.LogDebug("Retrying, waiting {RetryDelay}...", retryDelay); - return retryDelay; - }); + return retryDelay; + } + ); var response = await retryPolicy.ExecuteAsync(async () => await next()); return response; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/RetryPolicyAttribute.cs b/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/RetryPolicyAttribute.cs index 987b9de..c0fd808 100644 --- a/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/RetryPolicyAttribute.cs +++ b/src/BuildingBlocks/BulidingBlocks/Resiliency/Retry/RetryPolicyAttribute.cs @@ -24,7 +24,8 @@ public int RetryCount get => _retryCount; set { - if (value < 1) throw new ArgumentException("Retry count must be higher than 1.", nameof(value)); + if (value < 1) + throw new ArgumentException("Retry count must be higher than 1.", nameof(value)); _retryCount = value; } @@ -40,9 +41,10 @@ public int SleepDuration get => _sleepDuration; set { - if (value < 1) throw new ArgumentException("Sleep duration must be higher than 1ms.", nameof(value)); + if (value < 1) + throw new ArgumentException("Sleep duration must be higher than 1ms.", nameof(value)); _sleepDuration = value; } } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/ApiKey.cs b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/ApiKey.cs index 4fa1045..e43fcf3 100644 --- a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/ApiKey.cs +++ b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/ApiKey.cs @@ -19,4 +19,4 @@ public ApiKey(int id, string owner, string key, DateTime created, IReadOnlyColle public string Key { get; } public DateTime Created { get; } public IReadOnlyCollection Roles { get; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/ApiKeyAuthenticationHandler.cs b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/ApiKeyAuthenticationHandler.cs index 6209f18..f4947ca 100644 --- a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/ApiKeyAuthenticationHandler.cs +++ b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/ApiKeyAuthenticationHandler.cs @@ -25,7 +25,9 @@ public ApiKeyAuthenticationHandler( ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, - IGetApiKeyQuery getApiKeyQuery) : base(options, logger, encoder, clock) + IGetApiKeyQuery getApiKeyQuery + ) + : base(options, logger, encoder, clock) { _getApiKeyQuery = getApiKeyQuery ?? throw new ArgumentNullException(nameof(getApiKeyQuery)); } @@ -35,29 +37,29 @@ protected override async Task HandleAuthenticateAsync() StringValues apiKeyQueryValues = ""; StringValues apiKeyHeaderValues = ""; - if (Request.Headers.TryGetValue(ApiKeyConstants.HeaderName, out apiKeyHeaderValues) == false && - Request.Query.TryGetValue(ApiKeyConstants.HeaderName, out apiKeyQueryValues) == false) + if ( + Request.Headers.TryGetValue(ApiKeyConstants.HeaderName, out apiKeyHeaderValues) == false + && Request.Query.TryGetValue(ApiKeyConstants.HeaderName, out apiKeyQueryValues) == false + ) return AuthenticateResult.NoResult(); var providedApiKey = apiKeyHeaderValues.FirstOrDefault() ?? apiKeyQueryValues.FirstOrDefault(); - if ((apiKeyHeaderValues.Count == 0 && apiKeyQueryValues.Count == 0) || string.IsNullOrWhiteSpace - (providedApiKey)) + if ( + (apiKeyHeaderValues.Count == 0 && apiKeyQueryValues.Count == 0) || string.IsNullOrWhiteSpace(providedApiKey) + ) return AuthenticateResult.NoResult(); var existingApiKey = await _getApiKeyQuery.ExecuteAsync(providedApiKey); if (existingApiKey != null) { - var claims = new List - { - new(ClaimTypes.Name, existingApiKey.Owner) - }; + var claims = new List { new(ClaimTypes.Name, existingApiKey.Owner) }; claims.AddRange(existingApiKey.Roles.Select(role => new Claim(ClaimTypes.Role, role))); var identity = new ClaimsIdentity(claims, Options.AuthenticationType); - var identities = new List {identity}; + var identities = new List { identity }; var principal = new ClaimsPrincipal(identities); var ticket = new AuthenticationTicket(principal, Options.Scheme); @@ -87,10 +89,11 @@ protected override async Task HandleForbiddenAsync(AuthenticationProperties prop public static class DefaultJsonSerializerOptions { - public static JsonSerializerOptions Options => new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; + public static JsonSerializerOptions Options => + new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/ApiKeyAuthenticationOptions.cs b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/ApiKeyAuthenticationOptions.cs index 1bc3eea..84f3c24 100644 --- a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/ApiKeyAuthenticationOptions.cs +++ b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/ApiKeyAuthenticationOptions.cs @@ -7,4 +7,4 @@ public class ApiKeyAuthenticationOptions : AuthenticationSchemeOptions public const string DefaultScheme = "ApiKey"; public string AuthenticationType = DefaultScheme; public string Scheme => DefaultScheme; -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/ApiKeyConstants.cs b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/ApiKeyConstants.cs index 8fcb829..df4b908 100644 --- a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/ApiKeyConstants.cs +++ b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/ApiKeyConstants.cs @@ -3,4 +3,4 @@ namespace BuildingBlocks.Security.ApiKey; public class ApiKeyConstants { public const string HeaderName = "X-Api-Key"; -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/AuthenticationBuilderExtensions.cs b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/AuthenticationBuilderExtensions.cs index 06ea6cf..ddbb7de 100644 --- a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/AuthenticationBuilderExtensions.cs +++ b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/AuthenticationBuilderExtensions.cs @@ -5,10 +5,14 @@ namespace BuildingBlocks.Security.ApiKey; public static class AuthenticationBuilderExtensions { - public static AuthenticationBuilder AddApiKeySupport(this AuthenticationBuilder authenticationBuilder, - Action options) + public static AuthenticationBuilder AddApiKeySupport( + this AuthenticationBuilder authenticationBuilder, + Action options + ) { return authenticationBuilder.AddScheme( - ApiKeyAuthenticationOptions.DefaultScheme, options); + ApiKeyAuthenticationOptions.DefaultScheme, + options + ); } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/OnlyAdminsAuthorizationHandler.cs b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/OnlyAdminsAuthorizationHandler.cs index 23cc72a..97e3576 100644 --- a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/OnlyAdminsAuthorizationHandler.cs +++ b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/OnlyAdminsAuthorizationHandler.cs @@ -5,11 +5,14 @@ namespace BuildingBlocks.Security.ApiKey.Authorization; public class OnlyAdminsAuthorizationHandler : AuthorizationHandler { - protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, - OnlyAdminsRequirement requirement) + protected override Task HandleRequirementAsync( + AuthorizationHandlerContext context, + OnlyAdminsRequirement requirement + ) { - if (context.User.IsInRole(Roles.Admin)) context.Succeed(requirement); + if (context.User.IsInRole(Roles.Admin)) + context.Succeed(requirement); return Task.CompletedTask; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/OnlyAdminsRequirement.cs b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/OnlyAdminsRequirement.cs index 19eda86..34d9aae 100644 --- a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/OnlyAdminsRequirement.cs +++ b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/OnlyAdminsRequirement.cs @@ -2,6 +2,4 @@ namespace BuildingBlocks.Security.ApiKey.Authorization; -public class OnlyAdminsRequirement : IAuthorizationRequirement -{ -} \ No newline at end of file +public class OnlyAdminsRequirement : IAuthorizationRequirement { } diff --git a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/OnlyCustomersAuthorizationHandler.cs b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/OnlyCustomersAuthorizationHandler.cs index f5d6d30..17a0939 100644 --- a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/OnlyCustomersAuthorizationHandler.cs +++ b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/OnlyCustomersAuthorizationHandler.cs @@ -5,11 +5,14 @@ namespace BuildingBlocks.Security.ApiKey.Authorization; public class OnlyCustomersAuthorizationHandler : AuthorizationHandler { - protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, - OnlyCustomersRequirement requirement) + protected override Task HandleRequirementAsync( + AuthorizationHandlerContext context, + OnlyCustomersRequirement requirement + ) { - if (context.User.IsInRole(Roles.Customer)) context.Succeed(requirement); + if (context.User.IsInRole(Roles.Customer)) + context.Succeed(requirement); return Task.CompletedTask; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/OnlyCustomersRequirement.cs b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/OnlyCustomersRequirement.cs index e1b9e5e..1bc0a0e 100644 --- a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/OnlyCustomersRequirement.cs +++ b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/OnlyCustomersRequirement.cs @@ -2,6 +2,4 @@ namespace BuildingBlocks.Security.ApiKey.Authorization; -public class OnlyCustomersRequirement : IAuthorizationRequirement -{ -} \ No newline at end of file +public class OnlyCustomersRequirement : IAuthorizationRequirement { } diff --git a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/OnlyThirdPartiesAuthorizationHandler.cs b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/OnlyThirdPartiesAuthorizationHandler.cs index 7e03442..8436aba 100644 --- a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/OnlyThirdPartiesAuthorizationHandler.cs +++ b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/OnlyThirdPartiesAuthorizationHandler.cs @@ -5,11 +5,14 @@ namespace BuildingBlocks.Security.ApiKey.Authorization; public class OnlyThirdPartiesAuthorizationHandler : AuthorizationHandler { - protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, - OnlyThirdPartiesRequirement requirement) + protected override Task HandleRequirementAsync( + AuthorizationHandlerContext context, + OnlyThirdPartiesRequirement requirement + ) { - if (context.User.IsInRole(Roles.ThirdParty)) context.Succeed(requirement); + if (context.User.IsInRole(Roles.ThirdParty)) + context.Succeed(requirement); return Task.CompletedTask; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/OnlyThirdPartiesRequirement.cs b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/OnlyThirdPartiesRequirement.cs index 0eacb95..a3d6865 100644 --- a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/OnlyThirdPartiesRequirement.cs +++ b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/OnlyThirdPartiesRequirement.cs @@ -2,6 +2,4 @@ namespace BuildingBlocks.Security.ApiKey.Authorization; -public class OnlyThirdPartiesRequirement : IAuthorizationRequirement -{ -} \ No newline at end of file +public class OnlyThirdPartiesRequirement : IAuthorizationRequirement { } diff --git a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/Policies.cs b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/Policies.cs index 0c2bea9..0937111 100644 --- a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/Policies.cs +++ b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/Policies.cs @@ -5,4 +5,4 @@ public static class Policies public const string OnlyCustomers = nameof(OnlyCustomers); public const string OnlyAdmins = nameof(OnlyAdmins); public const string OnlyThirdParties = nameof(OnlyThirdParties); -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/Roles.cs b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/Roles.cs index d20a724..86b63bf 100644 --- a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/Roles.cs +++ b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/Authorization/Roles.cs @@ -5,4 +5,4 @@ public static class Roles public const string Customer = "Customer"; public const string Admin = "Admin"; public const string ThirdParty = "Third Party"; -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/IGetApiKeyQuery.cs b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/IGetApiKeyQuery.cs index a4c3e30..1cb1f67 100644 --- a/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/IGetApiKeyQuery.cs +++ b/src/BuildingBlocks/BulidingBlocks/Security/ApiKey/IGetApiKeyQuery.cs @@ -5,4 +5,4 @@ namespace BuildingBlocks.Security.ApiKey; public interface IGetApiKeyQuery { Task ExecuteAsync(string providedApiKey); -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Security/ForbiddenProblemDetails.cs b/src/BuildingBlocks/BulidingBlocks/Security/ForbiddenProblemDetails.cs index 9d13413..957b31b 100644 --- a/src/BuildingBlocks/BulidingBlocks/Security/ForbiddenProblemDetails.cs +++ b/src/BuildingBlocks/BulidingBlocks/Security/ForbiddenProblemDetails.cs @@ -11,4 +11,4 @@ public ForbiddenProblemDetails(string details = null) Status = 403; Type = "https://httpstatuses.com/403"; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Security/UnauthorizedProblemDetails.cs b/src/BuildingBlocks/BulidingBlocks/Security/UnauthorizedProblemDetails.cs index a1605da..9ce64ef 100644 --- a/src/BuildingBlocks/BulidingBlocks/Security/UnauthorizedProblemDetails.cs +++ b/src/BuildingBlocks/BulidingBlocks/Security/UnauthorizedProblemDetails.cs @@ -11,4 +11,4 @@ public UnauthorizedProblemDetails(string details = null) Status = 401; Type = "https://httpstatuses.com/401"; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Swagger/ConfigureSwaggerOptions.cs b/src/BuildingBlocks/BulidingBlocks/Swagger/ConfigureSwaggerOptions.cs index 92a478c..257d2da 100644 --- a/src/BuildingBlocks/BulidingBlocks/Swagger/ConfigureSwaggerOptions.cs +++ b/src/BuildingBlocks/BulidingBlocks/Swagger/ConfigureSwaggerOptions.cs @@ -9,7 +9,6 @@ namespace BuildingBlocks.Swagger; - public class ConfigureSwaggerOptions : IConfigureOptions { private readonly IApiVersionDescriptionProvider provider; @@ -44,8 +43,8 @@ private OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description) Version = description.ApiVersion.ToString(), Title = _options?.Title ?? "APIs", Description = "An application with Swagger, Swashbuckle, and API versioning.", - Contact = new OpenApiContact {Name = "", Email = ""}, - License = new OpenApiLicense {Name = "MIT", Url = new Uri("https://opensource.org/licenses/MIT")} + Contact = new OpenApiContact { Name = "", Email = "" }, + License = new OpenApiLicense { Name = "MIT", Url = new Uri("https://opensource.org/licenses/MIT") } }; if (description.IsDeprecated) @@ -57,9 +56,7 @@ private OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description) { if (policy.Date is DateTimeOffset when) { - text.Append(" The API will be sunset on ") - .Append(when.Date.ToShortDateString()) - .Append('.'); + text.Append(" The API will be sunset on ").Append(when.Date.ToShortDateString()).Append('.'); } if (policy.HasLinks) diff --git a/src/BuildingBlocks/BulidingBlocks/Swagger/ServiceCollectionExtensions.cs b/src/BuildingBlocks/BulidingBlocks/Swagger/ServiceCollectionExtensions.cs index 91c8d76..6e4acd5 100644 --- a/src/BuildingBlocks/BulidingBlocks/Swagger/ServiceCollectionExtensions.cs +++ b/src/BuildingBlocks/BulidingBlocks/Swagger/ServiceCollectionExtensions.cs @@ -17,64 +17,64 @@ public static class ServiceCollectionExtensions public static IServiceCollection AddCustomSwagger( this IServiceCollection services, IConfiguration configuration, - Assembly assembly) + Assembly assembly + ) { // https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/openapi services.AddEndpointsApiExplorer(); services.AddTransient, ConfigureSwaggerOptions>(); - services.AddOptions().Bind(configuration.GetSection(nameof(SwaggerOptions))) + services + .AddOptions() + .Bind(configuration.GetSection(nameof(SwaggerOptions))) .ValidateDataAnnotations(); - services.AddSwaggerGen( - options => - { - options.OperationFilter(); - //options.OperationFilter(); + services.AddSwaggerGen(options => + { + options.OperationFilter(); + //options.OperationFilter(); - var xmlFile = XmlCommentsFilePath(assembly); - if (File.Exists(xmlFile)) options.IncludeXmlComments(xmlFile); + var xmlFile = XmlCommentsFilePath(assembly); + if (File.Exists(xmlFile)) + options.IncludeXmlComments(xmlFile); - // https://github.com/domaindrivendev/Swashbuckle.AspNetCore#add-security-definitions-and-requirements - // https://swagger.io/docs/specification/authentication/ - // https://medium.com/@niteshsinghal85/assign-specific-authorization-scheme-to-endpoint-in-swagger-ui-in-net-core-cd84d2a2ebd7 - var bearerScheme = new OpenApiSecurityScheme() - { - Type = SecuritySchemeType.Http, - Name = JwtBearerDefaults.AuthenticationScheme, - Scheme = JwtBearerDefaults.AuthenticationScheme, - Reference = new() - { - Type = ReferenceType.SecurityScheme, Id = JwtBearerDefaults.AuthenticationScheme - } - }; - - var apiKeyScheme = new OpenApiSecurityScheme - { - Description = "Api key needed to access the endpoints. X-Api-Key: My_API_Key", - In = ParameterLocation.Header, - Name = "X-Api-Key", - Scheme = "ApiKey", - Type = SecuritySchemeType.ApiKey, - Reference = new() - { - Type = ReferenceType.SecurityScheme, Id = "X-Api-Key" - } - }; - - options.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, bearerScheme); - options.AddSecurityDefinition("X-Api-Key", apiKeyScheme); - - options.AddSecurityRequirement(new OpenApiSecurityRequirement + // https://github.com/domaindrivendev/Swashbuckle.AspNetCore#add-security-definitions-and-requirements + // https://swagger.io/docs/specification/authentication/ + // https://medium.com/@niteshsinghal85/assign-specific-authorization-scheme-to-endpoint-in-swagger-ui-in-net-core-cd84d2a2ebd7 + var bearerScheme = new OpenApiSecurityScheme() + { + Type = SecuritySchemeType.Http, + Name = JwtBearerDefaults.AuthenticationScheme, + Scheme = JwtBearerDefaults.AuthenticationScheme, + Reference = new() { Type = ReferenceType.SecurityScheme, Id = JwtBearerDefaults.AuthenticationScheme } + }; + + var apiKeyScheme = new OpenApiSecurityScheme + { + Description = "Api key needed to access the endpoints. X-Api-Key: My_API_Key", + In = ParameterLocation.Header, + Name = "X-Api-Key", + Scheme = "ApiKey", + Type = SecuritySchemeType.ApiKey, + Reference = new() { Type = ReferenceType.SecurityScheme, Id = "X-Api-Key" } + }; + + options.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, bearerScheme); + options.AddSecurityDefinition("X-Api-Key", apiKeyScheme); + + options.AddSecurityRequirement( + new OpenApiSecurityRequirement { - {bearerScheme, Array.Empty()}, {apiKeyScheme, Array.Empty()} - }); + { bearerScheme, Array.Empty() }, + { apiKeyScheme, Array.Empty() } + } + ); - options.ResolveConflictingActions(apiDescriptions => apiDescriptions.First()); + options.ResolveConflictingActions(apiDescriptions => apiDescriptions.First()); - // Enables Swagger annotations (SwaggerOperationAttribute, SwaggerParameterAttribute etc.) - options.EnableAnnotations(); - }); + // Enables Swagger annotations (SwaggerOperationAttribute, SwaggerParameterAttribute etc.) + options.EnableAnnotations(); + }); static string XmlCommentsFilePath(Assembly assembly) { @@ -89,20 +89,19 @@ static string XmlCommentsFilePath(Assembly assembly) public static WebApplication UseCustomSwagger(this WebApplication app) { app.UseSwagger(); - app.UseSwaggerUI( - options => - { - var descriptions = app.DescribeApiVersions(); + app.UseSwaggerUI(options => + { + var descriptions = app.DescribeApiVersions(); - // build a swagger endpoint for each discovered API version - foreach (var description in descriptions) - { - var url = $"/swagger/{description.GroupName}/swagger.json"; - var name = description.GroupName.ToUpperInvariant(); - options.SwaggerEndpoint(url, name); - } - }); + // build a swagger endpoint for each discovered API version + foreach (var description in descriptions) + { + var url = $"/swagger/{description.GroupName}/swagger.json"; + var name = description.GroupName.ToUpperInvariant(); + options.SwaggerEndpoint(url, name); + } + }); return app; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Swagger/SwaggerDefaultValues.cs b/src/BuildingBlocks/BulidingBlocks/Swagger/SwaggerDefaultValues.cs index 53bf26d..d66e004 100644 --- a/src/BuildingBlocks/BulidingBlocks/Swagger/SwaggerDefaultValues.cs +++ b/src/BuildingBlocks/BulidingBlocks/Swagger/SwaggerDefaultValues.cs @@ -49,10 +49,12 @@ public void Apply(OpenApiOperation operation, OperationFilterContext context) parameter.Description = description.ModelMetadata?.Description; } - if (parameter.Schema.Default == null && - description.DefaultValue != null && - description.DefaultValue is not DBNull && - description.ModelMetadata is ModelMetadata modelMetadata) + if ( + parameter.Schema.Default == null + && description.DefaultValue != null + && description.DefaultValue is not DBNull + && description.ModelMetadata is ModelMetadata modelMetadata + ) { // REF: https://github.com/Microsoft/aspnet-api-versioning/issues/429#issuecomment-605402330 var json = JsonSerializer.Serialize(description.DefaultValue, modelMetadata.ModelType); diff --git a/src/BuildingBlocks/BulidingBlocks/Swagger/SwaggerOptions.cs b/src/BuildingBlocks/BulidingBlocks/Swagger/SwaggerOptions.cs index cdfa95d..e434d0a 100644 --- a/src/BuildingBlocks/BulidingBlocks/Swagger/SwaggerOptions.cs +++ b/src/BuildingBlocks/BulidingBlocks/Swagger/SwaggerOptions.cs @@ -5,4 +5,4 @@ public class SwaggerOptions public string Title { get; set; } public string Name { get; set; } public string Version { get; set; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Utils/EnumExtensions.cs b/src/BuildingBlocks/BulidingBlocks/Utils/EnumExtensions.cs index 0150aa3..1bb18ef 100644 --- a/src/BuildingBlocks/BulidingBlocks/Utils/EnumExtensions.cs +++ b/src/BuildingBlocks/BulidingBlocks/Utils/EnumExtensions.cs @@ -8,14 +8,13 @@ public static class EnumExtensions { // This extension method is broken out so you can use a similar pattern with // other MetaData elements in the future. This is your base method for each. - public static T GetAttribute(this Enum value) where T : Attribute + public static T GetAttribute(this Enum value) + where T : Attribute { var type = value.GetType(); var memberInfo = type.GetMember(value.ToString()); var attributes = memberInfo[0].GetCustomAttributes(typeof(T), false); - return attributes.Length > 0 - ? (T) attributes[0] - : null; + return attributes.Length > 0 ? (T)attributes[0] : null; } // This method creates a specific call to the above method, requesting the @@ -25,4 +24,4 @@ public static string ToName(this Enum value) var attribute = value.GetAttribute(); return attribute == null ? value.ToString() : attribute.Description; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Utils/EnumerableExtensions.cs b/src/BuildingBlocks/BulidingBlocks/Utils/EnumerableExtensions.cs index 42fe1a8..fac1f4e 100644 --- a/src/BuildingBlocks/BulidingBlocks/Utils/EnumerableExtensions.cs +++ b/src/BuildingBlocks/BulidingBlocks/Utils/EnumerableExtensions.cs @@ -9,4 +9,4 @@ public static class EnumerableExtensions { return source.Select((item, index) => (item, index)); } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Utils/ObjectExtensions.cs b/src/BuildingBlocks/BulidingBlocks/Utils/ObjectExtensions.cs index 88a98c7..9bd84df 100644 --- a/src/BuildingBlocks/BulidingBlocks/Utils/ObjectExtensions.cs +++ b/src/BuildingBlocks/BulidingBlocks/Utils/ObjectExtensions.cs @@ -7,10 +7,11 @@ public static class ObjectExtensions { public static string GetQueryString(this object obj) { - var properties = from p in obj.GetType().GetProperties() + var properties = + from p in obj.GetType().GetProperties() where p.GetValue(obj, null) != null select p.Name + "=" + HttpUtility.UrlEncode(p.GetValue(obj, null).ToString()); return string.Join("&", properties.ToArray()); } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Utils/ReflectionHelpers.cs b/src/BuildingBlocks/BulidingBlocks/Utils/ReflectionHelpers.cs index 0d1e6e2..6c3be8b 100644 --- a/src/BuildingBlocks/BulidingBlocks/Utils/ReflectionHelpers.cs +++ b/src/BuildingBlocks/BulidingBlocks/Utils/ReflectionHelpers.cs @@ -29,19 +29,22 @@ public static string PrettyPrint(this Type type) { return t.Name; } - }); + } + ); } private static string PrettyPrintRecursive(Type type, int depth) { - if (depth > 3) return type.Name; + if (depth > 3) + return type.Name; var nameParts = type.Name.Split('`'); - if (nameParts.Length == 1) return nameParts[0]; + if (nameParts.Length == 1) + return nameParts[0]; var genericArguments = type.GetTypeInfo().GetGenericArguments(); return !type.IsConstructedGenericType ? $"{nameParts[0]}<{new string(',', genericArguments.Length - 1)}>" : $"{nameParts[0]}<{string.Join(",", genericArguments.Select(t => PrettyPrintRecursive(t, depth + 1)))}>"; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Validation/Extensions.cs b/src/BuildingBlocks/BulidingBlocks/Validation/Extensions.cs index 67883ea..35f5371 100644 --- a/src/BuildingBlocks/BulidingBlocks/Validation/Extensions.cs +++ b/src/BuildingBlocks/BulidingBlocks/Validation/Extensions.cs @@ -19,7 +19,8 @@ private static ValidationResultModel ToValidationResultModel(this ValidationResu public static async Task HandleValidationAsync(this IValidator validator, TRequest request) { var validationResult = await validator.ValidateAsync(request); - if (!validationResult.IsValid) throw new ValidationException(validationResult.ToValidationResultModel()); + if (!validationResult.IsValid) + throw new ValidationException(validationResult.ToValidationResultModel()); } public static IServiceCollection AddCustomValidators(this IServiceCollection services, Assembly assembly) @@ -27,4 +28,4 @@ public static IServiceCollection AddCustomValidators(this IServiceCollection ser //https://codewithmukesh.com/blog/mediatr-pipeline-behaviour/ return services.AddValidatorsFromAssembly(assembly); } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Validation/RequestValidationBehavior.cs b/src/BuildingBlocks/BulidingBlocks/Validation/RequestValidationBehavior.cs index 5ee24d4..02acc50 100644 --- a/src/BuildingBlocks/BulidingBlocks/Validation/RequestValidationBehavior.cs +++ b/src/BuildingBlocks/BulidingBlocks/Validation/RequestValidationBehavior.cs @@ -17,16 +17,21 @@ public class RequestValidationBehavior : IPipelineBehavior< private readonly IServiceProvider _serviceProvider; private IValidator _validator; - public RequestValidationBehavior(IServiceProvider serviceProvider, - ILogger> logger) + public RequestValidationBehavior( + IServiceProvider serviceProvider, + ILogger> logger + ) { _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); ; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken - cancellationToken) + public async Task Handle( + TRequest request, + RequestHandlerDelegate next, + CancellationToken cancellationToken + ) { _validator = _serviceProvider.GetService>(); if (_validator is null) @@ -34,11 +39,16 @@ public async Task Handle(TRequest request, RequestHandlerDelegate), typeof(TRequest).Name, - typeof(TResponse).Name); + nameof(RequestValidationBehavior), + typeof(TRequest).Name, + typeof(TResponse).Name + ); - _logger.LogDebug("Handling {FullName} with content {Serialize}", typeof(TRequest).FullName, - JsonSerializer.Serialize(request)); + _logger.LogDebug( + "Handling {FullName} with content {Serialize}", + typeof(TRequest).FullName, + JsonSerializer.Serialize(request) + ); await _validator.HandleValidationAsync(request); @@ -47,4 +57,4 @@ public async Task Handle(TRequest request, RequestHandlerDelegate new ValidationError(error.PropertyName, error.ErrorMessage)) + Errors = validationResult + .Errors.Select(error => new ValidationError(error.PropertyName, error.ErrorMessage)) .ToList(); } - public int StatusCode { get; set; } = (int) HttpStatusCode.BadRequest; + public int StatusCode { get; set; } = (int)HttpStatusCode.BadRequest; public string Message { get; set; } = "Validation Failed."; public List Errors { get; } @@ -24,4 +24,4 @@ public override string ToString() { return JsonSerializer.Serialize(this); } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Web/ApplicationBuilderExtensions.cs b/src/BuildingBlocks/BulidingBlocks/Web/ApplicationBuilderExtensions.cs index a4eabe8..13392b5 100644 --- a/src/BuildingBlocks/BulidingBlocks/Web/ApplicationBuilderExtensions.cs +++ b/src/BuildingBlocks/BulidingBlocks/Web/ApplicationBuilderExtensions.cs @@ -16,29 +16,38 @@ public static class ApplicationBuilderExtensions { public static IApplicationBuilder UseCustomHealthCheck(this IApplicationBuilder app) { - app.UseHealthChecks("/healthz", new HealthCheckOptions - { - Predicate = _ => true, - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse, - ResultStatusCodes = + app.UseHealthChecks( + "/healthz", + new HealthCheckOptions { - [HealthStatus.Healthy] = StatusCodes.Status200OK, - [HealthStatus.Degraded] = StatusCodes.Status500InternalServerError, - [HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse, + ResultStatusCodes = + { + [HealthStatus.Healthy] = StatusCodes.Status200OK, + [HealthStatus.Degraded] = StatusCodes.Status500InternalServerError, + [HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable + } } - }) - .UseHealthChecks("/health", new HealthCheckOptions - { - Predicate = check => !check.Tags.Contains("services"), - AllowCachingResponses = false, - ResponseWriter = WriteResponseAsync - }) - .UseHealthChecks("/health/ready", new HealthCheckOptions - { - Predicate = _ => true, - AllowCachingResponses = false, - ResponseWriter = WriteResponseAsync - }) + ) + .UseHealthChecks( + "/health", + new HealthCheckOptions + { + Predicate = check => !check.Tags.Contains("services"), + AllowCachingResponses = false, + ResponseWriter = WriteResponseAsync + } + ) + .UseHealthChecks( + "/health/ready", + new HealthCheckOptions + { + Predicate = _ => true, + AllowCachingResponses = false, + ResponseWriter = WriteResponseAsync + } + ) .UseHealthChecksUI(setup => { setup.ApiPath = "/healthcheck"; @@ -52,10 +61,7 @@ private static Task WriteResponseAsync(HttpContext context, HealthReport result) { context.Response.ContentType = "application/json; charset=utf-8"; - var options = new JsonWriterOptions - { - Indented = true - }; + var options = new JsonWriterOptions { Indented = true }; using var stream = new MemoryStream(); using (var writer = new Utf8JsonWriter(stream, options)) @@ -78,4 +84,4 @@ private static Task WriteResponseAsync(HttpContext context, HealthReport result) return context.Response.WriteAsync(json); } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Web/BaseController.cs b/src/BuildingBlocks/BulidingBlocks/Web/BaseController.cs index a21f788..d5c500e 100644 --- a/src/BuildingBlocks/BulidingBlocks/Web/BaseController.cs +++ b/src/BuildingBlocks/BulidingBlocks/Web/BaseController.cs @@ -13,8 +13,7 @@ public abstract class BaseController : Controller private IMediator _mediator; - protected IMediator Mediator => - _mediator ??= HttpContext.RequestServices.GetService(); + protected IMediator Mediator => _mediator ??= HttpContext.RequestServices.GetService(); protected IMapper Mapper => _mapper ??= HttpContext.RequestServices.GetService(); -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Web/IPageRequest.cs b/src/BuildingBlocks/BulidingBlocks/Web/IPageRequest.cs index 296e793..937c684 100644 --- a/src/BuildingBlocks/BulidingBlocks/Web/IPageRequest.cs +++ b/src/BuildingBlocks/BulidingBlocks/Web/IPageRequest.cs @@ -10,4 +10,4 @@ public interface IPageRequest public List Sorts { get; set; } public int Page { get; set; } public int PageSize { get; set; } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Web/ServiceCollectionExtensions.cs b/src/BuildingBlocks/BulidingBlocks/Web/ServiceCollectionExtensions.cs index b444b0e..67dc173 100644 --- a/src/BuildingBlocks/BulidingBlocks/Web/ServiceCollectionExtensions.cs +++ b/src/BuildingBlocks/BulidingBlocks/Web/ServiceCollectionExtensions.cs @@ -8,8 +8,10 @@ namespace BuildingBlocks.Web; public static class ServiceCollectionExtensions { - public static void AddCustomVersioning(this IServiceCollection services, - Action configurator = null) + public static void AddCustomVersioning( + this IServiceCollection services, + Action configurator = null + ) { // https://www.meziantou.net/versioning-an-asp-net-core-api.htm // https://dotnetthoughts.net/aspnetcore-api-versioning-with-net-6-minimal-apis/ @@ -18,7 +20,8 @@ public static void AddCustomVersioning(this IServiceCollection services, // https://www.nuget.org/packages/Asp.Versioning.Http // Support versioning in minimal apis with (Asp.Versioning.Http) dll - services.AddApiVersioning(options => + services + .AddApiVersioning(options => { // Add the headers "api-supported-versions" and "api-deprecated-versions" // This is better for discoverability @@ -35,30 +38,31 @@ public static void AddCustomVersioning(this IServiceCollection services, options.ApiVersionReader = ApiVersionReader.Combine( new HeaderApiVersionReader("api-version"), new QueryStringApiVersionReader(), - new UrlSegmentApiVersionReader()); + new UrlSegmentApiVersionReader() + ); configurator?.Invoke(options); }) - .AddApiExplorer( - options => - { - // add the versioned api explorer, which also adds IApiVersionDescriptionProvider service - // note: the specified format code will format the version as "'v'major[.minor][-status]" - options.GroupNameFormat = "'v'VVV"; - - // note1: this option is only necessary when versioning by url segment. - // note2:if we set it to true version will infer automatically in swagger based on mapped versions to api (swagger infer api versions in different tab in header Swagger UI), - // but if we set it to false we should put version for api endpoint manually. - // https://github.com/dotnet/aspnet-api-versioning/issues/909 - options.SubstituteApiVersionInUrl = true; - }) - + .AddApiExplorer(options => + { + // add the versioned api explorer, which also adds IApiVersionDescriptionProvider service + // note: the specified format code will format the version as "'v'major[.minor][-status]" + options.GroupNameFormat = "'v'VVV"; + + // note1: this option is only necessary when versioning by url segment. + // note2:if we set it to true version will infer automatically in swagger based on mapped versions to api (swagger infer api versions in different tab in header Swagger UI), + // but if we set it to false we should put version for api endpoint manually. + // https://github.com/dotnet/aspnet-api-versioning/issues/909 + options.SubstituteApiVersionInUrl = true; + }) // Support versioning in mvc with with (Asp.Versioning.Mvc.ApiExplorer) dll .AddMvc(); // https://www.nuget.org/packages/Asp.Versioning.Mvc.ApiExplorer } - public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, - Action configurator = null) + public static IServiceCollection AddCustomHealthCheck( + this IServiceCollection services, + Action configurator = null + ) { var healCheckBuilder = services.AddHealthChecks(); configurator?.Invoke(healCheckBuilder); @@ -73,15 +77,13 @@ public static IServiceCollection AddCustomHealthCheck(this IServiceCollection se return services; } - public static void Unregister(this IServiceCollection services) { var descriptor = services.FirstOrDefault(d => d.ServiceType == typeof(TService)); services.Remove(descriptor); } - public static void Replace(this IServiceCollection services, - ServiceLifetime lifetime) + public static void Replace(this IServiceCollection services, ServiceLifetime lifetime) { services.Unregister(); services.Add(new ServiceDescriptor(typeof(TService), typeof(TImplementation), lifetime)); @@ -95,10 +97,14 @@ public static void Replace(this IServiceCollection se /// The service collection /// Service type /// Implementation type - private static void TryAddTransientExact(this IServiceCollection services, Type serviceType, - Type implementationType) + private static void TryAddTransientExact( + this IServiceCollection services, + Type serviceType, + Type implementationType + ) { - if (services.Any(reg => reg.ServiceType == serviceType && reg.ImplementationType == implementationType)) return; + if (services.Any(reg => reg.ServiceType == serviceType && reg.ImplementationType == implementationType)) + return; services.AddTransient(serviceType, implementationType); } @@ -111,10 +117,10 @@ private static void TryAddTransientExact(this IServiceCollection services, Type /// The service collection /// Service type /// Implementation type - private static void TryAddScopeExact(this IServiceCollection services, Type serviceType, - Type implementationType) + private static void TryAddScopeExact(this IServiceCollection services, Type serviceType, Type implementationType) { - if (services.Any(reg => reg.ServiceType == serviceType && reg.ImplementationType == implementationType)) return; + if (services.Any(reg => reg.ServiceType == serviceType && reg.ImplementationType == implementationType)) + return; services.AddScoped(serviceType, implementationType); } @@ -127,10 +133,14 @@ private static void TryAddScopeExact(this IServiceCollection services, Type serv /// The service collection /// Service type /// Implementation type - private static void TryAddSingletonExact(this IServiceCollection services, Type serviceType, - Type implementationType) + private static void TryAddSingletonExact( + this IServiceCollection services, + Type serviceType, + Type implementationType + ) { - if (services.Any(reg => reg.ServiceType == serviceType && reg.ImplementationType == implementationType)) return; + if (services.Any(reg => reg.ServiceType == serviceType && reg.ImplementationType == implementationType)) + return; services.AddSingleton(serviceType, implementationType); } @@ -143,8 +153,10 @@ public static void ReplaceScoped(this IServiceCollect services.AddScoped(); } - public static void ReplaceScoped(this IServiceCollection services, - Func implementationFactory) + public static void ReplaceScoped( + this IServiceCollection services, + Func implementationFactory + ) where TService : class { services.Unregister(); @@ -159,8 +171,10 @@ public static void ReplaceTransient(this IServiceColl services.AddTransient(); } - public static void ReplaceTransient(this IServiceCollection services, - Func implementationFactory) + public static void ReplaceTransient( + this IServiceCollection services, + Func implementationFactory + ) where TService : class { services.Unregister(); @@ -175,8 +189,10 @@ public static void ReplaceSingleton(this IServiceColl services.AddSingleton(); } - public static void ReplaceSingleton(this IServiceCollection services, - Func implementationFactory) + public static void ReplaceSingleton( + this IServiceCollection services, + Func implementationFactory + ) where TService : class { services.Unregister(); @@ -192,12 +208,16 @@ public static void RegisterOptions(this IServiceCollection services, I services.AddSingleton(options); } - public static void RegisterOptions(this IServiceCollection services, IConfiguration configuration, - string name) where TOptions : class, new() + public static void RegisterOptions( + this IServiceCollection services, + IConfiguration configuration, + string name + ) + where TOptions : class, new() { var options = new TOptions(); configuration.Bind(name, options); services.AddSingleton(options); } -} \ No newline at end of file +} diff --git a/src/BuildingBlocks/BulidingBlocks/Web/SlugifyParameterTransformer.cs b/src/BuildingBlocks/BulidingBlocks/Web/SlugifyParameterTransformer.cs index c73eb92..9aecf77 100644 --- a/src/BuildingBlocks/BulidingBlocks/Web/SlugifyParameterTransformer.cs +++ b/src/BuildingBlocks/BulidingBlocks/Web/SlugifyParameterTransformer.cs @@ -12,4 +12,4 @@ public string TransformOutbound(object value) ? null : Regex.Replace(value.ToString() ?? string.Empty, "([a-z])([A-Z])", "$1-$2").ToLower(); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Api/MovieSearch.Api.csproj b/src/MovieSearch.Api/MovieSearch.Api.csproj index 6a195f4..3785142 100644 --- a/src/MovieSearch.Api/MovieSearch.Api.csproj +++ b/src/MovieSearch.Api/MovieSearch.Api.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable diff --git a/src/MovieSearch.Api/Movies/Models/GetMovieCreditsRequest.cs b/src/MovieSearch.Api/Movies/Models/GetMovieCreditsRequest.cs index 639754c..863ef41 100644 --- a/src/MovieSearch.Api/Movies/Models/GetMovieCreditsRequest.cs +++ b/src/MovieSearch.Api/Movies/Models/GetMovieCreditsRequest.cs @@ -3,4 +3,4 @@ namespace Thesaurus.Api.Words.ViewModels; public class GetMovieCreditsRequest { public int MovieId { get; set; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Api/Movies/Models/GetPopularMoviesRequest.cs b/src/MovieSearch.Api/Movies/Models/GetPopularMoviesRequest.cs index 1f3891d..133ee11 100644 --- a/src/MovieSearch.Api/Movies/Models/GetPopularMoviesRequest.cs +++ b/src/MovieSearch.Api/Movies/Models/GetPopularMoviesRequest.cs @@ -3,4 +3,4 @@ namespace Thesaurus.Api.Words.ViewModels; public class GetPopularMoviesRequest { public int Page { get; set; } = 1; -} \ No newline at end of file +} diff --git a/src/MovieSearch.Api/Movies/Models/GetUpcomingMoviesRequest.cs b/src/MovieSearch.Api/Movies/Models/GetUpcomingMoviesRequest.cs index 0b05196..2113a69 100644 --- a/src/MovieSearch.Api/Movies/Models/GetUpcomingMoviesRequest.cs +++ b/src/MovieSearch.Api/Movies/Models/GetUpcomingMoviesRequest.cs @@ -3,4 +3,4 @@ namespace Thesaurus.Api.Words.ViewModels; public class GetUpcomingMoviesRequest { public int Page { get; set; } = 1; -} \ No newline at end of file +} diff --git a/src/MovieSearch.Api/Movies/Models/SearchMoviesByTitleRequest.cs b/src/MovieSearch.Api/Movies/Models/SearchMoviesByTitleRequest.cs index 090950d..e391a4f 100644 --- a/src/MovieSearch.Api/Movies/Models/SearchMoviesByTitleRequest.cs +++ b/src/MovieSearch.Api/Movies/Models/SearchMoviesByTitleRequest.cs @@ -4,4 +4,4 @@ public class SearchMoviesByTitleRequest { public string SearchKeywords { get; set; } public int Page { get; set; } = 1; -} \ No newline at end of file +} diff --git a/src/MovieSearch.Api/Movies/Models/SearchMoviesRequest.cs b/src/MovieSearch.Api/Movies/Models/SearchMoviesRequest.cs index 8f926eb..4e63771 100644 --- a/src/MovieSearch.Api/Movies/Models/SearchMoviesRequest.cs +++ b/src/MovieSearch.Api/Movies/Models/SearchMoviesRequest.cs @@ -7,4 +7,4 @@ public class SearchMoviesRequest public bool IncludeAdult { get; set; } public string SearchKeywords { get; set; } public int Page { get; set; } = 1; -} \ No newline at end of file +} diff --git a/src/MovieSearch.Api/Movies/MoviesController.cs b/src/MovieSearch.Api/Movies/MoviesController.cs index 139f2c5..c800da6 100644 --- a/src/MovieSearch.Api/Movies/MoviesController.cs +++ b/src/MovieSearch.Api/Movies/MoviesController.cs @@ -38,7 +38,7 @@ public class MoviesController : BaseController [SwaggerOperation(Summary = "Get specific movie by id", Description = "Get a single movie by Id")] public async Task GetByIdAsync([FromRoute] int id, CancellationToken cancellationToken) { - var query = new FindMovieByIdQuery {Id = id}; + var query = new FindMovieByIdQuery { Id = id }; var result = await Mediator.Send(query, cancellationToken); return Ok(result); @@ -58,7 +58,7 @@ public async Task GetByIdAsync([FromRoute] int id, CancellationTok [SwaggerOperation(Summary = "Get specific order by id", Description = "Get a single Order by Id")] public async Task GetByImdbIdAsync([FromRoute] string imdbId, CancellationToken cancellationToken) { - var query = new FindMovieByImdbIdQuery {ImdbId = imdbId}; + var query = new FindMovieByImdbIdQuery { ImdbId = imdbId }; var result = await Mediator.Send(query, cancellationToken); return Ok(result); @@ -76,10 +76,15 @@ public async Task GetByImdbIdAsync([FromRoute] string imdbId, Canc [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [SwaggerOperation(Summary = "Get specific movie by id with its trailers", - Description = "Get specific movie by id with its trailers")] - public async Task GetWithTrailersById([FromRoute] int id, [FromQuery] int trailersCount = 20, - CancellationToken cancellationToken = default) + [SwaggerOperation( + Summary = "Get specific movie by id with its trailers", + Description = "Get specific movie by id with its trailers" + )] + public async Task GetWithTrailersById( + [FromRoute] int id, + [FromQuery] int trailersCount = 20, + CancellationToken cancellationToken = default + ) { var query = new FindMovieWithTrailersByIdQuery(id, trailersCount); var result = await Mediator.Send(query, cancellationToken); @@ -99,10 +104,15 @@ public async Task GetWithTrailersById([FromRoute] int id, [FromQue [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [SwaggerOperation(Summary = "Get specific movie by imdbId with its trailers.", - Description = "Get specific movie by imdbId with its trailers.")] - public async Task GetWithTrailersByImdbId([FromRoute] string imdbId, - [FromQuery] int trailersCount = 20, CancellationToken cancellationToken = default) + [SwaggerOperation( + Summary = "Get specific movie by imdbId with its trailers.", + Description = "Get specific movie by imdbId with its trailers." + )] + public async Task GetWithTrailersByImdbId( + [FromRoute] string imdbId, + [FromQuery] int trailersCount = 20, + CancellationToken cancellationToken = default + ) { var query = new FindMovieWithTrailersByImdbIdQuery(imdbId, trailersCount); var result = await Mediator.Send(query, cancellationToken); @@ -121,13 +131,12 @@ public async Task GetWithTrailersByImdbId([FromRoute] string imdbI [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [SwaggerOperation(Summary = "Search movies by title", Description = "Search movies by title")] - public async Task SearchByTitleAsync([FromQuery] SearchMoviesByTitleRequest request, - CancellationToken cancellationToken) + public async Task SearchByTitleAsync( + [FromQuery] SearchMoviesByTitleRequest request, + CancellationToken cancellationToken + ) { - var query = new SearchMovieByTitleQuery - { - Page = request.Page, SearchKeywords = request.SearchKeywords - }; + var query = new SearchMovieByTitleQuery { Page = request.Page, SearchKeywords = request.SearchKeywords }; var result = await Mediator.Send(query, cancellationToken); return Ok(result); @@ -143,13 +152,22 @@ public async Task SearchByTitleAsync([FromQuery] SearchMoviesByTit [ApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - [SwaggerOperation(Summary = "Search movies by different parameters", - Description = "Search movies by different parameters")] - public async Task SearchAsync([FromQuery] SearchMoviesRequest request, - CancellationToken cancellationToken) + [SwaggerOperation( + Summary = "Search movies by different parameters", + Description = "Search movies by different parameters" + )] + public async Task SearchAsync( + [FromQuery] SearchMoviesRequest request, + CancellationToken cancellationToken + ) { - var query = new SearchMovieQuery(request.SearchKeywords, request.Page, request.Year, request - .PrimaryReleaseYear, request.IncludeAdult); + var query = new SearchMovieQuery( + request.SearchKeywords, + request.Page, + request.Year, + request.PrimaryReleaseYear, + request.IncludeAdult + ); var result = await Mediator.Send(query, cancellationToken); return Ok(result); @@ -165,12 +183,16 @@ public async Task SearchAsync([FromQuery] SearchMoviesRequest requ [ApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - [SwaggerOperation(Summary = "Get a list of upcoming movies in theatres", - Description = "Get a list of upcoming movies in theatres")] - public async Task GetUpcomingMoviesAsync([FromQuery] GetUpcomingMoviesRequest request, - CancellationToken cancellationToken) + [SwaggerOperation( + Summary = "Get a list of upcoming movies in theatres", + Description = "Get a list of upcoming movies in theatres" + )] + public async Task GetUpcomingMoviesAsync( + [FromQuery] GetUpcomingMoviesRequest request, + CancellationToken cancellationToken + ) { - var query = new FindUpcomingMoviesQuery {Page = request.Page}; + var query = new FindUpcomingMoviesQuery { Page = request.Page }; var result = await Mediator.Send(query, cancellationToken); return Ok(result); @@ -186,12 +208,16 @@ public async Task GetUpcomingMoviesAsync([FromQuery] GetUpcomingMo [ApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - [SwaggerOperation(Summary = "Get a list of the current popular movies on TMDB. This list updates daily.", - Description = "Get a list of the current popular movies on TMDB. This list updates daily.")] - public async Task GetPopularMoviesAsync([FromQuery] GetPopularMoviesRequest request, - CancellationToken cancellationToken) + [SwaggerOperation( + Summary = "Get a list of the current popular movies on TMDB. This list updates daily.", + Description = "Get a list of the current popular movies on TMDB. This list updates daily." + )] + public async Task GetPopularMoviesAsync( + [FromQuery] GetPopularMoviesRequest request, + CancellationToken cancellationToken + ) { - var query = new FindPopularMoviesQuery {Page = request.Page}; + var query = new FindPopularMoviesQuery { Page = request.Page }; var result = await Mediator.Send(query, cancellationToken); return Ok(result); @@ -208,14 +234,18 @@ public async Task GetPopularMoviesAsync([FromQuery] GetPopularMovi [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [SwaggerOperation(Summary = "Get the cast and crew for a movie.", - Description = "Get the cast and crew for a movie.")] - public async Task GetMovieCreditsAsync([FromQuery] GetMovieCreditsRequest request, - CancellationToken cancellationToken) + [SwaggerOperation( + Summary = "Get the cast and crew for a movie.", + Description = "Get the cast and crew for a movie." + )] + public async Task GetMovieCreditsAsync( + [FromQuery] GetMovieCreditsRequest request, + CancellationToken cancellationToken + ) { var query = new FindMovieCreditsQuery(request.MovieId); var result = await Mediator.Send(query, cancellationToken); return Ok(result); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Api/Multi/Models/SearchMultipleModelRequest.cs b/src/MovieSearch.Api/Multi/Models/SearchMultipleModelRequest.cs index d492ae0..d5bc21a 100644 --- a/src/MovieSearch.Api/Multi/Models/SearchMultipleModelRequest.cs +++ b/src/MovieSearch.Api/Multi/Models/SearchMultipleModelRequest.cs @@ -6,4 +6,4 @@ public class SearchMultipleModelRequest public bool IncludeAdult { get; set; } public int Page { get; set; } = 1; public int Year { get; set; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Api/Multi/MultiController.cs b/src/MovieSearch.Api/Multi/MultiController.cs index 074faca..f11a687 100644 --- a/src/MovieSearch.Api/Multi/MultiController.cs +++ b/src/MovieSearch.Api/Multi/MultiController.cs @@ -27,12 +27,13 @@ public class MultiController : BaseController [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [SwaggerOperation( - Summary = - "Search multiple models in a single request. Multi search currently supports searching for movies, tv shows and people in a single request.", - Description = - "Search multiple models in a single request. Multi search currently supports searching for movies, tv shows and people in a single request.")] - public async Task SearchAsync([FromQuery] SearchMultipleModelRequest request, - CancellationToken cancellationToken) + Summary = "Search multiple models in a single request. Multi search currently supports searching for movies, tv shows and people in a single request.", + Description = "Search multiple models in a single request. Multi search currently supports searching for movies, tv shows and people in a single request." + )] + public async Task SearchAsync( + [FromQuery] SearchMultipleModelRequest request, + CancellationToken cancellationToken + ) { var query = new SearchMultipleModelQuery { @@ -45,4 +46,4 @@ public async Task SearchAsync([FromQuery] SearchMultipleModelReque return Ok(result); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Api/People/PeopleController.cs b/src/MovieSearch.Api/People/PeopleController.cs index b825da1..0da08f2 100644 --- a/src/MovieSearch.Api/People/PeopleController.cs +++ b/src/MovieSearch.Api/People/PeopleController.cs @@ -27,8 +27,10 @@ public class PeopleController : BaseController [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [SwaggerOperation(Summary = "Get the primary person details by id.", - Description = "Get the primary person details by id.")] + [SwaggerOperation( + Summary = "Get the primary person details by id.", + Description = "Get the primary person details by id." + )] public async Task GetByIdAsync([FromRoute] int personId, CancellationToken cancellationToken) { var query = new FindPersonByIdQuery(personId); @@ -47,10 +49,11 @@ public async Task GetByIdAsync([FromRoute] int personId, Cancellat [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [SwaggerOperation(Summary = "Get the movie credits for a person.", - Description = "Get the movie credits for a person.")] - public async Task GetPersonMovieCreditsAsync([FromRoute] int id, - CancellationToken cancellationToken) + [SwaggerOperation( + Summary = "Get the movie credits for a person.", + Description = "Get the movie credits for a person." + )] + public async Task GetPersonMovieCreditsAsync([FromRoute] int id, CancellationToken cancellationToken) { var query = new FindPersonMovieCreditsQuery(id); var result = await Mediator.Send(query, cancellationToken); @@ -68,14 +71,15 @@ public async Task GetPersonMovieCreditsAsync([FromRoute] int id, [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [SwaggerOperation(Summary = "Get the tv-show credits for a person.", - Description = "Get the tv-show credits for a person.")] - public async Task GetPersonTvShowCreditsAsync([FromRoute] int id, - CancellationToken cancellationToken) + [SwaggerOperation( + Summary = "Get the tv-show credits for a person.", + Description = "Get the tv-show credits for a person." + )] + public async Task GetPersonTvShowCreditsAsync([FromRoute] int id, CancellationToken cancellationToken) { var query = new FindPersonTVShowCreditsQuery(id); var result = await Mediator.Send(query, cancellationToken); return Ok(result); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Api/Program.cs b/src/MovieSearch.Api/Program.cs index 42ef226..8676727 100644 --- a/src/MovieSearch.Api/Program.cs +++ b/src/MovieSearch.Api/Program.cs @@ -16,30 +16,41 @@ using MovieSearch.Infrastructure; using Serilog; - // https://docs.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis // https://benfoster.io/blog/mvc-to-minimal-apis-aspnet-6/ var builder = WebApplication.CreateBuilder(args); -builder.Host.UseDefaultServiceProvider((env, c) => -{ - // Handling Captive Dependency Problem - // https://ankitvijay.net/2020/03/17/net-core-and-di-beware-of-captive-dependency/ - // https://levelup.gitconnected.com/top-misconceptions-about-dependency-injection-in-asp-net-core-c6a7afd14eb4 - // https://blog.ploeh.dk/2014/06/02/captive-dependency/ - if (env.HostingEnvironment.IsDevelopment() || env.HostingEnvironment.IsEnvironment("tests") || - env.HostingEnvironment.IsStaging()) - c.ValidateScopes = true; -}); +builder.Host.UseDefaultServiceProvider( + (env, c) => + { + // Handling Captive Dependency Problem + // https://ankitvijay.net/2020/03/17/net-core-and-di-beware-of-captive-dependency/ + // https://levelup.gitconnected.com/top-misconceptions-about-dependency-injection-in-asp-net-core-c6a7afd14eb4 + // https://blog.ploeh.dk/2014/06/02/captive-dependency/ + if ( + env.HostingEnvironment.IsDevelopment() + || env.HostingEnvironment.IsEnvironment("tests") + || env.HostingEnvironment.IsStaging() + ) + c.ValidateScopes = true; + } +); builder.AddCustomSerilog(); builder.Services.AddControllers(options => - options.Conventions.Add(new RouteTokenTransformerConvention(new SlugifyParameterTransformer()))); + options.Conventions.Add(new RouteTokenTransformerConvention(new SlugifyParameterTransformer())) +); builder.Services.AddCors(options => { - options.AddPolicy("api", policy => { policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod(); }); + options.AddPolicy( + "api", + policy => + { + policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod(); + } + ); }); builder.Services.AddApplication(); @@ -65,6 +76,4 @@ await app.RunAsync(); -public partial class Program -{ -} \ No newline at end of file +public partial class Program { } diff --git a/src/MovieSearch.Api/TvShows/Model/SearchTVShowsByTitleRequest.cs b/src/MovieSearch.Api/TvShows/Model/SearchTVShowsByTitleRequest.cs index 7b3828b..ff217a7 100644 --- a/src/MovieSearch.Api/TvShows/Model/SearchTVShowsByTitleRequest.cs +++ b/src/MovieSearch.Api/TvShows/Model/SearchTVShowsByTitleRequest.cs @@ -4,4 +4,4 @@ public class SearchTVShowsByTitleRequest { public int Page { get; set; } = 1; public string SearchKeywords { get; set; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Api/TvShows/Model/SearchTVShowsRequest.cs b/src/MovieSearch.Api/TvShows/Model/SearchTVShowsRequest.cs index ac397bd..8d7518a 100644 --- a/src/MovieSearch.Api/TvShows/Model/SearchTVShowsRequest.cs +++ b/src/MovieSearch.Api/TvShows/Model/SearchTVShowsRequest.cs @@ -6,4 +6,4 @@ public class SearchTVShowsRequest public bool IncludeAdult { get; set; } public string SearchKeywords { get; set; } public int Page { get; set; } = 1; -} \ No newline at end of file +} diff --git a/src/MovieSearch.Api/TvShows/TVShowsController.cs b/src/MovieSearch.Api/TvShows/TVShowsController.cs index 5d39d9d..1bc92bc 100644 --- a/src/MovieSearch.Api/TvShows/TVShowsController.cs +++ b/src/MovieSearch.Api/TvShows/TVShowsController.cs @@ -49,10 +49,15 @@ public async Task GetByIdAsync([FromRoute] int id, CancellationTok [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [SwaggerOperation(Summary = "Get specific tv-show by id with its trailers", - Description = "Get specific tv-show by id with its trailers")] - public async Task GetWithTrailersByIdAsync([FromRoute] int id, [FromQuery] int trailersCount = 20, - CancellationToken cancellationToken = default) + [SwaggerOperation( + Summary = "Get specific tv-show by id with its trailers", + Description = "Get specific tv-show by id with its trailers" + )] + public async Task GetWithTrailersByIdAsync( + [FromRoute] int id, + [FromQuery] int trailersCount = 20, + CancellationToken cancellationToken = default + ) { var query = new FindTVShowWithTrailersByIdQuery(id, trailersCount); var result = await Mediator.Send(query, cancellationToken); @@ -70,8 +75,10 @@ public async Task GetWithTrailersByIdAsync([FromRoute] int id, [Fr [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [SwaggerOperation(Summary = "Search tv-shows by title", Description = "Search tv-shows by title")] - public async Task SearchByTitleAsync([FromQuery] SearchTVShowsByTitleRequest request, - CancellationToken cancellationToken) + public async Task SearchByTitleAsync( + [FromQuery] SearchTVShowsByTitleRequest request, + CancellationToken cancellationToken + ) { var query = new SearchTVShowByTitleQuery(request.SearchKeywords, request.Page); var result = await Mediator.Send(query, cancellationToken); @@ -88,15 +95,23 @@ public async Task SearchByTitleAsync([FromQuery] SearchTVShowsByTi [HttpGet("search")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - [SwaggerOperation(Summary = "Search tv-shows by different parameters", - Description = "Search tv-shows by different parameters")] - public async Task SearchAsync([FromQuery] SearchTVShowsRequest request, - CancellationToken cancellationToken) + [SwaggerOperation( + Summary = "Search tv-shows by different parameters", + Description = "Search tv-shows by different parameters" + )] + public async Task SearchAsync( + [FromQuery] SearchTVShowsRequest request, + CancellationToken cancellationToken + ) { - var query = new SearchTVShowQuery(request.SearchKeywords, request.Page, request.FirstAirDateYear, - request.IncludeAdult); + var query = new SearchTVShowQuery( + request.SearchKeywords, + request.Page, + request.FirstAirDateYear, + request.IncludeAdult + ); var result = await Mediator.Send(query, cancellationToken); return Ok(result); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Api/Videos/Models/GetMovieTrailersRequest.cs b/src/MovieSearch.Api/Videos/Models/GetMovieTrailersRequest.cs index 6c6d4ea..43f2300 100644 --- a/src/MovieSearch.Api/Videos/Models/GetMovieTrailersRequest.cs +++ b/src/MovieSearch.Api/Videos/Models/GetMovieTrailersRequest.cs @@ -5,4 +5,4 @@ public class GetMovieTrailersRequest public string PageToken { get; set; } = ""; public int PageSize { get; set; } = 20; public int MovieId { get; set; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Api/Videos/Models/GetTVShowTrailersRequest.cs b/src/MovieSearch.Api/Videos/Models/GetTVShowTrailersRequest.cs index bf4e1d8..bfb3120 100644 --- a/src/MovieSearch.Api/Videos/Models/GetTVShowTrailersRequest.cs +++ b/src/MovieSearch.Api/Videos/Models/GetTVShowTrailersRequest.cs @@ -5,4 +5,4 @@ public class GetTVShowTrailersRequest public string PageToken { get; set; } = ""; public int PageSize { get; set; } = 20; public int TVShowId { get; set; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Api/Videos/Models/GetTrailersRequest.cs b/src/MovieSearch.Api/Videos/Models/GetTrailersRequest.cs index 264e406..b1831f6 100644 --- a/src/MovieSearch.Api/Videos/Models/GetTrailersRequest.cs +++ b/src/MovieSearch.Api/Videos/Models/GetTrailersRequest.cs @@ -9,4 +9,4 @@ public class GetTrailersRequest public string MovieName { get; set; } public DateTime? PublishedBefore { get; set; } public DateTime? PublishedAfter { get; set; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Api/Videos/VideosController.cs b/src/MovieSearch.Api/Videos/VideosController.cs index 18ba243..8f00d6b 100644 --- a/src/MovieSearch.Api/Videos/VideosController.cs +++ b/src/MovieSearch.Api/Videos/VideosController.cs @@ -25,16 +25,16 @@ public class VideosController : BaseController [HttpGet("search-trailers")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - [SwaggerOperation(Summary = "Get the trailers by movie or tv-show name", - Description = "Get the trailers by movie or tv-show name")] - public async Task GetTrailersAsync([FromQuery] GetTrailersRequest request, - CancellationToken cancellationToken) + [SwaggerOperation( + Summary = "Get the trailers by movie or tv-show name", + Description = "Get the trailers by movie or tv-show name" + )] + public async Task GetTrailersAsync( + [FromQuery] GetTrailersRequest request, + CancellationToken cancellationToken + ) { - var query = new FindTrailersQuery - { - MovieName = request.MovieName, - PageToken = request.PageToken - }; + var query = new FindTrailersQuery { MovieName = request.MovieName, PageToken = request.PageToken }; var result = await Mediator.Send(query, cancellationToken); return Ok(result); @@ -50,10 +50,14 @@ public async Task GetTrailersAsync([FromQuery] GetTrailersRequest [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [SwaggerOperation(Summary = "Get the trailers for a specific movie", - Description = "Get the trailers for a specific movie")] - public async Task GetMovieTrailersAsync([FromQuery] GetMovieTrailersRequest request, - CancellationToken cancellationToken) + [SwaggerOperation( + Summary = "Get the trailers for a specific movie", + Description = "Get the trailers for a specific movie" + )] + public async Task GetMovieTrailersAsync( + [FromQuery] GetMovieTrailersRequest request, + CancellationToken cancellationToken + ) { var query = new FindMovieTrailersQuery { @@ -76,10 +80,14 @@ public async Task GetMovieTrailersAsync([FromQuery] GetMovieTraile [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [SwaggerOperation(Summary = "Get the trailers for a specific tv-show", - Description = "Get the trailers for a specific tv-show")] - public async Task GetTVShowTrailersAsync([FromQuery] GetTVShowTrailersRequest request, - CancellationToken cancellationToken) + [SwaggerOperation( + Summary = "Get the trailers for a specific tv-show", + Description = "Get the trailers for a specific tv-show" + )] + public async Task GetTVShowTrailersAsync( + [FromQuery] GetTVShowTrailersRequest request, + CancellationToken cancellationToken + ) { var query = new FindTVShowTrailersQuery { @@ -91,4 +99,4 @@ public async Task GetTVShowTrailersAsync([FromQuery] GetTVShowTrai return Ok(result); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/ApplicationRoot.cs b/src/MovieSearch.Application/ApplicationRoot.cs index cf8d53e..ffdcb3e 100644 --- a/src/MovieSearch.Application/ApplicationRoot.cs +++ b/src/MovieSearch.Application/ApplicationRoot.cs @@ -1,5 +1,3 @@ namespace MovieSearch.Application; -public class ApplicationRoot -{ -} \ No newline at end of file +public class ApplicationRoot { } diff --git a/src/MovieSearch.Application/Companies/CompanyMappings.cs b/src/MovieSearch.Application/Companies/CompanyMappings.cs index 693fe4b..1b22c2e 100644 --- a/src/MovieSearch.Application/Companies/CompanyMappings.cs +++ b/src/MovieSearch.Application/Companies/CompanyMappings.cs @@ -10,4 +10,4 @@ public CompanyMappings() { CreateMap(); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Companies/Dtos/ProductCompanyDto.cs b/src/MovieSearch.Application/Companies/Dtos/ProductCompanyDto.cs index a7ca68a..1042e19 100644 --- a/src/MovieSearch.Application/Companies/Dtos/ProductCompanyDto.cs +++ b/src/MovieSearch.Application/Companies/Dtos/ProductCompanyDto.cs @@ -6,4 +6,4 @@ public class ProductionCompanyDto public string Name { get; init; } public string LogoPath { get; init; } public string OriginCountry { get; init; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Extensions.cs b/src/MovieSearch.Application/Extensions.cs index cfffa39..aea650a 100644 --- a/src/MovieSearch.Application/Extensions.cs +++ b/src/MovieSearch.Application/Extensions.cs @@ -10,4 +10,4 @@ public static IServiceCollection AddApplication(this IServiceCollection services { return services; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Generals/Dtos/CountryDto.cs b/src/MovieSearch.Application/Generals/Dtos/CountryDto.cs index 2656796..e555f8d 100644 --- a/src/MovieSearch.Application/Generals/Dtos/CountryDto.cs +++ b/src/MovieSearch.Application/Generals/Dtos/CountryDto.cs @@ -4,4 +4,4 @@ public class CountryDto { public string Iso3166Code { get; init; } public string Name { get; init; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Generals/Dtos/LanguageDto.cs b/src/MovieSearch.Application/Generals/Dtos/LanguageDto.cs index 5b6abb1..f562a81 100644 --- a/src/MovieSearch.Application/Generals/Dtos/LanguageDto.cs +++ b/src/MovieSearch.Application/Generals/Dtos/LanguageDto.cs @@ -5,4 +5,4 @@ public class LanguageDto public string Iso639Code { get; init; } public string Name { get; init; } public string EnglishName { get; init; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Generals/Dtos/MultiInfoDto.cs b/src/MovieSearch.Application/Generals/Dtos/MultiInfoDto.cs index fc3ae75..b9d9828 100644 --- a/src/MovieSearch.Application/Generals/Dtos/MultiInfoDto.cs +++ b/src/MovieSearch.Application/Generals/Dtos/MultiInfoDto.cs @@ -7,4 +7,4 @@ public class MultiInfoDto public int Id { get; init; } public virtual MediaType MediaType { get; init; } public double Popularity { get; init; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Generals/GeneralMappings.cs b/src/MovieSearch.Application/Generals/GeneralMappings.cs index 7f0204b..41845fd 100644 --- a/src/MovieSearch.Application/Generals/GeneralMappings.cs +++ b/src/MovieSearch.Application/Generals/GeneralMappings.cs @@ -12,4 +12,4 @@ public GeneralMappings() CreateMap(); CreateMap(); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Generals/Multi/Features/SearchMultipleModel/SearchMovieQueryValidator.cs b/src/MovieSearch.Application/Generals/Multi/Features/SearchMultipleModel/SearchMovieQueryValidator.cs index da59b1a..820e843 100644 --- a/src/MovieSearch.Application/Generals/Multi/Features/SearchMultipleModel/SearchMovieQueryValidator.cs +++ b/src/MovieSearch.Application/Generals/Multi/Features/SearchMultipleModel/SearchMovieQueryValidator.cs @@ -7,11 +7,17 @@ public class SearchMultipleModelQueryValidator : AbstractValidator query.Page).GreaterThan(0).WithMessage("page number should be greater than zero."); - RuleFor(query => query.Year).Must(x => x >= 1700).When(c => c.Year > 0) + RuleFor(query => query.Year) + .Must(x => x >= 1700) + .When(c => c.Year > 0) .WithMessage("year should be greater than 1700."); //if it's not a default value - RuleFor(query => query.Year).Must(x => x >= 1700).When(c => c.Year > 0) + RuleFor(query => query.Year) + .Must(x => x >= 1700) + .When(c => c.Year > 0) .WithMessage("primary release year should be greater than 1700."); - RuleFor(query => query.SearchKeywords).NotEmpty().NotNull() + RuleFor(query => query.SearchKeywords) + .NotEmpty() + .NotNull() .WithMessage("search value should not be null or empty."); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Generals/Multi/Features/SearchMultipleModel/SearchMultipleModelQuery.cs b/src/MovieSearch.Application/Generals/Multi/Features/SearchMultipleModel/SearchMultipleModelQuery.cs index 4eb1d48..20891c6 100644 --- a/src/MovieSearch.Application/Generals/Multi/Features/SearchMultipleModel/SearchMultipleModelQuery.cs +++ b/src/MovieSearch.Application/Generals/Multi/Features/SearchMultipleModel/SearchMultipleModelQuery.cs @@ -8,4 +8,4 @@ public class SearchMultipleModelQuery : IQuery public bool IncludeAdult { get; init; } public int Page { get; init; } = 1; public int Year { get; init; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Generals/Multi/Features/SearchMultipleModel/SearchMultipleModelQueryHandler.cs b/src/MovieSearch.Application/Generals/Multi/Features/SearchMultipleModel/SearchMultipleModelQueryHandler.cs index f0672ef..fd47aeb 100644 --- a/src/MovieSearch.Application/Generals/Multi/Features/SearchMultipleModel/SearchMultipleModelQueryHandler.cs +++ b/src/MovieSearch.Application/Generals/Multi/Features/SearchMultipleModel/SearchMultipleModelQueryHandler.cs @@ -7,8 +7,7 @@ namespace MovieSearch.Application.Generals.Multi.Features.SearchMultipleModel; -public class - SearchMultipleModelQueryHandler : IRequestHandler +public class SearchMultipleModelQueryHandler : IRequestHandler { private readonly IMapper _mapper; private readonly IMovieDbServiceClient _movieDbServiceClient; @@ -19,14 +18,21 @@ public SearchMultipleModelQueryHandler(IMovieDbServiceClient movieDbServiceClien _mapper = mapper; } - public async Task Handle(SearchMultipleModelQuery query, - CancellationToken cancellationToken) + public async Task Handle( + SearchMultipleModelQuery query, + CancellationToken cancellationToken + ) { Guard.Against.Null(query, nameof(SearchMultipleModelQuery)); - var searchData = await _movieDbServiceClient.SearchMultiAsync(query.SearchKeywords, query.Page, query - .IncludeAdult, query.Year, cancellationToken); + var searchData = await _movieDbServiceClient.SearchMultiAsync( + query.SearchKeywords, + query.Page, + query.IncludeAdult, + query.Year, + cancellationToken + ); - return new SearchMultipleModelQueryResult {List = searchData}; + return new SearchMultipleModelQueryResult { List = searchData }; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Generals/Multi/Features/SearchMultipleModel/SearchMultipleModelQueryResult.cs b/src/MovieSearch.Application/Generals/Multi/Features/SearchMultipleModel/SearchMultipleModelQueryResult.cs index 3c60622..6afa8da 100644 --- a/src/MovieSearch.Application/Generals/Multi/Features/SearchMultipleModel/SearchMultipleModelQueryResult.cs +++ b/src/MovieSearch.Application/Generals/Multi/Features/SearchMultipleModel/SearchMultipleModelQueryResult.cs @@ -5,4 +5,4 @@ namespace MovieSearch.Application.Generals.Multi.Features.SearchMultipleModel; public class SearchMultipleModelQueryResult { public ListResultModel List { get; set; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Genres/Dtos/GenreDto.cs b/src/MovieSearch.Application/Genres/Dtos/GenreDto.cs index c33ccb2..a4629c3 100644 --- a/src/MovieSearch.Application/Genres/Dtos/GenreDto.cs +++ b/src/MovieSearch.Application/Genres/Dtos/GenreDto.cs @@ -4,4 +4,4 @@ public class GenreDto { public int Id { get; init; } public string Name { get; init; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Genres/GenreMappings.cs b/src/MovieSearch.Application/Genres/GenreMappings.cs index 8b2e7ca..7e5580d 100644 --- a/src/MovieSearch.Application/Genres/GenreMappings.cs +++ b/src/MovieSearch.Application/Genres/GenreMappings.cs @@ -10,4 +10,4 @@ public GenreMappings() { CreateMap(); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/MovieSearch.Application.csproj b/src/MovieSearch.Application/MovieSearch.Application.csproj index af7a3c4..23a04ae 100644 --- a/src/MovieSearch.Application/MovieSearch.Application.csproj +++ b/src/MovieSearch.Application/MovieSearch.Application.csproj @@ -1,12 +1,12 @@ - net7.0 + net8.0 enable - + diff --git a/src/MovieSearch.Application/Movies/Dtos/MovieCreditDto.cs b/src/MovieSearch.Application/Movies/Dtos/MovieCreditDto.cs index 87d6906..d4ebc38 100644 --- a/src/MovieSearch.Application/Movies/Dtos/MovieCreditDto.cs +++ b/src/MovieSearch.Application/Movies/Dtos/MovieCreditDto.cs @@ -39,4 +39,4 @@ public class MovieCrewMemberDto public string KnownForDepartment { get; init; } public string OriginalName { get; init; } public float Popularity { get; init; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Dtos/MovieDto.cs b/src/MovieSearch.Application/Movies/Dtos/MovieDto.cs index 31110cf..fe3792a 100644 --- a/src/MovieSearch.Application/Movies/Dtos/MovieDto.cs +++ b/src/MovieSearch.Application/Movies/Dtos/MovieDto.cs @@ -34,4 +34,4 @@ public class MovieDto public bool IsVideo { get; init; } public double VoteAverage { get; init; } public int VoteCount { get; init; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Dtos/MovieInfoDto.cs b/src/MovieSearch.Application/Movies/Dtos/MovieInfoDto.cs index 220f086..b55d9c0 100644 --- a/src/MovieSearch.Application/Movies/Dtos/MovieInfoDto.cs +++ b/src/MovieSearch.Application/Movies/Dtos/MovieInfoDto.cs @@ -20,4 +20,4 @@ public class MovieInfoDto : MultiInfoDto public bool Video { get; init; } public double VoteAverage { get; init; } public int VoteCount { get; init; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Dtos/MovieWithTrailersDto.cs b/src/MovieSearch.Application/Movies/Dtos/MovieWithTrailersDto.cs index e359f47..a72835a 100644 --- a/src/MovieSearch.Application/Movies/Dtos/MovieWithTrailersDto.cs +++ b/src/MovieSearch.Application/Movies/Dtos/MovieWithTrailersDto.cs @@ -7,4 +7,4 @@ public class MovieWithTrailersDto { public MovieDto Movie { get; init; } public IEnumerable Trailers { get; init; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Exceptions/MovieCreditsNotFoundException.cs b/src/MovieSearch.Application/Movies/Exceptions/MovieCreditsNotFoundException.cs index 871cd87..4e47b6f 100644 --- a/src/MovieSearch.Application/Movies/Exceptions/MovieCreditsNotFoundException.cs +++ b/src/MovieSearch.Application/Movies/Exceptions/MovieCreditsNotFoundException.cs @@ -4,8 +4,6 @@ namespace MovieSearch.Application.Movies.Exceptions; public class MovieCreditsNotFoundException : NotFoundException { - public MovieCreditsNotFoundException(int id) : base( - $"can't find a movie_credits for a movie with movieId '{id}' in the database.") - { - } -} \ No newline at end of file + public MovieCreditsNotFoundException(int id) + : base($"can't find a movie_credits for a movie with movieId '{id}' in the database.") { } +} diff --git a/src/MovieSearch.Application/Movies/Exceptions/MovieNotFoundException.cs b/src/MovieSearch.Application/Movies/Exceptions/MovieNotFoundException.cs index 45f2dbc..1d3e03f 100644 --- a/src/MovieSearch.Application/Movies/Exceptions/MovieNotFoundException.cs +++ b/src/MovieSearch.Application/Movies/Exceptions/MovieNotFoundException.cs @@ -4,11 +4,9 @@ namespace MovieSearch.Application.Movies.Exceptions; public class MovieNotFoundException : NotFoundException { - public MovieNotFoundException(int id) : base($"can't find movie with id '{id}' in the database.") - { - } + public MovieNotFoundException(int id) + : base($"can't find movie with id '{id}' in the database.") { } - public MovieNotFoundException(string imdbId) : base($"can't find movie with imdbid '{imdbId}' in the database.") - { - } -} \ No newline at end of file + public MovieNotFoundException(string imdbId) + : base($"can't find movie with imdbid '{imdbId}' in the database.") { } +} diff --git a/src/MovieSearch.Application/Movies/Features/FindMovieById/FindMovieByIdQuery.cs b/src/MovieSearch.Application/Movies/Features/FindMovieById/FindMovieByIdQuery.cs index 33ff124..a22359f 100644 --- a/src/MovieSearch.Application/Movies/Features/FindMovieById/FindMovieByIdQuery.cs +++ b/src/MovieSearch.Application/Movies/Features/FindMovieById/FindMovieByIdQuery.cs @@ -17,4 +17,4 @@ public string GetCacheKey(FindMovieByIdQuery query) return CacheKey.With(query.GetType(), query.Id.ToString()); } } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/FindMovieById/FindMovieByIdQueryHandler.cs b/src/MovieSearch.Application/Movies/Features/FindMovieById/FindMovieByIdQueryHandler.cs index d9655cf..4b3e43a 100644 --- a/src/MovieSearch.Application/Movies/Features/FindMovieById/FindMovieByIdQueryHandler.cs +++ b/src/MovieSearch.Application/Movies/Features/FindMovieById/FindMovieByIdQueryHandler.cs @@ -31,6 +31,6 @@ public async Task Handle(FindMovieByIdQuery query, Can var result = _mapper.Map(movie); - return new FindMovieByIdQueryResult {Movie = result}; + return new FindMovieByIdQueryResult { Movie = result }; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/FindMovieById/FindMovieByIdQueryResult.cs b/src/MovieSearch.Application/Movies/Features/FindMovieById/FindMovieByIdQueryResult.cs index 2099650..38ced13 100644 --- a/src/MovieSearch.Application/Movies/Features/FindMovieById/FindMovieByIdQueryResult.cs +++ b/src/MovieSearch.Application/Movies/Features/FindMovieById/FindMovieByIdQueryResult.cs @@ -5,4 +5,4 @@ namespace MovieSearch.Application.Movies.Features.FindById; public class FindMovieByIdQueryResult { public MovieDto Movie { get; set; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/FindMovieById/FindMovieByIdQueryValidator.cs b/src/MovieSearch.Application/Movies/Features/FindMovieById/FindMovieByIdQueryValidator.cs index 2044590..45e0301 100644 --- a/src/MovieSearch.Application/Movies/Features/FindMovieById/FindMovieByIdQueryValidator.cs +++ b/src/MovieSearch.Application/Movies/Features/FindMovieById/FindMovieByIdQueryValidator.cs @@ -9,4 +9,4 @@ public FindMovieByIdQueryValidator() { RuleFor(query => query.Id).GreaterThan(0).WithMessage("id should be greater than zero."); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/FindMovieByImdbId/FindByImdbIdQueryResult.cs b/src/MovieSearch.Application/Movies/Features/FindMovieByImdbId/FindByImdbIdQueryResult.cs index f786f7a..26f3022 100644 --- a/src/MovieSearch.Application/Movies/Features/FindMovieByImdbId/FindByImdbIdQueryResult.cs +++ b/src/MovieSearch.Application/Movies/Features/FindMovieByImdbId/FindByImdbIdQueryResult.cs @@ -5,4 +5,4 @@ namespace MovieSearch.Application.Movies.Features.FindMovieByImdbId; public class FindByImdbIdQueryResult { public MovieDto Movie { get; init; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/FindMovieByImdbId/FindMovieByImdbIdQuery.cs b/src/MovieSearch.Application/Movies/Features/FindMovieByImdbId/FindMovieByImdbIdQuery.cs index 21d6f24..6bb517d 100644 --- a/src/MovieSearch.Application/Movies/Features/FindMovieByImdbId/FindMovieByImdbIdQuery.cs +++ b/src/MovieSearch.Application/Movies/Features/FindMovieByImdbId/FindMovieByImdbIdQuery.cs @@ -17,4 +17,4 @@ public string GetCacheKey(FindMovieByImdbIdQuery query) return CacheKey.With(query.GetType(), query.ImdbId); } } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/FindMovieByImdbId/FindMovieByImdbIdQueryHandler.cs b/src/MovieSearch.Application/Movies/Features/FindMovieByImdbId/FindMovieByImdbIdQueryHandler.cs index 9620089..f8160b5 100644 --- a/src/MovieSearch.Application/Movies/Features/FindMovieByImdbId/FindMovieByImdbIdQueryHandler.cs +++ b/src/MovieSearch.Application/Movies/Features/FindMovieByImdbId/FindMovieByImdbIdQueryHandler.cs @@ -20,8 +20,7 @@ public FindMovieByImdbIdQueryHandler(IMovieDbServiceClient movieDbServiceClient, _mapper = mapper; } - public async Task Handle(FindMovieByImdbIdQuery query, - CancellationToken cancellationToken) + public async Task Handle(FindMovieByImdbIdQuery query, CancellationToken cancellationToken) { Guard.Against.Null(query, nameof(FindMovieByImdbIdQuery)); @@ -32,6 +31,6 @@ public async Task Handle(FindMovieByImdbIdQuery query, var result = _mapper.Map(movie); - return new FindByImdbIdQueryResult {Movie = result}; + return new FindByImdbIdQueryResult { Movie = result }; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/FindMovieByImdbId/FindMovieByImdbIdQueryValidator.cs b/src/MovieSearch.Application/Movies/Features/FindMovieByImdbId/FindMovieByImdbIdQueryValidator.cs index 954d10f..2e28924 100644 --- a/src/MovieSearch.Application/Movies/Features/FindMovieByImdbId/FindMovieByImdbIdQueryValidator.cs +++ b/src/MovieSearch.Application/Movies/Features/FindMovieByImdbId/FindMovieByImdbIdQueryValidator.cs @@ -9,4 +9,4 @@ public FindMovieByImdbIdQueryValidator() { RuleFor(query => query.ImdbId).NotNull().NotEmpty().WithMessage("ImdbId should not be null or empty."); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/FindMovieCredits/FindMovieCreditsQuery.cs b/src/MovieSearch.Application/Movies/Features/FindMovieCredits/FindMovieCreditsQuery.cs index 084370e..24b22fc 100644 --- a/src/MovieSearch.Application/Movies/Features/FindMovieCredits/FindMovieCreditsQuery.cs +++ b/src/MovieSearch.Application/Movies/Features/FindMovieCredits/FindMovieCreditsQuery.cs @@ -10,4 +10,4 @@ public FindMovieCreditsQuery(int movieId) } public int MovieId { get; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/FindMovieCredits/FindMovieCreditsQueryHandler.cs b/src/MovieSearch.Application/Movies/Features/FindMovieCredits/FindMovieCreditsQueryHandler.cs index f5d9ddd..be4f650 100644 --- a/src/MovieSearch.Application/Movies/Features/FindMovieCredits/FindMovieCreditsQueryHandler.cs +++ b/src/MovieSearch.Application/Movies/Features/FindMovieCredits/FindMovieCreditsQueryHandler.cs @@ -20,8 +20,10 @@ public FindMovieCreditsQueryHandler(IMovieDbServiceClient movieDbServiceClient, _mapper = mapper; } - public async Task Handle(FindMovieCreditsQuery query, - CancellationToken cancellationToken) + public async Task Handle( + FindMovieCreditsQuery query, + CancellationToken cancellationToken + ) { Guard.Against.Null(query, nameof(FindMovieCreditsQuery)); @@ -32,6 +34,6 @@ public async Task Handle(FindMovieCreditsQuery quer var result = _mapper.Map(movieCredit); - return new FindMovieCreditsQueryResult {MovieCredit = result}; + return new FindMovieCreditsQueryResult { MovieCredit = result }; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/FindMovieCredits/FindMovieCreditsQueryResult.cs b/src/MovieSearch.Application/Movies/Features/FindMovieCredits/FindMovieCreditsQueryResult.cs index 1eda606..08cab18 100644 --- a/src/MovieSearch.Application/Movies/Features/FindMovieCredits/FindMovieCreditsQueryResult.cs +++ b/src/MovieSearch.Application/Movies/Features/FindMovieCredits/FindMovieCreditsQueryResult.cs @@ -5,4 +5,4 @@ namespace MovieSearch.Application.Movies.Features.FindMovieCredits; public class FindMovieCreditsQueryResult { public MovieCreditDto MovieCredit { get; init; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/FindMovieCredits/FindMovieCreditsQueryValidator.cs b/src/MovieSearch.Application/Movies/Features/FindMovieCredits/FindMovieCreditsQueryValidator.cs index fd68fc1..edfa356 100644 --- a/src/MovieSearch.Application/Movies/Features/FindMovieCredits/FindMovieCreditsQueryValidator.cs +++ b/src/MovieSearch.Application/Movies/Features/FindMovieCredits/FindMovieCreditsQueryValidator.cs @@ -9,4 +9,4 @@ public FindMovieCreditsQueryValidator() { RuleFor(query => query.MovieId).GreaterThan(0).WithMessage("movieId should be greater than zero."); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersById/FindMovieWithTrailersByIdQuery.cs b/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersById/FindMovieWithTrailersByIdQuery.cs index 7736fbb..c729e22 100644 --- a/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersById/FindMovieWithTrailersByIdQuery.cs +++ b/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersById/FindMovieWithTrailersByIdQuery.cs @@ -21,8 +21,10 @@ public class CachePolicy : ICachePolicy +public class FindMovieWithTrailersByIdQueryHandler + : IRequestHandler { private readonly IMapper _mapper; private readonly IMovieDbServiceClient _movieDbServiceClient; private readonly IVideoServiceClient _videoServiceClient; - public FindMovieWithTrailersByIdQueryHandler(IMovieDbServiceClient movieDbServiceClient, IVideoServiceClient - videoServiceClient, IMapper mapper) + public FindMovieWithTrailersByIdQueryHandler( + IMovieDbServiceClient movieDbServiceClient, + IVideoServiceClient videoServiceClient, + IMapper mapper + ) { _movieDbServiceClient = movieDbServiceClient; _videoServiceClient = videoServiceClient; _mapper = mapper; } - public async Task Handle(FindMovieWithTrailersByIdQuery query, - CancellationToken cancellationToken) + public async Task Handle( + FindMovieWithTrailersByIdQuery query, + CancellationToken cancellationToken + ) { Guard.Against.Null(query, nameof(FindMovieWithTrailersByIdQuery)); @@ -43,10 +47,8 @@ public async Task Handle(FindMovieWithTrai var trailers = (await _videoServiceClient.GetTrailers(movieDto.Title, query.TrailersCount)).Items; var trailersDto = _mapper.Map>(trailers); - return new FindMovieWithTrailersByIdQueryResult(new MovieWithTrailersDto - { - Movie = movieDto, - Trailers = trailersDto - }); + return new FindMovieWithTrailersByIdQueryResult( + new MovieWithTrailersDto { Movie = movieDto, Trailers = trailersDto } + ); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersById/FindMovieWithTrailersByIdQueryResult.cs b/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersById/FindMovieWithTrailersByIdQueryResult.cs index 829c691..f47b7a3 100644 --- a/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersById/FindMovieWithTrailersByIdQueryResult.cs +++ b/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersById/FindMovieWithTrailersByIdQueryResult.cs @@ -10,4 +10,4 @@ public FindMovieWithTrailersByIdQueryResult(MovieWithTrailersDto movieWithTraile } public MovieWithTrailersDto MovieWithTrailers { get; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersById/FindMovieWithTrailersByIdQueryValidator.cs b/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersById/FindMovieWithTrailersByIdQueryValidator.cs index 2e3fd23..8818161 100644 --- a/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersById/FindMovieWithTrailersByIdQueryValidator.cs +++ b/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersById/FindMovieWithTrailersByIdQueryValidator.cs @@ -9,4 +9,4 @@ public FindMovieWithTrailersByIdQueryValidator() RuleFor(query => query.MovieId).GreaterThan(0).WithMessage("id should be greater than zero."); RuleFor(query => query.TrailersCount).GreaterThan(0).WithMessage("trailers-count should be greater than zero."); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersByImdbId/FindMovieWithTrailersByImdbIdQuery.cs b/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersByImdbId/FindMovieWithTrailersByImdbIdQuery.cs index 5b15c15..01adf36 100644 --- a/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersByImdbId/FindMovieWithTrailersByImdbIdQuery.cs +++ b/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersByImdbId/FindMovieWithTrailersByImdbIdQuery.cs @@ -15,15 +15,17 @@ public FindMovieWithTrailersByImdbIdQuery(string imdbId, int trailersCount = 20) public string ImdbId { get; } public int TrailersCount { get; } - public class - CachePolicy : ICachePolicy + public class CachePolicy + : ICachePolicy { public DateTime? AbsoluteExpirationRelativeToNow => DateTime.Now.AddMinutes(15); public string GetCacheKey(FindMovieWithTrailersByImdbIdQuery query) { - return CacheKey.With(query.GetType(), - $"{nameof(ImdbId)}_{query.ImdbId}_{nameof(TrailersCount)}_{query.TrailersCount}"); + return CacheKey.With( + query.GetType(), + $"{nameof(ImdbId)}_{query.ImdbId}_{nameof(TrailersCount)}_{query.TrailersCount}" + ); } } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersByImdbId/FindMovieWithTrailersByImdbIdQueryHandler.cs b/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersByImdbId/FindMovieWithTrailersByImdbIdQueryHandler.cs index 22661fc..cb74da4 100644 --- a/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersByImdbId/FindMovieWithTrailersByImdbIdQueryHandler.cs +++ b/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersByImdbId/FindMovieWithTrailersByImdbIdQueryHandler.cs @@ -12,23 +12,28 @@ namespace MovieSearch.Application.Movies.Features.FindMovieWithTrailersByImdbId; //https://docs.microsoft.com/en-us/azure/architecture/patterns/gateway-aggregation -public class FindMovieWithTrailersByImdbIdQueryHandler : IRequestHandler +public class FindMovieWithTrailersByImdbIdQueryHandler + : IRequestHandler { private readonly IMapper _mapper; private readonly IMovieDbServiceClient _movieDbServiceClient; private readonly IVideoServiceClient _videoServiceClient; - public FindMovieWithTrailersByImdbIdQueryHandler(IMovieDbServiceClient movieDbServiceClient, - IVideoServiceClient videoServiceClient, IMapper mapper) + public FindMovieWithTrailersByImdbIdQueryHandler( + IMovieDbServiceClient movieDbServiceClient, + IVideoServiceClient videoServiceClient, + IMapper mapper + ) { _movieDbServiceClient = movieDbServiceClient; _videoServiceClient = videoServiceClient; _mapper = mapper; } - public async Task Handle(FindMovieWithTrailersByImdbIdQuery query, - CancellationToken cancellationToken) + public async Task Handle( + FindMovieWithTrailersByImdbIdQuery query, + CancellationToken cancellationToken + ) { Guard.Against.Null(query, nameof(FindMovieWithTrailersByImdbIdQuery)); @@ -44,11 +49,7 @@ public async Task Handle(FindMovieWith return new FindMovieWithTrailersByImdbIdQueryResult { - MovieWithTrailers = new MovieWithTrailersDto - { - Movie = movieDto, - Trailers = trailersDto - } + MovieWithTrailers = new MovieWithTrailersDto { Movie = movieDto, Trailers = trailersDto } }; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersByImdbId/FindMovieWithTrailersByImdbIdQueryResult.cs b/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersByImdbId/FindMovieWithTrailersByImdbIdQueryResult.cs index a1738a1..f2c7daa 100644 --- a/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersByImdbId/FindMovieWithTrailersByImdbIdQueryResult.cs +++ b/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersByImdbId/FindMovieWithTrailersByImdbIdQueryResult.cs @@ -5,4 +5,4 @@ namespace MovieSearch.Application.Movies.Features.FindMovieWithTrailersByImdbId; public class FindMovieWithTrailersByImdbIdQueryResult { public MovieWithTrailersDto MovieWithTrailers { get; init; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersByImdbId/FindMovieWithTrailersByImdbIdQueryValidator.cs b/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersByImdbId/FindMovieWithTrailersByImdbIdQueryValidator.cs index b7fcae6..e9bf62c 100644 --- a/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersByImdbId/FindMovieWithTrailersByImdbIdQueryValidator.cs +++ b/src/MovieSearch.Application/Movies/Features/FindMovieWithTrailersByImdbId/FindMovieWithTrailersByImdbIdQueryValidator.cs @@ -8,7 +8,9 @@ public class FindMovieWithTrailersByImdbIdQueryValidator : AbstractValidator query.ImdbId).NotNull().NotEmpty().WithMessage("ImdbId should not be null or empty."); - RuleFor(query => query.TrailersCount).NotNull().NotEmpty() + RuleFor(query => query.TrailersCount) + .NotNull() + .NotEmpty() .WithMessage("trailers-count should not be null or empty."); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/FindPopularMovies/FindPopularMoviesQuery.cs b/src/MovieSearch.Application/Movies/Features/FindPopularMovies/FindPopularMoviesQuery.cs index cae9b44..2fa09b6 100644 --- a/src/MovieSearch.Application/Movies/Features/FindPopularMovies/FindPopularMoviesQuery.cs +++ b/src/MovieSearch.Application/Movies/Features/FindPopularMovies/FindPopularMoviesQuery.cs @@ -5,4 +5,4 @@ namespace MovieSearch.Application.Movies.Features.FindPopularMovies; public class FindPopularMoviesQuery : IQuery { public int Page { get; init; } = 1; -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/FindPopularMovies/FindPopularMoviesQueryHandler.cs b/src/MovieSearch.Application/Movies/Features/FindPopularMovies/FindPopularMoviesQueryHandler.cs index d859282..4c6bc8d 100644 --- a/src/MovieSearch.Application/Movies/Features/FindPopularMovies/FindPopularMoviesQueryHandler.cs +++ b/src/MovieSearch.Application/Movies/Features/FindPopularMovies/FindPopularMoviesQueryHandler.cs @@ -19,8 +19,10 @@ public FindPopularMoviesQueryHandler(IMovieDbServiceClient movieDbServiceClient, _mapper = mapper; } - public async Task Handle(FindPopularMoviesQuery query, - CancellationToken cancellationToken) + public async Task Handle( + FindPopularMoviesQuery query, + CancellationToken cancellationToken + ) { Guard.Against.Null(query, nameof(FindPopularMoviesQuery)); @@ -28,6 +30,6 @@ public async Task Handle(FindPopularMoviesQuery qu var result = movies.Map(x => _mapper.Map(x)); - return new FindPopularMoviesQueryResult {MovieList = result}; + return new FindPopularMoviesQueryResult { MovieList = result }; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/FindPopularMovies/FindPopularMoviesQueryResult.cs b/src/MovieSearch.Application/Movies/Features/FindPopularMovies/FindPopularMoviesQueryResult.cs index 9aee05d..79e74cb 100644 --- a/src/MovieSearch.Application/Movies/Features/FindPopularMovies/FindPopularMoviesQueryResult.cs +++ b/src/MovieSearch.Application/Movies/Features/FindPopularMovies/FindPopularMoviesQueryResult.cs @@ -6,4 +6,4 @@ namespace MovieSearch.Application.Movies.Features.FindPopularMovies; public class FindPopularMoviesQueryResult { public ListResultModel MovieList { get; set; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/FindPopularMovies/FindPopularMoviesQueryValidator.cs b/src/MovieSearch.Application/Movies/Features/FindPopularMovies/FindPopularMoviesQueryValidator.cs index fc4ac0f..db6c8cd 100644 --- a/src/MovieSearch.Application/Movies/Features/FindPopularMovies/FindPopularMoviesQueryValidator.cs +++ b/src/MovieSearch.Application/Movies/Features/FindPopularMovies/FindPopularMoviesQueryValidator.cs @@ -9,4 +9,4 @@ public FindPopularMoviesQueryValidator() { RuleFor(query => query.Page).GreaterThan(0).WithMessage("page number should be greater than zero."); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/FindUpcomingMovies/FindUpcomingMoviesQuery.cs b/src/MovieSearch.Application/Movies/Features/FindUpcomingMovies/FindUpcomingMoviesQuery.cs index 2d26e6f..062495f 100644 --- a/src/MovieSearch.Application/Movies/Features/FindUpcomingMovies/FindUpcomingMoviesQuery.cs +++ b/src/MovieSearch.Application/Movies/Features/FindUpcomingMovies/FindUpcomingMoviesQuery.cs @@ -5,4 +5,4 @@ namespace MovieSearch.Application.Movies.Features.FindUpcomingMovies; public class FindUpcomingMoviesQuery : IQuery { public int Page { get; init; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/FindUpcomingMovies/FindUpcomingMoviesQueryHandler.cs b/src/MovieSearch.Application/Movies/Features/FindUpcomingMovies/FindUpcomingMoviesQueryHandler.cs index be5e2ec..5e953f4 100644 --- a/src/MovieSearch.Application/Movies/Features/FindUpcomingMovies/FindUpcomingMoviesQueryHandler.cs +++ b/src/MovieSearch.Application/Movies/Features/FindUpcomingMovies/FindUpcomingMoviesQueryHandler.cs @@ -8,8 +8,7 @@ namespace MovieSearch.Application.Movies.Features.FindUpcomingMovies; -public class - FindUpcomingMoviesQueryHandler : IRequestHandler +public class FindUpcomingMoviesQueryHandler : IRequestHandler { private readonly IMapper _mapper; private readonly IMovieDbServiceClient _movieDbServiceClient; @@ -20,8 +19,10 @@ public FindUpcomingMoviesQueryHandler(IMovieDbServiceClient movieDbServiceClient _mapper = mapper; } - public async Task Handle(FindUpcomingMoviesQuery query, - CancellationToken cancellationToken) + public async Task Handle( + FindUpcomingMoviesQuery query, + CancellationToken cancellationToken + ) { Guard.Against.Null(query, nameof(FindUpcomingMoviesQuery)); @@ -29,6 +30,6 @@ public async Task Handle(FindUpcomingMoviesQuery var result = movies.Map(x => _mapper.Map(x)); - return new FindUpcomingMoviesQueryResult {MovieList = result}; + return new FindUpcomingMoviesQueryResult { MovieList = result }; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/FindUpcomingMovies/FindUpcomingMoviesQueryResult.cs b/src/MovieSearch.Application/Movies/Features/FindUpcomingMovies/FindUpcomingMoviesQueryResult.cs index be1b594..5274684 100644 --- a/src/MovieSearch.Application/Movies/Features/FindUpcomingMovies/FindUpcomingMoviesQueryResult.cs +++ b/src/MovieSearch.Application/Movies/Features/FindUpcomingMovies/FindUpcomingMoviesQueryResult.cs @@ -6,4 +6,4 @@ namespace MovieSearch.Application.Movies.Features.FindUpcomingMovies; public class FindUpcomingMoviesQueryResult { public ListResultModel MovieList { get; set; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/FindUpcomingMovies/SearchMovieQueryValidator.cs b/src/MovieSearch.Application/Movies/Features/FindUpcomingMovies/SearchMovieQueryValidator.cs index d6d149c..7f7deee 100644 --- a/src/MovieSearch.Application/Movies/Features/FindUpcomingMovies/SearchMovieQueryValidator.cs +++ b/src/MovieSearch.Application/Movies/Features/FindUpcomingMovies/SearchMovieQueryValidator.cs @@ -9,4 +9,4 @@ public FindUpcomingMoviesQueryQueryValidator() { RuleFor(query => query.Page).GreaterThan(0).WithMessage("page number should be greater than zero."); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/SearchMovie/SearchMovieQuery.cs b/src/MovieSearch.Application/Movies/Features/SearchMovie/SearchMovieQuery.cs index b019dc7..ac2344e 100644 --- a/src/MovieSearch.Application/Movies/Features/SearchMovie/SearchMovieQuery.cs +++ b/src/MovieSearch.Application/Movies/Features/SearchMovie/SearchMovieQuery.cs @@ -6,8 +6,13 @@ namespace MovieSearch.Application.Movies.Features.SearchMovie; public class SearchMovieQuery : IQuery { - public SearchMovieQuery(string searchKeywords, int page = 1, int year = 0, int primaryReleaseYear = 0, - bool includeAdult = false) + public SearchMovieQuery( + string searchKeywords, + int page = 1, + int year = 0, + int primaryReleaseYear = 0, + bool includeAdult = false + ) { SearchKeywords = searchKeywords; Page = page; @@ -22,15 +27,16 @@ public SearchMovieQuery(string searchKeywords, int page = 1, int year = 0, int p public string SearchKeywords { get; } public int Page { get; } - public class CachePolicy : ICachePolicy { public DateTime? AbsoluteExpirationRelativeToNow => DateTime.Now.AddMinutes(15); public string GetCacheKey(SearchMovieQuery query) { - return CacheKey.With(query.GetType(), - $"SearchKeywords_{query.SearchKeywords?.ToLower().Trim()}_Page_{query.Page}_IncludeAdult_{query.IncludeAdult.ToString()}_PrimaryReleaseYear_{query.PrimaryReleaseYear}_Year_{query.Year}"); + return CacheKey.With( + query.GetType(), + $"SearchKeywords_{query.SearchKeywords?.ToLower().Trim()}_Page_{query.Page}_IncludeAdult_{query.IncludeAdult.ToString()}_PrimaryReleaseYear_{query.PrimaryReleaseYear}_Year_{query.Year}" + ); } } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/SearchMovie/SearchMovieQueryHandler.cs b/src/MovieSearch.Application/Movies/Features/SearchMovie/SearchMovieQueryHandler.cs index 1126f32..39065ce 100644 --- a/src/MovieSearch.Application/Movies/Features/SearchMovie/SearchMovieQueryHandler.cs +++ b/src/MovieSearch.Application/Movies/Features/SearchMovie/SearchMovieQueryHandler.cs @@ -23,12 +23,17 @@ public async Task Handle(SearchMovieQuery query, Cancell { Guard.Against.Null(query, nameof(SearchMovieQuery)); - var movies = await _movieDbServiceClient.SearchMovieAsync(query.SearchKeywords, - query.Page, query.IncludeAdult, query.Year, query - .PrimaryReleaseYear, cancellationToken); + var movies = await _movieDbServiceClient.SearchMovieAsync( + query.SearchKeywords, + query.Page, + query.IncludeAdult, + query.Year, + query.PrimaryReleaseYear, + cancellationToken + ); var result = movies.Map(x => _mapper.Map(x)); - return new SearchMovieQueryResult {MovieList = result}; + return new SearchMovieQueryResult { MovieList = result }; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/SearchMovie/SearchMovieQueryResult.cs b/src/MovieSearch.Application/Movies/Features/SearchMovie/SearchMovieQueryResult.cs index 113e4c6..e798cd3 100644 --- a/src/MovieSearch.Application/Movies/Features/SearchMovie/SearchMovieQueryResult.cs +++ b/src/MovieSearch.Application/Movies/Features/SearchMovie/SearchMovieQueryResult.cs @@ -6,4 +6,4 @@ namespace MovieSearch.Application.Movies.Features.SearchMovie; public class SearchMovieQueryResult { public ListResultModel MovieList { get; init; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/SearchMovie/SearchMovieQueryValidator.cs b/src/MovieSearch.Application/Movies/Features/SearchMovie/SearchMovieQueryValidator.cs index 74a5bd9..2e94d65 100644 --- a/src/MovieSearch.Application/Movies/Features/SearchMovie/SearchMovieQueryValidator.cs +++ b/src/MovieSearch.Application/Movies/Features/SearchMovie/SearchMovieQueryValidator.cs @@ -7,11 +7,17 @@ public class SearchMovieQueryValidator : AbstractValidator public SearchMovieQueryValidator() { RuleFor(query => query.Page).GreaterThan(0).WithMessage("page number should be greater than zero."); - RuleFor(query => query.Year).Must(x => x >= 1700).When(c => c.Year > 0) + RuleFor(query => query.Year) + .Must(x => x >= 1700) + .When(c => c.Year > 0) .WithMessage("year should be greater than 1700."); //if it's not a default value - RuleFor(query => query.PrimaryReleaseYear).Must(x => x >= 1700).When(c => c.PrimaryReleaseYear > 0) + RuleFor(query => query.PrimaryReleaseYear) + .Must(x => x >= 1700) + .When(c => c.PrimaryReleaseYear > 0) .WithMessage("primary release year should be greater than 1700."); - RuleFor(query => query.SearchKeywords).NotEmpty().NotNull() + RuleFor(query => query.SearchKeywords) + .NotEmpty() + .NotNull() .WithMessage("search value should not be null or empty."); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/SearchMovieByTitle/SearchMovieByTitleQuery.cs b/src/MovieSearch.Application/Movies/Features/SearchMovieByTitle/SearchMovieByTitleQuery.cs index 1034c54..a3baeea 100644 --- a/src/MovieSearch.Application/Movies/Features/SearchMovieByTitle/SearchMovieByTitleQuery.cs +++ b/src/MovieSearch.Application/Movies/Features/SearchMovieByTitle/SearchMovieByTitleQuery.cs @@ -15,8 +15,10 @@ public class CachePolicy : ICachePolicy +public class SearchMovieByTitleQueryHandler : IRequestHandler { private readonly IMapper _mapper; private readonly IMovieDbServiceClient _movieDbServiceClient; @@ -20,16 +19,21 @@ public SearchMovieByTitleQueryHandler(IMovieDbServiceClient movieDbServiceClient _mapper = mapper; } - public async Task Handle(SearchMovieByTitleQuery query, - CancellationToken cancellationToken) + public async Task Handle( + SearchMovieByTitleQuery query, + CancellationToken cancellationToken + ) { Guard.Against.Null(query, nameof(SearchMovieByTitleQuery)); - var movies = await _movieDbServiceClient.SearchMovieAsync(query.SearchKeywords, query - .Page, cancellationToken: cancellationToken); + var movies = await _movieDbServiceClient.SearchMovieAsync( + query.SearchKeywords, + query.Page, + cancellationToken: cancellationToken + ); var result = movies.Map(x => _mapper.Map(x)); - return new SearchMovieByTitleQueryResult {MovieList = result}; + return new SearchMovieByTitleQueryResult { MovieList = result }; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/SearchMovieByTitle/SearchMovieByTitleQueryResult.cs b/src/MovieSearch.Application/Movies/Features/SearchMovieByTitle/SearchMovieByTitleQueryResult.cs index 8bd4d22..3b7a60d 100644 --- a/src/MovieSearch.Application/Movies/Features/SearchMovieByTitle/SearchMovieByTitleQueryResult.cs +++ b/src/MovieSearch.Application/Movies/Features/SearchMovieByTitle/SearchMovieByTitleQueryResult.cs @@ -6,4 +6,4 @@ namespace MovieSearch.Application.Movies.Features.SearchMovieByTitle; public class SearchMovieByTitleQueryResult { public ListResultModel MovieList { get; set; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/Features/SearchMovieByTitle/SearchMovieByTitleQueryValidator.cs b/src/MovieSearch.Application/Movies/Features/SearchMovieByTitle/SearchMovieByTitleQueryValidator.cs index 05ee06c..9aca86d 100644 --- a/src/MovieSearch.Application/Movies/Features/SearchMovieByTitle/SearchMovieByTitleQueryValidator.cs +++ b/src/MovieSearch.Application/Movies/Features/SearchMovieByTitle/SearchMovieByTitleQueryValidator.cs @@ -8,7 +8,9 @@ public class SearchMovieByTitleQueryValidator : AbstractValidator query.Page).GreaterThan(0).WithMessage("page number should be greater than zero."); - RuleFor(query => query.SearchKeywords).NotEmpty().NotNull() + RuleFor(query => query.SearchKeywords) + .NotEmpty() + .NotNull() .WithMessage("search value should not be null or empty."); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Movies/MovieMappings.cs b/src/MovieSearch.Application/Movies/MovieMappings.cs index cc08303..98f40f7 100644 --- a/src/MovieSearch.Application/Movies/MovieMappings.cs +++ b/src/MovieSearch.Application/Movies/MovieMappings.cs @@ -14,4 +14,4 @@ public MovieMappings() CreateMap(); CreateMap(); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/People/Dtos/PersonDto.cs b/src/MovieSearch.Application/People/Dtos/PersonDto.cs index 42cf7b3..4fe71d8 100644 --- a/src/MovieSearch.Application/People/Dtos/PersonDto.cs +++ b/src/MovieSearch.Application/People/Dtos/PersonDto.cs @@ -20,4 +20,4 @@ public class PersonDto public string PlaceOfBirth { get; init; } public double Popularity { get; init; } public string ProfilePath { get; init; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/People/Dtos/PersonMovieCreditDto.cs b/src/MovieSearch.Application/People/Dtos/PersonMovieCreditDto.cs index 586d67c..3942ca4 100644 --- a/src/MovieSearch.Application/People/Dtos/PersonMovieCreditDto.cs +++ b/src/MovieSearch.Application/People/Dtos/PersonMovieCreditDto.cs @@ -33,4 +33,4 @@ public class PersonMovieCrewMemberDto public string PosterPath { get; init; } public DateTime? ReleaseDate { get; init; } public string Title { get; init; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/People/Dtos/PersonTVShowCreditDto.cs b/src/MovieSearch.Application/People/Dtos/PersonTVShowCreditDto.cs index 60225e8..4f67563 100644 --- a/src/MovieSearch.Application/People/Dtos/PersonTVShowCreditDto.cs +++ b/src/MovieSearch.Application/People/Dtos/PersonTVShowCreditDto.cs @@ -33,4 +33,4 @@ public class PersonTVShowCrewMemberDto public string Name { get; init; } public string OriginalName { get; init; } public string PosterPath { get; init; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/People/Exceptions/PersonMovieCreditsNotFoundException.cs b/src/MovieSearch.Application/People/Exceptions/PersonMovieCreditsNotFoundException.cs index 0487ae0..33fd497 100644 --- a/src/MovieSearch.Application/People/Exceptions/PersonMovieCreditsNotFoundException.cs +++ b/src/MovieSearch.Application/People/Exceptions/PersonMovieCreditsNotFoundException.cs @@ -4,8 +4,6 @@ namespace MovieSearch.Application.People.Exceptions; public class PersonMovieCreditsNotFoundException : NotFoundException { - public PersonMovieCreditsNotFoundException(int personId) : base( - $"can't find a person_movie_credits for a person with personId '{personId}' in the database.") - { - } -} \ No newline at end of file + public PersonMovieCreditsNotFoundException(int personId) + : base($"can't find a person_movie_credits for a person with personId '{personId}' in the database.") { } +} diff --git a/src/MovieSearch.Application/People/Exceptions/PersonNotFoundException.cs b/src/MovieSearch.Application/People/Exceptions/PersonNotFoundException.cs index 096f951..5987c98 100644 --- a/src/MovieSearch.Application/People/Exceptions/PersonNotFoundException.cs +++ b/src/MovieSearch.Application/People/Exceptions/PersonNotFoundException.cs @@ -4,7 +4,6 @@ namespace MovieSearch.Application.People.Exceptions; public class PersonNotFoundException : NotFoundException { - public PersonNotFoundException(int id) : base($"can't find a person with id '{id}' in the database.") - { - } -} \ No newline at end of file + public PersonNotFoundException(int id) + : base($"can't find a person with id '{id}' in the database.") { } +} diff --git a/src/MovieSearch.Application/People/Exceptions/PersonTVShowCreditsNotFoundException.cs b/src/MovieSearch.Application/People/Exceptions/PersonTVShowCreditsNotFoundException.cs index 37bcb7a..eda087a 100644 --- a/src/MovieSearch.Application/People/Exceptions/PersonTVShowCreditsNotFoundException.cs +++ b/src/MovieSearch.Application/People/Exceptions/PersonTVShowCreditsNotFoundException.cs @@ -4,8 +4,6 @@ namespace MovieSearch.Application.People.Exceptions; public class PersonTVShowCreditsNotFoundException : NotFoundException { - public PersonTVShowCreditsNotFoundException(int personId) : base( - $"can't find a person_tvshow_credits for a person with personId '{personId}' in the database.") - { - } -} \ No newline at end of file + public PersonTVShowCreditsNotFoundException(int personId) + : base($"can't find a person_tvshow_credits for a person with personId '{personId}' in the database.") { } +} diff --git a/src/MovieSearch.Application/People/Features/FindPersonById/FindByIdQueryHandler.cs b/src/MovieSearch.Application/People/Features/FindPersonById/FindByIdQueryHandler.cs index 13d28d1..7aa6b5d 100644 --- a/src/MovieSearch.Application/People/Features/FindPersonById/FindByIdQueryHandler.cs +++ b/src/MovieSearch.Application/People/Features/FindPersonById/FindByIdQueryHandler.cs @@ -20,8 +20,7 @@ public FindPersonByIdQueryHandler(IMovieDbServiceClient movieDbServiceClient, IM _mapper = mapper; } - public async Task Handle(FindPersonByIdQuery query, - CancellationToken cancellationToken) + public async Task Handle(FindPersonByIdQuery query, CancellationToken cancellationToken) { Guard.Against.Null(query, nameof(FindPersonByIdQuery)); @@ -32,6 +31,6 @@ public async Task Handle(FindPersonByIdQuery query, var result = _mapper.Map(person); - return new FindPersonByIdQueryResult {Person = result}; + return new FindPersonByIdQueryResult { Person = result }; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/People/Features/FindPersonById/FindMovieByIdQueryValidator.cs b/src/MovieSearch.Application/People/Features/FindPersonById/FindMovieByIdQueryValidator.cs index 2e9a9fa..bd5a7d5 100644 --- a/src/MovieSearch.Application/People/Features/FindPersonById/FindMovieByIdQueryValidator.cs +++ b/src/MovieSearch.Application/People/Features/FindPersonById/FindMovieByIdQueryValidator.cs @@ -9,4 +9,4 @@ public FindPersonByIdQueryValidator() { RuleFor(query => query.PersonId).GreaterThan(0).WithMessage("personId should be greater than zero."); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/People/Features/FindPersonById/FindPersonByIdQuery.cs b/src/MovieSearch.Application/People/Features/FindPersonById/FindPersonByIdQuery.cs index 57c08d4..ebfaac5 100644 --- a/src/MovieSearch.Application/People/Features/FindPersonById/FindPersonByIdQuery.cs +++ b/src/MovieSearch.Application/People/Features/FindPersonById/FindPersonByIdQuery.cs @@ -22,4 +22,4 @@ public string GetCacheKey(FindPersonByIdQuery query) return CacheKey.With(query.GetType(), query.PersonId.ToString()); } } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/People/Features/FindPersonById/FindPersonByIdQueryResult.cs b/src/MovieSearch.Application/People/Features/FindPersonById/FindPersonByIdQueryResult.cs index a101908..6c2fdb9 100644 --- a/src/MovieSearch.Application/People/Features/FindPersonById/FindPersonByIdQueryResult.cs +++ b/src/MovieSearch.Application/People/Features/FindPersonById/FindPersonByIdQueryResult.cs @@ -5,4 +5,4 @@ namespace MovieSearch.Application.People.Features.FindPersonById; public class FindPersonByIdQueryResult { public PersonDto Person { get; init; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/People/Features/FindPersonMovieCredits/FindPersonMovieCreditsQuery.cs b/src/MovieSearch.Application/People/Features/FindPersonMovieCredits/FindPersonMovieCreditsQuery.cs index 5d2cb1f..095e220 100644 --- a/src/MovieSearch.Application/People/Features/FindPersonMovieCredits/FindPersonMovieCreditsQuery.cs +++ b/src/MovieSearch.Application/People/Features/FindPersonMovieCredits/FindPersonMovieCreditsQuery.cs @@ -10,4 +10,4 @@ public FindPersonMovieCreditsQuery(int personId) } public int PersonId { get; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/People/Features/FindPersonMovieCredits/FindPersonMovieCreditsQueryHandler.cs b/src/MovieSearch.Application/People/Features/FindPersonMovieCredits/FindPersonMovieCreditsQueryHandler.cs index 9f7fd75..9697a47 100644 --- a/src/MovieSearch.Application/People/Features/FindPersonMovieCredits/FindPersonMovieCreditsQueryHandler.cs +++ b/src/MovieSearch.Application/People/Features/FindPersonMovieCredits/FindPersonMovieCreditsQueryHandler.cs @@ -9,9 +9,8 @@ namespace MovieSearch.Application.People.Features.FindPersonMovieCredits; -public class - FindPersonMovieCreditsQueryHandler : IRequestHandler +public class FindPersonMovieCreditsQueryHandler + : IRequestHandler { private readonly IMapper _mapper; private readonly IMovieDbServiceClient _movieDbServiceClient; @@ -22,13 +21,17 @@ public FindPersonMovieCreditsQueryHandler(IMovieDbServiceClient movieDbServiceCl _mapper = mapper; } - public async Task Handle(FindPersonMovieCreditsQuery query, - CancellationToken cancellationToken) + public async Task Handle( + FindPersonMovieCreditsQuery query, + CancellationToken cancellationToken + ) { Guard.Against.Null(query, nameof(FindPersonMovieCreditsQuery)); - var personMovieCredit = - await _movieDbServiceClient.GetPersonMovieCreditsAsync(query.PersonId, cancellationToken); + var personMovieCredit = await _movieDbServiceClient.GetPersonMovieCreditsAsync( + query.PersonId, + cancellationToken + ); if (personMovieCredit is null) throw new PersonMovieCreditsNotFoundException(query.PersonId); @@ -37,4 +40,4 @@ public async Task Handle(FindPersonMovieCredi return new FindPersonMovieCreditsQueryResult(result); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/People/Features/FindPersonMovieCredits/FindPersonMovieCreditsQueryResult.cs b/src/MovieSearch.Application/People/Features/FindPersonMovieCredits/FindPersonMovieCreditsQueryResult.cs index 34fa446..fee715b 100644 --- a/src/MovieSearch.Application/People/Features/FindPersonMovieCredits/FindPersonMovieCreditsQueryResult.cs +++ b/src/MovieSearch.Application/People/Features/FindPersonMovieCredits/FindPersonMovieCreditsQueryResult.cs @@ -10,4 +10,4 @@ public FindPersonMovieCreditsQueryResult(PersonMovieCreditDto personMovieCredit) } public PersonMovieCreditDto PersonMovieCredit { get; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/People/Features/FindPersonMovieCredits/FindPersonMovieCreditsQueryValidator.cs b/src/MovieSearch.Application/People/Features/FindPersonMovieCredits/FindPersonMovieCreditsQueryValidator.cs index b9ed8a1..062aa42 100644 --- a/src/MovieSearch.Application/People/Features/FindPersonMovieCredits/FindPersonMovieCreditsQueryValidator.cs +++ b/src/MovieSearch.Application/People/Features/FindPersonMovieCredits/FindPersonMovieCreditsQueryValidator.cs @@ -9,4 +9,4 @@ public FindPersonMovieCreditsQueryValidator() { RuleFor(query => query.PersonId).GreaterThan(0).WithMessage("personId should be greater than zero."); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/People/Features/FindPersonTVShowCredits/FindPersonTVShowCreditsQuery.cs b/src/MovieSearch.Application/People/Features/FindPersonTVShowCredits/FindPersonTVShowCreditsQuery.cs index 169c9ce..a60dca9 100644 --- a/src/MovieSearch.Application/People/Features/FindPersonTVShowCredits/FindPersonTVShowCreditsQuery.cs +++ b/src/MovieSearch.Application/People/Features/FindPersonTVShowCredits/FindPersonTVShowCreditsQuery.cs @@ -10,4 +10,4 @@ public FindPersonTVShowCreditsQuery(int personId) } public int PersonId { get; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/People/Features/FindPersonTVShowCredits/FindPersonTVShowCreditsQueryHandler.cs b/src/MovieSearch.Application/People/Features/FindPersonTVShowCredits/FindPersonTVShowCreditsQueryHandler.cs index 82915eb..3e0b67e 100644 --- a/src/MovieSearch.Application/People/Features/FindPersonTVShowCredits/FindPersonTVShowCreditsQueryHandler.cs +++ b/src/MovieSearch.Application/People/Features/FindPersonTVShowCredits/FindPersonTVShowCreditsQueryHandler.cs @@ -9,9 +9,8 @@ namespace MovieSearch.Application.People.Features.FindPersonTVShowCredits; -public class - FindPersonTVShowCreditsQueryHandler : IRequestHandler +public class FindPersonTVShowCreditsQueryHandler + : IRequestHandler { private readonly IMapper _mapper; private readonly IMovieDbServiceClient _movieDbServiceClient; @@ -22,13 +21,17 @@ public FindPersonTVShowCreditsQueryHandler(IMovieDbServiceClient movieDbServiceC _mapper = mapper; } - public async Task Handle(FindPersonTVShowCreditsQuery query, - CancellationToken cancellationToken) + public async Task Handle( + FindPersonTVShowCreditsQuery query, + CancellationToken cancellationToken + ) { Guard.Against.Null(query, nameof(FindPersonTVShowCreditsQuery)); - var personTVShowCredit = await _movieDbServiceClient.GetPersonTvShowCreditsAsync(query.PersonId, - cancellationToken); + var personTVShowCredit = await _movieDbServiceClient.GetPersonTvShowCreditsAsync( + query.PersonId, + cancellationToken + ); if (personTVShowCredit is null) throw new PersonTVShowCreditsNotFoundException(query.PersonId); @@ -37,4 +40,4 @@ public async Task Handle(FindPersonTVShowCre return new FindPersonTVShowCreditsQueryResult(result); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/People/Features/FindPersonTVShowCredits/FindPersonTVShowCreditsQueryResult.cs b/src/MovieSearch.Application/People/Features/FindPersonTVShowCredits/FindPersonTVShowCreditsQueryResult.cs index c1d0a8b..2dcae79 100644 --- a/src/MovieSearch.Application/People/Features/FindPersonTVShowCredits/FindPersonTVShowCreditsQueryResult.cs +++ b/src/MovieSearch.Application/People/Features/FindPersonTVShowCredits/FindPersonTVShowCreditsQueryResult.cs @@ -10,4 +10,4 @@ public FindPersonTVShowCreditsQueryResult(PersonTVShowCreditDto personTvShowCred } public PersonTVShowCreditDto PersonTVShowCredit { get; } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/People/Features/FindPersonTVShowCredits/FindPersonTVShowCreditsQueryValidator.cs b/src/MovieSearch.Application/People/Features/FindPersonTVShowCredits/FindPersonTVShowCreditsQueryValidator.cs index 2b38bf5..ba95e85 100644 --- a/src/MovieSearch.Application/People/Features/FindPersonTVShowCredits/FindPersonTVShowCreditsQueryValidator.cs +++ b/src/MovieSearch.Application/People/Features/FindPersonTVShowCredits/FindPersonTVShowCreditsQueryValidator.cs @@ -9,4 +9,4 @@ public FindPersonTVShowCreditsQueryValidator() { RuleFor(query => query.PersonId).GreaterThan(0).WithMessage("personId should be greater than zero."); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/People/PeopleMappings.cs b/src/MovieSearch.Application/People/PeopleMappings.cs index 8eeaa5e..fd315e3 100644 --- a/src/MovieSearch.Application/People/PeopleMappings.cs +++ b/src/MovieSearch.Application/People/PeopleMappings.cs @@ -17,4 +17,4 @@ public PeopleMappings() CreateMap(); CreateMap(); } -} \ No newline at end of file +} diff --git a/src/MovieSearch.Application/Services/Clients/IMovieDbServiceClient.cs b/src/MovieSearch.Application/Services/Clients/IMovieDbServiceClient.cs index 4e1d654..d14ccbd 100644 --- a/src/MovieSearch.Application/Services/Clients/IMovieDbServiceClient.cs +++ b/src/MovieSearch.Application/Services/Clients/IMovieDbServiceClient.cs @@ -10,6 +10,7 @@ using MovieSearch.Core.TV; namespace MovieSearch.Application.Services.Clients; + //https://deviq.com/domain-driven-design/anti-corruption-layer //https://docs.microsoft.com/en-us/azure/architecture/patterns/anti-corruption-layer //https://www.markhneedham.com/blog/2009/07/07/domain-driven-design-anti-corruption-layer/ @@ -29,8 +30,7 @@ public interface IMovieDbServiceClient /// /// /// - Task> GetNowPlayingAsync(int page, - CancellationToken cancellationToken = default); + Task> GetNowPlayingAsync(int page, CancellationToken cancellationToken = default); /// /// Search for movies. @@ -43,12 +43,14 @@ Task> GetNowPlayingAsync(int page, /// /// /// - Task> SearchMovieAsync(string keyword, + Task> SearchMovieAsync( + string keyword, int page = 1, bool includeAdult = false, int year = 0, int primaryReleaseYear = 0, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default + ); /// /// Get a list of the current popular movies on TMDB. This list updates daily. @@ -57,8 +59,7 @@ Task> SearchMovieAsync(string keyword, /// /// /// - Task> GetPopularMoviesAsync(int page, - CancellationToken cancellationToken = default); + Task> GetPopularMoviesAsync(int page, CancellationToken cancellationToken = default); /// /// Get the most newly created movie. This is a live response and will continuously change. @@ -76,8 +77,7 @@ Task> GetPopularMoviesAsync(int page, /// /// /// - Task> GetUpComingMoviesAsync(int page, - CancellationToken cancellationToken = default); + Task> GetUpComingMoviesAsync(int page, CancellationToken cancellationToken = default); /// /// Get the top rated movies on TMDB. @@ -86,8 +86,7 @@ Task> GetUpComingMoviesAsync(int page, /// /// /// - Task> GetTopRatedMoviesAsync(int page, - CancellationToken cancellationToken = default); + Task> GetTopRatedMoviesAsync(int page, CancellationToken cancellationToken = default); /// /// Get the list of official genres for movies. @@ -97,11 +96,17 @@ Task> GetTopRatedMoviesAsync(int page, /// Task> GetMovieGenresAsync(CancellationToken cancellationToken = default); - Task> FindMoviesByGenreAsync(IReadOnlyList genreIds, - int page = 1, CancellationToken cancellationToken = default); + Task> FindMoviesByGenreAsync( + IReadOnlyList genreIds, + int page = 1, + CancellationToken cancellationToken = default + ); - Task> FindTvShowsByGenreAsync(IReadOnlyList genreIds, - int page = 1, CancellationToken cancellationToken = default); + Task> FindTvShowsByGenreAsync( + IReadOnlyList genreIds, + int page = 1, + CancellationToken cancellationToken = default + ); /// /// Get the list of official genres for TV shows. @@ -146,8 +151,11 @@ Task> FindTvShowsByGenreAsync(IReadOnlyList gen /// /// /// - Task> GetMovieReviewsAsync(int movieId, int page = 1, - CancellationToken cancellationToken = default); + Task> GetMovieReviewsAsync( + int movieId, + int page = 1, + CancellationToken cancellationToken = default + ); /// /// Get the reviews for a TV show. @@ -157,8 +165,11 @@ Task> GetMovieReviewsAsync(int movieId, int page = 1 /// /// /// - Task> GetTvShowReviewsAsync(int tvShowId, int page = 1, - CancellationToken cancellationToken = default); + Task> GetTvShowReviewsAsync( + int tvShowId, + int page = 1, + CancellationToken cancellationToken = default + ); /// /// Retrieve the details of a movie or TV show review. @@ -185,8 +196,7 @@ Task> GetTvShowReviewsAsync(int tvShowId, int page = /// /// /// - Task GetPersonMovieCreditsAsync(int personId, - CancellationToken cancellationToken = default); + Task GetPersonMovieCreditsAsync(int personId, CancellationToken cancellationToken = default); /// /// Get the TV show credits for a person. @@ -195,8 +205,7 @@ Task GetPersonMovieCreditsAsync(int personId, /// /// /// - Task GetPersonTvShowCreditsAsync(int personId, - CancellationToken cancellationToken = default); + Task GetPersonTvShowCreditsAsync(int personId, CancellationToken cancellationToken = default); /// /// Get the credits (cast and crew) that have been added to a TV show. @@ -205,8 +214,7 @@ Task GetPersonTvShowCreditsAsync(int personId, /// /// /// - Task GetTvShowCreditsAsync(int tvShowId, - CancellationToken cancellationToken = default); + Task GetTvShowCreditsAsync(int tvShowId, CancellationToken cancellationToken = default); /// /// Get the images that belong to a movie. @@ -233,8 +241,10 @@ Task GetTvShowCreditsAsync(int tvShowId, /// /// /// - Task<(List