From 54fb39d15c6cdc131cb0d5a914609d914fab8f7e Mon Sep 17 00:00:00 2001 From: anian Date: Sun, 15 Mar 2026 23:33:02 +0800 Subject: [PATCH] =?UTF-8?q?1.4.3=E5=8E=9F=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 520 ++++++ .gitignore | 80 + LICENSE | 674 +++++++ OWNERS | 10 + README.md | 235 +++ api-docs/openapi/v3_0/pluginFriendsApi.json | 1520 ++++++++++++++++ build.gradle | 102 ++ gradle.properties | 1 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 61608 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 244 +++ gradlew.bat | 92 + package.json | 5 + packages/fridends-rss/.eslintrc.json | 33 + packages/fridends-rss/.gitignore | 24 + packages/fridends-rss/.prettierrc.json | 8 + packages/fridends-rss/package.json | 59 + packages/fridends-rss/src/friends-rss.ts | 93 + packages/fridends-rss/src/index.ts | 2 + packages/fridends-rss/src/loading-bar.ts | 51 + packages/fridends-rss/src/types/index.ts | 28 + packages/fridends-rss/src/vite-env.d.ts | 1 + .../src/vite/shared-plugin-config.ts | 14 + packages/fridends-rss/tsconfig.json | 33 + packages/fridends-rss/var.css | 16 + packages/fridends-rss/vite.config.dev.ts | 14 + packages/fridends-rss/vite.config.lib.ts | 34 + packages/fridends-rss/vitest.config.ts | 7 + pnpm-workspace.yaml | 3 + settings.gradle | 7 + .../moony/friends/FriendsHeadProcessor.java | 45 + .../java/la/moony/friends/FriendsPlugin.java | 79 + .../la/moony/friends/ListedRssSyncLog.java | 19 + .../la/moony/friends/RssFeedSyncEvent.java | 20 + .../friends/endpoint/FriendEndpoint.java | 170 ++ .../friends/extension/CronFriendPost.java | 56 + .../moony/friends/extension/FriendPost.java | 43 + .../java/la/moony/friends/extension/Link.java | 32 + .../la/moony/friends/extension/LinkGroup.java | 31 + .../friends/extension/RssFeedSyncLog.java | 41 + .../moony/friends/finders/FriendFinder.java | 28 + .../finders/impl/FriendFinderImpl.java | 240 +++ .../moony/friends/query/FriendPostQuery.java | 107 ++ .../moony/friends/query/RssSyncLogQuery.java | 60 + .../CronCronFriendPostReconciler.java | 121 ++ .../friends/reconciler/LinkReconciler.java | 47 + .../friends/reconciler/RssSyncReconciler.java | 300 ++++ .../la/moony/friends/rest/FriendRouter.java | 94 + .../friends/service/RssDetailService.java | 9 + .../service/RssFeedSyncLogService.java | 13 + .../service/impl/RssDetailServiceImpl.java | 53 + .../impl/RssFeedSyncLogServiceImpl.java | 97 + .../la/moony/friends/util/CommonUtils.java | 24 + .../la/moony/friends/util/LinkRequest.java | 53 + .../java/la/moony/friends/util/SortUtils.java | 43 + .../la/moony/friends/vo/FriendPostVo.java | 27 + .../java/la/moony/friends/vo/LinkGroupVo.java | 31 + src/main/java/la/moony/friends/vo/LinkVo.java | 38 + .../java/la/moony/friends/vo/RssDetail.java | 41 + .../extensions/annotation-setting.yaml | 13 + .../resources/extensions/reverseProxy.yaml | 8 + .../resources/extensions/roleTemplate.yaml | 59 + src/main/resources/extensions/settings.yaml | 19 + src/main/resources/logo.png | Bin 0 -> 5749 bytes src/main/resources/plugin.yaml | 25 + src/main/resources/static/friends-rss.iife.js | 118 ++ src/main/resources/static/var.css | 16 + src/main/resources/templates/friends.html | 1559 +++++++++++++++++ ui/.eslintrc.cjs | 22 + ui/env.d.ts | 16 + ui/package.json | 71 + ui/pnpm-workspace.yaml | 4 + ui/rsbuild.config.mjs | 36 + ui/src/api/generated/.gitignore | 4 + ui/src/api/generated/.npmignore | 1 + .../api/generated/.openapi-generator-ignore | 23 + ui/src/api/generated/.openapi-generator/FILES | 38 + .../api/generated/.openapi-generator/VERSION | 1 + ui/src/api/generated/api.ts | 21 + ...riend-moony-la-v1alpha1-friend-post-api.ts | 654 +++++++ .../api/cron-friend-post-v1alpha1-api.ts | 665 +++++++ .../generated/api/friend-post-v1alpha1-api.ts | 665 +++++++ .../api/rss-feed-sync-log-v1alpha1-api.ts | 665 +++++++ ui/src/api/generated/base.ts | 86 + ui/src/api/generated/common.ts | 150 ++ ui/src/api/generated/configuration.ts | 110 ++ ui/src/api/generated/git_push.sh | 57 + ui/src/api/generated/index.ts | 18 + ui/src/api/generated/models/add-operation.ts | 49 + ui/src/api/generated/models/channel.ts | 51 + ui/src/api/generated/models/copy-operation.ts | 49 + .../generated/models/cron-friend-post-list.ts | 81 + .../api/generated/models/cron-friend-post.ts | 63 + ui/src/api/generated/models/cron-spec.ts | 54 + ui/src/api/generated/models/cron-status.ts | 36 + .../api/generated/models/friend-post-list.ts | 81 + .../api/generated/models/friend-post-spec.ts | 72 + ui/src/api/generated/models/friend-post.ts | 54 + ui/src/api/generated/models/index.ts | 24 + ui/src/api/generated/models/item.ts | 60 + .../api/generated/models/json-patch-inner.ts | 41 + ui/src/api/generated/models/link-spec.ts | 60 + ui/src/api/generated/models/link.ts | 54 + .../models/listed-rss-sync-log-list.ts | 81 + .../generated/models/listed-rss-sync-log.ts | 42 + ui/src/api/generated/models/metadata.ts | 72 + ui/src/api/generated/models/move-operation.ts | 49 + .../api/generated/models/remove-operation.ts | 43 + .../api/generated/models/replace-operation.ts | 49 + ui/src/api/generated/models/rss-detail.ts | 33 + .../models/rss-feed-sync-log-list.ts | 81 + .../api/generated/models/rss-feed-sync-log.ts | 85 + ui/src/api/generated/models/test-operation.ts | 49 + ui/src/api/index.ts | 21 + ui/src/components/RssFeedSyncLogListItem.vue | 136 ++ .../components/filter/LinkFilterDropdown.vue | 164 ++ ui/src/components/formkit/LinkFormKit.vue | 53 + ui/src/editor/BlockActionSeparator.vue | 3 + ui/src/editor/RssView.vue | 123 ++ ui/src/editor/index.ts | 1 + ui/src/editor/rss-edited.ts | 201 +++ ui/src/icon/layout-grid.vue | 3 + ui/src/icon/layout-list.vue | 3 + ui/src/index.ts | 41 + ui/src/styles/index.css | 184 ++ ui/src/types/index.ts | 23 + ui/src/utils/date.ts | 63 + ui/src/utils/delete-node.ts | 47 + ui/src/utils/focus.ts | 14 + ui/src/views/CronTab.vue | 161 ++ ui/src/views/Friend.vue | 116 ++ ui/src/views/FriendPostTab.vue | 229 +++ ui/src/views/RssFeedSyncLogTab.vue | 134 ++ ui/tsconfig.json | 14 + ui/uno.config.ts | 7 + 135 files changed, 14156 insertions(+) create mode 100644 .editorconfig create mode 100755 .gitignore create mode 100644 LICENSE create mode 100644 OWNERS create mode 100644 README.md create mode 100644 api-docs/openapi/v3_0/pluginFriendsApi.json create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 package.json create mode 100644 packages/fridends-rss/.eslintrc.json create mode 100644 packages/fridends-rss/.gitignore create mode 100644 packages/fridends-rss/.prettierrc.json create mode 100644 packages/fridends-rss/package.json create mode 100644 packages/fridends-rss/src/friends-rss.ts create mode 100644 packages/fridends-rss/src/index.ts create mode 100644 packages/fridends-rss/src/loading-bar.ts create mode 100644 packages/fridends-rss/src/types/index.ts create mode 100644 packages/fridends-rss/src/vite-env.d.ts create mode 100644 packages/fridends-rss/src/vite/shared-plugin-config.ts create mode 100644 packages/fridends-rss/tsconfig.json create mode 100644 packages/fridends-rss/var.css create mode 100644 packages/fridends-rss/vite.config.dev.ts create mode 100644 packages/fridends-rss/vite.config.lib.ts create mode 100644 packages/fridends-rss/vitest.config.ts create mode 100644 pnpm-workspace.yaml create mode 100644 settings.gradle create mode 100644 src/main/java/la/moony/friends/FriendsHeadProcessor.java create mode 100644 src/main/java/la/moony/friends/FriendsPlugin.java create mode 100644 src/main/java/la/moony/friends/ListedRssSyncLog.java create mode 100644 src/main/java/la/moony/friends/RssFeedSyncEvent.java create mode 100644 src/main/java/la/moony/friends/endpoint/FriendEndpoint.java create mode 100644 src/main/java/la/moony/friends/extension/CronFriendPost.java create mode 100644 src/main/java/la/moony/friends/extension/FriendPost.java create mode 100644 src/main/java/la/moony/friends/extension/Link.java create mode 100644 src/main/java/la/moony/friends/extension/LinkGroup.java create mode 100644 src/main/java/la/moony/friends/extension/RssFeedSyncLog.java create mode 100644 src/main/java/la/moony/friends/finders/FriendFinder.java create mode 100644 src/main/java/la/moony/friends/finders/impl/FriendFinderImpl.java create mode 100644 src/main/java/la/moony/friends/query/FriendPostQuery.java create mode 100644 src/main/java/la/moony/friends/query/RssSyncLogQuery.java create mode 100644 src/main/java/la/moony/friends/reconciler/CronCronFriendPostReconciler.java create mode 100644 src/main/java/la/moony/friends/reconciler/LinkReconciler.java create mode 100644 src/main/java/la/moony/friends/reconciler/RssSyncReconciler.java create mode 100644 src/main/java/la/moony/friends/rest/FriendRouter.java create mode 100644 src/main/java/la/moony/friends/service/RssDetailService.java create mode 100644 src/main/java/la/moony/friends/service/RssFeedSyncLogService.java create mode 100644 src/main/java/la/moony/friends/service/impl/RssDetailServiceImpl.java create mode 100644 src/main/java/la/moony/friends/service/impl/RssFeedSyncLogServiceImpl.java create mode 100644 src/main/java/la/moony/friends/util/CommonUtils.java create mode 100644 src/main/java/la/moony/friends/util/LinkRequest.java create mode 100644 src/main/java/la/moony/friends/util/SortUtils.java create mode 100644 src/main/java/la/moony/friends/vo/FriendPostVo.java create mode 100644 src/main/java/la/moony/friends/vo/LinkGroupVo.java create mode 100644 src/main/java/la/moony/friends/vo/LinkVo.java create mode 100644 src/main/java/la/moony/friends/vo/RssDetail.java create mode 100644 src/main/resources/extensions/annotation-setting.yaml create mode 100644 src/main/resources/extensions/reverseProxy.yaml create mode 100644 src/main/resources/extensions/roleTemplate.yaml create mode 100644 src/main/resources/extensions/settings.yaml create mode 100644 src/main/resources/logo.png create mode 100644 src/main/resources/plugin.yaml create mode 100644 src/main/resources/static/friends-rss.iife.js create mode 100644 src/main/resources/static/var.css create mode 100644 src/main/resources/templates/friends.html create mode 100644 ui/.eslintrc.cjs create mode 100644 ui/env.d.ts create mode 100644 ui/package.json create mode 100644 ui/pnpm-workspace.yaml create mode 100644 ui/rsbuild.config.mjs create mode 100644 ui/src/api/generated/.gitignore create mode 100644 ui/src/api/generated/.npmignore create mode 100644 ui/src/api/generated/.openapi-generator-ignore create mode 100644 ui/src/api/generated/.openapi-generator/FILES create mode 100644 ui/src/api/generated/.openapi-generator/VERSION create mode 100644 ui/src/api/generated/api.ts create mode 100644 ui/src/api/generated/api/api-friend-moony-la-v1alpha1-friend-post-api.ts create mode 100644 ui/src/api/generated/api/cron-friend-post-v1alpha1-api.ts create mode 100644 ui/src/api/generated/api/friend-post-v1alpha1-api.ts create mode 100644 ui/src/api/generated/api/rss-feed-sync-log-v1alpha1-api.ts create mode 100644 ui/src/api/generated/base.ts create mode 100644 ui/src/api/generated/common.ts create mode 100644 ui/src/api/generated/configuration.ts create mode 100644 ui/src/api/generated/git_push.sh create mode 100644 ui/src/api/generated/index.ts create mode 100644 ui/src/api/generated/models/add-operation.ts create mode 100644 ui/src/api/generated/models/channel.ts create mode 100644 ui/src/api/generated/models/copy-operation.ts create mode 100644 ui/src/api/generated/models/cron-friend-post-list.ts create mode 100644 ui/src/api/generated/models/cron-friend-post.ts create mode 100644 ui/src/api/generated/models/cron-spec.ts create mode 100644 ui/src/api/generated/models/cron-status.ts create mode 100644 ui/src/api/generated/models/friend-post-list.ts create mode 100644 ui/src/api/generated/models/friend-post-spec.ts create mode 100644 ui/src/api/generated/models/friend-post.ts create mode 100644 ui/src/api/generated/models/index.ts create mode 100644 ui/src/api/generated/models/item.ts create mode 100644 ui/src/api/generated/models/json-patch-inner.ts create mode 100644 ui/src/api/generated/models/link-spec.ts create mode 100644 ui/src/api/generated/models/link.ts create mode 100644 ui/src/api/generated/models/listed-rss-sync-log-list.ts create mode 100644 ui/src/api/generated/models/listed-rss-sync-log.ts create mode 100644 ui/src/api/generated/models/metadata.ts create mode 100644 ui/src/api/generated/models/move-operation.ts create mode 100644 ui/src/api/generated/models/remove-operation.ts create mode 100644 ui/src/api/generated/models/replace-operation.ts create mode 100644 ui/src/api/generated/models/rss-detail.ts create mode 100644 ui/src/api/generated/models/rss-feed-sync-log-list.ts create mode 100644 ui/src/api/generated/models/rss-feed-sync-log.ts create mode 100644 ui/src/api/generated/models/test-operation.ts create mode 100644 ui/src/api/index.ts create mode 100644 ui/src/components/RssFeedSyncLogListItem.vue create mode 100644 ui/src/components/filter/LinkFilterDropdown.vue create mode 100644 ui/src/components/formkit/LinkFormKit.vue create mode 100644 ui/src/editor/BlockActionSeparator.vue create mode 100644 ui/src/editor/RssView.vue create mode 100644 ui/src/editor/index.ts create mode 100644 ui/src/editor/rss-edited.ts create mode 100644 ui/src/icon/layout-grid.vue create mode 100644 ui/src/icon/layout-list.vue create mode 100644 ui/src/index.ts create mode 100644 ui/src/styles/index.css create mode 100644 ui/src/types/index.ts create mode 100644 ui/src/utils/date.ts create mode 100644 ui/src/utils/delete-node.ts create mode 100644 ui/src/utils/focus.ts create mode 100644 ui/src/views/CronTab.vue create mode 100644 ui/src/views/Friend.vue create mode 100644 ui/src/views/FriendPostTab.vue create mode 100644 ui/src/views/RssFeedSyncLogTab.vue create mode 100644 ui/tsconfig.json create mode 100644 ui/uno.config.ts diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..03538a4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,520 @@ +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = false +max_line_length = 120 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = false +ij_smart_tabs = false +ij_wrap_on_typing = false + +[*.java] +max_line_length = 100 +ij_continuation_indent_size = 4 +ij_java_align_consecutive_assignments = false +ij_java_align_consecutive_variable_declarations = false +ij_java_align_group_field_declarations = false +ij_java_align_multiline_annotation_parameters = false +ij_java_align_multiline_array_initializer_expression = false +ij_java_align_multiline_assignment = false +ij_java_align_multiline_binary_operation = false +ij_java_align_multiline_chained_methods = false +ij_java_align_multiline_extends_list = false +ij_java_align_multiline_for = true +ij_java_align_multiline_method_parentheses = false +ij_java_align_multiline_parameters = false +ij_java_align_multiline_parameters_in_calls = false +ij_java_align_multiline_parenthesized_expression = false +ij_java_align_multiline_records = true +ij_java_align_multiline_resources = true +ij_java_align_multiline_ternary_operation = false +ij_java_align_multiline_text_blocks = false +ij_java_align_multiline_throws_list = false +ij_java_align_subsequent_simple_methods = false +ij_java_align_throws_keyword = false +ij_java_annotation_parameter_wrap = off +ij_java_array_initializer_new_line_after_left_brace = false +ij_java_array_initializer_right_brace_on_new_line = false +ij_java_array_initializer_wrap = normal +ij_java_assert_statement_colon_on_next_line = false +ij_java_assert_statement_wrap = normal +ij_java_assignment_wrap = normal +ij_java_binary_operation_sign_on_next_line = true +ij_java_binary_operation_wrap = normal +ij_java_blank_lines_after_anonymous_class_header = 0 +ij_java_blank_lines_after_class_header = 0 +ij_java_blank_lines_after_imports = 1 +ij_java_blank_lines_after_package = 1 +ij_java_blank_lines_around_class = 1 +ij_java_blank_lines_around_field = 0 +ij_java_blank_lines_around_field_in_interface = 0 +ij_java_blank_lines_around_initializer = 1 +ij_java_blank_lines_around_method = 1 +ij_java_blank_lines_around_method_in_interface = 1 +ij_java_blank_lines_before_class_end = 0 +ij_java_blank_lines_before_imports = 0 +ij_java_blank_lines_before_method_body = 0 +ij_java_blank_lines_before_package = 1 +ij_java_block_brace_style = end_of_line +ij_java_block_comment_at_first_column = false +ij_java_call_parameters_new_line_after_left_paren = false +ij_java_call_parameters_right_paren_on_new_line = false +ij_java_call_parameters_wrap = normal +ij_java_case_statement_on_separate_line = true +ij_java_catch_on_new_line = false +ij_java_class_annotation_wrap = split_into_lines +ij_java_class_brace_style = end_of_line +ij_java_class_count_to_use_import_on_demand = 999 +ij_java_class_names_in_javadoc = 1 +ij_java_do_not_indent_top_level_class_members = false +ij_java_do_not_wrap_after_single_annotation = false +ij_java_do_while_brace_force = always +ij_java_doc_add_blank_line_after_description = true +ij_java_doc_add_blank_line_after_param_comments = false +ij_java_doc_add_blank_line_after_return = false +ij_java_doc_add_p_tag_on_empty_lines = true +ij_java_doc_align_exception_comments = true +ij_java_doc_align_param_comments = false +ij_java_doc_do_not_wrap_if_one_line = false +ij_java_doc_enable_formatting = true +ij_java_doc_enable_leading_asterisks = true +ij_java_doc_indent_on_continuation = false +ij_java_doc_keep_empty_lines = true +ij_java_doc_keep_empty_parameter_tag = true +ij_java_doc_keep_empty_return_tag = true +ij_java_doc_keep_empty_throws_tag = true +ij_java_doc_keep_invalid_tags = true +ij_java_doc_param_description_on_new_line = false +ij_java_doc_preserve_line_breaks = false +ij_java_doc_use_throws_not_exception_tag = true +ij_java_else_on_new_line = false +ij_java_enum_constants_wrap = normal +ij_java_extends_keyword_wrap = normal +ij_java_extends_list_wrap = normal +ij_java_field_annotation_wrap = split_into_lines +ij_java_finally_on_new_line = false +ij_java_for_brace_force = always +ij_java_for_statement_new_line_after_left_paren = false +ij_java_for_statement_right_paren_on_new_line = false +ij_java_for_statement_wrap = normal +ij_java_generate_final_locals = false +ij_java_generate_final_parameters = false +ij_java_if_brace_force = always +ij_java_imports_layout = $*, |, *, |, * +ij_java_indent_case_from_switch = true +ij_java_insert_inner_class_imports = false +ij_java_insert_override_annotation = true +ij_java_keep_blank_lines_before_right_brace = 2 +ij_java_keep_blank_lines_between_package_declaration_and_header = 2 +ij_java_keep_blank_lines_in_code = 2 +ij_java_keep_blank_lines_in_declarations = 2 +ij_java_keep_control_statement_in_one_line = true +ij_java_keep_first_column_comment = true +ij_java_keep_indents_on_empty_lines = false +ij_java_keep_line_breaks = true +ij_java_keep_multiple_expressions_in_one_line = false +ij_java_keep_simple_blocks_in_one_line = false +ij_java_keep_simple_classes_in_one_line = false +ij_java_keep_simple_lambdas_in_one_line = false +ij_java_keep_simple_methods_in_one_line = false +ij_java_label_indent_absolute = false +ij_java_label_indent_size = 0 +ij_java_lambda_brace_style = end_of_line +ij_java_layout_static_imports_separately = true +ij_java_line_comment_add_space = true +ij_java_line_comment_at_first_column = false +ij_java_method_annotation_wrap = split_into_lines +ij_java_method_brace_style = end_of_line +ij_java_method_call_chain_wrap = normal +ij_java_method_parameters_new_line_after_left_paren = false +ij_java_method_parameters_right_paren_on_new_line = false +ij_java_method_parameters_wrap = normal +ij_java_modifier_list_wrap = false +ij_java_names_count_to_use_import_on_demand = 999 +ij_java_new_line_after_lparen_in_record_header = false +ij_java_parameter_annotation_wrap = normal +ij_java_parentheses_expression_new_line_after_left_paren = false +ij_java_parentheses_expression_right_paren_on_new_line = false +ij_java_place_assignment_sign_on_next_line = false +ij_java_prefer_longer_names = true +ij_java_prefer_parameters_wrap = false +ij_java_record_components_wrap = normal +ij_java_repeat_synchronized = true +ij_java_replace_instanceof_and_cast = false +ij_java_replace_null_check = true +ij_java_replace_sum_lambda_with_method_ref = true +ij_java_resource_list_new_line_after_left_paren = false +ij_java_resource_list_right_paren_on_new_line = false +ij_java_resource_list_wrap = normal +ij_java_rparen_on_new_line_in_record_header = false +ij_java_space_after_closing_angle_bracket_in_type_argument = false +ij_java_space_after_colon = true +ij_java_space_after_comma = true +ij_java_space_after_comma_in_type_arguments = true +ij_java_space_after_for_semicolon = true +ij_java_space_after_quest = true +ij_java_space_after_type_cast = true +ij_java_space_before_annotation_array_initializer_left_brace = false +ij_java_space_before_annotation_parameter_list = false +ij_java_space_before_array_initializer_left_brace = true +ij_java_space_before_catch_keyword = true +ij_java_space_before_catch_left_brace = true +ij_java_space_before_catch_parentheses = true +ij_java_space_before_class_left_brace = true +ij_java_space_before_colon = true +ij_java_space_before_colon_in_foreach = true +ij_java_space_before_comma = false +ij_java_space_before_do_left_brace = true +ij_java_space_before_else_keyword = true +ij_java_space_before_else_left_brace = true +ij_java_space_before_finally_keyword = true +ij_java_space_before_finally_left_brace = true +ij_java_space_before_for_left_brace = true +ij_java_space_before_for_parentheses = true +ij_java_space_before_for_semicolon = false +ij_java_space_before_if_left_brace = true +ij_java_space_before_if_parentheses = true +ij_java_space_before_method_call_parentheses = false +ij_java_space_before_method_left_brace = true +ij_java_space_before_method_parentheses = false +ij_java_space_before_opening_angle_bracket_in_type_parameter = false +ij_java_space_before_quest = true +ij_java_space_before_switch_left_brace = true +ij_java_space_before_switch_parentheses = true +ij_java_space_before_synchronized_left_brace = true +ij_java_space_before_synchronized_parentheses = true +ij_java_space_before_try_left_brace = true +ij_java_space_before_try_parentheses = true +ij_java_space_before_type_parameter_list = false +ij_java_space_before_while_keyword = true +ij_java_space_before_while_left_brace = true +ij_java_space_before_while_parentheses = true +ij_java_space_inside_one_line_enum_braces = false +ij_java_space_within_empty_array_initializer_braces = false +ij_java_space_within_empty_method_call_parentheses = false +ij_java_space_within_empty_method_parentheses = false +ij_java_spaces_around_additive_operators = true +ij_java_spaces_around_assignment_operators = true +ij_java_spaces_around_bitwise_operators = true +ij_java_spaces_around_equality_operators = true +ij_java_spaces_around_lambda_arrow = true +ij_java_spaces_around_logical_operators = true +ij_java_spaces_around_method_ref_dbl_colon = false +ij_java_spaces_around_multiplicative_operators = true +ij_java_spaces_around_relational_operators = true +ij_java_spaces_around_shift_operators = true +ij_java_spaces_around_type_bounds_in_type_parameters = true +ij_java_spaces_around_unary_operator = false +ij_java_spaces_within_angle_brackets = false +ij_java_spaces_within_annotation_parentheses = false +ij_java_spaces_within_array_initializer_braces = false +ij_java_spaces_within_braces = false +ij_java_spaces_within_brackets = false +ij_java_spaces_within_cast_parentheses = false +ij_java_spaces_within_catch_parentheses = false +ij_java_spaces_within_for_parentheses = false +ij_java_spaces_within_if_parentheses = false +ij_java_spaces_within_method_call_parentheses = false +ij_java_spaces_within_method_parentheses = false +ij_java_spaces_within_parentheses = false +ij_java_spaces_within_switch_parentheses = false +ij_java_spaces_within_synchronized_parentheses = false +ij_java_spaces_within_try_parentheses = false +ij_java_spaces_within_while_parentheses = false +ij_java_special_else_if_treatment = true +ij_java_subclass_name_suffix = Impl +ij_java_ternary_operation_signs_on_next_line = true +ij_java_ternary_operation_wrap = normal +ij_java_test_name_suffix = Test +ij_java_throws_keyword_wrap = normal +ij_java_throws_list_wrap = normal +ij_java_use_external_annotations = false +ij_java_use_fq_class_names = false +ij_java_use_relative_indents = false +ij_java_use_single_class_imports = true +ij_java_variable_annotation_wrap = normal +ij_java_visibility = public +ij_java_while_brace_force = always +ij_java_while_on_new_line = false +ij_java_wrap_comments = false +ij_java_wrap_first_method_in_call_chain = false +ij_java_wrap_long_lines = true + +[*.properties] +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 + +[.editorconfig] +ij_editorconfig_align_group_field_declarations = false +ij_editorconfig_space_after_colon = false +ij_editorconfig_space_after_comma = true +ij_editorconfig_space_before_colon = false +ij_editorconfig_space_before_comma = false +ij_editorconfig_spaces_around_assignment_operators = true + +[{*.ant, *.fxml, *.jhm, *.jnlp, *.jrxml, *.jspx, *.pom, *.rng, *.tagx, *.tld, *.wsdl, *.xml, *.xsd, *.xsl, *.xslt, *.xul}] +ij_xml_align_attributes = true +ij_xml_align_text = false +ij_xml_attribute_wrap = normal +ij_xml_block_comment_at_first_column = true +ij_xml_keep_blank_lines = 2 +ij_xml_keep_indents_on_empty_lines = false +ij_xml_keep_line_breaks = true +ij_xml_keep_line_breaks_in_text = true +ij_xml_keep_whitespaces = false +ij_xml_keep_whitespaces_around_cdata = preserve +ij_xml_keep_whitespaces_inside_cdata = false +ij_xml_line_comment_at_first_column = true +ij_xml_space_after_tag_name = false +ij_xml_space_around_equals_in_attribute = false +ij_xml_space_inside_empty_tag = false +ij_xml_text_wrap = normal + +[{*.bash, *.sh, *.zsh}] +indent_size = 2 +tab_width = 2 +ij_shell_binary_ops_start_line = false +ij_shell_keep_column_alignment_padding = false +ij_shell_minify_program = false +ij_shell_redirect_followed_by_space = false +ij_shell_switch_cases_indented = false + +[{*.gant, *.gradle, *.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_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_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_if_brace_force = never +ij_groovy_import_annotation_wrap = 2 +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_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_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_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_long_lines = false + +[{*.har, *.json}] +indent_size = 2 +ij_json_keep_blank_lines_in_code = 0 +ij_json_keep_indents_on_empty_lines = false +ij_json_keep_line_breaks = true +ij_json_space_after_colon = true +ij_json_space_after_comma = true +ij_json_space_before_colon = true +ij_json_space_before_comma = false +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_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 + +[{*.yaml, *.yml}] +indent_size = 2 +ij_yaml_keep_indents_on_empty_lines = false +ij_yaml_keep_line_breaks = true + +[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}] +charset = utf-8 +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +end_of_line = lf +max_line_length = 100 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..85f12f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,80 @@ +### Maven +target/ +logs/ +!.mvn/wrapper/maven-wrapper.jar + +### Gradle +.gradle +/build/ +/out/ +!gradle/wrapper/gradle-wrapper.jar +bin/ + +### Node +node_modules/ + +### STS +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA +.idea +*.iws +*.iml +*.ipr +log/ + +### NetBeans +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +### Mac +.DS_Store +*/.DS_Store + +### VSCode +*.project +*.factorypath +.vscode +!.vscode/extensions.json + +### Compiled class file +*.class + +### Log file +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +### BlueJ files +*.ctxt + +### Package Files +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +### Local file +application-local.yml +application-local.yaml +application-local.properties + +/ui/build/ +/workplace/ +/src/main/resources/console/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..20d40b6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/OWNERS b/OWNERS new file mode 100644 index 0000000..eeb425e --- /dev/null +++ b/OWNERS @@ -0,0 +1,10 @@ +reviewers: +- ruibaby +- guqing +- JohnNiang +- wangzhen-fit2cloud + +approvers: +- ruibaby +- guqing +- JohnNiang diff --git a/README.md b/README.md new file mode 100644 index 0000000..165dcc8 --- /dev/null +++ b/README.md @@ -0,0 +1,235 @@ +# plugin-friends +> 1.3.4版本重构朋友圈插件 +> 如果使用了之前版本,请清掉之前的所有数据,不然会有问题 +> `订阅` `订阅文章` 数据全部清除 + +![Snipaste_2024-09-08_23-38-00.png](https://api.minio.yyds.pink/halo-docs/2024/09/Snipaste_2024-09-08_23-38-00.png) + +* 朋友圈管理插件, 支持在 Console 进行管理以及为主题端提供 `/friends` 页面路由。 +* 提供对 RSS 链接的订阅功能,支持获取其订阅内容 + +## 朋友圈页面 +![Snipaste_2024-09-08_22-48-49.png](https://api.minio.yyds.pink/halo-docs/2024/09/Snipaste_2024-09-08_22-48-49.png) + +## 后台页面 +![Snipaste_2024-09-08_22-47-55.png](https://api.minio.yyds.pink/halo-docs/2024/09/Snipaste_2024-09-08_22-47-55.png) + +## 编辑器插入 RSS展示 + +### 编辑器 +* 列表视图 + ![Snipaste_2024-09-08_22-51-10.png](https://api.minio.yyds.pink/halo-docs/2024/09/Snipaste_2024-09-08_22-51-10.png) +* 网格视图 + ![Snipaste_2024-09-08_22-51-17.png](https://api.minio.yyds.pink/halo-docs/2024/09/Snipaste_2024-09-08_22-51-17.png) + +### 页面 +* 列表视图 + ![Snipaste_2024-09-08_22-51-57.png](https://api.minio.yyds.pink/halo-docs/2024/09/Snipaste_2024-09-08_22-51-57.png) +* 网格视图 + ![Snipaste_2024-09-08_22-51-42.png](https://api.minio.yyds.pink/halo-docs/2024/09/Snipaste_2024-09-08_22-51-42.png) + +## 使用方式 +* 在应用市场下载并启用。 +* 启用插件之后会在 Console 的左侧添加一个`朋友圈`的菜单项,点击即可进入`朋友圈`管理页面。 + +## 特性 +* 内置模板,无需主题支持,但也可以通过主题自定义模板。 + +## 📃文档 +https://docs.kunkunyu.com/docs/plugin-friends + +## 主题适配 +目前此插件为主题端提供了 `/friends` 路由,模板为 `friends.html`,也提供了 Finder API,可以将瞬间列表渲染到任何地方。 + +## 模板变量 +路由信息 +* 模板路径:/templates/friends.html +* 访问路径:/friends + +### 变量 +* friends + +### 变量类型 +* UrlContextListResult<[FriendPostVo](#FriendPostVo)> + +#### 示例 +```bash + +``` + +## Finder API + +### listAll() + +#### 描述 +获取全部订阅文章内容。 + +#### 参数 +无 + +#### 返回值 +List<[FriendPostVo](#FriendPostVo)> + +#### 示例 + +```bash +
+
+ +
+ avatar + + + +
+
+
+``` + +### list(page, size) + +#### 描述 +根据分页参数获取订阅文章内容。 + +#### 参数 +* page: int - 分页页码,从 1 开始 +* size: int - 分页条数 + +#### 返回值 +[ListResult](#ListResult) + +#### 示例 + +```bash + +
+
+ +
+ avatar + + + +
+
+
+
+ +
+
+``` + +## 类型定义 +### FriendPostVo +```bash +{ + "metadata": { + "name": "string", // 唯一标识 + "generateName": "string", + "version": 0, + "creationTimestamp": "2024-01-16T16:13:17.925131783Z", // 创建时间 + }, + "apiVersion": "friend.moony.la/v1alpha1", + "kind": "FriendPost", + "spec": { + "authorUrl": "string", // 作者链接 + "author": "string", // 作者名称 + "logo": "string", // 作者logo + "title": "string", // 标题 + "postLink": "string", // 链接 + "description": "string", // 内容 + "pubDate": "date", // 文章发布时间 + } +} +``` + +### ListResult + +```bash +{ + "page": 0, // 当前页码 + "size": 0, // 每页条数 + "total": 0, // 总条数 + "items": "List<#FriendPostVo>", // 订阅文章列表数据 + "first": true, // 是否为第一页 + "last": true, // 是否为最后一页 + "hasNext": true, // 是否有下一页 + "hasPrevious": true, // 是否有上一页 + "totalPages": 0 // 总页数 +} +``` + +### UrlContextListResult + +```bash +{ + "page": 0, // 当前页码 + "size": 0, // 每页条数 + "total": 0, // 总条数 + "items": "List<#FriendPostVo>", // 订阅文章列表数据 + "first": true, // 是否为第一页 + "last": true, // 是否为最后一页 + "hasNext": true, // 是否有下一页 + "hasPrevious": true, // 是否有上一页 + "totalPages": 0, // 总页数 + "prevUrl": "string", // 上一页链接 + "nextUrl": "string" // 下一页链接 +} +``` + +## 开发环境 + +```bash +git clone git@github.com:chengzhongxue/plugin-friends.git + +# 或者当你 fork 之后 + +git clone git@github.com:{your_github_id}/plugin-friends.git +``` + +```bash +cd path/to/plugin-friends +``` + +```bash +# macOS / Linux +./gradlew pnpmInstall + +# Windows +./gradlew.bat pnpmInstall +``` + +```bash +# macOS / Linux +./gradlew haloServer + +# Windows +./gradlew.bat haloServer +``` + +```bash +# macOS / Linux +./gradlew build + +# Windows +./gradlew.bat build +``` \ No newline at end of file diff --git a/api-docs/openapi/v3_0/pluginFriendsApi.json b/api-docs/openapi/v3_0/pluginFriendsApi.json new file mode 100644 index 0000000..b0ca3cb --- /dev/null +++ b/api-docs/openapi/v3_0/pluginFriendsApi.json @@ -0,0 +1,1520 @@ +{ + "openapi" : "3.0.1", + "info" : { + "title" : "Halo", + "version" : "2.20.11" + }, + "servers" : [ { + "url" : "http://localhost:65429", + "description" : "Generated server url" + } ], + "security" : [ { + "basicAuth" : [ ], + "bearerAuth" : [ ] + } ], + "paths" : { + "/apis/api.friend.moony.la/v1alpha1/friendposts" : { + "get" : { + "description" : "List friendPost.", + "operationId" : "ListFriendPosts", + "parameters" : [ { + "description" : "Page number. Default is 0.", + "in" : "query", + "name" : "page", + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "description" : "Size number. Default is 0.", + "in" : "query", + "name" : "size", + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "description" : "Label selector. e.g.: hidden!=true", + "in" : "query", + "name" : "labelSelector", + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + }, { + "description" : "Field selector. e.g.: metadata.name==halo", + "in" : "query", + "name" : "fieldSelector", + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + }, { + "description" : "Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported.", + "in" : "query", + "name" : "sort", + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + }, { + "description" : "CronFriendPost filtered by linkName.", + "in" : "query", + "name" : "linkName", + "schema" : { + "type" : "string" + } + }, { + "description" : "CronFriendPost filtered by keyword.", + "in" : "query", + "name" : "keyword", + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "default" : { + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/FriendPostList" + } + } + }, + "description" : "default response" + } + }, + "tags" : [ "api.friend.moony.la/v1alpha1/FriendPost" ] + } + }, + "/apis/api.friend.moony.la/v1alpha1/friendposts/-/delete" : { + "delete" : { + "description" : "Delete All RssFeedSyncLog.", + "operationId" : "DeleteAllRssFeedSyncLog", + "responses" : { + "default" : { + "content" : { }, + "description" : "default response" + } + }, + "tags" : [ "api.friend.moony.la/v1alpha1/FriendPost" ] + } + }, + "/apis/api.friend.moony.la/v1alpha1/parsingrss" : { + "get" : { + "operationId" : "ParsingRss", + "parameters" : [ { + "in" : "query", + "name" : "rssUrl", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "in" : "query", + "name" : "fetchLimitNumber", + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "default" : { + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/RssDetail" + } + } + }, + "description" : "default response" + } + }, + "tags" : [ "api.friend.moony.la/v1alpha1/FriendPost" ] + } + }, + "/apis/api.friend.moony.la/v1alpha1/rsssynclogs" : { + "get" : { + "description" : "List RssSyncLog.", + "operationId" : "ListRssSyncLogs", + "parameters" : [ { + "description" : "Page number. Default is 0.", + "in" : "query", + "name" : "page", + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "description" : "Size number. Default is 0.", + "in" : "query", + "name" : "size", + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "description" : "Label selector. e.g.: hidden!=true", + "in" : "query", + "name" : "labelSelector", + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + }, { + "description" : "Field selector. e.g.: metadata.name==halo", + "in" : "query", + "name" : "fieldSelector", + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + }, { + "description" : "Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported.", + "in" : "query", + "name" : "sort", + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + }, { + "in" : "query", + "name" : "keyword", + "schema" : { + "type" : "string" + } + }, { + "in" : "query", + "name" : "state", + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "default" : { + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ListedRssSyncLogList" + } + } + }, + "description" : "default response" + } + }, + "tags" : [ "api.friend.moony.la/v1alpha1/FriendPost" ] + } + }, + "/apis/api.friend.moony.la/v1alpha1/syncrssfeed/{name}" : { + "post" : { + "operationId" : "SyncRssFeed", + "parameters" : [ { + "in" : "path", + "name" : "name", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "default" : { + "content" : { }, + "description" : "default response" + } + }, + "tags" : [ "api.friend.moony.la/v1alpha1/FriendPost" ] + } + }, + "/apis/friend.moony.la/v1alpha1/cronfriendposts" : { + "get" : { + "description" : "List CronFriendPost", + "operationId" : "listCronFriendPost", + "parameters" : [ { + "description" : "Page number. Default is 0.", + "in" : "query", + "name" : "page", + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "description" : "Size number. Default is 0.", + "in" : "query", + "name" : "size", + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "description" : "Label selector. e.g.: hidden!=true", + "in" : "query", + "name" : "labelSelector", + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + }, { + "description" : "Field selector. e.g.: metadata.name==halo", + "in" : "query", + "name" : "fieldSelector", + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + }, { + "description" : "Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported.", + "in" : "query", + "name" : "sort", + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + } ], + "responses" : { + "200" : { + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/CronFriendPostList" + } + } + }, + "description" : "Response cronfriendposts" + } + }, + "tags" : [ "CronFriendPostV1alpha1" ] + }, + "post" : { + "description" : "Create CronFriendPost", + "operationId" : "createCronFriendPost", + "requestBody" : { + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/CronFriendPost" + } + } + }, + "description" : "Fresh cronfriendpost" + }, + "responses" : { + "200" : { + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/CronFriendPost" + } + } + }, + "description" : "Response cronfriendposts created just now" + } + }, + "tags" : [ "CronFriendPostV1alpha1" ] + } + }, + "/apis/friend.moony.la/v1alpha1/cronfriendposts/{name}" : { + "delete" : { + "description" : "Delete CronFriendPost", + "operationId" : "deleteCronFriendPost", + "parameters" : [ { + "description" : "Name of cronfriendpost", + "in" : "path", + "name" : "name", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Response cronfriendpost deleted just now" + } + }, + "tags" : [ "CronFriendPostV1alpha1" ] + }, + "get" : { + "description" : "Get CronFriendPost", + "operationId" : "getCronFriendPost", + "parameters" : [ { + "description" : "Name of cronfriendpost", + "in" : "path", + "name" : "name", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/CronFriendPost" + } + } + }, + "description" : "Response single cronfriendpost" + } + }, + "tags" : [ "CronFriendPostV1alpha1" ] + }, + "patch" : { + "description" : "Patch CronFriendPost", + "operationId" : "patchCronFriendPost", + "parameters" : [ { + "description" : "Name of cronfriendpost", + "in" : "path", + "name" : "name", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json-patch+json" : { + "schema" : { + "$ref" : "#/components/schemas/JsonPatch" + } + } + } + }, + "responses" : { + "200" : { + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/CronFriendPost" + } + } + }, + "description" : "Response cronfriendpost patched just now" + } + }, + "tags" : [ "CronFriendPostV1alpha1" ] + }, + "put" : { + "description" : "Update CronFriendPost", + "operationId" : "updateCronFriendPost", + "parameters" : [ { + "description" : "Name of cronfriendpost", + "in" : "path", + "name" : "name", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/CronFriendPost" + } + } + }, + "description" : "Updated cronfriendpost" + }, + "responses" : { + "200" : { + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/CronFriendPost" + } + } + }, + "description" : "Response cronfriendposts updated just now" + } + }, + "tags" : [ "CronFriendPostV1alpha1" ] + } + }, + "/apis/friend.moony.la/v1alpha1/friendposts" : { + "get" : { + "description" : "List FriendPost", + "operationId" : "listFriendPost", + "parameters" : [ { + "description" : "Page number. Default is 0.", + "in" : "query", + "name" : "page", + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "description" : "Size number. Default is 0.", + "in" : "query", + "name" : "size", + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "description" : "Label selector. e.g.: hidden!=true", + "in" : "query", + "name" : "labelSelector", + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + }, { + "description" : "Field selector. e.g.: metadata.name==halo", + "in" : "query", + "name" : "fieldSelector", + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + }, { + "description" : "Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported.", + "in" : "query", + "name" : "sort", + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + } ], + "responses" : { + "200" : { + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/FriendPostList" + } + } + }, + "description" : "Response friendposts" + } + }, + "tags" : [ "FriendPostV1alpha1" ] + }, + "post" : { + "description" : "Create FriendPost", + "operationId" : "createFriendPost", + "requestBody" : { + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/FriendPost" + } + } + }, + "description" : "Fresh friendpost" + }, + "responses" : { + "200" : { + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/FriendPost" + } + } + }, + "description" : "Response friendposts created just now" + } + }, + "tags" : [ "FriendPostV1alpha1" ] + } + }, + "/apis/friend.moony.la/v1alpha1/friendposts/{name}" : { + "delete" : { + "description" : "Delete FriendPost", + "operationId" : "deleteFriendPost", + "parameters" : [ { + "description" : "Name of friendpost", + "in" : "path", + "name" : "name", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Response friendpost deleted just now" + } + }, + "tags" : [ "FriendPostV1alpha1" ] + }, + "get" : { + "description" : "Get FriendPost", + "operationId" : "getFriendPost", + "parameters" : [ { + "description" : "Name of friendpost", + "in" : "path", + "name" : "name", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/FriendPost" + } + } + }, + "description" : "Response single friendpost" + } + }, + "tags" : [ "FriendPostV1alpha1" ] + }, + "patch" : { + "description" : "Patch FriendPost", + "operationId" : "patchFriendPost", + "parameters" : [ { + "description" : "Name of friendpost", + "in" : "path", + "name" : "name", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json-patch+json" : { + "schema" : { + "$ref" : "#/components/schemas/JsonPatch" + } + } + } + }, + "responses" : { + "200" : { + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/FriendPost" + } + } + }, + "description" : "Response friendpost patched just now" + } + }, + "tags" : [ "FriendPostV1alpha1" ] + }, + "put" : { + "description" : "Update FriendPost", + "operationId" : "updateFriendPost", + "parameters" : [ { + "description" : "Name of friendpost", + "in" : "path", + "name" : "name", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/FriendPost" + } + } + }, + "description" : "Updated friendpost" + }, + "responses" : { + "200" : { + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/FriendPost" + } + } + }, + "description" : "Response friendposts updated just now" + } + }, + "tags" : [ "FriendPostV1alpha1" ] + } + }, + "/apis/friend.moony.la/v1alpha1/rssfeedsynclogs" : { + "get" : { + "description" : "List RssFeedSyncLog", + "operationId" : "listRssFeedSyncLog", + "parameters" : [ { + "description" : "Page number. Default is 0.", + "in" : "query", + "name" : "page", + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "description" : "Size number. Default is 0.", + "in" : "query", + "name" : "size", + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "description" : "Label selector. e.g.: hidden!=true", + "in" : "query", + "name" : "labelSelector", + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + }, { + "description" : "Field selector. e.g.: metadata.name==halo", + "in" : "query", + "name" : "fieldSelector", + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + }, { + "description" : "Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported.", + "in" : "query", + "name" : "sort", + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + } ], + "responses" : { + "200" : { + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/RssFeedSyncLogList" + } + } + }, + "description" : "Response rssfeedsynclogs" + } + }, + "tags" : [ "RssFeedSyncLogV1alpha1" ] + }, + "post" : { + "description" : "Create RssFeedSyncLog", + "operationId" : "createRssFeedSyncLog", + "requestBody" : { + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/RssFeedSyncLog" + } + } + }, + "description" : "Fresh rssfeedsynclog" + }, + "responses" : { + "200" : { + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/RssFeedSyncLog" + } + } + }, + "description" : "Response rssfeedsynclogs created just now" + } + }, + "tags" : [ "RssFeedSyncLogV1alpha1" ] + } + }, + "/apis/friend.moony.la/v1alpha1/rssfeedsynclogs/{name}" : { + "delete" : { + "description" : "Delete RssFeedSyncLog", + "operationId" : "deleteRssFeedSyncLog", + "parameters" : [ { + "description" : "Name of rssfeedsynclog", + "in" : "path", + "name" : "name", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Response rssfeedsynclog deleted just now" + } + }, + "tags" : [ "RssFeedSyncLogV1alpha1" ] + }, + "get" : { + "description" : "Get RssFeedSyncLog", + "operationId" : "getRssFeedSyncLog", + "parameters" : [ { + "description" : "Name of rssfeedsynclog", + "in" : "path", + "name" : "name", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/RssFeedSyncLog" + } + } + }, + "description" : "Response single rssfeedsynclog" + } + }, + "tags" : [ "RssFeedSyncLogV1alpha1" ] + }, + "patch" : { + "description" : "Patch RssFeedSyncLog", + "operationId" : "patchRssFeedSyncLog", + "parameters" : [ { + "description" : "Name of rssfeedsynclog", + "in" : "path", + "name" : "name", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json-patch+json" : { + "schema" : { + "$ref" : "#/components/schemas/JsonPatch" + } + } + } + }, + "responses" : { + "200" : { + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/RssFeedSyncLog" + } + } + }, + "description" : "Response rssfeedsynclog patched just now" + } + }, + "tags" : [ "RssFeedSyncLogV1alpha1" ] + }, + "put" : { + "description" : "Update RssFeedSyncLog", + "operationId" : "updateRssFeedSyncLog", + "parameters" : [ { + "description" : "Name of rssfeedsynclog", + "in" : "path", + "name" : "name", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/RssFeedSyncLog" + } + } + }, + "description" : "Updated rssfeedsynclog" + }, + "responses" : { + "200" : { + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/RssFeedSyncLog" + } + } + }, + "description" : "Response rssfeedsynclogs updated just now" + } + }, + "tags" : [ "RssFeedSyncLogV1alpha1" ] + } + } + }, + "components" : { + "schemas" : { + "AddOperation" : { + "required" : [ "op", "path", "value" ], + "type" : "object", + "properties" : { + "op" : { + "type" : "string", + "enum" : [ "add" ] + }, + "path" : { + "pattern" : "^(/[^/~]*(~[01][^/~]*)*)*$", + "type" : "string", + "description" : "A JSON Pointer path pointing to the location to move/copy from.", + "example" : "/a/b/c" + }, + "value" : { + "description" : "Value can be any JSON value" + } + } + }, + "Channel" : { + "type" : "object", + "properties" : { + "description" : { + "type" : "string" + }, + "items" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Item" + } + }, + "link" : { + "type" : "string" + }, + "title" : { + "type" : "string" + } + } + }, + "CopyOperation" : { + "required" : [ "op", "from", "path" ], + "type" : "object", + "properties" : { + "from" : { + "pattern" : "^(/[^/~]*(~[01][^/~]*)*)*$", + "type" : "string", + "description" : "A JSON Pointer path pointing to the location to move/copy from.", + "example" : "/a/b/c" + }, + "op" : { + "type" : "string", + "enum" : [ "copy" ] + }, + "path" : { + "pattern" : "^(/[^/~]*(~[01][^/~]*)*)*$", + "type" : "string", + "description" : "A JSON Pointer path pointing to the location to move/copy from.", + "example" : "/a/b/c" + } + } + }, + "CronFriendPost" : { + "required" : [ "apiVersion", "kind", "metadata" ], + "type" : "object", + "properties" : { + "apiVersion" : { + "type" : "string" + }, + "kind" : { + "type" : "string" + }, + "metadata" : { + "$ref" : "#/components/schemas/Metadata" + }, + "spec" : { + "$ref" : "#/components/schemas/CronSpec" + }, + "status" : { + "$ref" : "#/components/schemas/CronStatus" + } + } + }, + "CronFriendPostList" : { + "required" : [ "first", "hasNext", "hasPrevious", "items", "last", "page", "size", "total", "totalPages" ], + "type" : "object", + "properties" : { + "first" : { + "type" : "boolean", + "description" : "Indicates whether current page is the first page." + }, + "hasNext" : { + "type" : "boolean", + "description" : "Indicates whether current page has previous page." + }, + "hasPrevious" : { + "type" : "boolean", + "description" : "Indicates whether current page has previous page." + }, + "items" : { + "type" : "array", + "description" : "A chunk of items.", + "items" : { + "$ref" : "#/components/schemas/CronFriendPost" + } + }, + "last" : { + "type" : "boolean", + "description" : "Indicates whether current page is the last page." + }, + "page" : { + "type" : "integer", + "description" : "Page number, starts from 1. If not set or equal to 0, it means no pagination.", + "format" : "int32" + }, + "size" : { + "type" : "integer", + "description" : "Size of each page. If not set or equal to 0, it means no pagination.", + "format" : "int32" + }, + "total" : { + "type" : "integer", + "description" : "Total elements.", + "format" : "int64" + }, + "totalPages" : { + "type" : "integer", + "description" : "Indicates total pages.", + "format" : "int64" + } + } + }, + "CronSpec" : { + "type" : "object", + "properties" : { + "cron" : { + "type" : "string" + }, + "disableSyncList" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "successfulRetainLimit" : { + "minimum" : 0, + "type" : "integer", + "format" : "int32" + }, + "suspend" : { + "type" : "boolean" + }, + "timezone" : { + "type" : "string" + } + } + }, + "CronStatus" : { + "type" : "object", + "properties" : { + "lastScheduledTimestamp" : { + "type" : "string", + "format" : "date-time" + }, + "nextSchedulingTimestamp" : { + "type" : "string", + "format" : "date-time" + } + } + }, + "FriendPost" : { + "required" : [ "apiVersion", "kind", "metadata", "spec" ], + "type" : "object", + "properties" : { + "apiVersion" : { + "type" : "string" + }, + "kind" : { + "type" : "string" + }, + "metadata" : { + "$ref" : "#/components/schemas/Metadata" + }, + "spec" : { + "$ref" : "#/components/schemas/FriendPostSpec" + } + } + }, + "FriendPostList" : { + "required" : [ "first", "hasNext", "hasPrevious", "items", "last", "page", "size", "total", "totalPages" ], + "type" : "object", + "properties" : { + "first" : { + "type" : "boolean", + "description" : "Indicates whether current page is the first page." + }, + "hasNext" : { + "type" : "boolean", + "description" : "Indicates whether current page has previous page." + }, + "hasPrevious" : { + "type" : "boolean", + "description" : "Indicates whether current page has previous page." + }, + "items" : { + "type" : "array", + "description" : "A chunk of items.", + "items" : { + "$ref" : "#/components/schemas/FriendPost" + } + }, + "last" : { + "type" : "boolean", + "description" : "Indicates whether current page is the last page." + }, + "page" : { + "type" : "integer", + "description" : "Page number, starts from 1. If not set or equal to 0, it means no pagination.", + "format" : "int32" + }, + "size" : { + "type" : "integer", + "description" : "Size of each page. If not set or equal to 0, it means no pagination.", + "format" : "int32" + }, + "total" : { + "type" : "integer", + "description" : "Total elements.", + "format" : "int64" + }, + "totalPages" : { + "type" : "integer", + "description" : "Indicates total pages.", + "format" : "int64" + } + } + }, + "FriendPostSpec" : { + "type" : "object", + "properties" : { + "author" : { + "type" : "string" + }, + "authorUrl" : { + "type" : "string" + }, + "description" : { + "type" : "string" + }, + "linkName" : { + "type" : "string" + }, + "logo" : { + "type" : "string" + }, + "postLink" : { + "type" : "string" + }, + "pubDate" : { + "type" : "string", + "format" : "date-time" + }, + "title" : { + "type" : "string" + } + } + }, + "Item" : { + "type" : "object", + "properties" : { + "author" : { + "type" : "string" + }, + "description" : { + "type" : "string" + }, + "link" : { + "type" : "string" + }, + "pubDate" : { + "type" : "string", + "format" : "date-time" + }, + "title" : { + "type" : "string" + }, + "uri" : { + "type" : "string" + } + } + }, + "JsonPatch" : { + "minItems" : 1, + "uniqueItems" : true, + "type" : "array", + "description" : "JSON schema for JSONPatch operations", + "items" : { + "oneOf" : [ { + "$ref" : "#/components/schemas/AddOperation" + }, { + "$ref" : "#/components/schemas/ReplaceOperation" + }, { + "$ref" : "#/components/schemas/TestOperation" + }, { + "$ref" : "#/components/schemas/RemoveOperation" + }, { + "$ref" : "#/components/schemas/MoveOperation" + }, { + "$ref" : "#/components/schemas/CopyOperation" + } ] + } + }, + "Link" : { + "required" : [ "apiVersion", "kind", "metadata" ], + "type" : "object", + "properties" : { + "apiVersion" : { + "type" : "string" + }, + "kind" : { + "type" : "string" + }, + "metadata" : { + "$ref" : "#/components/schemas/Metadata" + }, + "spec" : { + "$ref" : "#/components/schemas/LinkSpec" + } + } + }, + "LinkSpec" : { + "required" : [ "displayName", "url" ], + "type" : "object", + "properties" : { + "description" : { + "type" : "string" + }, + "displayName" : { + "type" : "string" + }, + "groupName" : { + "type" : "string" + }, + "logo" : { + "type" : "string" + }, + "priority" : { + "type" : "integer", + "format" : "int32" + }, + "url" : { + "type" : "string" + } + } + }, + "ListedRssSyncLog" : { + "required" : [ "link" ], + "type" : "object", + "properties" : { + "link" : { + "$ref" : "#/components/schemas/Link" + }, + "log" : { + "$ref" : "#/components/schemas/RssFeedSyncLog" + } + }, + "description" : "A chunk of items." + }, + "ListedRssSyncLogList" : { + "required" : [ "first", "hasNext", "hasPrevious", "items", "last", "page", "size", "total", "totalPages" ], + "type" : "object", + "properties" : { + "first" : { + "type" : "boolean", + "description" : "Indicates whether current page is the first page." + }, + "hasNext" : { + "type" : "boolean", + "description" : "Indicates whether current page has previous page." + }, + "hasPrevious" : { + "type" : "boolean", + "description" : "Indicates whether current page has previous page." + }, + "items" : { + "type" : "array", + "description" : "A chunk of items.", + "items" : { + "$ref" : "#/components/schemas/ListedRssSyncLog" + } + }, + "last" : { + "type" : "boolean", + "description" : "Indicates whether current page is the last page." + }, + "page" : { + "type" : "integer", + "description" : "Page number, starts from 1. If not set or equal to 0, it means no pagination.", + "format" : "int32" + }, + "size" : { + "type" : "integer", + "description" : "Size of each page. If not set or equal to 0, it means no pagination.", + "format" : "int32" + }, + "total" : { + "type" : "integer", + "description" : "Total elements.", + "format" : "int64" + }, + "totalPages" : { + "type" : "integer", + "description" : "Indicates total pages.", + "format" : "int64" + } + } + }, + "Metadata" : { + "required" : [ "name" ], + "type" : "object", + "properties" : { + "annotations" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "creationTimestamp" : { + "type" : "string", + "format" : "date-time", + "nullable" : true + }, + "deletionTimestamp" : { + "type" : "string", + "format" : "date-time", + "nullable" : true + }, + "finalizers" : { + "uniqueItems" : true, + "type" : "array", + "nullable" : true, + "items" : { + "type" : "string", + "nullable" : true + } + }, + "generateName" : { + "type" : "string", + "description" : "The name field will be generated automatically according to the given generateName field" + }, + "labels" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "name" : { + "type" : "string", + "description" : "Metadata name" + }, + "version" : { + "type" : "integer", + "format" : "int64", + "nullable" : true + } + } + }, + "MoveOperation" : { + "required" : [ "op", "from", "path" ], + "type" : "object", + "properties" : { + "from" : { + "pattern" : "^(/[^/~]*(~[01][^/~]*)*)*$", + "type" : "string", + "description" : "A JSON Pointer path pointing to the location to move/copy from.", + "example" : "/a/b/c" + }, + "op" : { + "type" : "string", + "enum" : [ "move" ] + }, + "path" : { + "pattern" : "^(/[^/~]*(~[01][^/~]*)*)*$", + "type" : "string", + "description" : "A JSON Pointer path pointing to the location to move/copy from.", + "example" : "/a/b/c" + } + } + }, + "RemoveOperation" : { + "required" : [ "op", "path" ], + "type" : "object", + "properties" : { + "op" : { + "type" : "string", + "enum" : [ "remove" ] + }, + "path" : { + "pattern" : "^(/[^/~]*(~[01][^/~]*)*)*$", + "type" : "string", + "description" : "A JSON Pointer path pointing to the location to move/copy from.", + "example" : "/a/b/c" + } + } + }, + "ReplaceOperation" : { + "required" : [ "op", "path", "value" ], + "type" : "object", + "properties" : { + "op" : { + "type" : "string", + "enum" : [ "replace" ] + }, + "path" : { + "pattern" : "^(/[^/~]*(~[01][^/~]*)*)*$", + "type" : "string", + "description" : "A JSON Pointer path pointing to the location to move/copy from.", + "example" : "/a/b/c" + }, + "value" : { + "description" : "Value can be any JSON value" + } + } + }, + "RssDetail" : { + "type" : "object", + "properties" : { + "channel" : { + "$ref" : "#/components/schemas/Channel" + } + } + }, + "RssFeedSyncLog" : { + "required" : [ "apiVersion", "kind", "linkName", "metadata", "state", "syncTime" ], + "type" : "object", + "properties" : { + "apiVersion" : { + "type" : "string" + }, + "failureMessage" : { + "type" : "string" + }, + "failureReason" : { + "type" : "string" + }, + "kind" : { + "type" : "string" + }, + "linkName" : { + "type" : "string" + }, + "metadata" : { + "$ref" : "#/components/schemas/Metadata" + }, + "state" : { + "type" : "string", + "default" : "nolink", + "enum" : [ "success", "failed", "onwait", "nolink" ] + }, + "syncTime" : { + "type" : "string", + "format" : "date-time" + } + } + }, + "RssFeedSyncLogList" : { + "required" : [ "first", "hasNext", "hasPrevious", "items", "last", "page", "size", "total", "totalPages" ], + "type" : "object", + "properties" : { + "first" : { + "type" : "boolean", + "description" : "Indicates whether current page is the first page." + }, + "hasNext" : { + "type" : "boolean", + "description" : "Indicates whether current page has previous page." + }, + "hasPrevious" : { + "type" : "boolean", + "description" : "Indicates whether current page has previous page." + }, + "items" : { + "type" : "array", + "description" : "A chunk of items.", + "items" : { + "$ref" : "#/components/schemas/RssFeedSyncLog" + } + }, + "last" : { + "type" : "boolean", + "description" : "Indicates whether current page is the last page." + }, + "page" : { + "type" : "integer", + "description" : "Page number, starts from 1. If not set or equal to 0, it means no pagination.", + "format" : "int32" + }, + "size" : { + "type" : "integer", + "description" : "Size of each page. If not set or equal to 0, it means no pagination.", + "format" : "int32" + }, + "total" : { + "type" : "integer", + "description" : "Total elements.", + "format" : "int64" + }, + "totalPages" : { + "type" : "integer", + "description" : "Indicates total pages.", + "format" : "int64" + } + } + }, + "TestOperation" : { + "required" : [ "op", "path", "value" ], + "type" : "object", + "properties" : { + "op" : { + "type" : "string", + "enum" : [ "test" ] + }, + "path" : { + "pattern" : "^(/[^/~]*(~[01][^/~]*)*)*$", + "type" : "string", + "description" : "A JSON Pointer path pointing to the location to move/copy from.", + "example" : "/a/b/c" + }, + "value" : { + "description" : "Value can be any JSON value" + } + } + } + }, + "securitySchemes" : { + "basicAuth" : { + "scheme" : "basic", + "type" : "http" + }, + "bearerAuth" : { + "bearerFormat" : "JWT", + "scheme" : "bearer", + "type" : "http" + } + } + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..6bbb5a3 --- /dev/null +++ b/build.gradle @@ -0,0 +1,102 @@ +plugins { + id 'java' + id "com.github.node-gradle.node" version "7.0.2" + id "io.freefair.lombok" version "8.0.1" + id "run.halo.plugin.devtools" version "0.2.0" +} + +group 'la.moony.friends' +sourceCompatibility = JavaVersion.VERSION_17 + +repositories { + mavenCentral() + maven { url 'https://s01.oss.sonatype.org/content/repositories/releases' } + maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' } + maven { url 'https://repo.spring.io/milestone' } +} + +dependencies { + implementation platform('run.halo.tools.platform:plugin:2.20.0-SNAPSHOT') + compileOnly 'run.halo.app:api' + + testImplementation 'run.halo.app:api' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + implementation 'com.rometools:rome:2.1.0' +} + +test { + useJUnitPlatform() +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = "UTF-8" +} + +node { + nodeProjectDir = file("${project.projectDir}") +} + +tasks.register('buildFrontend', PnpmTask) { + args = ['build'] + dependsOn('installDepsForUI') +} + +tasks.register('installDepsForUI', PnpmTask) { + args = ['install'] +} + +build { + // build frontend before build + tasks.named('compileJava').configure { + dependsOn('buildFrontend') + } +} + +halo { + version = '2.21.0' + superAdminUsername = 'admin' + superAdminPassword = 'admin' + externalUrl = 'http://localhost:8081' + docker { + // windows 需要改为 npipe:////./pipe/docker_engine + // linux、macos 需要改为 unix:///var/run/docker.sock + url = 'unix:///var/run/docker.sock' + apiVersion = '1.48' + } + port = 8081 + + // 开启 debug 断点模式 + debug = true + debugPort = 5011 + + containerName = 'plugin-friends' + // 控制台会挂起,等待点击 Attach debugger 按钮 + suspend = true +} + +haloPlugin { + openApi { + outputDir = file("$rootDir/api-docs/openapi/v3_0") + // 用于定义 API 分组规则 + groupingRules { + // 此名称为 group name,定义后 groupedApiMappings 中的 /v3/api-docs/ 后的名称需要与之相同,要避免与 halo 中已经存在的 group 相同避免生成后出现与插件无关的 API + pluginFriendsApi { + // 分组显示名称 + displayName = 'Extension API for Plugin Friends' + // 分组的 API 规则 + pathsToMatch = ["/apis/friend.moony.la/v1alpha1/**", + "/apis/api.friend.moony.la/v1alpha1/**" + ] + } + } + groupedApiMappings = [ + '/v3/api-docs/pluginFriendsApi': 'pluginFriendsApi.json' + ] + generator { + // 默认配置可缺省 + outputDir = file("${projectDir}/ui/src/api/generated") + } + } + +} + diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..0060beb --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +version=1.3.7 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..ccebba7710deaf9f98673a68957ea02138b60d0a GIT binary patch literal 61608 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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..171d876 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..79a61d4 --- /dev/null +++ b/gradlew @@ -0,0 +1,244 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..6689b85 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/package.json b/package.json new file mode 100644 index 0000000..1743676 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "scripts": { + "build": "pnpm --filter \"./packages/**\" build && pnpm --filter ./ui build" + } +} diff --git a/packages/fridends-rss/.eslintrc.json b/packages/fridends-rss/.eslintrc.json new file mode 100644 index 0000000..789afa3 --- /dev/null +++ b/packages/fridends-rss/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "root": true, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "plugin:prettier/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 2020, + "sourceType": "module" + }, + "plugins": ["@typescript-eslint"], + "env": { + "browser": true + }, + "rules": { + "no-prototype-builtins": "off", + "@typescript-eslint/ban-types": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-explicit-any": "error", + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-unused-vars": [ + "warn", + { + "argsIgnorePattern": "^_" + } + ] + } +} diff --git a/packages/fridends-rss/.gitignore b/packages/fridends-rss/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/packages/fridends-rss/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/packages/fridends-rss/.prettierrc.json b/packages/fridends-rss/.prettierrc.json new file mode 100644 index 0000000..dc7872f --- /dev/null +++ b/packages/fridends-rss/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "trailingComma": "es5", + "tabWidth": 2, + "singleQuote": true, + "bracketSpacing": true, + "arrowParens": "always", + "printWidth": 100 +} diff --git a/packages/fridends-rss/package.json b/packages/fridends-rss/package.json new file mode 100644 index 0000000..7aa4ae1 --- /dev/null +++ b/packages/fridends-rss/package.json @@ -0,0 +1,59 @@ +{ + "name": "@kunkunyu/fridends-rss", + "author": { + "name": "困困鱼", + "url": "https://github.com/chengzhongxue" + }, + "maintainers": [ + { + "name": "困困鱼", + "url": "https://github.com/chengzhongxue" + } + ], + "type": "module", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/friends-rss.js" + }, + "./var.css": "./var.css" + }, + "main": "./dist/friends-rss.js", + "module": "./dist/friends-rss.js", + "types": "./dist/index.d.ts", + "files": [ + "dist", + "var.css" + ], + "scripts": { + "dev": "vite --config vite.config.dev.ts", + "build": "vite build --config vite.config.lib.ts", + "lint": "lit-analyzer && eslint 'src/**/*.ts'", + "prettier": "prettier \"**/*.{cjs,html,js,json,md,ts}\" --ignore-path ./.gitignore --write", + "test": "vitest", + "test:ci": "vitest --run" + }, + "dependencies": { + "lit": "^3.1.3" + }, + "devDependencies": { + "@iconify/json": "^2.2.216", + "@rushstack/eslint-patch": "^1.7.2", + "@types/node": "20", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", + "@unocss/preset-icons": "^0.60.4", + "@unocss/reset": "^0.60.4", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", + "lit-analyzer": "^2.0.3", + "prettier": "^3.3.0", + "rollup-plugin-copy": "^3.5.0", + "typescript": "^5.4.5", + "unocss": "^0.60.4", + "vite": "^5.2.12", + "vite-plugin-dts": "^3.9.1", + "vitest": "^1.6.0" + } +} diff --git a/packages/fridends-rss/src/friends-rss.ts b/packages/fridends-rss/src/friends-rss.ts new file mode 100644 index 0000000..d315a22 --- /dev/null +++ b/packages/fridends-rss/src/friends-rss.ts @@ -0,0 +1,93 @@ +import resetStyles from '@unocss/reset/tailwind.css?inline'; +import { LitElement, css, html, unsafeCSS } from 'lit'; +import { property, state } from 'lit/decorators.js'; +import { RssDetail } from './types'; +import { repeat } from 'lit/directives/repeat.js'; +import './loading-bar'; + +export class FriendsRss extends LitElement { + @property({ type: String }) + src = ''; + + @property({ type: String }) + layout: 'list' | 'grid' = 'list'; + + @state() + rssDetail?: RssDetail; + + @state() + loading = false; + + override connectedCallback() { + super.connectedCallback(); + this.fetchRssDetail(); + } + + async fetchRssDetail() { + // Mock + try { + this.loading = true; + const response = await fetch( + `/apis/api.friend.moony.la/v1alpha1/parsingrss?rssUrl=${this.src}` + ); + + if (!response.ok) { + throw new Error('Failed to fetch site data'); + } + + this.rssDetail = (await response.json()) as RssDetail; + } catch (error) { + console.error(error); + } finally { + this.loading = false; + } + } + + override render() { + if (this.loading) { + return html``; + } + + return html` +
    + ${repeat( + // @ts-ignore + this.rssDetail?.channel.items, + (item) => item.link, + (item) => html` +
  • + +

    + ${item?.title} +

    +
    +

    ${item?.description}

    +
  • + ` + )} +
+ `; + } + + static override styles = [ + unsafeCSS(resetStyles), + css` + :host { + display: inline-block; + width: 100%; + } + @unocss-placeholder; + `, + ]; +} + +customElements.get('friends-rss') || customElements.define('friends-rss', FriendsRss); + +declare global { + interface HTMLElementTagNameMap { + 'friends-rss': FriendsRss; + } +} diff --git a/packages/fridends-rss/src/index.ts b/packages/fridends-rss/src/index.ts new file mode 100644 index 0000000..2b2b81b --- /dev/null +++ b/packages/fridends-rss/src/index.ts @@ -0,0 +1,2 @@ +export * from './friends-rss'; + diff --git a/packages/fridends-rss/src/loading-bar.ts b/packages/fridends-rss/src/loading-bar.ts new file mode 100644 index 0000000..2425f40 --- /dev/null +++ b/packages/fridends-rss/src/loading-bar.ts @@ -0,0 +1,51 @@ +import { LitElement, css, html } from 'lit'; + +export class LoadingBar extends LitElement { + override render() { + return html` + + + + + `; + } + + static override styles = css` + :host { + display: flex; + align-items: center; + justify-content: center; + padding: 1em; + } + svg { + height: 1.25em; + width: 1.25em; + animation: spin 1s linear infinite; + } + + @keyframes spin { + to { + transform: rotate(360deg); + } + } + `; +} + +customElements.get('loading-bar') || customElements.define('loading-bar', LoadingBar); + +declare global { + interface HTMLElementTagNameMap { + 'loading-block': LoadingBar; + } +} diff --git a/packages/fridends-rss/src/types/index.ts b/packages/fridends-rss/src/types/index.ts new file mode 100644 index 0000000..ec20c2b --- /dev/null +++ b/packages/fridends-rss/src/types/index.ts @@ -0,0 +1,28 @@ +export interface RssDetail { + 'channel': Channel; +} + +export interface Channel { + + 'description'?: string; + + 'items': Array; + + 'link': string; + + 'title': string; +} +export interface Item { + + 'author'?: string; + + 'description'?: string; + + 'link': string; + + 'pubDate': string; + + 'title': string; + + 'uri'?: string; +} \ No newline at end of file diff --git a/packages/fridends-rss/src/vite-env.d.ts b/packages/fridends-rss/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/packages/fridends-rss/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/fridends-rss/src/vite/shared-plugin-config.ts b/packages/fridends-rss/src/vite/shared-plugin-config.ts new file mode 100644 index 0000000..4604e17 --- /dev/null +++ b/packages/fridends-rss/src/vite/shared-plugin-config.ts @@ -0,0 +1,14 @@ +import presetIcons from '@unocss/preset-icons'; +import { presetUno } from 'unocss'; +import UnoCSS from 'unocss/vite'; + +export const sharedPluginsConfig = [ + UnoCSS({ + mode: 'shadow-dom', + presets: [presetUno(), presetIcons()], + shortcuts: { + 'text-title': 'text-[var(--friends-rss-title-color,#18181b)]', + 'text-description': 'text-[var(--friends-rss-description-color,#71717a)]', + }, + }), +]; diff --git a/packages/fridends-rss/tsconfig.json b/packages/fridends-rss/tsconfig.json new file mode 100644 index 0000000..6078f67 --- /dev/null +++ b/packages/fridends-rss/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "target": "es2021", + "module": "es2020", + "lib": ["es2021", "DOM", "DOM.Iterable"], + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "inlineSources": true, + "outDir": "./types", + "rootDir": "./src", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitThis": true, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true, + "forceConsistentCasingInFileNames": true, + "noImplicitOverride": true, + "skipLibCheck": true, + "plugins": [ + { + "name": "ts-lit-plugin", + "strict": true + } + ] + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/fridends-rss/var.css b/packages/fridends-rss/var.css new file mode 100644 index 0000000..e09bac7 --- /dev/null +++ b/packages/fridends-rss/var.css @@ -0,0 +1,16 @@ +@media (prefers-color-scheme: dark) { + .color-scheme-auto, + [data-color-scheme='auto'] friends-rss { + color-scheme: dark; + --friends-rss-title-color: #f4f4f5; + --friends-rss-description-color: #a1a1aa; + } +} + +.color-scheme-dark, +.dark, +[data-color-scheme='dark'] friends-rss { + color-scheme: dark; + --friends-rss-title-color: #f4f4f5; + --friends-rss-description-color: #a1a1aa; +} diff --git a/packages/fridends-rss/vite.config.dev.ts b/packages/fridends-rss/vite.config.dev.ts new file mode 100644 index 0000000..446415a --- /dev/null +++ b/packages/fridends-rss/vite.config.dev.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite'; +import { sharedPluginsConfig } from './src/vite/shared-plugin-config'; + +export default defineConfig({ + plugins: [...sharedPluginsConfig], + server: { + proxy: { + '/apis': { + target: 'http://localhost:8090', + changeOrigin: true, + }, + }, + }, +}); diff --git a/packages/fridends-rss/vite.config.lib.ts b/packages/fridends-rss/vite.config.lib.ts new file mode 100644 index 0000000..00b09be --- /dev/null +++ b/packages/fridends-rss/vite.config.lib.ts @@ -0,0 +1,34 @@ +import copy from 'rollup-plugin-copy'; +import { fileURLToPath } from 'url'; +import { defineConfig } from 'vite'; +import dts from 'vite-plugin-dts'; +import { sharedPluginsConfig } from './src/vite/shared-plugin-config'; + +export default defineConfig({ + build: { + lib: { + entry: 'src/index.ts', + name: 'friends-rss', + fileName: 'friends-rss', + formats: ['iife', 'es'], + }, + emptyOutDir: true, + rollupOptions: { + output: { + extend: true, + }, + }, + }, + plugins: [ + ...sharedPluginsConfig, + dts(), + copy({ + targets: [ + { + src: ['./dist/friends-rss.iife.js', './var.css'], + dest: fileURLToPath(new URL('../../src/main/resources/static', import.meta.url)), + }, + ], + }), + ], +}); diff --git a/packages/fridends-rss/vitest.config.ts b/packages/fridends-rss/vitest.config.ts new file mode 100644 index 0000000..6ec74ee --- /dev/null +++ b/packages/fridends-rss/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['src/**/*.test.ts'], + }, +}); diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..9453fe0 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - 'ui' + - 'packages/**' diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..a41e22d --- /dev/null +++ b/settings.gradle @@ -0,0 +1,7 @@ +pluginManagement { + repositories { + gradlePluginPortal() + } +} +rootProject.name = 'plugin-friends' + diff --git a/src/main/java/la/moony/friends/FriendsHeadProcessor.java b/src/main/java/la/moony/friends/FriendsHeadProcessor.java new file mode 100644 index 0000000..ec38943 --- /dev/null +++ b/src/main/java/la/moony/friends/FriendsHeadProcessor.java @@ -0,0 +1,45 @@ +package la.moony.friends; + +import lombok.RequiredArgsConstructor; +import org.pf4j.PluginWrapper; +import org.springframework.stereotype.Component; +import org.springframework.util.PropertyPlaceholderHelper; +import org.thymeleaf.context.ITemplateContext; +import org.thymeleaf.model.IModel; +import org.thymeleaf.model.IModelFactory; +import org.thymeleaf.processor.element.IElementModelStructureHandler; +import reactor.core.publisher.Mono; +import run.halo.app.theme.dialect.TemplateHeadProcessor; + +import java.util.Properties; + +@Component +@RequiredArgsConstructor +public class FriendsHeadProcessor implements TemplateHeadProcessor { + + static final PropertyPlaceholderHelper PROPERTY_PLACEHOLDER_HELPER = + new PropertyPlaceholderHelper("${", "}"); + + private final PluginWrapper pluginWrapper; + + @Override + public Mono process(ITemplateContext context, IModel model, + IElementModelStructureHandler structureHandler) { + final IModelFactory modelFactory = context.getModelFactory(); + model.add(modelFactory.createText(friendsRssComponentScript())); + return Mono.empty(); + } + + private String friendsRssComponentScript() { + + final Properties properties = new Properties(); + properties.setProperty("version", pluginWrapper.getDescriptor().getVersion()); + + return PROPERTY_PLACEHOLDER_HELPER.replacePlaceholders(""" + + + + + """, properties); + } +} diff --git a/src/main/java/la/moony/friends/FriendsPlugin.java b/src/main/java/la/moony/friends/FriendsPlugin.java new file mode 100644 index 0000000..98c19ae --- /dev/null +++ b/src/main/java/la/moony/friends/FriendsPlugin.java @@ -0,0 +1,79 @@ +package la.moony.friends; + +import la.moony.friends.extension.CronFriendPost; +import la.moony.friends.extension.FriendPost; +import la.moony.friends.extension.RssFeedSyncLog; +import org.springframework.stereotype.Component; +import run.halo.app.extension.Scheme; +import run.halo.app.extension.SchemeManager; +import run.halo.app.extension.index.IndexSpec; +import run.halo.app.plugin.BasePlugin; +import run.halo.app.plugin.PluginContext; + +import static run.halo.app.extension.index.IndexAttributeFactory.simpleAttribute; + +@Component +public class FriendsPlugin extends BasePlugin { + + private final SchemeManager schemeManager; + + public FriendsPlugin(PluginContext pluginContext, SchemeManager schemeManager) { + super(pluginContext); + this.schemeManager = schemeManager; + } + + @Override + public void start() { + schemeManager.register(FriendPost.class, indexSpecs -> { + indexSpecs.add(new IndexSpec() + .setName("spec.author") + .setIndexFunc( + simpleAttribute(FriendPost.class, friendPost -> friendPost.getSpec().getAuthor()))); + indexSpecs.add(new IndexSpec() + .setName("spec.title") + .setIndexFunc( + simpleAttribute(FriendPost.class, friendPost -> friendPost.getSpec().getTitle()))); + indexSpecs.add(new IndexSpec() + .setName("spec.description") + .setIndexFunc( + simpleAttribute(FriendPost.class, friendPost -> friendPost.getSpec().getDescription()))); + indexSpecs.add(new IndexSpec() + .setName("spec.postLink") + .setIndexFunc( + simpleAttribute(FriendPost.class, friendPost -> friendPost.getSpec().getPostLink()))); + indexSpecs.add(new IndexSpec() + .setName("spec.authorUrl") + .setIndexFunc( + simpleAttribute(FriendPost.class, friendPost -> friendPost.getSpec().getAuthorUrl()))); + indexSpecs.add(new IndexSpec() + .setName("spec.linkName") + .setIndexFunc( + simpleAttribute(FriendPost.class, friendPost -> friendPost.getSpec().getLinkName()))); + indexSpecs.add(new IndexSpec() + .setName("spec.pubDate") + .setIndexFunc(simpleAttribute(FriendPost.class, friendPost -> { + var pubDate = friendPost.getSpec().getPubDate(); + return pubDate == null ? null : pubDate.toString(); + })) + ); + }); + schemeManager.register(CronFriendPost.class); + schemeManager.register(RssFeedSyncLog.class, indexSpecs -> { + indexSpecs.add(new IndexSpec() + .setName("state") + .setIndexFunc(simpleAttribute(RssFeedSyncLog.class, rssFeedSyncLog -> { + var state = rssFeedSyncLog.getState(); + return state == null ? null : state.name(); + }))); + }); + + } + + @Override + public void stop() { + schemeManager.unregister(Scheme.buildFromType(FriendPost.class)); + schemeManager.unregister(Scheme.buildFromType(CronFriendPost.class)); + schemeManager.unregister(Scheme.buildFromType(RssFeedSyncLog.class)); + + } +} diff --git a/src/main/java/la/moony/friends/ListedRssSyncLog.java b/src/main/java/la/moony/friends/ListedRssSyncLog.java new file mode 100644 index 0000000..571e3fa --- /dev/null +++ b/src/main/java/la/moony/friends/ListedRssSyncLog.java @@ -0,0 +1,19 @@ +package la.moony.friends; + +import io.swagger.v3.oas.annotations.media.Schema; +import la.moony.friends.extension.Link; +import la.moony.friends.extension.RssFeedSyncLog; +import lombok.Data; +import lombok.experimental.Accessors; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; + +@Data +@Accessors(chain = true) +public class ListedRssSyncLog { + + @Schema(requiredMode = REQUIRED) + private Link link; + + private RssFeedSyncLog log; +} diff --git a/src/main/java/la/moony/friends/RssFeedSyncEvent.java b/src/main/java/la/moony/friends/RssFeedSyncEvent.java new file mode 100644 index 0000000..fb7d716 --- /dev/null +++ b/src/main/java/la/moony/friends/RssFeedSyncEvent.java @@ -0,0 +1,20 @@ +package la.moony.friends; + +import la.moony.friends.extension.Link; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; +import java.util.List; + +@Getter +public class RssFeedSyncEvent extends ApplicationEvent { + private final Link link; + private final int sum; + private final List disableSyncList; + + public RssFeedSyncEvent(Object source, Link link, int sum, List disableSyncList) { + super(source); + this.link = link; + this.sum = sum; + this.disableSyncList = disableSyncList; + } +} diff --git a/src/main/java/la/moony/friends/endpoint/FriendEndpoint.java b/src/main/java/la/moony/friends/endpoint/FriendEndpoint.java new file mode 100644 index 0000000..f37f3d5 --- /dev/null +++ b/src/main/java/la/moony/friends/endpoint/FriendEndpoint.java @@ -0,0 +1,170 @@ +package la.moony.friends.endpoint; + +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import la.moony.friends.ListedRssSyncLog; +import la.moony.friends.extension.FriendPost; +import la.moony.friends.extension.RssFeedSyncLog; +import la.moony.friends.query.FriendPostQuery; +import la.moony.friends.query.RssSyncLogQuery; +import la.moony.friends.service.RssDetailService; +import la.moony.friends.service.RssFeedSyncLogService; +import la.moony.friends.vo.RssDetail; +import org.springdoc.webflux.core.fn.SpringdocRouteBuilder; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; +import reactor.core.publisher.Mono; +import run.halo.app.core.extension.endpoint.CustomEndpoint; +import run.halo.app.extension.GroupVersion; +import run.halo.app.extension.ListOptions; +import run.halo.app.extension.ListResult; +import run.halo.app.extension.ReactiveExtensionClient; +import run.halo.app.extension.router.selector.FieldSelector; + +import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder; +import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder; +import static run.halo.app.extension.index.query.QueryFactory.isNull; + +@Component +public class FriendEndpoint implements CustomEndpoint { + + private final String tag = "api.friend.moony.la/v1alpha1/FriendPost"; + + + private final ReactiveExtensionClient client; + + private final RssDetailService rssDetailService; + + private final RssFeedSyncLogService rssFeedSyncLogService; + + + public FriendEndpoint(ReactiveExtensionClient client, RssDetailService rssDetailService, + RssFeedSyncLogService rssFeedSyncLogService) { + this.client = client; + this.rssDetailService = rssDetailService; + this.rssFeedSyncLogService = rssFeedSyncLogService; + } + + @Override + public RouterFunction endpoint() { + return SpringdocRouteBuilder.route() + .GET("friendposts", this::listFriendPost, builder -> { + builder.operationId("ListFriendPosts") + .description("List friendPost.") + .tag(tag) + .response( + responseBuilder() + .implementation(ListResult.generateGenericClass(FriendPost.class)) + ); + FriendPostQuery.buildParameters(builder); + }) + .DELETE("friendposts/-/delete", this::deleteAllRssFeedSyncLog, builder -> { + builder.operationId("DeleteAllRssFeedSyncLog") + .description("Delete All RssFeedSyncLog.") + .tag(tag) + .response(responseBuilder() + .implementation(Void.class)); + }) + .GET("rsssynclogs", this::listRssSyncLog, builder -> { + builder.operationId("ListRssSyncLogs") + .description("List RssSyncLog.") + .tag(tag) + .response( + responseBuilder() + .implementation(ListResult.generateGenericClass(ListedRssSyncLog.class)) + ); + RssSyncLogQuery.buildParameters(builder); + }) + .GET("parsingrss", this::parsingRss, builder -> { + builder.operationId("ParsingRss") + .tag(tag) + .parameter(parameterBuilder() + .name("rssUrl") + .in(ParameterIn.QUERY) + .required(true) + .implementation(String.class) + ) + .parameter(parameterBuilder() + .name("fetchLimitNumber") + .in(ParameterIn.QUERY) + .required(false) + .implementation(String.class) + ) + .response( + responseBuilder() + .implementation(RssDetail.class) + ); + }) + .POST("syncrssfeed/{name}", this::syncRssFeed, + builder -> builder.operationId("SyncRssFeed") + .tag(tag) + .parameter(parameterBuilder().name("name") + .in(ParameterIn.PATH) + .required(true) + .implementation(String.class)) + .response(responseBuilder() + .implementation(Void.class)) + ) + .build(); + } + + + Mono listFriendPost(ServerRequest request) { + FriendPostQuery friendPostQuery = new FriendPostQuery(request); + return client.listBy(FriendPost.class, friendPostQuery.toListOptions(),friendPostQuery.toPageRequest()) + .flatMap(friendPosts -> ServerResponse.ok().bodyValue(friendPosts)); + } + + Mono deleteAllRssFeedSyncLog(ServerRequest request) { + return deleteAllRssFeedSyncLog() + .then(deleteAllFriendPost()) + .then(ServerResponse.ok().build()); + } + + private Mono deleteAllRssFeedSyncLog() { + ListOptions listOptions = new ListOptions().setFieldSelector( + FieldSelector.of(isNull("metadata.deletionTimestamp"))); + + return client.listAll(RssFeedSyncLog.class, listOptions, Sort.unsorted()) + .flatMap(rssFeedSyncLog -> client.delete(rssFeedSyncLog)) + .then(); + } + + private Mono deleteAllFriendPost() { + ListOptions listOptions = new ListOptions().setFieldSelector( + FieldSelector.of(isNull("metadata.deletionTimestamp"))); + + return client.listAll(FriendPost.class, listOptions, Sort.unsorted()) + .flatMap(friendPost -> client.delete(friendPost)) + .then(); + } + + Mono listRssSyncLog(ServerRequest request) { + RssSyncLogQuery rssSyncLogQuery = new RssSyncLogQuery(request); + return rssFeedSyncLogService.listRssSyncLog(rssSyncLogQuery) + .flatMap(listRssSyncLogs -> ServerResponse.ok().bodyValue(listRssSyncLogs)); + } + + Mono parsingRss(ServerRequest request) { + final var rssUrl = request.queryParam("rssUrl").orElseThrow(); + final var fetchLimitNumber = request.queryParam("fetchLimitNumber") + .map(Integer::parseInt) + .orElse(5); + return rssDetailService.fetchRssDetail(rssUrl,fetchLimitNumber) + .flatMap(rssDetail -> ServerResponse.ok().bodyValue(rssDetail)); + } + + private Mono syncRssFeed(ServerRequest request) { + var name = request.pathVariable("name"); + + return rssFeedSyncLogService.publishRssFeedSyncEvent(name) + .flatMap(rssFeedSyncEvent -> ServerResponse.ok().build()); + } + + @Override + public GroupVersion groupVersion() { + return GroupVersion.parseAPIVersion("api.friend.moony.la/v1alpha1"); + } +} diff --git a/src/main/java/la/moony/friends/extension/CronFriendPost.java b/src/main/java/la/moony/friends/extension/CronFriendPost.java new file mode 100644 index 0000000..621f745 --- /dev/null +++ b/src/main/java/la/moony/friends/extension/CronFriendPost.java @@ -0,0 +1,56 @@ +package la.moony.friends.extension; + + +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.Instant; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import run.halo.app.extension.AbstractExtension; +import run.halo.app.extension.GVK; + + +@Data +@ToString +@EqualsAndHashCode(callSuper = true) +@GVK( + group = "friend.moony.la", + version = "v1alpha1", + kind = "CronFriendPost", + singular = "cronfriendpost", + plural = "cronfriendposts" +) +public class CronFriendPost extends AbstractExtension { + + + private CronSpec spec = new CronSpec(); + + private CronStatus status = new CronStatus(); + + @Data + public static class CronSpec { + private String cron; + private String timezone; + private boolean suspend; + + @Schema( + minimum = "0" + ) + private int successfulRetainLimit; + + private List disableSyncList; + + } + + @Data + public static class CronStatus { + private Instant lastScheduledTimestamp; + private Instant nextSchedulingTimestamp; + + } + + + + +} diff --git a/src/main/java/la/moony/friends/extension/FriendPost.java b/src/main/java/la/moony/friends/extension/FriendPost.java new file mode 100644 index 0000000..a8f4800 --- /dev/null +++ b/src/main/java/la/moony/friends/extension/FriendPost.java @@ -0,0 +1,43 @@ +package la.moony.friends.extension; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import run.halo.app.extension.AbstractExtension; +import run.halo.app.extension.GVK; + +import java.time.Instant; + +@Data +@ToString +@EqualsAndHashCode(callSuper = true) +@GVK(kind = "FriendPost", group = "friend.moony.la", + version = "v1alpha1", singular = "friendpost", plural = "friendposts") +public class FriendPost extends AbstractExtension { + + + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + private FriendPostSpec spec; + + @Data + public static class FriendPostSpec { + + private String author; + + private String authorUrl; + + private String logo; + + private String title; + + private String postLink; + + private String description; + + private Instant pubDate; + + private String linkName; + } + +} diff --git a/src/main/java/la/moony/friends/extension/Link.java b/src/main/java/la/moony/friends/extension/Link.java new file mode 100644 index 0000000..d1b4c06 --- /dev/null +++ b/src/main/java/la/moony/friends/extension/Link.java @@ -0,0 +1,32 @@ +package la.moony.friends.extension; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import run.halo.app.extension.AbstractExtension; +import run.halo.app.extension.GVK; +@Data +@EqualsAndHashCode(callSuper = true) +@GVK(group = "core.halo.run", version = "v1alpha1", + kind = "Link", plural = "links", singular = "link") +public class Link extends AbstractExtension { + + private LinkSpec spec; + + @Data + public static class LinkSpec { + @Schema(required = true) + private String url; + + @Schema(required = true) + private String displayName; + + private String logo; + + private String description; + + private Integer priority; + + private String groupName; + } +} \ No newline at end of file diff --git a/src/main/java/la/moony/friends/extension/LinkGroup.java b/src/main/java/la/moony/friends/extension/LinkGroup.java new file mode 100644 index 0000000..9de5f1d --- /dev/null +++ b/src/main/java/la/moony/friends/extension/LinkGroup.java @@ -0,0 +1,31 @@ +package la.moony.friends.extension; + +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.LinkedHashSet; +import lombok.Data; +import lombok.EqualsAndHashCode; +import run.halo.app.extension.AbstractExtension; +import run.halo.app.extension.GVK; + + +@Data +@EqualsAndHashCode(callSuper = true) +@GVK(group = "core.halo.run", version = "v1alpha1", kind = "LinkGroup", plural = "linkgroups", singular = "linkgroup") +public class LinkGroup extends AbstractExtension { + + private LinkGroupSpec spec; + + @Data + public static class LinkGroupSpec { + @Schema(required = true) + private String displayName; + + private Integer priority; + + @Deprecated(since = "1.2.0", forRemoval = true) + @Schema(description = "Names of links below this group.") + @ArraySchema(arraySchema = @Schema(description = "Links of this group."), schema = @Schema(description = "Name of link.")) + private LinkedHashSet links; + } +} diff --git a/src/main/java/la/moony/friends/extension/RssFeedSyncLog.java b/src/main/java/la/moony/friends/extension/RssFeedSyncLog.java new file mode 100644 index 0000000..1d954bb --- /dev/null +++ b/src/main/java/la/moony/friends/extension/RssFeedSyncLog.java @@ -0,0 +1,41 @@ +package la.moony.friends.extension; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import run.halo.app.extension.AbstractExtension; +import run.halo.app.extension.GVK; +import java.time.Instant; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; + +@Data +@EqualsAndHashCode(callSuper = true) +@GVK(kind = "RssFeedSyncLog", group = "friend.moony.la", + version = "v1alpha1", singular = "rssfeedsynclog", plural = "rssfeedsynclogs") +public class RssFeedSyncLog extends AbstractExtension { + + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + private String linkName; + + @Schema(requiredMode = REQUIRED,defaultValue = "nolink") + private RssFeedSyncLogState state; + + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + private Instant syncTime; + + private String failureReason; + + private String failureMessage; + + public enum RssFeedSyncLogState { + success, + failed, + onwait, + nolink; + + private RssFeedSyncLogState() { + } + + } +} \ No newline at end of file diff --git a/src/main/java/la/moony/friends/finders/FriendFinder.java b/src/main/java/la/moony/friends/finders/FriendFinder.java new file mode 100644 index 0000000..ce77557 --- /dev/null +++ b/src/main/java/la/moony/friends/finders/FriendFinder.java @@ -0,0 +1,28 @@ +package la.moony.friends.finders; + +import la.moony.friends.finders.impl.FriendFinderImpl; +import la.moony.friends.vo.FriendPostVo; +import la.moony.friends.vo.LinkGroupVo; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import run.halo.app.extension.ListResult; +import java.util.Map; +import org.springframework.lang.Nullable; + +public interface FriendFinder { + + Flux listAll(); + + Mono> list(@Nullable Integer page, @Nullable Integer size); + + /** + * Lists friendPosts by query params. + * + * @param params query params see {@link FriendFinderImpl.FriendPostQuery} + */ + Mono> list(Map params); + + Mono getByName(String friendPostName); + + Flux linkGroupBy(); +} diff --git a/src/main/java/la/moony/friends/finders/impl/FriendFinderImpl.java b/src/main/java/la/moony/friends/finders/impl/FriendFinderImpl.java new file mode 100644 index 0000000..096a26a --- /dev/null +++ b/src/main/java/la/moony/friends/finders/impl/FriendFinderImpl.java @@ -0,0 +1,240 @@ +package la.moony.friends.finders.impl; + +import jakarta.annotation.Nonnull; +import la.moony.friends.extension.FriendPost; +import la.moony.friends.extension.Link; +import la.moony.friends.extension.LinkGroup; +import la.moony.friends.extension.RssFeedSyncLog; +import la.moony.friends.finders.FriendFinder; +import la.moony.friends.util.SortUtils; +import la.moony.friends.vo.FriendPostVo; +import la.moony.friends.vo.LinkGroupVo; +import la.moony.friends.vo.LinkVo; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.domain.Sort; +import org.springframework.util.Assert; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import org.springframework.lang.NonNull; +import run.halo.app.extension.*; +import run.halo.app.extension.router.selector.FieldSelector; +import run.halo.app.infra.utils.JsonUtils; +import run.halo.app.theme.finders.Finder; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import static org.springframework.data.domain.Sort.Order.asc; +import static run.halo.app.extension.index.query.QueryFactory.*; + + +@Finder("friendFinder") +@RequiredArgsConstructor +public class FriendFinderImpl implements FriendFinder { + + static final String UNGROUPED_NAME = "ungrouped"; + + private final ReactiveExtensionClient client; + + + + @Override + public Flux listAll() { + var listOptions = new ListOptions(); + var query = all(); + listOptions.setFieldSelector(FieldSelector.of(query)); + return client.listAll(FriendPost.class, listOptions, defaultSort()) + .flatMap(this::getFriendPostVo); + } + + @Override + public Mono> list(Integer page, Integer size) { + return pageFriendPost(new ListOptions(), getPageRequest(page, size)); + } + + private PageRequestImpl getPageRequest(Integer page, Integer size) { + return PageRequestImpl.of(pageNullSafe(page), sizeNullSafe(size), defaultSort()); + } + + @Override + public Mono> list(Map params) { + var query = Optional.ofNullable(params) + .map(map -> JsonUtils.mapToObject(map, FriendPostQuery.class)) + .orElseGet(FriendPostQuery::new); + return pageFriendPost(query.toListOptions(), query.toPageRequest()); + } + + + @Override + public Mono getByName(String friendPostName) { + return client.fetch(FriendPost.class, friendPostName) + .map(FriendPostVo::from); + } + + @Override + public Flux linkGroupBy() { + return client.listAll(LinkGroup.class, new ListOptions(), defaultGroupSort()) + .map(LinkGroupVo::from) + .concatMap(group -> linkListBy(group.getMetadata().getName()) + .collectList() + .map(group::withLinks) + .defaultIfEmpty(group) + ) + .mergeWith(Mono.defer(() -> linkListBy(UNGROUPED_NAME) + .collectList() + // do not return ungrouped group if no links + .filter(links -> !links.isEmpty()) + .flatMap(links -> ungrouped() + .map(LinkGroupVo::from) + .map(group -> group.withLinks(links)) + ) + )); + } + + private Mono> pageFriendPost(ListOptions queryOptions, PageRequest page){ + var listOptions = new ListOptions(); + var query = all(); + listOptions.setFieldSelector(FieldSelector.of(query)); + var fieldSelector = queryOptions.getFieldSelector(); + if (fieldSelector != null) { + listOptions.setFieldSelector(listOptions.getFieldSelector() + .andQuery(fieldSelector.query())); + } + + return client.listBy(FriendPost.class, listOptions, page) + .flatMap(list -> Flux.fromStream(list.get()) + .concatMap(this::getFriendPostVo) + .collectList() + .map(friendPostVos -> new ListResult<>(list.getPage(), list.getSize(), + list.getTotal(), friendPostVos) + ) + ) + .defaultIfEmpty(ListResult.emptyResult()); + + } + + public Flux linkListBy(String groupName) { + var listOptions = new ListOptions(); + var query = isNull("metadata.deletionTimestamp"); + if (UNGROUPED_NAME.equals(groupName)) { + query = and(query, isNull("spec.groupName")); + } else { + query = and(query, equal("spec.groupName", groupName)); + } + listOptions.setFieldSelector(FieldSelector.of(query)); + return client.listAll(Link.class, listOptions, defaultLinkSort()) + .flatMapSequential(this::convertToListedVo) + .collectList() + .map(links -> { + // 排序逻辑 + return links.stream() + .sorted(Comparator.comparing(link -> { + List friendPosts = link.getFriendPosts(); + if (friendPosts != null && !friendPosts.isEmpty()) { + return friendPosts.get(0).getSpec().getPubDate(); + } + return null; + }, Comparator.nullsLast(Comparator.reverseOrder()) + )).collect(Collectors.toList()); + }) + .flatMapMany(Flux::fromIterable); + } + + + public Mono convertToListedVo(@NonNull Link link) { + Assert.notNull(link, "Link must not be null"); + LinkVo linkVo = LinkVo.from(link); + linkVo.setFriendPosts(List.of()); + return Mono.just(linkVo) + .flatMap(lp -> friendPostListBy(lp.getMetadata().getName()) + .doOnNext(lp::setFriendPosts) + .thenReturn(lp) + ).flatMap(p -> { + String linkName = p.getMetadata().getName(); + return client.fetch(RssFeedSyncLog.class,"sync-log-" + linkName) + .doOnNext(p::setRssFeedSyncLog) + .thenReturn(p); + }) + .defaultIfEmpty(linkVo); + } + + public Mono> friendPostListBy(String linkName) { + var listOptions = new ListOptions(); + var query = isNull("metadata.deletionTimestamp"); + query = and(query, equal("spec.linkName", linkName)); + listOptions.setFieldSelector(FieldSelector.of(query)); + var pageRequest = PageRequestImpl.of(pageNullSafe(1), sizeNullSafe(2), defaultSort()); + return client.listBy(FriendPost.class, listOptions, pageRequest) + .flatMap(friendPosts -> Flux.fromStream(friendPosts.get()) + .map(FriendPostVo::from).collectList()); + } + + Mono ungrouped() { + LinkGroup linkGroup = new LinkGroup(); + linkGroup.setMetadata(new Metadata()); + linkGroup.getMetadata().setName(UNGROUPED_NAME); + linkGroup.setSpec(new LinkGroup.LinkGroupSpec()); + linkGroup.getSpec().setDisplayName(""); + linkGroup.getSpec().setPriority(0); + return Mono.just(linkGroup); + } + + static Sort defaultGroupSort() { + return Sort.by(asc("spec.priority"), + asc("metadata.creationTimestamp"), + asc("metadata.name") + ); + } + + static Sort defaultLinkSort() { + return Sort.by(asc("spec.priority"), + asc("metadata.creationTimestamp"), + asc("metadata.name") + ); + } + + static Sort defaultSort() { + return Sort.by("spec.pubDate").descending(); + } + + private Mono getFriendPostVo(@Nonnull FriendPost friendPost) { + FriendPostVo friendPostVo = FriendPostVo.from(friendPost); + return Mono.just(friendPostVo); + } + + static int pageNullSafe(Integer page) { + return ObjectUtils.defaultIfNull(page, 1); + } + + static int sizeNullSafe(Integer size) { + return ObjectUtils.defaultIfNull(size, 10); + } + + @Data + public static class FriendPostQuery { + private Integer page; + private Integer size; + private String linkName; + private List sort; + + public ListOptions toListOptions() { + var builder = ListOptions.builder(); + if (StringUtils.isNotBlank(linkName)) { + builder.andQuery(equal("spec.linkName", linkName)); + } + return builder.build(); + } + + public PageRequest toPageRequest() { + return PageRequestImpl.of(pageNullSafe(getPage()), + sizeNullSafe(getSize()), SortUtils.resolve(sort).and(defaultSort())); + } + } + + +} diff --git a/src/main/java/la/moony/friends/query/FriendPostQuery.java b/src/main/java/la/moony/friends/query/FriendPostQuery.java new file mode 100644 index 0000000..288d0e1 --- /dev/null +++ b/src/main/java/la/moony/friends/query/FriendPostQuery.java @@ -0,0 +1,107 @@ +package la.moony.friends.query; + +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import la.moony.friends.extension.FriendPost; +import org.apache.commons.lang3.StringUtils; +import org.springdoc.core.fn.builders.operation.Builder; +import org.springframework.data.domain.Sort; +import org.springframework.lang.Nullable; +import org.springframework.web.reactive.function.server.ServerRequest; +import run.halo.app.core.extension.endpoint.SortResolver; +import run.halo.app.extension.ListOptions; +import run.halo.app.extension.PageRequest; +import run.halo.app.extension.PageRequestImpl; +import run.halo.app.extension.router.IListRequest; +import run.halo.app.extension.router.SortableRequest; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; + +import static java.util.Comparator.comparing; +import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder; +import static org.springframework.data.domain.Sort.Order.desc; +import static run.halo.app.extension.index.query.QueryFactory.*; +import static run.halo.app.extension.router.QueryParamBuildUtil.sortParameter; + +public class FriendPostQuery extends SortableRequest { + + public FriendPostQuery(ServerRequest request) { + super(request.exchange()); + } + @Nullable + public String getKeyword() { + return StringUtils.defaultIfBlank(queryParams.getFirst("keyword"), null); + } + + @Nullable + public String getLinkName() { + return queryParams.getFirst("linkName"); + } + + + public ListOptions toListOptions() { + var builder = ListOptions.builder(super.toListOptions()); + + Optional.ofNullable(getKeyword()) + .filter(StringUtils::isNotBlank) + .ifPresent(keyword -> builder.andQuery(or( + contains("spec.author", keyword), + contains("spec.authorUrl", keyword), + contains("spec.title", keyword) + ))); + + Optional.ofNullable(getLinkName()) + .filter(StringUtils::isNotBlank) + .ifPresent(linkName -> builder.andQuery(equal("spec.linkName", linkName))); + + return builder.build(); + } + + public Comparator toComparator() { + List> comparators = new ArrayList<>(); + var sort = getSort(); + var ctOrder = sort.getOrderFor("pubDate"); + if (ctOrder != null) { + Comparator comparator = + comparing(friendPost -> friendPost.getSpec().getPubDate()); + if (ctOrder.isDescending()) { + comparator = comparator.reversed(); + } + comparators.add(comparator); + } + Comparator comparator = + comparing(friendPost -> friendPost.getSpec().getPubDate()); + comparators.add(comparator.reversed()); + return comparators.stream() + .reduce(Comparator::thenComparing) + .orElse(null); + + } + + public PageRequest toPageRequest() { + var sort = SortResolver.defaultInstance.resolve(exchange).and(defaultSort()); + return PageRequestImpl.of(getPage(), getSize(), sort); + } + + public static Sort defaultSort() { + return Sort.by(desc("spec.pubDate")); + } + + public static void buildParameters(Builder builder) { + IListRequest.buildParameters(builder); + builder.parameter(sortParameter()) + .parameter(parameterBuilder() + .in(ParameterIn.QUERY) + .name("linkName") + .description("CronFriendPost filtered by linkName.") + .implementation(String.class) + .required(false)) + .parameter(parameterBuilder() + .in(ParameterIn.QUERY) + .name("keyword") + .description("CronFriendPost filtered by keyword.") + .implementation(String.class) + .required(false)); + } +} \ No newline at end of file diff --git a/src/main/java/la/moony/friends/query/RssSyncLogQuery.java b/src/main/java/la/moony/friends/query/RssSyncLogQuery.java new file mode 100644 index 0000000..2cfcf87 --- /dev/null +++ b/src/main/java/la/moony/friends/query/RssSyncLogQuery.java @@ -0,0 +1,60 @@ +package la.moony.friends.query; + +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import org.apache.commons.lang3.StringUtils; +import org.springdoc.core.fn.builders.operation.Builder; +import org.springframework.lang.Nullable; +import org.springframework.web.reactive.function.server.ServerRequest; +import run.halo.app.extension.ListOptions; +import run.halo.app.extension.router.IListRequest; +import run.halo.app.extension.router.SortableRequest; +import java.util.Optional; + +import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder; +import static run.halo.app.extension.index.query.QueryFactory.*; +import static run.halo.app.extension.router.QueryParamBuildUtil.sortParameter; + +public class RssSyncLogQuery extends SortableRequest { + + public RssSyncLogQuery(ServerRequest request) { + super(request.exchange()); + } + @Nullable + public String getKeyword() { + return StringUtils.defaultIfBlank(queryParams.getFirst("keyword"), null); + } + + @Nullable + public String getState() { + return queryParams.getFirst("state"); + } + + + public ListOptions toListOptions() { + var builder = ListOptions.builder(super.toListOptions()); + + Optional.ofNullable(getKeyword()) + .filter(StringUtils::isNotBlank) + .ifPresent(keyword -> builder.andQuery(or( + contains("metadata.name", keyword), + contains("spec.displayName", keyword) + ))); + + return builder.build(); + } + + public static void buildParameters(Builder builder) { + IListRequest.buildParameters(builder); + builder.parameter(sortParameter()) + .parameter(parameterBuilder() + .in(ParameterIn.QUERY) + .name("keyword") + .implementation(String.class) + .required(false)) + .parameter(parameterBuilder() + .in(ParameterIn.QUERY) + .name("state") + .implementation(String.class) + .required(false)); + } +} \ No newline at end of file diff --git a/src/main/java/la/moony/friends/reconciler/CronCronFriendPostReconciler.java b/src/main/java/la/moony/friends/reconciler/CronCronFriendPostReconciler.java new file mode 100644 index 0000000..a71ee2d --- /dev/null +++ b/src/main/java/la/moony/friends/reconciler/CronCronFriendPostReconciler.java @@ -0,0 +1,121 @@ +package la.moony.friends.reconciler; + +import la.moony.friends.extension.CronFriendPost; +import la.moony.friends.service.RssFeedSyncLogService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.convert.ApplicationConversionService; +import org.springframework.scheduling.support.CronExpression; +import org.springframework.stereotype.Component; +import run.halo.app.extension.ExtensionClient; +import run.halo.app.extension.ExtensionUtil; +import run.halo.app.extension.controller.Controller; +import run.halo.app.extension.controller.ControllerBuilder; +import run.halo.app.extension.controller.Reconciler; +import java.time.*; +import java.util.Objects; + +@Component +public class CronCronFriendPostReconciler implements Reconciler{ + + private static final Logger log = LoggerFactory.getLogger(CronCronFriendPostReconciler.class); + + private final ExtensionClient client; + private Clock clock; + private final RssFeedSyncLogService rssFeedSyncLogService; + + + public CronCronFriendPostReconciler(ExtensionClient client,RssFeedSyncLogService rssFeedSyncLogService) { + this.client = client; + this.rssFeedSyncLogService = rssFeedSyncLogService; + this.clock = Clock.systemDefaultZone(); + } + + + void setClock(Clock clock) { + this.clock = clock; + } + + + public Result reconcile(Request request) { + return (Result)this.client.fetch(CronFriendPost.class, request.name()).map((cronFriendPost) -> { + if (ExtensionUtil.isDeleted(cronFriendPost)) { + return Result.doNotRetry(); + } else { + CronFriendPost.CronSpec spec = cronFriendPost.getSpec(); + if (!spec.isSuspend()) { + return Result.doNotRetry(); + } else { + String cron = spec.getCron(); + String timezone = spec.getTimezone(); + ZoneId zoneId = ZoneId.systemDefault(); + if (timezone != null) { + try { + zoneId = ApplicationConversionService.getSharedInstance().convert(timezone, ZoneId.class); + } catch (DateTimeException dateTimeException) { + log.error("Invalid zone ID {}", timezone, dateTimeException); + return Result.doNotRetry(); + } + } + + Instant now = Instant.now(this.clock); + if (!CronExpression.isValidExpression(cron)) { + log.error("Cron expression {} is invalid.", cron); + return Result.doNotRetry(); + } else { + CronExpression cronExp = CronExpression.parse(cron); + CronFriendPost.CronStatus status = cronFriendPost.getStatus(); + Instant lastScheduledTimestamp = status.getLastScheduledTimestamp(); + if (lastScheduledTimestamp == null) { + lastScheduledTimestamp = cronFriendPost.getMetadata().getCreationTimestamp(); + } + + ZonedDateTime nextFromNow = cronExp.next(now.atZone(zoneId)); + ZonedDateTime nextFromLast = cronExp.next(lastScheduledTimestamp.atZone(zoneId)); + + if (nextFromNow != null && nextFromLast != null) { + if (Objects.equals(nextFromNow, nextFromLast)) { + log.info("Skip scheduling and next scheduled at {}", nextFromNow); + status.setNextSchedulingTimestamp(nextFromNow.toInstant()); + this.client.update(cronFriendPost); + return new Result(true, Duration.between(now, nextFromNow)); + } else { + + + rssFeedSyncLogService.publishRssFeedSyncEvent("all").block(); + + ZonedDateTime zonedNow = now.atZone(zoneId); + ZonedDateTime scheduleTimestamp = now.atZone(zoneId); + + ZonedDateTime next; + for(next = lastScheduledTimestamp.atZone(zoneId); next != null && next.isBefore(zonedNow); next = cronExp.next(next)) { + scheduleTimestamp = next; + } + + status.setLastScheduledTimestamp(scheduleTimestamp.toInstant()); + if (next != null) { + status.setNextSchedulingTimestamp(next.toInstant()); + } + + this.client.update(cronFriendPost); + log.info("Scheduled at {} and next scheduled at {}", scheduleTimestamp, next); + return new Result(true, Duration.between(now, next)); + } + + } else { + return Result.doNotRetry(); + } + + } + + } + } + + }).orElseGet(Result::doNotRetry); + } + + + public Controller setupWith(ControllerBuilder builder) { + return builder.extension(new CronFriendPost()).workerCount(1).build(); + } +} diff --git a/src/main/java/la/moony/friends/reconciler/LinkReconciler.java b/src/main/java/la/moony/friends/reconciler/LinkReconciler.java new file mode 100644 index 0000000..f3f8f4f --- /dev/null +++ b/src/main/java/la/moony/friends/reconciler/LinkReconciler.java @@ -0,0 +1,47 @@ +package la.moony.friends.reconciler; + +import la.moony.friends.extension.FriendPost; +import la.moony.friends.extension.Link; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Component; +import run.halo.app.extension.ExtensionClient; +import run.halo.app.extension.ExtensionUtil; +import run.halo.app.extension.ListOptions; +import run.halo.app.extension.controller.Controller; +import run.halo.app.extension.controller.ControllerBuilder; +import run.halo.app.extension.controller.Reconciler; +import run.halo.app.extension.router.selector.FieldSelector; + +import static run.halo.app.extension.index.query.QueryFactory.equal; + +@Component +@RequiredArgsConstructor +public class LinkReconciler implements Reconciler { + + private final ExtensionClient client; + + @Override + public Result reconcile(Request request) { + client.fetch(Link.class, request.name()) + .ifPresent(link -> { + if (ExtensionUtil.isDeleted(link)) { + var friendPostListOptions = new ListOptions(); + friendPostListOptions.setFieldSelector(FieldSelector.of( + equal("spec.linkName",link.getMetadata().getName()) + )); + client.listAll(FriendPost.class,friendPostListOptions, Sort.by("metadata.creationTimestamp").descending()) + .forEach(friendPost -> client.delete(friendPost)); + } + }); + return Result.doNotRetry(); + } + + @Override + public Controller setupWith(ControllerBuilder builder) { + return builder + .extension(new Link()) + .build(); + } + +} diff --git a/src/main/java/la/moony/friends/reconciler/RssSyncReconciler.java b/src/main/java/la/moony/friends/reconciler/RssSyncReconciler.java new file mode 100644 index 0000000..bde516f --- /dev/null +++ b/src/main/java/la/moony/friends/reconciler/RssSyncReconciler.java @@ -0,0 +1,300 @@ +package la.moony.friends.reconciler; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.rometools.rome.feed.synd.SyndContent; +import com.rometools.rome.feed.synd.SyndEntry; +import com.rometools.rome.feed.synd.SyndFeed; +import com.rometools.rome.io.SyndFeedInput; +import com.rometools.rome.io.XmlReader; +import la.moony.friends.RssFeedSyncEvent; +import la.moony.friends.extension.CronFriendPost; +import la.moony.friends.extension.FriendPost; +import la.moony.friends.extension.Link; +import la.moony.friends.extension.RssFeedSyncLog; +import la.moony.friends.util.CommonUtils; +import la.moony.friends.util.LinkRequest; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.SmartLifecycle; +import org.springframework.context.event.EventListener; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Component; +import run.halo.app.extension.ExtensionClient; +import run.halo.app.extension.ListOptions; +import run.halo.app.extension.ListResult; +import run.halo.app.extension.Metadata; +import run.halo.app.extension.PageRequestImpl; +import run.halo.app.extension.Unstructured; +import run.halo.app.extension.controller.*; +import run.halo.app.extension.router.selector.FieldSelector; +import java.net.URL; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static org.springframework.data.domain.Sort.Order.asc; +import static run.halo.app.extension.MetadataUtil.nullSafeAnnotations; +import static run.halo.app.extension.index.query.QueryFactory.and; +import static run.halo.app.extension.index.query.QueryFactory.equal; +import static run.halo.app.extension.index.query.QueryFactory.isNull; + +@Component +public class RssSyncReconciler implements Reconciler,SmartLifecycle { + + private static final Logger log = LoggerFactory.getLogger(RssSyncReconciler.class); + + private volatile boolean running = false; + + private final ExtensionClient client; + private final RequestQueue queue; + private final Controller controller; + private final ObjectMapper objectMapper = Unstructured.OBJECT_MAPPER; + + + public RssSyncReconciler(ExtensionClient client) { + this.client = client; + queue = new DefaultQueue<>(Instant::now); + controller = this.setupWith(null); + } + + @Override + public Result reconcile(Request request) { + Link link = request.link(); + int sum = request.sum(); + List disableSyncList = request.disableSyncList(); + var linkName = link.getMetadata().getName(); + String rssUrl = getRss(link); + boolean isContains = false; + if (ObjectUtils.isNotEmpty(disableSyncList)) { + isContains = disableSyncList.contains(linkName); + } + String syncLogName = "sync-log-" + linkName; + RssFeedSyncLog newRssFeedSyncLog = new RssFeedSyncLog(); + Metadata metadata = new Metadata(); + metadata.setName(syncLogName); + newRssFeedSyncLog.setMetadata(metadata); + newRssFeedSyncLog.setLinkName(linkName); + if (StringUtils.isNotEmpty(rssUrl) && !isContains) { + tryToSynchronizeFriendPost(newRssFeedSyncLog, link, sum); + }else if (StringUtils.isEmpty(rssUrl)) { + newRssFeedSyncLog.setSyncTime(Instant.now()); + newRssFeedSyncLog.setState(RssFeedSyncLog.RssFeedSyncLogState.nolink); + newRssFeedSyncLog.setFailureReason("No RSS link"); + newRssFeedSyncLog.setFailureMessage("无 RSS 链接."); + }else if (isContains) { + newRssFeedSyncLog.setSyncTime(Instant.now()); + newRssFeedSyncLog.setState(RssFeedSyncLog.RssFeedSyncLogState.failed); + newRssFeedSyncLog.setFailureReason("admin disables sync"); + newRssFeedSyncLog.setFailureMessage("管理员禁用同步."); + } + Optional fetch = client.fetch(RssFeedSyncLog.class, syncLogName); + if (fetch.isPresent()) { + RssFeedSyncLog rssFeedSyncLog = fetch.get(); + rssFeedSyncLog.setState(newRssFeedSyncLog.getState()); + rssFeedSyncLog.setSyncTime(newRssFeedSyncLog.getSyncTime()); + rssFeedSyncLog.setFailureReason(newRssFeedSyncLog.getFailureReason()); + rssFeedSyncLog.setFailureMessage(newRssFeedSyncLog.getFailureMessage()); + client.update(rssFeedSyncLog); + }else { + client.create(newRssFeedSyncLog); + } + return Result.doNotRetry(); + } + + public String getRss(Link link) { + var annotations = nullSafeAnnotations(link); + + //处理旧数据 rss_uri + String rssUri = annotations.get("rss_uri"); + + String rssUrl = annotations.get("rss_url"); + String isRequest = annotations.get("is_request"); + if ((StringUtils.isEmpty(rssUrl) && StringUtils.isEmpty(isRequest)) || StringUtils.isNotEmpty(rssUri)) { + String linkRss = LinkRequest.getLinkRss(link.getSpec().getUrl()); + if (StringUtils.isNotEmpty(rssUri)) { + linkRss = rssUri; + annotations.remove("rss_uri"); + annotations.put("rss_url", rssUri); + }else { + annotations.put("is_request","true"); + if (StringUtils.isNotEmpty(linkRss)) { + annotations.put("rss_url",linkRss); + } + } + updateLink(link); + return linkRss; + } + return rssUrl; + } + + + public void updateLink(Link link) { + Map extensionMap = objectMapper.convertValue(link, Map.class); + var extension = new Unstructured(extensionMap); + client.update(extension); + } + + private void tryToSynchronizeFriendPost(RssFeedSyncLog syncLog, Link link, int sum) { + var linkName = link.getMetadata().getName(); + var annotations = nullSafeAnnotations(link); + var rssUrl = annotations.get("rss_url"); + + CronFriendPost cron = new CronFriendPost(); + var spec = new CronFriendPost.CronSpec(); + spec.setSuccessfulRetainLimit(5); + cron.setSpec(spec); + syncLog.setSyncTime(Instant.now()); + List friendPostList = fetchFriendPost(rssUrl, sum, syncLog); + + // 即使获取失败也继续处理 + if (friendPostList != null && !friendPostList.isEmpty()) { + for (FriendPost rssPost : friendPostList) { + var friendPostListOptions = new ListOptions(); + friendPostListOptions.setFieldSelector(FieldSelector.of( + and(isNull("metadata.deletionTimestamp"), + equal("spec.postLink",rssPost.getSpec().getPostLink()) + ) + )); + long total = client.listBy(FriendPost.class, friendPostListOptions, + PageRequestImpl.ofSize(1)).getTotal(); + if (total==0) { + FriendPost friendPost = new FriendPost(); + // 设置元数据才能保存 + friendPost.setMetadata(new Metadata()); + friendPost.getMetadata().setGenerateName("friend-post-"); + friendPost.setSpec(rssPost.getSpec()); + var friendPostSpec = friendPost.getSpec(); + friendPostSpec.setLogo(link.getSpec().getLogo()); + friendPostSpec.setAuthor(link.getSpec().getDisplayName()); + friendPostSpec.setAuthorUrl(link.getSpec().getUrl()); + friendPostSpec.setLinkName(linkName); + String description = friendPost.getSpec().getDescription(); + //解析html内容转换成文本 + if (StringUtils.isNotEmpty(description)){ + description = CommonUtils.parseAndTruncateHtml2Text(description, 200); + String regexp = "[ *|\\s*]*"; + description = description.replaceFirst(regexp, "").trim(); + friendPostSpec.setDescription(description); + } + client.create(friendPost); + } + } + delFriendPost(linkName,sum); + } + } + + public List fetchFriendPost(String rssAddress,int postsLimit, RssFeedSyncLog sync) { + List friendPostList = new ArrayList<>(); + try { + SyndFeed feed = new SyndFeedInput().build(new XmlReader(new URL(rssAddress))); + int postCount = 0; + for (SyndEntry entry : feed.getEntries()) { + Date date = entry.getPublishedDate(); + if (date == null) { + date = entry.getUpdatedDate(); + } + String description = ""; + SyndContent content = entry.getDescription(); + if (content != null) { + description = content.getValue(); + }else { + List contents = entry.getContents(); + if (!contents.isEmpty()) { + description = contents.get(0).getValue(); + } + } + + FriendPost friendPost = new FriendPost(); + friendPost.setSpec(new FriendPost.FriendPostSpec()); + friendPost.getSpec().setTitle(entry.getTitle()); + friendPost.getSpec().setPostLink(entry.getLink()); + friendPost.getSpec().setPubDate(date.toInstant()); + friendPost.getSpec().setDescription(description); + friendPostList.add(friendPost); + postCount++; + if (postCount >= postsLimit) { + break; + } + } + sync.setState(RssFeedSyncLog.RssFeedSyncLogState.success); + } catch (Exception e) { + sync.setState(RssFeedSyncLog.RssFeedSyncLogState.failed); + sync.setFailureReason("CrawlingRSSError"); + sync.setFailureMessage(e.getMessage()); + log.error("error in crawling rss", e); + return null; + } + return friendPostList; + } + + public void delFriendPost(String linkName,Integer sum) { + var friendPostListOptions = new ListOptions(); + friendPostListOptions.setFieldSelector(FieldSelector.of( + equal("spec.linkName",linkName) + )); + long total = client.listBy(FriendPost.class, friendPostListOptions, PageRequestImpl.ofSize(1)).getTotal(); + if (total > sum) { + int l = (int) (total - sum); + PageRequestImpl pageRequestImpl = PageRequestImpl.of(pageNullSafe(1), sizeNullSafe(l), + Sort.by(asc("spec.pubDate"))); + ListResult friendPosts = + client.listBy(FriendPost.class, friendPostListOptions, pageRequestImpl); + for (FriendPost friendPost : friendPosts) { + client.delete(friendPost); + } + } + } + + static int pageNullSafe(Integer page) { + return ObjectUtils.defaultIfNull(page, 1); + } + + static int sizeNullSafe(Integer size) { + return ObjectUtils.defaultIfNull(size, 10); + } + + + @Override + public Controller setupWith(ControllerBuilder builder) { + return new DefaultController<>( + this.getClass().getName(), + this, + queue, + null, + Duration.ofMillis(300), + Duration.ofMinutes(5), + 5); + } + + @Override + public void start() { + this.controller.start(); + this.running = true; + } + + @Override + public void stop() { + this.running = false; + this.controller.dispose(); + } + + @Override + public boolean isRunning() { + return this.running; + } + + @EventListener(RssFeedSyncEvent.class) + public void onReplyEvent(RssFeedSyncEvent event) { + var request = new Request(event.getLink(),event.getSum(),event.getDisableSyncList()); + queue.addImmediately(request); + } + + public record Request(Link link,int sum,List disableSyncList) { + } +} diff --git a/src/main/java/la/moony/friends/rest/FriendRouter.java b/src/main/java/la/moony/friends/rest/FriendRouter.java new file mode 100644 index 0000000..e6be1d7 --- /dev/null +++ b/src/main/java/la/moony/friends/rest/FriendRouter.java @@ -0,0 +1,94 @@ +package la.moony.friends.rest; + +import la.moony.friends.finders.FriendFinder; +import la.moony.friends.vo.FriendPostVo; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.RouterFunctions; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; +import reactor.core.publisher.Mono; +import run.halo.app.plugin.ReactiveSettingFetcher; +import run.halo.app.theme.TemplateNameResolver; +import run.halo.app.theme.router.ModelConst; +import run.halo.app.theme.router.PageUrlUtils; +import run.halo.app.theme.router.UrlContextListResult; + +import java.util.HashMap; +import java.util.Map; + +import static run.halo.app.theme.router.PageUrlUtils.totalPage; + +@Configuration +@RequiredArgsConstructor +@Slf4j +public class FriendRouter { + + + private final FriendFinder friendFinder; + + private final TemplateNameResolver templateNameResolver; + + private final ReactiveSettingFetcher settingFetcher; + + @Bean + RouterFunction friendTemplateRoute() { + return RouterFunctions.route().GET("/friends",this::handlerFunction) + .GET("/friends/page/{page:\\d+}",this::handlerFunction) + .build(); + } + + + private Mono handlerFunction(ServerRequest request) { + return templateNameResolver.resolveTemplateNameOrDefault(request.exchange(), "friends") + .flatMap( templateName -> ServerResponse.ok().render(templateName, + java.util.Map.of(ModelConst.TEMPLATE_ID, templateName, + "friends", friendPostList(request), + "title",getFriendTitle()) + )); + } + + Mono getFriendTitle() { + return this.settingFetcher.get("base").map( + setting -> setting.get("title").asText("友链朋友圈")).defaultIfEmpty( + "友链朋友圈"); + } + + private Mono> friendPostList(ServerRequest request) { + String path = request.path(); + int pageNum = pageNumInPathVariable(request); + String linkName = request.queryParam("linkName") + .filter(StringUtils::isNotBlank) + .orElse(null); + return this.settingFetcher.get("base") + .map(item -> item.get("pageSize").asInt(10)) + .defaultIfEmpty(10) + .flatMap(pageSize -> { + Map params = new HashMap<>(); + params.put("page",pageNum); + params.put("size",pageSize); + if(StringUtils.isNotBlank(linkName)){ + params.put("linkName",linkName); + } + return friendFinder.list(params) + .map(list -> new UrlContextListResult.Builder() + .listResult(list) + .nextUrl(PageUrlUtils.nextPageUrl(path, totalPage(list))) + .prevUrl(PageUrlUtils.prevPageUrl(path)) + .build() + ); + }); + } + + + private int pageNumInPathVariable(ServerRequest request) { + String page = request.pathVariables().get("page"); + return NumberUtils.toInt(page, 1); + } + +} diff --git a/src/main/java/la/moony/friends/service/RssDetailService.java b/src/main/java/la/moony/friends/service/RssDetailService.java new file mode 100644 index 0000000..6c792b7 --- /dev/null +++ b/src/main/java/la/moony/friends/service/RssDetailService.java @@ -0,0 +1,9 @@ +package la.moony.friends.service; + +import la.moony.friends.vo.RssDetail; +import reactor.core.publisher.Mono; + +public interface RssDetailService { + + Mono fetchRssDetail(String rssUrl, int fetchLimitNumber); +} diff --git a/src/main/java/la/moony/friends/service/RssFeedSyncLogService.java b/src/main/java/la/moony/friends/service/RssFeedSyncLogService.java new file mode 100644 index 0000000..0c187a4 --- /dev/null +++ b/src/main/java/la/moony/friends/service/RssFeedSyncLogService.java @@ -0,0 +1,13 @@ +package la.moony.friends.service; + +import la.moony.friends.ListedRssSyncLog; +import la.moony.friends.query.RssSyncLogQuery; +import reactor.core.publisher.Mono; +import run.halo.app.extension.ListResult; + +public interface RssFeedSyncLogService { + + Mono> listRssSyncLog(RssSyncLogQuery query); + + Mono publishRssFeedSyncEvent(String name); +} diff --git a/src/main/java/la/moony/friends/service/impl/RssDetailServiceImpl.java b/src/main/java/la/moony/friends/service/impl/RssDetailServiceImpl.java new file mode 100644 index 0000000..c3ca9ea --- /dev/null +++ b/src/main/java/la/moony/friends/service/impl/RssDetailServiceImpl.java @@ -0,0 +1,53 @@ +package la.moony.friends.service.impl; + +import com.rometools.rome.feed.synd.SyndEntry; +import com.rometools.rome.feed.synd.SyndFeed; +import com.rometools.rome.io.SyndFeedInput; +import com.rometools.rome.io.XmlReader; +import la.moony.friends.service.RssDetailService; +import la.moony.friends.vo.RssDetail; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +@Component +public class RssDetailServiceImpl implements RssDetailService { + + + @Override + public Mono fetchRssDetail(String rssUrl, int fetchLimitNumber) { + RssDetail rssDetail = new RssDetail(); + try { + // 创建 SyndFeedInput 对象并从 URL 获取 RSS 提要 + SyndFeedInput input = new SyndFeedInput(); + SyndFeed feed = input.build(new XmlReader(new URL(rssUrl))); + RssDetail.Channel channel = new RssDetail.Channel(); + channel.setTitle(feed.getTitle()); + channel.setLink(feed.getLink()); + channel.setDescription(feed.getDescription()); + rssDetail.setChannel(channel); + List items = new ArrayList<>(); + int count = 0; + for (SyndEntry syndEntry : feed.getEntries()) { + RssDetail.Item item = new RssDetail.Item(); + item.setTitle(syndEntry.getTitle()); + item.setLink(syndEntry.getLink()); + item.setAuthor(syndEntry.getAuthor()); + item.setDescription(syndEntry.getDescription().getValue()); + item.setPubDate(syndEntry.getPublishedDate().toInstant()); + items.add(item); + count++; + if (count >= fetchLimitNumber) { + break; + } + } + channel.setItems(items); + }catch (Exception e) { + e.printStackTrace(); + } + return Mono.just(rssDetail); + } + +} diff --git a/src/main/java/la/moony/friends/service/impl/RssFeedSyncLogServiceImpl.java b/src/main/java/la/moony/friends/service/impl/RssFeedSyncLogServiceImpl.java new file mode 100644 index 0000000..07a7dc5 --- /dev/null +++ b/src/main/java/la/moony/friends/service/impl/RssFeedSyncLogServiceImpl.java @@ -0,0 +1,97 @@ +package la.moony.friends.service.impl; + +import la.moony.friends.ListedRssSyncLog; +import la.moony.friends.RssFeedSyncEvent; +import la.moony.friends.extension.CronFriendPost; +import la.moony.friends.extension.Link; +import la.moony.friends.extension.RssFeedSyncLog; +import la.moony.friends.query.RssSyncLogQuery; +import la.moony.friends.service.RssFeedSyncLogService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import run.halo.app.extension.ListOptions; +import run.halo.app.extension.ListResult; +import run.halo.app.extension.PageRequestImpl; +import run.halo.app.extension.ReactiveExtensionClient; +import run.halo.app.extension.router.selector.FieldSelector; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import static run.halo.app.extension.index.query.QueryFactory.equal; +import static run.halo.app.extension.index.query.QueryFactory.isNull; + +@Component +public class RssFeedSyncLogServiceImpl implements RssFeedSyncLogService { + + private final ReactiveExtensionClient client; + + private final ApplicationEventPublisher eventPublisher; + + public RssFeedSyncLogServiceImpl(ReactiveExtensionClient client, + ApplicationEventPublisher eventPublisher) { + this.client = client; + this.eventPublisher = eventPublisher; + } + + @Override + public Mono> listRssSyncLog(RssSyncLogQuery query) { + return buildListOptions(query) + .flatMap(listOptions -> client.listBy(Link.class, listOptions, + PageRequestImpl.of(query.getPage(), query.getSize(), query.getSort()) + )) + .flatMap(listResult -> Flux.fromStream(listResult.get()) + .map(this::getListedRssSyncLog) + .flatMapSequential(Function.identity()) + .collectList() + .map(listedApplications -> new ListResult<>(listResult.getPage(), listResult.getSize(), + listResult.getTotal(), listedApplications) + ) + .defaultIfEmpty(ListResult.emptyResult()) + ); + } + + private Mono getListedRssSyncLog(Link link) { + Assert.notNull(link, "The link must not be null."); + var listedRssSyncLog = new ListedRssSyncLog().setLink(link); + var logMono = client.fetch(RssFeedSyncLog.class,"sync-log-"+link.getMetadata().getName()) + .doOnNext(listedRssSyncLog::setLog); + + return Mono.when(logMono) + .thenReturn(listedRssSyncLog); + } + + Mono buildListOptions(RssSyncLogQuery query) { + return Mono.just(query.toListOptions()); + } + + public Mono publishRssFeedSyncEvent(String name) { + CronFriendPost cron = new CronFriendPost(); + CronFriendPost.CronSpec cronSpec = new CronFriendPost.CronSpec(); + cronSpec.setSuccessfulRetainLimit(5); + cronSpec.setDisableSyncList(new ArrayList<>()); + cron.setSpec(cronSpec); + return client.fetch(CronFriendPost.class, "cron-friends-default") + .defaultIfEmpty(cron) + .flatMap(cronFriendPost -> { + var spec = cronFriendPost.getSpec(); + int successfulRetainLimit = spec.getSuccessfulRetainLimit(); + List disableSyncList = spec.getDisableSyncList(); + int sum = successfulRetainLimit == 0 ? 5 : successfulRetainLimit; + var listOptions = new ListOptions(); + FieldSelector fieldSelector = FieldSelector.of(isNull("metadata.deletionTimestamp")); + if (!StringUtils.equals(name,"all")) { + fieldSelector = fieldSelector.andQuery(equal("metadata.name",name)); + } + listOptions.setFieldSelector(fieldSelector); + return client.listAll(Link.class, listOptions, Sort.by("metadata.creationTimestamp")) + .doOnNext(link -> eventPublisher.publishEvent(new RssFeedSyncEvent(this, link,sum,disableSyncList))) + .then(); + }); + } +} diff --git a/src/main/java/la/moony/friends/util/CommonUtils.java b/src/main/java/la/moony/friends/util/CommonUtils.java new file mode 100644 index 0000000..8f987e2 --- /dev/null +++ b/src/main/java/la/moony/friends/util/CommonUtils.java @@ -0,0 +1,24 @@ +package la.moony.friends.util; + +import org.apache.commons.lang3.StringUtils; +import org.jsoup.Jsoup; + +public class CommonUtils { + + public static String parseAndTruncateHtml2Text(String html, int length) { + if (StringUtils.isBlank(html)) { + return ""; + } + + String text = Jsoup.parse(html).text(); + if (text.length() <= length) { + return text; + } else { + text = text.substring(0, length); + text += "..."; + } + return text; + } + + +} diff --git a/src/main/java/la/moony/friends/util/LinkRequest.java b/src/main/java/la/moony/friends/util/LinkRequest.java new file mode 100644 index 0000000..e3fa74d --- /dev/null +++ b/src/main/java/la/moony/friends/util/LinkRequest.java @@ -0,0 +1,53 @@ +package la.moony.friends.util; + +import java.io.IOException; +import java.util.Map; +import java.util.Objects; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LinkRequest { + + private static final Logger log = LoggerFactory.getLogger(LinkRequest.class); + + private final static String USER_AGENT = + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " + + "Chrome/58.0.3029.110 Safari/537.3"; + + public static String getLinkRss(String linkUrl) { + Document document = null; + try { + document = Jsoup.connect(linkUrl) + .followRedirects(true) + .maxBodySize(1024 * 1024 * 20) + .timeout(10000) + .headers( + Map.of( + "User-Agent", USER_AGENT, + "Referer", linkUrl, + "Accept", "text/html,application/xhtml+xml,application/xml" + ) + ) + .get(); + } catch (IOException e) { + log.error("Failed to get link rss ",e); + } + + if(document != null) { + Element rssElement = document.selectFirst("link[type=application/rss+xml]"); + if (Objects.nonNull(rssElement)) { + return rssElement.absUrl("href"); + } + + Element atomElement = document.selectFirst("link[type=application/atom+xml]"); + if (Objects.nonNull(atomElement)) { + return atomElement.absUrl("href"); + } + } + + return ""; + } +} diff --git a/src/main/java/la/moony/friends/util/SortUtils.java b/src/main/java/la/moony/friends/util/SortUtils.java new file mode 100644 index 0000000..5030fec --- /dev/null +++ b/src/main/java/la/moony/friends/util/SortUtils.java @@ -0,0 +1,43 @@ +package la.moony.friends.util; + +import java.util.List; +import lombok.experimental.UtilityClass; +import org.springframework.data.domain.Sort; +import org.springframework.lang.NonNull; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; + +@UtilityClass +public class SortUtils { + static final String delimiter = ","; + + /** + *

Resolve from direction params, e.g. "name,asc" or "name"

+ * + * @param directionParams direction params + * @return sort object + */ + public static Sort resolve(List directionParams) { + if (CollectionUtils.isEmpty(directionParams)) { + return Sort.unsorted(); + } + Sort.Order[] orders = new Sort.Order[directionParams.size()]; + for (int i = 0; i < directionParams.size(); i++) { + String[] parts = directionParams.get(i).split(delimiter); + if (parts.length == 1) { + orders[i] = new Sort.Order(Sort.Direction.ASC, parts[0]); + } else { + orders[i] = new Sort.Order(toDirection(parts[1]), parts[0]); + } + } + return Sort.by(orders); + } + + private static Sort.Direction toDirection(@NonNull String direction) { + Assert.notNull(direction, "Direction must not be null"); + if (direction.contains(" ")) { + throw new IllegalArgumentException("Direction must not contain whitespace"); + } + return Sort.Direction.fromString(direction); + } +} diff --git a/src/main/java/la/moony/friends/vo/FriendPostVo.java b/src/main/java/la/moony/friends/vo/FriendPostVo.java new file mode 100644 index 0000000..5b190b8 --- /dev/null +++ b/src/main/java/la/moony/friends/vo/FriendPostVo.java @@ -0,0 +1,27 @@ +package la.moony.friends.vo; + +import la.moony.friends.extension.FriendPost; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.experimental.SuperBuilder; +import run.halo.app.extension.MetadataOperator; + + +@Data +@SuperBuilder +@ToString +@EqualsAndHashCode +public class FriendPostVo { + + private MetadataOperator metadata; + + private FriendPost.FriendPostSpec spec; + + public static FriendPostVo from(FriendPost friendPost) { + return FriendPostVo.builder() + .metadata(friendPost.getMetadata()) + .spec(friendPost.getSpec()) + .build(); + } +} diff --git a/src/main/java/la/moony/friends/vo/LinkGroupVo.java b/src/main/java/la/moony/friends/vo/LinkGroupVo.java new file mode 100644 index 0000000..0dfd8c6 --- /dev/null +++ b/src/main/java/la/moony/friends/vo/LinkGroupVo.java @@ -0,0 +1,31 @@ +package la.moony.friends.vo; + +import la.moony.friends.extension.LinkGroup; +import lombok.Builder; +import lombok.Value; +import lombok.With; +import run.halo.app.extension.MetadataOperator; +import run.halo.app.theme.finders.vo.ExtensionVoOperator; + +import java.util.List; + + +@Value +@Builder +public class LinkGroupVo implements ExtensionVoOperator { + + MetadataOperator metadata; + + LinkGroup.LinkGroupSpec spec; + + @With + List links; + + public static LinkGroupVo from(LinkGroup linkGroup) { + return LinkGroupVo.builder() + .metadata(linkGroup.getMetadata()) + .spec(linkGroup.getSpec()) + .links(List.of()) + .build(); + } +} diff --git a/src/main/java/la/moony/friends/vo/LinkVo.java b/src/main/java/la/moony/friends/vo/LinkVo.java new file mode 100644 index 0000000..c364662 --- /dev/null +++ b/src/main/java/la/moony/friends/vo/LinkVo.java @@ -0,0 +1,38 @@ +package la.moony.friends.vo; + +import la.moony.friends.extension.Link; +import la.moony.friends.extension.RssFeedSyncLog; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import lombok.With; +import lombok.experimental.SuperBuilder; +import run.halo.app.extension.MetadataOperator; +import run.halo.app.theme.finders.vo.ExtensionVoOperator; +import java.util.List; + +@Data +@SuperBuilder +@ToString +@EqualsAndHashCode +public class LinkVo implements ExtensionVoOperator { + + MetadataOperator metadata; + + Link.LinkSpec spec; + + + RssFeedSyncLog rssFeedSyncLog; + + List friendPosts; + + public static LinkVo from(Link link) { + return LinkVo.builder() + .metadata(link.getMetadata()) + .spec(link.getSpec()) + .friendPosts(List.of()) + .build(); + } +} diff --git a/src/main/java/la/moony/friends/vo/RssDetail.java b/src/main/java/la/moony/friends/vo/RssDetail.java new file mode 100644 index 0000000..793c008 --- /dev/null +++ b/src/main/java/la/moony/friends/vo/RssDetail.java @@ -0,0 +1,41 @@ +package la.moony.friends.vo; + +import lombok.Data; +import java.time.Instant; +import java.util.List; + + +@Data +public class RssDetail { + + private Channel channel; + + @Data + public static class Channel { + + private String title; + + private String link; + + private String description; + + private List items; + } + + @Data + public static class Item { + private String title; + + private String link; + + private String author; + + private String description; + + private String uri; + + private Instant pubDate; + + } + +} diff --git a/src/main/resources/extensions/annotation-setting.yaml b/src/main/resources/extensions/annotation-setting.yaml new file mode 100644 index 0000000..b33239d --- /dev/null +++ b/src/main/resources/extensions/annotation-setting.yaml @@ -0,0 +1,13 @@ +apiVersion: v1alpha1 +kind: AnnotationSetting +metadata: + name: annotation-setting-link-rss +spec: + targetRef: + group: core.halo.run + kind: Link + formSchema: + - $formkit: "url" + name: "rss_url" + label: "RSS 地址" + diff --git a/src/main/resources/extensions/reverseProxy.yaml b/src/main/resources/extensions/reverseProxy.yaml new file mode 100644 index 0000000..3602910 --- /dev/null +++ b/src/main/resources/extensions/reverseProxy.yaml @@ -0,0 +1,8 @@ +apiVersion: plugin.halo.run/v1alpha1 +kind: ReverseProxy +metadata: + name: friends-static-assets +rules: + - path: /static/** + file: + directory: static diff --git a/src/main/resources/extensions/roleTemplate.yaml b/src/main/resources/extensions/roleTemplate.yaml new file mode 100644 index 0000000..97a4094 --- /dev/null +++ b/src/main/resources/extensions/roleTemplate.yaml @@ -0,0 +1,59 @@ +apiVersion: v1alpha1 +kind: Role +metadata: + name: role-template-fridends-anonymous + labels: + halo.run/role-template: "true" + halo.run/hidden: "true" + rbac.authorization.halo.run/aggregate-to-anonymous: "true" + annotations: + rbac.authorization.halo.run/module: "朋友圈" + rbac.authorization.halo.run/display-name: "朋友圈匿名接口" +rules: + - apiGroups: ["api.friend.moony.la"] + resources: ["parsingrss","friendposts"] + verbs: [ "get", "list" ] + +--- + +apiVersion: v1alpha1 +kind: Role +metadata: + name: role-template-friends-view + labels: + halo.run/role-template: "true" + annotations: + rbac.authorization.halo.run/module: "朋友圈" + rbac.authorization.halo.run/display-name: "朋友圈查看" + rbac.authorization.halo.run/ui-permissions: | + ["plugin:friends:view"] +rules: + - apiGroups: [ "friend.moony.la" ] + resources: [ "friendposts","cronfriendposts" ] + verbs: [ "get", "list" ] + - apiGroups: [ "api.friend.moony.la" ] + resources: [ "friendposts","parsingrss"] + verbs: [ "get", "list" ] + +--- + +apiVersion: v1alpha1 +kind: Role +metadata: + name: role-template-friends-manage + labels: + halo.run/role-template: "true" + annotations: + rbac.authorization.halo.run/module: "朋友圈" + rbac.authorization.halo.run/display-name: "朋友圈管理" + rbac.authorization.halo.run/ui-permissions: | + ["plugin:friends:manage"] + rbac.authorization.halo.run/dependencies: | + ["role-template-friends-view"] +rules: + - apiGroups: [ "friend.moony.la" ] + resources: [ "friendposts","cronfriendposts"] + verbs: [ "create", "patch", "update", "delete", "deletecollection" ] + - apiGroups: [ "api.friend.moony.la" ] + resources: [ "friendposts","syncrssfeed","friendposts/delete"] + verbs: [ "create", "patch", "update", "delete", "deletecollection" ] \ No newline at end of file diff --git a/src/main/resources/extensions/settings.yaml b/src/main/resources/extensions/settings.yaml new file mode 100644 index 0000000..dc002b7 --- /dev/null +++ b/src/main/resources/extensions/settings.yaml @@ -0,0 +1,19 @@ +apiVersion: v1alpha1 +kind: Setting +metadata: + name: plugin-friends-settings +spec: + forms: + - group: base + label: 基本设置 + formSchema: + - $formkit: text + label: 页面标题 + name: title + validation: required + value: '友链朋友圈' + - $formkit: text + label: 朋友圈列表显示条数 + name: pageSize + validation: required|Number + value: 10 diff --git a/src/main/resources/logo.png b/src/main/resources/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..61e377bcda605d4e49cda2f2180ea9f342e5be58 GIT binary patch literal 5749 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/plugin.yaml b/src/main/resources/plugin.yaml new file mode 100644 index 0000000..d8aff85 --- /dev/null +++ b/src/main/resources/plugin.yaml @@ -0,0 +1,25 @@ +apiVersion: plugin.halo.run/v1alpha1 +kind: Plugin +metadata: + name: plugin-friends + annotations: + "store.halo.run/app-id": "app-yISsV" +spec: + enabled: false + requires: ">=2.21.0" + pluginDependencies: + "PluginLinks": ">=1.5.0" + author: + name: 困困鱼 + website: https://github.com/chengzhongxue + logo: logo.png + homepage: https://www.halo.run/store/apps/app-yISsV + repo: https://github.com/chengzhongxue/plugin-friends-new + issues: https://github.com/chengzhongxue/plugin-friends-new/issues + configMapName: plugin-friends-configMap + settingName: plugin-friends-settings + displayName: "朋友圈" + description: "提供对 RSS 链接的订阅功能,支持获取其订阅内容" + license: + - name: "GPL-3.0" + url: "https://github.com/chengzhongxue/plugin-friends-new/blob/main/LICENSE" \ No newline at end of file diff --git a/src/main/resources/static/friends-rss.iife.js b/src/main/resources/static/friends-rss.iife.js new file mode 100644 index 0000000..037eeff --- /dev/null +++ b/src/main/resources/static/friends-rss.iife.js @@ -0,0 +1,118 @@ +(function(it){"use strict";var xt;const St='*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:var(--un-default-border-color, #e5e7eb)}:before,:after{--un-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}';/** + * @license + * Copyright 2019 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */const R=globalThis,q=R.ShadowRoot&&(R.ShadyCSS===void 0||R.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,V=Symbol(),rt=new WeakMap;let nt=class{constructor(t,e,i){if(this._$cssResult$=!0,i!==V)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const e=this.t;if(q&&t===void 0){const i=e!==void 0&&e.length===1;i&&(t=rt.get(e)),t===void 0&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),i&&rt.set(e,t))}return t}toString(){return this.cssText}};const ot=r=>new nt(typeof r=="string"?r:r+"",void 0,V),at=(r,...t)=>{const e=r.length===1?r[0]:t.reduce((i,s,o)=>i+(n=>{if(n._$cssResult$===!0)return n.cssText;if(typeof n=="number")return n;throw Error("Value passed to 'css' function must be a 'css' function result: "+n+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(s)+r[o+1],r[0]);return new nt(e,r,V)},kt=(r,t)=>{if(q)r.adoptedStyleSheets=t.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(const e of t){const i=document.createElement("style"),s=R.litNonce;s!==void 0&&i.setAttribute("nonce",s),i.textContent=e.cssText,r.appendChild(i)}},lt=q?r=>r:r=>r instanceof CSSStyleSheet?(t=>{let e="";for(const i of t.cssRules)e+=i.cssText;return ot(e)})(r):r;/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */const{is:Ct,defineProperty:Pt,getOwnPropertyDescriptor:Ut,getOwnPropertyNames:Mt,getOwnPropertySymbols:zt,getPrototypeOf:Ot}=Object,m=globalThis,ht=m.trustedTypes,Ht=ht?ht.emptyScript:"",W=m.reactiveElementPolyfillSupport,U=(r,t)=>r,j={toAttribute(r,t){switch(t){case Boolean:r=r?Ht:null;break;case Object:case Array:r=r==null?r:JSON.stringify(r)}return r},fromAttribute(r,t){let e=r;switch(t){case Boolean:e=r!==null;break;case Number:e=r===null?null:Number(r);break;case Object:case Array:try{e=JSON.parse(r)}catch{e=null}}return e}},Z=(r,t)=>!Ct(r,t),ct={attribute:!0,type:String,converter:j,reflect:!1,useDefault:!1,hasChanged:Z};Symbol.metadata??(Symbol.metadata=Symbol("metadata")),m.litPropertyMetadata??(m.litPropertyMetadata=new WeakMap);let S=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??(this.l=[])).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=ct){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){const i=Symbol(),s=this.getPropertyDescriptor(t,i,e);s!==void 0&&Pt(this.prototype,t,s)}}static getPropertyDescriptor(t,e,i){const{get:s,set:o}=Ut(this.prototype,t)??{get(){return this[e]},set(n){this[e]=n}};return{get:s,set(n){const l=s==null?void 0:s.call(this);o==null||o.call(this,n),this.requestUpdate(t,l,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??ct}static _$Ei(){if(this.hasOwnProperty(U("elementProperties")))return;const t=Ot(this);t.finalize(),t.l!==void 0&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(U("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(U("properties"))){const e=this.properties,i=[...Mt(e),...zt(e)];for(const s of i)this.createProperty(s,e[s])}const t=this[Symbol.metadata];if(t!==null){const e=litPropertyMetadata.get(t);if(e!==void 0)for(const[i,s]of e)this.elementProperties.set(i,s)}this._$Eh=new Map;for(const[e,i]of this.elementProperties){const s=this._$Eu(e,i);s!==void 0&&this._$Eh.set(s,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const i=new Set(t.flat(1/0).reverse());for(const s of i)e.unshift(lt(s))}else t!==void 0&&e.push(lt(t));return e}static _$Eu(t,e){const i=e.attribute;return i===!1?void 0:typeof i=="string"?i:typeof t=="string"?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){var t;this._$ES=new Promise(e=>this.enableUpdating=e),this._$AL=new Map,this._$E_(),this.requestUpdate(),(t=this.constructor.l)==null||t.forEach(e=>e(this))}addController(t){var e;(this._$EO??(this._$EO=new Set)).add(t),this.renderRoot!==void 0&&this.isConnected&&((e=t.hostConnected)==null||e.call(t))}removeController(t){var e;(e=this._$EO)==null||e.delete(t)}_$E_(){const t=new Map,e=this.constructor.elementProperties;for(const i of e.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t)}createRenderRoot(){const t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return kt(t,this.constructor.elementStyles),t}connectedCallback(){var t;this.renderRoot??(this.renderRoot=this.createRenderRoot()),this.enableUpdating(!0),(t=this._$EO)==null||t.forEach(e=>{var i;return(i=e.hostConnected)==null?void 0:i.call(e)})}enableUpdating(t){}disconnectedCallback(){var t;(t=this._$EO)==null||t.forEach(e=>{var i;return(i=e.hostDisconnected)==null?void 0:i.call(e)})}attributeChangedCallback(t,e,i){this._$AK(t,i)}_$ET(t,e){var o;const i=this.constructor.elementProperties.get(t),s=this.constructor._$Eu(t,i);if(s!==void 0&&i.reflect===!0){const n=(((o=i.converter)==null?void 0:o.toAttribute)!==void 0?i.converter:j).toAttribute(e,i.type);this._$Em=t,n==null?this.removeAttribute(s):this.setAttribute(s,n),this._$Em=null}}_$AK(t,e){var o,n;const i=this.constructor,s=i._$Eh.get(t);if(s!==void 0&&this._$Em!==s){const l=i.getPropertyOptions(s),a=typeof l.converter=="function"?{fromAttribute:l.converter}:((o=l.converter)==null?void 0:o.fromAttribute)!==void 0?l.converter:j;this._$Em=s;const c=a.fromAttribute(e,l.type);this[s]=c??((n=this._$Ej)==null?void 0:n.get(s))??c,this._$Em=null}}requestUpdate(t,e,i){var s;if(t!==void 0){const o=this.constructor,n=this[t];if(i??(i=o.getPropertyOptions(t)),!((i.hasChanged??Z)(n,e)||i.useDefault&&i.reflect&&n===((s=this._$Ej)==null?void 0:s.get(t))&&!this.hasAttribute(o._$Eu(t,i))))return;this.C(t,e,i)}this.isUpdatePending===!1&&(this._$ES=this._$EP())}C(t,e,{useDefault:i,reflect:s,wrapped:o},n){i&&!(this._$Ej??(this._$Ej=new Map)).has(t)&&(this._$Ej.set(t,n??e??this[t]),o!==!0||n!==void 0)||(this._$AL.has(t)||(this.hasUpdated||i||(e=void 0),this._$AL.set(t,e)),s===!0&&this._$Em!==t&&(this._$Eq??(this._$Eq=new Set)).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}const t=this.scheduleUpdate();return t!=null&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){var i;if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??(this.renderRoot=this.createRenderRoot()),this._$Ep){for(const[o,n]of this._$Ep)this[o]=n;this._$Ep=void 0}const s=this.constructor.elementProperties;if(s.size>0)for(const[o,n]of s){const{wrapped:l}=n,a=this[o];l!==!0||this._$AL.has(o)||a===void 0||this.C(o,void 0,n,a)}}let t=!1;const e=this._$AL;try{t=this.shouldUpdate(e),t?(this.willUpdate(e),(i=this._$EO)==null||i.forEach(s=>{var o;return(o=s.hostUpdate)==null?void 0:o.call(s)}),this.update(e)):this._$EM()}catch(s){throw t=!1,this._$EM(),s}t&&this._$AE(e)}willUpdate(t){}_$AE(t){var e;(e=this._$EO)==null||e.forEach(i=>{var s;return(s=i.hostUpdated)==null?void 0:s.call(i)}),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&(this._$Eq=this._$Eq.forEach(e=>this._$ET(e,this[e]))),this._$EM()}updated(t){}firstUpdated(t){}};S.elementStyles=[],S.shadowRootOptions={mode:"open"},S[U("elementProperties")]=new Map,S[U("finalized")]=new Map,W==null||W({ReactiveElement:S}),(m.reactiveElementVersions??(m.reactiveElementVersions=[])).push("2.1.1");/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */const M=globalThis,D=M.trustedTypes,ut=D?D.createPolicy("lit-html",{createHTML:r=>r}):void 0,dt="$lit$",b=`lit$${Math.random().toFixed(9).slice(2)}$`,pt="?"+b,Tt=`<${pt}>`,_=document,z=()=>_.createComment(""),O=r=>r===null||typeof r!="object"&&typeof r!="function",F=Array.isArray,Nt=r=>F(r)||typeof(r==null?void 0:r[Symbol.iterator])=="function",J=`[ +\f\r]`,H=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,ft=/-->/g,$t=/>/g,A=RegExp(`>|${J}(?:([^\\s"'>=/]+)(${J}*=${J}*(?:[^ +\f\r"'\`<>=]|("|')|))|$)`,"g"),gt=/'/g,mt=/"/g,bt=/^(?:script|style|textarea|title)$/i,Rt=r=>(t,...e)=>({_$litType$:r,strings:t,values:e}),I=Rt(1),v=Symbol.for("lit-noChange"),f=Symbol.for("lit-nothing"),yt=new WeakMap,w=_.createTreeWalker(_,129);function _t(r,t){if(!F(r)||!r.hasOwnProperty("raw"))throw Error("invalid template strings array");return ut!==void 0?ut.createHTML(t):t}const jt=(r,t)=>{const e=r.length-1,i=[];let s,o=t===2?"":t===3?"":"",n=H;for(let l=0;l"?(n=s??H,h=-1):p[1]===void 0?h=-2:(h=n.lastIndex-p[2].length,c=p[1],n=p[3]===void 0?A:p[3]==='"'?mt:gt):n===mt||n===gt?n=A:n===ft||n===$t?n=H:(n=A,s=void 0);const u=n===A&&r[l+1].startsWith("/>")?" ":"";o+=n===H?a+Tt:h>=0?(i.push(c),a.slice(0,h)+dt+a.slice(h)+b+u):a+b+(h===-2?l:u)}return[_t(r,o+(r[e]||"")+(t===2?"":t===3?"":"")),i]};class T{constructor({strings:t,_$litType$:e},i){let s;this.parts=[];let o=0,n=0;const l=t.length-1,a=this.parts,[c,p]=jt(t,e);if(this.el=T.createElement(c,i),w.currentNode=this.el.content,e===2||e===3){const h=this.el.content.firstChild;h.replaceWith(...h.childNodes)}for(;(s=w.nextNode())!==null&&a.length0){s.textContent=D?D.emptyScript:"";for(let u=0;u2||i[0]!==""||i[1]!==""?(this._$AH=Array(i.length-1).fill(new String),this.strings=i):this._$AH=f}_$AI(t,e=this,i,s){const o=this.strings;let n=!1;if(o===void 0)t=k(this,t,e,0),n=!O(t)||t!==this._$AH&&t!==v,n&&(this._$AH=t);else{const l=t;let a,c;for(t=o[0],a=0;a{const i=(e==null?void 0:e.renderBefore)??t;let s=i._$litPart$;if(s===void 0){const o=(e==null?void 0:e.renderBefore)??null;i._$litPart$=s=new C(t.insertBefore(z(),o),o,void 0,e??{})}return s._$AI(r),s};/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */const x=globalThis;let P=class extends S{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){var e;const t=super.createRenderRoot();return(e=this.renderOptions).renderBefore??(e.renderBefore=t.firstChild),t}update(t){const e=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=Wt(e,this.renderRoot,this.renderOptions)}connectedCallback(){var t;super.connectedCallback(),(t=this._$Do)==null||t.setConnected(!0)}disconnectedCallback(){var t;super.disconnectedCallback(),(t=this._$Do)==null||t.setConnected(!1)}render(){return v}};P._$litElement$=!0,P.finalized=!0,(xt=x.litElementHydrateSupport)==null||xt.call(x,{LitElement:P});const Q=x.litElementPolyfillSupport;Q==null||Q({LitElement:P}),(x.litElementVersions??(x.litElementVersions=[])).push("4.2.1");/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */const Zt={attribute:!0,type:String,converter:j,reflect:!1,hasChanged:Z},Ft=(r=Zt,t,e)=>{const{kind:i,metadata:s}=e;let o=globalThis.litPropertyMetadata.get(s);if(o===void 0&&globalThis.litPropertyMetadata.set(s,o=new Map),i==="setter"&&((r=Object.create(r)).wrapped=!0),o.set(e.name,r),i==="accessor"){const{name:n}=e;return{set(l){const a=t.get.call(this);t.set.call(this,l),this.requestUpdate(n,a,r)},init(l){return l!==void 0&&this.C(n,void 0,r,l),l}}}if(i==="setter"){const{name:n}=e;return function(l){const a=this[n];t.call(this,l),this.requestUpdate(n,a,r)}}throw Error("Unsupported decorator location: "+i)};function G(r){return(t,e)=>typeof e=="object"?Ft(r,t,e):((i,s,o)=>{const n=s.hasOwnProperty(o);return s.constructor.createProperty(o,i),n?Object.getOwnPropertyDescriptor(s,o):void 0})(r,t,e)}/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */function At(r){return G({...r,state:!0,attribute:!1})}/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */const Jt={CHILD:2},Kt=r=>(...t)=>({_$litDirective$:r,values:t});class Qt{constructor(t){}get _$AU(){return this._$AM._$AU}_$AT(t,e,i){this._$Ct=t,this._$AM=e,this._$Ci=i}_$AS(t,e){return this.update(t,e)}update(t,e){return this.render(...e)}}/** + * @license + * Copyright 2020 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */const{I:Gt}=Vt,vt=()=>document.createComment(""),N=(r,t,e)=>{var o;const i=r._$AA.parentNode,s=t===void 0?r._$AB:t._$AA;if(e===void 0){const n=i.insertBefore(vt(),s),l=i.insertBefore(vt(),s);e=new Gt(n,l,r,r.options)}else{const n=e._$AB.nextSibling,l=e._$AM,a=l!==r;if(a){let c;(o=e._$AQ)==null||o.call(e,r),e._$AM=r,e._$AP!==void 0&&(c=r._$AU)!==l._$AU&&e._$AP(c)}if(n!==s||a){let c=e._$AA;for(;c!==n;){const p=c.nextSibling;i.insertBefore(c,s),c=p}}}return e},E=(r,t,e=r)=>(r._$AI(t,e),r),Xt={},Yt=(r,t=Xt)=>r._$AH=t,te=r=>r._$AH,X=r=>{r._$AR(),r._$AA.remove()};/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */const wt=(r,t,e)=>{const i=new Map;for(let s=t;s<=e;s++)i.set(r[s],s);return i},ee=Kt(class extends Qt{constructor(r){if(super(r),r.type!==Jt.CHILD)throw Error("repeat() can only be used in text expressions")}dt(r,t,e){let i;e===void 0?e=t:t!==void 0&&(i=t);const s=[],o=[];let n=0;for(const l of r)s[n]=i?i(l,n):n,o[n]=e(l,n),n++;return{values:o,keys:s}}render(r,t,e){return this.dt(r,t,e).values}update(r,[t,e,i]){const s=te(r),{values:o,keys:n}=this.dt(t,e,i);if(!Array.isArray(s))return this.ut=n,o;const l=this.ut??(this.ut=[]),a=[];let c,p,h=0,d=s.length-1,u=0,$=o.length-1;for(;h<=d&&u<=$;)if(s[h]===null)h++;else if(s[d]===null)d--;else if(l[h]===n[u])a[u]=E(s[h],o[u]),h++,u++;else if(l[d]===n[$])a[$]=E(s[d],o[$]),d--,$--;else if(l[h]===n[$])a[$]=E(s[h],o[$]),N(r,a[$+1],s[h]),h++,$--;else if(l[d]===n[u])a[u]=E(s[d],o[u]),N(r,s[h],s[d]),d--,u++;else if(c===void 0&&(c=wt(n,u,$),p=wt(l,h,d)),c.has(l[h]))if(c.has(l[d])){const g=p.get(n[u]),st=g!==void 0?s[g]:null;if(st===null){const Et=N(r,s[h]);E(Et,o[u]),a[u]=Et}else a[u]=E(st,o[u]),N(r,s[h],st),s[g]=null;u++}else X(s[d]),d--;else X(s[h]),h++;for(;u<=$;){const g=N(r,a[$+1]);E(g,o[u]),a[u++]=g}for(;h<=d;){const g=s[h++];g!==null&&X(g)}return this.ut=n,Yt(r,a),v}}),tt=class tt extends P{render(){return I` + + + + + `}};tt.styles=at` + :host { + display: flex; + align-items: center; + justify-content: center; + padding: 1em; + } + svg { + height: 1.25em; + width: 1.25em; + animation: spin 1s linear infinite; + } + + @keyframes spin { + to { + transform: rotate(360deg); + } + } + `;let Y=tt;customElements.get("loading-bar")||customElements.define("loading-bar",Y);var se=Object.defineProperty,L=(r,t,e,i)=>{for(var s=void 0,o=r.length-1,n;o>=0;o--)(n=r[o])&&(s=n(t,e,s)||s);return s&&se(t,e,s),s};const et=class et extends P{constructor(){super(...arguments),this.src="",this.layout="list",this.loading=!1}connectedCallback(){super.connectedCallback(),this.fetchRssDetail()}async fetchRssDetail(){try{this.loading=!0;const t=await fetch(`/apis/api.friend.moony.la/v1alpha1/parsingrss?rssUrl=${this.src}`);if(!t.ok)throw new Error("Failed to fetch site data");this.rssDetail=await t.json()}catch(t){console.error(t)}finally{this.loading=!1}}render(){var t;return this.loading?I``:I` + + `}};et.styles=[ot(St),at` + :host { + display: inline-block; + width: 100%; + } + /* layer: preflights */ +*,::before,::after{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset: ;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / 0.5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: ;}::backdrop{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset: ;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / 0.5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: ;} +/* layer: shortcuts */ +.text-description{color:var(--friends-rss-description-color,#71717a);} +.text-title{color:var(--friends-rss-title-color,#18181b);} +/* layer: default */ +.relative{position:relative;} +.static{position:static;} +.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;} +.z-\\[1\\]{z-index:1;} +.grid{display:grid;} +.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr));} +.inline-block{display:inline-block;} +.space-y-1>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(0.25rem * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(0.25rem * var(--un-space-y-reverse));} +.p-2{padding:0.5rem;} +.text-base{font-size:1rem;line-height:1.5rem;} +.text-sm{font-size:0.875rem;line-height:1.25rem;} +.font-semibold{font-weight:600;} +@media (min-width: 640px){ +.sm\\:flex-row{flex-direction:row;} +} +@media (min-width: 1024px){ +.lg\\:line-clamp-1{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:1;line-clamp:1;} +}; + `];let y=et;L([G({type:String})],y.prototype,"src"),L([G({type:String})],y.prototype,"layout"),L([At()],y.prototype,"rssDetail"),L([At()],y.prototype,"loading"),customElements.get("friends-rss")||customElements.define("friends-rss",y),it.FriendsRss=y,Object.defineProperty(it,Symbol.toStringTag,{value:"Module"})})(this["friends-rss"]=this["friends-rss"]||{}); diff --git a/src/main/resources/static/var.css b/src/main/resources/static/var.css new file mode 100644 index 0000000..e09bac7 --- /dev/null +++ b/src/main/resources/static/var.css @@ -0,0 +1,16 @@ +@media (prefers-color-scheme: dark) { + .color-scheme-auto, + [data-color-scheme='auto'] friends-rss { + color-scheme: dark; + --friends-rss-title-color: #f4f4f5; + --friends-rss-description-color: #a1a1aa; + } +} + +.color-scheme-dark, +.dark, +[data-color-scheme='dark'] friends-rss { + color-scheme: dark; + --friends-rss-title-color: #f4f4f5; + --friends-rss-description-color: #a1a1aa; +} diff --git a/src/main/resources/templates/friends.html b/src/main/resources/templates/friends.html new file mode 100644 index 0000000..cdd805c --- /dev/null +++ b/src/main/resources/templates/friends.html @@ -0,0 +1,1559 @@ + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+

+

这里只是我而已。

+ +
+
+
+
+
+

+
+

+
+
+ + + + + + + +
+
+
+ +
+ + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/ui/.eslintrc.cjs b/ui/.eslintrc.cjs new file mode 100644 index 0000000..16a4d81 --- /dev/null +++ b/ui/.eslintrc.cjs @@ -0,0 +1,22 @@ +/* eslint-env node */ +require("@rushstack/eslint-patch/modern-module-resolution"); + +module.exports = { + root: true, + extends: [ + "plugin:vue/vue3-recommended", + "eslint:recommended", + "@vue/eslint-config-typescript/recommended", + "@vue/eslint-config-prettier", + '@unocss', + ], + parserOptions: { + ecmaVersion: "latest", + }, + env: { + "vue/setup-compiler-macros": true, + }, + rules: { + "@unocss/enforce-class-compile": 1, + }, +}; diff --git a/ui/env.d.ts b/ui/env.d.ts new file mode 100644 index 0000000..3301ad5 --- /dev/null +++ b/ui/env.d.ts @@ -0,0 +1,16 @@ +/// +/// + +export {}; + +declare module "axios" { + export interface AxiosRequestConfig { + mute?: boolean; + } +} + +declare module "vue" { + interface ComponentCustomProperties { + $formkit: any; + } +} diff --git a/ui/package.json b/ui/package.json new file mode 100644 index 0000000..67f60cb --- /dev/null +++ b/ui/package.json @@ -0,0 +1,71 @@ +{ + "type": "module", + "scripts": { + "build": "rsbuild build", + "dev": "rsbuild build --env-mode development --watch", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path ../.gitignore", + "prettier": "prettier --write src/", + "type-check": "vue-tsc --build --force" + }, + "prettier": { + "arrowParens": "always", + "bracketSpacing": true, + "endOfLine": "lf", + "plugins": [ + "prettier-plugin-organize-imports" + ], + "printWidth": 100, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "es5", + "useTabs": false + }, + "dependencies": { + "@halo-dev/api-client": "^2.21.1", + "@halo-dev/components": "2.21.0", + "@halo-dev/console-shared": "^2.21.0", + "@halo-dev/richtext-editor": "^2.21.0", + "axios": "^1.7.2", + "canvas-confetti": "^1.9.3", + "vue": "^3.4.31", + "@types/lodash.clonedeep": "^4.5.7", + "lodash.clonedeep": "^4.5.0", + "@vueuse/router": "^10.6.0", + "@tanstack/vue-query": "^4.26.1", + "dayjs": "^1.11.9", + "javascript-time-ago": "^2.5.9", + "@vueuse/shared": "^10.9.0", + "@formkit/core": "^1.4.0", + "@kunkunyu/fridends-rss": "workspace:*" + }, + "devDependencies": { + "@halo-dev/ui-plugin-bundler-kit": "^2.21.2", + "@iconify/json": "^2.2.224", + "@rspack/cli": "1.4.0-beta.0", + "@rspack/core": "1.4.0-beta.0", + "@rushstack/eslint-patch": "^1.10.3", + "@tsconfig/node20": "^20.1.4", + "@types/canvas-confetti": "^1.6.4", + "@types/jsdom": "^21.1.7", + "@types/node": "^20.14.9", + "@unocss/eslint-config": "^66.2.0", + "@unocss/webpack": "^66.2.0", + "@vue/eslint-config-prettier": "^9.0.0", + "@vue/eslint-config-typescript": "^13.0.0", + "@vue/test-utils": "^2.4.6", + "@vue/tsconfig": "^0.5.1", + "css-loader": "^7.1.2", + "eslint": "^8.57.0", + "eslint-plugin-vue": "^9.26.0", + "jsdom": "^24.1.0", + "npm-run-all2": "^6.2.0", + "prettier": "^3.3.2", + "prettier-plugin-organize-imports": "^4.2.0", + "style-loader": "^4.0.0", + "typescript": "~5.5.3", + "unocss": "^66.2.0", + "unplugin-icons": "^22.1.0", + "vue-loader": "^17.4.2", + "vue-tsc": "^2.0.24" + } +} diff --git a/ui/pnpm-workspace.yaml b/ui/pnpm-workspace.yaml new file mode 100644 index 0000000..045dc7f --- /dev/null +++ b/ui/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +onlyBuiltDependencies: + - core-js + - esbuild + - vue-demi diff --git a/ui/rsbuild.config.mjs b/ui/rsbuild.config.mjs new file mode 100644 index 0000000..73c9a58 --- /dev/null +++ b/ui/rsbuild.config.mjs @@ -0,0 +1,36 @@ +import { rsbuildConfig } from '@halo-dev/ui-plugin-bundler-kit'; +import { UnoCSSRspackPlugin } from '@unocss/webpack/rspack'; +import Icons from 'unplugin-icons/rspack'; + +const OUT_DIR_PROD = '../src/main/resources/console'; +const OUT_DIR_DEV = '../build/resources/main/console'; + +export default rsbuildConfig({ + rsbuild: ({ envMode }) => { + const isProduction = envMode === 'production'; + const outDir = isProduction ? OUT_DIR_PROD : OUT_DIR_DEV; + + return { + resolve: { + alias: { + '@': './src', + }, + }, + output: { + distPath: { + root: outDir, + }, + }, + tools: { + rspack: { + plugins: [ + Icons({ + compiler: 'vue3', + }), + UnoCSSRspackPlugin(), + ], + }, + }, + }; + }, +}); diff --git a/ui/src/api/generated/.gitignore b/ui/src/api/generated/.gitignore new file mode 100644 index 0000000..149b576 --- /dev/null +++ b/ui/src/api/generated/.gitignore @@ -0,0 +1,4 @@ +wwwroot/*.js +node_modules +typings +dist diff --git a/ui/src/api/generated/.npmignore b/ui/src/api/generated/.npmignore new file mode 100644 index 0000000..999d88d --- /dev/null +++ b/ui/src/api/generated/.npmignore @@ -0,0 +1 @@ +# empty npmignore to ensure all required files (e.g., in the dist folder) are published by npm \ No newline at end of file diff --git a/ui/src/api/generated/.openapi-generator-ignore b/ui/src/api/generated/.openapi-generator-ignore new file mode 100644 index 0000000..7484ee5 --- /dev/null +++ b/ui/src/api/generated/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/ui/src/api/generated/.openapi-generator/FILES b/ui/src/api/generated/.openapi-generator/FILES new file mode 100644 index 0000000..6ee88a2 --- /dev/null +++ b/ui/src/api/generated/.openapi-generator/FILES @@ -0,0 +1,38 @@ +.gitignore +.npmignore +.openapi-generator-ignore +api.ts +api/api-friend-moony-la-v1alpha1-friend-post-api.ts +api/cron-friend-post-v1alpha1-api.ts +api/friend-post-v1alpha1-api.ts +api/rss-feed-sync-log-v1alpha1-api.ts +base.ts +common.ts +configuration.ts +git_push.sh +index.ts +models/add-operation.ts +models/channel.ts +models/copy-operation.ts +models/cron-friend-post-list.ts +models/cron-friend-post.ts +models/cron-spec.ts +models/cron-status.ts +models/friend-post-list.ts +models/friend-post-spec.ts +models/friend-post.ts +models/index.ts +models/item.ts +models/json-patch-inner.ts +models/link-spec.ts +models/link.ts +models/listed-rss-sync-log-list.ts +models/listed-rss-sync-log.ts +models/metadata.ts +models/move-operation.ts +models/remove-operation.ts +models/replace-operation.ts +models/rss-detail.ts +models/rss-feed-sync-log-list.ts +models/rss-feed-sync-log.ts +models/test-operation.ts diff --git a/ui/src/api/generated/.openapi-generator/VERSION b/ui/src/api/generated/.openapi-generator/VERSION new file mode 100644 index 0000000..1985849 --- /dev/null +++ b/ui/src/api/generated/.openapi-generator/VERSION @@ -0,0 +1 @@ +7.7.0 diff --git a/ui/src/api/generated/api.ts b/ui/src/api/generated/api.ts new file mode 100644 index 0000000..4c5a0b6 --- /dev/null +++ b/ui/src/api/generated/api.ts @@ -0,0 +1,21 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +export * from './api/api-friend-moony-la-v1alpha1-friend-post-api'; +export * from './api/cron-friend-post-v1alpha1-api'; +export * from './api/friend-post-v1alpha1-api'; +export * from './api/rss-feed-sync-log-v1alpha1-api'; + diff --git a/ui/src/api/generated/api/api-friend-moony-la-v1alpha1-friend-post-api.ts b/ui/src/api/generated/api/api-friend-moony-la-v1alpha1-friend-post-api.ts new file mode 100644 index 0000000..f6c4667 --- /dev/null +++ b/ui/src/api/generated/api/api-friend-moony-la-v1alpha1-friend-post-api.ts @@ -0,0 +1,654 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import type { Configuration } from '../configuration'; +import type { AxiosPromise, AxiosInstance, RawAxiosRequestConfig } from 'axios'; +import globalAxios from 'axios'; +// Some imports not used depending on template conditions +// @ts-ignore +import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from '../common'; +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS, type RequestArgs, BaseAPI, RequiredError, operationServerMap } from '../base'; +// @ts-ignore +import type { FriendPostList } from '../models'; +// @ts-ignore +import type { ListedRssSyncLogList } from '../models'; +// @ts-ignore +import type { RssDetail } from '../models'; +/** + * ApiFriendMoonyLaV1alpha1FriendPostApi - axios parameter creator + * @export + */ +export const ApiFriendMoonyLaV1alpha1FriendPostApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * Delete All RssFeedSyncLog. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deleteAllRssFeedSyncLog: async (options: RawAxiosRequestConfig = {}): Promise => { + const localVarPath = `/apis/api.friend.moony.la/v1alpha1/friendposts/-/delete`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication basicAuth required + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration) + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * List friendPost. + * @param {number} [page] Page number. Default is 0. + * @param {number} [size] Size number. Default is 0. + * @param {Array} [labelSelector] Label selector. e.g.: hidden!=true + * @param {Array} [fieldSelector] Field selector. e.g.: metadata.name==halo + * @param {Array} [sort] Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. + * @param {string} [linkName] CronFriendPost filtered by linkName. + * @param {string} [keyword] CronFriendPost filtered by keyword. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listFriendPosts: async (page?: number, size?: number, labelSelector?: Array, fieldSelector?: Array, sort?: Array, linkName?: string, keyword?: string, options: RawAxiosRequestConfig = {}): Promise => { + const localVarPath = `/apis/api.friend.moony.la/v1alpha1/friendposts`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication basicAuth required + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration) + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + if (page !== undefined) { + localVarQueryParameter['page'] = page; + } + + if (size !== undefined) { + localVarQueryParameter['size'] = size; + } + + if (labelSelector) { + localVarQueryParameter['labelSelector'] = labelSelector; + } + + if (fieldSelector) { + localVarQueryParameter['fieldSelector'] = fieldSelector; + } + + if (sort) { + localVarQueryParameter['sort'] = sort; + } + + if (linkName !== undefined) { + localVarQueryParameter['linkName'] = linkName; + } + + if (keyword !== undefined) { + localVarQueryParameter['keyword'] = keyword; + } + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * List RssSyncLog. + * @param {number} [page] Page number. Default is 0. + * @param {number} [size] Size number. Default is 0. + * @param {Array} [labelSelector] Label selector. e.g.: hidden!=true + * @param {Array} [fieldSelector] Field selector. e.g.: metadata.name==halo + * @param {Array} [sort] Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. + * @param {string} [keyword] + * @param {string} [state] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listRssSyncLogs: async (page?: number, size?: number, labelSelector?: Array, fieldSelector?: Array, sort?: Array, keyword?: string, state?: string, options: RawAxiosRequestConfig = {}): Promise => { + const localVarPath = `/apis/api.friend.moony.la/v1alpha1/rsssynclogs`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication basicAuth required + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration) + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + if (page !== undefined) { + localVarQueryParameter['page'] = page; + } + + if (size !== undefined) { + localVarQueryParameter['size'] = size; + } + + if (labelSelector) { + localVarQueryParameter['labelSelector'] = labelSelector; + } + + if (fieldSelector) { + localVarQueryParameter['fieldSelector'] = fieldSelector; + } + + if (sort) { + localVarQueryParameter['sort'] = sort; + } + + if (keyword !== undefined) { + localVarQueryParameter['keyword'] = keyword; + } + + if (state !== undefined) { + localVarQueryParameter['state'] = state; + } + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {string} rssUrl + * @param {string} [fetchLimitNumber] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + parsingRss: async (rssUrl: string, fetchLimitNumber?: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'rssUrl' is not null or undefined + assertParamExists('parsingRss', 'rssUrl', rssUrl) + const localVarPath = `/apis/api.friend.moony.la/v1alpha1/parsingrss`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication basicAuth required + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration) + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + if (rssUrl !== undefined) { + localVarQueryParameter['rssUrl'] = rssUrl; + } + + if (fetchLimitNumber !== undefined) { + localVarQueryParameter['fetchLimitNumber'] = fetchLimitNumber; + } + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {string} name + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + syncRssFeed: async (name: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'name' is not null or undefined + assertParamExists('syncRssFeed', 'name', name) + const localVarPath = `/apis/api.friend.moony.la/v1alpha1/syncrssfeed/{name}` + .replace(`{${"name"}}`, encodeURIComponent(String(name))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication basicAuth required + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration) + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * ApiFriendMoonyLaV1alpha1FriendPostApi - functional programming interface + * @export + */ +export const ApiFriendMoonyLaV1alpha1FriendPostApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = ApiFriendMoonyLaV1alpha1FriendPostApiAxiosParamCreator(configuration) + return { + /** + * Delete All RssFeedSyncLog. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async deleteAllRssFeedSyncLog(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.deleteAllRssFeedSyncLog(options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['ApiFriendMoonyLaV1alpha1FriendPostApi.deleteAllRssFeedSyncLog']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * List friendPost. + * @param {number} [page] Page number. Default is 0. + * @param {number} [size] Size number. Default is 0. + * @param {Array} [labelSelector] Label selector. e.g.: hidden!=true + * @param {Array} [fieldSelector] Field selector. e.g.: metadata.name==halo + * @param {Array} [sort] Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. + * @param {string} [linkName] CronFriendPost filtered by linkName. + * @param {string} [keyword] CronFriendPost filtered by keyword. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async listFriendPosts(page?: number, size?: number, labelSelector?: Array, fieldSelector?: Array, sort?: Array, linkName?: string, keyword?: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.listFriendPosts(page, size, labelSelector, fieldSelector, sort, linkName, keyword, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['ApiFriendMoonyLaV1alpha1FriendPostApi.listFriendPosts']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * List RssSyncLog. + * @param {number} [page] Page number. Default is 0. + * @param {number} [size] Size number. Default is 0. + * @param {Array} [labelSelector] Label selector. e.g.: hidden!=true + * @param {Array} [fieldSelector] Field selector. e.g.: metadata.name==halo + * @param {Array} [sort] Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. + * @param {string} [keyword] + * @param {string} [state] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async listRssSyncLogs(page?: number, size?: number, labelSelector?: Array, fieldSelector?: Array, sort?: Array, keyword?: string, state?: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.listRssSyncLogs(page, size, labelSelector, fieldSelector, sort, keyword, state, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['ApiFriendMoonyLaV1alpha1FriendPostApi.listRssSyncLogs']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * + * @param {string} rssUrl + * @param {string} [fetchLimitNumber] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async parsingRss(rssUrl: string, fetchLimitNumber?: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.parsingRss(rssUrl, fetchLimitNumber, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['ApiFriendMoonyLaV1alpha1FriendPostApi.parsingRss']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * + * @param {string} name + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async syncRssFeed(name: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.syncRssFeed(name, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['ApiFriendMoonyLaV1alpha1FriendPostApi.syncRssFeed']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + } +}; + +/** + * ApiFriendMoonyLaV1alpha1FriendPostApi - factory interface + * @export + */ +export const ApiFriendMoonyLaV1alpha1FriendPostApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = ApiFriendMoonyLaV1alpha1FriendPostApiFp(configuration) + return { + /** + * Delete All RssFeedSyncLog. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deleteAllRssFeedSyncLog(options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.deleteAllRssFeedSyncLog(options).then((request) => request(axios, basePath)); + }, + /** + * List friendPost. + * @param {ApiFriendMoonyLaV1alpha1FriendPostApiListFriendPostsRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listFriendPosts(requestParameters: ApiFriendMoonyLaV1alpha1FriendPostApiListFriendPostsRequest = {}, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.listFriendPosts(requestParameters.page, requestParameters.size, requestParameters.labelSelector, requestParameters.fieldSelector, requestParameters.sort, requestParameters.linkName, requestParameters.keyword, options).then((request) => request(axios, basePath)); + }, + /** + * List RssSyncLog. + * @param {ApiFriendMoonyLaV1alpha1FriendPostApiListRssSyncLogsRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listRssSyncLogs(requestParameters: ApiFriendMoonyLaV1alpha1FriendPostApiListRssSyncLogsRequest = {}, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.listRssSyncLogs(requestParameters.page, requestParameters.size, requestParameters.labelSelector, requestParameters.fieldSelector, requestParameters.sort, requestParameters.keyword, requestParameters.state, options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {ApiFriendMoonyLaV1alpha1FriendPostApiParsingRssRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + parsingRss(requestParameters: ApiFriendMoonyLaV1alpha1FriendPostApiParsingRssRequest, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.parsingRss(requestParameters.rssUrl, requestParameters.fetchLimitNumber, options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {ApiFriendMoonyLaV1alpha1FriendPostApiSyncRssFeedRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + syncRssFeed(requestParameters: ApiFriendMoonyLaV1alpha1FriendPostApiSyncRssFeedRequest, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.syncRssFeed(requestParameters.name, options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * Request parameters for listFriendPosts operation in ApiFriendMoonyLaV1alpha1FriendPostApi. + * @export + * @interface ApiFriendMoonyLaV1alpha1FriendPostApiListFriendPostsRequest + */ +export interface ApiFriendMoonyLaV1alpha1FriendPostApiListFriendPostsRequest { + /** + * Page number. Default is 0. + * @type {number} + * @memberof ApiFriendMoonyLaV1alpha1FriendPostApiListFriendPosts + */ + readonly page?: number + + /** + * Size number. Default is 0. + * @type {number} + * @memberof ApiFriendMoonyLaV1alpha1FriendPostApiListFriendPosts + */ + readonly size?: number + + /** + * Label selector. e.g.: hidden!=true + * @type {Array} + * @memberof ApiFriendMoonyLaV1alpha1FriendPostApiListFriendPosts + */ + readonly labelSelector?: Array + + /** + * Field selector. e.g.: metadata.name==halo + * @type {Array} + * @memberof ApiFriendMoonyLaV1alpha1FriendPostApiListFriendPosts + */ + readonly fieldSelector?: Array + + /** + * Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. + * @type {Array} + * @memberof ApiFriendMoonyLaV1alpha1FriendPostApiListFriendPosts + */ + readonly sort?: Array + + /** + * CronFriendPost filtered by linkName. + * @type {string} + * @memberof ApiFriendMoonyLaV1alpha1FriendPostApiListFriendPosts + */ + readonly linkName?: string + + /** + * CronFriendPost filtered by keyword. + * @type {string} + * @memberof ApiFriendMoonyLaV1alpha1FriendPostApiListFriendPosts + */ + readonly keyword?: string +} + +/** + * Request parameters for listRssSyncLogs operation in ApiFriendMoonyLaV1alpha1FriendPostApi. + * @export + * @interface ApiFriendMoonyLaV1alpha1FriendPostApiListRssSyncLogsRequest + */ +export interface ApiFriendMoonyLaV1alpha1FriendPostApiListRssSyncLogsRequest { + /** + * Page number. Default is 0. + * @type {number} + * @memberof ApiFriendMoonyLaV1alpha1FriendPostApiListRssSyncLogs + */ + readonly page?: number + + /** + * Size number. Default is 0. + * @type {number} + * @memberof ApiFriendMoonyLaV1alpha1FriendPostApiListRssSyncLogs + */ + readonly size?: number + + /** + * Label selector. e.g.: hidden!=true + * @type {Array} + * @memberof ApiFriendMoonyLaV1alpha1FriendPostApiListRssSyncLogs + */ + readonly labelSelector?: Array + + /** + * Field selector. e.g.: metadata.name==halo + * @type {Array} + * @memberof ApiFriendMoonyLaV1alpha1FriendPostApiListRssSyncLogs + */ + readonly fieldSelector?: Array + + /** + * Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. + * @type {Array} + * @memberof ApiFriendMoonyLaV1alpha1FriendPostApiListRssSyncLogs + */ + readonly sort?: Array + + /** + * + * @type {string} + * @memberof ApiFriendMoonyLaV1alpha1FriendPostApiListRssSyncLogs + */ + readonly keyword?: string + + /** + * + * @type {string} + * @memberof ApiFriendMoonyLaV1alpha1FriendPostApiListRssSyncLogs + */ + readonly state?: string +} + +/** + * Request parameters for parsingRss operation in ApiFriendMoonyLaV1alpha1FriendPostApi. + * @export + * @interface ApiFriendMoonyLaV1alpha1FriendPostApiParsingRssRequest + */ +export interface ApiFriendMoonyLaV1alpha1FriendPostApiParsingRssRequest { + /** + * + * @type {string} + * @memberof ApiFriendMoonyLaV1alpha1FriendPostApiParsingRss + */ + readonly rssUrl: string + + /** + * + * @type {string} + * @memberof ApiFriendMoonyLaV1alpha1FriendPostApiParsingRss + */ + readonly fetchLimitNumber?: string +} + +/** + * Request parameters for syncRssFeed operation in ApiFriendMoonyLaV1alpha1FriendPostApi. + * @export + * @interface ApiFriendMoonyLaV1alpha1FriendPostApiSyncRssFeedRequest + */ +export interface ApiFriendMoonyLaV1alpha1FriendPostApiSyncRssFeedRequest { + /** + * + * @type {string} + * @memberof ApiFriendMoonyLaV1alpha1FriendPostApiSyncRssFeed + */ + readonly name: string +} + +/** + * ApiFriendMoonyLaV1alpha1FriendPostApi - object-oriented interface + * @export + * @class ApiFriendMoonyLaV1alpha1FriendPostApi + * @extends {BaseAPI} + */ +export class ApiFriendMoonyLaV1alpha1FriendPostApi extends BaseAPI { + /** + * Delete All RssFeedSyncLog. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ApiFriendMoonyLaV1alpha1FriendPostApi + */ + public deleteAllRssFeedSyncLog(options?: RawAxiosRequestConfig) { + return ApiFriendMoonyLaV1alpha1FriendPostApiFp(this.configuration).deleteAllRssFeedSyncLog(options).then((request) => request(this.axios, this.basePath)); + } + + /** + * List friendPost. + * @param {ApiFriendMoonyLaV1alpha1FriendPostApiListFriendPostsRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ApiFriendMoonyLaV1alpha1FriendPostApi + */ + public listFriendPosts(requestParameters: ApiFriendMoonyLaV1alpha1FriendPostApiListFriendPostsRequest = {}, options?: RawAxiosRequestConfig) { + return ApiFriendMoonyLaV1alpha1FriendPostApiFp(this.configuration).listFriendPosts(requestParameters.page, requestParameters.size, requestParameters.labelSelector, requestParameters.fieldSelector, requestParameters.sort, requestParameters.linkName, requestParameters.keyword, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * List RssSyncLog. + * @param {ApiFriendMoonyLaV1alpha1FriendPostApiListRssSyncLogsRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ApiFriendMoonyLaV1alpha1FriendPostApi + */ + public listRssSyncLogs(requestParameters: ApiFriendMoonyLaV1alpha1FriendPostApiListRssSyncLogsRequest = {}, options?: RawAxiosRequestConfig) { + return ApiFriendMoonyLaV1alpha1FriendPostApiFp(this.configuration).listRssSyncLogs(requestParameters.page, requestParameters.size, requestParameters.labelSelector, requestParameters.fieldSelector, requestParameters.sort, requestParameters.keyword, requestParameters.state, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {ApiFriendMoonyLaV1alpha1FriendPostApiParsingRssRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ApiFriendMoonyLaV1alpha1FriendPostApi + */ + public parsingRss(requestParameters: ApiFriendMoonyLaV1alpha1FriendPostApiParsingRssRequest, options?: RawAxiosRequestConfig) { + return ApiFriendMoonyLaV1alpha1FriendPostApiFp(this.configuration).parsingRss(requestParameters.rssUrl, requestParameters.fetchLimitNumber, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {ApiFriendMoonyLaV1alpha1FriendPostApiSyncRssFeedRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ApiFriendMoonyLaV1alpha1FriendPostApi + */ + public syncRssFeed(requestParameters: ApiFriendMoonyLaV1alpha1FriendPostApiSyncRssFeedRequest, options?: RawAxiosRequestConfig) { + return ApiFriendMoonyLaV1alpha1FriendPostApiFp(this.configuration).syncRssFeed(requestParameters.name, options).then((request) => request(this.axios, this.basePath)); + } +} + diff --git a/ui/src/api/generated/api/cron-friend-post-v1alpha1-api.ts b/ui/src/api/generated/api/cron-friend-post-v1alpha1-api.ts new file mode 100644 index 0000000..c8bfd6a --- /dev/null +++ b/ui/src/api/generated/api/cron-friend-post-v1alpha1-api.ts @@ -0,0 +1,665 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import type { Configuration } from '../configuration'; +import type { AxiosPromise, AxiosInstance, RawAxiosRequestConfig } from 'axios'; +import globalAxios from 'axios'; +// Some imports not used depending on template conditions +// @ts-ignore +import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from '../common'; +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS, type RequestArgs, BaseAPI, RequiredError, operationServerMap } from '../base'; +// @ts-ignore +import type { CronFriendPost } from '../models'; +// @ts-ignore +import type { CronFriendPostList } from '../models'; +// @ts-ignore +import type { JsonPatchInner } from '../models'; +/** + * CronFriendPostV1alpha1Api - axios parameter creator + * @export + */ +export const CronFriendPostV1alpha1ApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * Create CronFriendPost + * @param {CronFriendPost} [cronFriendPost] Fresh cronfriendpost + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + createCronFriendPost: async (cronFriendPost?: CronFriendPost, options: RawAxiosRequestConfig = {}): Promise => { + const localVarPath = `/apis/friend.moony.la/v1alpha1/cronfriendposts`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication basicAuth required + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration) + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(cronFriendPost, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Delete CronFriendPost + * @param {string} name Name of cronfriendpost + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deleteCronFriendPost: async (name: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'name' is not null or undefined + assertParamExists('deleteCronFriendPost', 'name', name) + const localVarPath = `/apis/friend.moony.la/v1alpha1/cronfriendposts/{name}` + .replace(`{${"name"}}`, encodeURIComponent(String(name))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication basicAuth required + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration) + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Get CronFriendPost + * @param {string} name Name of cronfriendpost + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getCronFriendPost: async (name: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'name' is not null or undefined + assertParamExists('getCronFriendPost', 'name', name) + const localVarPath = `/apis/friend.moony.la/v1alpha1/cronfriendposts/{name}` + .replace(`{${"name"}}`, encodeURIComponent(String(name))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication basicAuth required + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration) + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * List CronFriendPost + * @param {number} [page] Page number. Default is 0. + * @param {number} [size] Size number. Default is 0. + * @param {Array} [labelSelector] Label selector. e.g.: hidden!=true + * @param {Array} [fieldSelector] Field selector. e.g.: metadata.name==halo + * @param {Array} [sort] Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listCronFriendPost: async (page?: number, size?: number, labelSelector?: Array, fieldSelector?: Array, sort?: Array, options: RawAxiosRequestConfig = {}): Promise => { + const localVarPath = `/apis/friend.moony.la/v1alpha1/cronfriendposts`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication basicAuth required + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration) + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + if (page !== undefined) { + localVarQueryParameter['page'] = page; + } + + if (size !== undefined) { + localVarQueryParameter['size'] = size; + } + + if (labelSelector) { + localVarQueryParameter['labelSelector'] = labelSelector; + } + + if (fieldSelector) { + localVarQueryParameter['fieldSelector'] = fieldSelector; + } + + if (sort) { + localVarQueryParameter['sort'] = sort; + } + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Patch CronFriendPost + * @param {string} name Name of cronfriendpost + * @param {Array} [jsonPatchInner] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + patchCronFriendPost: async (name: string, jsonPatchInner?: Array, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'name' is not null or undefined + assertParamExists('patchCronFriendPost', 'name', name) + const localVarPath = `/apis/friend.moony.la/v1alpha1/cronfriendposts/{name}` + .replace(`{${"name"}}`, encodeURIComponent(String(name))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'PATCH', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication basicAuth required + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration) + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + localVarHeaderParameter['Content-Type'] = 'application/json-patch+json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(jsonPatchInner, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Update CronFriendPost + * @param {string} name Name of cronfriendpost + * @param {CronFriendPost} [cronFriendPost] Updated cronfriendpost + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updateCronFriendPost: async (name: string, cronFriendPost?: CronFriendPost, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'name' is not null or undefined + assertParamExists('updateCronFriendPost', 'name', name) + const localVarPath = `/apis/friend.moony.la/v1alpha1/cronfriendposts/{name}` + .replace(`{${"name"}}`, encodeURIComponent(String(name))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication basicAuth required + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration) + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(cronFriendPost, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * CronFriendPostV1alpha1Api - functional programming interface + * @export + */ +export const CronFriendPostV1alpha1ApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = CronFriendPostV1alpha1ApiAxiosParamCreator(configuration) + return { + /** + * Create CronFriendPost + * @param {CronFriendPost} [cronFriendPost] Fresh cronfriendpost + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async createCronFriendPost(cronFriendPost?: CronFriendPost, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.createCronFriendPost(cronFriendPost, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['CronFriendPostV1alpha1Api.createCronFriendPost']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Delete CronFriendPost + * @param {string} name Name of cronfriendpost + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async deleteCronFriendPost(name: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.deleteCronFriendPost(name, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['CronFriendPostV1alpha1Api.deleteCronFriendPost']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Get CronFriendPost + * @param {string} name Name of cronfriendpost + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getCronFriendPost(name: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getCronFriendPost(name, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['CronFriendPostV1alpha1Api.getCronFriendPost']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * List CronFriendPost + * @param {number} [page] Page number. Default is 0. + * @param {number} [size] Size number. Default is 0. + * @param {Array} [labelSelector] Label selector. e.g.: hidden!=true + * @param {Array} [fieldSelector] Field selector. e.g.: metadata.name==halo + * @param {Array} [sort] Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async listCronFriendPost(page?: number, size?: number, labelSelector?: Array, fieldSelector?: Array, sort?: Array, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.listCronFriendPost(page, size, labelSelector, fieldSelector, sort, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['CronFriendPostV1alpha1Api.listCronFriendPost']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Patch CronFriendPost + * @param {string} name Name of cronfriendpost + * @param {Array} [jsonPatchInner] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async patchCronFriendPost(name: string, jsonPatchInner?: Array, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.patchCronFriendPost(name, jsonPatchInner, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['CronFriendPostV1alpha1Api.patchCronFriendPost']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Update CronFriendPost + * @param {string} name Name of cronfriendpost + * @param {CronFriendPost} [cronFriendPost] Updated cronfriendpost + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async updateCronFriendPost(name: string, cronFriendPost?: CronFriendPost, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.updateCronFriendPost(name, cronFriendPost, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['CronFriendPostV1alpha1Api.updateCronFriendPost']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + } +}; + +/** + * CronFriendPostV1alpha1Api - factory interface + * @export + */ +export const CronFriendPostV1alpha1ApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = CronFriendPostV1alpha1ApiFp(configuration) + return { + /** + * Create CronFriendPost + * @param {CronFriendPostV1alpha1ApiCreateCronFriendPostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + createCronFriendPost(requestParameters: CronFriendPostV1alpha1ApiCreateCronFriendPostRequest = {}, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.createCronFriendPost(requestParameters.cronFriendPost, options).then((request) => request(axios, basePath)); + }, + /** + * Delete CronFriendPost + * @param {CronFriendPostV1alpha1ApiDeleteCronFriendPostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deleteCronFriendPost(requestParameters: CronFriendPostV1alpha1ApiDeleteCronFriendPostRequest, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.deleteCronFriendPost(requestParameters.name, options).then((request) => request(axios, basePath)); + }, + /** + * Get CronFriendPost + * @param {CronFriendPostV1alpha1ApiGetCronFriendPostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getCronFriendPost(requestParameters: CronFriendPostV1alpha1ApiGetCronFriendPostRequest, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.getCronFriendPost(requestParameters.name, options).then((request) => request(axios, basePath)); + }, + /** + * List CronFriendPost + * @param {CronFriendPostV1alpha1ApiListCronFriendPostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listCronFriendPost(requestParameters: CronFriendPostV1alpha1ApiListCronFriendPostRequest = {}, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.listCronFriendPost(requestParameters.page, requestParameters.size, requestParameters.labelSelector, requestParameters.fieldSelector, requestParameters.sort, options).then((request) => request(axios, basePath)); + }, + /** + * Patch CronFriendPost + * @param {CronFriendPostV1alpha1ApiPatchCronFriendPostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + patchCronFriendPost(requestParameters: CronFriendPostV1alpha1ApiPatchCronFriendPostRequest, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.patchCronFriendPost(requestParameters.name, requestParameters.jsonPatchInner, options).then((request) => request(axios, basePath)); + }, + /** + * Update CronFriendPost + * @param {CronFriendPostV1alpha1ApiUpdateCronFriendPostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updateCronFriendPost(requestParameters: CronFriendPostV1alpha1ApiUpdateCronFriendPostRequest, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.updateCronFriendPost(requestParameters.name, requestParameters.cronFriendPost, options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * Request parameters for createCronFriendPost operation in CronFriendPostV1alpha1Api. + * @export + * @interface CronFriendPostV1alpha1ApiCreateCronFriendPostRequest + */ +export interface CronFriendPostV1alpha1ApiCreateCronFriendPostRequest { + /** + * Fresh cronfriendpost + * @type {CronFriendPost} + * @memberof CronFriendPostV1alpha1ApiCreateCronFriendPost + */ + readonly cronFriendPost?: CronFriendPost +} + +/** + * Request parameters for deleteCronFriendPost operation in CronFriendPostV1alpha1Api. + * @export + * @interface CronFriendPostV1alpha1ApiDeleteCronFriendPostRequest + */ +export interface CronFriendPostV1alpha1ApiDeleteCronFriendPostRequest { + /** + * Name of cronfriendpost + * @type {string} + * @memberof CronFriendPostV1alpha1ApiDeleteCronFriendPost + */ + readonly name: string +} + +/** + * Request parameters for getCronFriendPost operation in CronFriendPostV1alpha1Api. + * @export + * @interface CronFriendPostV1alpha1ApiGetCronFriendPostRequest + */ +export interface CronFriendPostV1alpha1ApiGetCronFriendPostRequest { + /** + * Name of cronfriendpost + * @type {string} + * @memberof CronFriendPostV1alpha1ApiGetCronFriendPost + */ + readonly name: string +} + +/** + * Request parameters for listCronFriendPost operation in CronFriendPostV1alpha1Api. + * @export + * @interface CronFriendPostV1alpha1ApiListCronFriendPostRequest + */ +export interface CronFriendPostV1alpha1ApiListCronFriendPostRequest { + /** + * Page number. Default is 0. + * @type {number} + * @memberof CronFriendPostV1alpha1ApiListCronFriendPost + */ + readonly page?: number + + /** + * Size number. Default is 0. + * @type {number} + * @memberof CronFriendPostV1alpha1ApiListCronFriendPost + */ + readonly size?: number + + /** + * Label selector. e.g.: hidden!=true + * @type {Array} + * @memberof CronFriendPostV1alpha1ApiListCronFriendPost + */ + readonly labelSelector?: Array + + /** + * Field selector. e.g.: metadata.name==halo + * @type {Array} + * @memberof CronFriendPostV1alpha1ApiListCronFriendPost + */ + readonly fieldSelector?: Array + + /** + * Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. + * @type {Array} + * @memberof CronFriendPostV1alpha1ApiListCronFriendPost + */ + readonly sort?: Array +} + +/** + * Request parameters for patchCronFriendPost operation in CronFriendPostV1alpha1Api. + * @export + * @interface CronFriendPostV1alpha1ApiPatchCronFriendPostRequest + */ +export interface CronFriendPostV1alpha1ApiPatchCronFriendPostRequest { + /** + * Name of cronfriendpost + * @type {string} + * @memberof CronFriendPostV1alpha1ApiPatchCronFriendPost + */ + readonly name: string + + /** + * + * @type {Array} + * @memberof CronFriendPostV1alpha1ApiPatchCronFriendPost + */ + readonly jsonPatchInner?: Array +} + +/** + * Request parameters for updateCronFriendPost operation in CronFriendPostV1alpha1Api. + * @export + * @interface CronFriendPostV1alpha1ApiUpdateCronFriendPostRequest + */ +export interface CronFriendPostV1alpha1ApiUpdateCronFriendPostRequest { + /** + * Name of cronfriendpost + * @type {string} + * @memberof CronFriendPostV1alpha1ApiUpdateCronFriendPost + */ + readonly name: string + + /** + * Updated cronfriendpost + * @type {CronFriendPost} + * @memberof CronFriendPostV1alpha1ApiUpdateCronFriendPost + */ + readonly cronFriendPost?: CronFriendPost +} + +/** + * CronFriendPostV1alpha1Api - object-oriented interface + * @export + * @class CronFriendPostV1alpha1Api + * @extends {BaseAPI} + */ +export class CronFriendPostV1alpha1Api extends BaseAPI { + /** + * Create CronFriendPost + * @param {CronFriendPostV1alpha1ApiCreateCronFriendPostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof CronFriendPostV1alpha1Api + */ + public createCronFriendPost(requestParameters: CronFriendPostV1alpha1ApiCreateCronFriendPostRequest = {}, options?: RawAxiosRequestConfig) { + return CronFriendPostV1alpha1ApiFp(this.configuration).createCronFriendPost(requestParameters.cronFriendPost, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Delete CronFriendPost + * @param {CronFriendPostV1alpha1ApiDeleteCronFriendPostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof CronFriendPostV1alpha1Api + */ + public deleteCronFriendPost(requestParameters: CronFriendPostV1alpha1ApiDeleteCronFriendPostRequest, options?: RawAxiosRequestConfig) { + return CronFriendPostV1alpha1ApiFp(this.configuration).deleteCronFriendPost(requestParameters.name, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Get CronFriendPost + * @param {CronFriendPostV1alpha1ApiGetCronFriendPostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof CronFriendPostV1alpha1Api + */ + public getCronFriendPost(requestParameters: CronFriendPostV1alpha1ApiGetCronFriendPostRequest, options?: RawAxiosRequestConfig) { + return CronFriendPostV1alpha1ApiFp(this.configuration).getCronFriendPost(requestParameters.name, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * List CronFriendPost + * @param {CronFriendPostV1alpha1ApiListCronFriendPostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof CronFriendPostV1alpha1Api + */ + public listCronFriendPost(requestParameters: CronFriendPostV1alpha1ApiListCronFriendPostRequest = {}, options?: RawAxiosRequestConfig) { + return CronFriendPostV1alpha1ApiFp(this.configuration).listCronFriendPost(requestParameters.page, requestParameters.size, requestParameters.labelSelector, requestParameters.fieldSelector, requestParameters.sort, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Patch CronFriendPost + * @param {CronFriendPostV1alpha1ApiPatchCronFriendPostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof CronFriendPostV1alpha1Api + */ + public patchCronFriendPost(requestParameters: CronFriendPostV1alpha1ApiPatchCronFriendPostRequest, options?: RawAxiosRequestConfig) { + return CronFriendPostV1alpha1ApiFp(this.configuration).patchCronFriendPost(requestParameters.name, requestParameters.jsonPatchInner, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Update CronFriendPost + * @param {CronFriendPostV1alpha1ApiUpdateCronFriendPostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof CronFriendPostV1alpha1Api + */ + public updateCronFriendPost(requestParameters: CronFriendPostV1alpha1ApiUpdateCronFriendPostRequest, options?: RawAxiosRequestConfig) { + return CronFriendPostV1alpha1ApiFp(this.configuration).updateCronFriendPost(requestParameters.name, requestParameters.cronFriendPost, options).then((request) => request(this.axios, this.basePath)); + } +} + diff --git a/ui/src/api/generated/api/friend-post-v1alpha1-api.ts b/ui/src/api/generated/api/friend-post-v1alpha1-api.ts new file mode 100644 index 0000000..64e2b0c --- /dev/null +++ b/ui/src/api/generated/api/friend-post-v1alpha1-api.ts @@ -0,0 +1,665 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import type { Configuration } from '../configuration'; +import type { AxiosPromise, AxiosInstance, RawAxiosRequestConfig } from 'axios'; +import globalAxios from 'axios'; +// Some imports not used depending on template conditions +// @ts-ignore +import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from '../common'; +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS, type RequestArgs, BaseAPI, RequiredError, operationServerMap } from '../base'; +// @ts-ignore +import type { FriendPost } from '../models'; +// @ts-ignore +import type { FriendPostList } from '../models'; +// @ts-ignore +import type { JsonPatchInner } from '../models'; +/** + * FriendPostV1alpha1Api - axios parameter creator + * @export + */ +export const FriendPostV1alpha1ApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * Create FriendPost + * @param {FriendPost} [friendPost] Fresh friendpost + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + createFriendPost: async (friendPost?: FriendPost, options: RawAxiosRequestConfig = {}): Promise => { + const localVarPath = `/apis/friend.moony.la/v1alpha1/friendposts`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication basicAuth required + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration) + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(friendPost, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Delete FriendPost + * @param {string} name Name of friendpost + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deleteFriendPost: async (name: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'name' is not null or undefined + assertParamExists('deleteFriendPost', 'name', name) + const localVarPath = `/apis/friend.moony.la/v1alpha1/friendposts/{name}` + .replace(`{${"name"}}`, encodeURIComponent(String(name))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication basicAuth required + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration) + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Get FriendPost + * @param {string} name Name of friendpost + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getFriendPost: async (name: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'name' is not null or undefined + assertParamExists('getFriendPost', 'name', name) + const localVarPath = `/apis/friend.moony.la/v1alpha1/friendposts/{name}` + .replace(`{${"name"}}`, encodeURIComponent(String(name))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication basicAuth required + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration) + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * List FriendPost + * @param {number} [page] Page number. Default is 0. + * @param {number} [size] Size number. Default is 0. + * @param {Array} [labelSelector] Label selector. e.g.: hidden!=true + * @param {Array} [fieldSelector] Field selector. e.g.: metadata.name==halo + * @param {Array} [sort] Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listFriendPost: async (page?: number, size?: number, labelSelector?: Array, fieldSelector?: Array, sort?: Array, options: RawAxiosRequestConfig = {}): Promise => { + const localVarPath = `/apis/friend.moony.la/v1alpha1/friendposts`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication basicAuth required + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration) + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + if (page !== undefined) { + localVarQueryParameter['page'] = page; + } + + if (size !== undefined) { + localVarQueryParameter['size'] = size; + } + + if (labelSelector) { + localVarQueryParameter['labelSelector'] = labelSelector; + } + + if (fieldSelector) { + localVarQueryParameter['fieldSelector'] = fieldSelector; + } + + if (sort) { + localVarQueryParameter['sort'] = sort; + } + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Patch FriendPost + * @param {string} name Name of friendpost + * @param {Array} [jsonPatchInner] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + patchFriendPost: async (name: string, jsonPatchInner?: Array, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'name' is not null or undefined + assertParamExists('patchFriendPost', 'name', name) + const localVarPath = `/apis/friend.moony.la/v1alpha1/friendposts/{name}` + .replace(`{${"name"}}`, encodeURIComponent(String(name))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'PATCH', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication basicAuth required + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration) + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + localVarHeaderParameter['Content-Type'] = 'application/json-patch+json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(jsonPatchInner, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Update FriendPost + * @param {string} name Name of friendpost + * @param {FriendPost} [friendPost] Updated friendpost + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updateFriendPost: async (name: string, friendPost?: FriendPost, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'name' is not null or undefined + assertParamExists('updateFriendPost', 'name', name) + const localVarPath = `/apis/friend.moony.la/v1alpha1/friendposts/{name}` + .replace(`{${"name"}}`, encodeURIComponent(String(name))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication basicAuth required + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration) + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(friendPost, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * FriendPostV1alpha1Api - functional programming interface + * @export + */ +export const FriendPostV1alpha1ApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = FriendPostV1alpha1ApiAxiosParamCreator(configuration) + return { + /** + * Create FriendPost + * @param {FriendPost} [friendPost] Fresh friendpost + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async createFriendPost(friendPost?: FriendPost, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.createFriendPost(friendPost, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['FriendPostV1alpha1Api.createFriendPost']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Delete FriendPost + * @param {string} name Name of friendpost + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async deleteFriendPost(name: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.deleteFriendPost(name, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['FriendPostV1alpha1Api.deleteFriendPost']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Get FriendPost + * @param {string} name Name of friendpost + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getFriendPost(name: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getFriendPost(name, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['FriendPostV1alpha1Api.getFriendPost']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * List FriendPost + * @param {number} [page] Page number. Default is 0. + * @param {number} [size] Size number. Default is 0. + * @param {Array} [labelSelector] Label selector. e.g.: hidden!=true + * @param {Array} [fieldSelector] Field selector. e.g.: metadata.name==halo + * @param {Array} [sort] Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async listFriendPost(page?: number, size?: number, labelSelector?: Array, fieldSelector?: Array, sort?: Array, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.listFriendPost(page, size, labelSelector, fieldSelector, sort, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['FriendPostV1alpha1Api.listFriendPost']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Patch FriendPost + * @param {string} name Name of friendpost + * @param {Array} [jsonPatchInner] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async patchFriendPost(name: string, jsonPatchInner?: Array, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.patchFriendPost(name, jsonPatchInner, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['FriendPostV1alpha1Api.patchFriendPost']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Update FriendPost + * @param {string} name Name of friendpost + * @param {FriendPost} [friendPost] Updated friendpost + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async updateFriendPost(name: string, friendPost?: FriendPost, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.updateFriendPost(name, friendPost, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['FriendPostV1alpha1Api.updateFriendPost']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + } +}; + +/** + * FriendPostV1alpha1Api - factory interface + * @export + */ +export const FriendPostV1alpha1ApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = FriendPostV1alpha1ApiFp(configuration) + return { + /** + * Create FriendPost + * @param {FriendPostV1alpha1ApiCreateFriendPostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + createFriendPost(requestParameters: FriendPostV1alpha1ApiCreateFriendPostRequest = {}, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.createFriendPost(requestParameters.friendPost, options).then((request) => request(axios, basePath)); + }, + /** + * Delete FriendPost + * @param {FriendPostV1alpha1ApiDeleteFriendPostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deleteFriendPost(requestParameters: FriendPostV1alpha1ApiDeleteFriendPostRequest, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.deleteFriendPost(requestParameters.name, options).then((request) => request(axios, basePath)); + }, + /** + * Get FriendPost + * @param {FriendPostV1alpha1ApiGetFriendPostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getFriendPost(requestParameters: FriendPostV1alpha1ApiGetFriendPostRequest, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.getFriendPost(requestParameters.name, options).then((request) => request(axios, basePath)); + }, + /** + * List FriendPost + * @param {FriendPostV1alpha1ApiListFriendPostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listFriendPost(requestParameters: FriendPostV1alpha1ApiListFriendPostRequest = {}, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.listFriendPost(requestParameters.page, requestParameters.size, requestParameters.labelSelector, requestParameters.fieldSelector, requestParameters.sort, options).then((request) => request(axios, basePath)); + }, + /** + * Patch FriendPost + * @param {FriendPostV1alpha1ApiPatchFriendPostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + patchFriendPost(requestParameters: FriendPostV1alpha1ApiPatchFriendPostRequest, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.patchFriendPost(requestParameters.name, requestParameters.jsonPatchInner, options).then((request) => request(axios, basePath)); + }, + /** + * Update FriendPost + * @param {FriendPostV1alpha1ApiUpdateFriendPostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updateFriendPost(requestParameters: FriendPostV1alpha1ApiUpdateFriendPostRequest, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.updateFriendPost(requestParameters.name, requestParameters.friendPost, options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * Request parameters for createFriendPost operation in FriendPostV1alpha1Api. + * @export + * @interface FriendPostV1alpha1ApiCreateFriendPostRequest + */ +export interface FriendPostV1alpha1ApiCreateFriendPostRequest { + /** + * Fresh friendpost + * @type {FriendPost} + * @memberof FriendPostV1alpha1ApiCreateFriendPost + */ + readonly friendPost?: FriendPost +} + +/** + * Request parameters for deleteFriendPost operation in FriendPostV1alpha1Api. + * @export + * @interface FriendPostV1alpha1ApiDeleteFriendPostRequest + */ +export interface FriendPostV1alpha1ApiDeleteFriendPostRequest { + /** + * Name of friendpost + * @type {string} + * @memberof FriendPostV1alpha1ApiDeleteFriendPost + */ + readonly name: string +} + +/** + * Request parameters for getFriendPost operation in FriendPostV1alpha1Api. + * @export + * @interface FriendPostV1alpha1ApiGetFriendPostRequest + */ +export interface FriendPostV1alpha1ApiGetFriendPostRequest { + /** + * Name of friendpost + * @type {string} + * @memberof FriendPostV1alpha1ApiGetFriendPost + */ + readonly name: string +} + +/** + * Request parameters for listFriendPost operation in FriendPostV1alpha1Api. + * @export + * @interface FriendPostV1alpha1ApiListFriendPostRequest + */ +export interface FriendPostV1alpha1ApiListFriendPostRequest { + /** + * Page number. Default is 0. + * @type {number} + * @memberof FriendPostV1alpha1ApiListFriendPost + */ + readonly page?: number + + /** + * Size number. Default is 0. + * @type {number} + * @memberof FriendPostV1alpha1ApiListFriendPost + */ + readonly size?: number + + /** + * Label selector. e.g.: hidden!=true + * @type {Array} + * @memberof FriendPostV1alpha1ApiListFriendPost + */ + readonly labelSelector?: Array + + /** + * Field selector. e.g.: metadata.name==halo + * @type {Array} + * @memberof FriendPostV1alpha1ApiListFriendPost + */ + readonly fieldSelector?: Array + + /** + * Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. + * @type {Array} + * @memberof FriendPostV1alpha1ApiListFriendPost + */ + readonly sort?: Array +} + +/** + * Request parameters for patchFriendPost operation in FriendPostV1alpha1Api. + * @export + * @interface FriendPostV1alpha1ApiPatchFriendPostRequest + */ +export interface FriendPostV1alpha1ApiPatchFriendPostRequest { + /** + * Name of friendpost + * @type {string} + * @memberof FriendPostV1alpha1ApiPatchFriendPost + */ + readonly name: string + + /** + * + * @type {Array} + * @memberof FriendPostV1alpha1ApiPatchFriendPost + */ + readonly jsonPatchInner?: Array +} + +/** + * Request parameters for updateFriendPost operation in FriendPostV1alpha1Api. + * @export + * @interface FriendPostV1alpha1ApiUpdateFriendPostRequest + */ +export interface FriendPostV1alpha1ApiUpdateFriendPostRequest { + /** + * Name of friendpost + * @type {string} + * @memberof FriendPostV1alpha1ApiUpdateFriendPost + */ + readonly name: string + + /** + * Updated friendpost + * @type {FriendPost} + * @memberof FriendPostV1alpha1ApiUpdateFriendPost + */ + readonly friendPost?: FriendPost +} + +/** + * FriendPostV1alpha1Api - object-oriented interface + * @export + * @class FriendPostV1alpha1Api + * @extends {BaseAPI} + */ +export class FriendPostV1alpha1Api extends BaseAPI { + /** + * Create FriendPost + * @param {FriendPostV1alpha1ApiCreateFriendPostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof FriendPostV1alpha1Api + */ + public createFriendPost(requestParameters: FriendPostV1alpha1ApiCreateFriendPostRequest = {}, options?: RawAxiosRequestConfig) { + return FriendPostV1alpha1ApiFp(this.configuration).createFriendPost(requestParameters.friendPost, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Delete FriendPost + * @param {FriendPostV1alpha1ApiDeleteFriendPostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof FriendPostV1alpha1Api + */ + public deleteFriendPost(requestParameters: FriendPostV1alpha1ApiDeleteFriendPostRequest, options?: RawAxiosRequestConfig) { + return FriendPostV1alpha1ApiFp(this.configuration).deleteFriendPost(requestParameters.name, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Get FriendPost + * @param {FriendPostV1alpha1ApiGetFriendPostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof FriendPostV1alpha1Api + */ + public getFriendPost(requestParameters: FriendPostV1alpha1ApiGetFriendPostRequest, options?: RawAxiosRequestConfig) { + return FriendPostV1alpha1ApiFp(this.configuration).getFriendPost(requestParameters.name, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * List FriendPost + * @param {FriendPostV1alpha1ApiListFriendPostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof FriendPostV1alpha1Api + */ + public listFriendPost(requestParameters: FriendPostV1alpha1ApiListFriendPostRequest = {}, options?: RawAxiosRequestConfig) { + return FriendPostV1alpha1ApiFp(this.configuration).listFriendPost(requestParameters.page, requestParameters.size, requestParameters.labelSelector, requestParameters.fieldSelector, requestParameters.sort, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Patch FriendPost + * @param {FriendPostV1alpha1ApiPatchFriendPostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof FriendPostV1alpha1Api + */ + public patchFriendPost(requestParameters: FriendPostV1alpha1ApiPatchFriendPostRequest, options?: RawAxiosRequestConfig) { + return FriendPostV1alpha1ApiFp(this.configuration).patchFriendPost(requestParameters.name, requestParameters.jsonPatchInner, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Update FriendPost + * @param {FriendPostV1alpha1ApiUpdateFriendPostRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof FriendPostV1alpha1Api + */ + public updateFriendPost(requestParameters: FriendPostV1alpha1ApiUpdateFriendPostRequest, options?: RawAxiosRequestConfig) { + return FriendPostV1alpha1ApiFp(this.configuration).updateFriendPost(requestParameters.name, requestParameters.friendPost, options).then((request) => request(this.axios, this.basePath)); + } +} + diff --git a/ui/src/api/generated/api/rss-feed-sync-log-v1alpha1-api.ts b/ui/src/api/generated/api/rss-feed-sync-log-v1alpha1-api.ts new file mode 100644 index 0000000..0a99c13 --- /dev/null +++ b/ui/src/api/generated/api/rss-feed-sync-log-v1alpha1-api.ts @@ -0,0 +1,665 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import type { Configuration } from '../configuration'; +import type { AxiosPromise, AxiosInstance, RawAxiosRequestConfig } from 'axios'; +import globalAxios from 'axios'; +// Some imports not used depending on template conditions +// @ts-ignore +import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from '../common'; +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS, type RequestArgs, BaseAPI, RequiredError, operationServerMap } from '../base'; +// @ts-ignore +import type { JsonPatchInner } from '../models'; +// @ts-ignore +import type { RssFeedSyncLog } from '../models'; +// @ts-ignore +import type { RssFeedSyncLogList } from '../models'; +/** + * RssFeedSyncLogV1alpha1Api - axios parameter creator + * @export + */ +export const RssFeedSyncLogV1alpha1ApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * Create RssFeedSyncLog + * @param {RssFeedSyncLog} [rssFeedSyncLog] Fresh rssfeedsynclog + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + createRssFeedSyncLog: async (rssFeedSyncLog?: RssFeedSyncLog, options: RawAxiosRequestConfig = {}): Promise => { + const localVarPath = `/apis/friend.moony.la/v1alpha1/rssfeedsynclogs`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication basicAuth required + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration) + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(rssFeedSyncLog, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Delete RssFeedSyncLog + * @param {string} name Name of rssfeedsynclog + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deleteRssFeedSyncLog: async (name: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'name' is not null or undefined + assertParamExists('deleteRssFeedSyncLog', 'name', name) + const localVarPath = `/apis/friend.moony.la/v1alpha1/rssfeedsynclogs/{name}` + .replace(`{${"name"}}`, encodeURIComponent(String(name))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication basicAuth required + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration) + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Get RssFeedSyncLog + * @param {string} name Name of rssfeedsynclog + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getRssFeedSyncLog: async (name: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'name' is not null or undefined + assertParamExists('getRssFeedSyncLog', 'name', name) + const localVarPath = `/apis/friend.moony.la/v1alpha1/rssfeedsynclogs/{name}` + .replace(`{${"name"}}`, encodeURIComponent(String(name))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication basicAuth required + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration) + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * List RssFeedSyncLog + * @param {number} [page] Page number. Default is 0. + * @param {number} [size] Size number. Default is 0. + * @param {Array} [labelSelector] Label selector. e.g.: hidden!=true + * @param {Array} [fieldSelector] Field selector. e.g.: metadata.name==halo + * @param {Array} [sort] Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listRssFeedSyncLog: async (page?: number, size?: number, labelSelector?: Array, fieldSelector?: Array, sort?: Array, options: RawAxiosRequestConfig = {}): Promise => { + const localVarPath = `/apis/friend.moony.la/v1alpha1/rssfeedsynclogs`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication basicAuth required + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration) + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + if (page !== undefined) { + localVarQueryParameter['page'] = page; + } + + if (size !== undefined) { + localVarQueryParameter['size'] = size; + } + + if (labelSelector) { + localVarQueryParameter['labelSelector'] = labelSelector; + } + + if (fieldSelector) { + localVarQueryParameter['fieldSelector'] = fieldSelector; + } + + if (sort) { + localVarQueryParameter['sort'] = sort; + } + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Patch RssFeedSyncLog + * @param {string} name Name of rssfeedsynclog + * @param {Array} [jsonPatchInner] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + patchRssFeedSyncLog: async (name: string, jsonPatchInner?: Array, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'name' is not null or undefined + assertParamExists('patchRssFeedSyncLog', 'name', name) + const localVarPath = `/apis/friend.moony.la/v1alpha1/rssfeedsynclogs/{name}` + .replace(`{${"name"}}`, encodeURIComponent(String(name))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'PATCH', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication basicAuth required + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration) + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + localVarHeaderParameter['Content-Type'] = 'application/json-patch+json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(jsonPatchInner, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Update RssFeedSyncLog + * @param {string} name Name of rssfeedsynclog + * @param {RssFeedSyncLog} [rssFeedSyncLog] Updated rssfeedsynclog + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updateRssFeedSyncLog: async (name: string, rssFeedSyncLog?: RssFeedSyncLog, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'name' is not null or undefined + assertParamExists('updateRssFeedSyncLog', 'name', name) + const localVarPath = `/apis/friend.moony.la/v1alpha1/rssfeedsynclogs/{name}` + .replace(`{${"name"}}`, encodeURIComponent(String(name))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication basicAuth required + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration) + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(rssFeedSyncLog, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * RssFeedSyncLogV1alpha1Api - functional programming interface + * @export + */ +export const RssFeedSyncLogV1alpha1ApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = RssFeedSyncLogV1alpha1ApiAxiosParamCreator(configuration) + return { + /** + * Create RssFeedSyncLog + * @param {RssFeedSyncLog} [rssFeedSyncLog] Fresh rssfeedsynclog + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async createRssFeedSyncLog(rssFeedSyncLog?: RssFeedSyncLog, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.createRssFeedSyncLog(rssFeedSyncLog, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['RssFeedSyncLogV1alpha1Api.createRssFeedSyncLog']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Delete RssFeedSyncLog + * @param {string} name Name of rssfeedsynclog + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async deleteRssFeedSyncLog(name: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.deleteRssFeedSyncLog(name, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['RssFeedSyncLogV1alpha1Api.deleteRssFeedSyncLog']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Get RssFeedSyncLog + * @param {string} name Name of rssfeedsynclog + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getRssFeedSyncLog(name: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getRssFeedSyncLog(name, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['RssFeedSyncLogV1alpha1Api.getRssFeedSyncLog']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * List RssFeedSyncLog + * @param {number} [page] Page number. Default is 0. + * @param {number} [size] Size number. Default is 0. + * @param {Array} [labelSelector] Label selector. e.g.: hidden!=true + * @param {Array} [fieldSelector] Field selector. e.g.: metadata.name==halo + * @param {Array} [sort] Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async listRssFeedSyncLog(page?: number, size?: number, labelSelector?: Array, fieldSelector?: Array, sort?: Array, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.listRssFeedSyncLog(page, size, labelSelector, fieldSelector, sort, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['RssFeedSyncLogV1alpha1Api.listRssFeedSyncLog']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Patch RssFeedSyncLog + * @param {string} name Name of rssfeedsynclog + * @param {Array} [jsonPatchInner] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async patchRssFeedSyncLog(name: string, jsonPatchInner?: Array, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.patchRssFeedSyncLog(name, jsonPatchInner, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['RssFeedSyncLogV1alpha1Api.patchRssFeedSyncLog']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Update RssFeedSyncLog + * @param {string} name Name of rssfeedsynclog + * @param {RssFeedSyncLog} [rssFeedSyncLog] Updated rssfeedsynclog + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async updateRssFeedSyncLog(name: string, rssFeedSyncLog?: RssFeedSyncLog, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.updateRssFeedSyncLog(name, rssFeedSyncLog, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['RssFeedSyncLogV1alpha1Api.updateRssFeedSyncLog']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + } +}; + +/** + * RssFeedSyncLogV1alpha1Api - factory interface + * @export + */ +export const RssFeedSyncLogV1alpha1ApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = RssFeedSyncLogV1alpha1ApiFp(configuration) + return { + /** + * Create RssFeedSyncLog + * @param {RssFeedSyncLogV1alpha1ApiCreateRssFeedSyncLogRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + createRssFeedSyncLog(requestParameters: RssFeedSyncLogV1alpha1ApiCreateRssFeedSyncLogRequest = {}, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.createRssFeedSyncLog(requestParameters.rssFeedSyncLog, options).then((request) => request(axios, basePath)); + }, + /** + * Delete RssFeedSyncLog + * @param {RssFeedSyncLogV1alpha1ApiDeleteRssFeedSyncLogRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deleteRssFeedSyncLog(requestParameters: RssFeedSyncLogV1alpha1ApiDeleteRssFeedSyncLogRequest, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.deleteRssFeedSyncLog(requestParameters.name, options).then((request) => request(axios, basePath)); + }, + /** + * Get RssFeedSyncLog + * @param {RssFeedSyncLogV1alpha1ApiGetRssFeedSyncLogRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getRssFeedSyncLog(requestParameters: RssFeedSyncLogV1alpha1ApiGetRssFeedSyncLogRequest, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.getRssFeedSyncLog(requestParameters.name, options).then((request) => request(axios, basePath)); + }, + /** + * List RssFeedSyncLog + * @param {RssFeedSyncLogV1alpha1ApiListRssFeedSyncLogRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listRssFeedSyncLog(requestParameters: RssFeedSyncLogV1alpha1ApiListRssFeedSyncLogRequest = {}, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.listRssFeedSyncLog(requestParameters.page, requestParameters.size, requestParameters.labelSelector, requestParameters.fieldSelector, requestParameters.sort, options).then((request) => request(axios, basePath)); + }, + /** + * Patch RssFeedSyncLog + * @param {RssFeedSyncLogV1alpha1ApiPatchRssFeedSyncLogRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + patchRssFeedSyncLog(requestParameters: RssFeedSyncLogV1alpha1ApiPatchRssFeedSyncLogRequest, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.patchRssFeedSyncLog(requestParameters.name, requestParameters.jsonPatchInner, options).then((request) => request(axios, basePath)); + }, + /** + * Update RssFeedSyncLog + * @param {RssFeedSyncLogV1alpha1ApiUpdateRssFeedSyncLogRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updateRssFeedSyncLog(requestParameters: RssFeedSyncLogV1alpha1ApiUpdateRssFeedSyncLogRequest, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.updateRssFeedSyncLog(requestParameters.name, requestParameters.rssFeedSyncLog, options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * Request parameters for createRssFeedSyncLog operation in RssFeedSyncLogV1alpha1Api. + * @export + * @interface RssFeedSyncLogV1alpha1ApiCreateRssFeedSyncLogRequest + */ +export interface RssFeedSyncLogV1alpha1ApiCreateRssFeedSyncLogRequest { + /** + * Fresh rssfeedsynclog + * @type {RssFeedSyncLog} + * @memberof RssFeedSyncLogV1alpha1ApiCreateRssFeedSyncLog + */ + readonly rssFeedSyncLog?: RssFeedSyncLog +} + +/** + * Request parameters for deleteRssFeedSyncLog operation in RssFeedSyncLogV1alpha1Api. + * @export + * @interface RssFeedSyncLogV1alpha1ApiDeleteRssFeedSyncLogRequest + */ +export interface RssFeedSyncLogV1alpha1ApiDeleteRssFeedSyncLogRequest { + /** + * Name of rssfeedsynclog + * @type {string} + * @memberof RssFeedSyncLogV1alpha1ApiDeleteRssFeedSyncLog + */ + readonly name: string +} + +/** + * Request parameters for getRssFeedSyncLog operation in RssFeedSyncLogV1alpha1Api. + * @export + * @interface RssFeedSyncLogV1alpha1ApiGetRssFeedSyncLogRequest + */ +export interface RssFeedSyncLogV1alpha1ApiGetRssFeedSyncLogRequest { + /** + * Name of rssfeedsynclog + * @type {string} + * @memberof RssFeedSyncLogV1alpha1ApiGetRssFeedSyncLog + */ + readonly name: string +} + +/** + * Request parameters for listRssFeedSyncLog operation in RssFeedSyncLogV1alpha1Api. + * @export + * @interface RssFeedSyncLogV1alpha1ApiListRssFeedSyncLogRequest + */ +export interface RssFeedSyncLogV1alpha1ApiListRssFeedSyncLogRequest { + /** + * Page number. Default is 0. + * @type {number} + * @memberof RssFeedSyncLogV1alpha1ApiListRssFeedSyncLog + */ + readonly page?: number + + /** + * Size number. Default is 0. + * @type {number} + * @memberof RssFeedSyncLogV1alpha1ApiListRssFeedSyncLog + */ + readonly size?: number + + /** + * Label selector. e.g.: hidden!=true + * @type {Array} + * @memberof RssFeedSyncLogV1alpha1ApiListRssFeedSyncLog + */ + readonly labelSelector?: Array + + /** + * Field selector. e.g.: metadata.name==halo + * @type {Array} + * @memberof RssFeedSyncLogV1alpha1ApiListRssFeedSyncLog + */ + readonly fieldSelector?: Array + + /** + * Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. + * @type {Array} + * @memberof RssFeedSyncLogV1alpha1ApiListRssFeedSyncLog + */ + readonly sort?: Array +} + +/** + * Request parameters for patchRssFeedSyncLog operation in RssFeedSyncLogV1alpha1Api. + * @export + * @interface RssFeedSyncLogV1alpha1ApiPatchRssFeedSyncLogRequest + */ +export interface RssFeedSyncLogV1alpha1ApiPatchRssFeedSyncLogRequest { + /** + * Name of rssfeedsynclog + * @type {string} + * @memberof RssFeedSyncLogV1alpha1ApiPatchRssFeedSyncLog + */ + readonly name: string + + /** + * + * @type {Array} + * @memberof RssFeedSyncLogV1alpha1ApiPatchRssFeedSyncLog + */ + readonly jsonPatchInner?: Array +} + +/** + * Request parameters for updateRssFeedSyncLog operation in RssFeedSyncLogV1alpha1Api. + * @export + * @interface RssFeedSyncLogV1alpha1ApiUpdateRssFeedSyncLogRequest + */ +export interface RssFeedSyncLogV1alpha1ApiUpdateRssFeedSyncLogRequest { + /** + * Name of rssfeedsynclog + * @type {string} + * @memberof RssFeedSyncLogV1alpha1ApiUpdateRssFeedSyncLog + */ + readonly name: string + + /** + * Updated rssfeedsynclog + * @type {RssFeedSyncLog} + * @memberof RssFeedSyncLogV1alpha1ApiUpdateRssFeedSyncLog + */ + readonly rssFeedSyncLog?: RssFeedSyncLog +} + +/** + * RssFeedSyncLogV1alpha1Api - object-oriented interface + * @export + * @class RssFeedSyncLogV1alpha1Api + * @extends {BaseAPI} + */ +export class RssFeedSyncLogV1alpha1Api extends BaseAPI { + /** + * Create RssFeedSyncLog + * @param {RssFeedSyncLogV1alpha1ApiCreateRssFeedSyncLogRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof RssFeedSyncLogV1alpha1Api + */ + public createRssFeedSyncLog(requestParameters: RssFeedSyncLogV1alpha1ApiCreateRssFeedSyncLogRequest = {}, options?: RawAxiosRequestConfig) { + return RssFeedSyncLogV1alpha1ApiFp(this.configuration).createRssFeedSyncLog(requestParameters.rssFeedSyncLog, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Delete RssFeedSyncLog + * @param {RssFeedSyncLogV1alpha1ApiDeleteRssFeedSyncLogRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof RssFeedSyncLogV1alpha1Api + */ + public deleteRssFeedSyncLog(requestParameters: RssFeedSyncLogV1alpha1ApiDeleteRssFeedSyncLogRequest, options?: RawAxiosRequestConfig) { + return RssFeedSyncLogV1alpha1ApiFp(this.configuration).deleteRssFeedSyncLog(requestParameters.name, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Get RssFeedSyncLog + * @param {RssFeedSyncLogV1alpha1ApiGetRssFeedSyncLogRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof RssFeedSyncLogV1alpha1Api + */ + public getRssFeedSyncLog(requestParameters: RssFeedSyncLogV1alpha1ApiGetRssFeedSyncLogRequest, options?: RawAxiosRequestConfig) { + return RssFeedSyncLogV1alpha1ApiFp(this.configuration).getRssFeedSyncLog(requestParameters.name, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * List RssFeedSyncLog + * @param {RssFeedSyncLogV1alpha1ApiListRssFeedSyncLogRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof RssFeedSyncLogV1alpha1Api + */ + public listRssFeedSyncLog(requestParameters: RssFeedSyncLogV1alpha1ApiListRssFeedSyncLogRequest = {}, options?: RawAxiosRequestConfig) { + return RssFeedSyncLogV1alpha1ApiFp(this.configuration).listRssFeedSyncLog(requestParameters.page, requestParameters.size, requestParameters.labelSelector, requestParameters.fieldSelector, requestParameters.sort, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Patch RssFeedSyncLog + * @param {RssFeedSyncLogV1alpha1ApiPatchRssFeedSyncLogRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof RssFeedSyncLogV1alpha1Api + */ + public patchRssFeedSyncLog(requestParameters: RssFeedSyncLogV1alpha1ApiPatchRssFeedSyncLogRequest, options?: RawAxiosRequestConfig) { + return RssFeedSyncLogV1alpha1ApiFp(this.configuration).patchRssFeedSyncLog(requestParameters.name, requestParameters.jsonPatchInner, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Update RssFeedSyncLog + * @param {RssFeedSyncLogV1alpha1ApiUpdateRssFeedSyncLogRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof RssFeedSyncLogV1alpha1Api + */ + public updateRssFeedSyncLog(requestParameters: RssFeedSyncLogV1alpha1ApiUpdateRssFeedSyncLogRequest, options?: RawAxiosRequestConfig) { + return RssFeedSyncLogV1alpha1ApiFp(this.configuration).updateRssFeedSyncLog(requestParameters.name, requestParameters.rssFeedSyncLog, options).then((request) => request(this.axios, this.basePath)); + } +} + diff --git a/ui/src/api/generated/base.ts b/ui/src/api/generated/base.ts new file mode 100644 index 0000000..34e330d --- /dev/null +++ b/ui/src/api/generated/base.ts @@ -0,0 +1,86 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import type { Configuration } from './configuration'; +// Some imports not used depending on template conditions +// @ts-ignore +import type { AxiosPromise, AxiosInstance, RawAxiosRequestConfig } from 'axios'; +import globalAxios from 'axios'; + +export const BASE_PATH = "http://localhost:65429".replace(/\/+$/, ""); + +/** + * + * @export + */ +export const COLLECTION_FORMATS = { + csv: ",", + ssv: " ", + tsv: "\t", + pipes: "|", +}; + +/** + * + * @export + * @interface RequestArgs + */ +export interface RequestArgs { + url: string; + options: RawAxiosRequestConfig; +} + +/** + * + * @export + * @class BaseAPI + */ +export class BaseAPI { + protected configuration: Configuration | undefined; + + constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) { + if (configuration) { + this.configuration = configuration; + this.basePath = configuration.basePath ?? basePath; + } + } +}; + +/** + * + * @export + * @class RequiredError + * @extends {Error} + */ +export class RequiredError extends Error { + constructor(public field: string, msg?: string) { + super(msg); + this.name = "RequiredError" + } +} + +interface ServerMap { + [key: string]: { + url: string, + description: string, + }[]; +} + +/** + * + * @export + */ +export const operationServerMap: ServerMap = { +} diff --git a/ui/src/api/generated/common.ts b/ui/src/api/generated/common.ts new file mode 100644 index 0000000..26708e2 --- /dev/null +++ b/ui/src/api/generated/common.ts @@ -0,0 +1,150 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import type { Configuration } from "./configuration"; +import type { RequestArgs } from "./base"; +import type { AxiosInstance, AxiosResponse } from 'axios'; +import { RequiredError } from "./base"; + +/** + * + * @export + */ +export const DUMMY_BASE_URL = 'https://example.com' + +/** + * + * @throws {RequiredError} + * @export + */ +export const assertParamExists = function (functionName: string, paramName: string, paramValue: unknown) { + if (paramValue === null || paramValue === undefined) { + throw new RequiredError(paramName, `Required parameter ${paramName} was null or undefined when calling ${functionName}.`); + } +} + +/** + * + * @export + */ +export const setApiKeyToObject = async function (object: any, keyParamName: string, configuration?: Configuration) { + if (configuration && configuration.apiKey) { + const localVarApiKeyValue = typeof configuration.apiKey === 'function' + ? await configuration.apiKey(keyParamName) + : await configuration.apiKey; + object[keyParamName] = localVarApiKeyValue; + } +} + +/** + * + * @export + */ +export const setBasicAuthToObject = function (object: any, configuration?: Configuration) { + if (configuration && (configuration.username || configuration.password)) { + object["auth"] = { username: configuration.username, password: configuration.password }; + } +} + +/** + * + * @export + */ +export const setBearerAuthToObject = async function (object: any, configuration?: Configuration) { + if (configuration && configuration.accessToken) { + const accessToken = typeof configuration.accessToken === 'function' + ? await configuration.accessToken() + : await configuration.accessToken; + object["Authorization"] = "Bearer " + accessToken; + } +} + +/** + * + * @export + */ +export const setOAuthToObject = async function (object: any, name: string, scopes: string[], configuration?: Configuration) { + if (configuration && configuration.accessToken) { + const localVarAccessTokenValue = typeof configuration.accessToken === 'function' + ? await configuration.accessToken(name, scopes) + : await configuration.accessToken; + object["Authorization"] = "Bearer " + localVarAccessTokenValue; + } +} + +function setFlattenedQueryParams(urlSearchParams: URLSearchParams, parameter: any, key: string = ""): void { + if (parameter == null) return; + if (typeof parameter === "object") { + if (Array.isArray(parameter)) { + (parameter as any[]).forEach(item => setFlattenedQueryParams(urlSearchParams, item, key)); + } + else { + Object.keys(parameter).forEach(currentKey => + setFlattenedQueryParams(urlSearchParams, parameter[currentKey], `${key}${key !== '' ? '.' : ''}${currentKey}`) + ); + } + } + else { + if (urlSearchParams.has(key)) { + urlSearchParams.append(key, parameter); + } + else { + urlSearchParams.set(key, parameter); + } + } +} + +/** + * + * @export + */ +export const setSearchParams = function (url: URL, ...objects: any[]) { + const searchParams = new URLSearchParams(url.search); + setFlattenedQueryParams(searchParams, objects); + url.search = searchParams.toString(); +} + +/** + * + * @export + */ +export const serializeDataIfNeeded = function (value: any, requestOptions: any, configuration?: Configuration) { + const nonString = typeof value !== 'string'; + const needsSerialization = nonString && configuration && configuration.isJsonMime + ? configuration.isJsonMime(requestOptions.headers['Content-Type']) + : nonString; + return needsSerialization + ? JSON.stringify(value !== undefined ? value : {}) + : (value || ""); +} + +/** + * + * @export + */ +export const toPathString = function (url: URL) { + return url.pathname + url.search + url.hash +} + +/** + * + * @export + */ +export const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) { + return >(axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { + const axiosRequestArgs = {...axiosArgs.options, url: (axios.defaults.baseURL ? '' : configuration?.basePath ?? basePath) + axiosArgs.url}; + return axios.request(axiosRequestArgs); + }; +} diff --git a/ui/src/api/generated/configuration.ts b/ui/src/api/generated/configuration.ts new file mode 100644 index 0000000..452b67e --- /dev/null +++ b/ui/src/api/generated/configuration.ts @@ -0,0 +1,110 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface ConfigurationParameters { + apiKey?: string | Promise | ((name: string) => string) | ((name: string) => Promise); + username?: string; + password?: string; + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise); + basePath?: string; + serverIndex?: number; + baseOptions?: any; + formDataCtor?: new () => any; +} + +export class Configuration { + /** + * parameter for apiKey security + * @param name security name + * @memberof Configuration + */ + apiKey?: string | Promise | ((name: string) => string) | ((name: string) => Promise); + /** + * parameter for basic security + * + * @type {string} + * @memberof Configuration + */ + username?: string; + /** + * parameter for basic security + * + * @type {string} + * @memberof Configuration + */ + password?: string; + /** + * parameter for oauth2 security + * @param name security name + * @param scopes oauth2 scope + * @memberof Configuration + */ + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise); + /** + * override base path + * + * @type {string} + * @memberof Configuration + */ + basePath?: string; + /** + * override server index + * + * @type {number} + * @memberof Configuration + */ + serverIndex?: number; + /** + * base options for axios calls + * + * @type {any} + * @memberof Configuration + */ + baseOptions?: any; + /** + * The FormData constructor that will be used to create multipart form data + * requests. You can inject this here so that execution environments that + * do not support the FormData class can still run the generated client. + * + * @type {new () => FormData} + */ + formDataCtor?: new () => any; + + constructor(param: ConfigurationParameters = {}) { + this.apiKey = param.apiKey; + this.username = param.username; + this.password = param.password; + this.accessToken = param.accessToken; + this.basePath = param.basePath; + this.serverIndex = param.serverIndex; + this.baseOptions = param.baseOptions; + this.formDataCtor = param.formDataCtor; + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime - MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + public isJsonMime(mime: string): boolean { + const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i'); + return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json'); + } +} diff --git a/ui/src/api/generated/git_push.sh b/ui/src/api/generated/git_push.sh new file mode 100644 index 0000000..f53a75d --- /dev/null +++ b/ui/src/api/generated/git_push.sh @@ -0,0 +1,57 @@ +#!/bin/sh +# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ +# +# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 +git_host=$4 + +if [ "$git_host" = "" ]; then + git_host="github.com" + echo "[INFO] No command line input provided. Set \$git_host to $git_host" +fi + +if [ "$git_user_id" = "" ]; then + git_user_id="GIT_USER_ID" + echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" +fi + +if [ "$git_repo_id" = "" ]; then + git_repo_id="GIT_REPO_ID" + echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" +fi + +if [ "$release_note" = "" ]; then + release_note="Minor update" + echo "[INFO] No command line input provided. Set \$release_note to $release_note" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "$release_note" + +# Sets the new remote +git_remote=$(git remote) +if [ "$git_remote" = "" ]; then # git remote not defined + + if [ "$GIT_TOKEN" = "" ]; then + echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." + git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git + else + git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git + fi + +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' diff --git a/ui/src/api/generated/index.ts b/ui/src/api/generated/index.ts new file mode 100644 index 0000000..7c2845c --- /dev/null +++ b/ui/src/api/generated/index.ts @@ -0,0 +1,18 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export * from "./api"; +export * from "./configuration"; +export * from "./models"; diff --git a/ui/src/api/generated/models/add-operation.ts b/ui/src/api/generated/models/add-operation.ts new file mode 100644 index 0000000..6cbb6cb --- /dev/null +++ b/ui/src/api/generated/models/add-operation.ts @@ -0,0 +1,49 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +/** + * + * @export + * @interface AddOperation + */ +export interface AddOperation { + /** + * + * @type {string} + * @memberof AddOperation + */ + 'op': AddOperationOpEnum; + /** + * A JSON Pointer path pointing to the location to move/copy from. + * @type {string} + * @memberof AddOperation + */ + 'path': string; + /** + * Value can be any JSON value + * @type {any} + * @memberof AddOperation + */ + 'value': any; +} + +export const AddOperationOpEnum = { + Add: 'add' +} as const; + +export type AddOperationOpEnum = typeof AddOperationOpEnum[keyof typeof AddOperationOpEnum]; + + diff --git a/ui/src/api/generated/models/channel.ts b/ui/src/api/generated/models/channel.ts new file mode 100644 index 0000000..0bcf738 --- /dev/null +++ b/ui/src/api/generated/models/channel.ts @@ -0,0 +1,51 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +// May contain unused imports in some cases +// @ts-ignore +import type { Item } from './item'; + +/** + * + * @export + * @interface Channel + */ +export interface Channel { + /** + * + * @type {string} + * @memberof Channel + */ + 'description'?: string; + /** + * + * @type {Array} + * @memberof Channel + */ + 'items'?: Array; + /** + * + * @type {string} + * @memberof Channel + */ + 'link'?: string; + /** + * + * @type {string} + * @memberof Channel + */ + 'title'?: string; +} + diff --git a/ui/src/api/generated/models/copy-operation.ts b/ui/src/api/generated/models/copy-operation.ts new file mode 100644 index 0000000..e4550e2 --- /dev/null +++ b/ui/src/api/generated/models/copy-operation.ts @@ -0,0 +1,49 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +/** + * + * @export + * @interface CopyOperation + */ +export interface CopyOperation { + /** + * A JSON Pointer path pointing to the location to move/copy from. + * @type {string} + * @memberof CopyOperation + */ + 'from': string; + /** + * + * @type {string} + * @memberof CopyOperation + */ + 'op': CopyOperationOpEnum; + /** + * A JSON Pointer path pointing to the location to move/copy from. + * @type {string} + * @memberof CopyOperation + */ + 'path': string; +} + +export const CopyOperationOpEnum = { + Copy: 'copy' +} as const; + +export type CopyOperationOpEnum = typeof CopyOperationOpEnum[keyof typeof CopyOperationOpEnum]; + + diff --git a/ui/src/api/generated/models/cron-friend-post-list.ts b/ui/src/api/generated/models/cron-friend-post-list.ts new file mode 100644 index 0000000..021c294 --- /dev/null +++ b/ui/src/api/generated/models/cron-friend-post-list.ts @@ -0,0 +1,81 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +// May contain unused imports in some cases +// @ts-ignore +import type { CronFriendPost } from './cron-friend-post'; + +/** + * + * @export + * @interface CronFriendPostList + */ +export interface CronFriendPostList { + /** + * Indicates whether current page is the first page. + * @type {boolean} + * @memberof CronFriendPostList + */ + 'first': boolean; + /** + * Indicates whether current page has previous page. + * @type {boolean} + * @memberof CronFriendPostList + */ + 'hasNext': boolean; + /** + * Indicates whether current page has previous page. + * @type {boolean} + * @memberof CronFriendPostList + */ + 'hasPrevious': boolean; + /** + * A chunk of items. + * @type {Array} + * @memberof CronFriendPostList + */ + 'items': Array; + /** + * Indicates whether current page is the last page. + * @type {boolean} + * @memberof CronFriendPostList + */ + 'last': boolean; + /** + * Page number, starts from 1. If not set or equal to 0, it means no pagination. + * @type {number} + * @memberof CronFriendPostList + */ + 'page': number; + /** + * Size of each page. If not set or equal to 0, it means no pagination. + * @type {number} + * @memberof CronFriendPostList + */ + 'size': number; + /** + * Total elements. + * @type {number} + * @memberof CronFriendPostList + */ + 'total': number; + /** + * Indicates total pages. + * @type {number} + * @memberof CronFriendPostList + */ + 'totalPages': number; +} + diff --git a/ui/src/api/generated/models/cron-friend-post.ts b/ui/src/api/generated/models/cron-friend-post.ts new file mode 100644 index 0000000..e00be1c --- /dev/null +++ b/ui/src/api/generated/models/cron-friend-post.ts @@ -0,0 +1,63 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +// May contain unused imports in some cases +// @ts-ignore +import type { CronSpec } from './cron-spec'; +// May contain unused imports in some cases +// @ts-ignore +import type { CronStatus } from './cron-status'; +// May contain unused imports in some cases +// @ts-ignore +import type { Metadata } from './metadata'; + +/** + * + * @export + * @interface CronFriendPost + */ +export interface CronFriendPost { + /** + * + * @type {string} + * @memberof CronFriendPost + */ + 'apiVersion': string; + /** + * + * @type {string} + * @memberof CronFriendPost + */ + 'kind': string; + /** + * + * @type {Metadata} + * @memberof CronFriendPost + */ + 'metadata': Metadata; + /** + * + * @type {CronSpec} + * @memberof CronFriendPost + */ + 'spec'?: CronSpec; + /** + * + * @type {CronStatus} + * @memberof CronFriendPost + */ + 'status'?: CronStatus; +} + diff --git a/ui/src/api/generated/models/cron-spec.ts b/ui/src/api/generated/models/cron-spec.ts new file mode 100644 index 0000000..1f230c1 --- /dev/null +++ b/ui/src/api/generated/models/cron-spec.ts @@ -0,0 +1,54 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +/** + * + * @export + * @interface CronSpec + */ +export interface CronSpec { + /** + * + * @type {string} + * @memberof CronSpec + */ + 'cron'?: string; + /** + * + * @type {Array} + * @memberof CronSpec + */ + 'disableSyncList'?: Array; + /** + * + * @type {number} + * @memberof CronSpec + */ + 'successfulRetainLimit'?: number; + /** + * + * @type {boolean} + * @memberof CronSpec + */ + 'suspend'?: boolean; + /** + * + * @type {string} + * @memberof CronSpec + */ + 'timezone'?: string; +} + diff --git a/ui/src/api/generated/models/cron-status.ts b/ui/src/api/generated/models/cron-status.ts new file mode 100644 index 0000000..49409da --- /dev/null +++ b/ui/src/api/generated/models/cron-status.ts @@ -0,0 +1,36 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +/** + * + * @export + * @interface CronStatus + */ +export interface CronStatus { + /** + * + * @type {string} + * @memberof CronStatus + */ + 'lastScheduledTimestamp'?: string; + /** + * + * @type {string} + * @memberof CronStatus + */ + 'nextSchedulingTimestamp'?: string; +} + diff --git a/ui/src/api/generated/models/friend-post-list.ts b/ui/src/api/generated/models/friend-post-list.ts new file mode 100644 index 0000000..e347e76 --- /dev/null +++ b/ui/src/api/generated/models/friend-post-list.ts @@ -0,0 +1,81 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +// May contain unused imports in some cases +// @ts-ignore +import type { FriendPost } from './friend-post'; + +/** + * + * @export + * @interface FriendPostList + */ +export interface FriendPostList { + /** + * Indicates whether current page is the first page. + * @type {boolean} + * @memberof FriendPostList + */ + 'first': boolean; + /** + * Indicates whether current page has previous page. + * @type {boolean} + * @memberof FriendPostList + */ + 'hasNext': boolean; + /** + * Indicates whether current page has previous page. + * @type {boolean} + * @memberof FriendPostList + */ + 'hasPrevious': boolean; + /** + * A chunk of items. + * @type {Array} + * @memberof FriendPostList + */ + 'items': Array; + /** + * Indicates whether current page is the last page. + * @type {boolean} + * @memberof FriendPostList + */ + 'last': boolean; + /** + * Page number, starts from 1. If not set or equal to 0, it means no pagination. + * @type {number} + * @memberof FriendPostList + */ + 'page': number; + /** + * Size of each page. If not set or equal to 0, it means no pagination. + * @type {number} + * @memberof FriendPostList + */ + 'size': number; + /** + * Total elements. + * @type {number} + * @memberof FriendPostList + */ + 'total': number; + /** + * Indicates total pages. + * @type {number} + * @memberof FriendPostList + */ + 'totalPages': number; +} + diff --git a/ui/src/api/generated/models/friend-post-spec.ts b/ui/src/api/generated/models/friend-post-spec.ts new file mode 100644 index 0000000..3ecafca --- /dev/null +++ b/ui/src/api/generated/models/friend-post-spec.ts @@ -0,0 +1,72 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +/** + * + * @export + * @interface FriendPostSpec + */ +export interface FriendPostSpec { + /** + * + * @type {string} + * @memberof FriendPostSpec + */ + 'author'?: string; + /** + * + * @type {string} + * @memberof FriendPostSpec + */ + 'authorUrl'?: string; + /** + * + * @type {string} + * @memberof FriendPostSpec + */ + 'description'?: string; + /** + * + * @type {string} + * @memberof FriendPostSpec + */ + 'linkName'?: string; + /** + * + * @type {string} + * @memberof FriendPostSpec + */ + 'logo'?: string; + /** + * + * @type {string} + * @memberof FriendPostSpec + */ + 'postLink'?: string; + /** + * + * @type {string} + * @memberof FriendPostSpec + */ + 'pubDate'?: string; + /** + * + * @type {string} + * @memberof FriendPostSpec + */ + 'title'?: string; +} + diff --git a/ui/src/api/generated/models/friend-post.ts b/ui/src/api/generated/models/friend-post.ts new file mode 100644 index 0000000..edf9366 --- /dev/null +++ b/ui/src/api/generated/models/friend-post.ts @@ -0,0 +1,54 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +// May contain unused imports in some cases +// @ts-ignore +import type { FriendPostSpec } from './friend-post-spec'; +// May contain unused imports in some cases +// @ts-ignore +import type { Metadata } from './metadata'; + +/** + * + * @export + * @interface FriendPost + */ +export interface FriendPost { + /** + * + * @type {string} + * @memberof FriendPost + */ + 'apiVersion': string; + /** + * + * @type {string} + * @memberof FriendPost + */ + 'kind': string; + /** + * + * @type {Metadata} + * @memberof FriendPost + */ + 'metadata': Metadata; + /** + * + * @type {FriendPostSpec} + * @memberof FriendPost + */ + 'spec': FriendPostSpec; +} + diff --git a/ui/src/api/generated/models/index.ts b/ui/src/api/generated/models/index.ts new file mode 100644 index 0000000..557e3cf --- /dev/null +++ b/ui/src/api/generated/models/index.ts @@ -0,0 +1,24 @@ +export * from './add-operation'; +export * from './channel'; +export * from './copy-operation'; +export * from './cron-friend-post'; +export * from './cron-friend-post-list'; +export * from './cron-spec'; +export * from './cron-status'; +export * from './friend-post'; +export * from './friend-post-list'; +export * from './friend-post-spec'; +export * from './item'; +export * from './json-patch-inner'; +export * from './link'; +export * from './link-spec'; +export * from './listed-rss-sync-log'; +export * from './listed-rss-sync-log-list'; +export * from './metadata'; +export * from './move-operation'; +export * from './remove-operation'; +export * from './replace-operation'; +export * from './rss-detail'; +export * from './rss-feed-sync-log'; +export * from './rss-feed-sync-log-list'; +export * from './test-operation'; diff --git a/ui/src/api/generated/models/item.ts b/ui/src/api/generated/models/item.ts new file mode 100644 index 0000000..37b6091 --- /dev/null +++ b/ui/src/api/generated/models/item.ts @@ -0,0 +1,60 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +/** + * + * @export + * @interface Item + */ +export interface Item { + /** + * + * @type {string} + * @memberof Item + */ + 'author'?: string; + /** + * + * @type {string} + * @memberof Item + */ + 'description'?: string; + /** + * + * @type {string} + * @memberof Item + */ + 'link'?: string; + /** + * + * @type {string} + * @memberof Item + */ + 'pubDate'?: string; + /** + * + * @type {string} + * @memberof Item + */ + 'title'?: string; + /** + * + * @type {string} + * @memberof Item + */ + 'uri'?: string; +} + diff --git a/ui/src/api/generated/models/json-patch-inner.ts b/ui/src/api/generated/models/json-patch-inner.ts new file mode 100644 index 0000000..a63285d --- /dev/null +++ b/ui/src/api/generated/models/json-patch-inner.ts @@ -0,0 +1,41 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +// May contain unused imports in some cases +// @ts-ignore +import type { AddOperation } from './add-operation'; +// May contain unused imports in some cases +// @ts-ignore +import type { CopyOperation } from './copy-operation'; +// May contain unused imports in some cases +// @ts-ignore +import type { MoveOperation } from './move-operation'; +// May contain unused imports in some cases +// @ts-ignore +import type { RemoveOperation } from './remove-operation'; +// May contain unused imports in some cases +// @ts-ignore +import type { ReplaceOperation } from './replace-operation'; +// May contain unused imports in some cases +// @ts-ignore +import type { TestOperation } from './test-operation'; + +/** + * @type JsonPatchInner + * @export + */ +export type JsonPatchInner = AddOperation | CopyOperation | MoveOperation | RemoveOperation | ReplaceOperation | TestOperation; + + diff --git a/ui/src/api/generated/models/link-spec.ts b/ui/src/api/generated/models/link-spec.ts new file mode 100644 index 0000000..9b89247 --- /dev/null +++ b/ui/src/api/generated/models/link-spec.ts @@ -0,0 +1,60 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +/** + * + * @export + * @interface LinkSpec + */ +export interface LinkSpec { + /** + * + * @type {string} + * @memberof LinkSpec + */ + 'description'?: string; + /** + * + * @type {string} + * @memberof LinkSpec + */ + 'displayName': string; + /** + * + * @type {string} + * @memberof LinkSpec + */ + 'groupName'?: string; + /** + * + * @type {string} + * @memberof LinkSpec + */ + 'logo'?: string; + /** + * + * @type {number} + * @memberof LinkSpec + */ + 'priority'?: number; + /** + * + * @type {string} + * @memberof LinkSpec + */ + 'url': string; +} + diff --git a/ui/src/api/generated/models/link.ts b/ui/src/api/generated/models/link.ts new file mode 100644 index 0000000..057979a --- /dev/null +++ b/ui/src/api/generated/models/link.ts @@ -0,0 +1,54 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +// May contain unused imports in some cases +// @ts-ignore +import type { LinkSpec } from './link-spec'; +// May contain unused imports in some cases +// @ts-ignore +import type { Metadata } from './metadata'; + +/** + * + * @export + * @interface Link + */ +export interface Link { + /** + * + * @type {string} + * @memberof Link + */ + 'apiVersion': string; + /** + * + * @type {string} + * @memberof Link + */ + 'kind': string; + /** + * + * @type {Metadata} + * @memberof Link + */ + 'metadata': Metadata; + /** + * + * @type {LinkSpec} + * @memberof Link + */ + 'spec'?: LinkSpec; +} + diff --git a/ui/src/api/generated/models/listed-rss-sync-log-list.ts b/ui/src/api/generated/models/listed-rss-sync-log-list.ts new file mode 100644 index 0000000..6361ec5 --- /dev/null +++ b/ui/src/api/generated/models/listed-rss-sync-log-list.ts @@ -0,0 +1,81 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +// May contain unused imports in some cases +// @ts-ignore +import type { ListedRssSyncLog } from './listed-rss-sync-log'; + +/** + * + * @export + * @interface ListedRssSyncLogList + */ +export interface ListedRssSyncLogList { + /** + * Indicates whether current page is the first page. + * @type {boolean} + * @memberof ListedRssSyncLogList + */ + 'first': boolean; + /** + * Indicates whether current page has previous page. + * @type {boolean} + * @memberof ListedRssSyncLogList + */ + 'hasNext': boolean; + /** + * Indicates whether current page has previous page. + * @type {boolean} + * @memberof ListedRssSyncLogList + */ + 'hasPrevious': boolean; + /** + * A chunk of items. + * @type {Array} + * @memberof ListedRssSyncLogList + */ + 'items': Array; + /** + * Indicates whether current page is the last page. + * @type {boolean} + * @memberof ListedRssSyncLogList + */ + 'last': boolean; + /** + * Page number, starts from 1. If not set or equal to 0, it means no pagination. + * @type {number} + * @memberof ListedRssSyncLogList + */ + 'page': number; + /** + * Size of each page. If not set or equal to 0, it means no pagination. + * @type {number} + * @memberof ListedRssSyncLogList + */ + 'size': number; + /** + * Total elements. + * @type {number} + * @memberof ListedRssSyncLogList + */ + 'total': number; + /** + * Indicates total pages. + * @type {number} + * @memberof ListedRssSyncLogList + */ + 'totalPages': number; +} + diff --git a/ui/src/api/generated/models/listed-rss-sync-log.ts b/ui/src/api/generated/models/listed-rss-sync-log.ts new file mode 100644 index 0000000..0415af0 --- /dev/null +++ b/ui/src/api/generated/models/listed-rss-sync-log.ts @@ -0,0 +1,42 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +// May contain unused imports in some cases +// @ts-ignore +import type { Link } from './link'; +// May contain unused imports in some cases +// @ts-ignore +import type { RssFeedSyncLog } from './rss-feed-sync-log'; + +/** + * A chunk of items. + * @export + * @interface ListedRssSyncLog + */ +export interface ListedRssSyncLog { + /** + * + * @type {Link} + * @memberof ListedRssSyncLog + */ + 'link': Link; + /** + * + * @type {RssFeedSyncLog} + * @memberof ListedRssSyncLog + */ + 'log'?: RssFeedSyncLog; +} + diff --git a/ui/src/api/generated/models/metadata.ts b/ui/src/api/generated/models/metadata.ts new file mode 100644 index 0000000..4c56cf8 --- /dev/null +++ b/ui/src/api/generated/models/metadata.ts @@ -0,0 +1,72 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +/** + * + * @export + * @interface Metadata + */ +export interface Metadata { + /** + * + * @type {{ [key: string]: string; }} + * @memberof Metadata + */ + 'annotations'?: { [key: string]: string; }; + /** + * + * @type {string} + * @memberof Metadata + */ + 'creationTimestamp'?: string | null; + /** + * + * @type {string} + * @memberof Metadata + */ + 'deletionTimestamp'?: string | null; + /** + * + * @type {Array} + * @memberof Metadata + */ + 'finalizers'?: Array | null; + /** + * The name field will be generated automatically according to the given generateName field + * @type {string} + * @memberof Metadata + */ + 'generateName'?: string; + /** + * + * @type {{ [key: string]: string; }} + * @memberof Metadata + */ + 'labels'?: { [key: string]: string; }; + /** + * Metadata name + * @type {string} + * @memberof Metadata + */ + 'name': string; + /** + * + * @type {number} + * @memberof Metadata + */ + 'version'?: number | null; +} + diff --git a/ui/src/api/generated/models/move-operation.ts b/ui/src/api/generated/models/move-operation.ts new file mode 100644 index 0000000..92189a9 --- /dev/null +++ b/ui/src/api/generated/models/move-operation.ts @@ -0,0 +1,49 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +/** + * + * @export + * @interface MoveOperation + */ +export interface MoveOperation { + /** + * A JSON Pointer path pointing to the location to move/copy from. + * @type {string} + * @memberof MoveOperation + */ + 'from': string; + /** + * + * @type {string} + * @memberof MoveOperation + */ + 'op': MoveOperationOpEnum; + /** + * A JSON Pointer path pointing to the location to move/copy from. + * @type {string} + * @memberof MoveOperation + */ + 'path': string; +} + +export const MoveOperationOpEnum = { + Move: 'move' +} as const; + +export type MoveOperationOpEnum = typeof MoveOperationOpEnum[keyof typeof MoveOperationOpEnum]; + + diff --git a/ui/src/api/generated/models/remove-operation.ts b/ui/src/api/generated/models/remove-operation.ts new file mode 100644 index 0000000..cc4bd53 --- /dev/null +++ b/ui/src/api/generated/models/remove-operation.ts @@ -0,0 +1,43 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +/** + * + * @export + * @interface RemoveOperation + */ +export interface RemoveOperation { + /** + * + * @type {string} + * @memberof RemoveOperation + */ + 'op': RemoveOperationOpEnum; + /** + * A JSON Pointer path pointing to the location to move/copy from. + * @type {string} + * @memberof RemoveOperation + */ + 'path': string; +} + +export const RemoveOperationOpEnum = { + Remove: 'remove' +} as const; + +export type RemoveOperationOpEnum = typeof RemoveOperationOpEnum[keyof typeof RemoveOperationOpEnum]; + + diff --git a/ui/src/api/generated/models/replace-operation.ts b/ui/src/api/generated/models/replace-operation.ts new file mode 100644 index 0000000..599d74b --- /dev/null +++ b/ui/src/api/generated/models/replace-operation.ts @@ -0,0 +1,49 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +/** + * + * @export + * @interface ReplaceOperation + */ +export interface ReplaceOperation { + /** + * + * @type {string} + * @memberof ReplaceOperation + */ + 'op': ReplaceOperationOpEnum; + /** + * A JSON Pointer path pointing to the location to move/copy from. + * @type {string} + * @memberof ReplaceOperation + */ + 'path': string; + /** + * Value can be any JSON value + * @type {any} + * @memberof ReplaceOperation + */ + 'value': any; +} + +export const ReplaceOperationOpEnum = { + Replace: 'replace' +} as const; + +export type ReplaceOperationOpEnum = typeof ReplaceOperationOpEnum[keyof typeof ReplaceOperationOpEnum]; + + diff --git a/ui/src/api/generated/models/rss-detail.ts b/ui/src/api/generated/models/rss-detail.ts new file mode 100644 index 0000000..7c82133 --- /dev/null +++ b/ui/src/api/generated/models/rss-detail.ts @@ -0,0 +1,33 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +// May contain unused imports in some cases +// @ts-ignore +import type { Channel } from './channel'; + +/** + * + * @export + * @interface RssDetail + */ +export interface RssDetail { + /** + * + * @type {Channel} + * @memberof RssDetail + */ + 'channel'?: Channel; +} + diff --git a/ui/src/api/generated/models/rss-feed-sync-log-list.ts b/ui/src/api/generated/models/rss-feed-sync-log-list.ts new file mode 100644 index 0000000..93702a6 --- /dev/null +++ b/ui/src/api/generated/models/rss-feed-sync-log-list.ts @@ -0,0 +1,81 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +// May contain unused imports in some cases +// @ts-ignore +import type { RssFeedSyncLog } from './rss-feed-sync-log'; + +/** + * + * @export + * @interface RssFeedSyncLogList + */ +export interface RssFeedSyncLogList { + /** + * Indicates whether current page is the first page. + * @type {boolean} + * @memberof RssFeedSyncLogList + */ + 'first': boolean; + /** + * Indicates whether current page has previous page. + * @type {boolean} + * @memberof RssFeedSyncLogList + */ + 'hasNext': boolean; + /** + * Indicates whether current page has previous page. + * @type {boolean} + * @memberof RssFeedSyncLogList + */ + 'hasPrevious': boolean; + /** + * A chunk of items. + * @type {Array} + * @memberof RssFeedSyncLogList + */ + 'items': Array; + /** + * Indicates whether current page is the last page. + * @type {boolean} + * @memberof RssFeedSyncLogList + */ + 'last': boolean; + /** + * Page number, starts from 1. If not set or equal to 0, it means no pagination. + * @type {number} + * @memberof RssFeedSyncLogList + */ + 'page': number; + /** + * Size of each page. If not set or equal to 0, it means no pagination. + * @type {number} + * @memberof RssFeedSyncLogList + */ + 'size': number; + /** + * Total elements. + * @type {number} + * @memberof RssFeedSyncLogList + */ + 'total': number; + /** + * Indicates total pages. + * @type {number} + * @memberof RssFeedSyncLogList + */ + 'totalPages': number; +} + diff --git a/ui/src/api/generated/models/rss-feed-sync-log.ts b/ui/src/api/generated/models/rss-feed-sync-log.ts new file mode 100644 index 0000000..3ee66a0 --- /dev/null +++ b/ui/src/api/generated/models/rss-feed-sync-log.ts @@ -0,0 +1,85 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +// May contain unused imports in some cases +// @ts-ignore +import type { Metadata } from './metadata'; + +/** + * + * @export + * @interface RssFeedSyncLog + */ +export interface RssFeedSyncLog { + /** + * + * @type {string} + * @memberof RssFeedSyncLog + */ + 'apiVersion': string; + /** + * + * @type {string} + * @memberof RssFeedSyncLog + */ + 'failureMessage'?: string; + /** + * + * @type {string} + * @memberof RssFeedSyncLog + */ + 'failureReason'?: string; + /** + * + * @type {string} + * @memberof RssFeedSyncLog + */ + 'kind': string; + /** + * + * @type {string} + * @memberof RssFeedSyncLog + */ + 'linkName': string; + /** + * + * @type {Metadata} + * @memberof RssFeedSyncLog + */ + 'metadata': Metadata; + /** + * + * @type {string} + * @memberof RssFeedSyncLog + */ + 'state': RssFeedSyncLogStateEnum; + /** + * + * @type {string} + * @memberof RssFeedSyncLog + */ + 'syncTime': string; +} + +export const RssFeedSyncLogStateEnum = { + Success: 'success', + Failed: 'failed', + Onwait: 'onwait', + Nolink: 'nolink' +} as const; + +export type RssFeedSyncLogStateEnum = typeof RssFeedSyncLogStateEnum[keyof typeof RssFeedSyncLogStateEnum]; + + diff --git a/ui/src/api/generated/models/test-operation.ts b/ui/src/api/generated/models/test-operation.ts new file mode 100644 index 0000000..d7ba2ff --- /dev/null +++ b/ui/src/api/generated/models/test-operation.ts @@ -0,0 +1,49 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.11 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +/** + * + * @export + * @interface TestOperation + */ +export interface TestOperation { + /** + * + * @type {string} + * @memberof TestOperation + */ + 'op': TestOperationOpEnum; + /** + * A JSON Pointer path pointing to the location to move/copy from. + * @type {string} + * @memberof TestOperation + */ + 'path': string; + /** + * Value can be any JSON value + * @type {any} + * @memberof TestOperation + */ + 'value': any; +} + +export const TestOperationOpEnum = { + Test: 'test' +} as const; + +export type TestOperationOpEnum = typeof TestOperationOpEnum[keyof typeof TestOperationOpEnum]; + + diff --git a/ui/src/api/index.ts b/ui/src/api/index.ts new file mode 100644 index 0000000..b8d1afd --- /dev/null +++ b/ui/src/api/index.ts @@ -0,0 +1,21 @@ +import { axiosInstance } from "@halo-dev/api-client"; +import { + ApiFriendMoonyLaV1alpha1FriendPostApi, + CronFriendPostV1alpha1Api, + FriendPostV1alpha1Api, + RssFeedSyncLogV1alpha1Api + + +} from "./generated"; + +const friendsCoreApiClient = { + cron: new CronFriendPostV1alpha1Api(undefined, "", axiosInstance), + friendPost: new FriendPostV1alpha1Api(undefined, "", axiosInstance), + rssFeedSyncLog: new RssFeedSyncLogV1alpha1Api(undefined, "", axiosInstance) + +}; + +const friendsApiClient = { + friendPost: new ApiFriendMoonyLaV1alpha1FriendPostApi(undefined,"",axiosInstance), +}; +export { friendsCoreApiClient, friendsApiClient }; diff --git a/ui/src/components/RssFeedSyncLogListItem.vue b/ui/src/components/RssFeedSyncLogListItem.vue new file mode 100644 index 0000000..49178f4 --- /dev/null +++ b/ui/src/components/RssFeedSyncLogListItem.vue @@ -0,0 +1,136 @@ + + diff --git a/ui/src/components/filter/LinkFilterDropdown.vue b/ui/src/components/filter/LinkFilterDropdown.vue new file mode 100644 index 0000000..f1e977f --- /dev/null +++ b/ui/src/components/filter/LinkFilterDropdown.vue @@ -0,0 +1,164 @@ + + + diff --git a/ui/src/components/formkit/LinkFormKit.vue b/ui/src/components/formkit/LinkFormKit.vue new file mode 100644 index 0000000..e54bf29 --- /dev/null +++ b/ui/src/components/formkit/LinkFormKit.vue @@ -0,0 +1,53 @@ + + diff --git a/ui/src/editor/BlockActionSeparator.vue b/ui/src/editor/BlockActionSeparator.vue new file mode 100644 index 0000000..70ffcc7 --- /dev/null +++ b/ui/src/editor/BlockActionSeparator.vue @@ -0,0 +1,3 @@ + diff --git a/ui/src/editor/RssView.vue b/ui/src/editor/RssView.vue new file mode 100644 index 0000000..d910cf8 --- /dev/null +++ b/ui/src/editor/RssView.vue @@ -0,0 +1,123 @@ + + + + diff --git a/ui/src/editor/index.ts b/ui/src/editor/index.ts new file mode 100644 index 0000000..d0e3a14 --- /dev/null +++ b/ui/src/editor/index.ts @@ -0,0 +1 @@ +export { default as FriendsRssExtension } from "./rss-edited"; diff --git a/ui/src/editor/rss-edited.ts b/ui/src/editor/rss-edited.ts new file mode 100644 index 0000000..0546593 --- /dev/null +++ b/ui/src/editor/rss-edited.ts @@ -0,0 +1,201 @@ +import { + type Editor, + isActive, + mergeAttributes, + Node, + nodeInputRule, + type Range, + VueNodeViewRenderer, + type EditorState, +} from "@halo-dev/richtext-editor"; +import RssView from "./RssView.vue"; +import { markRaw } from "vue"; +import { ToolboxItem } from "@halo-dev/richtext-editor"; +import MdiShare from "~icons/mdi/share"; +import BlockActionSeparator from "./BlockActionSeparator.vue"; +import MdiDeleteForeverOutline from "~icons/mdi/delete-forever-outline?color=red"; +import { deleteNode } from "../utils/delete-node"; +import LayoutGrid from "@/icon/layout-grid.vue"; +import LayoutList from "@/icon/layout-list.vue"; +import FluentRss20Regular from '~icons/fluent/rss-20-regular'; + +declare module "@halo-dev/richtext-editor" { + interface Commands { + "friends-rss": { + setFriendsRss: (options: { src: string }) => ReturnType; + }; + } +} + +const FriendsRssExtension = Node.create({ + name: "friends-rss", + fakeSelection: true, + + group() { + return "block"; + }, + + addAttributes() { + return { + ...this.parent?.(), + src: { + default: null, + parseHTML: (element) => { + return element.getAttribute("src"); + }, + renderHTML(element) { + return { src: element.src }; + }, + }, + layout: { + default: "list", + parseHTML: (element) => { + return element.getAttribute("layout"); + }, + renderHTML(element) { + return { layout: element.layout }; + }, + }, + }; + }, + + parseHTML() { + return [ + { + tag: "friends-rss", + }, + ]; + }, + + renderHTML({ HTMLAttributes }) { + return ["friends-rss", mergeAttributes(HTMLAttributes)]; + }, + addCommands() { + return { + setFriendsRss: + (options) => + ({ commands }) => { + return commands.insertContent([ + { + type: this.name, + attrs: options, + }, + { + type: "paragraph", + content: "" + }, + ]); + }, + }; + }, + + addInputRules() { + return [ + nodeInputRule({ + find: /^\$friends-rss\$$/, + type: this.type, + getAttributes: (e) => ({ + src: e[1] + }), + }), + ]; + }, + + addNodeView() { + return VueNodeViewRenderer(RssView); + }, + + addOptions() { + return { + getCommandMenuItems() { + return { + priority: 2e2, + icon: markRaw(FluentRss20Regular), + title: "RSS展示", + keywords: ["friends-rss", "rss", "RSS展示"], + command: ({ editor, range }: { editor: Editor; range: Range }) => { + editor + .chain() + .focus() + .deleteRange(range) + .setFriendsRss({ src: "" }) + .run(); + }, + }; + }, + getToolboxItems({ editor }: { editor: Editor }) { + return { + priority: 59, + component: markRaw(ToolboxItem), + props: { + editor, + icon: markRaw(FluentRss20Regular), + title: "RSS展示", + action: () => { + editor + .chain() + .focus() + .setFriendsRss({ src: "" }) + .run(); + }, + }, + }; + }, + getBubbleMenu({ editor }: { editor: Editor }) { + return { + pluginKey: "friends-rss-bubble-menu", + shouldShow: ({ state }: { state: EditorState }) => { + return isActive(state, FriendsRssExtension.name); + }, + items: [ + { + priority: 10, + props: { + icon: markRaw(LayoutList), + title: "列表视图", + isActive: () => editor.isActive(FriendsRssExtension.name,{ layout: "list" }), + action: () => editor.commands.updateAttributes(FriendsRssExtension.name, { layout: "list",}), + }, + }, + { + priority: 20, + props: { + icon: markRaw(LayoutGrid), + title: "网格试图", + isActive: () => editor.isActive(FriendsRssExtension.name,{ layout: "grid" }), + action: () => editor.commands.updateAttributes(FriendsRssExtension.name, { layout: "grid",}), + }, + }, + { + priority: 30, + props: { + icon: markRaw(MdiShare), + title: "打开链接", + action: () => { + window.open(editor.getAttributes(FriendsRssExtension.name).src, "_blank"); + }, + }, + }, + { + priority: 40, + component: markRaw(BlockActionSeparator), + }, + { + priority: 50, + props: { + icon: markRaw(MdiDeleteForeverOutline), + title: "删除", + action: ({ editor }: { editor: Editor }) => { + deleteNode(FriendsRssExtension.name, editor); + }, + }, + }, + ], + }; + }, + } + } + + +}) +export default FriendsRssExtension; diff --git a/ui/src/icon/layout-grid.vue b/ui/src/icon/layout-grid.vue new file mode 100644 index 0000000..0a65531 --- /dev/null +++ b/ui/src/icon/layout-grid.vue @@ -0,0 +1,3 @@ + diff --git a/ui/src/icon/layout-list.vue b/ui/src/icon/layout-list.vue new file mode 100644 index 0000000..2e0597b --- /dev/null +++ b/ui/src/icon/layout-list.vue @@ -0,0 +1,3 @@ + diff --git a/ui/src/index.ts b/ui/src/index.ts new file mode 100644 index 0000000..e8aea52 --- /dev/null +++ b/ui/src/index.ts @@ -0,0 +1,41 @@ +import "./styles/index.css"; +import { VLoading } from "@halo-dev/components"; +import "uno.css"; +import { definePlugin } from "@halo-dev/console-shared"; +import RiBloggerLine from '~icons/ri/blogger-line'; +import { defineAsyncComponent, markRaw } from "vue"; +import {FriendsRssExtension} from "@/editor"; +import '@kunkunyu/fridends-rss'; + +export default definePlugin({ + components: {}, + routes: [ + { + parentName: "Root", + route: { + path: "/friends", + name: "Friends", + component: defineAsyncComponent({ + loader: () => import("@/views/Friend.vue"), + loadingComponent: VLoading, + }), + meta: { + title: "朋友圈", + searchable: true, + permissions: ["plugin:friends:view"], + menu: { + name: "朋友圈", + icon: markRaw(RiBloggerLine), + group: "content", + priority: 20, + }, + }, + }, + }, + ], + extensionPoints: { + "default:editor:extension:create": () => { + return [FriendsRssExtension]; + }, + }, +}); diff --git a/ui/src/styles/index.css b/ui/src/styles/index.css new file mode 100644 index 0000000..00a5c59 --- /dev/null +++ b/ui/src/styles/index.css @@ -0,0 +1,184 @@ +.thyuu-card p { + font-size: 14px; + line-height: 1.5; + margin: 1em 0; +} +:is(.thyuu-card, .thyuu-card-menu, .thyuu-paginate) a { + color: inherit; + text-decoration: none; +} +.thyuu-card { + display: grid; + grid: auto / repeat(auto-fill, minmax(min(25em, 100%), 1fr)); + gap: 1em; + margin: 1em 0; + width: 100%; +} +.thyuu-card.column { + grid: auto / auto; +} +.thyuu-card li { + display: flex; + justify-content: space-between; + align-content: space-between; + align-items: center; + flex-wrap: wrap; + padding: 1em; + gap: 1em; + margin: 0; + background-color: rgb(243 244 246 / var(--tw-bg-opacity)); + border-radius: 1em; + overflow: hidden; +} +.thyuu-card.column li > * { + flex: 1; + min-width: min(20em, 100%); +} +.thyuu-card header { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + gap: 1em .5em; + width: 100%; +} +.thyuu-card header > *:last-child { + margin: 0 0 0 auto; + font-size: 12px; + opacity: .3; +} +.thyuu-card header span { + font-size: 14px; + color: #3c434a; +} +.thyuu-card header img { + border-radius: 50%; +} +.thyuu-card article { + display: flex; + flex-wrap: wrap; + gap: 1em .5em; +} +.thyuu-card article > * { + margin: 0; + display: -webkit-inline-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 3; + text-overflow: ellipsis; + overflow: hidden; +} +.thyuu-card article h4 { + color: #3c434a; + font-size: 14px; + font-weight: 600; +} +.thyuu-card footer { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: .5em; + width: 100%; +} +.thyuu-card footer time { + flex: 1; + font-size: 12px; + opacity: .5; + margin: 0 auto 0 0; +} +.thyuu-card .button { + --btn-color: 200deg 60% 40%; + display: flex; + gap: .5em; + color: hsl(var(--btn-color)); + border: 0; + font-size: 12px; + height: 2.5em; + min-height: 2.5em; + line-height: 1.5em; + padding: .5em 1em; + margin: 0; + transition: .3s; +} +.thyuu-card .button:hover { + color: white; + background: hsl(var(--btn-color, 200deg 60% 40%)); +} +.thyuu-card .button.im { + --btn-color: 0deg 60% 40%; +} +.thyuu-card .button, .thyuu-card .button-secondary { + border-radius: 2em; + background: rgb(255 255 255 / var(--tw-bg-opacity)); + vertical-align: top; +} + +.contact-friends-rss-container { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000); + --tw-ring-opacity: 1; + --tw-ring-color: rgb(229 231 235 / var(--tw-ring-opacity)); + border-radius: 4px; + overflow: hidden; + margin-top: .75em +} + +.contact-friends-rss-container--selected { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000); + --tw-ring-color: inherit +} + +.contact-friends-rss-nav { + border-bottom: 1px #e7e7e7 solid; + display: flex; + padding: 5px 10px; + align-items: center +} + +.contact-friends-rss-nav-start { + flex: 1; + display: flex; + align-items: center; + gap: 10px; + font-size: 14px +} + +.contact-friends-rss-nav-end { + justify-content: flex-end +} + +.contact-friends-rss-preview { + padding: 5px 10px +} +.halo-rich-text-editor .editor-content .ProseMirror .block-rss { + list-style: none; + padding: 0; +} +ul.block-rss.block-rss { + box-sizing: border-box; +} +ul.block-rss.is-grid { + display: flex; + flex-wrap: wrap; + list-style: none; + padding: 0; +} + +ul.block-rss.is-grid li { + margin: 0 1em 1em 0; + width: 100% +} + +@media (min-width: 600px) { + ul.block-rss.columns-2 li { + width:calc(50% - 1em) + } +} + +.friends-rss-editor-block__actions-separator { + @apply h-5 bg-slate-200 mx-1.5; + width: 1px; +} diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts new file mode 100644 index 0000000..0f55c82 --- /dev/null +++ b/ui/src/types/index.ts @@ -0,0 +1,23 @@ +import type {Link} from "@/api/generated"; + +export interface LinkList { + + 'first': boolean; + + 'hasNext': boolean; + + 'hasPrevious': boolean; + + 'items': Array; + + 'last': boolean; + + 'page': number; + + 'size': number; + + 'total': number; + + 'totalPages': number; +} + diff --git a/ui/src/utils/date.ts b/ui/src/utils/date.ts new file mode 100644 index 0000000..39def32 --- /dev/null +++ b/ui/src/utils/date.ts @@ -0,0 +1,63 @@ +import dayjs from "dayjs"; +import "dayjs/locale/zh-cn"; +import timezone from "dayjs/plugin/timezone"; +import TimeAgo from "javascript-time-ago"; +import zh from "javascript-time-ago/locale/zh"; + +dayjs.extend(timezone); + +dayjs.locale("zh-cn"); + +TimeAgo.addDefaultLocale(zh); + +export function formatDatetime(date: string | Date | undefined | null): string { + if (!date) { + return ""; + } + return dayjs(date).format("YYYY-MM-DD HH:mm"); +} + +export function timeAgo( + date: string | Date | number | undefined | null +): string { + if (!date) { + return ""; + } + + const currentDate = new Date(); + const inputDate = new Date(date); + + // 365天 * 24小时 * 60分钟 * 60秒 * 1000毫秒 + const oneYearInMilliseconds = 365 * 24 * 60 * 60 * 1000; + + if (currentDate.getTime() - inputDate.getTime() > oneYearInMilliseconds) { + return dayjs(date).format("YYYY-MM-DD"); + } + + const timeAgo = new TimeAgo("zh"); + + return timeAgo.format(new Date(date)); +} + + +export function calculateTimeDifferenceInSeconds( + startTimestamp: string | Date | number | undefined | null, + completionTimestamp: string | Date | number | undefined | null +): string { + if (!startTimestamp || !completionTimestamp) { + return "Invalid input"; + } + + // 将输入转换为 Date 对象 + const startDate = new Date(startTimestamp); + const completionDate = new Date(completionTimestamp); + + // 计算时间差(以毫秒为单位) + const differenceInMilliseconds = completionDate.getTime() - startDate.getTime(); + + // 将时间差转换为秒 + const differenceInSeconds = differenceInMilliseconds / 1000; + + // 返回时间差的整数部分 + return Math.floor(differenceInSeconds).toString(); +} diff --git a/ui/src/utils/delete-node.ts b/ui/src/utils/delete-node.ts new file mode 100644 index 0000000..93fb1c3 --- /dev/null +++ b/ui/src/utils/delete-node.ts @@ -0,0 +1,47 @@ +import type { Editor } from "@halo-dev/richtext-editor"; + +export const deleteNode = (nodeType: string, editor: Editor) => { + const { state } = editor; + const $pos = state.selection.$anchor; + let done = false; + + if ($pos.depth) { + for (let d = $pos.depth; d > 0; d--) { + const node = $pos.node(d); + if (node.type.name === nodeType) { + // @ts-ignore + if (editor.dispatchTransaction) + // @ts-ignore + editor.dispatchTransaction( + state.tr.delete($pos.before(d), $pos.after(d)).scrollIntoView() + ); + done = true; + } + } + } else { + // @ts-ignore + const node = state.selection.node; + if (node && node.type.name === nodeType) { + editor.chain().deleteSelection().run(); + done = true; + } + } + + if (!done) { + const pos = $pos.pos; + + if (pos) { + const node = state.tr.doc.nodeAt(pos); + + if (node && node.type.name === nodeType) { + // @ts-ignore + if (editor.dispatchTransaction) + // @ts-ignore + editor.dispatchTransaction(state.tr.delete(pos, pos + node.nodeSize)); + done = true; + } + } + } + + return done; +}; diff --git a/ui/src/utils/focus.ts b/ui/src/utils/focus.ts new file mode 100644 index 0000000..43995a7 --- /dev/null +++ b/ui/src/utils/focus.ts @@ -0,0 +1,14 @@ +export function setFocus(id: string) { + const inputElement = document.getElementById(id); + if ( + inputElement instanceof HTMLInputElement || + inputElement instanceof HTMLTextAreaElement + ) { + const timer = setTimeout(() => { + const end = inputElement.value.length; + inputElement.setSelectionRange(end, end); + inputElement?.focus(); + clearTimeout(timer); + }, 0); + } +} diff --git a/ui/src/views/CronTab.vue b/ui/src/views/CronTab.vue new file mode 100644 index 0000000..1a35cbd --- /dev/null +++ b/ui/src/views/CronTab.vue @@ -0,0 +1,161 @@ + + + + + diff --git a/ui/src/views/Friend.vue b/ui/src/views/Friend.vue new file mode 100644 index 0000000..87221c2 --- /dev/null +++ b/ui/src/views/Friend.vue @@ -0,0 +1,116 @@ + + + diff --git a/ui/src/views/FriendPostTab.vue b/ui/src/views/FriendPostTab.vue new file mode 100644 index 0000000..ce456e5 --- /dev/null +++ b/ui/src/views/FriendPostTab.vue @@ -0,0 +1,229 @@ + + + diff --git a/ui/src/views/RssFeedSyncLogTab.vue b/ui/src/views/RssFeedSyncLogTab.vue new file mode 100644 index 0000000..34f5af2 --- /dev/null +++ b/ui/src/views/RssFeedSyncLogTab.vue @@ -0,0 +1,134 @@ + + diff --git a/ui/tsconfig.json b/ui/tsconfig.json new file mode 100644 index 0000000..3db79ce --- /dev/null +++ b/ui/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["./env.d.ts", "./src/**/*", "./src/**/*.vue"], + "exclude": ["./src/**/__tests__/*"], + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + "types": ["unplugin-icons/types/vue"] + } +} diff --git a/ui/uno.config.ts b/ui/uno.config.ts new file mode 100644 index 0000000..9d6cc36 --- /dev/null +++ b/ui/uno.config.ts @@ -0,0 +1,7 @@ + +import { defineConfig, presetWind3, transformerCompileClass } from "unocss"; + +export default defineConfig({ + presets: [presetWind3()], + transformers: [transformerCompileClass()], +});