diff --git a/.editorconfig b/.editorconfig index 5bc8171a..b03610c9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -15,26 +15,6 @@ ij_smart_tabs = false ij_visual_guides = 80, 120, 150 ij_wrap_on_typing = false -[*.css] -ij_css_align_closing_brace_with_properties = false -ij_css_blank_lines_around_nested_selector = 1 -ij_css_blank_lines_between_blocks = 1 -ij_css_block_comment_add_space = false -ij_css_brace_placement = end_of_line -ij_css_enforce_quotes_on_format = false -ij_css_hex_color_long_format = false -ij_css_hex_color_lower_case = false -ij_css_hex_color_short_format = false -ij_css_hex_color_upper_case = false -ij_css_keep_blank_lines_in_code = 2 -ij_css_keep_indents_on_empty_lines = false -ij_css_keep_single_line_blocks = false -ij_css_properties_order = font, font-family, font-size, font-weight, font-style, font-variant, font-size-adjust, font-stretch, line-height, position, z-index, top, right, bottom, left, display, visibility, float, clear, overflow, overflow-x, overflow-y, clip, zoom, align-content, align-items, align-self, flex, flex-flow, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, justify-content, order, box-sizing, width, min-width, max-width, height, min-height, max-height, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, table-layout, empty-cells, caption-side, border-spacing, border-collapse, list-style, list-style-position, list-style-type, list-style-image, content, quotes, counter-reset, counter-increment, resize, cursor, user-select, nav-index, nav-up, nav-right, nav-down, nav-left, transition, transition-delay, transition-timing-function, transition-duration, transition-property, transform, transform-origin, animation, animation-name, animation-duration, animation-play-state, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, text-align, text-align-last, vertical-align, white-space, text-decoration, text-emphasis, text-emphasis-color, text-emphasis-style, text-emphasis-position, text-indent, text-justify, letter-spacing, word-spacing, text-outline, text-transform, text-wrap, text-overflow, text-overflow-ellipsis, text-overflow-mode, word-wrap, word-break, tab-size, hyphens, pointer-events, opacity, color, border, border-width, border-style, border-color, border-top, border-top-width, border-top-style, border-top-color, border-right, border-right-width, border-right-style, border-right-color, border-bottom, border-bottom-width, border-bottom-style, border-bottom-color, border-left, border-left-width, border-left-style, border-left-color, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-image, border-image-source, border-image-slice, border-image-width, border-image-outset, border-image-repeat, outline, outline-width, outline-style, outline-color, outline-offset, background, background-color, background-image, background-repeat, background-attachment, background-position, background-position-x, background-position-y, background-clip, background-origin, background-size, box-decoration-break, box-shadow, text-shadow -ij_css_space_after_colon = true -ij_css_space_before_opening_brace = true -ij_css_use_double_quotes = true -ij_css_value_alignment = do_not_align - [*.java] ij_continuation_indent_size = 4 ij_java_align_consecutive_assignments = false @@ -304,98 +284,6 @@ ij_java_wrap_first_method_in_call_chain = false ij_java_wrap_long_lines = false ij_java_wrap_semicolon_after_call_chain = false -[*.less] -indent_size = 2 -ij_less_align_closing_brace_with_properties = false -ij_less_blank_lines_around_nested_selector = 1 -ij_less_blank_lines_between_blocks = 1 -ij_less_block_comment_add_space = false -ij_less_brace_placement = 0 -ij_less_enforce_quotes_on_format = false -ij_less_hex_color_long_format = false -ij_less_hex_color_lower_case = false -ij_less_hex_color_short_format = false -ij_less_hex_color_upper_case = false -ij_less_keep_blank_lines_in_code = 2 -ij_less_keep_indents_on_empty_lines = false -ij_less_keep_single_line_blocks = false -ij_less_line_comment_add_space = false -ij_less_line_comment_at_first_column = false -ij_less_properties_order = font, font-family, font-size, font-weight, font-style, font-variant, font-size-adjust, font-stretch, line-height, position, z-index, top, right, bottom, left, display, visibility, float, clear, overflow, overflow-x, overflow-y, clip, zoom, align-content, align-items, align-self, flex, flex-flow, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, justify-content, order, box-sizing, width, min-width, max-width, height, min-height, max-height, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, table-layout, empty-cells, caption-side, border-spacing, border-collapse, list-style, list-style-position, list-style-type, list-style-image, content, quotes, counter-reset, counter-increment, resize, cursor, user-select, nav-index, nav-up, nav-right, nav-down, nav-left, transition, transition-delay, transition-timing-function, transition-duration, transition-property, transform, transform-origin, animation, animation-name, animation-duration, animation-play-state, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, text-align, text-align-last, vertical-align, white-space, text-decoration, text-emphasis, text-emphasis-color, text-emphasis-style, text-emphasis-position, text-indent, text-justify, letter-spacing, word-spacing, text-outline, text-transform, text-wrap, text-overflow, text-overflow-ellipsis, text-overflow-mode, word-wrap, word-break, tab-size, hyphens, pointer-events, opacity, color, border, border-width, border-style, border-color, border-top, border-top-width, border-top-style, border-top-color, border-right, border-right-width, border-right-style, border-right-color, border-bottom, border-bottom-width, border-bottom-style, border-bottom-color, border-left, border-left-width, border-left-style, border-left-color, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-image, border-image-source, border-image-slice, border-image-width, border-image-outset, border-image-repeat, outline, outline-width, outline-style, outline-color, outline-offset, background, background-color, background-image, background-repeat, background-attachment, background-position, background-position-x, background-position-y, background-clip, background-origin, background-size, box-decoration-break, box-shadow, text-shadow -ij_less_space_after_colon = true -ij_less_space_before_opening_brace = true -ij_less_use_double_quotes = true -ij_less_value_alignment = 0 - -[*.proto] -indent_size = 2 -tab_width = 2 -ij_continuation_indent_size = 4 -ij_protobuf_keep_blank_lines_in_code = 2 -ij_protobuf_keep_indents_on_empty_lines = false -ij_protobuf_keep_line_breaks = true -ij_protobuf_space_after_comma = true -ij_protobuf_space_before_comma = false -ij_protobuf_spaces_around_assignment_operators = true -ij_protobuf_spaces_within_braces = false -ij_protobuf_spaces_within_brackets = false - -[*.sass] -indent_size = 2 -ij_sass_align_closing_brace_with_properties = false -ij_sass_blank_lines_around_nested_selector = 1 -ij_sass_blank_lines_between_blocks = 1 -ij_sass_brace_placement = 0 -ij_sass_enforce_quotes_on_format = false -ij_sass_hex_color_long_format = false -ij_sass_hex_color_lower_case = false -ij_sass_hex_color_short_format = false -ij_sass_hex_color_upper_case = false -ij_sass_keep_blank_lines_in_code = 2 -ij_sass_keep_indents_on_empty_lines = false -ij_sass_keep_single_line_blocks = false -ij_sass_line_comment_add_space = false -ij_sass_line_comment_at_first_column = false -ij_sass_properties_order = font, font-family, font-size, font-weight, font-style, font-variant, font-size-adjust, font-stretch, line-height, position, z-index, top, right, bottom, left, display, visibility, float, clear, overflow, overflow-x, overflow-y, clip, zoom, align-content, align-items, align-self, flex, flex-flow, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, justify-content, order, box-sizing, width, min-width, max-width, height, min-height, max-height, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, table-layout, empty-cells, caption-side, border-spacing, border-collapse, list-style, list-style-position, list-style-type, list-style-image, content, quotes, counter-reset, counter-increment, resize, cursor, user-select, nav-index, nav-up, nav-right, nav-down, nav-left, transition, transition-delay, transition-timing-function, transition-duration, transition-property, transform, transform-origin, animation, animation-name, animation-duration, animation-play-state, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, text-align, text-align-last, vertical-align, white-space, text-decoration, text-emphasis, text-emphasis-color, text-emphasis-style, text-emphasis-position, text-indent, text-justify, letter-spacing, word-spacing, text-outline, text-transform, text-wrap, text-overflow, text-overflow-ellipsis, text-overflow-mode, word-wrap, word-break, tab-size, hyphens, pointer-events, opacity, color, border, border-width, border-style, border-color, border-top, border-top-width, border-top-style, border-top-color, border-right, border-right-width, border-right-style, border-right-color, border-bottom, border-bottom-width, border-bottom-style, border-bottom-color, border-left, border-left-width, border-left-style, border-left-color, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-image, border-image-source, border-image-slice, border-image-width, border-image-outset, border-image-repeat, outline, outline-width, outline-style, outline-color, outline-offset, background, background-color, background-image, background-repeat, background-attachment, background-position, background-position-x, background-position-y, background-clip, background-origin, background-size, box-decoration-break, box-shadow, text-shadow -ij_sass_space_after_colon = true -ij_sass_space_before_opening_brace = true -ij_sass_use_double_quotes = true -ij_sass_value_alignment = 0 - -[*.scss] -indent_size = 2 -ij_scss_align_closing_brace_with_properties = false -ij_scss_blank_lines_around_nested_selector = 1 -ij_scss_blank_lines_between_blocks = 1 -ij_scss_block_comment_add_space = false -ij_scss_brace_placement = 0 -ij_scss_enforce_quotes_on_format = false -ij_scss_hex_color_long_format = false -ij_scss_hex_color_lower_case = false -ij_scss_hex_color_short_format = false -ij_scss_hex_color_upper_case = false -ij_scss_keep_blank_lines_in_code = 2 -ij_scss_keep_indents_on_empty_lines = false -ij_scss_keep_single_line_blocks = false -ij_scss_line_comment_add_space = false -ij_scss_line_comment_at_first_column = false -ij_scss_properties_order = font, font-family, font-size, font-weight, font-style, font-variant, font-size-adjust, font-stretch, line-height, position, z-index, top, right, bottom, left, display, visibility, float, clear, overflow, overflow-x, overflow-y, clip, zoom, align-content, align-items, align-self, flex, flex-flow, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, justify-content, order, box-sizing, width, min-width, max-width, height, min-height, max-height, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, table-layout, empty-cells, caption-side, border-spacing, border-collapse, list-style, list-style-position, list-style-type, list-style-image, content, quotes, counter-reset, counter-increment, resize, cursor, user-select, nav-index, nav-up, nav-right, nav-down, nav-left, transition, transition-delay, transition-timing-function, transition-duration, transition-property, transform, transform-origin, animation, animation-name, animation-duration, animation-play-state, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, text-align, text-align-last, vertical-align, white-space, text-decoration, text-emphasis, text-emphasis-color, text-emphasis-style, text-emphasis-position, text-indent, text-justify, letter-spacing, word-spacing, text-outline, text-transform, text-wrap, text-overflow, text-overflow-ellipsis, text-overflow-mode, word-wrap, word-break, tab-size, hyphens, pointer-events, opacity, color, border, border-width, border-style, border-color, border-top, border-top-width, border-top-style, border-top-color, border-right, border-right-width, border-right-style, border-right-color, border-bottom, border-bottom-width, border-bottom-style, border-bottom-color, border-left, border-left-width, border-left-style, border-left-color, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-image, border-image-source, border-image-slice, border-image-width, border-image-outset, border-image-repeat, outline, outline-width, outline-style, outline-color, outline-offset, background, background-color, background-image, background-repeat, background-attachment, background-position, background-position-x, background-position-y, background-clip, background-origin, background-size, box-decoration-break, box-shadow, text-shadow -ij_scss_space_after_colon = true -ij_scss_space_before_opening_brace = true -ij_scss_use_double_quotes = true -ij_scss_value_alignment = 0 - -[*.vue] -indent_size = 2 -tab_width = 2 -ij_continuation_indent_size = 4 -ij_vue_indent_children_of_top_level = template -ij_vue_interpolation_new_line_after_start_delimiter = true -ij_vue_interpolation_new_line_before_end_delimiter = true -ij_vue_interpolation_wrap = off -ij_vue_keep_indents_on_empty_lines = false -ij_vue_spaces_within_interpolation_expressions = true - [.editorconfig] ij_editorconfig_align_group_field_declarations = false ij_editorconfig_space_after_colon = false @@ -426,179 +314,6 @@ ij_xml_space_around_equals_in_attribute = false ij_xml_space_inside_empty_tag = false ij_xml_text_wrap = normal -[{*.ats,*.cts,*.mts,*.ts}] -ij_continuation_indent_size = 4 -ij_typescript_align_imports = false -ij_typescript_align_multiline_array_initializer_expression = false -ij_typescript_align_multiline_binary_operation = false -ij_typescript_align_multiline_chained_methods = false -ij_typescript_align_multiline_extends_list = false -ij_typescript_align_multiline_for = true -ij_typescript_align_multiline_parameters = true -ij_typescript_align_multiline_parameters_in_calls = false -ij_typescript_align_multiline_ternary_operation = false -ij_typescript_align_object_properties = 0 -ij_typescript_align_union_types = false -ij_typescript_align_var_statements = 0 -ij_typescript_array_initializer_new_line_after_left_brace = false -ij_typescript_array_initializer_right_brace_on_new_line = false -ij_typescript_array_initializer_wrap = off -ij_typescript_assignment_wrap = off -ij_typescript_binary_operation_sign_on_next_line = false -ij_typescript_binary_operation_wrap = off -ij_typescript_blacklist_imports = rxjs/Rx, node_modules/**, **/node_modules/**, @angular/material, @angular/material/typings/** -ij_typescript_blank_lines_after_imports = 1 -ij_typescript_blank_lines_around_class = 1 -ij_typescript_blank_lines_around_field = 0 -ij_typescript_blank_lines_around_field_in_interface = 0 -ij_typescript_blank_lines_around_function = 1 -ij_typescript_blank_lines_around_method = 1 -ij_typescript_blank_lines_around_method_in_interface = 1 -ij_typescript_block_brace_style = end_of_line -ij_typescript_block_comment_add_space = false -ij_typescript_block_comment_at_first_column = true -ij_typescript_call_parameters_new_line_after_left_paren = false -ij_typescript_call_parameters_right_paren_on_new_line = false -ij_typescript_call_parameters_wrap = off -ij_typescript_catch_on_new_line = false -ij_typescript_chained_call_dot_on_new_line = true -ij_typescript_class_brace_style = end_of_line -ij_typescript_comma_on_new_line = false -ij_typescript_do_while_brace_force = never -ij_typescript_else_on_new_line = false -ij_typescript_enforce_trailing_comma = keep -ij_typescript_enum_constants_wrap = on_every_item -ij_typescript_extends_keyword_wrap = off -ij_typescript_extends_list_wrap = off -ij_typescript_field_prefix = _ -ij_typescript_file_name_style = relaxed -ij_typescript_finally_on_new_line = false -ij_typescript_for_brace_force = never -ij_typescript_for_statement_new_line_after_left_paren = false -ij_typescript_for_statement_right_paren_on_new_line = false -ij_typescript_for_statement_wrap = off -ij_typescript_force_quote_style = false -ij_typescript_force_semicolon_style = false -ij_typescript_function_expression_brace_style = end_of_line -ij_typescript_if_brace_force = never -ij_typescript_import_merge_members = global -ij_typescript_import_prefer_absolute_path = global -ij_typescript_import_sort_members = true -ij_typescript_import_sort_module_name = false -ij_typescript_import_use_node_resolution = true -ij_typescript_imports_wrap = on_every_item -ij_typescript_indent_case_from_switch = true -ij_typescript_indent_chained_calls = true -ij_typescript_indent_package_children = 0 -ij_typescript_jsdoc_include_types = false -ij_typescript_jsx_attribute_value = braces -ij_typescript_keep_blank_lines_in_code = 2 -ij_typescript_keep_first_column_comment = true -ij_typescript_keep_indents_on_empty_lines = false -ij_typescript_keep_line_breaks = true -ij_typescript_keep_simple_blocks_in_one_line = false -ij_typescript_keep_simple_methods_in_one_line = false -ij_typescript_line_comment_add_space = true -ij_typescript_line_comment_at_first_column = false -ij_typescript_method_brace_style = end_of_line -ij_typescript_method_call_chain_wrap = off -ij_typescript_method_parameters_new_line_after_left_paren = false -ij_typescript_method_parameters_right_paren_on_new_line = false -ij_typescript_method_parameters_wrap = off -ij_typescript_object_literal_wrap = on_every_item -ij_typescript_object_types_wrap = on_every_item -ij_typescript_parentheses_expression_new_line_after_left_paren = false -ij_typescript_parentheses_expression_right_paren_on_new_line = false -ij_typescript_place_assignment_sign_on_next_line = false -ij_typescript_prefer_as_type_cast = false -ij_typescript_prefer_explicit_types_function_expression_returns = false -ij_typescript_prefer_explicit_types_function_returns = false -ij_typescript_prefer_explicit_types_vars_fields = false -ij_typescript_prefer_parameters_wrap = false -ij_typescript_reformat_c_style_comments = false -ij_typescript_space_after_colon = true -ij_typescript_space_after_comma = true -ij_typescript_space_after_dots_in_rest_parameter = false -ij_typescript_space_after_generator_mult = true -ij_typescript_space_after_property_colon = true -ij_typescript_space_after_quest = true -ij_typescript_space_after_type_colon = true -ij_typescript_space_after_unary_not = false -ij_typescript_space_before_async_arrow_lparen = true -ij_typescript_space_before_catch_keyword = true -ij_typescript_space_before_catch_left_brace = true -ij_typescript_space_before_catch_parentheses = true -ij_typescript_space_before_class_lbrace = true -ij_typescript_space_before_class_left_brace = true -ij_typescript_space_before_colon = true -ij_typescript_space_before_comma = false -ij_typescript_space_before_do_left_brace = true -ij_typescript_space_before_else_keyword = true -ij_typescript_space_before_else_left_brace = true -ij_typescript_space_before_finally_keyword = true -ij_typescript_space_before_finally_left_brace = true -ij_typescript_space_before_for_left_brace = true -ij_typescript_space_before_for_parentheses = true -ij_typescript_space_before_for_semicolon = false -ij_typescript_space_before_function_left_parenth = true -ij_typescript_space_before_generator_mult = false -ij_typescript_space_before_if_left_brace = true -ij_typescript_space_before_if_parentheses = true -ij_typescript_space_before_method_call_parentheses = false -ij_typescript_space_before_method_left_brace = true -ij_typescript_space_before_method_parentheses = false -ij_typescript_space_before_property_colon = false -ij_typescript_space_before_quest = true -ij_typescript_space_before_switch_left_brace = true -ij_typescript_space_before_switch_parentheses = true -ij_typescript_space_before_try_left_brace = true -ij_typescript_space_before_type_colon = false -ij_typescript_space_before_unary_not = false -ij_typescript_space_before_while_keyword = true -ij_typescript_space_before_while_left_brace = true -ij_typescript_space_before_while_parentheses = true -ij_typescript_spaces_around_additive_operators = true -ij_typescript_spaces_around_arrow_function_operator = true -ij_typescript_spaces_around_assignment_operators = true -ij_typescript_spaces_around_bitwise_operators = true -ij_typescript_spaces_around_equality_operators = true -ij_typescript_spaces_around_logical_operators = true -ij_typescript_spaces_around_multiplicative_operators = true -ij_typescript_spaces_around_relational_operators = true -ij_typescript_spaces_around_shift_operators = true -ij_typescript_spaces_around_unary_operator = false -ij_typescript_spaces_within_array_initializer_brackets = false -ij_typescript_spaces_within_brackets = false -ij_typescript_spaces_within_catch_parentheses = false -ij_typescript_spaces_within_for_parentheses = false -ij_typescript_spaces_within_if_parentheses = false -ij_typescript_spaces_within_imports = false -ij_typescript_spaces_within_interpolation_expressions = false -ij_typescript_spaces_within_method_call_parentheses = false -ij_typescript_spaces_within_method_parentheses = false -ij_typescript_spaces_within_object_literal_braces = false -ij_typescript_spaces_within_object_type_braces = true -ij_typescript_spaces_within_parentheses = false -ij_typescript_spaces_within_switch_parentheses = false -ij_typescript_spaces_within_type_assertion = false -ij_typescript_spaces_within_union_types = true -ij_typescript_spaces_within_while_parentheses = false -ij_typescript_special_else_if_treatment = true -ij_typescript_ternary_operation_signs_on_next_line = false -ij_typescript_ternary_operation_wrap = off -ij_typescript_union_types_wrap = on_every_item -ij_typescript_use_chained_calls_group_indents = false -ij_typescript_use_double_quotes = true -ij_typescript_use_explicit_js_extension = auto -ij_typescript_use_import_type = auto -ij_typescript_use_path_mapping = always -ij_typescript_use_public_modifier = false -ij_typescript_use_semicolon_after_statement = true -ij_typescript_var_declaration_wrap = normal -ij_typescript_while_brace_force = never -ij_typescript_while_on_new_line = false -ij_typescript_wrap_comments = false - [{*.bash,*.sh,*.zsh}] indent_size = 2 tab_width = 2 @@ -610,369 +325,6 @@ ij_shell_redirect_followed_by_space = false ij_shell_switch_cases_indented = false ij_shell_use_unix_line_separator = true -[{*.cjs,*.js}] -ij_continuation_indent_size = 4 -ij_javascript_align_imports = false -ij_javascript_align_multiline_array_initializer_expression = false -ij_javascript_align_multiline_binary_operation = false -ij_javascript_align_multiline_chained_methods = false -ij_javascript_align_multiline_extends_list = false -ij_javascript_align_multiline_for = true -ij_javascript_align_multiline_parameters = true -ij_javascript_align_multiline_parameters_in_calls = false -ij_javascript_align_multiline_ternary_operation = false -ij_javascript_align_object_properties = 0 -ij_javascript_align_union_types = false -ij_javascript_align_var_statements = 0 -ij_javascript_array_initializer_new_line_after_left_brace = false -ij_javascript_array_initializer_right_brace_on_new_line = false -ij_javascript_array_initializer_wrap = off -ij_javascript_assignment_wrap = off -ij_javascript_binary_operation_sign_on_next_line = false -ij_javascript_binary_operation_wrap = off -ij_javascript_blacklist_imports = rxjs/Rx, node_modules/**, **/node_modules/**, @angular/material, @angular/material/typings/** -ij_javascript_blank_lines_after_imports = 1 -ij_javascript_blank_lines_around_class = 1 -ij_javascript_blank_lines_around_field = 0 -ij_javascript_blank_lines_around_function = 1 -ij_javascript_blank_lines_around_method = 1 -ij_javascript_block_brace_style = end_of_line -ij_javascript_block_comment_add_space = false -ij_javascript_block_comment_at_first_column = true -ij_javascript_call_parameters_new_line_after_left_paren = false -ij_javascript_call_parameters_right_paren_on_new_line = false -ij_javascript_call_parameters_wrap = off -ij_javascript_catch_on_new_line = false -ij_javascript_chained_call_dot_on_new_line = true -ij_javascript_class_brace_style = end_of_line -ij_javascript_comma_on_new_line = false -ij_javascript_do_while_brace_force = never -ij_javascript_else_on_new_line = false -ij_javascript_enforce_trailing_comma = keep -ij_javascript_extends_keyword_wrap = off -ij_javascript_extends_list_wrap = off -ij_javascript_field_prefix = _ -ij_javascript_file_name_style = relaxed -ij_javascript_finally_on_new_line = false -ij_javascript_for_brace_force = never -ij_javascript_for_statement_new_line_after_left_paren = false -ij_javascript_for_statement_right_paren_on_new_line = false -ij_javascript_for_statement_wrap = off -ij_javascript_force_quote_style = false -ij_javascript_force_semicolon_style = false -ij_javascript_function_expression_brace_style = end_of_line -ij_javascript_if_brace_force = never -ij_javascript_import_merge_members = global -ij_javascript_import_prefer_absolute_path = global -ij_javascript_import_sort_members = true -ij_javascript_import_sort_module_name = false -ij_javascript_import_use_node_resolution = true -ij_javascript_imports_wrap = on_every_item -ij_javascript_indent_case_from_switch = true -ij_javascript_indent_chained_calls = true -ij_javascript_indent_package_children = 0 -ij_javascript_jsx_attribute_value = braces -ij_javascript_keep_blank_lines_in_code = 2 -ij_javascript_keep_first_column_comment = true -ij_javascript_keep_indents_on_empty_lines = false -ij_javascript_keep_line_breaks = true -ij_javascript_keep_simple_blocks_in_one_line = false -ij_javascript_keep_simple_methods_in_one_line = false -ij_javascript_line_comment_add_space = true -ij_javascript_line_comment_at_first_column = false -ij_javascript_method_brace_style = end_of_line -ij_javascript_method_call_chain_wrap = off -ij_javascript_method_parameters_new_line_after_left_paren = false -ij_javascript_method_parameters_right_paren_on_new_line = false -ij_javascript_method_parameters_wrap = off -ij_javascript_object_literal_wrap = on_every_item -ij_javascript_object_types_wrap = on_every_item -ij_javascript_parentheses_expression_new_line_after_left_paren = false -ij_javascript_parentheses_expression_right_paren_on_new_line = false -ij_javascript_place_assignment_sign_on_next_line = false -ij_javascript_prefer_as_type_cast = false -ij_javascript_prefer_explicit_types_function_expression_returns = false -ij_javascript_prefer_explicit_types_function_returns = false -ij_javascript_prefer_explicit_types_vars_fields = false -ij_javascript_prefer_parameters_wrap = false -ij_javascript_reformat_c_style_comments = false -ij_javascript_space_after_colon = true -ij_javascript_space_after_comma = true -ij_javascript_space_after_dots_in_rest_parameter = false -ij_javascript_space_after_generator_mult = true -ij_javascript_space_after_property_colon = true -ij_javascript_space_after_quest = true -ij_javascript_space_after_type_colon = true -ij_javascript_space_after_unary_not = false -ij_javascript_space_before_async_arrow_lparen = true -ij_javascript_space_before_catch_keyword = true -ij_javascript_space_before_catch_left_brace = true -ij_javascript_space_before_catch_parentheses = true -ij_javascript_space_before_class_lbrace = true -ij_javascript_space_before_class_left_brace = true -ij_javascript_space_before_colon = true -ij_javascript_space_before_comma = false -ij_javascript_space_before_do_left_brace = true -ij_javascript_space_before_else_keyword = true -ij_javascript_space_before_else_left_brace = true -ij_javascript_space_before_finally_keyword = true -ij_javascript_space_before_finally_left_brace = true -ij_javascript_space_before_for_left_brace = true -ij_javascript_space_before_for_parentheses = true -ij_javascript_space_before_for_semicolon = false -ij_javascript_space_before_function_left_parenth = true -ij_javascript_space_before_generator_mult = false -ij_javascript_space_before_if_left_brace = true -ij_javascript_space_before_if_parentheses = true -ij_javascript_space_before_method_call_parentheses = false -ij_javascript_space_before_method_left_brace = true -ij_javascript_space_before_method_parentheses = false -ij_javascript_space_before_property_colon = false -ij_javascript_space_before_quest = true -ij_javascript_space_before_switch_left_brace = true -ij_javascript_space_before_switch_parentheses = true -ij_javascript_space_before_try_left_brace = true -ij_javascript_space_before_type_colon = false -ij_javascript_space_before_unary_not = false -ij_javascript_space_before_while_keyword = true -ij_javascript_space_before_while_left_brace = true -ij_javascript_space_before_while_parentheses = true -ij_javascript_spaces_around_additive_operators = true -ij_javascript_spaces_around_arrow_function_operator = true -ij_javascript_spaces_around_assignment_operators = true -ij_javascript_spaces_around_bitwise_operators = true -ij_javascript_spaces_around_equality_operators = true -ij_javascript_spaces_around_logical_operators = true -ij_javascript_spaces_around_multiplicative_operators = true -ij_javascript_spaces_around_relational_operators = true -ij_javascript_spaces_around_shift_operators = true -ij_javascript_spaces_around_unary_operator = false -ij_javascript_spaces_within_array_initializer_brackets = false -ij_javascript_spaces_within_brackets = false -ij_javascript_spaces_within_catch_parentheses = false -ij_javascript_spaces_within_for_parentheses = false -ij_javascript_spaces_within_if_parentheses = false -ij_javascript_spaces_within_imports = false -ij_javascript_spaces_within_interpolation_expressions = false -ij_javascript_spaces_within_method_call_parentheses = false -ij_javascript_spaces_within_method_parentheses = false -ij_javascript_spaces_within_object_literal_braces = false -ij_javascript_spaces_within_object_type_braces = true -ij_javascript_spaces_within_parentheses = false -ij_javascript_spaces_within_switch_parentheses = false -ij_javascript_spaces_within_type_assertion = false -ij_javascript_spaces_within_union_types = true -ij_javascript_spaces_within_while_parentheses = false -ij_javascript_special_else_if_treatment = true -ij_javascript_ternary_operation_signs_on_next_line = false -ij_javascript_ternary_operation_wrap = off -ij_javascript_union_types_wrap = on_every_item -ij_javascript_use_chained_calls_group_indents = false -ij_javascript_use_double_quotes = true -ij_javascript_use_explicit_js_extension = auto -ij_javascript_use_import_type = auto -ij_javascript_use_path_mapping = always -ij_javascript_use_public_modifier = false -ij_javascript_use_semicolon_after_statement = true -ij_javascript_var_declaration_wrap = normal -ij_javascript_while_brace_force = never -ij_javascript_while_on_new_line = false -ij_javascript_wrap_comments = false - -[{*.ft,*.vm,*.vsl}] -ij_vtl_keep_indents_on_empty_lines = false - -[{*.gant,*.groovy,*.gy}] -ij_groovy_align_group_field_declarations = false -ij_groovy_align_multiline_array_initializer_expression = false -ij_groovy_align_multiline_assignment = false -ij_groovy_align_multiline_binary_operation = false -ij_groovy_align_multiline_chained_methods = false -ij_groovy_align_multiline_extends_list = false -ij_groovy_align_multiline_for = true -ij_groovy_align_multiline_list_or_map = true -ij_groovy_align_multiline_method_parentheses = false -ij_groovy_align_multiline_parameters = true -ij_groovy_align_multiline_parameters_in_calls = false -ij_groovy_align_multiline_resources = true -ij_groovy_align_multiline_ternary_operation = false -ij_groovy_align_multiline_throws_list = false -ij_groovy_align_named_args_in_map = true -ij_groovy_align_throws_keyword = false -ij_groovy_array_initializer_new_line_after_left_brace = false -ij_groovy_array_initializer_right_brace_on_new_line = false -ij_groovy_array_initializer_wrap = off -ij_groovy_assert_statement_wrap = off -ij_groovy_assignment_wrap = off -ij_groovy_binary_operation_wrap = off -ij_groovy_blank_lines_after_class_header = 0 -ij_groovy_blank_lines_after_imports = 1 -ij_groovy_blank_lines_after_package = 1 -ij_groovy_blank_lines_around_class = 1 -ij_groovy_blank_lines_around_field = 0 -ij_groovy_blank_lines_around_field_in_interface = 0 -ij_groovy_blank_lines_around_method = 1 -ij_groovy_blank_lines_around_method_in_interface = 1 -ij_groovy_blank_lines_before_imports = 1 -ij_groovy_blank_lines_before_method_body = 0 -ij_groovy_blank_lines_before_package = 0 -ij_groovy_block_brace_style = end_of_line -ij_groovy_block_comment_add_space = false -ij_groovy_block_comment_at_first_column = true -ij_groovy_call_parameters_new_line_after_left_paren = false -ij_groovy_call_parameters_right_paren_on_new_line = false -ij_groovy_call_parameters_wrap = off -ij_groovy_catch_on_new_line = false -ij_groovy_class_annotation_wrap = split_into_lines -ij_groovy_class_brace_style = end_of_line -ij_groovy_class_count_to_use_import_on_demand = 5 -ij_groovy_do_while_brace_force = never -ij_groovy_else_on_new_line = false -ij_groovy_enable_groovydoc_formatting = true -ij_groovy_enum_constants_wrap = off -ij_groovy_extends_keyword_wrap = off -ij_groovy_extends_list_wrap = off -ij_groovy_field_annotation_wrap = split_into_lines -ij_groovy_finally_on_new_line = false -ij_groovy_for_brace_force = never -ij_groovy_for_statement_new_line_after_left_paren = false -ij_groovy_for_statement_right_paren_on_new_line = false -ij_groovy_for_statement_wrap = off -ij_groovy_ginq_general_clause_wrap_policy = 2 -ij_groovy_ginq_having_wrap_policy = 1 -ij_groovy_ginq_indent_having_clause = true -ij_groovy_ginq_indent_on_clause = true -ij_groovy_ginq_on_wrap_policy = 1 -ij_groovy_ginq_space_after_keyword = true -ij_groovy_if_brace_force = never -ij_groovy_import_annotation_wrap = 2 -ij_groovy_imports_layout = *, |, javax.**, java.**, |, $* -ij_groovy_indent_case_from_switch = true -ij_groovy_indent_label_blocks = true -ij_groovy_insert_inner_class_imports = false -ij_groovy_keep_blank_lines_before_right_brace = 2 -ij_groovy_keep_blank_lines_in_code = 2 -ij_groovy_keep_blank_lines_in_declarations = 2 -ij_groovy_keep_control_statement_in_one_line = true -ij_groovy_keep_first_column_comment = true -ij_groovy_keep_indents_on_empty_lines = false -ij_groovy_keep_line_breaks = true -ij_groovy_keep_multiple_expressions_in_one_line = false -ij_groovy_keep_simple_blocks_in_one_line = false -ij_groovy_keep_simple_classes_in_one_line = true -ij_groovy_keep_simple_lambdas_in_one_line = true -ij_groovy_keep_simple_methods_in_one_line = true -ij_groovy_label_indent_absolute = false -ij_groovy_label_indent_size = 0 -ij_groovy_lambda_brace_style = end_of_line -ij_groovy_layout_static_imports_separately = true -ij_groovy_line_comment_add_space = false -ij_groovy_line_comment_add_space_on_reformat = false -ij_groovy_line_comment_at_first_column = true -ij_groovy_method_annotation_wrap = split_into_lines -ij_groovy_method_brace_style = end_of_line -ij_groovy_method_call_chain_wrap = off -ij_groovy_method_parameters_new_line_after_left_paren = false -ij_groovy_method_parameters_right_paren_on_new_line = false -ij_groovy_method_parameters_wrap = off -ij_groovy_modifier_list_wrap = false -ij_groovy_names_count_to_use_import_on_demand = 3 -ij_groovy_packages_to_use_import_on_demand = java.awt.*, javax.swing.* -ij_groovy_parameter_annotation_wrap = off -ij_groovy_parentheses_expression_new_line_after_left_paren = false -ij_groovy_parentheses_expression_right_paren_on_new_line = false -ij_groovy_prefer_parameters_wrap = false -ij_groovy_resource_list_new_line_after_left_paren = false -ij_groovy_resource_list_right_paren_on_new_line = false -ij_groovy_resource_list_wrap = off -ij_groovy_space_after_assert_separator = true -ij_groovy_space_after_colon = true -ij_groovy_space_after_comma = true -ij_groovy_space_after_comma_in_type_arguments = true -ij_groovy_space_after_for_semicolon = true -ij_groovy_space_after_quest = true -ij_groovy_space_after_type_cast = true -ij_groovy_space_before_annotation_parameter_list = false -ij_groovy_space_before_array_initializer_left_brace = false -ij_groovy_space_before_assert_separator = false -ij_groovy_space_before_catch_keyword = true -ij_groovy_space_before_catch_left_brace = true -ij_groovy_space_before_catch_parentheses = true -ij_groovy_space_before_class_left_brace = true -ij_groovy_space_before_closure_left_brace = true -ij_groovy_space_before_colon = true -ij_groovy_space_before_comma = false -ij_groovy_space_before_do_left_brace = true -ij_groovy_space_before_else_keyword = true -ij_groovy_space_before_else_left_brace = true -ij_groovy_space_before_finally_keyword = true -ij_groovy_space_before_finally_left_brace = true -ij_groovy_space_before_for_left_brace = true -ij_groovy_space_before_for_parentheses = true -ij_groovy_space_before_for_semicolon = false -ij_groovy_space_before_if_left_brace = true -ij_groovy_space_before_if_parentheses = true -ij_groovy_space_before_method_call_parentheses = false -ij_groovy_space_before_method_left_brace = true -ij_groovy_space_before_method_parentheses = false -ij_groovy_space_before_quest = true -ij_groovy_space_before_record_parentheses = false -ij_groovy_space_before_switch_left_brace = true -ij_groovy_space_before_switch_parentheses = true -ij_groovy_space_before_synchronized_left_brace = true -ij_groovy_space_before_synchronized_parentheses = true -ij_groovy_space_before_try_left_brace = true -ij_groovy_space_before_try_parentheses = true -ij_groovy_space_before_while_keyword = true -ij_groovy_space_before_while_left_brace = true -ij_groovy_space_before_while_parentheses = true -ij_groovy_space_in_named_argument = true -ij_groovy_space_in_named_argument_before_colon = false -ij_groovy_space_within_empty_array_initializer_braces = false -ij_groovy_space_within_empty_method_call_parentheses = false -ij_groovy_spaces_around_additive_operators = true -ij_groovy_spaces_around_assignment_operators = true -ij_groovy_spaces_around_bitwise_operators = true -ij_groovy_spaces_around_equality_operators = true -ij_groovy_spaces_around_lambda_arrow = true -ij_groovy_spaces_around_logical_operators = true -ij_groovy_spaces_around_multiplicative_operators = true -ij_groovy_spaces_around_regex_operators = true -ij_groovy_spaces_around_relational_operators = true -ij_groovy_spaces_around_shift_operators = true -ij_groovy_spaces_within_annotation_parentheses = false -ij_groovy_spaces_within_array_initializer_braces = false -ij_groovy_spaces_within_braces = true -ij_groovy_spaces_within_brackets = false -ij_groovy_spaces_within_cast_parentheses = false -ij_groovy_spaces_within_catch_parentheses = false -ij_groovy_spaces_within_for_parentheses = false -ij_groovy_spaces_within_gstring_injection_braces = false -ij_groovy_spaces_within_if_parentheses = false -ij_groovy_spaces_within_list_or_map = false -ij_groovy_spaces_within_method_call_parentheses = false -ij_groovy_spaces_within_method_parentheses = false -ij_groovy_spaces_within_parentheses = false -ij_groovy_spaces_within_switch_parentheses = false -ij_groovy_spaces_within_synchronized_parentheses = false -ij_groovy_spaces_within_try_parentheses = false -ij_groovy_spaces_within_tuple_expression = false -ij_groovy_spaces_within_while_parentheses = false -ij_groovy_special_else_if_treatment = true -ij_groovy_ternary_operation_wrap = off -ij_groovy_throws_keyword_wrap = off -ij_groovy_throws_list_wrap = off -ij_groovy_use_flying_geese_braces = false -ij_groovy_use_fq_class_names = false -ij_groovy_use_fq_class_names_in_javadoc = true -ij_groovy_use_relative_indents = false -ij_groovy_use_single_class_imports = true -ij_groovy_variable_annotation_wrap = off -ij_groovy_while_brace_force = never -ij_groovy_while_on_new_line = false -ij_groovy_wrap_chain_calls_after_dot = false -ij_groovy_wrap_long_lines = false - [{*.har,*.jsb2,*.jsb3,*.json,*.jsonc,*.postman_collection,*.postman_environment,.babelrc,.eslintrc,.stylelintrc,.ws-context,bowerrc,jest.config}] indent_size = 2 tab_width = 2 @@ -992,49 +344,6 @@ ij_json_spaces_within_braces = false ij_json_spaces_within_brackets = false ij_json_wrap_long_lines = false -[{*.htm,*.html,*.sht,*.shtm,*.shtml}] -ij_html_add_new_line_before_tags = body, div, p, form, h1, h2, h3 -ij_html_align_attributes = true -ij_html_align_text = false -ij_html_attribute_wrap = normal -ij_html_block_comment_add_space = false -ij_html_block_comment_at_first_column = true -ij_html_do_not_align_children_of_min_lines = 0 -ij_html_do_not_break_if_inline_tags = title, h1, h2, h3, h4, h5, h6, p -ij_html_do_not_indent_children_of_tags = html, body, thead, tbody, tfoot -ij_html_enforce_quotes = false -ij_html_inline_tags = a, abbr, acronym, b, basefont, bdo, big, br, cite, cite, code, dfn, em, font, i, img, input, kbd, label, q, s, samp, select, small, span, strike, strong, sub, sup, textarea, tt, u, var -ij_html_keep_blank_lines = 2 -ij_html_keep_indents_on_empty_lines = false -ij_html_keep_line_breaks = true -ij_html_keep_line_breaks_in_text = true -ij_html_keep_whitespaces = false -ij_html_keep_whitespaces_inside = span, pre, textarea -ij_html_line_comment_at_first_column = true -ij_html_new_line_after_last_attribute = never -ij_html_new_line_before_first_attribute = never -ij_html_quote_style = double -ij_html_remove_new_line_before_tags = br -ij_html_space_after_tag_name = false -ij_html_space_around_equality_in_attribute = false -ij_html_space_inside_empty_tag = false -ij_html_text_wrap = normal - -[{*.http,*.rest}] -indent_size = 0 -ij_continuation_indent_size = 4 -ij_http-request_call_parameters_wrap = normal -ij_http-request_method_parameters_wrap = split_into_lines -ij_http-request_space_before_comma = true -ij_http-request_spaces_around_assignment_operators = true - -[{*.jsf,*.jsp,*.jspf,*.tag,*.tagf,*.xjsp}] -ij_jsp_jsp_prefer_comma_separated_import_list = false -ij_jsp_keep_indents_on_empty_lines = false - -[{*.jspx,*.tagx}] -ij_jspx_keep_indents_on_empty_lines = false - [{*.kt,*.kts}] ij_kotlin_align_in_columns_case_branch = false ij_kotlin_align_multiline_binary_operation = false @@ -1137,32 +446,12 @@ ij_markdown_min_lines_between_paragraphs = 1 ij_markdown_wrap_text_if_long = true ij_markdown_wrap_text_inside_blockquotes = true -[{*.pb,*.textproto,*.txtpb}] -indent_size = 2 -tab_width = 2 -ij_continuation_indent_size = 4 -ij_prototext_keep_blank_lines_in_code = 2 -ij_prototext_keep_indents_on_empty_lines = false -ij_prototext_keep_line_breaks = true -ij_prototext_space_after_colon = true -ij_prototext_space_after_comma = true -ij_prototext_space_before_colon = false -ij_prototext_space_before_comma = false -ij_prototext_spaces_within_braces = true -ij_prototext_spaces_within_brackets = false - [{*.properties,spring.handlers,spring.schemas}] ij_properties_align_group_field_declarations = false ij_properties_keep_blank_lines = false ij_properties_key_value_delimiter = equals ij_properties_spaces_around_key_value_delimiter = false -[{*.qute.htm,*.qute.html,*.qute.json,*.qute.txt,*.qute.yaml,*.qute.yml}] -ij_qute_keep_indents_on_empty_lines = false - -[{*.toml,Cargo.lock,Cargo.toml.orig,Gopkg.lock,Pipfile,poetry.lock}] -ij_toml_keep_indents_on_empty_lines = false - [{*.yaml,*.yml}] indent_size = 2 tab_width = 2 diff --git a/.github/workflows/deploy-project.yaml b/.github/workflows/deploy-project.yaml index bba1a11d..ff96cee7 100644 --- a/.github/workflows/deploy-project.yaml +++ b/.github/workflows/deploy-project.yaml @@ -1,4 +1,4 @@ -# Name of the workflow and a display name for the workflow +# Name of the workflow and an action title to display in the GitHub UI name: Deploy Project run-name: ${{ github.actor }} is deploying the project @@ -16,35 +16,36 @@ on: # Runs every time a pull request is created, updated, etc. can use "types" to run only on select PR activities pull_request: -# Used to make sure that only one workflow in specified group runs at the same time +# Used to make sure that only one workflow in the specified group runs at the same time concurrency: - # Define concurrency group which will then be used to determine duplicate workflow runs + # Define concurrency group which is then used to determine duplicate workflow runs # The second property uses fallback values since not every run is a PR run group: ${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }} cancel-in-progress: true # Modify access granted to the workflow in the GITHUB_TOKEN -# Mainly used here to give checks a write permission so we can get a test run report +# Mainly used here to give checks a write permission to allow a test run reporter to publish test results permissions: contents: read actions: read - checks: write + checks: write # See the [[test-report-publisher]] step for explanation why this is needed -# List of jobs that this workflow will execute +# List of jobs that this workflow executes, each job can run on different runners, have different steps, etc. jobs: - # Unique identifier for this job, make sure it's unique, you can use "name" property to give it more descriptive name + # Unique identifier for this job, make sure it is unique, you can use "name" property to give it a more descriptive name test-project: - # This name will be displayed in the GitHub UI when + # This name is displayed in the GitHub UI when the job is running name: Build and Test the Project - # Defines what runner to run this job on, if you want you can use an array to match runner name - # for example this can be written as [self-hosted, linux] and only runner matching all of these values will run it + # Defines what runner to run this job on, if you want you can use an array of tags/identifiers to match the runner name + # For example this can be written as [self-hosted, linux] and only runner matching all of these values will run it runs-on: self-hosted-linux - # List of steps that this job needs to execute in sequential order, any changes done in one step will carry over to - # others, so be mindful of that when making changes to files + # List of steps that this job executes in sequential order, any changes done in one step will carry over to others + # Be mindful of that when making changes to files steps: - # Simple step which merely checks out the repository to the runner + # Simple step which merely checks out the repository to the runner, making it available for other steps - name: Checkout - # Specifies that this step should run a pre-defined action + # Specifies that this step should run a pre-defined action, meaning an action that was made by someone else or exists elsewhere + # In this case the provided identifier is a reference to a repository in: https://github.com/actions/checkout and the version is set tov4 uses: actions/checkout@v4 # This steps installs specified Java JDK @@ -53,19 +54,19 @@ jobs: # Settings/Inputs/Parameters for the action with: # What JDK distribution to download and use - distribution: temurin # If you have no good reason go with Temurin distribution + distribution: temurin # If you have no good reasons to choose something else, go with Temurin distribution java-version: 21 architecture: x64 # Sets up caching and restoring of dependencies for the specified package manager # NB! If you decide to use a non-standard path for package repositories you need to set up caching yourself - # In that case use the "@actions/cache" action + # In that case use the "@actions/cache" action cache: maven - # Since self hosted runner does not come with Maven, we need to install it - # Same for git, we need it to upload test results, hence why we initialize the repo - # Lastly gettext provides envsubst which is used in the "Verify the Project" step to replace env variables + # Since the self-hosted runner does not come with Maven, it must be installed manually + # Same for git, it is needed to upload the test results, hence why repo is initialized using "git init" + # Lastly gettext provides envsubst which is used in the [[verify-the-project]] step to replace env variables - name: Install Maven and Initialize Repo - # Map of environment variables available for only this step, you can define it on job or workflow level too + # Map of environment variables available for this step only, you can define it on job or workflow level too env: MAVEN_VERSION: 3.9.7 # So we can easily change the Maven version run: | # Pipe allows to make the script multiline, if you need to run only one command you can skip it @@ -78,9 +79,10 @@ jobs: git init mvn -v - # This step imports secrets from Vault, it is recommended to use Vault as it allows checking and editing - # GitHub secrets wont permit you to see or re-use secrets, speak with Platform team to set up a namespace for you - # They should also give you an approle "user" which will allow you to get the secrets from github action + # This step imports secrets from Vault, it is recommended to use Vault as it allows checking and editing the secrets + # GitHub does not permit to check secret value or re-use the secrets + # Speak with Platform team to set up a namespace if needed + # They should also create an approle "user" which allows fetching the secrets within a GitHub action # # The url must be in form: https://, while IDs are UUIDs # When providing path to the secret you need to include "/data" after namespace and before the secret name @@ -88,66 +90,62 @@ jobs: uses: hashicorp/vault-action@v3 with: url: ${{ secrets.VAULT_URL }} - method: approle + method: approle # Method used to authenticate against Vault roleId: ${{ secrets.VAULT_ROLE_ID }} secretId: ${{ secrets.VAULT_SECRET_ID }} - secrets: secret/v1/application/k8s/mlt/data/proxy * + secrets: secret/v1/application/k8s/mlt/data/proxy * # The "*" at the end is a wildcard, it will get all secrets in the path - # This step will actually build, test, and verify the project - # envsubst is used to provide proxy settings for Maven, proxy host address must be an IP address, not URL + # This step actually builds, tests, and verifies the project using Maven #[[verify-the-project]] + # "envsubst" supplies proxy settings for Maven, proxy host address must be an IP address, not a URL - name: Verify the Project + # Replace tags in settings.xml with values from environment and save it to a new file, then use it when running Maven run: | envsubst < .m2/settings.xml > .m2/replaced.xml mvn -B -e -s .m2/replaced.xml verify - # This step will use the test-reporter action to publish test results in the workflow run + # This step uses the test-reporter action to publish test results in the workflow run #[[test-report-publisher]] + # It looks for any file named "TEST-...xml" in the project and uses it to publish the test results + # The job name in GitHub with the test results is "JUnit Test Results" - name: Publish Test Report uses: dorny/test-reporter@v1 # If defines conditions that need to be fulfilled to run this step, in this case this step will always run + # That is because the report should be published, even if the tests failed if: always() with: - # Name for the report - name: JUnit Tests - # Where to look for report files, in this case look for any file named "TEST-...xml" anywhere in the project - path: ./**/TEST-*.xml - # What reporter created to use, since this project uses JUnit java-junit is used - reporter: java-junit - # Fail this step if there are test errors - fail-on-error: true - # Fail this step if no test results were found - fail-on-empty: true + name: JUnit Test Results # Name for the report + path: ./**/TEST-*.xml # Where to look for report files, any file named "TEST-...xml" anywhere in the project + reporter: java-junit # What reporter to use, since this project uses JUnit, the reporter is "java-junit" + fail-on-error: true # Fail this step if there are test errors + fail-on-empty: true # Fail this step if no test results were found - # Last step will upload the JAR file as an artifact that can then be used in other jobs + # This step uploads the JAR file as an artifact which can then be used in other jobs - name: Upload the JAR File uses: actions/upload-artifact@v4 with: - # Artifact ID - name: packaged-project - # Where to find artifact file(s) - # Add "project.build.finalName --> ${project.artifactId}" to pom.xml to drop the version suffix - path: target/wls.jar - # Fail if artifact is not found - if-no-files-found: error - # Allow overwriting of previous artifacts - overwrite: true + name: packaged-project # Artifact ID, used to reference the artifact in other jobs when downloading it #[[packaged-project-id]] + path: target/wls.jar # File(s) to include, the path is relative to the repository root + # Set "project.build.finalName --> ${project.artifactId}" in pom.xml to drop the version suffix + if-no-files-found: error # Fail if artifact is not found + overwrite: true # Allow overwriting of previous artifacts - # This job will build a Docker image with relevant tags and labels, and publish it in Harbor + # This job builds a Docker image with relevant tags and labels, and publishes it to Harbor publish-image: name: Build and Publish the Docker Image # Defines that test-project job must finish successfully first, before this one can run needs: test-project - # The if statement limits it to run only if action was triggered on main branch or one of the tags + # The if statement limits the job to only run if action was triggered on main branch or one of the tags if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') runs-on: self-hosted-linux # Outputs allows a job to output values that can be picked up by _downstream_ jobs that _depend_ on this job outputs: - # The metadata step produces an image name that we need to use in deployment + # The metadata step produces an image name that is then used in deployment jobs + # Gets the value from the "meta" step: [[meta-step-id]] image-name: ${{ steps.meta.outputs.tags }} steps: - name: Checkout uses: actions/checkout@v4 - # Sets up a Docker Build action which will produce an image + # Sets up a Docker Build action which is then used to produce the Docker image - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 with: @@ -163,9 +161,9 @@ jobs: secretId: ${{ secrets.VAULT_SECRET_ID }} secrets: secret/v1/application/k8s/mlt/data/harbor * - # Simple action that authenticates against the NLB's Harbor instance - # The url must be in form: https://, username and password should be for the github robot user - # Ask Platform team to create a robot user for you that has access to your project(s) in Harbor registry + # Simple action that authenticates against the NLN's Harbor instance + # The url must be in form: https://, username and password should be for the GitHub robot user + # Ask Platform team to create a robot user with access to your project(s) in Harbor registry - name: Log in to Harbor Registry uses: docker/login-action@v3 with: @@ -174,21 +172,21 @@ jobs: password: ${{ env.HARBOR_PASSWORD }} - name: Extract Metadata for Docker - # Give this step an unique ID that can be used for referencing it later, here it's used to get the value of tags + # Give this step a unique ID for referencing it elsewhere, here it is used to get the value of tags #[[meta-step-id]] id: meta uses: docker/metadata-action@v5 with: - # Define the base that should be used for tags, can be multiple values if needed + # Define to use for tags, can be multiple values if needed images: harbor.nb.no/mlt/wls # Defines list of tag types to use for generating the metadata - # the semver type will use GitHub tag in semver form and turn it into a proper tag for the image - # the ref type wil base its version either on branch or PR event and generate the tag for the image + # the semver type uses GitHub tag in semver form and turns it into a proper tag for the image (semantic versioning: v4.2.0 -> 4.2.0) + # the ref type bases its version either on branch or PR event and generates the tag for the image based on that (branch -> branch-name, pr -> pr-number) tags: | type=semver,pattern={{version}} type=ref,event=branch type=ref,event=pr - # Downloads previously uploaded artifact with provided name + # Downloads previously uploaded artifact with provided id/name, see [[packaged-project-id]] to see how it was defined - name: Download the JAR File uses: actions/download-artifact@v4 with: @@ -198,16 +196,11 @@ jobs: - name: Build the Docker Image uses: docker/build-push-action@v5 with: - # Makes the action push image to Harbor, equivalent to "--output-type=registry" - push: true - # Build context, makes the build process use actual files in the runner instead of using files from GitHub - context: . - # Override the path to the Dockerfile, since this project has it in the docker folder - file: ./docker/Dockerfile - # Set tags for the image using output from the meta step - tags: ${{ steps.meta.outputs.tags }} - # Set labels for the image using output from the meta step - labels: ${{ steps.meta.outputs.labels }} + push: true # Makes the action push image to Harbor, equivalent to "--output-type=registry" + context: . # Build context, makes the build process use actual files in the runner instead of using files from GitHub + file: ./docker/Dockerfile # Override the path to the Dockerfile, since this project has it in the docker folder + tags: ${{ steps.meta.outputs.tags }} # Set tags for the image using output from the meta step + labels: ${{ steps.meta.outputs.labels }} # Set labels for the image using output from the meta step # Deploys the image to kubernetes stage environment deploy-to-stage: @@ -217,8 +210,8 @@ jobs: # Can be changed to also run on tags, to ensure that stage & prod use same image after a new version release if: github.ref == 'refs/heads/main' runs-on: self-hosted-linux - # Defines what environment this job references, this allows you to set deployment protections in the project - # Allows requiring a number of reviewers or specific reviewers, allow only specific branches or tags + # Defines what environment this job references, this allows for setting deployment protections in the project + # Allows requiring a number of reviewers or specific reviewers, or allow only specific branches or tags # It also permits setting specific secrets and variables environment: stage steps: @@ -239,13 +232,13 @@ jobs: uses: azure/setup-kubectl@v4 # Script that sets some needed variables in deployment file, and then configures kubectl for deploying to stage - # Script itself is rather generic, especially for simple deployments so you can easily re-use it - # Just make sure to set correct context and namespace for your own needs + # Script itself is rather generic, especially for simple deployments, so it can be easily re-used + # Just make sure to set correct context and namespace when used in other projects # - # K8S_HOST_URL - URL pointing to where you want the app hosted, we have two options for both stage and prod + # K8S_HOST_URL - URL pointing where to host the app, there are two default options for both stage and prod # K8S_STAGE_SERVER - URL to the stage server in form https://: # K8S_STAGE_NB_NO_CA - certificate auth data, get it from the Platform team - # K8S_STAGE_USER - name of the robot user that can deploy to your namespace, get it from the Platform team + # K8S_STAGE_USER - name of the robot user that can deploy to the given namespace, get it from the Platform team # K8S_STAGE_NB_NO_TOKEN - credentials token for the robot user, get it from the Platform team - name: Deploy to Stage run: | @@ -266,7 +259,7 @@ jobs: name: Deploy to Kubernetes Prod needs: publish-image # Runs only on tags - if: startsWith(github.event.ref, 'refs/tags/v') + if: startsWith(github.event.ref,'refs/tags/v') runs-on: self-hosted-linux environment: prod steps: diff --git a/.gitignore b/.gitignore index 549e00a2..a23e9325 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -HELP.md +### Compile Output ### target/ !.mvn/wrapper/maven-wrapper.jar !**/src/main/**/target/ diff --git a/README.md b/README.md index a1dd7822..35c7ca4c 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ After building the JAR file, it can be used to build a Docker image using the fo ```shell # Move the jar to the Docker directory -cp target/wls.jar docker/ +cp target/wls.jar docker/wls.jar # Use Docker Buildx to build the Docker Image docker buildx build --platform linux/amd64 -t wls:latest docker/ @@ -107,14 +107,14 @@ The images are built based on the `main` branch as well as project `tags`, and c # Pull the latest image docker pull harbor.nb.no/mlt/wls:latest -# Or pull a specific tag (either a GitHub tag or 'main' for the latest main branch image) +# Or pull a specific tag (either a GitHub tag or "main" for the latest main branch image) docker pull harbor.nb.no/mlt/wls: ``` With the image either built or pulled, it can be run using the following command: ```shell -docker run -p 8080:8080 -e SPRING_PROFILES_ACTIVE='local-dev' harbor.nb.no/mlt/wls: +docker run -p 8080:8080 -e SPRING_PROFILES_ACTIVE="local-dev" harbor.nb.no/mlt/wls: ``` ### Using an IDE diff --git a/docker/.dockerignore b/docker/.dockerignore new file mode 100644 index 00000000..ec5244b8 --- /dev/null +++ b/docker/.dockerignore @@ -0,0 +1,60 @@ +# Ignore Maven build files +target/ +build/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties + +# Ignore IntelliJ IDEA project files +.idea/ +*.iml +*.ipr +*.iws + +# Ignore VS Code project files +.vscode/ + +# Ignore Maven files +.mvn/ +.m2/ +mvnw +mvnw.cmd + +# Ignore OS-specific files +.DS_Store +Thumbs.db + +# Ignore logs +*.log + +# Ignore temporary files +*.tmp +*.swp +*.swo +*~ + +# Ignore backup files +*.bak + +# Ignore Git configuration files +.github/ +.git/ +.gitignore +.gitattributes +.gitmodules + +# Ignore Kubernetes configuration files +k8s/ + +# Exclude test-related files and coverage reports +test-results/ +surefire-reports/ + + +# Ignore project specific files +docker/keycloak/ +docker/mongo/ +docker/keycloak-realm-export.md +.editorconfig diff --git a/docker/Dockerfile b/docker/Dockerfile index fc77f18f..15411dd5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,14 +1,28 @@ -# What base image to use, since we are using Java let's use the temurin image from our harbor registry +# Use temurin as base image, getting it from local harbor registry, since this is a Kotlin application +# To keep the bloat to a minumum, it uses the alpine version of the image +# Should consider removing the french language pack as well: "sudo rm -fr /*" FROM harbor.nb.no/library/eclipse-temurin:21-jdk-alpine -# Set the maintainer of the image, makes it easier to know who to contact if there are any issues -LABEL maintainer="Magasin og Logistikk" +# Set metadata for the image, this is used to more easily identify the image and who created it +LABEL maintainer="\"Magasin og Logistikk\" team at NLN" +LABEL description="Hermes WLS (Warehouse and Logistics Service) functions as a \ +middleware between NLNs (National Library of Norway) catalogues and storage systems" -# Copy the jar file from the target folder to the root of the container, the wls.jar comes from GitHub workflow +# Copy the jar file from the target folder to the root of the container +# GitHub workflow generates the wls.jar file +# When building manually generate the jar file with "mvn clean package" +# and copy it to the docker folder with "cp target/wls.jar docker/wls.jar" COPY wls.jar app.jar -# Expose the port that the application is running on, this is defined in the src/main/resources/application.yml file +# Mark the port that the application is listenting on +# This port is defined in the src/main/resources/application.yml file EXPOSE 8080 +# Add a healthcheck to the container, this is used to check if the container is running as expected +# This is not a replacement for health check in kubernetes +# However it is convenient when running locally, or in a purely Docker based environment +HEALTHCHECK --start-period=40s --retries=5 \ + CMD curl --fail --silent localhost:8081/actuator/health | grep UP || exit 1 + # Set the entrypoint for the container, this is the command that will be run when the container starts up -ENTRYPOINT ["java", "-Duser.timezone=Europe/Oslo", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app.jar"] \ No newline at end of file +ENTRYPOINT ["java", "-Duser.timezone=Europe/Oslo", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app.jar"] diff --git a/docker/compose.yaml b/docker/compose.yaml index e6597b45..f8e32556 100644 --- a/docker/compose.yaml +++ b/docker/compose.yaml @@ -1,7 +1,8 @@ +# Define what services to set up for local development services: - # Sets up a simple mongo database for local development + # Set up a simple mongo database for local development mongo-db: - image: mongo:4.4.22 # Same as the version in prod env + image: mongo:4.4.22 # Same as the version in the prod env restart: always environment: MONGO_INITDB_ROOT_USERNAME: root @@ -10,17 +11,18 @@ services: ports: - "27017:27017" volumes: + # Copy script to populate the database with test data - ./mongo/mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro # Set up a mongo-express instance to view the mongo database - # Will be available at: http://localhost:8081 + # Makes GUI available at: http://localhost:8081 mongo-express: image: mongo-express:1.0.2 restart: always ports: - "8081:8081" depends_on: - - mongo-db + - mongo-db # Needs to have mongo-db container running environment: ME_CONFIG_MONGODB_SERVER: mongo-db ME_CONFIG_MONGODB_ADMINUSERNAME: root @@ -28,11 +30,17 @@ services: ME_CONFIG_OPTIONS_EDITORTHEME: "gruvbox-dark" ME_CONFIG_BASICAUTH: false + # Set up a local keycloak instance for local development # Keycloak is configured to automatically import a test realm with service accounts - # GUI will be available at: http://localhost:8082 + # Makes GUI available at: http://localhost:8082 + # # When making changes to the realm, export the realm and save it in the keycloak/import folder # docker exec -it wls-local-keycloak-1 /opt/keycloak/bin/kc.sh export --file "/tmp/mlt-local-realm-export.json" --users "same_file" --realm "mlt-local" # docker cp wls-local-keycloak-1:/tmp/mlt-local-realm-export.json ./docker/keycloak/import/mlt-local-realm-export.json + # git add docker/keycloak/import/mlt-local-realm-export.json + # This connects interactively to the container, uses keycloak export command to export the realm, copies the file to the local machine, and adds it to git + # + # Also, since I am a lazy boi: axiell -> E93MF2F8UfpRrCowAbVMStvsTzy0gmgr keycloak: image: quay.io/keycloak/keycloak:24.0.1 # Same as the version in prod env restart: always @@ -42,12 +50,15 @@ services: KEYCLOAK_ADMIN_PASSWORD: toor ports: - "8082:8080" - command: start-dev --import-realm + command: start-dev --import-realm # Automatically import realm on startup with needed clients volumes: - ./keycloak/import:/opt/keycloak/data/import + # Set up a local SynQ instance for local development + # Provides an option to test out communication with SynQ API locally, without the need to use SynQ in dev/prod environments + # Makes Swagger GUI available at: http://localhost:8181/synq/swagger dummy-synq: - image: harbor.nb.no/mlt/dummy-synq:main + image: harbor.nb.no/mlt/dummy-synq:main # Get the "latest" version of the dummy-synq image restart: always ports: - "8181:8181" diff --git a/docker/keycloak-realm-export.md b/docker/keycloak-realm-export.md index 52b5220f..5442ea85 100644 --- a/docker/keycloak-realm-export.md +++ b/docker/keycloak-realm-export.md @@ -2,11 +2,13 @@ Keycloak is configured to automatically import a test realm with service accounts. GUI with imported realm is then available [here](http://localhost:8082/admin/master/console/#/mlt-local "Keycloak Admin Console for MLT-Local Realm"). When making changes to the realm, export the realm and save it in the keycloak/import folder. -That way changes will be persisted after the container is restarted, and available to others. +That way changes will persist even if the container is restarted, and be available to others. + ```bash docker exec -it wls-local-keycloak-1 /opt/keycloak/bin/kc.sh export --file "/tmp/mlt-local-realm-export.json" --users "same_file" --realm "mlt-local" docker cp wls-local-keycloak-1:/tmp/mlt-local-realm-export.json ./docker/keycloak/import/mlt-local-realm-export.json +git add docker/keycloak/import/mlt-local-realm-export.json ``` Make sure that the container name `wls-local-keycloak-1` is correct. -You can check the container name by running `docker ps` and looking for the container running the `quay.io/keycloak/keycloak` image. +Check the container name with `docker ps` and look for the container running the `quay.io/keycloak/keycloak` image. diff --git a/docker/keycloak/import/mlt-local-realm-export.json b/docker/keycloak/import/mlt-local-realm-export.json index 11fe5873..4e433a49 100644 --- a/docker/keycloak/import/mlt-local-realm-export.json +++ b/docker/keycloak/import/mlt-local-realm-export.json @@ -553,12 +553,12 @@ "directAccessGrantsEnabled" : false, "serviceAccountsEnabled" : true, "publicClient" : false, - "frontchannelLogout" : true, + "frontchannelLogout" : false, "protocol" : "openid-connect", "attributes" : { "oidc.ciba.grant.enabled" : "false", "client.secret.creation.time" : "1720685173", - "backchannel.logout.session.required" : "true", + "backchannel.logout.session.required" : "false", "post.logout.redirect.uris" : "+", "oauth2.device.authorization.grant.enabled" : "false", "display.on.consent.screen" : "false", @@ -741,12 +741,13 @@ "directAccessGrantsEnabled" : false, "serviceAccountsEnabled" : true, "publicClient" : false, - "frontchannelLogout" : true, + "frontchannelLogout" : false, "protocol" : "openid-connect", "attributes" : { "oidc.ciba.grant.enabled" : "false", "client.secret.creation.time" : "1728308503", - "backchannel.logout.session.required" : "true", + "backchannel.logout.session.required" : "false", + "post.logout.redirect.uris" : "+", "oauth2.device.authorization.grant.enabled" : "false", "display.on.consent.screen" : "false", "backchannel.logout.revoke.offline.tokens" : "false" @@ -763,6 +764,7 @@ "config" : { "user.session.note" : "clientAddress", "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "clientAddress", @@ -777,6 +779,7 @@ "config" : { "user.session.note" : "client_id", "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "client_id", @@ -791,13 +794,14 @@ "config" : { "user.session.note" : "clientHost", "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "clientHost", "jsonType.label" : "String" } } ], - "defaultClientScopes" : [ "wls-synq", "wls-item", "wls-audience", "wls-order", "wls-subject" ], + "defaultClientScopes" : [ "wls-synq", "wls-audience", "wls-subject" ], "optionalClientScopes" : [ "web-origins", "acr", "address", "phone", "roles", "profile", "offline_access", "microprofile-jwt", "email" ] }, { "id" : "e84865db-9f5c-4213-b9c9-23c911520899", @@ -822,7 +826,7 @@ "directAccessGrantsEnabled" : false, "serviceAccountsEnabled" : true, "publicClient" : false, - "frontchannelLogout" : true, + "frontchannelLogout" : false, "protocol" : "openid-connect", "attributes" : { "client.secret.creation.time" : "1719488186", @@ -832,7 +836,7 @@ "use.refresh.tokens" : "true", "oidc.ciba.grant.enabled" : "false", "client.use.lightweight.access.token.enabled" : "false", - "backchannel.logout.session.required" : "true", + "backchannel.logout.session.required" : "false", "client_credentials.use_refresh_token" : "false", "tls.client.certificate.bound.access.tokens" : "false", "require.pushed.authorization.requests" : "false", @@ -889,7 +893,7 @@ "jsonType.label" : "String" } } ], - "defaultClientScopes" : [ "wls-item", "wls-audience", "wls-order", "wls-subject" ], + "defaultClientScopes" : [ "wls-synq", "wls-item", "wls-audience", "wls-order", "wls-subject" ], "optionalClientScopes" : [ "web-origins", "acr", "address", "phone", "roles", "profile", "offline_access", "microprofile-jwt", "email" ] } ], "clientScopes" : [ { @@ -967,7 +971,7 @@ }, { "id" : "e77adead-5964-4400-9368-3b1520d57bdd", "name" : "wls-item", - "description" : "A client scope used to add a 'item' scope to the JWT scope field", + "description" : "A client scope used to add a \"item\" scope to the JWT scope field", "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "true", @@ -1284,10 +1288,10 @@ }, { "id" : "43e7474f-a3fe-4d44-a0e1-48518c8330f1", "name" : "wls-synq", - "description" : "A client scope used to add a 'synq' scope to the JWT scope field", + "description" : "A client scope used to add a \"synq\" scope to the JWT scope field", "protocol" : "openid-connect", "attributes" : { - "include.in.token.scope" : "false", + "include.in.token.scope" : "true", "display.on.consent.screen" : "false", "gui.order" : "", "consent.screen.text" : "" @@ -1427,7 +1431,7 @@ }, { "id" : "2f9f07c6-b866-402b-8f18-3f7baf021f12", "name" : "wls-order", - "description" : "A client scope used to add an 'order' scope to the JWT scope field", + "description" : "A client scope used to add an \"order\" scope to the JWT scope field", "protocol" : "openid-connect", "attributes" : { "include.in.token.scope" : "true", @@ -1553,7 +1557,7 @@ "subType" : "authenticated", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "oidc-usermodel-attribute-mapper", "saml-user-property-mapper", "oidc-full-name-mapper", "saml-user-attribute-mapper", "oidc-usermodel-property-mapper", "oidc-address-mapper", "saml-role-list-mapper", "oidc-sha256-pairwise-sub-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-full-name-mapper", "oidc-address-mapper", "saml-user-property-mapper", "oidc-usermodel-property-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-role-list-mapper", "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper" ] } }, { "id" : "85a712b7-0975-406f-ba2e-c9a99625cfad", @@ -1562,7 +1566,7 @@ "subType" : "anonymous", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "oidc-sha256-pairwise-sub-mapper", "saml-user-attribute-mapper", "oidc-address-mapper", "oidc-usermodel-property-mapper", "saml-user-property-mapper", "oidc-full-name-mapper", "saml-role-list-mapper", "oidc-usermodel-attribute-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-sha256-pairwise-sub-mapper", "oidc-full-name-mapper", "oidc-address-mapper", "saml-role-list-mapper", "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper", "saml-user-property-mapper", "saml-user-attribute-mapper" ] } } ], "org.keycloak.keys.KeyProvider" : [ { @@ -2185,4 +2189,4 @@ "clientPolicies" : { "policies" : [ ] } -} \ No newline at end of file +} diff --git a/docker/mongo/mongo-init.js b/docker/mongo/mongo-init.js index 8c0fb2a4..f96da598 100644 --- a/docker/mongo/mongo-init.js +++ b/docker/mongo/mongo-init.js @@ -1,72 +1,73 @@ -// Create a WLS database, a user, and a collection for products +// Create a WLS database, a user, and a collection for items and orders. // This file only runs once during container creation. -// If for some reason it fails to load, re-create the container -// (E.G. running "docker compose down") -print('Start #################################################################'); +// If for some reason it fails to load, re-create the container. +// (by running "docker compose down") +print("START ##################################################################"); db = db.getSiblingDB('wls'); db.createUser( { - user: 'bruker', - pwd: 'drossap', - roles: [{ role: 'readWrite', db: 'wls' }], + user: "bruker", + pwd: "drossap", + roles: [{ role: "readWrite", db: "wls" }], }, ); -db.createCollection('items'); + +db.createCollection("items"); db.items.insertOne({ + "hostId": "mlt-12345", "hostName": "AXIELL", - "hostId": "item-12345", - "itemCategory": "BOOK", - "description": "Tyv etter loven", + "description": "Tyven, tyven skal du hete", + "itemCategory": "PAPER", + "preferredEnvironment": "NONE", "packaging": "NONE", + "owner": "NB", + "callbackUrl": "https://callback-wls.no/item", "location": "SYNQ_WAREHOUSE", "quantity": 1, - "preferredEnvironment": "NONE", - "owner": "NB", "_class": "no.nb.mlt.wls.infrastructure.repositories.item.MongoItem" }) - db.items.insertOne({ + "hostId": "mlt-54321", "hostName": "AXIELL", - "hostId": "item-54321", - "itemCategory": "BOOK", "description": "Tyv etter loven", + "itemCategory": "PAPER", + "preferredEnvironment": "NONE", "packaging": "NONE", + "owner": "NB", + "callbackUrl": "https://callback-wls.no/item", "location": "SYNQ_WAREHOUSE", "quantity": 1, - "preferredEnvironment": "NONE", - "owner": "NB", "_class": "no.nb.mlt.wls.infrastructure.repositories.item.MongoItem" }) -db.createCollection('orders') - +db.createCollection("orders") db.orders.insertOne({ - "hostName": "AXIELL", - "hostOrderId": "order-12345", - "status": "NOT_STARTED", - "orderLine": [ + "hostName": "AXIELL", + "hostOrderId": "mlt-12345-order", + "status": "NOT_STARTED", + "orderLine": [ { - "hostId": "item-12345", - "status": "NOT_STARTED" + "hostId": "mlt-12345", + "status": "NOT_STARTED" } - ], - "orderType": "LOAN", - "owner": "NB", - "contactPerson": "MLT Team", - "address": { - "recipient": "Doug Doug", - "addressLine1": "Somewhere", - "addressLine2": "Behind a cardboard box", - "city": "Las Vegas", + ], + "orderType": "LOAN", + "owner": "NB", + "contactPerson": "Dr. Heinz Doofenshmirtz", + "address": { + "recipient": "Doug Dimmadome", + "addressLine1": "Dimmsdale Dimmadome", + "addressLine2": "21st Texan Ave.", + "city": "Dimmsdale", "country": "United States", - "region": "Texas", - "postcode": "TX-55415" - }, - "callbackUrl": "https://example.com/send/callback/here", + "region": "California", + "postcode": "CA-55415" + }, + "callbackUrl": "https://callback-wls.no/order", "_class": "no.nb.mlt.wls.infrastructure.repositories.order.MongoOrder" }) -print('END #################################################################'); +print("END ####################################################################"); diff --git a/k8s/prod/wls.yml b/k8s/prod/wls.yml index d197ab4f..c7db3f07 100644 --- a/k8s/prod/wls.yml +++ b/k8s/prod/wls.yml @@ -1,3 +1,4 @@ +# Define the API version of the Kubernetes object apiVersion: apps/v1 # Kind is a string value representing type of object this manifest represents @@ -16,7 +17,7 @@ metadata: spec: # The .spec.selector field defines how the Deployment finds which Pods to manage selector: - # In this case, you simply select a label that is defined in the Pod template (app: wls) + # In this case, simply select a label that is defined in the Pod template (app: wls) matchLabels: app: wls @@ -104,9 +105,11 @@ spec: requests: cpu: 500m memory: 512Mi + ephemeral-storage: 42Gi limits: cpu: 1000m memory: 1024Mi + ephemeral-storage: 69Gi # Define settings for running the container's liveness probe livenessProbe: diff --git a/k8s/stage/wls.yml b/k8s/stage/wls.yml index d993b14b..b50cb4ca 100644 --- a/k8s/stage/wls.yml +++ b/k8s/stage/wls.yml @@ -1,3 +1,4 @@ +# Define the API version of the Kubernetes object apiVersion: apps/v1 # Kind is a string value representing type of object this manifest represents @@ -104,9 +105,11 @@ spec: requests: cpu: 200m memory: 256Mi + ephemeral-storage: 4Gi limits: cpu: 500m memory: 512Mi + ephemeral-storage: 6Gi # Define settings for running the container's liveness probe livenessProbe: diff --git a/pom.xml b/pom.xml index 3765eaf9..1ed75c87 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.2.6 + 3.3.5 @@ -14,8 +14,8 @@ wls 0.0.1-SNAPSHOT Hermes - Hermes WLS (Warehouse and Logistics Service) functions as a middleware between NLNs (National Library of Norway) catalogues and - storage systems + + Hermes WLS (Warehouse and Logistics Service) functions as a middleware between NLNs (National Library of Norway) catalogues and storage systems https://wls-api.nb.no/ @@ -55,6 +55,18 @@ https://www.nb.no/ansatte/noah-bjerkli-aanonli/ National Library of Norway https://nb.no/ + + Application Developer + + Europe/Oslo + + + tomo + Tom Kristian Olsen + tom.olsen@nb.no + https://www.nb.no/ansatte/tom-kristian-olsen/ + National Library of Norway + https://nb.no/ Advisor @@ -70,7 +82,9 @@ 2.6.0 2.43.0 2023.0.1 - 1.19.8 + 1.20.3 + 1.3.0 + 1.9.0 official 21 @@ -143,6 +157,12 @@ spring-cloud-starter-gateway + + commons-validator + commons-validator + ${commons.validator.version} + + org.springframework.boot spring-boot-devtools @@ -195,7 +215,7 @@ com.tngtech.archunit archunit-junit5 - 1.3.0 + ${archunit.junit5.version} test diff --git a/src/main/kotlin/no/nb/mlt/wls/application/hostapi/config/SecurityConfig.kt b/src/main/kotlin/no/nb/mlt/wls/application/hostapi/config/SecurityConfig.kt index fa764f93..ea83f8e4 100644 --- a/src/main/kotlin/no/nb/mlt/wls/application/hostapi/config/SecurityConfig.kt +++ b/src/main/kotlin/no/nb/mlt/wls/application/hostapi/config/SecurityConfig.kt @@ -55,7 +55,7 @@ class SecurityConfig { } fun JwtAuthenticationToken.checkIfAuthorized(host: HostName) { - if (name.uppercase() == host.name || name == "wls") return + if (name.uppercase() == host.name) return throw ResponseStatusException( HttpStatus.FORBIDDEN, diff --git a/src/main/kotlin/no/nb/mlt/wls/application/hostapi/config/SwaggerConfig.kt b/src/main/kotlin/no/nb/mlt/wls/application/hostapi/config/SwaggerConfig.kt index 485bbc83..52775743 100644 --- a/src/main/kotlin/no/nb/mlt/wls/application/hostapi/config/SwaggerConfig.kt +++ b/src/main/kotlin/no/nb/mlt/wls/application/hostapi/config/SwaggerConfig.kt @@ -36,20 +36,23 @@ class SwaggerConfig { .title("Hermes WLS (Warehouse and Logistics Service) middleware") .description( """ - Hermes is developed by the MLT (Warehouse and Logistics team) at the National Library of Norway (NLN). - Hermes facilitates communication between the NLN's storage systems and the cataloging systems. - Hermes' name is inspired by the Greek deity Hermes, who was the herald of the gods. + Hermes, developed and maintained by the Warehouse and Logistics team (MLT) at the National Library of Norway (NLN). + Hermes facilitates communication between the NLN's storage systems and catalogues, serving as a master system. + Hermes' name is inspired by the Greek deity Hermes, who was known as the herald of the gods. - Applications that need to use Hermes must authenticate with a JWT token. - They can get it from the NLN's instance of Keycloak with help of a Service Account client. - If you are unsure how to do this, please contact the MLT team for help. + This submodule of Hermes is responsible for receiving item masters and order requests from catalogues. + These are then sent along to storage systems, so that items can be stored or retrieved, and orders can be processed. + + Applications that want to use Hermes must authenticate with a JWT token. + It is provided by the NLN's instance of Keycloak with help of a Service Account client. + Please contact the MLT team for help getting set up with one or to reset credentials. """.trimIndent() ).contact( Contact() .name("MLT team at the National Library of Norway") .email("mlt@nb.no") .url("https://www.nb.no") - ) + ).version("1.0.0") ) .addSecurityItem( SecurityRequirement().addList("clientCredentials") @@ -60,7 +63,7 @@ class SwaggerConfig { fun hostApi(): GroupedOpenApi { return GroupedOpenApi.builder() .group("Host API") - .displayName("Host API for catalogues") + .displayName("API for catalogues (hosts) to interact with Hermes WLS") .pathsToMatch("/v1/**") .build() } diff --git a/src/main/kotlin/no/nb/mlt/wls/application/hostapi/item/ApiItemPayload.kt b/src/main/kotlin/no/nb/mlt/wls/application/hostapi/item/ApiItemPayload.kt index ce81d5c7..b4643305 100644 --- a/src/main/kotlin/no/nb/mlt/wls/application/hostapi/item/ApiItemPayload.kt +++ b/src/main/kotlin/no/nb/mlt/wls/application/hostapi/item/ApiItemPayload.kt @@ -10,84 +10,83 @@ import no.nb.mlt.wls.domain.model.Owner import no.nb.mlt.wls.domain.model.Packaging import no.nb.mlt.wls.domain.ports.inbound.ItemMetadata import no.nb.mlt.wls.domain.ports.inbound.ValidationException -import java.net.URI +import org.apache.commons.validator.routines.UrlValidator @Schema( - description = "Payload for registering an item in Hermes WLS, and appropriate storage system for the item.", + description = """Payload for registering an item in Hermes WLS, and appropriate storage systems.""", example = """ { "hostId": "mlt-12345", "hostName": "AXIELL", "description": "Tyven, tyven skal du hete", - "itemCategory": "BOOK", + "itemCategory": "PAPER", "preferredEnvironment": "NONE", "packaging": "NONE", - "owner": "NB" + "owner": "NB", + "callbackUrl": "https://callback-wls.no/item" } """ ) data class ApiItemPayload( @Schema( - description = "The item ID from the host system, usually a barcode or an equivalent ID.", + description = """The item ID from the host system, usually a barcode or an equivalent ID.""", example = "mlt-12345" ) val hostId: String, @Schema( - description = - "Name of the host system which the item originates from. " + - "Host system is usually the catalogue that the item is registered in.", + description = """Name of the host system which the item originates from. + Host system is usually the catalogue that the item is registered in.""", examples = [ "AXIELL", "ALMA", "ASTA", "BIBLIOFIL" ] ) val hostName: HostName, @Schema( - description = - "Description of the item for easy identification in the warehouse system. " + - "Usually an item title/name, e.g. book title, film name, etc. or contents description.", + description = """Description of the item for easy identification in the warehouse system. + Usually an item title/name, e.g. book title, film name, etc. or contents description.""", examples = ["Tyven, tyven skal du hete", "Avisa Hemnes", "Kill Buljo"] ) val description: String, - // TODO - Update this schema to reflect new categories @Schema( - description = "What kind of item category the item belongs to, e.g. Books, Issues, Films, etc.", - examples = [ - "safetyfilm", "nitratfilm", "film", "plater", "fotografier", "papir", - "gjenstand", "lydbånd", "videobånd", "sekkepost", "magnetbånd" - ] + description = """Item's category, same category indicates that the items can be stored together without any preservation issues. + For example: books, magazines, newspapers, etc. are of type PAPER, and can be stored together without damaging each other.""", + examples = ["PAPER", "DISC", "FILM", "PHOTO", "EQUIPMENT", "BULK_ITEMS", "MAGNETIC_TAPE"] ) val itemCategory: ItemCategory, @Schema( - description = "What kind of environment the item should be stored in, e.g. NONE, FRYS, MUGG_CELLE, etc.", + description = """What kind of environment the item should be stored in. + "NONE" is for normal storage for the item category, "FRYS" is for frozen storage, etc. + NOTE: This is not a guarantee that the item will be stored in the preferred environment. + In cases where storage space is limited, the item may be stored in a different environment.""", examples = ["NONE", "FRYS"] ) val preferredEnvironment: Environment, @Schema( - description = - "Whether the item is a single object or a box/abox/crate of other items. " + - "NONE is for single objects, BOX is for boxes, ABOX is for archival boxes, and CRATE is for crates.", + description = """Whether the item is a single object or a container with other items inside. + "NONE" is for single objects, "ABOX" is for archival boxes, etc. + NOTE: It is up to the catalogue to keep track of the items inside a container.""", examples = ["NONE", "BOX", "ABOX", "CRATE"] ) val packaging: Packaging, @Schema( - description = "Who owns the item. Usually the National Library of Norway (NB) or the National Archives of Norway (ARKIVVERKET).", + description = """Who owns the item. Usually the National Library of Norway ("NB") or the National Archives of Norway ("ARKIVVERKET").""", examples = ["NB", "ARKIVVERKET"] ) val owner: Owner, @Schema( - description = "Callback URL for the item used to update the item information in the host system.", - example = "https://example.com/send/callback/here" + description = """Callback URL to use for sending item updates to the host system. + For example when item moves or changes quantity in storage.""", + example = "https://callback-wls.no/item" ) val callbackUrl: String?, @Schema( - description = "Where the item is located, e.g. SYNQ_WAREHOUSE, AUTOSTORE, KARDEX, etc.", + description = """Where the item is located, can be used for tracking item movement through storage systems.""", examples = ["SYNQ_WAREHOUSE", "AUTOSTORE", "KARDEX"], accessMode = READ_ONLY, required = false ) val location: String?, @Schema( - description = - "Quantity on hand of the item, this denotes if the item is in storage or not. " + - "If the item is in storage then quantity is at least 1, if it's not in storage then quantity is 0.", + description = """Quantity on hand of the item, this easily denotes if the item is in the storage or not. + If the item is in storage then quantity is 1, if it's not in storage then quantity is 0.""", examples = [ "0", "1"], accessMode = READ_ONLY, required = false @@ -123,36 +122,32 @@ data class ApiItemPayload( @Throws(ValidationException::class) fun validate() { if (hostId.isBlank()) { - throw ValidationException("The item's hostId is required, and it cannot be blank") + throw ValidationException("The item's 'hostId' is required, and it cannot be blank") } if (description.isBlank()) { - throw ValidationException("The item's description is required, and it cannot be blank") + throw ValidationException("The item's 'description' is required, and it cannot be blank") } if (location != null && location.isBlank()) { - throw ValidationException("The item's location cannot be blank if set") + throw ValidationException("The item's 'location' cannot be blank if set") } - if (quantity != null && quantity < 0.0) { - throw ValidationException("The item's quantity must be positive or zero if set") + if (quantity != null && quantity != 0 && quantity != 1) { + throw ValidationException("The item's 'quantity' must be one or zero if set") } if (callbackUrl != null && !isValidUrl(callbackUrl)) { - throw ValidationException("The item's callback URL must be valid if set") + throw ValidationException("The item's 'callback URL' must be valid if set") } } private fun isValidUrl(url: String): Boolean { // Yes I am aware that this function is duplicated in three places - // But I prefer readability over DRY in cases like this + // But I prefer readability to DRY in cases like this - return try { - URI(url).toURL() // Try to create a URL object - true - } catch (_: Exception) { - false // If exception is thrown, it's not a valid URL - } + val validator = UrlValidator(arrayOf("http", "https")) // Allow only HTTP/HTTPS + return validator.isValid(url) } } diff --git a/src/main/kotlin/no/nb/mlt/wls/application/hostapi/item/ItemController.kt b/src/main/kotlin/no/nb/mlt/wls/application/hostapi/item/ItemController.kt index df711f65..02596be5 100644 --- a/src/main/kotlin/no/nb/mlt/wls/application/hostapi/item/ItemController.kt +++ b/src/main/kotlin/no/nb/mlt/wls/application/hostapi/item/ItemController.kt @@ -24,29 +24,31 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody as SwaggerRequestBod @RestController @RequestMapping(path = [ "/v1"]) -@Tag(name = "Item Controller", description = "API for managing items in Hermes WLS") +@Tag(name = "Item Controller", description = """API endpoints used by catalogs for managing items in Hermes WLS""") class ItemController( private val addNewItem: AddNewItem, private val getItem: GetItem ) { @Operation( - summary = "Register an items in the storage system", - description = """Register data about the item in Hermes WLS and appropriate storage system, - so that the physical item can be placed in the physical storage. - An item is also called item by some storage systems and users, those mean the same thing in Hermes. - NOTE: When registering new item quantity and location are set to default values (0 and null). - Hence you should not provide these values in the payload, or at least know they will be overwritten.""" + summary = "Register an item in Hermes", + description = """Register data about the item in Hermes WLS and appropriate storage systems. + This step is required to store the item in the physical storage system, as it needs to have metadata about the object. + An item is also called product by some storage systems and users, those mean the same thing to Hermes. + Do not provide quantity or location information in this step, as it is overridden with default values.""" ) + + // ? TODO: Replace the schema with a model class without stuff like location, quantity, etc. + @ApiResponses( value = [ ApiResponse( responseCode = "200", - description = """Item with given 'hostName' and 'hostId' already exists in the system. + description = """Item with given "hostName" and "hostId" already exists in the system. No new item was created, neither was the old item updated. Existing item information is returned for inspection. - In rare cases the response body may be empty, that can happen if Hermes WLS - does not have the information about the item stored in its database and - is unable to retrieve the existing item information from the storage system.""", + In some rare cases the response body may be empty. + That can happen if Hermes WLS does not have the information about the item stored in its database, + and is unable to retrieve the existing item information from the storage system(s).""", content = [ Content( mediaType = "application/json", @@ -57,7 +59,7 @@ class ItemController( ApiResponse( responseCode = "201", description = """Item payload is valid and the item information was registered successfully. - Item was created in the appropriate storage system. + Item was created in the appropriate storage system(s). New item information is returned for inspection.""", content = [ Content( @@ -68,18 +70,18 @@ class ItemController( ), ApiResponse( responseCode = "400", - description = """Item payload is invalid and no new item was created. - Error message contains information about the invalid fields.""", + description = """Item payload is invalid, no new item was created. + Error message contains information about the invalid field(s).""", content = [Content(schema = Schema())] ), ApiResponse( responseCode = "401", - description = "Client sending the request is not authorized to create a new item.", + description = """Client sending the request is not authorized to operate on items.""", content = [Content(schema = Schema())] ), ApiResponse( responseCode = "403", - description = "A valid 'Authorization' header is missing from the request.", + description = """A valid "Authorization" header is missing from the request.""", content = [Content(schema = Schema())] ) ] @@ -87,21 +89,19 @@ class ItemController( @Callbacks( Callback( name = "Item Callback", - callbackUrlExpression = "\$request.body#/callbackUrl", + callbackUrlExpression = "{\$request.body#/callbackUrl}", operation = arrayOf( Operation( summary = "Notification of updated item", description = """This callback triggers when an item is updated inside the storage systems. It contains the full data of the item, including the current quantity and location of it. - """, + Situations where this callback is triggered may include: item moves in storage, + item is picked for order, item is returned to storage, etc.""", method = "post", requestBody = SwaggerRequestBody( - content = - arrayOf( - Content(schema = Schema(implementation = NotificationItemPayload::class)) - ), + content = [Content(schema = Schema(implementation = NotificationItemPayload::class))], required = true ) ) diff --git a/src/main/kotlin/no/nb/mlt/wls/application/hostapi/order/ApiOrderPayload.kt b/src/main/kotlin/no/nb/mlt/wls/application/hostapi/order/ApiOrderPayload.kt index 1b69640d..e2386d24 100644 --- a/src/main/kotlin/no/nb/mlt/wls/application/hostapi/order/ApiOrderPayload.kt +++ b/src/main/kotlin/no/nb/mlt/wls/application/hostapi/order/ApiOrderPayload.kt @@ -7,10 +7,10 @@ import no.nb.mlt.wls.domain.model.Order import no.nb.mlt.wls.domain.model.Owner import no.nb.mlt.wls.domain.ports.inbound.CreateOrderDTO import no.nb.mlt.wls.domain.ports.inbound.ValidationException -import java.net.URI +import org.apache.commons.validator.routines.UrlValidator @Schema( - description = "Payload for creating and editing an order in Hermes WLS, and appropriate storage system(s).", + description = """Payload for creating orders in Hermes WLS, and appropriate storage system(s).""", example = """ { "hostName": "AXIELL", @@ -24,72 +24,72 @@ import java.net.URI ], "orderType": "LOAN", "owner": "NB", - "contactPerson": "Hermes the Great", + "contactPerson": "Dr. Heinz Doofenshmirtz", "address": { - "recipient": "Nasjonalbibliotekaren", - "addressLine1": "Henrik Ibsens gate 110", - "city": "Oslo", - "country": "Norway", - "postcode": "0255" + "recipient": "Doug Dimmadome", + "addressLine1": "Dimmsdale Dimmadome", + "addressLine2": "21st Texan Ave.", + "city": "Dimmsdale", + "country": "United States", + "region": "California", + "postcode": "CA-55415" }, "note": "Handle with care", - "callbackUrl": "https://example.com/send/callback/here" + "callbackUrl": "https://callback-wls.no/order" } """ ) data class ApiOrderPayload( @Schema( - description = "Name of the host system which made the order.", + description = """Name of the host system which made the order.""", examples = ["AXIELL", "ALMA", "ASTA", "BIBLIOFIL"] ) val hostName: HostName, @Schema( - description = "Order ID from the host system which made the order.", + description = """ID for the order, preferably the same ID as the one in the host system.""", example = "mlt-12345-order" ) val hostOrderId: String, @Schema( - description = "Current status of the order as a whole.", - examples = ["NOT_STARTED", "IN_PROGRESS", "COMPLETED", "DELETED"] + description = """Current status for the whole order. + "COMPLETED" means that the order is finished and items are ready for pickup / sent to receiver. + "RETURNED" means that the order items have been returned to the storage.""", + examples = ["NOT_STARTED", "IN_PROGRESS", "COMPLETED", "RETURNED", "DELETED"] ) val status: Order.Status?, @Schema( - description = "List of items in the order, also called order lines.", + description = """List of items in the order, also called order lines.""", accessMode = READ_ONLY ) val orderLine: List, @Schema( - description = "Describes what type of order this is", + description = """Describes what type of order this is. + "LOAN" means that the order is for borrowing items to external or internal users, + usually meaning the items will be viewed, inspected, etc. + "DIGITIZATION" means that the order is specifically for digitizing items, + usually meaning that the order will be delivered to digitization workstation.""", examples = ["LOAN", "DIGITIZATION"] ) val orderType: Order.Type, @Schema( - description = "The name of the person to contact with manners related to this order", - example = "Hermes" + description = """Who to contact in relation to the order if case of any problems/issues/questions.""", + example = "Dr. Heinz Doofenshmirtz" ) val contactPerson: String, @Schema( - description = "Any notes about the order", - example = "This is required in four weeks time" + description = """Address for the order, used in cases where storage operator sends out the order directly.""", + example = "{...}" ) - val note: String?, - // TODO - Should this use custom DTO? + val address: Order.Address?, @Schema( - description = "The delivery address of this order", - example = """ - "address": { - "recipient": "Nasjonalbibliotekaren", - "addressLine1": "Henrik Ibsens gate 110", - "city": "Oslo", - "country": "Norway", - "postcode": "0255" - } - """ + description = """Notes regarding the order, such as delivery instructions, special requests, etc.""", + example = "I need this order in four weeks, not right now." ) - val address: Order.Address?, + val note: String?, @Schema( - description = "Callback URL for the order used to update the order information in the host system.", - example = "https://example.com/send/callback/here" + description = """Callback URL to use for sending order updates to the host system. + For example when order items get picked or the order is cancelled.""", + example = "https://callback-wls.no/order" ) val callbackUrl: String ) { @@ -106,6 +106,7 @@ data class ApiOrderPayload( callbackUrl = callbackUrl ) + @Throws(ValidationException::class) fun validate() { if (hostOrderId.isBlank()) { throw ValidationException("The order's hostOrderId is required, and can not be blank") @@ -127,12 +128,8 @@ data class ApiOrderPayload( // Yes I am aware that this function is duplicated in three places // But I prefer readability over DRY in cases like this - return try { - URI(url).toURL() // Try to create a URL object - true - } catch (_: Exception) { - false // If exception is thrown, it's not a valid URL - } + val validator = UrlValidator(arrayOf("http", "https")) // Allow only HTTP/HTTPS + return validator.isValid(url) } } @@ -150,7 +147,7 @@ fun Order.toApiOrderPayload() = ) @Schema( - description = "Represents an order line in an order, containing information about ordered item.", + description = """Represents an order line in an order, containing information about the ordered item.""", example = """ { "hostId": "mlt-12345", @@ -160,12 +157,12 @@ fun Order.toApiOrderPayload() = ) data class OrderLine( @Schema( - description = "Item ID from the host system.", + description = """Item ID from the host system.""", example = "mlt-12345" ) val hostId: String, @Schema( - description = "Current status of the item in the order.", + description = """Current status for the ordered item.""", examples = ["NOT_STARTED", "PICKED", "FAILED"] ) val status: Order.OrderItem.Status? diff --git a/src/main/kotlin/no/nb/mlt/wls/application/hostapi/order/ApiUpdateOrderPayload.kt b/src/main/kotlin/no/nb/mlt/wls/application/hostapi/order/ApiUpdateOrderPayload.kt index da57f081..2c4397ae 100644 --- a/src/main/kotlin/no/nb/mlt/wls/application/hostapi/order/ApiUpdateOrderPayload.kt +++ b/src/main/kotlin/no/nb/mlt/wls/application/hostapi/order/ApiUpdateOrderPayload.kt @@ -5,11 +5,11 @@ import io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY import no.nb.mlt.wls.domain.model.HostName import no.nb.mlt.wls.domain.model.Order import no.nb.mlt.wls.domain.ports.inbound.ValidationException -import java.net.URI +import org.apache.commons.validator.routines.UrlValidator import kotlin.jvm.Throws @Schema( - description = "Payload for creating and editing an order in Hermes WLS, and appropriate storage system(s).", + description = """Payload for editing an order in Hermes WLS, and appropriate storage system(s).""", example = """ { "hostName": "AXIELL", @@ -21,7 +21,7 @@ import kotlin.jvm.Throws } ], "orderType": "LOAN", - "contactPerson": "MLT Team", + "contactPerson": "Dr. Heinz Doofenshmirtz", "address": { "recipient": "Doug Dimmadome", "addressLine1": "Dimmsdale Dimmadome", @@ -32,59 +32,49 @@ import kotlin.jvm.Throws "postcode": "CA-55415" }, "note": "Handle with care", - "callbackUrl": "https://example.com/send/callback/here" + "callbackUrl": "https://callback-wls.no/order" } """ ) data class ApiUpdateOrderPayload( @Schema( - description = "Name of the host system which made the order.", + description = """Name of the host system which made the order.""", examples = [ "AXIELL", "ALMA", "ASTA", "BIBLIOFIL" ] ) val hostName: HostName, @Schema( - description = "Order ID from the host system which made the order.", + description = """Order ID from the host system which made the order.""", example = "mlt-12345-order" ) val hostOrderId: String, @Schema( - description = "List of items in the order, also called order lines.", + description = """List of items in the order, also called order lines.""", accessMode = READ_ONLY ) val orderLine: List, @Schema( - description = "Describes what type of order this is", + description = """Describes what type of order this is""", examples = [ "LOAN", "DIGITIZATION" ] ) val orderType: Order.Type, @Schema( - description = "Who's the receiver of the material in the order." + description = """Who to contact in relation to the order if case of any problems/issues/questions.""", + example = "Dr. Heinz Doofenshmirtz" ) val contactPerson: String, @Schema( - description = """ - The delivery address of the order. - If delivering to a country with states (I.E. the United States), include the state name in the region. - """, - example = """ - "address": { - "recipient": "Nasjonalbibliotekaren", - "addressLine1": "Henrik Ibsens gate 110", - "city": "Oslo", - "country": "Norway", - "postcode": "0255" - } - """ + description = """Address for the order, used in cases where storage operator sends out the order directly.""", + example = "{...}" ) val address: Order.Address?, @Schema( - description = "Any notes about the order", - example = "This is required in four weeks time" + description = """Notes regarding the order, such as delivery instructions, special requests, etc.""", + example = "I need this order in four weeks, not right now." ) val note: String?, @Schema( - description = "URL to send a callback to when the order is completed.", - example = "https://example.com/send/callback/here" + description = """URL to send a callback to when the order is completed.""", + example = "https://callback-wls.no/order" ) val callbackUrl: String ) { @@ -110,11 +100,7 @@ data class ApiUpdateOrderPayload( // Yes I am aware that this function is duplicated in three places // But I prefer readability over DRY in cases like this - return try { - URI(url).toURL() // Try to create a URL object - true - } catch (_: Exception) { - false // If exception is thrown, it's not a valid URL - } + val validator = UrlValidator(arrayOf("http", "https")) // Allow only HTTP/HTTPS + return validator.isValid(url) } } diff --git a/src/main/kotlin/no/nb/mlt/wls/application/hostapi/order/OrderController.kt b/src/main/kotlin/no/nb/mlt/wls/application/hostapi/order/OrderController.kt index c2e0414b..8b8bfa70 100644 --- a/src/main/kotlin/no/nb/mlt/wls/application/hostapi/order/OrderController.kt +++ b/src/main/kotlin/no/nb/mlt/wls/application/hostapi/order/OrderController.kt @@ -1,6 +1,7 @@ package no.nb.mlt.wls.application.hostapi.order import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.Parameter import io.swagger.v3.oas.annotations.callbacks.Callback import io.swagger.v3.oas.annotations.callbacks.Callbacks import io.swagger.v3.oas.annotations.media.Content @@ -34,7 +35,7 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody as SwaggerRequestBod @RestController @RequestMapping(path = ["/v1"]) -@Tag(name = "Order Controller", description = "API for ordering items via Hermes WLS") +@Tag(name = "Order Controller", description = """API for ordering items via Hermes WLS""") class OrderController( private val createOrder: CreateOrder, private val getOrder: GetOrder, @@ -49,7 +50,7 @@ class OrderController( @ApiResponses( ApiResponse( responseCode = "200", - description = """Order with given 'hostName' and 'hostOrderId' already exists in the system. + description = """Order with given "hostName" and "hostOrderId" already exists in the system. No new order was created, neither was the old order updated. Existing order information is returned for inspection. In rare cases the response body may be empty, that can happen if Hermes WLS does not @@ -64,7 +65,7 @@ class OrderController( ), ApiResponse( responseCode = "201", - description = "Created order for specified items to appropriate systems", + description = """Created order for specified items to appropriate systems""", content = [ Content( mediaType = "application/json", @@ -86,12 +87,12 @@ class OrderController( ), ApiResponse( responseCode = "401", - description = "Client sending the request is not authorized to order items.", + description = """Client sending the request is not authorized to order items.""", content = [Content(schema = Schema())] ), ApiResponse( responseCode = "403", - description = "A valid 'Authorization' header is missing from the request.", + description = """A valid "Authorization" header is missing from the request.""", content = [Content(schema = Schema())] ) ) @@ -104,8 +105,7 @@ class OrderController( Operation( summary = "Notification of updated order", description = """This callback triggers when the order is updated inside the storage systems. - It contains the data of the complete and updated order. - """, + It returns the same data as one would receive from the GET endpoint, meaning complete information about the order.""", method = "post", requestBody = SwaggerRequestBody( @@ -151,13 +151,17 @@ class OrderController( } @Operation( - summary = "Gets an order from the storage system", - description = "Checks if a specified order exists within Hermes WLS." + summary = "Retrieves order information from Hermes WLS", + description = """Endpoint for receiving detailed order information from our system, with updated status information. + Order status is updated based on information provided from the storage systems. + As such there might be a delay in the status update. + Some systems don't give any status updates and the order might be stuck in "NOT_STARTED" status until it's manually marked as "COMPLETED". + The caller must "own" the order, e.g. be the creator of the order.""" ) @ApiResponses( ApiResponse( responseCode = "200", - description = "Order with given 'hostName' and 'hostOrderId' exists in the system.", + description = """Information about the order with given "hostname" and "hostOrderId"""", content = [ Content( mediaType = "application/json", @@ -167,29 +171,42 @@ class OrderController( ), ApiResponse( responseCode = "400", - description = """Some field was invalid in your request. The error message contains information about the invalid fields.""", + description = """Some fields in your request are invalid. + The error message contains information about the invalid fields.""", content = [Content(schema = Schema())] ), ApiResponse( responseCode = "401", - description = "Client sending the request is not authorized to request orders, or this order does not belong to them.", + description = """Client sending the request is not authorized to request order info, or this order does not belong to them.""", content = [Content(schema = Schema())] ), ApiResponse( responseCode = "403", - description = "A valid 'Authorization' header is missing from the request.", + description = """A valid "Authorization" header is missing from the request.""", content = [Content(schema = Schema())] ), ApiResponse( responseCode = "404", - description = "The order with hostname and hostOrderId does not exist in the system.", + description = """The order with given "hostname" and "hostOrderId" does not exist in the system.""", content = [Content(schema = Schema())] ) ) @GetMapping("/order/{hostName}/{hostOrderId}") suspend fun getOrder( @AuthenticationPrincipal jwt: JwtAuthenticationToken, + @Parameter( + description = """Name of the host system which made the order.""", + required = true, + allowEmptyValue = false, + example = "AXIELL" + ) @PathVariable("hostName") hostName: HostName, + @Parameter( + description = """ID of the order which you wish to retrieve.""", + required = true, + allowEmptyValue = false, + example = "mlt-12345-order" + ) @PathVariable("hostOrderId") hostOrderId: String ): ResponseEntity { jwt.checkIfAuthorized(hostName) @@ -202,14 +219,16 @@ class OrderController( } @Operation( - summary = "Updates an existing order in the storage system(s)", - description = """Updates a specified order to the various storage systems via Hermes WLS. - """ + summary = "Update an existing order in the WLS", + description = """Updates the specified order in Hermes WLS and appropriate storage systems. + The order must have a status of "NOT_STARTED" to be updated. + The caller must "own" the order, e.g. be the creator of the order.""" ) @ApiResponses( ApiResponse( responseCode = "200", - description = "The order was updated with the new items, and sent to appropriate systems", + description = """The order was updated, and sent to appropriate systems. + Returns the updated order information for inspection.""", content = [ Content( mediaType = "application/json", @@ -226,17 +245,17 @@ class OrderController( ), ApiResponse( responseCode = "401", - description = "Client sending the request is not authorized to update orders, or this order does not belong to them.", + description = """Client sending the request is not authorized to update orders, or this order does not belong to them.""", content = [Content(schema = Schema())] ), ApiResponse( responseCode = "403", - description = "A valid 'Authorization' header is missing from the request.", + description = """A valid "Authorization" header is missing from the request.""", content = [Content(schema = Schema())] ), ApiResponse( responseCode = "409", - description = "The order is already being processed, and can not be edited at this point.", + description = """The order is already being processed, and can not be edited at this point.""", content = [Content(schema = Schema())] ) ) @@ -272,21 +291,20 @@ class OrderController( @ApiResponses( ApiResponse( responseCode = "200", - description = "Order with given 'hostName' and 'hostOrderId' was deleted from the system.", + description = """Order with given "hostName" and "hostOrderId" was deleted from the system.""", content = [Content(schema = Schema())] ), ApiResponse( responseCode = "401", - description = "Client sending the request is not authorized to delete orders, or this order does not belong to them." + description = """Client sending the request is not authorized to delete orders, or this order does not belong to them.""" ), ApiResponse( responseCode = "403", - description = """A valid 'Authorization' header is missing from the request, - or the caller is not authorized to delete the order.""" + description = """A valid "Authorization" header is missing from the request, or the caller is not authorized to delete the order.""" ), ApiResponse( responseCode = "404", - description = "Order with given 'hostName' and 'hostOrderId' does not exist in the system." + description = """Order with given "hostName" and "hostOrderId" does not exist in the system.""" ) ) @DeleteMapping("/order/{hostName}/{hostOrderId}") diff --git a/src/main/kotlin/no/nb/mlt/wls/application/synqapi/config/SwaggerConfig.kt b/src/main/kotlin/no/nb/mlt/wls/application/synqapi/config/SwaggerConfig.kt index c48264bb..30e8d15a 100644 --- a/src/main/kotlin/no/nb/mlt/wls/application/synqapi/config/SwaggerConfig.kt +++ b/src/main/kotlin/no/nb/mlt/wls/application/synqapi/config/SwaggerConfig.kt @@ -34,11 +34,12 @@ class SwaggerConfig { .title("SynQ updates port for Hermes WLS") .description( """ - Hermes is developed by the MLT (Warehouse and Logistics team) at the National Library of Norway (NLN). - Hermes facilitates communication between the NLN's storage systems and the cataloging systems. + Hermes, developed and maintained by the Warehouse and Logistics team (MLT) at the National Library of Norway (NLN). + Hermes facilitates communication between the NLN's storage systems and catalogues, serving as a master system. + This submodule of Hermes is responsible for receiving product/item and order updates from SynQ. - Hermes will then use these to convert the updates into a format that can be used by the rest of the system. - And send these updates along to appropriate catalogs and update internal item and order information. + Hermes converts these updates into a format that can be used by the rest of the system. + These are then sent along to appropriate catalogues, they also update internal item and order information. """.trimIndent() ).contact( Contact() diff --git a/src/main/kotlin/no/nb/mlt/wls/application/synqapi/synq/SynqBatchMoveItemPayload.kt b/src/main/kotlin/no/nb/mlt/wls/application/synqapi/synq/SynqBatchMoveItemPayload.kt index 0bcda5e9..ec52ca46 100644 --- a/src/main/kotlin/no/nb/mlt/wls/application/synqapi/synq/SynqBatchMoveItemPayload.kt +++ b/src/main/kotlin/no/nb/mlt/wls/application/synqapi/synq/SynqBatchMoveItemPayload.kt @@ -8,7 +8,7 @@ import no.nb.mlt.wls.domain.ports.inbound.MoveItemPayload import no.nb.mlt.wls.domain.ports.inbound.ValidationException @Schema( - description = "Payload for receiving Item updates in batch from SynQ storage system.", + description = """Payload with Product/Item movement updates from the SynQ storage system.""", example = """ { "tuId" : "6942066642", @@ -42,39 +42,42 @@ import no.nb.mlt.wls.domain.ports.inbound.ValidationException ) data class SynqBatchMoveItemPayload( @Schema( - description = "ID of the transport unit in the SynQ storage system.", + description = """ID of the transport unit in the SynQ storage system.""", example = "6942066642" ) val tuId: String, @Schema( - description = "Current location of the transport unit and its contents in the SynQ storage system.", + description = """Current location of the transport unit and its contents in the SynQ storage system.""", example = "SYNQ_WAREHOUSE" ) val location: String, @Schema( - description = "Previous location of the transport unit and its contents in the SynQ storage system.", + description = """Previous location of the transport unit and its contents in the SynQ storage system.""", example = "WS_PLUKKSENTER_1" ) val prevLocation: String, @Schema( - description = "List of products in the transport unit.", + description = """List of products/items in the transport unit (referred to as load units in SynQ). + Since we only have unique items an LU is equivalent to a product. + In usual warehouses you have multiple copies of the same product, i.e. 100 shirts here, 100 shirts there... + """, example = "[{...}]" ) val loadUnit: List, @Schema( - description = "User who initiated the update.", + description = """Identifier of user who caused the items to move, can be system if that was an automatic action.""", example = "per.person@nb.no" ) val user: String, @Schema( - description = "Warehouse where the transport unit is located.", + description = """Warehouse in which the TU moved.""", example = "Sikringsmagasin_2" ) val warehouse: String ) @Schema( - description = "Product information class, this is equivalent to an Item in the Hermes WLS.", + description = """Information about a product/item (LU) in the transport unit (TU) in the SynQ storage system.""", example = """ { "confidentialProduct" : false, @@ -99,59 +102,58 @@ data class SynqBatchMoveItemPayload( ) data class Product( @Schema( - description = "Marks the product as confidential, meaning only people with special access can modify or view the product.", + description = """Marks the product as confidential, meaning only people with special access can view, modify, or request the product.""", example = "false" ) val confidentialProduct: Boolean, @Schema( - description = "Name of the host system where the product is registered.", + description = """Name of the host system which the product belongs to.""", example = "AXIELL" ) @NotBlank val hostName: String, @Schema( - description = "Product ID from the host system, usually a barcode or an equivalent ID.", + description = """Product ID from the host system, usually a barcode value or an equivalent ID.""", example = "mlt-12345" ) @NotBlank val productId: String, @Schema( - description = "Owner of the product, usually the National Library of Norway (NB) or the National Archives of Norway (AV).", + description = """Product's owner, usually the National Library of Norway (NB) or the National Archives of Norway (AV).""", example = "NB" ) @NotBlank val productOwner: String, @Schema( - description = "Product version ID in the storage system, seems to always have value 'Default'.", + description = """Product version ID in the storage system, seems to always have value "Default".""", example = "Default" ) val productVersionId: String, @Schema( - description = """Quantity of the product in the transport unit. - Accepts doubles, but they will be converted to integers.""", + description = """Product quantity in the TU, SynQ uses doubles for quantity, however we convert it to integers.""", example = "1.0" ) @PositiveOrZero val quantityOnHand: Int, @Schema( - description = "Signifies the product is marked as suspect in the storage system and needs to be manually verified.", + description = """Signifies the product is missing, damaged, or otherwise suspect, and it requires manual action from the operator.""", example = "false" ) val suspect: Boolean, @Schema( - description = "List of attribute values for the product.", + description = """List of attributes for the product.""", example = "[{...}]" ) val attributeValue: List, @Schema( - description = "Position of the product in the TU, not used by us so it always have default values of '1,1,1'.", + description = """Position of the product in the TU, not used by us so this is pretty irrelevant.""", example = "{...}" ) val position: Position ) @Schema( - description = "Represents an attribute value for the product.", + description = """Represents a product's attribute.""", example = """ { "name" : "materialStatus", @@ -160,16 +162,17 @@ data class Product( ) data class AttributeValue( @Schema( - description = "Name of the attribute.", + description = """Name of the attribute.""", example = "materialStatus" ) val name: String, @Schema( - description = "Value of the attribute.", + description = """Value of the attribute.""", example = "Available" ) val value: String ) { + @Throws(ValidationException::class) fun validate() { if (name.isBlank()) { throw ValidationException("Attribute name cannot be blank") @@ -182,7 +185,7 @@ data class AttributeValue( } @Schema( - description = "Represents position of the product in the TU.", + description = """Represents position of the product in the TU.""", example = """ { "xPosition" : 1, @@ -192,17 +195,17 @@ data class AttributeValue( ) data class Position( @Schema( - description = "X position of the product in the TU.", + description = """X position of the product in the TU.""", example = "1" ) val xPosition: Int, @Schema( - description = "Y position of the product in the TU.", + description = """Y position of the product in the TU.""", example = "1" ) val yPosition: Int, @Schema( - description = "Z position of the product in the TU.", + description = """Z position of the product in the TU.""", example = "1" ) val zPosition: Int diff --git a/src/main/kotlin/no/nb/mlt/wls/application/synqapi/synq/SynqController.kt b/src/main/kotlin/no/nb/mlt/wls/application/synqapi/synq/SynqController.kt index 18eeca21..abdbc37d 100644 --- a/src/main/kotlin/no/nb/mlt/wls/application/synqapi/synq/SynqController.kt +++ b/src/main/kotlin/no/nb/mlt/wls/application/synqapi/synq/SynqController.kt @@ -21,7 +21,7 @@ import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping(path = ["/synq/v1"]) -@Tag(name = "SynQ Controller", description = "API for receiving product and order updates from SynQ in Hermes WLS") +@Tag(name = "SynQ Controller", description = """API for receiving product and order updates from SynQ in Hermes WLS""") class SynqController( private val moveItem: MoveItem, private val pickItems: PickItems, @@ -29,28 +29,28 @@ class SynqController( private val orderStatusUpdate: OrderStatusUpdate ) { @Operation( - summary = "Updates the status and location for items", - description = "Parses all the items from the SynQ load unit, and updates both status & location for them." + summary = "Updates item's status and location", + description = """Extracts information about every item, updates their location and quantity, and sends an update to the host systems.""" ) @ApiResponses( ApiResponse( responseCode = "200", - description = "Item with given 'hostName' and 'hostId' was found and updated.", + description = """Item with given "hostName" and "hostId" was found and updated.""", content = [Content(schema = Schema())] ), ApiResponse( responseCode = "400", - description = "The payload for moving items was invalid and nothing got updated.", + description = """The payload for moving items was invalid and nothing got updated.""", content = [Content(schema = Schema())] ), ApiResponse( responseCode = "403", - description = "A valid 'Authorization' header is missing from the request.", + description = """A valid "Authorization" header is missing from the request.""", content = [Content(schema = Schema())] ), ApiResponse( responseCode = "404", - description = """An item for a specific 'hostName' and 'hostId' was not found. + description = """An item for a specific "hostName" and "hostId" was not found. Error message contains information about the missing item.""", content = [Content(schema = Schema())] ) @@ -65,29 +65,28 @@ class SynqController( @Operation( summary = "Confirms the picking of items from a specific SynQ order", - description = """Updates the items from a specific order when they are picked from a SynQ warehouse. - This does not update the order status, as SynQ sends an update to the order-update endpoint later. - """ + description = """Updates status of items in a specific order when they are picked from a SynQ warehouse. + This does not update the order status, as SynQ sends an update to the order-update endpoint later.""" ) @ApiResponses( ApiResponse( responseCode = "200", - description = "The items were picked successfully.", + description = """The items were picked successfully.""", content = [Content(schema = Schema())] ), ApiResponse( responseCode = "400", - description = "Order update payload was invalid and nothing got updated.", + description = """Order update payload was invalid and nothing got updated.""", content = [Content(schema = Schema())] ), ApiResponse( responseCode = "401", - description = "Client sending the request is not authorized to update orders.", + description = """Client sending the request is not authorized to update orders.""", content = [Content(schema = Schema())] ), ApiResponse( responseCode = "403", - description = "A valid 'Authorization' header is missing from the request.", + description = """A valid "Authorization" header is missing from the request.""", content = [Content(schema = Schema())] ) ) @@ -116,36 +115,35 @@ class SynqController( @Operation( summary = "Updates order status based on SynQ order status update", - description = """Finds a specified order and updates its status given the message we receive from SynQ. - SynQ only sends us the update for the whole order, not for individual products. - They are updated in the pick-update endpoint. - """ + description = """Finds a specified order and updates its status. + SynQ only sends the order-update for the whole order, not for individual products in the order. + They are updated in the pick-update endpoint.""" ) @ApiResponses( ApiResponse( responseCode = "200", - description = """Order with given 'hostName' and 'orderId' was found and updated. + description = """Order with given "hostName" and "orderId" was found and updated. The response body contains the updated order.""", content = [Content(schema = Schema())] ), ApiResponse( responseCode = "400", - description = "Order update payload was invalid and nothing got updated.", + description = """Order update payload was invalid and nothing got updated.""", content = [Content(schema = Schema())] ), ApiResponse( responseCode = "401", - description = "Client sending the request is not authorized to update orders.", + description = """Client sending the request is not authorized to update orders.""", content = [Content(schema = Schema())] ), ApiResponse( responseCode = "403", - description = "A valid 'Authorization' header is missing from the request.", + description = """A valid "Authorization" header is missing from the request.""", content = [Content(schema = Schema())] ), ApiResponse( responseCode = "404", - description = """Order with given 'hostName' and 'orderId' was not found. + description = """Order with given "hostName" and "orderId" was not found. Error message contains information about the missing order.""", content = [Content(schema = Schema())] ) diff --git a/src/main/kotlin/no/nb/mlt/wls/application/synqapi/synq/SynqOrderPickingConfirmationPayload.kt b/src/main/kotlin/no/nb/mlt/wls/application/synqapi/synq/SynqOrderPickingConfirmationPayload.kt index 96454df7..e0c675f1 100644 --- a/src/main/kotlin/no/nb/mlt/wls/application/synqapi/synq/SynqOrderPickingConfirmationPayload.kt +++ b/src/main/kotlin/no/nb/mlt/wls/application/synqapi/synq/SynqOrderPickingConfirmationPayload.kt @@ -5,7 +5,7 @@ import no.nb.mlt.wls.domain.model.HostName import no.nb.mlt.wls.domain.ports.inbound.ValidationException @Schema( - description = "Payload for confirming picking of the order products/items in SynQ.", + description = """Payload which confirms the picking of products/items in a SynQ order.""", example = """ { "orderLine" : [ @@ -32,24 +32,22 @@ import no.nb.mlt.wls.domain.ports.inbound.ValidationException ) data class SynqOrderPickingConfirmationPayload( @Schema( - description = "List of order lines representing the picked products/items.", + description = """List of order lines representing the picked products/items.""", example = "[{...}]" ) val orderLine: List, @Schema( - description = "User who confirmed the picking.", + description = """User who picked the products/items.""", example = "per@person@nb.no" ) val operator: String, @Schema( - description = "Name of the warehouse where the order products/items are located.", + description = """Name of the warehouse where the order products/items were picked from.""", example = "Sikringsmagasin_2" ) val warehouse: String ) { - /** - * Validates the packet data and structure - */ + @Throws(ValidationException::class) fun validate() { if (orderLine.isEmpty()) { throw ValidationException("Picking update does not contain any elements in the order line") @@ -98,7 +96,7 @@ data class SynqOrderPickingConfirmationPayload( } @Schema( - description = "Order line representing a picked product/item in an order.", + description = """Order line representing a picked product/item in a SynQ order.""", example = """ { "confidentialProduct" : false, @@ -119,51 +117,52 @@ data class SynqOrderPickingConfirmationPayload( ) data class OrderLine( @Schema( - description = "Whether the product/item is confidential.", + description = """Marks the product as confidential, meaning only people with special access can view, modify, or request the product.""", example = "false" ) val confidentialProduct: Boolean, @Schema( - description = "Name of the host system which placed the order/owns the order products/items.", + description = """Name of the host system which the product belongs to.""", example = "AXIELL" ) val hostName: String, @Schema( - description = "Order line number.", + description = """Order line number/index.""", example = "1" ) val orderLineNumber: Int, @Schema( - description = "ID of the transport unit (TU) with the product/item.", + description = """ID of the transport unit (TU) with the product/item in SynQ.""", example = "SYS_TU_00000001157" ) val orderTuId: String, @Schema( - description = "Type of the transport unit (TU) with the product/item.", + description = """Type of the transport unit (TU) with the product/item.""", example = "UFO" ) val orderTuType: String, @Schema( - description = "Storage ID of the product/item.", + description = """Product ID from the host system, usually a barcode value or an equivalent ID.""", example = "mlt-12345" ) val productId: String, @Schema( - description = "Version ID of the product/item.", + description = """Product version ID in the storage system, seems to always have value "Default".""", example = "Default" ) val productVersionId: String, @Schema( - description = "Quantity of the product/item.", + description = """Number of picked products/items, in our case it should be 1 and nothing more.""", example = "1.0" ) val quantity: Int, @Schema( - description = "List of attribute values of the product/item.", + description = """List of attributes for the product.""", example = "[{...}]" ) val attributeValue: List ) { + @Throws(ValidationException::class) fun validate() { if (hostName.isBlank()) { throw ValidationException("Order Line's host name can not be blank") @@ -194,7 +193,7 @@ data class OrderLine( } if (quantity < 0) { - throw ValidationException("Order Line's quantity for the product $productId must be positive") + throw ValidationException("Order Line's quantity for the product '$productId' must be positive") } if (attributeValue.isNotEmpty()) { diff --git a/src/main/kotlin/no/nb/mlt/wls/application/synqapi/synq/SynqOrderStatusUpdatePayload.kt b/src/main/kotlin/no/nb/mlt/wls/application/synqapi/synq/SynqOrderStatusUpdatePayload.kt index a49d64b7..60966d8f 100644 --- a/src/main/kotlin/no/nb/mlt/wls/application/synqapi/synq/SynqOrderStatusUpdatePayload.kt +++ b/src/main/kotlin/no/nb/mlt/wls/application/synqapi/synq/SynqOrderStatusUpdatePayload.kt @@ -6,7 +6,7 @@ import no.nb.mlt.wls.domain.model.Order import no.nb.mlt.wls.domain.ports.inbound.ValidationException @Schema( - description = "Payload for updating the status of an order placed in SynQ.", + description = """Payload with updated status information for an order placed in SynQ.""", example = """ { "prevStatus": "PICKED", @@ -17,26 +17,27 @@ import no.nb.mlt.wls.domain.ports.inbound.ValidationException ) data class SynqOrderStatusUpdatePayload( @Schema( - description = "Previous status of the order in SynQ.", + description = """Previous order status.""", example = "PICKED" ) val prevStatus: SynqOrderStatus, @Schema( - description = "Current status of the order in SynQ.", + description = """Current order status.""", example = "COMPLETED" ) val status: SynqOrderStatus, @Schema( - description = "Name of the host system which placed the order/owns the order products/items.", + description = """Name of the host system which placed the order.""", example = "AXIELL" ) val hostName: HostName, @Schema( - description = "Name of the warehouse where the order products/items are located.", + description = """Name of the warehouse where the order products/items are located.""", example = "Sikringmagasin_2" ) val warehouse: String ) { + @Throws(ValidationException::class) fun validate() { if (warehouse.isBlank()) { throw ValidationException("Order status update cannot hve a blank warehouse") diff --git a/src/main/kotlin/no/nb/mlt/wls/domain/model/Item.kt b/src/main/kotlin/no/nb/mlt/wls/domain/model/Item.kt index 9d651da7..01702366 100644 --- a/src/main/kotlin/no/nb/mlt/wls/domain/model/Item.kt +++ b/src/main/kotlin/no/nb/mlt/wls/domain/model/Item.kt @@ -24,7 +24,7 @@ data class Item( if (amountPicked > itemsInStockQuantity) { logger.error { "Tried to pick too many items for item with id '$hostId'. " + - "WLS DB has $itemsInStockQuantity stocked, and storage system tried to pick $amountPicked" + "WLS DB has '$itemsInStockQuantity' stocked, and storage system tried to pick '$amountPicked'" } } val quantity = Math.clamp(itemsInStockQuantity.minus(amountPicked).toLong(), 0, Int.MAX_VALUE) @@ -37,7 +37,7 @@ data class Item( } else { // Rare edge case. Log it until we can determine if this actually happens in production logger.error { - "Item with ID $hostId for host $hostName without a location was picked. Location was set to \"UNKNOWN\"." + "Item with ID '$hostId' for host '$hostName' without a location was picked. Location was set to 'UNKNOWN'." } "UNKNOWN" } diff --git a/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/AddNewItem.kt b/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/AddNewItem.kt index 52534155..882e90d1 100644 --- a/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/AddNewItem.kt +++ b/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/AddNewItem.kt @@ -7,7 +7,7 @@ import no.nb.mlt.wls.domain.model.ItemCategory import no.nb.mlt.wls.domain.model.Owner import no.nb.mlt.wls.domain.model.Packaging -interface AddNewItem { +fun interface AddNewItem { suspend fun addItem(itemMetadata: ItemMetadata): Item } diff --git a/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/CreateOrder.kt b/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/CreateOrder.kt index 3fbf5841..8089273e 100644 --- a/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/CreateOrder.kt +++ b/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/CreateOrder.kt @@ -4,7 +4,7 @@ import no.nb.mlt.wls.domain.model.HostName import no.nb.mlt.wls.domain.model.Order import no.nb.mlt.wls.domain.model.Owner -interface CreateOrder { +fun interface CreateOrder { suspend fun createOrder(orderDTO: CreateOrderDTO): Order } diff --git a/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/DeleteOrder.kt b/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/DeleteOrder.kt index bb3f488a..6a31c6ee 100644 --- a/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/DeleteOrder.kt +++ b/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/DeleteOrder.kt @@ -3,7 +3,7 @@ package no.nb.mlt.wls.domain.ports.inbound import no.nb.mlt.wls.domain.model.HostName import kotlin.jvm.Throws -interface DeleteOrder { +fun interface DeleteOrder { @Throws(OrderNotFoundException::class) suspend fun deleteOrder( hostName: HostName, diff --git a/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/GetItem.kt b/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/GetItem.kt index 16befcab..c3bd7a73 100644 --- a/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/GetItem.kt +++ b/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/GetItem.kt @@ -3,7 +3,7 @@ package no.nb.mlt.wls.domain.ports.inbound import no.nb.mlt.wls.domain.model.HostName import no.nb.mlt.wls.domain.model.Item -interface GetItem { +fun interface GetItem { suspend fun getItem( hostName: HostName, hostId: String diff --git a/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/GetOrder.kt b/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/GetOrder.kt index 891e9c4a..51b60b82 100644 --- a/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/GetOrder.kt +++ b/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/GetOrder.kt @@ -4,7 +4,7 @@ import no.nb.mlt.wls.domain.model.HostName import no.nb.mlt.wls.domain.model.Order import kotlin.jvm.Throws -interface GetOrder { +fun interface GetOrder { @Throws(OrderNotFoundException::class) suspend fun getOrder( hostName: HostName, diff --git a/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/MoveItem.kt b/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/MoveItem.kt index 16dac8e8..de497c9d 100644 --- a/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/MoveItem.kt +++ b/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/MoveItem.kt @@ -11,7 +11,7 @@ import no.nb.mlt.wls.domain.model.Item * storage systems. * In both cases we want to know where the item went, and if the count changed */ -interface MoveItem { +fun interface MoveItem { suspend fun moveItem(moveItemPayload: MoveItemPayload): Item } @@ -21,6 +21,7 @@ data class MoveItemPayload( val quantity: Int, val location: String ) { + @Throws(ValidationException::class) fun validate() { if (quantity < 0.0) { throw ValidationException("Quantity can not be negative") diff --git a/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/OrderStatusUpdate.kt b/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/OrderStatusUpdate.kt index cae00f7f..38624b92 100644 --- a/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/OrderStatusUpdate.kt +++ b/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/OrderStatusUpdate.kt @@ -4,7 +4,7 @@ import no.nb.mlt.wls.domain.model.HostName import no.nb.mlt.wls.domain.model.Order import kotlin.jvm.Throws -interface OrderStatusUpdate { +fun interface OrderStatusUpdate { @Throws(OrderNotFoundException::class) suspend fun updateOrderStatus( hostName: HostName, diff --git a/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/PickItems.kt b/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/PickItems.kt index 766aea1e..13aa3de9 100644 --- a/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/PickItems.kt +++ b/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/PickItems.kt @@ -6,7 +6,7 @@ import no.nb.mlt.wls.domain.model.HostName * This interface handles updating item counts when an item is picked out of * a storage system. */ -interface PickItems { +fun interface PickItems { suspend fun pickItems( hostName: HostName, itemsPickedMap: Map diff --git a/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/PickOrderItems.kt b/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/PickOrderItems.kt index e9c8b438..d2fb2aa0 100644 --- a/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/PickOrderItems.kt +++ b/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/PickOrderItems.kt @@ -6,7 +6,7 @@ import no.nb.mlt.wls.domain.model.HostName * This interface is used to handle when an items from an Order has been picked, which * indicates that the status in the Order Items need to be updated */ -interface PickOrderItems { +fun interface PickOrderItems { suspend fun pickOrderItems( hostName: HostName, pickedHostIds: List, diff --git a/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/UpdateOrder.kt b/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/UpdateOrder.kt index dfee16d3..1e02e411 100644 --- a/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/UpdateOrder.kt +++ b/src/main/kotlin/no/nb/mlt/wls/domain/ports/inbound/UpdateOrder.kt @@ -5,7 +5,7 @@ import no.nb.mlt.wls.domain.model.Order import no.nb.mlt.wls.domain.ports.outbound.OrderUpdateException import kotlin.jvm.Throws -interface UpdateOrder { +fun interface UpdateOrder { @Throws(OrderNotFoundException::class, ValidationException::class, OrderUpdateException::class, IllegalOrderStateException::class) suspend fun updateOrder( hostName: HostName, diff --git a/src/main/kotlin/no/nb/mlt/wls/infrastructure/callbacks/NotificationItemPayload.kt b/src/main/kotlin/no/nb/mlt/wls/infrastructure/callbacks/NotificationItemPayload.kt index 18d2531e..957af0d2 100644 --- a/src/main/kotlin/no/nb/mlt/wls/infrastructure/callbacks/NotificationItemPayload.kt +++ b/src/main/kotlin/no/nb/mlt/wls/infrastructure/callbacks/NotificationItemPayload.kt @@ -9,66 +9,83 @@ import no.nb.mlt.wls.domain.model.ItemCategory import no.nb.mlt.wls.domain.model.Owner import no.nb.mlt.wls.domain.model.Packaging +@Schema( + description = """Payload for updates about items sent from Hermes WLS to the appropriate catalogues.""", + example = """ + { + "hostId": "mlt-12345", + "hostName": "AXIELL", + "description": "Tyven, tyven skal du hete", + "itemCategory": "PAPER", + "preferredEnvironment": "NONE", + "packaging": "NONE", + "owner": "NB", + "callbackUrl": "https://callback-wls.no/item", + "location": "SYNQ_WAREHOUSE", + "quantity": 1 + } + """ +) data class NotificationItemPayload( @Schema( - description = "ID from the host system which owns the item.", - examples = ["item-12345"] + description = """The item ID from the host system, usually a barcode or an equivalent ID.""", + example = "item-12345" ) val hostId: String, @Schema( - description = "Name of the host system which manages the item.", + description = """Name of the host system which the item originates from. + Host system is usually the catalogue that the item is registered in.""", examples = ["AXIELL", "ALMA", "ASTA", "BIBLIOFIL"] ) val hostName: HostName, @Schema( - description = - "Description of the item for easy identification in the warehouse system. " + - "Usually an item title/name, e.g. book title, film name, etc. or contents description.", + description = """Description of the item for easy identification in the warehouse system. + Usually an item title/name, e.g. book title, film name, etc. or contents description.""", examples = ["Tyven, tyven skal du hete", "Avisa Hemnes", "Kill Buljo"] ) val description: String, - // TODO - Update this schema to reflect new categories @Schema( - description = "What kind of item category the item belongs to, e.g. Books, Issues, Films, etc.", - examples = [ - "safetyfilm", "nitratfilm", "film", "plater", "fotografier", "papir", - "gjenstand", "lydbånd", "videobånd", "sekkepost", "magnetbånd" - ] + description = """Item's category, same category indicates that the items can be stored together without any preservation issues. + For example: books, magazines, newspapers, etc. are of type PAPER, and can be stored together without damaging each other.""", + examples = ["PAPER", "DISC", "FILM", "PHOTO", "EQUIPMENT", "BULK_ITEMS", "MAGNETIC_TAPE"] ) val itemCategory: ItemCategory, @Schema( - description = "What kind of environment the item should be stored in, e.g. NONE, FRYS, MUGG_CELLE, etc.", + description = """What kind of environment the item should be stored in. + "NONE" is for normal storage for the item category, "FRYS" is for frozen storage, etc. + NOTE: This is not a guarantee that the item will be stored in the preferred environment. + In cases where storage space is limited, the item may be stored in a different environment.""", examples = ["NONE", "FRYS"] ) val preferredEnvironment: Environment, @Schema( - description = - "Whether the item is a single object or a box/abox/crate of other items. " + - "NONE is for single objects, BOX is for boxes, ABOX is for archival boxes, and CRATE is for crates.", + description = """Whether the item is a single object or a container with other items inside. + "NONE" is for single objects, "ABOX" is for archival boxes, etc. + NOTE: It is up to the catalogue to keep track of the items inside a container.""", examples = ["NONE", "BOX", "ABOX", "CRATE"] ) val packaging: Packaging, @Schema( - description = "Who's the owner of the item.", + description = """Who owns the item. Usually the National Library of Norway ("NB") or the National Archives of Norway ("ARKIVVERKET").""", examples = ["NB", "ARKIVVERKET"] ) val owner: Owner, @Schema( - description = "Callback URL for the item used to update the item in the host system.", - example = "https://example.com/send/callback/here" + description = """Callback URL to use for sending item updates to the host system. + For example when item moves or changes quantity in storage.""", + example = "https://callback-wls.no/item" ) val callbackUrl: String?, @Schema( - description = "Where the item is located, e.g. SYNQ_WAREHOUSE, AUTOSTORE, KARDEX, etc.", + description = """Where the item is located, can be used for tracking item movement through storage systems.""", examples = ["SYNQ_WAREHOUSE", "AUTOSTORE", "KARDEX"], accessMode = READ_ONLY, required = false ) val location: String?, @Schema( - description = - "Quantity on hand of the item, this denotes if the item is in storage or not. " + - "If the item is in storage then quantity is 1.0, if it's not in storage then quantity is 0.0.", + description = """Quantity on hand of the item, this easily denotes if the item is in the storage or not. + If the item is in storage then quantity is 1, if it's not in storage then quantity is 0.""", examples = [ "0.0", "1.0"], accessMode = READ_ONLY, required = false diff --git a/src/main/kotlin/no/nb/mlt/wls/infrastructure/callbacks/NotificationOrderPayload.kt b/src/main/kotlin/no/nb/mlt/wls/infrastructure/callbacks/NotificationOrderPayload.kt index f1e807ea..bcc576d5 100644 --- a/src/main/kotlin/no/nb/mlt/wls/infrastructure/callbacks/NotificationOrderPayload.kt +++ b/src/main/kotlin/no/nb/mlt/wls/infrastructure/callbacks/NotificationOrderPayload.kt @@ -6,7 +6,7 @@ import no.nb.mlt.wls.domain.model.Order import no.nb.mlt.wls.domain.model.Owner @Schema( - description = "Payload for creating and editing an order in Hermes WLS, and appropriate storage system(s).", + description = """Payload for updates about orders sent from Hermes WLS to the appropriate catalogues.""", example = """ { "hostName": "AXIELL", @@ -20,7 +20,7 @@ import no.nb.mlt.wls.domain.model.Owner ], "orderType": "LOAN", "owner": "NB", - "contactPerson": "MLT Team", + "contactPerson": "Dr. Heinz Doofenshmirtz", "address": { "recipient": "Doug Dimmadome", "addressLine1": "Dimmsdale Dimmadome", @@ -30,56 +30,66 @@ import no.nb.mlt.wls.domain.model.Owner "region": "California", "postcode": "CA-55415" }, - "callbackUrl": "https://example.com/send/callback/here" + "callbackUrl": "https://callback-wls.no/order" } """ ) data class NotificationOrderPayload( @Schema( - description = "Name of the host system which made the order.", + description = """Name of the host system which made the order.""", examples = ["AXIELL", "ALMA", "ASTA", "BIBLIOFIL"] ) val hostName: HostName, @Schema( - description = "Order ID from the host system which made the order.", + description = """ID for the order, preferably the same ID as the one in the host system.""", example = "mlt-12345-order" ) val hostOrderId: String, @Schema( - description = "Current status of the order as a whole.", - examples = ["NOT_STARTED", "IN_PROGRESS", "COMPLETED", "DELETED"] + description = """Current status for the whole order. + "COMPLETED" means that the order is finished and items are ready for pickup / sent to receiver. + "RETURNED" means that the order items have been returned to the storage.""", + examples = ["NOT_STARTED", "IN_PROGRESS", "COMPLETED", "RETURNED", "DELETED"] ) val status: Order.Status?, @Schema( - description = "List of items in the order, also called order lines." + description = """List of items in the order, also called order lines.""", + example = "NOT_STARTED" ) val orderLine: List, @Schema( - description = "Describes what type of order this is", + description = """Describes what type of order this is. + "LOAN" means that the order is for borrowing items to external or internal users, + usually meaning the items will be viewed, inspected, etc. + "DIGITIZATION" means that the order is specifically for digitizing items, + usually meaning that the order will be delivered to digitization workstation.""", examples = ["LOAN", "DIGITIZATION"] ) val orderType: Order.Type, @Schema( - description = "Who's the owner of the material in the order.", + description = """Who's the owner of the material in the order.""", examples = ["NB", "ARKIVVERKET"] ) val owner: Owner?, @Schema( - description = "The person to contact regarding matters about the order" + description = """Who to contact in relation to the order if case of any problems/issues/questions.""", + example = "Dr. Heinz Doofenshmirtz" ) val contactPerson: String, @Schema( - description = "The address of the receiver of the material in the order." + description = """Address for the order, used in cases where storage operator sends out the order directly.""", + example = "{...}" ) val address: Order.Address?, @Schema( - description = "Any notes about the order", - example = "This is required in four weeks time" + description = """Notes regarding the order, such as delivery instructions, special requests, etc.""", + example = "I need this order in four weeks, not right now." ) val note: String?, @Schema( - description = "Callback URL for the order used to update the order information in the host system.", - example = "https://example.com/send/callback/here" + description = """Callback URL to use for sending order updates to the host system. + For example when order items get picked or the order is cancelled.""", + example = "https://callback-wls.no/order" ) val callbackUrl: String ) diff --git a/src/main/kotlin/no/nb/mlt/wls/infrastructure/repositories/order/MongoOrder.kt b/src/main/kotlin/no/nb/mlt/wls/infrastructure/repositories/order/MongoOrder.kt index ca74ced2..e4b07004 100644 --- a/src/main/kotlin/no/nb/mlt/wls/infrastructure/repositories/order/MongoOrder.kt +++ b/src/main/kotlin/no/nb/mlt/wls/infrastructure/repositories/order/MongoOrder.kt @@ -8,8 +8,8 @@ import org.springframework.data.annotation.Id import org.springframework.data.mongodb.core.index.CompoundIndex import org.springframework.data.mongodb.core.mapping.Document -@CompoundIndex(unique = true, def = "{'hostName':1,'hostOrderId':1}") @Document(collection = "orders") +@CompoundIndex(unique = true, def = "{'hostName':1,'hostOrderId':1}") data class MongoOrder( @Id private val id: ObjectId = ObjectId(), diff --git a/src/main/kotlin/no/nb/mlt/wls/infrastructure/synq/SynqOrderPayload.kt b/src/main/kotlin/no/nb/mlt/wls/infrastructure/synq/SynqOrderPayload.kt index 6288bb3a..79bcca90 100644 --- a/src/main/kotlin/no/nb/mlt/wls/infrastructure/synq/SynqOrderPayload.kt +++ b/src/main/kotlin/no/nb/mlt/wls/infrastructure/synq/SynqOrderPayload.kt @@ -44,11 +44,15 @@ data class ShippingAddress( val address: Address ) { data class Address( + // SynQ does not have a field where we can put owner/contact person for the order, as such this field will be used for order's contact person val contactPerson: String, + // This will contain address.recipient, as contactPerson is used for something else, explained above ^ @JsonInclude(JsonInclude.Include.NON_NULL) val addressLine1: String? = null, + // This will contain address.addressLine1, as ... @JsonInclude(JsonInclude.Include.NON_NULL) val addressLine2: String? = null, + // ...addressLine2... @JsonInclude(JsonInclude.Include.NON_NULL) val addressLine3: String? = null, @JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/src/test/kotlin/no/nb/mlt/wls/TraillingSlashRedirectTest.kt b/src/test/kotlin/no/nb/mlt/wls/TraillingSlashRedirectTest.kt index b8d89e33..145a9fac 100644 --- a/src/test/kotlin/no/nb/mlt/wls/TraillingSlashRedirectTest.kt +++ b/src/test/kotlin/no/nb/mlt/wls/TraillingSlashRedirectTest.kt @@ -87,7 +87,7 @@ class TraillingSlashRedirectTest( preferredEnvironment = NONE, packaging = Packaging.NONE, owner = Owner.NB, - callbackUrl = "https://callback.com/item", + callbackUrl = "https://callback-wls.no/item", location = "SYNQ_WAREHOUSE", quantity = 1 ) diff --git a/src/test/kotlin/no/nb/mlt/wls/domain/WLSServiceTest.kt b/src/test/kotlin/no/nb/mlt/wls/domain/WLSServiceTest.kt index f2ed835e..d4af1dfb 100644 --- a/src/test/kotlin/no/nb/mlt/wls/domain/WLSServiceTest.kt +++ b/src/test/kotlin/no/nb/mlt/wls/domain/WLSServiceTest.kt @@ -227,7 +227,7 @@ class WLSServiceTest { runTest { val order = cut.createOrder( - createOrderDTO.copy(callbackUrl = "https://newurl.com") + createOrderDTO.copy(callbackUrl = "https://new-callback-wls.no/order") ) assertThat(order).isEqualTo(testOrder) @@ -418,7 +418,7 @@ class WLSServiceTest { preferredEnvironment = Environment.NONE, packaging = Packaging.NONE, owner = Owner.NB, - callbackUrl = "https://callback.com/item", + callbackUrl = "https://callback-wls.no/item", location = null, quantity = null ) @@ -434,7 +434,7 @@ class WLSServiceTest { contactPerson = "contactPerson", address = createOrderAddress(), note = "note", - callbackUrl = "https://callback.com/order" + callbackUrl = "https://callback-wls.no/order" ) private val updatedOrder = diff --git a/src/test/kotlin/no/nb/mlt/wls/item/controller/ItemControllerTest.kt b/src/test/kotlin/no/nb/mlt/wls/item/controller/ItemControllerTest.kt index 336dfa82..0efb06d8 100644 --- a/src/test/kotlin/no/nb/mlt/wls/item/controller/ItemControllerTest.kt +++ b/src/test/kotlin/no/nb/mlt/wls/item/controller/ItemControllerTest.kt @@ -244,7 +244,7 @@ class ItemControllerTest( preferredEnvironment = NONE, packaging = Packaging.BOX, owner = Owner.NB, - callbackUrl = "https://callback.com/item", + callbackUrl = "https://callback-wls.no/item", location = "SYNQ_WAREHOUSE", quantity = 1 ) @@ -261,7 +261,7 @@ class ItemControllerTest( preferredEnvironment = NONE, packaging = Packaging.NONE, owner = Owner.NB, - callbackUrl = "https://callback.com/item", + callbackUrl = "https://callback-wls.no/item", location = "SYNQ_WAREHOUSE", quantity = 1 ) diff --git a/src/test/kotlin/no/nb/mlt/wls/item/model/ItemModelConversionTest.kt b/src/test/kotlin/no/nb/mlt/wls/item/model/ItemModelConversionTest.kt index fd2a135f..78057f1a 100644 --- a/src/test/kotlin/no/nb/mlt/wls/item/model/ItemModelConversionTest.kt +++ b/src/test/kotlin/no/nb/mlt/wls/item/model/ItemModelConversionTest.kt @@ -27,7 +27,7 @@ class ItemModelConversionTest { preferredEnvironment = Environment.NONE, packaging = Packaging.NONE, owner = Owner.NB, - callbackUrl = "https://callback.com/item", + callbackUrl = "https://callback-wls.no/item", location = "", quantity = 1 ) @@ -41,7 +41,7 @@ class ItemModelConversionTest { preferredEnvironment = Environment.NONE, packaging = Packaging.NONE, owner = Owner.NB, - callbackUrl = "https://callback.com/item", + callbackUrl = "https://callback-wls.no/item", location = "", quantity = 1 ) @@ -67,7 +67,7 @@ class ItemModelConversionTest { preferredEnvironment = Environment.NONE, packaging = Packaging.NONE, owner = Owner.NB, - callbackUrl = "https://callback.com/item", + callbackUrl = "https://callback-wls.no/item", location = "", quantity = 1 ) diff --git a/src/test/kotlin/no/nb/mlt/wls/item/model/ItemModelValidationTest.kt b/src/test/kotlin/no/nb/mlt/wls/item/model/ItemModelValidationTest.kt index 3a6599bb..4d221b8d 100644 --- a/src/test/kotlin/no/nb/mlt/wls/item/model/ItemModelValidationTest.kt +++ b/src/test/kotlin/no/nb/mlt/wls/item/model/ItemModelValidationTest.kt @@ -83,7 +83,7 @@ class ItemModelValidationTest { @Test fun `item with invalid callbackUrl should fail validation`() { - val item = validItem.copy(callbackUrl = "hppt://callback.com/item") + val item = validItem.copy(callbackUrl = "hppts://invalid-callback-wls.no/item") val thrown = catchThrowable(item::validate) @@ -106,7 +106,7 @@ class ItemModelValidationTest { preferredEnvironment = Environment.NONE, packaging = Packaging.NONE, owner = Owner.NB, - callbackUrl = "https://callback.com/item", + callbackUrl = "https://callback-wls.no/item", location = "location", quantity = 1 ) diff --git a/src/test/kotlin/no/nb/mlt/wls/order/controller/OrderControllerTest.kt b/src/test/kotlin/no/nb/mlt/wls/order/controller/OrderControllerTest.kt index cca8bebb..622c7a91 100644 --- a/src/test/kotlin/no/nb/mlt/wls/order/controller/OrderControllerTest.kt +++ b/src/test/kotlin/no/nb/mlt/wls/order/controller/OrderControllerTest.kt @@ -477,7 +477,7 @@ class OrderControllerTest( ), contactPerson = "named person", note = "note", - callbackUrl = "https://callback.com/order" + callbackUrl = "https://callback-wls.no/order" ) /** @@ -503,7 +503,7 @@ class OrderControllerTest( ), contactPerson = "named person", note = "note", - callbackUrl = "https://callback.com/order" + callbackUrl = "https://callback-wls.no/order" ) private val orderInProgress = @@ -526,7 +526,7 @@ class OrderControllerTest( ), contactPerson = "named person", note = "note", - callbackUrl = "https://callback.com/order" + callbackUrl = "https://callback-wls.no/order" ) /** @@ -554,7 +554,7 @@ class OrderControllerTest( owner = Owner.NB, location = "location", quantity = 1, - callbackUrl = "https://callback.com/item" + callbackUrl = "https://callback-wls.no/item" ) } ) diff --git a/src/test/kotlin/no/nb/mlt/wls/order/model/OrderModelValidationTest.kt b/src/test/kotlin/no/nb/mlt/wls/order/model/OrderModelValidationTest.kt index e9a2145c..d520201c 100644 --- a/src/test/kotlin/no/nb/mlt/wls/order/model/OrderModelValidationTest.kt +++ b/src/test/kotlin/no/nb/mlt/wls/order/model/OrderModelValidationTest.kt @@ -223,7 +223,7 @@ class OrderModelValidationTest { orderType = Order.Type.LOAN, address = validAddress, contactPerson = "contactPerson", - callbackUrl = "https://callback.com/order", + callbackUrl = "https://callback-wls.no/order", note = "note" ) @@ -235,7 +235,7 @@ class OrderModelValidationTest { orderType = Order.Type.LOAN, contactPerson = "contactPerson", address = validAddress, - callbackUrl = "https://callback.com/order", + callbackUrl = "https://callback-wls.no/order", note = "note" ) } diff --git a/src/test/kotlin/no/nb/mlt/wls/synq/controller/SynqControllerTest.kt b/src/test/kotlin/no/nb/mlt/wls/synq/controller/SynqControllerTest.kt index d0c4098a..b7b749a0 100644 --- a/src/test/kotlin/no/nb/mlt/wls/synq/controller/SynqControllerTest.kt +++ b/src/test/kotlin/no/nb/mlt/wls/synq/controller/SynqControllerTest.kt @@ -365,7 +365,7 @@ class SynqControllerTest( itemCategory = ItemCategory.PAPER, preferredEnvironment = Environment.FRYS, packaging = Packaging.BOX, - callbackUrl = "https://callback.com/item", + callbackUrl = "https://callback-wls.no/item", location = null, quantity = 0 ) @@ -379,7 +379,7 @@ class SynqControllerTest( itemCategory = ItemCategory.PAPER, preferredEnvironment = Environment.FRYS, packaging = Packaging.BOX, - callbackUrl = "https://callback.com/item", + callbackUrl = "https://callback-wls.no/item", location = null, quantity = 0 ) @@ -403,7 +403,7 @@ class SynqControllerTest( null, null ), - callbackUrl = "https://callback.com/order", + callbackUrl = "https://callback-wls.no/order", note = "note" )