├── .devcontainer ├── Dockerfile ├── celeryCommand.sh ├── devcontainer.json ├── django.Dockerfile ├── docker-compose.yml └── postAttach.sh ├── .dockerignore ├── .editorconfig ├── .env.template ├── .flake8 ├── .github ├── dependabot.yml └── workflows │ ├── codeql.yml │ ├── deploy.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .gitmodules ├── .pre-commit-config.yaml ├── .vscode └── launch.json ├── Dockerfile ├── LICENSE ├── README.md ├── builder ├── gulpfile.js ├── package.json ├── src │ ├── api │ │ ├── api.ts │ │ ├── client.ts │ │ ├── error.ts │ │ ├── index.ts │ │ ├── models.ts │ │ ├── packageSubmit.ts │ │ └── urls.ts │ ├── components │ │ ├── CodeInputPanel.tsx │ │ ├── CommunitySelector.tsx │ │ ├── CsrfTokenContext.tsx │ │ ├── DragDropFileInput.tsx │ │ ├── FormRow.tsx │ │ ├── FormSelectField.tsx │ │ ├── PackageManagement │ │ │ ├── CategoriesSelect.tsx │ │ │ ├── Context.tsx │ │ │ ├── CsrfInput.tsx │ │ │ ├── Deprecation.tsx │ │ │ ├── Modal.tsx │ │ │ ├── PackageStatus.tsx │ │ │ ├── Panel.tsx │ │ │ └── hooks.ts │ │ ├── PackageReview │ │ │ ├── Context.tsx │ │ │ ├── Modal.tsx │ │ │ ├── Panel.tsx │ │ │ ├── ReviewStatus.tsx │ │ │ └── useForm.ts │ │ ├── PackageVersionSummary.tsx │ │ ├── ProgressBar.tsx │ │ ├── ReportModal │ │ │ ├── ReportButton.tsx │ │ │ ├── ReportModal.tsx │ │ │ ├── ReportModalContext.tsx │ │ │ └── hooks.ts │ │ ├── WikiEditor │ │ │ ├── EditorInput.tsx │ │ │ ├── ErrorList.tsx │ │ │ ├── LoadingIndicator.tsx │ │ │ ├── MarkdownPreview.tsx │ │ │ ├── MarkdownPreviewContext.tsx │ │ │ ├── PageEditor.tsx │ │ │ ├── SizeIndicator.tsx │ │ │ └── WikiEditContext.tsx │ │ └── common │ │ │ └── useOnEscape.ts │ ├── debounce.ts │ ├── js │ │ └── custom.js │ ├── main.ts │ ├── manifest.tsx │ ├── markdown.tsx │ ├── retry.ts │ ├── scss │ │ ├── all.scss │ │ ├── base.scss │ │ ├── bootstrap-custom.scss │ │ ├── code-input.scss │ │ ├── footer.scss │ │ ├── forms.scss │ │ ├── general.scss │ │ ├── generic-flex.scss │ │ ├── markdown.scss │ │ ├── navbar.scss │ │ ├── package-list.scss │ │ ├── settings.scss │ │ ├── slimselect.scss │ │ └── theme │ │ │ ├── _bootswatch.scss │ │ │ └── _variables.scss │ ├── state │ │ ├── FileUpload.ts │ │ ├── OnBeforeUnload.ts │ │ ├── PackageSubmission.ts.txt │ │ └── PackageValidator.ts.txt │ ├── throttle.ts │ ├── upload.tsx │ ├── utils.ts │ ├── vendor │ │ ├── zip-fs-full.d.ts │ │ └── zip-fs-full.js │ └── workers.ts ├── tsconfig.json ├── worker_src │ └── md5.ts └── yarn.lock ├── django ├── .coveragerc ├── .gitignore ├── .mypy.ini ├── .test_durations ├── conftest.py ├── django_contracts │ ├── __init__.py │ ├── admin │ │ ├── __init__.py │ │ └── contract.py │ ├── apps.py │ ├── compat.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models │ │ ├── __init__.py │ │ ├── contract.py │ │ └── publishable.py │ └── tests │ │ ├── __init__.py │ │ ├── test_admin.py │ │ └── test_contract.py ├── django_extrafields │ ├── __init__.py │ └── models.py ├── docker_entrypoint.py ├── manage.py ├── overwolf_auth │ ├── README.md │ ├── __init__.py │ ├── apps.py │ ├── backends.py │ ├── cached_jwk_client.py │ ├── compat.py │ └── test_owauth.py ├── poetry.lock ├── pyproject.toml ├── pytest.ini ├── readycheck.py ├── static │ ├── favicon.ico │ ├── icon.png │ ├── overwolf-logo.svg │ ├── ts-logo-horizontal.svg │ └── webfonts │ │ ├── fa-brands-400.eot │ │ ├── fa-brands-400.svg │ │ ├── fa-brands-400.ttf │ │ ├── fa-brands-400.woff │ │ ├── fa-brands-400.woff2 │ │ ├── fa-regular-400.eot │ │ ├── fa-regular-400.svg │ │ ├── fa-regular-400.ttf │ │ ├── fa-regular-400.woff │ │ ├── fa-regular-400.woff2 │ │ ├── fa-solid-900.eot │ │ ├── fa-solid-900.svg │ │ ├── fa-solid-900.ttf │ │ ├── fa-solid-900.woff │ │ └── fa-solid-900.woff2 └── thunderstore │ ├── __init__.py │ ├── abyss │ ├── __init__.py │ ├── middleware.py │ └── storage.py │ ├── account │ ├── __init__.py │ ├── admin │ │ ├── __init__.py │ │ ├── service_account.py │ │ ├── tests │ │ │ ├── __init__.py │ │ │ └── test_user_admin.py │ │ ├── token.py │ │ ├── user.py │ │ └── user_flag.py │ ├── apps.py │ ├── authentication.py │ ├── factories.py │ ├── forms.py │ ├── middleware.py │ ├── migrations │ │ ├── 0001_add_serviceaccount.py │ │ ├── 0002_add_serviceaccount_api_token.py │ │ ├── 0003_add_user_flag.py │ │ ├── 0004_add_usersettings.py │ │ ├── 0005_serviceaccount_created_by.py │ │ └── __init__.py │ ├── models │ │ ├── __init__.py │ │ ├── service_account.py │ │ ├── user_flag.py │ │ └── user_settings.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_middleware.py │ │ ├── test_service_account.py │ │ ├── test_service_account_api_token.py │ │ ├── test_user_flags.py │ │ ├── test_user_session_token.py │ │ ├── test_user_settings.py │ │ └── test_utils.py │ ├── tokens.py │ └── utils.py │ ├── api │ ├── __init__.py │ ├── apps.py │ ├── cyberstorm │ │ ├── serializers │ │ │ ├── __init__.py │ │ │ ├── community.py │ │ │ ├── package.py │ │ │ ├── package_listing.py │ │ │ └── team.py │ │ ├── services │ │ │ ├── __init__.py │ │ │ ├── package.py │ │ │ ├── package_listing.py │ │ │ └── team.py │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── services │ │ │ │ ├── __init__.py │ │ │ │ ├── test_package_listing_services.py │ │ │ │ ├── test_package_services.py │ │ │ │ └── test_team_services.py │ │ │ ├── test_community.py │ │ │ ├── test_community_filters.py │ │ │ ├── test_community_list.py │ │ │ ├── test_create_team.py │ │ │ ├── test_disband_team.py │ │ │ ├── test_markdown.py │ │ │ ├── test_package_deprecate.py │ │ │ ├── test_package_listing.py │ │ │ ├── test_package_listing_actions.py │ │ │ ├── test_package_listing_list.py │ │ │ ├── test_package_permissions.py │ │ │ ├── test_package_rating.py │ │ │ ├── test_package_version_list.py │ │ │ └── test_team.py │ │ └── views │ │ │ ├── __init__.py │ │ │ ├── community.py │ │ │ ├── community_filters.py │ │ │ ├── community_list.py │ │ │ ├── markdown.py │ │ │ ├── package_deprecate.py │ │ │ ├── package_listing.py │ │ │ ├── package_listing_actions.py │ │ │ ├── package_listing_list.py │ │ │ ├── package_permissions.py │ │ │ ├── package_rating.py │ │ │ ├── package_version_list.py │ │ │ └── team.py │ ├── ordering.py │ ├── urls.py │ └── utils.py │ ├── cache │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── cache.py │ ├── enums.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── pagination.py │ ├── storage.py │ ├── tasks.py │ ├── tests │ │ ├── __init__.py │ │ └── test_cache.py │ └── utils.py │ ├── community │ ├── __init__.py │ ├── admin │ │ ├── __init__.py │ │ ├── community.py │ │ ├── community_site.py │ │ ├── package_category.py │ │ ├── package_listing.py │ │ └── package_listing_section.py │ ├── api │ │ ├── __init__.py │ │ └── experimental │ │ │ ├── __init__.py │ │ │ ├── serializers.py │ │ │ ├── tests │ │ │ ├── test_api_approval_status.py │ │ │ ├── test_api_categories_list.py │ │ │ ├── test_api_communities_list.py │ │ │ ├── test_api_current_community.py │ │ │ └── test_api_package_listing.py │ │ │ ├── urls.py │ │ │ └── views │ │ │ ├── __init__.py │ │ │ ├── _utils.py │ │ │ ├── category.py │ │ │ ├── community.py │ │ │ └── listing.py │ ├── apps.py │ ├── consts.py │ ├── context_processors.py │ ├── factories.py │ ├── forms │ │ ├── __init__.py │ │ ├── package_listing.py │ │ └── package_listing_section.py │ ├── middleware.py │ ├── migrations │ │ ├── 0001_add_package_listing.py │ │ ├── 0002_add_webhook_exclude_categories.py │ │ ├── 0003_add_community.py │ │ ├── 0004_make_community_mandatory.py │ │ ├── 0005_add_community_site.py │ │ ├── 0006_add_default_site_mappings.py │ │ ├── 0007_add_community_links.py │ │ ├── 0008_add_dynamic_site_meta.py │ │ ├── 0009_add_dynamic_social_auth_settings.py │ │ ├── 0010_add_is_listed_flag.py │ │ ├── 0011_add_community_site_is_listed_flag.py │ │ ├── 0012_add_unique_constraints.py │ │ ├── 0013_rename_community_listings_relation.py │ │ ├── 0014_fix_category_uniqueness.py │ │ ├── 0015_add_community_site_background_image.py │ │ ├── 0016_add_package_listing_section.py │ │ ├── 0017_add_package_approval_system.py │ │ ├── 0018_add_package_rejection_reason.py │ │ ├── 0019_add_community_metadata.py │ │ ├── 0020_copy_community_data.py │ │ ├── 0021_remove_unused_fields.py │ │ ├── 0022_add_block_auto_updates.py │ │ ├── 0023_add_decompilation_visibility_option.py │ │ ├── 0024_add_community_aggregated_fields.py │ │ ├── 0025_init_aggregated_fields.py │ │ ├── 0026_schedule_aggregated_fields_refresh.py │ │ ├── 0027_add_review_requested_field.py │ │ ├── 0028_add_cover_image_fields.py │ │ ├── 0029_packagelisting_is_auto_imported.py │ │ ├── 0030_add_community_hero_image.py │ │ ├── 0031_community_short_description.py │ │ ├── 0032_add_community_icon.py │ │ ├── 0033_add_mod_manager_support_field.py │ │ ├── 0034_add_search_keywords.py │ │ ├── 0035_add_janitor_role.py │ │ ├── 0036_packagelisting_visibility.py │ │ ├── 0037_create_default_visibility_for_existing_listings.py │ │ └── __init__.py │ ├── models │ │ ├── __init__.py │ │ ├── community.py │ │ ├── community_membership.py │ │ ├── community_site.py │ │ ├── package_category.py │ │ ├── package_listing.py │ │ └── package_listing_section.py │ ├── tasks.py │ ├── templates │ │ └── community │ │ │ ├── community_list.html │ │ │ ├── includes │ │ │ ├── package_header.html │ │ │ ├── package_tabs.html │ │ │ └── version_table.html │ │ │ ├── packagelisting_changelog.html │ │ │ ├── packagelisting_detail.html │ │ │ ├── packagelisting_list.html │ │ │ └── packagelisting_versions.html │ ├── tests │ │ ├── __init__.py │ │ ├── test_admin_package_listing.py │ │ ├── test_admin_package_listing_section.py │ │ ├── test_community.py │ │ ├── test_community_aggregated_fields.py │ │ ├── test_community_site.py │ │ ├── test_forms_package_listing.py │ │ ├── test_forms_package_listing_section.py │ │ ├── test_middleware.py │ │ ├── test_package_category.py │ │ ├── test_package_listing.py │ │ └── test_views.py │ ├── urls.py │ ├── utils.py │ └── views.py │ ├── core │ ├── __init__.py │ ├── admin.py │ ├── api_urls.py │ ├── apps.py │ ├── celery.py │ ├── enums.py │ ├── exception_handler.py │ ├── exceptions.py │ ├── factories.py │ ├── healthcheck.py │ ├── inheritance.py │ ├── jwt_helpers.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ ├── clear_cache.py │ │ │ ├── content │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── community.py │ │ │ ├── community_site.py │ │ │ ├── contract.py │ │ │ ├── contract_version.py │ │ │ ├── dependencies.py │ │ │ ├── markdown.md │ │ │ ├── package.py │ │ │ ├── package_listing.py │ │ │ ├── package_version.py │ │ │ ├── package_wiki.py │ │ │ ├── package_wiki_pages.py │ │ │ └── team.py │ │ │ └── create_test_data.py │ ├── middleware.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_incomingjwtauthconfiguration.py │ │ ├── 0003_adjust_jwt_type_choices.py │ │ └── __init__.py │ ├── mixins.py │ ├── models.py │ ├── setting_urls.py │ ├── settings.py │ ├── storage.py │ ├── tasks.py │ ├── tests │ │ ├── test_celery.py │ │ ├── test_exception_logger.py │ │ ├── test_inheritance.py │ │ ├── test_jwt.py │ │ ├── test_management_commands.py │ │ ├── test_storage.py │ │ └── test_utils.py │ ├── types.py │ ├── urls.py │ ├── utils.py │ └── wsgi.py │ ├── debug │ ├── __init__.py │ └── decorators.py │ ├── frontend │ ├── __init__.py │ ├── admin.py │ ├── api │ │ ├── __init__.py │ │ └── experimental │ │ │ ├── __init__.py │ │ │ ├── serializers │ │ │ ├── __init__.py │ │ │ ├── markdown.py │ │ │ └── views.py │ │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── test_community_package_list.py │ │ │ ├── test_frontpage.py │ │ │ ├── test_markdown.py │ │ │ └── test_package_details.py │ │ │ ├── urls.py │ │ │ └── views │ │ │ ├── __init__.py │ │ │ ├── community_package_list.py │ │ │ ├── frontpage.py │ │ │ ├── markdown.py │ │ │ └── package_details.py │ ├── apps.py │ ├── context.py │ ├── extract_props.py │ ├── middleware.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_modify_meta.py │ │ ├── 0003_add_placement_info.py │ │ ├── 0004_dynamichtml_ordering.py │ │ ├── 0005_add_webhook_exclude_categories.py │ │ ├── 0006_add_community_filters.py │ │ ├── 0007_add_main_left_and_right.py │ │ ├── 0008_add_footer.py │ │ ├── 0009_add_nav_links.py │ │ ├── 0010_add_user_flag.py │ │ ├── 0011_add_package_page_placeholder.py │ │ ├── 0012_add_footer_links.py │ │ ├── 0013_add_nav_bar_right_nav.py │ │ └── __init__.py │ ├── models.py │ ├── templates │ │ ├── ads.txt │ │ ├── base.html │ │ ├── errors │ │ │ ├── 404.html │ │ │ ├── 500.html │ │ │ ├── auth_already_associated.html │ │ │ ├── auth_canceled.html │ │ │ └── auth_failed.html │ │ ├── frontend │ │ │ ├── manifest_v1_validator.html │ │ │ └── markdown_preview.html │ │ └── robots.txt │ ├── templatetags │ │ ├── __init__.py │ │ ├── arrow.py │ │ ├── auth_login.py │ │ ├── cache_until.py │ │ ├── community_url.py │ │ ├── dynamic_html.py │ │ ├── encode_props.py │ │ ├── qurl.py │ │ └── tests │ │ │ ├── __init__.py │ │ │ └── test_community_url.py │ ├── tests │ │ ├── __init__.py │ │ ├── data │ │ │ └── scripts.html │ │ ├── test_context.py │ │ ├── test_dynamic_html.py │ │ ├── test_encode_props.py │ │ ├── test_errors.py │ │ ├── test_extract_props.py │ │ ├── test_footer_links.py │ │ ├── test_qurl.py │ │ ├── test_url_reverse.py │ │ ├── test_views.py │ │ └── utils.py │ ├── url_reverse.py │ └── views.py │ ├── legal │ ├── __init__.py │ ├── apps.py │ ├── context_processors.py │ ├── templates │ │ └── contracts │ │ │ ├── contract.html │ │ │ └── contract_history.html │ ├── tests │ │ ├── __init__.py │ │ ├── test_context_processors.py │ │ └── test_views.py │ ├── urls.py │ └── views.py │ ├── markdown │ ├── __init__.py │ ├── allowed_tags.py │ ├── templatetags │ │ ├── __init__.py │ │ └── markdownify.py │ └── tests │ │ ├── __init__.py │ │ └── test_markdownify.py │ ├── metrics │ ├── __init__.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ └── models │ │ ├── __init__.py │ │ └── package_download.py │ ├── moderation │ ├── __init__.py │ └── urls.py │ ├── modpacks │ ├── __init__.py │ ├── admin │ │ ├── __init__.py │ │ └── legacyprofile.py │ ├── api │ │ ├── __init__.py │ │ └── experimental │ │ │ ├── __init__.py │ │ │ ├── tests │ │ │ ├── __init__.py │ │ │ └── test_legacyprofile.py │ │ │ ├── urls.py │ │ │ └── views │ │ │ ├── __init__.py │ │ │ └── legacyprofile.py │ ├── factories.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_add_file_sha256.py │ │ ├── 0003_add_checksum_index.py │ │ └── __init__.py │ ├── models │ │ ├── __init__.py │ │ └── legacyprofile.py │ └── tests │ │ ├── __init__.py │ │ └── test_legacyprofile.py │ ├── monkeypatch │ ├── __init__.py │ └── monkeypatch_thumbnailer.py │ ├── permissions │ ├── __init__.py │ ├── admin │ │ └── __init__.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── mixins.py │ ├── models │ │ ├── __init__.py │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── _utils.py │ │ │ └── test_visibility.py │ │ └── visibility.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_mixins.py │ │ └── test_validate_user.py │ └── utils.py │ ├── plugins │ ├── __init__.py │ ├── base.py │ ├── loading.py │ ├── registry.py │ └── types.py │ ├── repository │ ├── __init__.py │ ├── admin │ │ ├── __init__.py │ │ ├── actions.py │ │ ├── discord_bot.py │ │ ├── namespace.py │ │ ├── package.py │ │ ├── package_installer.py │ │ ├── package_rating.py │ │ ├── package_version.py │ │ ├── submission.py │ │ ├── team.py │ │ └── tests │ │ │ ├── __init__.py │ │ │ ├── test_actions.py │ │ │ ├── test_discord_bot.py │ │ │ ├── test_namespace.py │ │ │ ├── test_package.py │ │ │ ├── test_package_installer.py │ │ │ ├── test_package_rating.py │ │ │ ├── test_package_version.py │ │ │ ├── test_submission.py │ │ │ └── test_team.py │ ├── api │ │ ├── __init__.py │ │ ├── experimental │ │ │ ├── __init__.py │ │ │ ├── serializers │ │ │ │ ├── __init__.py │ │ │ │ ├── main.py │ │ │ │ ├── markdown.py │ │ │ │ └── validators.py │ │ │ ├── tests │ │ │ │ ├── __init__.py │ │ │ │ ├── test_api_experimental.py │ │ │ │ ├── test_api_experimental_submit.py │ │ │ │ ├── test_async_submit.py │ │ │ │ ├── test_package_index.py │ │ │ │ └── test_package_version.py │ │ │ ├── urls.py │ │ │ └── views │ │ │ │ ├── __init__.py │ │ │ │ ├── package.py │ │ │ │ ├── package_index.py │ │ │ │ ├── package_version.py │ │ │ │ ├── submit.py │ │ │ │ ├── submit_async.py │ │ │ │ ├── tests │ │ │ │ ├── __init__.py │ │ │ │ ├── test_validators.py │ │ │ │ └── test_wiki.py │ │ │ │ ├── upload.py │ │ │ │ ├── validators.py │ │ │ │ └── wiki.py │ │ └── v1 │ │ │ ├── __init__.py │ │ │ ├── serializers.py │ │ │ ├── tasks.py │ │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── test_api_v1.py │ │ │ ├── test_caches.py │ │ │ ├── test_metrics.py │ │ │ ├── test_package_index_build.py │ │ │ └── test_serializers.py │ │ │ ├── urls.py │ │ │ ├── views │ │ │ ├── __init__.py │ │ │ ├── deprecate.py │ │ │ ├── listing_index.py │ │ │ └── metrics.py │ │ │ └── viewsets.py │ ├── apps.py │ ├── cache.py │ ├── consts.py │ ├── context_processors.py │ ├── factories.py │ ├── filetree.py │ ├── forms │ │ ├── __init__.py │ │ └── team.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ └── update_caches.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_add_packageversiondownloadevent.py │ │ ├── 0003_add_dependencies.py │ │ ├── 0004_add_update_date.py │ │ ├── 0005_migrate_update_dates.py │ │ ├── 0006_package_pinned.py │ │ ├── 0007_rename_pinned.py │ │ ├── 0008_add_uploader_id.py │ │ ├── 0009_create_uploader_ids.py │ │ ├── 0010_package_uploader_field.py │ │ ├── 0011_package_remove_owner.py │ │ ├── 0012_package_is_deprecated.py │ │ ├── 0013_package_cache_latest.py │ │ ├── 0014_update_meta.py │ │ ├── 0015_add_ratings.py │ │ ├── 0016_recache_versions.py │ │ ├── 0017_organize_storage.py │ │ ├── 0018_add_discord_permissions.py │ │ ├── 0019_add_version_file_size.py │ │ ├── 0020_fetch_version_file_sizes.py │ │ ├── 0021_set_file_size_required.py │ │ ├── 0022_add_package_listing.py │ │ ├── 0023_add_cache_refresh_celery_schedule.py │ │ ├── 0024_add_uploader_identity_is_active.py │ │ ├── 0025_alter_name_validator.py │ │ ├── 0026_add_unique_constraints.py │ │ ├── 0027_rename_uploader_identity_to_team.py │ │ ├── 0028_namespace.py │ │ ├── 0029_create_namespaces_for_teams.py │ │ ├── 0030_package_namespace.py │ │ ├── 0031_add_namespaces_to_existing_packages.py │ │ ├── 0032_package_namespace_non_nullable.py │ │ ├── 0033_add_s3_cache.py │ │ ├── 0034_add_team_donation_link.py │ │ ├── 0035_add_format_spec.py │ │ ├── 0036_fix_package_last_updated.py │ │ ├── 0037_add_changelog_field.py │ │ ├── 0038_add_wiki.py │ │ ├── 0039_add_package_file_tree.py │ │ ├── 0040_add_decompilation_visibility_option.py │ │ ├── 0041_add_indexes.py │ │ ├── 0042_add_package_index_cache.py │ │ ├── 0043_add_package_index_task_schedule.py │ │ ├── 0044_add_deprecate_permission.py │ │ ├── 0045_add_async_submission.py │ │ ├── 0046_add_submission_cleanup_schedule.py │ │ ├── 0047_add_visibility_flags.py │ │ ├── 0048_populate_visibility.py │ │ ├── 0049_add_package_installer.py │ │ ├── 0050_add_installer_name_description.py │ │ ├── 0051_bigint_file_size.py │ │ ├── 0052_add_chunked_package_cache.py │ │ ├── 0053_schedule_chunked_package_caching.py │ │ ├── 0054_alter_chunked_package_cache_index.py │ │ ├── 0055_delete_namespaces_without_team.py │ │ ├── 0056_packageversion_uploaded_by.py │ │ ├── 0057_team_max_file_count_per_zip.py │ │ ├── 0058_packageversion_review_status.py │ │ ├── 0059_package_visibility.py │ │ ├── 0060_create_default_visibility_for_existing_records.py │ │ └── __init__.py │ ├── mixins.py │ ├── models │ │ ├── __init__.py │ │ ├── cache.py │ │ ├── discord_bot.py │ │ ├── namespace.py │ │ ├── package.py │ │ ├── package_download.py │ │ ├── package_installer.py │ │ ├── package_rating.py │ │ ├── package_version.py │ │ ├── submission.py │ │ ├── team.py │ │ └── wiki.py │ ├── package_formats.py │ ├── package_manifest.py │ ├── package_reference.py │ ├── package_upload.py │ ├── permissions.py │ ├── serializer_fields.py │ ├── tasks │ │ ├── __init__.py │ │ ├── caches.py │ │ ├── downloads.py │ │ ├── files.py │ │ ├── submission.py │ │ └── tests │ │ │ ├── __init__.py │ │ │ ├── test_files.py │ │ │ ├── test_submission.py │ │ │ └── test_submission_cleanup.py │ ├── templates │ │ ├── repository │ │ │ ├── _wiki_base.html │ │ │ ├── includes │ │ │ │ ├── dependencies.html │ │ │ │ └── pagination.html │ │ │ ├── package_create.html │ │ │ ├── package_create_old.html │ │ │ ├── package_docs.html │ │ │ ├── package_wiki_404.html │ │ │ ├── package_wiki_detail.html │ │ │ ├── package_wiki_edit.html │ │ │ ├── package_wiki_home.html │ │ │ └── packageversion_detail.html │ │ └── settings │ │ │ ├── team_add_member.html │ │ │ ├── team_add_service_account.html │ │ │ ├── team_base.html │ │ │ ├── team_create.html │ │ │ ├── team_detail.html │ │ │ ├── team_disband.html │ │ │ ├── team_donation_link.html │ │ │ ├── team_leave.html │ │ │ ├── team_list.html │ │ │ └── team_service_account.html │ ├── tests │ │ ├── __init__.py │ │ ├── test_async_submission.py │ │ ├── test_bot_api.py │ │ ├── test_cache_models.py │ │ ├── test_download_metrics.py │ │ ├── test_namespace.py │ │ ├── test_package.py │ │ ├── test_package_cache.py │ │ ├── test_package_detail_view.py │ │ ├── test_package_formats.py │ │ ├── test_package_manifest.py │ │ ├── test_package_rating.py │ │ ├── test_package_reference.py │ │ ├── test_package_upload.py │ │ ├── test_package_version.py │ │ ├── test_package_wiki.py │ │ ├── test_permissions.py │ │ ├── test_permissions_checker.py │ │ ├── test_query_performance.py │ │ ├── test_serializer_fields.py │ │ ├── test_tabs_mixin.py │ │ ├── test_team.py │ │ ├── test_team_forms.py │ │ ├── test_utils.py │ │ ├── test_validators.py │ │ ├── test_views.py │ │ └── test_wiki_views.py │ ├── urls.py │ ├── utils.py │ ├── validation │ │ ├── __init__.py │ │ ├── categories.py │ │ ├── icon.py │ │ ├── manifest.py │ │ ├── markdown.py │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── test_categories.py │ │ │ ├── test_icon.py │ │ │ ├── test_manifest.py │ │ │ ├── test_markdown.py │ │ │ └── test_zip.py │ │ └── zip.py │ ├── validators.py │ └── views │ │ ├── __init__.py │ │ ├── docs.py │ │ ├── mixins.py │ │ ├── package │ │ ├── __init__.py │ │ ├── _utils.py │ │ ├── create.py │ │ ├── detail.py │ │ ├── download.py │ │ ├── list.py │ │ ├── tabs │ │ │ ├── __init__.py │ │ │ ├── changelog.py │ │ │ ├── tests │ │ │ │ ├── __init__.py │ │ │ │ ├── test_changelog.py │ │ │ │ └── test_versions.py │ │ │ └── versions.py │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── test_list.py │ │ │ └── test_utils.py │ │ └── version.py │ │ ├── repository.py │ │ ├── team_settings.py │ │ └── wiki.py │ ├── schema_import │ ├── __init__.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── schema.py │ ├── sync.py │ ├── tasks.py │ └── tests │ │ ├── __init__.py │ │ └── test_sync.py │ ├── schema_server │ ├── __init__.py │ ├── admin │ │ ├── __init__.py │ │ ├── channel.py │ │ └── tests │ │ │ ├── __init__.py │ │ │ └── test_channel.py │ ├── api │ │ ├── __init__.py │ │ └── experimental │ │ │ ├── __init__.py │ │ │ ├── tests │ │ │ ├── __init__.py │ │ │ └── test_channel.py │ │ │ ├── urls.py │ │ │ └── views │ │ │ ├── __init__.py │ │ │ └── channel.py │ ├── apps.py │ ├── factories.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ └── models │ │ ├── __init__.py │ │ ├── channel.py │ │ ├── file.py │ │ └── tests │ │ ├── __init__.py │ │ ├── test_channel.py │ │ └── test_file.py │ ├── social │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ ├── experimental │ │ │ ├── __init__.py │ │ │ ├── tests │ │ │ │ ├── __init__.py │ │ │ │ └── test_current_user_rated_packages.py │ │ │ ├── urls.py │ │ │ └── views │ │ │ │ ├── __init__.py │ │ │ │ ├── complete_login.py │ │ │ │ ├── current_user.py │ │ │ │ ├── delete_session.py │ │ │ │ ├── overwolf.py │ │ │ │ └── validate_session.py │ │ └── v1 │ │ │ ├── __init__.py │ │ │ └── views │ │ │ ├── __init__.py │ │ │ └── current_user.py │ ├── permissions.py │ ├── providers.py │ ├── templates │ │ └── settings │ │ │ ├── base.html │ │ │ ├── delete_account.html │ │ │ ├── includes │ │ │ └── provider_display.html │ │ │ └── linked_accounts.html │ ├── templatetags │ │ ├── __init__.py │ │ └── social.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_complete_login.py │ │ ├── test_current_user.py │ │ ├── test_delete_session.py │ │ ├── test_overwolf.py │ │ ├── test_providers.py │ │ ├── test_utils.py │ │ └── test_validate_session.py │ ├── urls.py │ ├── utils.py │ └── views.py │ ├── storage │ ├── __init__.py │ ├── admin │ │ ├── __init__.py │ │ ├── blob.py │ │ ├── group.py │ │ ├── mixins.py │ │ ├── reference.py │ │ └── tests │ │ │ ├── __init__.py │ │ │ └── test_admin.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_add_group.py │ │ └── __init__.py │ └── models │ │ ├── __init__.py │ │ ├── blob.py │ │ ├── group.py │ │ ├── reference.py │ │ └── tests │ │ ├── __init__.py │ │ ├── test_blob.py │ │ ├── test_group.py │ │ └── test_reference.py │ ├── ts_reports │ ├── __init__.py │ ├── admin │ │ ├── __init__.py │ │ └── package_report.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_add_package_report_model.py │ │ └── __init__.py │ ├── models │ │ ├── __init__.py │ │ ├── _common.py │ │ └── package_report.py │ └── tests │ │ ├── __init__.py │ │ └── test_package_report.py │ ├── usermedia │ ├── __init__.py │ ├── admin │ │ ├── __init__.py │ │ └── usermedia.py │ ├── api │ │ ├── __init__.py │ │ └── experimental │ │ │ ├── __init__.py │ │ │ ├── serializers │ │ │ ├── __init__.py │ │ │ └── upload.py │ │ │ ├── urls.py │ │ │ └── views │ │ │ ├── __init__.py │ │ │ └── upload.py │ ├── apps.py │ ├── cleanup.py │ ├── consts.py │ ├── exceptions.py │ ├── factories.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_add_cleanup_schedule.py │ │ ├── 0003_usermedia_owner_set_null.py │ │ ├── 0004_usermedia_size_bigint.py │ │ └── __init__.py │ ├── models │ │ ├── __init__.py │ │ └── usermedia.py │ ├── s3_client.py │ ├── s3_upload.py │ ├── tasks.py │ └── tests │ │ ├── __init__.py │ │ ├── test_api.py │ │ ├── test_cleanup.py │ │ ├── test_models.py │ │ ├── test_s3_client.py │ │ ├── test_s3_upload.py │ │ ├── test_serializer_fields.py │ │ ├── test_tasks.py │ │ └── utils.py │ ├── utils │ ├── __init__.py │ ├── batch.py │ ├── contexts.py │ ├── decorators.py │ ├── gzip.py │ ├── iterators.py │ ├── makemigrations.py │ └── tests │ │ ├── __init__.py │ │ ├── test_batch.py │ │ ├── test_contexts.py │ │ ├── test_decorators.py │ │ └── test_makemigrations.py │ ├── webhooks │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── audit.py │ ├── discord.py │ ├── forms │ │ ├── __init__.py │ │ └── webhook.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_add_webhook_exclude_categories.py │ │ ├── 0003_add_webhook_nsfw_exclusion.py │ │ ├── 0004_add_webhook_inclusion_filters.py │ │ ├── 0005_add_community_site_relation.py │ │ ├── 0006_make_community_mandatory.py │ │ ├── 0007_add_community_to_webhook.py │ │ ├── 0008_remove_community_site.py │ │ ├── 0009_add_audit_webhook.py │ │ └── __init__.py │ ├── models │ │ ├── __init__.py │ │ ├── audit.py │ │ └── release.py │ ├── tasks │ │ ├── __init__.py │ │ ├── audit.py │ │ └── tests │ │ │ ├── __init__.py │ │ │ └── test_audit.py │ └── tests │ │ ├── __init__.py │ │ ├── test_forms_webhook.py │ │ └── test_webhook.py │ └── wiki │ ├── __init__.py │ ├── admin │ ├── __init__.py │ ├── page.py │ ├── tests │ │ ├── __init__.py │ │ └── test_admin.py │ └── wiki.py │ ├── api │ ├── __init__.py │ └── experimental │ │ ├── __init__.py │ │ ├── serializers.py │ │ ├── tests │ │ ├── __init__.py │ │ └── test_views.py │ │ ├── urls.py │ │ └── views.py │ ├── apps.py │ ├── factories.py │ ├── migrations │ ├── 0001_initial.py │ ├── 0002_add_index.py │ ├── 0003_add_index.py │ └── __init__.py │ ├── models │ ├── __init__.py │ └── wiki.py │ └── tests │ ├── __init__.py │ └── test_models.py ├── docker-compose.yml ├── docker ├── builder.Dockerfile └── docker-compose.pytest.yml ├── docs ├── db-upgrade.md ├── rfc_service_layer_document.md └── service_layer_implementation_guide.md ├── loadtest ├── .gitignore ├── README.md ├── loadtest │ ├── __init__.py │ ├── cli.py │ └── loadtest.py ├── locustfile.py ├── main.py ├── poetry.lock └── pyproject.toml ├── minio ├── Dockerfile └── thunderstore-entrypoint.sh └── pgbouncer.ini /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8-buster 2 | 3 | ENV PYTHONUNBUFFERED 1 4 | 5 | RUN apt-get update \ 6 | && apt-get install -y \ 7 | curl build-essential sudo git \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | ARG USERNAME=vscode 11 | ARG USER_UID=1000 12 | ARG USER_GID=$USER_UID 13 | 14 | RUN groupadd --gid $USER_GID $USERNAME \ 15 | && useradd -mrs /bin/bash --uid $USER_UID --gid $USER_GID $USERNAME \ 16 | && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ 17 | && chmod 0440 /etc/sudoers.d/$USERNAME 18 | 19 | RUN mkdir -p /workspace/django/var && chown -R $USERNAME:$USERNAME /workspace 20 | 21 | USER $USERNAME 22 | ENV PATH /home/$USERNAME/.local/bin:$PATH 23 | 24 | RUN pip install -U pip poetry~=1.1.4 --no-cache-dir && \ 25 | poetry config virtualenvs.in-project true 26 | 27 | WORKDIR /workspace 28 | -------------------------------------------------------------------------------- /.devcontainer/celeryCommand.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd /workspace/django 4 | poetry run python /workspace/django/docker_entrypoint.py "$@" 5 | -------------------------------------------------------------------------------- /.devcontainer/django.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8-buster 2 | 3 | ENV PYTHONUNBUFFERED 1 4 | 5 | RUN apt-get update \ 6 | && apt-get install -y \ 7 | curl build-essential sudo git \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | RUN pip install -U pip poetry~=1.1.4 --no-cache-dir && \ 11 | poetry config virtualenvs.in-project true 12 | -------------------------------------------------------------------------------- /.devcontainer/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | x-django-services: &django-service 4 | build: 5 | context: . 6 | dockerfile: .devcontainer/django.Dockerfile 7 | volumes: 8 | - .:/workspace:ro 9 | 10 | services: 11 | django: 12 | build: 13 | context: . 14 | dockerfile: .devcontainer/Dockerfile 15 | command: sleep infinity 16 | volumes: 17 | - built-static:/workspace/django/static_built:ro 18 | - .:/workspace 19 | 20 | django-worker: 21 | <<: *django-service 22 | command: bash /workspace/.devcontainer/celeryCommand.sh celeryworker 23 | django-beat: 24 | <<: *django-service 25 | command: bash /workspace/.devcontainer/celeryCommand.sh celerybeat 26 | -------------------------------------------------------------------------------- /.devcontainer/postAttach.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd /workspace/django/ 4 | poetry install 2>&1 5 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | builder/node_modules 2 | django/.pytest_cache 3 | django/.coverage 4 | django/htmlcov 5 | .vscode/ 6 | .env 7 | package-example/ 8 | *.sql 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | charset = utf-8 8 | indent_style = space 9 | indent_size = 4 10 | 11 | [{*.yml,*.yaml}] 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 88 3 | exclude = 4 | .git, 5 | __pycache__, 6 | migrations 7 | ignore = E203,W503,PT003,PIE783,E501 8 | pytest-parametrize-values-type = tuple 9 | pytest-parametrize-values-row-type = tuple 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pip" 4 | directory: "/django/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "npm" 8 | directory: "/builder/" 9 | schedule: 10 | interval: "daily" 11 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | schedule: 9 | - cron: "0 0 * * *" 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | language: ["python", "javascript"] 20 | 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@v3 24 | with: 25 | submodules: false 26 | 27 | - name: Initialize CodeQL 28 | uses: github/codeql-action/init@v1 29 | with: 30 | languages: ${{ matrix.language }} 31 | 32 | - name: Perform CodeQL Analysis 33 | uses: github/codeql-action/analyze@v1 34 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Trigger deploy 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*.*.*" 7 | 8 | jobs: 9 | deploy: 10 | name: Trigger deploy 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Call deployment workflow 14 | run: | 15 | curl \ 16 | -X POST \ 17 | -u "${{ secrets.DEPLOY_WORKFLOW_DISPATCH_USER }}:${{ secrets.DEPLOY_WORKFLOW_DISPATCH_PAT }}" \ 18 | -H "Accept: application/vnd.github.v3+json" \ 19 | ${{ secrets.DEPLOY_WORKFLOW_DISPATCH_URL }} \ 20 | -d '{"ref": "master", "inputs": {"tag": "${{ github.ref_name }}"}}' 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Custom 2 | node/ 3 | .vscode/settings.json 4 | .env 5 | **/*.sql 6 | *.service-account.json 7 | kubernetes/deployment.yaml 8 | package-example/ 9 | **/node_modules/ 10 | .idea/ 11 | 12 | # QoL for local dev scripts 13 | *.local.py 14 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "python-packages"] 2 | path = python-packages 3 | url = git@github.com:thunderstore-io/python-packages.git 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Python: Django", 6 | "type": "python", 7 | "request": "launch", 8 | "program": "${workspaceFolder}/django/manage.py", 9 | "console": "integratedTerminal", 10 | "args": ["runserver", "0.0.0.0:8000"], 11 | "django": true 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /builder/src/api/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./models"; 2 | export * from "./error"; 3 | export * from "./api"; 4 | -------------------------------------------------------------------------------- /builder/src/components/CsrfTokenContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { PropsWithChildren, useContext } from "react"; 2 | 3 | export interface CsrfTokenProviderProps { 4 | token: string; 5 | } 6 | 7 | export const CsrfTokenProvider: React.FC< 8 | PropsWithChildren 9 | > = ({ children, token }) => { 10 | return ( 11 | 12 | {children} 13 | 14 | ); 15 | }; 16 | 17 | export const CsrfTokenContext = React.createContext( 18 | undefined 19 | ); 20 | 21 | export const useCsrfToken = (): string => { 22 | return useContext(CsrfTokenContext)!; 23 | }; 24 | -------------------------------------------------------------------------------- /builder/src/components/FormRow.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface FormRowProps { 4 | label: string; 5 | labelFor: string; 6 | error: string | null; 7 | wrap?: boolean; 8 | } 9 | 10 | export const FormRow: React.FC = (props) => { 11 | return ( 12 |
13 |
14 | 15 | {props.children} 16 |
17 | {props.error && ( 18 |
{props.error}
19 | )} 20 |
21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /builder/src/components/PackageManagement/CsrfInput.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useCsrfToken } from "../CsrfTokenContext"; 3 | 4 | export const CsrfInput: React.FC = () => { 5 | const token = useCsrfToken(); 6 | return ; 7 | }; 8 | -------------------------------------------------------------------------------- /builder/src/components/PackageManagement/PackageStatus.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface PackageStatusProps { 4 | isDeprecated: boolean; 5 | } 6 | 7 | export const PackageStatus: React.FC = (props) => { 8 | const text = props.isDeprecated ? "Deprecated" : "Active"; 9 | const className = props.isDeprecated ? "text-warning" : "text-success"; 10 | 11 | return
{text}
; 12 | }; 13 | -------------------------------------------------------------------------------- /builder/src/components/PackageReview/ReviewStatus.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ReviewStatus } from "../../api"; 3 | 4 | interface PackageStatusProps { 5 | reviewStatus: ReviewStatus; 6 | } 7 | 8 | function getStatusClassName(status: ReviewStatus) { 9 | switch (status) { 10 | case "approved": 11 | return "text-success"; 12 | case "unreviewed": 13 | return "text-warning"; 14 | case "rejected": 15 | return "text-danger"; 16 | } 17 | } 18 | 19 | export const ReviewStatusDisplay: React.FC = ({ 20 | reviewStatus, 21 | }) => { 22 | return ( 23 |
24 | {reviewStatus} 25 |
26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /builder/src/components/ProgressBar.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { observer } from "mobx-react"; 3 | 4 | interface ProgressBarProps { 5 | className: string; 6 | progress: number; 7 | } 8 | export const ProgressBar: React.FC = observer( 9 | ({ className, progress }) => { 10 | return ( 11 |
12 |
22 |
23 | ); 24 | } 25 | ); 26 | -------------------------------------------------------------------------------- /builder/src/components/WikiEditor/ErrorList.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export interface ErrorListProps { 4 | errors?: string[]; 5 | } 6 | export const ErrorList: React.FC = ({ errors }) => { 7 | if (!errors || errors.length == 0) return null; 8 | if (errors.length == 1) { 9 | return ( 10 |
11 | {errors[0]} 12 |
13 | ); 14 | } 15 | return ( 16 |
17 |
    18 | {errors.map((x, i) => ( 19 |
  • {x}
  • 20 | ))} 21 |
22 |
23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /builder/src/components/WikiEditor/LoadingIndicator.tsx: -------------------------------------------------------------------------------- 1 | export const LoadingIndicator = () => { 2 | return ( 3 |
18 | 19 | 20 | Loading... 21 | 22 |
23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /builder/src/components/WikiEditor/MarkdownPreview.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { userMarkdownPreview } from "./MarkdownPreviewContext"; 3 | import { LoadingIndicator } from "./LoadingIndicator"; 4 | 5 | export const MarkdownPreview: React.FC = () => { 6 | const context = userMarkdownPreview(); 7 | 8 | return ( 9 |
10 | {context.isLoading && } 11 |
15 |
16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /builder/src/components/WikiEditor/SizeIndicator.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export interface SizeIndicatorProps { 4 | current: number; 5 | max: number; 6 | } 7 | export const SizeIndicator: React.FC = ({ 8 | current, 9 | max, 10 | }) => { 11 | const exceedsLimit = current > max; 12 | const closeToLimit = current > max * 0.9; 13 | return ( 14 | 23 | {current} / {max} 24 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /builder/src/components/common/useOnEscape.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | 3 | export const useOnEscape = (onEscape: () => void) => { 4 | const handleEvent = (event: KeyboardEvent) => { 5 | if (event.key === "Escape") { 6 | onEscape(); 7 | } 8 | }; 9 | useEffect(() => { 10 | document.addEventListener("keydown", handleEvent); 11 | return () => document.removeEventListener("keydown", handleEvent); 12 | }, [handleEvent]); 13 | }; 14 | -------------------------------------------------------------------------------- /builder/src/debounce.ts: -------------------------------------------------------------------------------- 1 | import { DependencyList, EffectCallback, useEffect } from "react"; 2 | 3 | export const useDebounce = ( 4 | time: number, 5 | effect: EffectCallback, 6 | deps?: DependencyList, 7 | onChange?: () => void 8 | ) => { 9 | useEffect(() => { 10 | if (onChange) onChange(); 11 | const timeoutId = setTimeout(() => effect(), time); 12 | return () => clearTimeout(timeoutId); 13 | }, deps); 14 | }; 15 | -------------------------------------------------------------------------------- /builder/src/retry.ts: -------------------------------------------------------------------------------- 1 | import { sleep } from "./utils"; 2 | 3 | export async function retry( 4 | attempts: number, 5 | fn: () => Promise, 6 | canRetry: () => boolean, 7 | onError: (e: Error | unknown) => void 8 | ): Promise { 9 | for (let retries = attempts; retries > 0; retries--) { 10 | if (!canRetry()) { 11 | throw new Error("Retries interrupted"); 12 | } 13 | try { 14 | return await fn(); 15 | } catch (e) { 16 | onError(e); 17 | await sleep(5000); 18 | } 19 | } 20 | throw new Error(`Promise failed after ${attempts} retries!`); 21 | } 22 | -------------------------------------------------------------------------------- /builder/src/scss/all.scss: -------------------------------------------------------------------------------- 1 | // Bootstrap and theme 2 | @import "./theme/_variables"; 3 | @import "./node_modules/bootstrap/scss/bootstrap"; 4 | @import "./bootstrap-custom"; 5 | @import "./theme/_bootswatch"; 6 | 7 | // Font awesome 8 | @import "./node_modules/@fortawesome/fontawesome-free/css/all"; 9 | 10 | // GitHub Markdown 11 | @import "./node_modules/github-markdown-css/github-markdown"; 12 | 13 | // Slimselect 14 | @import "./node_modules/slim-select/dist/slimselect"; 15 | 16 | // Custom Markdown 17 | @import "./markdown"; 18 | 19 | // Other custom 20 | @import "./base"; 21 | @import "./settings"; 22 | @import "./general"; 23 | @import "./package-list"; 24 | @import "./forms"; 25 | @import "./slimselect"; 26 | @import "./navbar"; 27 | @import "./footer"; 28 | @import "./generic-flex"; 29 | 30 | // Code Input 31 | @import "./code-input"; 32 | -------------------------------------------------------------------------------- /builder/src/scss/base.scss: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | min-height: 100vh; 4 | } 5 | 6 | body { 7 | display: flex; 8 | flex-direction: column; 9 | } 10 | 11 | :root { 12 | color-scheme: dark; 13 | } 14 | -------------------------------------------------------------------------------- /builder/src/scss/code-input.scss: -------------------------------------------------------------------------------- 1 | .code-input { 2 | width: 100%; 3 | overflow-y: scroll; 4 | border-radius: 4px; 5 | border-style: none; 6 | font-family: "Courier New", monospace; 7 | background-color: #191919; 8 | color: #ffffff; 9 | padding: 6px; 10 | 11 | -ms-overflow-style: none; /* Internet Explorer 10+ */ 12 | resize: none; 13 | } 14 | .code-input:focus-visible { 15 | outline: none; 16 | } 17 | 18 | .code-input::-webkit-scrollbar { 19 | background-color: rgba(0, 0, 0, 0.2); 20 | } 21 | 22 | .code-input::-webkit-scrollbar-corner { 23 | background-color: rgba(0, 0, 0, 0.6); 24 | } 25 | 26 | .code-input::-webkit-scrollbar-thumb { 27 | background-color: rgba(255, 255, 255, 0.4); 28 | cursor: pointer; 29 | } 30 | 31 | .code-input::-webkit-scrollbar-track { 32 | display: none; 33 | } 34 | -------------------------------------------------------------------------------- /builder/src/scss/generic-flex.scss: -------------------------------------------------------------------------------- 1 | .gap-1 { 2 | gap: 6px; 3 | } 4 | 5 | .gap-2 { 6 | gap: 12px; 7 | } 8 | -------------------------------------------------------------------------------- /builder/src/scss/markdown.scss: -------------------------------------------------------------------------------- 1 | .markdown-body { 2 | color: #ffffff; 3 | } 4 | 5 | .markdown-body a { 6 | // TODO: Use the link color variable from _variables.scss. 7 | // didn't work for some reason so just copypasting for now. 8 | color: #00bc8c; 9 | text-decoration: none; 10 | } 11 | 12 | .markdown-body h6 { 13 | color: inherit; 14 | } 15 | 16 | .markdown-body pre { 17 | background-color: #191919; 18 | } 19 | 20 | .markdown-body code { 21 | background-color: #191919; 22 | color: #ffffff; 23 | } 24 | 25 | .markdown-body table td, 26 | .markdown-body table th { 27 | border-color: #1f1c1c; 28 | } 29 | 30 | .markdown-body table tr { 31 | background-color: #252424; 32 | } 33 | 34 | .markdown-body table tr:nth-child(2n) { 35 | background-color: #2f2e2e; 36 | } 37 | 38 | .markdown-body img { 39 | background-color: inherit; 40 | } 41 | -------------------------------------------------------------------------------- /builder/src/scss/settings.scss: -------------------------------------------------------------------------------- 1 | @import "./theme/_variables"; 2 | 3 | .text-large { 4 | font-size: 1.5rem; 5 | } 6 | 7 | pre.important { 8 | margin: 1rem auto; 9 | padding: 1rem 2rem; 10 | border-radius: 0.5rem; 11 | text-align: center; 12 | font-size: 87.5%; 13 | color: $cyan; 14 | background-color: $gray-900; 15 | } 16 | -------------------------------------------------------------------------------- /builder/src/scss/slimselect.scss: -------------------------------------------------------------------------------- 1 | @import "./theme/_variables"; 2 | 3 | .slimselect-lg .ss-multi-selected, 4 | .slimselect-lg .ss-single-selected { 5 | min-height: 36px; 6 | 7 | .ss-disabled { 8 | color: #999999; 9 | } 10 | 11 | .ss-disabled, 12 | .ss-values { 13 | .ss-value { 14 | font-size: 16px; 15 | } 16 | .ss-disabled { 17 | color: #999999; 18 | } 19 | } 20 | } 21 | 22 | .slimselect-info { 23 | .ss-multi-selected .ss-values .ss-value { 24 | background-color: $cyan; 25 | } 26 | } 27 | 28 | .slimselect-danger { 29 | .ss-multi-selected .ss-values .ss-value { 30 | background-color: $red; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /builder/src/state/PackageSubmission.ts.txt: -------------------------------------------------------------------------------- 1 | // import { observable } from "mobx"; 2 | // import { FileUpload } from "./FileUpload"; 3 | 4 | import { action, makeObservable, observable } from "mobx"; 5 | 6 | 7 | export class PackageSubmission { 8 | // @observable fileUpload: FileUpload | null; 9 | @observable selectedFile: File | null = null; 10 | 11 | constructor() { 12 | makeObservable(this); 13 | } 14 | 15 | @action 16 | public setFile(file: File | null) { 17 | this.selectedFile = file; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /builder/src/throttle.ts: -------------------------------------------------------------------------------- 1 | import { sleep } from "./utils"; 2 | 3 | export class PromiseThrottler { 4 | private readonly parallelism: number; 5 | 6 | private ongoing: number = 0; 7 | 8 | constructor(parallelism: number) { 9 | this.parallelism = parallelism; 10 | } 11 | 12 | private get canStartNext(): boolean { 13 | return this.parallelism - this.ongoing > 0; 14 | } 15 | 16 | async throttle(fn: () => Promise): Promise { 17 | while (!this.canStartNext) { 18 | await sleep(100); 19 | } 20 | try { 21 | this.ongoing += 1; 22 | return await fn(); 23 | } finally { 24 | this.ongoing -= 1; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /django/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | data_file = coverage.coverage 4 | omit = 5 | *migrations/* 6 | *settings* 7 | *tests/* 8 | */apps.py 9 | */urls.py 10 | */wsgi.py 11 | */manage.py 12 | */admin.py 13 | thunderstore/debug/* 14 | */python-packages/* 15 | 16 | [report] 17 | exclude_lines = 18 | pragma: no cover 19 | if TYPE_CHECKING: 20 | def __repr__ 21 | if self.debug: 22 | if settings.DEBUG 23 | raise AssertionError 24 | raise NotImplementedError 25 | if __name__ == .__main__.: 26 | 27 | 28 | [html] 29 | directory = htmlcov 30 | -------------------------------------------------------------------------------- /django/.mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | plugins = 3 | mypy_django_plugin.main, 4 | mypy_drf_plugin.main 5 | 6 | [mypy.plugins.django-stubs] 7 | django_settings_module = "thunderstore.core.settings" 8 | -------------------------------------------------------------------------------- /django/django_contracts/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = "django_contracts.apps.ContractsAppConfig" 2 | -------------------------------------------------------------------------------- /django/django_contracts/admin/__init__.py: -------------------------------------------------------------------------------- 1 | from .contract import * 2 | -------------------------------------------------------------------------------- /django/django_contracts/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ContractsAppConfig(AppConfig): 5 | name = "django_contracts" 6 | label = "contracts" 7 | -------------------------------------------------------------------------------- /django/django_contracts/compat.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module exists as an abstraction layer to make future refactoring easier 3 | if this module is decoupled from the Thunderstore project. 4 | 5 | The idea is that the dependencies imported from Thunderstore are all imported 6 | through this module, meaning replacing them in the future will be easy. 7 | """ 8 | from thunderstore.core.mixins import TimestampMixin 9 | -------------------------------------------------------------------------------- /django/django_contracts/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/django_contracts/migrations/__init__.py -------------------------------------------------------------------------------- /django/django_contracts/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .contract import * 2 | -------------------------------------------------------------------------------- /django/django_contracts/models/publishable.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils import timezone 3 | 4 | 5 | class PublishStatus(models.TextChoices): 6 | DRAFT = "DRAFT" 7 | PUBLISHED = "PUBLISHED" 8 | 9 | 10 | class PublishableMixin(models.Model): 11 | publish_status = models.TextField( 12 | choices=PublishStatus.choices, 13 | blank=False, 14 | null=False, 15 | default=PublishStatus.DRAFT, 16 | ) 17 | datetime_published = models.DateTimeField(blank=True, null=True) 18 | 19 | def publish(self): 20 | if self.datetime_published is None: 21 | self.datetime_published = timezone.now() 22 | self.publish_status = PublishStatus.PUBLISHED 23 | self.save() 24 | 25 | class Meta: 26 | abstract = True 27 | -------------------------------------------------------------------------------- /django/django_contracts/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/django_contracts/tests/__init__.py -------------------------------------------------------------------------------- /django/django_extrafields/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/django_extrafields/__init__.py -------------------------------------------------------------------------------- /django/django_extrafields/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.db.models.fields.related_descriptors import ReverseOneToOneDescriptor 3 | 4 | 5 | class SafeReverseOnetoOneDescriptor(ReverseOneToOneDescriptor): 6 | def __get__(self, *args, **kwargs): 7 | try: 8 | return super().__get__(*args, **kwargs) 9 | except self.RelatedObjectDoesNotExist: 10 | return None 11 | 12 | 13 | class SafeOneToOneField(models.OneToOneField): 14 | """ 15 | Same as OneToOneField but returns None instead of raising an exception if 16 | the relation doesn't exist. 17 | """ 18 | 19 | related_accessor_class = SafeReverseOnetoOneDescriptor 20 | 21 | 22 | # Typo fix backwards compat 23 | SafeOneToOneOrField = SafeOneToOneField 24 | -------------------------------------------------------------------------------- /django/overwolf_auth/README.md: -------------------------------------------------------------------------------- 1 | # Overwolf OAuth backend for Python Social Auth 2 | 3 | https://overwolf.github.io/topics/integrations/login-with-overwolf 4 | 5 | ## Dependencies 6 | 7 | ``` 8 | django 9 | pyjwt (2.0.1, see cached_jwk_client for more info) 10 | social-auth-core (e.g. 4.1.0) 11 | social-auth-app-django (e.g. 5.0.0) 12 | ``` 13 | 14 | ## Configuration 15 | 16 | In Django's config, add `"overwolf_auth.backends.OverwolfOAuth2"` to 17 | `AUTHENTICATION_BACKENDS`. Also set suitable values for: 18 | 19 | ``` 20 | SOCIAL_AUTH_OVERWOLF_KEY = "id received from Overwolf" 21 | SOCIAL_AUTH_OVERWOLF_SECRET = "secret token received from Overwolf" 22 | SOCIAL_AUTH_OVERWOLF_SCOPE = ["openid", "profile", "email"] # "openid" is required 23 | SOCIAL_AUTH_OVERWOLF_GET_ALL_EXTRA_DATA = True 24 | ``` 25 | -------------------------------------------------------------------------------- /django/overwolf_auth/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = "overwolf_auth.apps.OverwolfAuthAppConfig" 2 | -------------------------------------------------------------------------------- /django/overwolf_auth/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class OverwolfAuthAppConfig(AppConfig): 5 | name = "overwolf_auth" 6 | label = "overwolf_auth" 7 | -------------------------------------------------------------------------------- /django/overwolf_auth/compat.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module exists as an abstraction layer to make future refactoring 3 | easier if this module is decoupled from the Thunderstore project. 4 | 5 | The idea is that the dependencies imported from Thunderstore are all 6 | imported through this module, meaning replacing them in the future will 7 | be easier. 8 | """ 9 | from thunderstore.cache.cache import cache_function_result 10 | from thunderstore.cache.enums import CacheBustCondition 11 | -------------------------------------------------------------------------------- /django/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | DJANGO_SETTINGS_MODULE = thunderstore.core.settings 3 | norecursedirs = static var htmlcov .pytest_cache .mypy_cache .vscode static_built __pycache__ node_modules 4 | addopts = --reuse-db 5 | env = 6 | DEBUG_TOOLBAR_ENABLED=0 7 | PRIMARY_HOST=testsite.test 8 | ALLOWED_HOSTS=testsite.test 9 | CELERY_TASK_ALWAYS_EAGER=True 10 | CELERY_EAGER_PROPAGATES_EXCEPTIONS=True 11 | DATABASE_URL=psql://django:django@db/django 12 | ALWAYS_RAISE_EXCEPTIONS=True 13 | AUTH_EXCLUSIVE_HOST=auth.testsite.test 14 | SESSION_COOKIE_DOMAIN=testsite.test 15 | AWS_S3_CUSTOM_DOMAIN=minio:9000/thunderstore 16 | AWS_S3_SECURE_URLS=False 17 | CACHE_S3_CUSTOM_DOMAIN=minio:9000/thunderstore 18 | CACHE_S3_SECURE_URLS=False 19 | IS_CYBERSTORM_ENABLED=True 20 | SHOW_CYBERSTORM_API_DOCS=True 21 | USE_MULTIPLE_CACHES=False 22 | -------------------------------------------------------------------------------- /django/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/static/favicon.ico -------------------------------------------------------------------------------- /django/static/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/static/icon.png -------------------------------------------------------------------------------- /django/static/webfonts/fa-brands-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/static/webfonts/fa-brands-400.eot -------------------------------------------------------------------------------- /django/static/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/static/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /django/static/webfonts/fa-brands-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/static/webfonts/fa-brands-400.woff -------------------------------------------------------------------------------- /django/static/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/static/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /django/static/webfonts/fa-regular-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/static/webfonts/fa-regular-400.eot -------------------------------------------------------------------------------- /django/static/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/static/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /django/static/webfonts/fa-regular-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/static/webfonts/fa-regular-400.woff -------------------------------------------------------------------------------- /django/static/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/static/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /django/static/webfonts/fa-solid-900.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/static/webfonts/fa-solid-900.eot -------------------------------------------------------------------------------- /django/static/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/static/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /django/static/webfonts/fa-solid-900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/static/webfonts/fa-solid-900.woff -------------------------------------------------------------------------------- /django/static/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/static/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /django/thunderstore/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/abyss/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/abyss/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/abyss/middleware.py: -------------------------------------------------------------------------------- 1 | from abyss.django import AbyssMiddleware 2 | from django.http import HttpRequest 3 | from django.utils import timezone 4 | 5 | 6 | class TracingMiddleware(AbyssMiddleware): 7 | STORAGE_CLASS = "thunderstore.abyss.storage.get_abyss_storage" 8 | FILENAME_PREFIX = "traces/" 9 | 10 | def build_filename_for_request(self, request: HttpRequest) -> str: 11 | timestamp = timezone.now().isoformat().replace(":", "-") 12 | path_stamp = request.path.strip("/").replace("/", "-") 13 | prefix = self.FILENAME_PREFIX or "" 14 | return f"{prefix}{timestamp}_{path_stamp}.tracing" 15 | 16 | def should_profile_request(self, request: HttpRequest): 17 | base = super().should_profile_request(request) 18 | return ( 19 | base 20 | and hasattr(request, "user") 21 | and (request.user.is_staff or request.user.is_superuser) 22 | ) 23 | -------------------------------------------------------------------------------- /django/thunderstore/abyss/storage.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from storages.backends.s3boto3 import S3Boto3Storage 3 | 4 | 5 | def get_abyss_storage(): 6 | return S3Boto3Storage( 7 | **{ 8 | "access_key": settings.ABYSS_S3_ACCESS_KEY_ID, 9 | "secret_key": settings.ABYSS_S3_SECRET_ACCESS_KEY, 10 | "file_overwrite": settings.ABYSS_S3_FILE_OVERWRITE, 11 | "object_parameters": settings.ABYSS_S3_OBJECT_PARAMETERS, 12 | "bucket_name": settings.ABYSS_S3_STORAGE_BUCKET_NAME, 13 | "location": settings.ABYSS_S3_LOCATION, 14 | "custom_domain": settings.ABYSS_S3_CUSTOM_DOMAIN, 15 | "secure_urls": settings.ABYSS_S3_SECURE_URLS, 16 | "endpoint_url": settings.ABYSS_S3_ENDPOINT_URL, 17 | "region_name": settings.ABYSS_S3_REGION_NAME, 18 | "default_acl": settings.ABYSS_S3_DEFAULT_ACL, 19 | } 20 | ) 21 | -------------------------------------------------------------------------------- /django/thunderstore/account/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = "thunderstore.account.apps.AccountAppConfig" 2 | -------------------------------------------------------------------------------- /django/thunderstore/account/admin/__init__.py: -------------------------------------------------------------------------------- 1 | from .service_account import * 2 | from .token import * 3 | from .user import * 4 | from .user_flag import * 5 | -------------------------------------------------------------------------------- /django/thunderstore/account/admin/service_account.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from django.contrib import admin 4 | from django.http import HttpRequest 5 | 6 | from thunderstore.account.models import ServiceAccount 7 | 8 | 9 | @admin.register(ServiceAccount) 10 | class ServiceAccountAdmin(admin.ModelAdmin): 11 | raw_id_fields = ("user",) 12 | list_display = ( 13 | "uuid", 14 | "nickname", 15 | "user", 16 | "owner", 17 | "created_at", 18 | "last_used", 19 | ) 20 | list_filter = () 21 | search_fields = ("user__username", "uuid", "owner__name") 22 | 23 | def has_add_permission(self, request: HttpRequest) -> bool: 24 | return False 25 | 26 | def has_change_permission( 27 | self, request: HttpRequest, obj: Optional[ServiceAccount] = None 28 | ) -> bool: 29 | return False 30 | -------------------------------------------------------------------------------- /django/thunderstore/account/admin/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/account/admin/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/account/admin/token.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from rest_framework.authtoken.admin import TokenAdmin 3 | from rest_framework.authtoken.models import TokenProxy 4 | 5 | 6 | class CustomTokenAdmin(TokenAdmin): 7 | raw_id_fields = ("user",) 8 | 9 | 10 | admin.site.unregister(TokenProxy) 11 | admin.site.register(TokenProxy, CustomTokenAdmin) 12 | -------------------------------------------------------------------------------- /django/thunderstore/account/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AccountAppConfig(AppConfig): 5 | name = "thunderstore.account" 6 | label = "account" 7 | -------------------------------------------------------------------------------- /django/thunderstore/account/middleware.py: -------------------------------------------------------------------------------- 1 | from functools import lru_cache 2 | from typing import Callable, List 3 | 4 | from django.http import HttpRequest, HttpResponse 5 | from django.utils import timezone 6 | 7 | from thunderstore.account.models import UserFlag 8 | 9 | 10 | class UserFlagsHttpRequest(HttpRequest): 11 | get_user_flags: Callable[[], List[str]] 12 | 13 | 14 | class UserFlagsMiddleware: 15 | def __init__(self, get_response): 16 | self.get_response = get_response 17 | 18 | def __call__(self, request: UserFlagsHttpRequest) -> HttpResponse: 19 | user = getattr(request, "user", None) 20 | 21 | @lru_cache(maxsize=1) 22 | def get_user_flags() -> List[str]: 23 | return UserFlag.get_active_flags_on_user(user, timezone.now()) 24 | 25 | request.get_user_flags = get_user_flags 26 | return self.get_response(request) 27 | -------------------------------------------------------------------------------- /django/thunderstore/account/migrations/0005_serviceaccount_created_by.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2024-10-17 19:30 2 | 3 | import django.db.models.deletion 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ("account", "0004_add_usersettings"), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name="serviceaccount", 18 | name="created_by", 19 | field=models.ForeignKey( 20 | blank=True, 21 | null=True, 22 | on_delete=django.db.models.deletion.SET_NULL, 23 | related_name="created_service_accounts", 24 | to=settings.AUTH_USER_MODEL, 25 | ), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /django/thunderstore/account/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/account/migrations/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/account/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .service_account import * 2 | from .user_flag import * 3 | from .user_settings import * 4 | -------------------------------------------------------------------------------- /django/thunderstore/account/models/user_settings.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from django.contrib.auth import get_user_model 4 | from django.db import models 5 | from django.db.models import Manager 6 | 7 | from django_extrafields.models import SafeOneToOneOrField 8 | 9 | User = get_user_model() 10 | 11 | 12 | class UserSettings(models.Model): 13 | objects: Manager["UserSettings"] 14 | 15 | user = SafeOneToOneOrField( 16 | User, 17 | related_name="settings", 18 | on_delete=models.CASCADE, 19 | ) 20 | 21 | @classmethod 22 | def get_for_user(cls, user: User) -> Optional["UserSettings"]: 23 | settings = cls.objects.filter(user=user).first() 24 | return settings if settings else cls.objects.create(user=user) 25 | 26 | class Meta: 27 | verbose_name = "user settings" 28 | verbose_name_plural = "user settings" 29 | -------------------------------------------------------------------------------- /django/thunderstore/account/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/account/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/account/tests/test_user_settings.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from thunderstore.account.models import UserSettings 4 | from thunderstore.core.types import UserType 5 | 6 | 7 | @pytest.mark.django_db 8 | def test_account_user_settings_get_for_user_creation(user: UserType): 9 | assert UserSettings.objects.count() == 0 10 | assert user.settings is None 11 | settings = UserSettings.get_for_user(user) 12 | assert settings is not None 13 | assert UserSettings.objects.count() == 1 14 | 15 | 16 | @pytest.mark.django_db 17 | def test_account_user_settings_get_for_user_retrieval(user_with_settings: UserType): 18 | assert UserSettings.objects.count() == 1 19 | assert user_with_settings.settings is not None 20 | settings = UserSettings.get_for_user(user_with_settings) 21 | assert settings == user_with_settings.settings 22 | assert UserSettings.objects.count() == 1 23 | -------------------------------------------------------------------------------- /django/thunderstore/account/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import MagicMock 2 | 3 | from django.test import RequestFactory 4 | 5 | from thunderstore.account.utils import get_request_user_flags 6 | 7 | 8 | def test_utils_get_request_user_flags(rf: RequestFactory): 9 | request = rf.get("/") 10 | request.get_user_flags = MagicMock(return_value=["test"]) 11 | result = get_request_user_flags(request) 12 | request.get_user_flags.assert_called_once() 13 | assert result == ["test"] 14 | -------------------------------------------------------------------------------- /django/thunderstore/account/utils.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from thunderstore.account.middleware import UserFlagsHttpRequest 4 | 5 | 6 | def get_request_user_flags(request: UserFlagsHttpRequest) -> List[str]: 7 | get_flags = getattr(request, "get_user_flags", lambda: []) 8 | return get_flags() 9 | -------------------------------------------------------------------------------- /django/thunderstore/api/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = "thunderstore.api.apps.APIConfig" 2 | -------------------------------------------------------------------------------- /django/thunderstore/api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class APIConfig(AppConfig): 5 | name = "thunderstore.api" 6 | label = "api" 7 | -------------------------------------------------------------------------------- /django/thunderstore/api/cyberstorm/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/api/cyberstorm/services/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/api/cyberstorm/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/api/cyberstorm/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/api/cyberstorm/tests/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/api/cyberstorm/tests/services/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/api/cyberstorm/views/community.py: -------------------------------------------------------------------------------- 1 | from rest_framework.generics import RetrieveAPIView 2 | 3 | from thunderstore.api.cyberstorm.serializers import CyberstormCommunitySerializer 4 | from thunderstore.api.utils import CyberstormAutoSchemaMixin 5 | from thunderstore.community.models import Community 6 | 7 | 8 | class CommunityAPIView(CyberstormAutoSchemaMixin, RetrieveAPIView): 9 | lookup_url_kwarg = "community_id" 10 | lookup_field = "identifier" 11 | permission_classes = [] 12 | 13 | # Unlisted communities are included, as direct links to them should work. 14 | queryset = Community.objects.all() 15 | serializer_class = CyberstormCommunitySerializer 16 | -------------------------------------------------------------------------------- /django/thunderstore/api/utils.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from drf_yasg.utils import swagger_auto_schema, unset # type: ignore 3 | 4 | 5 | def conditional_swagger_auto_schema(*args, **kwargs): 6 | def decorator(f): 7 | if settings.SHOW_CYBERSTORM_API_DOCS: 8 | return swagger_auto_schema(*args, auto_schema=unset, **kwargs)(f) 9 | return swagger_auto_schema(*args, auto_schema=None, **kwargs)(f) 10 | 11 | return decorator 12 | 13 | 14 | class CyberstormAutoSchemaMixin: # pragma: no cover 15 | """ 16 | Control Cyberstorm API endpoint visibility in Swagger docs. 17 | 18 | Use SHOW_CYBERSTORM_API_DOCS env variable to control whether the 19 | endpoints are included in the API documentation or not. 20 | """ 21 | 22 | @conditional_swagger_auto_schema(tags=["cyberstorm"]) 23 | def get(self, *args, **kwargs): 24 | return super().get(*args, **kwargs) 25 | -------------------------------------------------------------------------------- /django/thunderstore/cache/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = "thunderstore.cache.apps.CacheAppConfig" 2 | -------------------------------------------------------------------------------- /django/thunderstore/cache/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import DatabaseCache 4 | 5 | 6 | @admin.register(DatabaseCache) 7 | class DatabaseCacheAdmin(admin.ModelAdmin): 8 | list_display = ( 9 | "id", 10 | "key", 11 | "expires_on", 12 | "hits", 13 | "datetime_created", 14 | "datetime_updated", 15 | ) 16 | list_display_links = ( 17 | "id", 18 | "key", 19 | ) 20 | search_fields = ("key",) 21 | exclude = ("content",) 22 | 23 | def has_change_permission(self, request, obj=None): 24 | return False 25 | -------------------------------------------------------------------------------- /django/thunderstore/cache/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CacheAppConfig(AppConfig): 5 | name = "thunderstore.cache" 6 | label = "cache" 7 | -------------------------------------------------------------------------------- /django/thunderstore/cache/enums.py: -------------------------------------------------------------------------------- 1 | from thunderstore.core.utils import ChoiceEnum 2 | 3 | 4 | # TODO: Support parameters in cache bust conditions (e.g. specific package update) 5 | class CacheBustCondition(ChoiceEnum): 6 | background_update_only = "manual_update_only" 7 | any_package_updated = "any_package_updated" 8 | dynamic_html_updated = "dynamic_html_updated" 9 | -------------------------------------------------------------------------------- /django/thunderstore/cache/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/cache/migrations/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/cache/tasks.py: -------------------------------------------------------------------------------- 1 | from celery import shared_task 2 | from django.conf import settings 3 | from django.core.cache import cache 4 | 5 | from thunderstore.cache.enums import CacheBustCondition 6 | from thunderstore.core.settings import CeleryQueues 7 | from thunderstore.utils.decorators import run_after_commit 8 | 9 | 10 | @run_after_commit 11 | def invalidate_cache_on_commit_async(cache_bust_condition: str): 12 | if cache_bust_condition in settings.DISABLED_CACHE_BUST_CONDITIONS: 13 | return 14 | invalidate_cache.delay(cache_bust_condition) 15 | 16 | 17 | @shared_task(queue=CeleryQueues.BackgroundCache) 18 | def invalidate_cache(cache_bust_condition: str): 19 | if cache_bust_condition == CacheBustCondition.background_update_only: 20 | raise AttributeError("Invalid cache bust condition") 21 | if hasattr(cache, "delete_pattern"): 22 | cache.delete_pattern(f"cache.{cache_bust_condition}.*") 23 | -------------------------------------------------------------------------------- /django/thunderstore/cache/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/cache/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/cache/tests/test_cache.py: -------------------------------------------------------------------------------- 1 | import time 2 | from typing import Any 3 | 4 | from thunderstore.cache.cache import cache_function_result 5 | from thunderstore.cache.enums import CacheBustCondition 6 | 7 | 8 | def test_cache_clear_with_args() -> None: 9 | @cache_function_result(CacheBustCondition.background_update_only) 10 | def get_time(cache_vary: Any) -> float: 11 | return time.time() 12 | 13 | first = get_time("test") 14 | time.sleep(0.01) 15 | first_cached = get_time("test") 16 | second = get_time("test2") 17 | assert first == first_cached 18 | assert second > first 19 | time.sleep(0.01) 20 | get_time.clear_cache_with_args("test") 21 | first_busted = get_time("test") 22 | assert first_busted > first 23 | assert first_busted > second 24 | -------------------------------------------------------------------------------- /django/thunderstore/community/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = "thunderstore.community.apps.CommunityAppConfig" 2 | -------------------------------------------------------------------------------- /django/thunderstore/community/admin/__init__.py: -------------------------------------------------------------------------------- 1 | from .community import * 2 | from .community_site import * 3 | from .package_category import * 4 | from .package_listing import * 5 | from .package_listing_section import * 6 | -------------------------------------------------------------------------------- /django/thunderstore/community/admin/community_site.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from ..models.community_site import CommunitySite 4 | 5 | 6 | @admin.register(CommunitySite) 7 | class CommunitySiteAdmin(admin.ModelAdmin): 8 | filter_horizontal = () 9 | list_filter = ("is_listed",) 10 | raw_id_fields = ( 11 | "site", 12 | "community", 13 | ) 14 | list_display = ( 15 | "id", 16 | "community", 17 | "site", 18 | "is_listed", 19 | "datetime_created", 20 | "datetime_updated", 21 | ) 22 | list_display_links = ( 23 | "id", 24 | "community", 25 | "site", 26 | ) 27 | search_fields = ( 28 | "community__identifier", 29 | "community__name", 30 | "site__domain", 31 | "site__name", 32 | ) 33 | readonly_fields = ( 34 | "datetime_created", 35 | "datetime_updated", 36 | ) 37 | -------------------------------------------------------------------------------- /django/thunderstore/community/admin/package_category.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from ..models.package_category import PackageCategory 4 | 5 | 6 | @admin.register(PackageCategory) 7 | class PackageCategoryAdmin(admin.ModelAdmin): 8 | list_filter = ("community",) 9 | list_display = ( 10 | "id", 11 | "name", 12 | "slug", 13 | "datetime_created", 14 | "datetime_updated", 15 | "community", 16 | ) 17 | list_display_links = ( 18 | "id", 19 | "name", 20 | "slug", 21 | ) 22 | search_fields = ( 23 | "name", 24 | "slug", 25 | ) 26 | readonly_fields = ( 27 | "datetime_created", 28 | "datetime_updated", 29 | "community", 30 | ) 31 | 32 | def get_readonly_fields(self, request, obj=None): 33 | if obj: 34 | return self.readonly_fields 35 | else: 36 | return [] 37 | -------------------------------------------------------------------------------- /django/thunderstore/community/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/community/api/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/community/api/experimental/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/community/api/experimental/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/community/api/experimental/tests/test_api_communities_list.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.mark.django_db 5 | def test_api_experimental_communities_list( 6 | api_client, 7 | ): 8 | response = api_client.get( 9 | "/api/experimental/community/", 10 | HTTP_ACCEPT="application/json", 11 | ) 12 | assert response.status_code == 200 13 | assert isinstance(response.json()["results"], list) 14 | -------------------------------------------------------------------------------- /django/thunderstore/community/api/experimental/tests/test_api_current_community.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from thunderstore.frontend.api.experimental.serializers.views import CommunitySerializer 4 | 5 | 6 | @pytest.mark.django_db 7 | def test_api_experimental_current_community(user, api_client, community): 8 | api_client.force_authenticate(user) 9 | response = api_client.get( 10 | "/api/experimental/current-community/", 11 | HTTP_ACCEPT="application/json", 12 | ) 13 | assert response.status_code == 200 14 | assert response.json() == CommunitySerializer(community).data 15 | -------------------------------------------------------------------------------- /django/thunderstore/community/api/experimental/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/community/api/experimental/views/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/community/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CommunityAppConfig(AppConfig): 5 | name = "thunderstore.community" 6 | label = "community" 7 | -------------------------------------------------------------------------------- /django/thunderstore/community/consts.py: -------------------------------------------------------------------------------- 1 | from thunderstore.core.utils import ChoiceEnum 2 | 3 | 4 | class PackageListingReviewStatus(ChoiceEnum): 5 | unreviewed = "unreviewed" 6 | approved = "approved" 7 | rejected = "rejected" 8 | -------------------------------------------------------------------------------- /django/thunderstore/community/forms/__init__.py: -------------------------------------------------------------------------------- 1 | from .package_listing import * 2 | from .package_listing_section import * 3 | -------------------------------------------------------------------------------- /django/thunderstore/community/migrations/0002_add_webhook_exclude_categories.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.4 on 2020-09-08 07:39 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("community", "0001_add_package_listing"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name="packagecategory", 15 | options={ 16 | "verbose_name": "package category", 17 | "verbose_name_plural": "package categories", 18 | }, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /django/thunderstore/community/migrations/0007_add_community_links.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.4 on 2020-12-12 12:53 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("community", "0006_add_default_site_mappings"), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name="communitysite", 15 | name="discord_url", 16 | ), 17 | migrations.AddField( 18 | model_name="community", 19 | name="discord_url", 20 | field=models.CharField(blank=True, max_length=512, null=True), 21 | ), 22 | migrations.AddField( 23 | model_name="community", 24 | name="wiki_url", 25 | field=models.CharField(blank=True, max_length=512, null=True), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /django/thunderstore/community/migrations/0010_add_is_listed_flag.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.6 on 2021-02-25 02:27 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("community", "0009_add_dynamic_social_auth_settings"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="community", 15 | name="is_listed", 16 | field=models.BooleanField(default=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /django/thunderstore/community/migrations/0011_add_community_site_is_listed_flag.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.6 on 2021-02-25 02:46 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("community", "0010_add_is_listed_flag"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="communitysite", 15 | name="is_listed", 16 | field=models.BooleanField(default=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /django/thunderstore/community/migrations/0012_add_unique_constraints.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.6 on 2021-03-07 02:16 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("community", "0011_add_community_site_is_listed_flag"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddConstraint( 14 | model_name="packagelisting", 15 | constraint=models.UniqueConstraint( 16 | fields=("package", "community"), name="one_listing_per_community" 17 | ), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /django/thunderstore/community/migrations/0013_rename_community_listings_relation.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.6 on 2021-03-07 10:17 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("repository", "0026_add_unique_constraints"), 11 | ("community", "0012_add_unique_constraints"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name="packagelisting", 17 | name="package", 18 | field=models.ForeignKey( 19 | on_delete=django.db.models.deletion.CASCADE, 20 | related_name="community_listings", 21 | to="repository.package", 22 | ), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /django/thunderstore/community/migrations/0014_fix_category_uniqueness.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.6 on 2021-03-16 18:51 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("community", "0013_rename_community_listings_relation"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="packagecategory", 15 | name="slug", 16 | field=models.SlugField(), 17 | ), 18 | migrations.AddConstraint( 19 | model_name="packagecategory", 20 | constraint=models.UniqueConstraint( 21 | fields=("slug", "community"), name="unique_category_slug_per_community" 22 | ), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /django/thunderstore/community/migrations/0018_add_package_rejection_reason.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2021-05-07 03:37 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("community", "0017_add_package_approval_system"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="packagelisting", 15 | name="notes", 16 | field=models.TextField(blank=True, null=True), 17 | ), 18 | migrations.AddField( 19 | model_name="packagelisting", 20 | name="rejection_reason", 21 | field=models.TextField(blank=True, null=True), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /django/thunderstore/community/migrations/0022_add_block_auto_updates.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2023-01-08 09:26 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("community", "0021_remove_unused_fields"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="community", 15 | name="block_auto_updates", 16 | field=models.BooleanField(default=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /django/thunderstore/community/migrations/0023_add_decompilation_visibility_option.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2023-09-26 19:24 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("community", "0022_add_block_auto_updates"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="community", 15 | name="show_decompilation_results", 16 | field=models.TextField( 17 | choices=[("NONE", "None"), ("YES", "Yes"), ("NO", "No")], default="NONE" 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /django/thunderstore/community/migrations/0027_add_review_requested_field.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2024-01-09 12:30 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("community", "0026_schedule_aggregated_fields_refresh"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="packagelisting", 15 | name="is_review_requested", 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /django/thunderstore/community/migrations/0029_packagelisting_is_auto_imported.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2024-06-05 18:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("community", "0028_add_cover_image_fields"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="packagelisting", 15 | name="is_auto_imported", 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /django/thunderstore/community/migrations/0031_community_short_description.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2024-11-27 11:36 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("community", "0030_add_community_hero_image"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="community", 15 | name="short_description", 16 | field=models.CharField(blank=True, max_length=512, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /django/thunderstore/community/migrations/0033_add_mod_manager_support_field.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2025-02-24 11:01 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("community", "0032_add_community_icon"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="community", 15 | name="has_mod_manager_support", 16 | field=models.BooleanField(default=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /django/thunderstore/community/migrations/0034_add_search_keywords.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2025-03-05 08:35 2 | 3 | import django.contrib.postgres.fields 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("community", "0033_add_mod_manager_support_field"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name="community", 16 | name="search_keywords", 17 | field=django.contrib.postgres.fields.ArrayField( 18 | base_field=models.CharField(max_length=512), 19 | blank=True, 20 | default=list, 21 | null=True, 22 | size=None, 23 | ), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /django/thunderstore/community/migrations/0035_add_janitor_role.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2025-03-28 18:46 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("community", "0034_add_search_keywords"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="communitymembership", 15 | name="role", 16 | field=models.CharField( 17 | choices=[ 18 | ("owner", "owner"), 19 | ("moderator", "moderator"), 20 | ("janitor", "janitor"), 21 | ("member", "member"), 22 | ], 23 | default="member", 24 | max_length=64, 25 | ), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /django/thunderstore/community/migrations/0036_packagelisting_visibility.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2025-03-06 21:33 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("permissions", "0001_initial"), 11 | ("community", "0035_add_janitor_role"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name="packagelisting", 17 | name="visibility", 18 | field=models.OneToOneField( 19 | blank=True, 20 | null=True, 21 | on_delete=django.db.models.deletion.PROTECT, 22 | to="permissions.visibilityflags", 23 | ), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /django/thunderstore/community/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/community/migrations/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/community/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .community import * 2 | from .community_site import * 3 | from .package_category import * 4 | from .package_listing import * 5 | from .package_listing_section import * 6 | -------------------------------------------------------------------------------- /django/thunderstore/community/tasks.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from celery import shared_task 4 | 5 | from thunderstore.community.models import Community, CommunityAggregatedFields 6 | from thunderstore.core.settings import CeleryQueues 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | @shared_task(queue=CeleryQueues.BackgroundTask) 12 | def update_community_aggregated_fields() -> None: 13 | # Create the relation for all Communities, even if they're unlisted, 14 | # so they have the relation if they do get listed. 15 | logger.info("Creating CommunityAggregatedFields") 16 | CommunityAggregatedFields.create_missing() 17 | 18 | logger.info("Updating fields values for listed communities") 19 | for c in Community.objects.all(): 20 | CommunityAggregatedFields.update_for_community(c) 21 | -------------------------------------------------------------------------------- /django/thunderstore/community/templates/community/includes/package_tabs.html: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /django/thunderstore/community/templates/community/includes/version_table.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% for version in versions %} 9 | 10 | 11 | 12 | 13 | 17 | 18 | {% endfor %} 19 |
Upload dateVersion numberDownloadsActions
{{ version.date_created|date:"Y-n-j" }}{{ version.version_number }}{{ version.downloads }} 14 | Download 15 | Install 16 |
20 | -------------------------------------------------------------------------------- /django/thunderstore/community/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/community/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/community/tests/test_community_site.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from thunderstore.community.factories import CommunitySiteFactory 4 | from thunderstore.community.models import CommunitySite 5 | 6 | 7 | @pytest.mark.django_db 8 | def test_community_site_manager_listed(): 9 | c1 = CommunitySiteFactory(is_listed=True) 10 | c2 = CommunitySiteFactory(is_listed=False) 11 | 12 | listed_communities = CommunitySite.objects.listed() 13 | assert c1 in listed_communities 14 | assert c2 not in listed_communities 15 | 16 | 17 | @pytest.mark.django_db 18 | def test_community_site_full_url(settings): 19 | site = CommunitySiteFactory(is_listed=True) 20 | assert site.site.domain in site.full_url 21 | settings.PROTOCOL = "https://" 22 | assert site.full_url.startswith("https://") 23 | settings.PROTOCOL = "http://" 24 | assert site.full_url.startswith("http://") 25 | -------------------------------------------------------------------------------- /django/thunderstore/community/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import include, path 2 | 3 | from thunderstore.repository.urls import package_urls 4 | 5 | community_urls = [ 6 | path( 7 | "/", 8 | include((package_urls, "community"), namespace="community"), 9 | ), 10 | ] 11 | -------------------------------------------------------------------------------- /django/thunderstore/community/views.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.db.models import QuerySet 3 | from django.shortcuts import redirect 4 | from django.views import View 5 | from django.views.generic import ListView 6 | 7 | from thunderstore.community.models import Community 8 | 9 | 10 | class FaviconView(View): 11 | def get(self, *args, **kwargs): 12 | return redirect(f"{settings.STATIC_URL}favicon.ico") 13 | 14 | 15 | class CommunityListView(ListView): 16 | model = Community 17 | 18 | def get_queryset(self) -> QuerySet[Community]: 19 | return Community.objects.listed().order_by( 20 | "-aggregated_fields__package_count", "-datetime_created" 21 | ) 22 | -------------------------------------------------------------------------------- /django/thunderstore/core/__init__.py: -------------------------------------------------------------------------------- 1 | from .celery import app as celery_app 2 | 3 | default_app_config = "thunderstore.core.apps.CoreAppConfig" 4 | 5 | __all__ = ("celery_app", "default_app_config") 6 | -------------------------------------------------------------------------------- /django/thunderstore/core/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from thunderstore.core.models import IncomingJWTAuthConfiguration 4 | 5 | 6 | @admin.register(IncomingJWTAuthConfiguration) 7 | class PackageAdmin(admin.ModelAdmin): 8 | readonly_fields = ("key_id",) 9 | raw_id_fields = ("user",) 10 | list_display = ( 11 | "name", 12 | "user", 13 | "secret_type", 14 | "key_id", 15 | ) 16 | list_filter = ("secret_type",) 17 | search_fields = ( 18 | "key_id", 19 | "user__username", 20 | ) 21 | -------------------------------------------------------------------------------- /django/thunderstore/core/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CoreAppConfig(AppConfig): 5 | name = "thunderstore.core" 6 | label = "core" 7 | -------------------------------------------------------------------------------- /django/thunderstore/core/celery.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from celery import Celery 4 | 5 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "thunderstore.core.settings") 6 | 7 | app = Celery("thunderstore") 8 | 9 | app.config_from_object("django.conf:settings", namespace="CELERY") 10 | app.autodiscover_tasks() 11 | -------------------------------------------------------------------------------- /django/thunderstore/core/enums.py: -------------------------------------------------------------------------------- 1 | from django.db.models import TextChoices 2 | 3 | 4 | class OptionalBoolChoice(TextChoices): 5 | NONE = "NONE" 6 | YES = "YES" 7 | NO = "NO" 8 | -------------------------------------------------------------------------------- /django/thunderstore/core/exceptions.py: -------------------------------------------------------------------------------- 1 | from django.core.exceptions import ValidationError 2 | 3 | 4 | class PermissionValidationError(ValidationError): 5 | def __init__(self, message=None, code=None, is_public=True, **kwargs): 6 | self.is_public = is_public 7 | super().__init__(message, code, **kwargs) 8 | -------------------------------------------------------------------------------- /django/thunderstore/core/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from django.conf import settings 3 | from factory.django import DjangoModelFactory 4 | 5 | 6 | class UserFactory(DjangoModelFactory): 7 | class Meta: 8 | model = settings.AUTH_USER_MODEL 9 | 10 | username = factory.Sequence(lambda n: f"TestUser{n}") 11 | email = factory.Faker("email") 12 | first_name = factory.Faker("first_name") 13 | last_name = factory.Faker("last_name") 14 | -------------------------------------------------------------------------------- /django/thunderstore/core/healthcheck.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | 3 | 4 | def healthcheck_view(request): 5 | return HttpResponse("OK") 6 | -------------------------------------------------------------------------------- /django/thunderstore/core/inheritance.py: -------------------------------------------------------------------------------- 1 | from thunderstore.core.enums import OptionalBoolChoice 2 | 3 | 4 | def get_effective_bool_choice_depth_first( 5 | *args: OptionalBoolChoice, 6 | ) -> OptionalBoolChoice: 7 | for arg in reversed(args): 8 | if arg != OptionalBoolChoice.NONE: 9 | return arg 10 | return OptionalBoolChoice.NONE 11 | -------------------------------------------------------------------------------- /django/thunderstore/core/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/core/management/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/core/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/core/management/commands/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/core/management/commands/content/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/core/management/commands/content/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/core/middleware.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | 4 | class QueryCountHeaderMiddleware: 5 | def __init__(self, get_response): 6 | self.get_response = get_response 7 | 8 | def __call__(self, request): 9 | if not settings.DATABASE_QUERY_COUNT_HEADER: 10 | return self.get_response(request) 11 | else: 12 | from django.db import connection 13 | from django.test.utils import CaptureQueriesContext 14 | 15 | with CaptureQueriesContext(connection) as context: 16 | response = self.get_response(request) 17 | response["Django-Query-Count"] = len(context) 18 | return response 19 | -------------------------------------------------------------------------------- /django/thunderstore/core/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | from django.contrib.postgres.operations import TrigramExtension 2 | from django.db import migrations 3 | 4 | 5 | class Migration(migrations.Migration): 6 | 7 | dependencies = [] 8 | 9 | operations = [ 10 | TrigramExtension(), 11 | ] 12 | -------------------------------------------------------------------------------- /django/thunderstore/core/migrations/0003_adjust_jwt_type_choices.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.6 on 2019-11-14 20:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("core", "0002_incomingjwtauthconfiguration"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="incomingjwtauthconfiguration", 15 | name="secret_type", 16 | field=models.CharField( 17 | choices=[("HS256", "HS256"), ("RS256", "RS256")], max_length=16 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /django/thunderstore/core/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/core/migrations/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/core/setting_urls.py: -------------------------------------------------------------------------------- 1 | from thunderstore.plugins.registry import plugin_registry 2 | from thunderstore.repository.urls import settings_urls as repository_settings_urls 3 | from thunderstore.social.urls import settings_urls as social_settings_urls 4 | 5 | settings_urls = ( 6 | social_settings_urls 7 | + repository_settings_urls 8 | + plugin_registry.get_settings_urls() 9 | ) 10 | -------------------------------------------------------------------------------- /django/thunderstore/core/tasks.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Optional, Union 2 | 3 | import requests 4 | from celery import shared_task 5 | 6 | from thunderstore.core.settings import CeleryQueues 7 | 8 | 9 | @shared_task(queue=CeleryQueues.Default) 10 | def celery_post( 11 | webhook_url: str, 12 | data: Optional[str] = None, 13 | headers: Union[Dict, None] = None, 14 | ): 15 | response = requests.post( 16 | webhook_url, 17 | data=data, 18 | headers=headers, 19 | ) 20 | return { 21 | "url": response.url, 22 | "status": response.status_code, 23 | "reason": response.reason, 24 | "content": response.text, 25 | } 26 | -------------------------------------------------------------------------------- /django/thunderstore/core/types.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Optional 2 | 3 | from django.contrib.auth.models import User 4 | from django.http import HttpRequest 5 | 6 | if TYPE_CHECKING: 7 | from django.db.models import Manager 8 | 9 | from thunderstore.account.models import UserSettings 10 | from thunderstore.community.models import Community 11 | from thunderstore.repository.models import PackageRating, Team 12 | 13 | class UserType(User): 14 | teams: "Manager[Team]" 15 | settings: Optional[UserSettings] 16 | package_ratings: "Manager[PackageRating]" 17 | 18 | class HttpRequestType(HttpRequest): 19 | community: "Optional[Community]" 20 | 21 | else: 22 | UserType = None 23 | HttpRequestType = None 24 | 25 | __all__ = ["UserType", "HttpRequestType"] 26 | -------------------------------------------------------------------------------- /django/thunderstore/core/wsgi.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.core.wsgi import get_wsgi_application 4 | 5 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "thunderstore.core.settings") 6 | 7 | application = get_wsgi_application() 8 | 9 | from django.conf import settings # noqa 10 | 11 | import thunderstore.monkeypatch # noqa 12 | -------------------------------------------------------------------------------- /django/thunderstore/debug/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/debug/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/frontend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/frontend/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/frontend/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/frontend/api/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/frontend/api/experimental/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/frontend/api/experimental/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/frontend/api/experimental/serializers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/frontend/api/experimental/serializers/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/frontend/api/experimental/serializers/markdown.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from thunderstore.repository.validation.markdown import MAX_MARKDOWN_SIZE 4 | 5 | 6 | class RenderMarkdownParamsSerializer(serializers.Serializer): 7 | markdown = serializers.CharField(max_length=MAX_MARKDOWN_SIZE) 8 | 9 | 10 | class RenderMarkdownResponseSerializer(serializers.Serializer): 11 | html = serializers.CharField() 12 | -------------------------------------------------------------------------------- /django/thunderstore/frontend/api/experimental/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/frontend/api/experimental/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/frontend/api/experimental/views/__init__.py: -------------------------------------------------------------------------------- 1 | from .community_package_list import CommunityPackageListApiView 2 | from .frontpage import FrontPageApiView 3 | from .markdown import RenderMarkdownApiView 4 | from .package_details import PackageDetailApiView 5 | 6 | __all__ = [ 7 | "CommunityPackageListApiView", 8 | "FrontPageApiView", 9 | "PackageDetailApiView", 10 | "RenderMarkdownApiView", 11 | ] 12 | -------------------------------------------------------------------------------- /django/thunderstore/frontend/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class FrontendConfig(AppConfig): 5 | name = "thunderstore.frontend" 6 | label = "frontend" 7 | -------------------------------------------------------------------------------- /django/thunderstore/frontend/migrations/0002_modify_meta.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2019-05-07 20:38 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("frontend", "0001_initial"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name="dynamichtml", 15 | options={ 16 | "verbose_name": "Dynamic HTML", 17 | "verbose_name_plural": "Dynamic HTML", 18 | }, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /django/thunderstore/frontend/migrations/0004_dynamichtml_ordering.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2019-05-23 17:55 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("frontend", "0003_add_placement_info"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="dynamichtml", 15 | name="ordering", 16 | field=models.IntegerField(default=0), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /django/thunderstore/frontend/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/frontend/migrations/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/frontend/templates/ads.txt: -------------------------------------------------------------------------------- 1 | {% load dynamic_html %} 2 | {% dynamic_html "ads_txt" %} 3 | -------------------------------------------------------------------------------- /django/thunderstore/frontend/templates/errors/404.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |

Page not found

5 |

6 | The page you are looking for has either never existed, or it has been 7 | removed. 8 |

9 |

10 | If the problem persists and you require immediate assistance, please 11 | head to our Discord server and 12 | contact an administrator. 13 |

14 | Back to the main page 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /django/thunderstore/frontend/templates/errors/500.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |

Internal server error

5 |

6 | Something went wrong while trying to process your request. The error has 7 | been logged, and a singular code monkey has been dispatched to 8 | investigate the issue. 9 |

10 |

11 | If the problem persists and you require immediate assistance, please 12 | head to our Discord server and 13 | contact an administrator. 14 |

15 | Back to the main page 16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /django/thunderstore/frontend/templates/errors/auth_already_associated.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |

Account already associated

5 |

6 | This account has already been associated to another user. Please log out 7 | and log back in with it instead. 8 |

9 |

10 | If the problem persists and you require immediate assistance, please 11 | head to our Discord server and 12 | contact an administrator. 13 |

14 | Back to the main page 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /django/thunderstore/frontend/templates/errors/auth_canceled.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |

Auth canceled

5 |

6 | The authorization session was canceled. 7 |

8 | Back to the main page 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /django/thunderstore/frontend/templates/errors/auth_failed.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |

Auth failed

5 |

6 | The authentication failed for some reason, and this could be a 7 | temporary problem. Please try again. 8 |

9 |

10 | If the problem persists and you require immediate assistance, please 11 | head to our Discord server and 12 | contact an administrator. 13 |

14 | Back to the main page 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /django/thunderstore/frontend/templates/frontend/manifest_v1_validator.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %}Manifest Validator{% endblock %} 4 | {% block main_content_left %}{% endblock %} 5 | {% block content_beginning %}{% endblock %} 6 | 7 | {% block content %} 8 |

Manifest Validator

9 | 10 |
11 | 14 | 15 | {% endblock %} 16 | {% block footer_top %}{% endblock %} 17 | {% block content_end %}{% endblock %} 18 | {% block main_content_right %}{% endblock %} 19 | -------------------------------------------------------------------------------- /django/thunderstore/frontend/templates/frontend/markdown_preview.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %}Markdown Preview{% endblock %} 4 | 5 | {% block main_content_left %}{% endblock %} 6 | {% block content_beginning %}{% endblock %} 7 | 8 | {% block content %} 9 |

Markdown Preview

10 | 11 |
12 | 15 | 16 | {% endblock %} 17 | {% block footer_top %}{% endblock %} 18 | {% block content_end %}{% endblock %} 19 | {% block main_content_right %}{% endblock %} 20 | -------------------------------------------------------------------------------- /django/thunderstore/frontend/templates/robots.txt: -------------------------------------------------------------------------------- 1 | {% load dynamic_html %} 2 | {% dynamic_html "robots_txt" %} 3 | -------------------------------------------------------------------------------- /django/thunderstore/frontend/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/frontend/templatetags/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/frontend/templatetags/arrow.py: -------------------------------------------------------------------------------- 1 | import arrow 2 | from django import template 3 | from django.utils import timezone 4 | 5 | register = template.Library() 6 | 7 | 8 | @register.simple_tag 9 | def humanize_timestamp(timestamp): 10 | timestamp = arrow.get(timestamp) 11 | now = arrow.get(timezone.now()) 12 | return timestamp.humanize(now) 13 | -------------------------------------------------------------------------------- /django/thunderstore/frontend/templatetags/encode_props.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import json 3 | 4 | from django import template 5 | from django.utils.safestring import mark_safe 6 | 7 | register = template.Library() 8 | 9 | 10 | @register.filter 11 | def encode_props(props): 12 | # Dump to JSON and encode as Base64 to avoid escaping issues. 13 | # The React components handle decoding in reverse 14 | encoded = base64.b64encode(json.dumps(props).encode()).decode() 15 | return mark_safe(f'"{encoded}"') 16 | -------------------------------------------------------------------------------- /django/thunderstore/frontend/templatetags/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/frontend/templatetags/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/frontend/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/frontend/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/frontend/tests/test_context.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from thunderstore.frontend.context import nav_links 4 | from thunderstore.frontend.models import NavLink 5 | 6 | 7 | @pytest.mark.django_db 8 | def test_nav_link_context(): 9 | links = [ 10 | NavLink.objects.create( 11 | title=f"link {i}", 12 | href="#test", 13 | order=-i, 14 | is_active=i % 2 == 0, 15 | ) 16 | for i in range(3) 17 | ] 18 | context = nav_links(None) 19 | assert list(context["global_nav_links"].values_list("pk", flat=True)) == [ 20 | links[2].pk, 21 | links[0].pk, 22 | ] 23 | -------------------------------------------------------------------------------- /django/thunderstore/frontend/tests/test_encode_props.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import json 3 | from typing import Any 4 | 5 | import pytest 6 | from django.utils.safestring import SafeString 7 | 8 | from thunderstore.frontend.extract_props import decode_props 9 | from thunderstore.frontend.templatetags.encode_props import encode_props 10 | 11 | 12 | @pytest.mark.parametrize( 13 | "data", 14 | ( 15 | {"isDeprecated": True, "canDeprecate": False, "canUndeprecate": True}, 16 | "This is a string, not JSON", 17 | ), 18 | ) 19 | def test_encode_props(data: Any): 20 | encoded = encode_props(data) 21 | assert isinstance(encoded, SafeString) 22 | assert encoded.startswith('"') 23 | assert encoded.endswith('"') 24 | decoded = json.loads(base64.b64decode(encoded[1:-1])) 25 | assert decoded == data 26 | assert decoded == decode_props(encoded[1:-1]) 27 | -------------------------------------------------------------------------------- /django/thunderstore/frontend/tests/test_errors.py: -------------------------------------------------------------------------------- 1 | import base64 2 | 3 | import pytest 4 | from django.conf import settings 5 | from django.test import RequestFactory 6 | 7 | from thunderstore.frontend.views import handle404, handle500 8 | 9 | 10 | @pytest.mark.django_db 11 | @pytest.mark.parametrize("static", (False, True)) 12 | @pytest.mark.parametrize("view", (handle404, handle500)) 13 | def test_frontend_error_cache_headers(rf: RequestFactory, static: bool, view): 14 | # b64 to ensure no accidental overlap with setting value if not intended 15 | path = ( 16 | settings.STATIC_URL 17 | if static 18 | else base64.b64encode(settings.STATIC_URL.encode()).decode() 19 | ) 20 | response = view(rf.get(path)) 21 | if static: 22 | assert response["Cache-Control"] == "no-cache" 23 | else: 24 | assert "Cache-Control" not in response 25 | -------------------------------------------------------------------------------- /django/thunderstore/frontend/tests/utils.py: -------------------------------------------------------------------------------- 1 | def get_url_kwarg(arg_name: str) -> str: 2 | # This would be petter if the params could be chosen based on the URL 3 | # pattern name, so consider exposing it here if more complex use cases 4 | # appear. 5 | return "32" if arg_name == "page" else "test" 6 | -------------------------------------------------------------------------------- /django/thunderstore/frontend/url_reverse.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Optional 2 | 3 | from thunderstore.community.models import Community 4 | 5 | 6 | def get_community_url_reverse_args( 7 | community: Optional[Community], 8 | viewname: str, 9 | kwargs: Optional[Dict] = None, 10 | ) -> Dict: 11 | if Community.should_use_old_urls(community): 12 | return { 13 | "viewname": f"old_urls:{viewname}", 14 | "kwargs": kwargs, 15 | } 16 | else: 17 | if kwargs is None: 18 | kwargs = dict() 19 | kwargs["community_identifier"] = community.identifier 20 | return { 21 | "viewname": f"communities:community:{viewname}", 22 | "kwargs": kwargs, 23 | } 24 | -------------------------------------------------------------------------------- /django/thunderstore/legal/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = "thunderstore.legal.apps.LegalAppConfig" 2 | -------------------------------------------------------------------------------- /django/thunderstore/legal/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class LegalAppConfig(AppConfig): 5 | name = "thunderstore.legal" 6 | label = "ts_legal" 7 | -------------------------------------------------------------------------------- /django/thunderstore/legal/context_processors.py: -------------------------------------------------------------------------------- 1 | from django_contracts.models import LegalContract 2 | from django_contracts.models.publishable import PublishStatus 3 | 4 | 5 | def legal_contracts(request): 6 | return { 7 | "legal_contracts": LegalContract.objects.filter( 8 | publish_status=PublishStatus.PUBLISHED 9 | ) 10 | .exclude(latest=None) 11 | .order_by("-title") 12 | } 13 | -------------------------------------------------------------------------------- /django/thunderstore/legal/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/legal/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/legal/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from thunderstore.legal.views import ( 4 | LegalContractHistoryView, 5 | LegalContractVersionView, 6 | LegalContractView, 7 | ) 8 | 9 | legal_urls = [ 10 | path("c//", LegalContractView.as_view(), name="contract"), 11 | path( 12 | "c//history/", 13 | LegalContractHistoryView.as_view(), 14 | name="contract.history", 15 | ), 16 | path( 17 | "c//v//", 18 | LegalContractVersionView.as_view(), 19 | name="contract.version", 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /django/thunderstore/markdown/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/markdown/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/markdown/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/markdown/templatetags/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/markdown/templatetags/markdownify.py: -------------------------------------------------------------------------------- 1 | import bleach 2 | from django import template 3 | from django.template.defaultfilters import stringfilter 4 | from django.utils.safestring import mark_safe 5 | from markdown_it import MarkdownIt 6 | 7 | from thunderstore.markdown.allowed_tags import ( 8 | ALLOWED_ATTRIBUTES, 9 | ALLOWED_PROTOCOLS, 10 | ALLOWED_TAGS, 11 | ) 12 | 13 | register = template.Library() 14 | md = MarkdownIt("gfm-like") 15 | 16 | 17 | def render_markdown(value: str): 18 | if value.startswith("\ufeff"): 19 | value = value[1:] 20 | return mark_safe( 21 | bleach.clean( 22 | text=md.render(value.strip()), 23 | tags=ALLOWED_TAGS, 24 | protocols=ALLOWED_PROTOCOLS, 25 | attributes=ALLOWED_ATTRIBUTES, 26 | ), 27 | ) 28 | 29 | 30 | @register.filter 31 | @stringfilter 32 | def markdownify(value): 33 | return render_markdown(value) 34 | -------------------------------------------------------------------------------- /django/thunderstore/markdown/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/markdown/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/metrics/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = "thunderstore.metrics.apps.MetricsAppConfig" 2 | -------------------------------------------------------------------------------- /django/thunderstore/metrics/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MetricsAppConfig(AppConfig): 5 | name = "thunderstore.metrics" 6 | label = "metrics" 7 | -------------------------------------------------------------------------------- /django/thunderstore/metrics/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2023-12-14 17:44 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name="PackageVersionDownloadEvent", 15 | fields=[ 16 | ( 17 | "id", 18 | models.BigAutoField( 19 | editable=False, primary_key=True, serialize=False 20 | ), 21 | ), 22 | ("version_id", models.BigIntegerField(db_index=True)), 23 | ("timestamp", models.DateTimeField()), 24 | ], 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /django/thunderstore/metrics/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/metrics/migrations/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/metrics/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .package_download import PackageVersionDownloadEvent 2 | -------------------------------------------------------------------------------- /django/thunderstore/metrics/models/package_download.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class PackageVersionDownloadEvent(models.Model): 5 | id = models.BigAutoField(primary_key=True, editable=False) 6 | version_id = models.BigIntegerField(db_index=True) 7 | timestamp = models.DateTimeField() 8 | -------------------------------------------------------------------------------- /django/thunderstore/moderation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/moderation/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/moderation/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from thunderstore.repository.views.package.list import PackageReviewListView 4 | 5 | moderation_urls = [ 6 | path( 7 | "review-queue/packages/", 8 | PackageReviewListView.as_view(), 9 | name="review-queue.packages", 10 | ), 11 | ] 12 | -------------------------------------------------------------------------------- /django/thunderstore/modpacks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/modpacks/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/modpacks/admin/__init__.py: -------------------------------------------------------------------------------- 1 | from .legacyprofile import LegacyProfileAdmin 2 | -------------------------------------------------------------------------------- /django/thunderstore/modpacks/admin/legacyprofile.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from thunderstore.modpacks.models import LegacyProfile 4 | 5 | 6 | @admin.register(LegacyProfile) 7 | class LegacyProfileAdmin(admin.ModelAdmin): 8 | readonly_fields = ( 9 | "datetime_created", 10 | "datetime_updated", 11 | "file", 12 | "file_size", 13 | "file_sha256", 14 | ) 15 | list_display = ( 16 | "id", 17 | "file_size", 18 | "file_sha256", 19 | "datetime_created", 20 | "datetime_updated", 21 | ) 22 | list_display_links = ("id",) 23 | date_hierarchy = "datetime_created" 24 | search_fields = ("id", "file_sha256") 25 | -------------------------------------------------------------------------------- /django/thunderstore/modpacks/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/modpacks/api/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/modpacks/api/experimental/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/modpacks/api/experimental/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/modpacks/api/experimental/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/modpacks/api/experimental/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/modpacks/api/experimental/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from thunderstore.modpacks.api.experimental.views.legacyprofile import ( 4 | LegacyProfileCreateApiView, 5 | LegacyProfileRetrieveApiView, 6 | ) 7 | 8 | urls = [ 9 | path( 10 | "legacyprofile/create/", 11 | LegacyProfileCreateApiView.as_view(), 12 | name="legacyprofile.create", 13 | ), 14 | path( 15 | "legacyprofile/get//", 16 | LegacyProfileRetrieveApiView.as_view(), 17 | name="legacyprofile.retrieve", 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /django/thunderstore/modpacks/api/experimental/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/modpacks/api/experimental/views/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/modpacks/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from factory.django import DjangoModelFactory 3 | 4 | from thunderstore.modpacks.models import LegacyProfile 5 | 6 | LEGACYPROFILE_TEST_CONTENT = b"test content" 7 | 8 | 9 | class LegacyProfileFactory(DjangoModelFactory): 10 | class Meta: 11 | model = LegacyProfile 12 | 13 | file = factory.django.FileField(data=LEGACYPROFILE_TEST_CONTENT) 14 | file_size = len(LEGACYPROFILE_TEST_CONTENT) 15 | -------------------------------------------------------------------------------- /django/thunderstore/modpacks/migrations/0002_add_file_sha256.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2023-01-09 16:23 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("modpacks", "0001_initial"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="legacyprofile", 15 | name="file_sha256", 16 | field=models.CharField( 17 | blank=True, editable=False, max_length=512, null=True 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /django/thunderstore/modpacks/migrations/0003_add_checksum_index.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2024-01-14 22:42 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("modpacks", "0002_add_file_sha256"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="legacyprofile", 15 | name="file_sha256", 16 | field=models.CharField( 17 | blank=True, db_index=True, editable=False, max_length=512, null=True 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /django/thunderstore/modpacks/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/modpacks/migrations/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/modpacks/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .legacyprofile import LegacyProfile 2 | -------------------------------------------------------------------------------- /django/thunderstore/modpacks/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/modpacks/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/monkeypatch/__init__.py: -------------------------------------------------------------------------------- 1 | from . import monkeypatch_thumbnailer # noqa 2 | -------------------------------------------------------------------------------- /django/thunderstore/permissions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/permissions/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/permissions/admin/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/permissions/admin/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/permissions/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/permissions/migrations/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/permissions/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .visibility import VisibilityFlags 2 | -------------------------------------------------------------------------------- /django/thunderstore/permissions/models/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/permissions/models/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/permissions/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/permissions/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/permissions/utils.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from thunderstore.core.exceptions import PermissionValidationError 4 | from thunderstore.core.types import UserType 5 | 6 | 7 | def validate_user( 8 | user: Optional[UserType], allow_serviceaccount: bool = False 9 | ) -> UserType: 10 | if not user or not user.is_authenticated: 11 | raise PermissionValidationError("Must be authenticated") 12 | if not user.is_active: 13 | raise PermissionValidationError("User has been deactivated", is_public=False) 14 | if hasattr(user, "service_account") and not allow_serviceaccount: 15 | raise PermissionValidationError( 16 | "Service accounts are unable to perform this action" 17 | ) 18 | return user 19 | -------------------------------------------------------------------------------- /django/thunderstore/plugins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/plugins/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/plugins/types.py: -------------------------------------------------------------------------------- 1 | from typing import TypedDict 2 | 3 | 4 | class SettingsLink(TypedDict): 5 | url: str 6 | title: str 7 | -------------------------------------------------------------------------------- /django/thunderstore/repository/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = "thunderstore.repository.apps.RepositoryAppConfig" 2 | -------------------------------------------------------------------------------- /django/thunderstore/repository/admin/__init__.py: -------------------------------------------------------------------------------- 1 | from .discord_bot import DiscordUserBotPermissionAdmin 2 | from .namespace import NamespaceAdmin 3 | from .package import PackageAdmin 4 | from .package_installer import PackageInstallerAdmin 5 | from .package_rating import PackageRatingAdmin 6 | from .package_version import PackageVersionAdmin 7 | from .submission import AsyncPackageSubmissionAdmin 8 | from .team import TeamAdmin 9 | -------------------------------------------------------------------------------- /django/thunderstore/repository/admin/actions.py: -------------------------------------------------------------------------------- 1 | from django.db import transaction 2 | from django.db.models import QuerySet 3 | 4 | 5 | @transaction.atomic 6 | def activate(modeladmin, request, queryset: QuerySet): 7 | for package in queryset: 8 | package.is_active = True 9 | package.save(update_fields=("is_active",)) 10 | 11 | 12 | activate.short_description = "Activate" 13 | 14 | 15 | @transaction.atomic 16 | def deactivate(modeladmin, request, queryset: QuerySet): 17 | for package in queryset: 18 | package.is_active = False 19 | package.save(update_fields=("is_active",)) 20 | 21 | 22 | deactivate.short_description = "Deactivate" 23 | -------------------------------------------------------------------------------- /django/thunderstore/repository/admin/discord_bot.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from thunderstore.repository.models import DiscordUserBotPermission 4 | 5 | 6 | @admin.register(DiscordUserBotPermission) 7 | class DiscordUserBotPermissionAdmin(admin.ModelAdmin): 8 | raw_id_fields = ("thunderstore_user",) 9 | list_display = ( 10 | "thunderstore_user", 11 | "label", 12 | "discord_user_id", 13 | "can_deprecate", 14 | ) 15 | list_select_related = ("thunderstore_user",) 16 | list_filter = ("can_deprecate",) 17 | search_fields = ( 18 | "label", 19 | "thunderstore_user__username", 20 | ) 21 | -------------------------------------------------------------------------------- /django/thunderstore/repository/admin/package_installer.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.http import HttpRequest 3 | 4 | from thunderstore.repository.models import PackageInstaller 5 | 6 | 7 | @admin.register(PackageInstaller) 8 | class PackageInstallerAdmin(admin.ModelAdmin): 9 | readonly_fields = ("identifier",) 10 | list_display = ("identifier", "name") 11 | list_display_links = ("identifier", "name") 12 | search_fields = ("identifier", "name") 13 | 14 | def has_add_permission(self, request: HttpRequest, obj=None) -> bool: 15 | return False 16 | 17 | def has_change_permission(self, request: HttpRequest, obj=None) -> bool: 18 | return False 19 | 20 | def has_delete_permission(self, request: HttpRequest, obj=None) -> bool: 21 | return False 22 | -------------------------------------------------------------------------------- /django/thunderstore/repository/admin/package_rating.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.http import HttpRequest 3 | 4 | from thunderstore.repository.models import PackageRating 5 | 6 | 7 | @admin.register(PackageRating) 8 | class PackageRatingAdmin(admin.ModelAdmin): 9 | model = PackageRating 10 | list_display = ( 11 | "rater", 12 | "package", 13 | "date_created", 14 | ) 15 | list_select_related = ( 16 | "package", 17 | "rater", 18 | ) 19 | raw_id_fields = ( 20 | "package", 21 | "rater", 22 | ) 23 | 24 | def has_add_permission(self, request: HttpRequest, obj=None) -> bool: 25 | return False 26 | 27 | def has_change_permission(self, request: HttpRequest, obj=None) -> bool: 28 | return False 29 | 30 | def has_delete_permission(self, request: HttpRequest, obj=None) -> bool: 31 | return False 32 | -------------------------------------------------------------------------------- /django/thunderstore/repository/admin/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/repository/admin/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/repository/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/repository/api/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/repository/api/experimental/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/repository/api/experimental/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/repository/api/experimental/serializers/__init__.py: -------------------------------------------------------------------------------- 1 | from .main import * 2 | from .markdown import * 3 | -------------------------------------------------------------------------------- /django/thunderstore/repository/api/experimental/serializers/markdown.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | 4 | class MarkdownResponseSerializer(serializers.Serializer): 5 | markdown = serializers.CharField(required=True, allow_null=True) 6 | -------------------------------------------------------------------------------- /django/thunderstore/repository/api/experimental/serializers/validators.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from thunderstore.repository.models import Team 4 | from thunderstore.repository.serializer_fields import Base64Field, ModelChoiceField 5 | from thunderstore.repository.validation.icon import MAX_ICON_SIZE 6 | 7 | 8 | class ReadmeValidatorParamsSerializer(serializers.Serializer): 9 | readme_data = Base64Field() 10 | 11 | 12 | class ManifestV1ValidatorParamsSerializer(serializers.Serializer): 13 | namespace = ModelChoiceField( 14 | queryset=Team.objects.exclude(is_active=False), 15 | to_field="name", 16 | ) 17 | manifest_data = Base64Field() 18 | 19 | 20 | class IconValidatorParamsSerializer(serializers.Serializer): 21 | icon_data = Base64Field(max_size=MAX_ICON_SIZE) 22 | 23 | 24 | class ValidatorResponseSerializer(serializers.Serializer): 25 | success = serializers.BooleanField() 26 | -------------------------------------------------------------------------------- /django/thunderstore/repository/api/experimental/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/repository/api/experimental/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/repository/api/experimental/views/__init__.py: -------------------------------------------------------------------------------- 1 | from .package import PackageDetailApiView, PackageListApiView 2 | from .package_version import ( 3 | PackageVersionChangelogApiView, 4 | PackageVersionDetailApiView, 5 | PackageVersionReadmeApiView, 6 | ) 7 | from .upload import UploadPackageApiView 8 | 9 | __all__ = [ 10 | "PackageListApiView", 11 | "PackageDetailApiView", 12 | "PackageVersionChangelogApiView", 13 | "PackageVersionReadmeApiView", 14 | "PackageVersionDetailApiView", 15 | "UploadPackageApiView", 16 | ] 17 | -------------------------------------------------------------------------------- /django/thunderstore/repository/api/experimental/views/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/repository/api/experimental/views/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/repository/api/v1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/repository/api/v1/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/repository/api/v1/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/repository/api/v1/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/repository/api/v1/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/repository/api/v1/views/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/repository/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class RepositoryAppConfig(AppConfig): 5 | name = "thunderstore.repository" 6 | label = "repository" 7 | -------------------------------------------------------------------------------- /django/thunderstore/repository/consts.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from thunderstore.core.utils import ChoiceEnum 4 | 5 | PACKAGE_NAME_REGEX = re.compile(r"^[a-zA-Z0-9\_]+$") 6 | PACKAGE_VERSION_REGEX = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+$") 7 | 8 | 9 | PACKAGE_REFERENCE_COMPONENT_REGEX = re.compile( 10 | r"^[a-zA-Z0-9]+([a-zA-Z0-9\_]+[a-zA-Z0-9])?$" 11 | ) 12 | 13 | 14 | class PackageVersionReviewStatus(ChoiceEnum): 15 | pending = "pending" 16 | approved = "approved" 17 | rejected = "rejected" 18 | skipped = "skipped" 19 | immune = "immune" 20 | -------------------------------------------------------------------------------- /django/thunderstore/repository/context_processors.py: -------------------------------------------------------------------------------- 1 | def team(request): 2 | if not (hasattr(request, "user") and request.user.is_authenticated): 3 | return {} 4 | name = request.user.username 5 | membership = request.user.teams.filter(team__is_active=True).first() 6 | if membership: 7 | name = membership.team.name 8 | return {"team": name} 9 | -------------------------------------------------------------------------------- /django/thunderstore/repository/filetree.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import IO, Any 3 | from zipfile import ZipFile 4 | 5 | from thunderstore.storage.models import DataBlobGroup 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | def create_file_tree_from_zip_data( 11 | name: str, 12 | zip_data: IO[Any], 13 | ) -> DataBlobGroup: 14 | with ZipFile(zip_data) as unzip: 15 | group: DataBlobGroup = DataBlobGroup.objects.create(name=name) 16 | for entry in unzip.infolist(): 17 | logger.info(f"Processing {entry.filename}") 18 | if entry.is_dir(): 19 | continue 20 | group.add_entry(unzip.read(entry), entry.filename) 21 | group.set_complete() 22 | return group 23 | -------------------------------------------------------------------------------- /django/thunderstore/repository/forms/__init__.py: -------------------------------------------------------------------------------- 1 | from .team import * 2 | -------------------------------------------------------------------------------- /django/thunderstore/repository/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/repository/management/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/repository/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/repository/management/commands/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/repository/management/commands/update_caches.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | 3 | from thunderstore.repository.api.v1.tasks import update_api_v1_caches 4 | 5 | 6 | class Command(BaseCommand): 7 | help = "Updates repository specific caches" 8 | 9 | def handle(self, *args, **kwargs): 10 | print("Updating caches") 11 | update_api_v1_caches() 12 | print("Caches updated!") 13 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/0003_add_dependencies.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2019-04-06 22:04 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("repository", "0002_add_packageversiondownloadevent"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="packageversion", 15 | name="dependencies", 16 | field=models.ManyToManyField( 17 | blank=True, related_name="dependants", to="repository.PackageVersion" 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/0004_add_update_date.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2019-04-06 22:39 2 | 3 | import django.utils.timezone 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("repository", "0003_add_dependencies"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name="package", 16 | name="date_updated", 17 | field=models.DateTimeField( 18 | auto_now_add=True, default=django.utils.timezone.now 19 | ), 20 | preserve_default=False, 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/0006_package_pinned.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2019-04-19 14:38 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("repository", "0005_migrate_update_dates"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="package", 15 | name="pinned", 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/0007_rename_pinned.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2019-04-20 08:59 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("repository", "0006_package_pinned"), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name="package", 15 | old_name="pinned", 16 | new_name="is_pinned", 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/0012_package_is_deprecated.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2019-05-27 00:23 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("repository", "0011_package_remove_owner"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="package", 15 | name="is_deprecated", 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/0014_update_meta.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.2 on 2019-05-29 05:20 2 | 3 | import django.db.models.deletion 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("repository", "0013_package_cache_latest"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name="uploaderidentitymember", 17 | name="user", 18 | field=models.ForeignKey( 19 | on_delete=django.db.models.deletion.CASCADE, 20 | related_name="uploader_identities", 21 | to=settings.AUTH_USER_MODEL, 22 | ), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/0019_add_version_file_size.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.6 on 2019-12-29 19:06 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("repository", "0018_add_discord_permissions"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="packageversion", 15 | name="file_size", 16 | field=models.PositiveIntegerField(null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/0021_set_file_size_required.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.6 on 2019-12-29 19:09 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("repository", "0020_fetch_version_file_sizes"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="packageversion", 15 | name="file_size", 16 | field=models.PositiveIntegerField(), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/0024_add_uploader_identity_is_active.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.6 on 2021-02-17 17:53 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("repository", "0023_add_cache_refresh_celery_schedule"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="uploaderidentity", 15 | name="is_active", 16 | field=models.BooleanField(default=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/0025_alter_name_validator.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.6 on 2021-02-20 22:11 2 | 3 | from django.db import migrations, models 4 | 5 | import thunderstore.repository.validators 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("repository", "0024_add_uploader_identity_is_active"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name="uploaderidentity", 17 | name="name", 18 | field=models.CharField( 19 | max_length=64, 20 | unique=True, 21 | validators=[ 22 | thunderstore.repository.validators.PackageReferenceComponentValidator( 23 | "Author name" 24 | ) 25 | ], 26 | ), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/0030_package_namespace.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2021-11-06 13:55 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("repository", "0029_create_namespaces_for_teams"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name="package", 16 | name="namespace", 17 | field=models.ForeignKey( 18 | null=True, 19 | on_delete=django.db.models.deletion.PROTECT, 20 | related_name="packages", 21 | to="repository.namespace", 22 | ), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/0032_package_namespace_non_nullable.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2022-01-10 18:51 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("repository", "0031_add_namespaces_to_existing_packages"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="package", 16 | name="namespace", 17 | field=models.ForeignKey( 18 | on_delete=django.db.models.deletion.PROTECT, 19 | related_name="packages", 20 | to="repository.namespace", 21 | ), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/0034_add_team_donation_link.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2022-04-02 02:35 2 | 3 | import django.core.validators 4 | import storages.backends.s3boto3 5 | from django.db import migrations, models 6 | 7 | import thunderstore.repository.models.package_version 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ("repository", "0033_add_s3_cache"), 14 | ] 15 | 16 | operations = [ 17 | migrations.AddField( 18 | model_name="team", 19 | name="donation_link", 20 | field=models.CharField( 21 | blank=True, 22 | max_length=1024, 23 | null=True, 24 | validators=[django.core.validators.URLValidator(["https"])], 25 | ), 26 | ) 27 | ] 28 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/0039_add_package_file_tree.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2023-09-25 10:19 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("storage", "0002_add_group"), 11 | ("repository", "0038_add_wiki"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name="packageversion", 17 | name="file_tree", 18 | field=models.ForeignKey( 19 | blank=True, 20 | null=True, 21 | on_delete=django.db.models.deletion.PROTECT, 22 | related_name="package_versions", 23 | to="storage.datablobgroup", 24 | ), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/0040_add_decompilation_visibility_option.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2023-09-26 19:24 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("repository", "0039_add_package_file_tree"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="package", 15 | name="show_decompilation_results", 16 | field=models.TextField( 17 | choices=[("NONE", "None"), ("YES", "Yes"), ("NO", "No")], default="NONE" 18 | ), 19 | ), 20 | migrations.AddField( 21 | model_name="team", 22 | name="show_decompilation_results", 23 | field=models.TextField( 24 | choices=[("NONE", "None"), ("YES", "Yes"), ("NO", "No")], default="NONE" 25 | ), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/0041_add_indexes.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2023-11-14 08:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("repository", "0040_add_decompilation_visibility_option"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="packageversion", 15 | name="is_active", 16 | field=models.BooleanField(db_index=True, default=True), 17 | ), 18 | migrations.AddIndex( 19 | model_name="packageversion", 20 | index=models.Index( 21 | fields=["date_created", "id"], name="repository__date_cr_f62328_idx" 22 | ), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/0044_add_deprecate_permission.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2023-12-03 03:02 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("repository", "0043_add_package_index_task_schedule"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name="package", 15 | options={ 16 | "permissions": ( 17 | ("deprecate_package", "Can manage package deprecation status"), 18 | ) 19 | }, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/0047_add_visibility_flags.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2024-01-16 02:10 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("permissions", "__first__"), 11 | ("repository", "0046_add_submission_cleanup_schedule"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name="packageversion", 17 | name="visibility", 18 | field=models.OneToOneField( 19 | blank=True, 20 | null=True, 21 | on_delete=django.db.models.deletion.PROTECT, 22 | to="permissions.visibilityflags", 23 | ), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/0050_add_installer_name_description.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2024-01-27 06:09 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("repository", "0049_add_package_installer"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="packageinstaller", 15 | name="description", 16 | field=models.TextField(default=""), 17 | preserve_default=False, 18 | ), 19 | migrations.AddField( 20 | model_name="packageinstaller", 21 | name="name", 22 | field=models.TextField(default=""), 23 | preserve_default=False, 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/0051_bigint_file_size.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2024-04-07 16:30 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("repository", "0050_add_installer_name_description"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="packageversion", 15 | name="file_size", 16 | field=models.PositiveBigIntegerField(), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/0054_alter_chunked_package_cache_index.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2024-05-22 09:01 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("storage", "0002_add_group"), 11 | ("repository", "0053_schedule_chunked_package_caching"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name="apiv1chunkedpackagecache", 17 | name="index", 18 | field=models.ForeignKey( 19 | on_delete=django.db.models.deletion.PROTECT, 20 | related_name="chunked_package_indexes", 21 | to="storage.datablob", 22 | ), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/0055_delete_namespaces_without_team.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2025-01-07 15:07 2 | 3 | from django.db import migrations 4 | 5 | 6 | def forwards(apps, schema_editor): 7 | Namespace = apps.get_model("repository", "Namespace") 8 | 9 | Namespace.objects.filter(team__isnull=True, packages=None).delete() 10 | 11 | 12 | class Migration(migrations.Migration): 13 | 14 | dependencies = [ 15 | ("repository", "0054_alter_chunked_package_cache_index"), 16 | ] 17 | 18 | operations = [ 19 | migrations.RunPython(forwards, migrations.RunPython.noop), 20 | ] 21 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/0056_packageversion_uploaded_by.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2024-10-17 19:05 2 | 3 | import django.db.models.deletion 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ("repository", "0055_delete_namespaces_without_team"), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name="packageversion", 18 | name="uploaded_by", 19 | field=models.ForeignKey( 20 | blank=True, 21 | null=True, 22 | on_delete=django.db.models.deletion.SET_NULL, 23 | related_name="uploaded_versions", 24 | to=settings.AUTH_USER_MODEL, 25 | ), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/0057_team_max_file_count_per_zip.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2025-04-21 18:55 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("repository", "0056_packageversion_uploaded_by"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="team", 15 | name="max_file_count_per_zip", 16 | field=models.IntegerField( 17 | blank=True, 18 | help_text="Optional limit on the max number of files in a zip uploaded by this team, replacing the default limit", 19 | null=True, 20 | ), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/0058_packageversion_review_status.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2025-03-07 20:08 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("repository", "0057_team_max_file_count_per_zip"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="packageversion", 15 | name="review_status", 16 | field=models.TextField( 17 | choices=[ 18 | ("pending", "pending"), 19 | ("approved", "approved"), 20 | ("rejected", "rejected"), 21 | ("skipped", "skipped"), 22 | ("immune", "immune"), 23 | ], 24 | default="skipped", 25 | ), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/0059_package_visibility.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2025-05-09 21:32 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("permissions", "0001_initial"), 11 | ("repository", "0058_packageversion_review_status"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name="package", 17 | name="visibility", 18 | field=models.OneToOneField( 19 | blank=True, 20 | null=True, 21 | on_delete=django.db.models.deletion.PROTECT, 22 | to="permissions.visibilityflags", 23 | ), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /django/thunderstore/repository/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/repository/migrations/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/repository/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .cache import * 2 | from .discord_bot import * 3 | from .namespace import * 4 | from .package import * 5 | from .package_download import * 6 | from .package_installer import * 7 | from .package_rating import * 8 | from .package_version import * 9 | from .submission import * 10 | from .team import * 11 | from .wiki import * 12 | -------------------------------------------------------------------------------- /django/thunderstore/repository/models/discord_bot.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.db import models 3 | 4 | 5 | class DiscordUserBotPermission(models.Model): 6 | thunderstore_user = models.ForeignKey( 7 | settings.AUTH_USER_MODEL, 8 | on_delete=models.CASCADE, 9 | ) 10 | label = models.CharField( 11 | max_length=64, 12 | ) 13 | discord_user_id = models.CharField( 14 | max_length=64, 15 | ) 16 | can_deprecate = models.BooleanField( 17 | default=False, 18 | ) 19 | 20 | class Meta: 21 | constraints = [ 22 | models.UniqueConstraint( 23 | fields=("thunderstore_user", "discord_user_id"), 24 | name="one_permission_per_user", 25 | ), 26 | ] 27 | verbose_name = "Discord User Permissions" 28 | verbose_name_plural = "Discord User Permissions" 29 | -------------------------------------------------------------------------------- /django/thunderstore/repository/permissions.py: -------------------------------------------------------------------------------- 1 | from django.core.exceptions import ValidationError 2 | from rest_framework.exceptions import PermissionDenied 3 | 4 | from thunderstore.core.types import UserType 5 | from thunderstore.permissions.utils import validate_user 6 | from thunderstore.repository.models import Package 7 | 8 | 9 | def ensure_can_rate_package(user: UserType, package: Package): 10 | try: 11 | validate_user(user) 12 | except ValidationError as e: 13 | raise PermissionDenied(e.message) from e 14 | return True 15 | -------------------------------------------------------------------------------- /django/thunderstore/repository/tasks/__init__.py: -------------------------------------------------------------------------------- 1 | from .caches import * 2 | from .downloads import * 3 | from .files import * 4 | from .submission import * 5 | -------------------------------------------------------------------------------- /django/thunderstore/repository/tasks/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/repository/tasks/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/repository/templates/repository/package_wiki_404.html: -------------------------------------------------------------------------------- 1 | {% extends 'repository/_wiki_base.html' %} 2 | {% load arrow %} 3 | {% load markdownify %} 4 | 5 | {% block wiki_content %} 6 |
7 |
8 |

Wiki page not found

9 |
10 |
11 |
12 |
13 |

Looks like this wiki page doesn't exist

14 | Wiki home 15 |
16 |
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /django/thunderstore/repository/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/repository/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/repository/validation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/repository/validation/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/repository/validation/markdown.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | 3 | from django.core.exceptions import ValidationError 4 | 5 | MAX_MARKDOWN_SIZE = 1000 * 100 # 100kb 6 | 7 | 8 | def validate_markdown(filename: str, readme_data: bytes) -> str: 9 | try: 10 | readme = readme_data.decode("utf-8") 11 | except UnicodeDecodeError as exc: 12 | raise ValidationError( 13 | [ 14 | f"Unable to parse {filename}: {exc}\n", 15 | f"Make sure the {filename} is UTF-8 compatible", 16 | ], 17 | ) 18 | if len(readme) > MAX_MARKDOWN_SIZE: 19 | raise ValidationError(f"{filename} is too long, max: {MAX_MARKDOWN_SIZE}") 20 | if readme_data.startswith(codecs.BOM_UTF8): 21 | raise ValidationError( 22 | f"{filename} starts with a UTF-8 BOM, please try to re-save the file without a BOM.", 23 | ) 24 | 25 | return readme 26 | -------------------------------------------------------------------------------- /django/thunderstore/repository/validation/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/repository/validation/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/repository/views/__init__.py: -------------------------------------------------------------------------------- 1 | from .docs import PackageDocsView 2 | from .package.create import PackageCreateOldView, PackageCreateView 3 | from .package.detail import PackageDetailView 4 | from .package.download import PackageDownloadView 5 | from .package.list import ( 6 | PackageListByDependencyView, 7 | PackageListByOwnerView, 8 | PackageListSearchView, 9 | PackageListView, 10 | ) 11 | from .package.version import PackageVersionDetailView 12 | 13 | __all__ = [ 14 | "PackageDocsView", 15 | ] 16 | -------------------------------------------------------------------------------- /django/thunderstore/repository/views/docs.py: -------------------------------------------------------------------------------- 1 | from django.views.generic import TemplateView 2 | 3 | from thunderstore.repository.mixins import CommunityMixin 4 | 5 | 6 | class PackageDocsView(CommunityMixin, TemplateView): 7 | template_name = "repository/package_docs.html" 8 | -------------------------------------------------------------------------------- /django/thunderstore/repository/views/package/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/repository/views/package/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/repository/views/package/tabs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/repository/views/package/tabs/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/repository/views/package/tabs/changelog.py: -------------------------------------------------------------------------------- 1 | from thunderstore.repository.views.mixins import PackageListingDetailView 2 | 3 | 4 | class PackageChangelogTabView(PackageListingDetailView): 5 | tab_name = "changelog" 6 | template_name = "community/packagelisting_changelog.html" 7 | -------------------------------------------------------------------------------- /django/thunderstore/repository/views/package/tabs/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/repository/views/package/tabs/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/repository/views/package/tabs/versions.py: -------------------------------------------------------------------------------- 1 | from thunderstore.repository.views.mixins import PackageListingDetailView 2 | 3 | 4 | class PackageVersionsTabView(PackageListingDetailView): 5 | tab_name = "versions" 6 | template_name = "community/packagelisting_versions.html" 7 | -------------------------------------------------------------------------------- /django/thunderstore/repository/views/package/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/repository/views/package/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/repository/views/repository.py: -------------------------------------------------------------------------------- 1 | from .package._utils import get_package_listing_or_404 2 | 3 | __all__ = ["get_package_listing_or_404"] 4 | -------------------------------------------------------------------------------- /django/thunderstore/schema_import/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/schema_import/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/schema_import/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class SchemaAppConfig(AppConfig): 5 | name = "thunderstore.schema_import" 6 | label = "schema_import" 7 | -------------------------------------------------------------------------------- /django/thunderstore/schema_import/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/schema_import/migrations/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/schema_import/tasks.py: -------------------------------------------------------------------------------- 1 | from celery import shared_task 2 | 3 | from thunderstore.schema_import.sync import sync_thunderstore_schema 4 | 5 | 6 | @shared_task 7 | def sync_ecosystem_schema(): 8 | sync_thunderstore_schema() 9 | -------------------------------------------------------------------------------- /django/thunderstore/schema_import/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/schema_import/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/schema_server/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = "thunderstore.schema_server.apps.SchemaServerAppConfig" 2 | -------------------------------------------------------------------------------- /django/thunderstore/schema_server/admin/__init__.py: -------------------------------------------------------------------------------- 1 | from .channel import * 2 | -------------------------------------------------------------------------------- /django/thunderstore/schema_server/admin/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/schema_server/admin/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/schema_server/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/schema_server/api/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/schema_server/api/experimental/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/schema_server/api/experimental/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/schema_server/api/experimental/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/schema_server/api/experimental/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/schema_server/api/experimental/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from thunderstore.schema_server.api.experimental.views.channel import ( 4 | SchemaChannelApiView, 5 | SchemaChannelLatestApiView, 6 | ) 7 | 8 | urls = [ 9 | path( 10 | "schema//", 11 | SchemaChannelApiView.as_view(), 12 | name="schema.channel", 13 | ), 14 | path( 15 | "schema//latest/", 16 | SchemaChannelLatestApiView.as_view(), 17 | name="schema.channel.latest", 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /django/thunderstore/schema_server/api/experimental/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/schema_server/api/experimental/views/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/schema_server/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class SchemaServerAppConfig(AppConfig): 5 | name = "thunderstore.schema_server" 6 | label = "schema_server" 7 | verbose_name = "schema server" 8 | -------------------------------------------------------------------------------- /django/thunderstore/schema_server/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from factory.django import DjangoModelFactory 3 | 4 | from thunderstore.schema_server.models import SchemaChannel 5 | 6 | 7 | class SchemaChannelFactory(DjangoModelFactory): 8 | class Meta: 9 | model = SchemaChannel 10 | 11 | identifier = factory.Sequence(lambda n: f"test-channel-{n}") 12 | -------------------------------------------------------------------------------- /django/thunderstore/schema_server/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/schema_server/migrations/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/schema_server/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .channel import * 2 | from .file import * 3 | -------------------------------------------------------------------------------- /django/thunderstore/schema_server/models/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/schema_server/models/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/social/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/social/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/social/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/social/api/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/social/api/experimental/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/social/api/experimental/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/social/api/experimental/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/social/api/experimental/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/social/api/experimental/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from thunderstore.social.api.experimental.views import ( 4 | CompleteLoginApiView, 5 | DeleteSessionApiView, 6 | OverwolfLoginApiView, 7 | ValidateSessionApiView, 8 | ) 9 | 10 | urls = [ 11 | path( 12 | "auth/complete//", 13 | CompleteLoginApiView.as_view(), 14 | name="auth.complete", 15 | ), 16 | path( 17 | "auth/delete/", 18 | DeleteSessionApiView.as_view(), 19 | name="auth.delete", 20 | ), 21 | path( 22 | "auth/overwolf/login/", 23 | OverwolfLoginApiView.as_view(), 24 | name="auth.overwolf.login", 25 | ), 26 | path( 27 | "auth/validate/", 28 | ValidateSessionApiView.as_view(), 29 | name="auth.validate", 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /django/thunderstore/social/api/experimental/views/__init__.py: -------------------------------------------------------------------------------- 1 | from .complete_login import CompleteLoginApiView 2 | from .current_user import CurrentUserExperimentalApiView 3 | from .delete_session import DeleteSessionApiView 4 | from .overwolf import OverwolfLoginApiView 5 | from .validate_session import ValidateSessionApiView 6 | 7 | __all__ = [ 8 | "CompleteLoginApiView", 9 | "CurrentUserExperimentalApiView", 10 | "DeleteSessionApiView", 11 | "OverwolfLoginApiView", 12 | "ValidateSessionApiView", 13 | ] 14 | -------------------------------------------------------------------------------- /django/thunderstore/social/api/v1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/social/api/v1/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/social/api/v1/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/social/api/v1/views/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/social/templates/settings/includes/provider_display.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | {% spaceless %} 4 | {% if provider == "overwolf" %} 5 | 7 | {% else %} 8 | 9 | {% endif %} 10 | {{ provider }} 11 | {% endspaceless %} 12 | -------------------------------------------------------------------------------- /django/thunderstore/social/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/social/templatetags/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/social/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/social/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/social/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from .views import DeleteAccountView, LinkedAccountsView 4 | 5 | settings_urls = [ 6 | path( 7 | "linked-accounts/", 8 | LinkedAccountsView.as_view(), 9 | name="settings.linked-accounts", 10 | ), 11 | path( 12 | "delete-account/", DeleteAccountView.as_view(), name="settings.delete-account" 13 | ), 14 | ] 15 | -------------------------------------------------------------------------------- /django/thunderstore/storage/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/storage/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/storage/admin/__init__.py: -------------------------------------------------------------------------------- 1 | from .blob import DataBlobAdmin 2 | from .group import DataBlobGroupAdmin 3 | from .reference import DataBlobReferenceAdmin 4 | -------------------------------------------------------------------------------- /django/thunderstore/storage/admin/mixins.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from django.db.models import Model 4 | from django.http import HttpRequest 5 | 6 | 7 | class ReadOnlyInline: 8 | extra = 0 9 | 10 | def has_add_permission( 11 | self, request: HttpRequest, obj: Optional[Model] = None 12 | ) -> bool: 13 | return False 14 | 15 | def has_delete_permission( 16 | self, request: HttpRequest, obj: Optional[Model] = None 17 | ) -> bool: 18 | return False 19 | 20 | def has_change_permission( 21 | self, request: HttpRequest, obj: Optional[Model] = None 22 | ) -> bool: 23 | return False 24 | -------------------------------------------------------------------------------- /django/thunderstore/storage/admin/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/storage/admin/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/storage/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/storage/migrations/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/storage/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .blob import DataBlob 2 | from .group import DataBlobGroup 3 | from .reference import DataBlobReference 4 | -------------------------------------------------------------------------------- /django/thunderstore/storage/models/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/storage/models/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/ts_reports/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = "thunderstore.ts_reports.apps.ReportsAppConfig" 2 | -------------------------------------------------------------------------------- /django/thunderstore/ts_reports/admin/__init__.py: -------------------------------------------------------------------------------- 1 | from .package_report import PackageReportAdmin 2 | -------------------------------------------------------------------------------- /django/thunderstore/ts_reports/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ReportsAppConfig(AppConfig): 5 | name = "thunderstore.ts_reports" 6 | label = "ts_reports" 7 | verbose_name = "reports" 8 | -------------------------------------------------------------------------------- /django/thunderstore/ts_reports/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/ts_reports/migrations/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/ts_reports/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .package_report import * 2 | -------------------------------------------------------------------------------- /django/thunderstore/ts_reports/models/_common.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class ActiveManager(models.Manager): 5 | def active(self): 6 | return self.exclude(is_active=False) 7 | -------------------------------------------------------------------------------- /django/thunderstore/ts_reports/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/ts_reports/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/usermedia/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = "thunderstore.usermedia.apps.UsermediaConfig" 2 | -------------------------------------------------------------------------------- /django/thunderstore/usermedia/admin/__init__.py: -------------------------------------------------------------------------------- 1 | from .usermedia import UserMediaAdmin 2 | 3 | __all__ = ["UserMediaAdmin"] 4 | -------------------------------------------------------------------------------- /django/thunderstore/usermedia/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/usermedia/api/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/usermedia/api/experimental/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/usermedia/api/experimental/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/usermedia/api/experimental/serializers/__init__.py: -------------------------------------------------------------------------------- 1 | from .upload import ( 2 | UploadPartUrlSerializer, 3 | UserMediaFinishUploadParamsSerializer, 4 | UserMediaInitiateUploadParams, 5 | UserMediaInitiateUploadResponseSerializer, 6 | UserMediaSerializer, 7 | ) 8 | 9 | __all__ = [ 10 | "UserMediaSerializer", 11 | "UploadPartUrlSerializer", 12 | "UserMediaInitiateUploadParams", 13 | "UserMediaInitiateUploadResponseSerializer", 14 | "UserMediaFinishUploadParamsSerializer", 15 | ] 16 | -------------------------------------------------------------------------------- /django/thunderstore/usermedia/api/experimental/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from thunderstore.usermedia.api.experimental.views.upload import ( 4 | UserMediaAbortUploadApiView, 5 | UserMediaFinishUploadApiView, 6 | UserMediaInitiateUploadApiView, 7 | ) 8 | 9 | urls = [ 10 | path( 11 | "usermedia/initiate-upload/", 12 | UserMediaInitiateUploadApiView.as_view(), 13 | name="usermedia.initiate-upload", 14 | ), 15 | path( 16 | "usermedia//finish-upload/", 17 | UserMediaFinishUploadApiView.as_view(), 18 | name="usermedia.finish-upload", 19 | ), 20 | path( 21 | "usermedia//abort-upload/", 22 | UserMediaAbortUploadApiView.as_view(), 23 | name="usermedia.abort-upload", 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /django/thunderstore/usermedia/api/experimental/views/__init__.py: -------------------------------------------------------------------------------- 1 | from .upload import UserMediaFinishUploadApiView, UserMediaInitiateUploadApiView 2 | 3 | __all__ = [ 4 | "UserMediaInitiateUploadApiView", 5 | "UserMediaFinishUploadApiView", 6 | ] 7 | -------------------------------------------------------------------------------- /django/thunderstore/usermedia/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UsermediaConfig(AppConfig): 5 | name = "thunderstore.usermedia" 6 | label = "usermedia" 7 | -------------------------------------------------------------------------------- /django/thunderstore/usermedia/cleanup.py: -------------------------------------------------------------------------------- 1 | from thunderstore.core.utils import capture_exception 2 | from thunderstore.usermedia.models import UserMedia 3 | from thunderstore.usermedia.s3_client import get_s3_client 4 | from thunderstore.usermedia.s3_upload import cleanup_expired_upload 5 | 6 | 7 | def cleanup_expired_uploads(): 8 | client = get_s3_client() 9 | for entry in UserMedia.objects.expired().filter(async_package_submissions=None): 10 | try: 11 | cleanup_expired_upload(entry, client) 12 | except Exception as e: 13 | capture_exception(e) 14 | -------------------------------------------------------------------------------- /django/thunderstore/usermedia/consts.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | UPLOAD_PART_SIZE = 1024 * 1024 * 50 4 | MAX_UPLOAD_SIZE = 1024 * 1024 * settings.REPOSITORY_MAX_PACKAGE_SIZE_MB 5 | MIN_UPLOAD_SIZE = 1 6 | -------------------------------------------------------------------------------- /django/thunderstore/usermedia/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from factory.django import DjangoModelFactory 3 | 4 | from thunderstore.core.factories import UserFactory 5 | from thunderstore.usermedia.models import UserMedia 6 | 7 | 8 | class UserMediaFactory(DjangoModelFactory): 9 | class Meta: 10 | model = UserMedia 11 | 12 | owner = factory.SubFactory(UserFactory) 13 | filename = factory.Sequence(lambda n: f"testfile-{n}") 14 | key = factory.Sequence(lambda n: f"testfile-{n}") 15 | size = 2048 16 | -------------------------------------------------------------------------------- /django/thunderstore/usermedia/migrations/0003_usermedia_owner_set_null.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2024-01-04 15:48 2 | 3 | import django.db.models.deletion 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ("usermedia", "0002_add_cleanup_schedule"), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name="usermedia", 18 | name="owner", 19 | field=models.ForeignKey( 20 | blank=True, 21 | null=True, 22 | on_delete=django.db.models.deletion.SET_NULL, 23 | related_name="usermedia", 24 | to=settings.AUTH_USER_MODEL, 25 | ), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /django/thunderstore/usermedia/migrations/0004_usermedia_size_bigint.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2024-01-04 15:59 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("usermedia", "0003_usermedia_owner_set_null"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="usermedia", 15 | name="size", 16 | field=models.PositiveBigIntegerField(), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /django/thunderstore/usermedia/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/usermedia/migrations/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/usermedia/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .usermedia import UserMedia 2 | 3 | __all__ = ["UserMedia"] 4 | -------------------------------------------------------------------------------- /django/thunderstore/usermedia/tasks.py: -------------------------------------------------------------------------------- 1 | from celery import shared_task 2 | 3 | from thunderstore.core.settings import CeleryQueues 4 | from thunderstore.usermedia.cleanup import cleanup_expired_uploads 5 | 6 | 7 | @shared_task(queue=CeleryQueues.Default) 8 | def celery_cleanup_expired_uploads(): 9 | cleanup_expired_uploads() 10 | -------------------------------------------------------------------------------- /django/thunderstore/usermedia/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/usermedia/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/usermedia/tests/test_serializer_fields.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from thunderstore.usermedia.api.experimental.serializers.upload import FilenameField 4 | 5 | 6 | @pytest.mark.parametrize( 7 | ("filename", "expected"), 8 | ( 9 | ("asd/dsa", "dsa"), 10 | ("asd/dsa.zip.png", "dsa.zip.png"), 11 | ("asd\\dsa.zip.png", "dsa.zip.png"), 12 | ("asd\\foo/bar/dsa.zip.png", "dsa.zip.png"), 13 | ("asd\\foo/bar/.././dsa-zip_.png", "dsa-zip_.png"), 14 | ), 15 | ) 16 | def test_serializers_filename_field(filename: str, expected: str): 17 | field = FilenameField() 18 | assert field.to_internal_value(filename) == expected 19 | -------------------------------------------------------------------------------- /django/thunderstore/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/utils/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/utils/batch.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, List, TypeVar 2 | 3 | T = TypeVar("T") 4 | 5 | 6 | def batch(batch_size: int, iterable: Iterable[T]) -> Iterable[List[T]]: 7 | collected = [] 8 | for entry in iterable: 9 | collected.append(entry) 10 | if len(collected) >= batch_size: 11 | yield collected 12 | collected = [] 13 | if len(collected) > 0: 14 | yield collected 15 | -------------------------------------------------------------------------------- /django/thunderstore/utils/contexts.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager 2 | from tempfile import SpooledTemporaryFile 3 | from typing import IO, Any 4 | 5 | 6 | @contextmanager 7 | def TemporarySpooledCopy(source_file: IO[Any]): 8 | """ 9 | Context with a temporary copy of the given file. 10 | """ 11 | try: 12 | original_pos = source_file.tell() 13 | source_file.seek(0) 14 | temp_file = SpooledTemporaryFile() 15 | temp_file.write(source_file.read()) 16 | temp_file.seek(0) 17 | source_file.seek(original_pos) 18 | yield temp_file 19 | finally: 20 | temp_file.close() 21 | -------------------------------------------------------------------------------- /django/thunderstore/utils/decorators.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | from typing import Callable 3 | 4 | from django.db import transaction 5 | 6 | 7 | def run_after_commit(fn: Callable[..., None]) -> Callable[..., None]: 8 | @wraps(fn) 9 | def wrapper(*args, **kwargs): 10 | transaction.on_commit(lambda: fn(*args, **kwargs)) 11 | 12 | return wrapper 13 | -------------------------------------------------------------------------------- /django/thunderstore/utils/gzip.py: -------------------------------------------------------------------------------- 1 | import gzip 2 | import io 3 | 4 | 5 | def gzip_compress(data: bytes) -> bytes: 6 | with io.BytesIO() as buffer: 7 | with gzip.GzipFile(fileobj=buffer, mode="wb") as gz: 8 | gz.write(data) 9 | return buffer.getvalue() 10 | 11 | 12 | def gzip_decompress(data: bytes) -> bytes: 13 | with io.BytesIO(data) as buffer: 14 | with gzip.GzipFile(fileobj=buffer, mode="rb") as gz: 15 | return gz.read() 16 | -------------------------------------------------------------------------------- /django/thunderstore/utils/iterators.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Iterator, TypeVar, Union 2 | 3 | T = TypeVar("T", bound=Union[Iterable, Iterator]) 4 | 5 | 6 | def print_progress(iterator: T, max: int, frequency: int = 100) -> T: 7 | i = 0 8 | for i, result in enumerate(iterator): 9 | if i % frequency == 0: 10 | print(f"{i + 1} / {max}...") 11 | yield result 12 | print(f"{i + 1} / {max}") 13 | -------------------------------------------------------------------------------- /django/thunderstore/utils/makemigrations.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from django.core.files.storage import Storage 4 | from django.utils.deconstruct import deconstructible 5 | 6 | 7 | @deconstructible 8 | class StubStorage(Storage): 9 | pass 10 | 11 | 12 | def is_migrate_check() -> bool: 13 | return len(sys.argv) >= 2 and ( 14 | (sys.argv[0] == "manage.py" and sys.argv[1] == "makemigrations") 15 | or (sys.argv[0] == "manage.py" and sys.argv[1] == "migrate") 16 | ) 17 | -------------------------------------------------------------------------------- /django/thunderstore/utils/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/utils/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/utils/tests/test_batch.py: -------------------------------------------------------------------------------- 1 | from thunderstore.utils.batch import batch 2 | 3 | 4 | def test_batch(): 5 | data = [1, 2, 3, 4, 5, 6, 7] 6 | batches = list(batch(2, data)) 7 | assert batches == [[1, 2], [3, 4], [5, 6], [7]] 8 | -------------------------------------------------------------------------------- /django/thunderstore/utils/tests/test_contexts.py: -------------------------------------------------------------------------------- 1 | from io import BytesIO 2 | from tempfile import TemporaryFile 3 | 4 | from thunderstore.utils.contexts import TemporarySpooledCopy 5 | 6 | 7 | def test_content_is_copied(): 8 | original_content = b"Lorem ipsum" 9 | 10 | with BytesIO() as original: 11 | original.write(original_content) 12 | 13 | with TemporarySpooledCopy(original) as tmp: 14 | tmp_content = tmp.read() 15 | 16 | assert tmp_content == original_content 17 | 18 | 19 | def test_original_file_is_unaffected(): 20 | position = 123 21 | 22 | with TemporaryFile() as original: 23 | original.truncate(1023) 24 | original.seek(position) 25 | 26 | with TemporarySpooledCopy(original) as tmp: 27 | tmp.seek(999) 28 | 29 | assert not original.closed 30 | assert original.tell() == position 31 | -------------------------------------------------------------------------------- /django/thunderstore/utils/tests/test_decorators.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from django.db import transaction 3 | 4 | from thunderstore.utils.decorators import run_after_commit 5 | 6 | 7 | @pytest.mark.django_db(transaction=True) 8 | def test_utils_run_after_commit(): 9 | calls = [] 10 | 11 | @run_after_commit 12 | def test_fn(): 13 | calls.append(1) 14 | 15 | with transaction.atomic(): 16 | assert len(calls) == 0 17 | test_fn() 18 | assert len(calls) == 0 19 | assert len(calls) == 1 20 | test_fn() 21 | assert len(calls) == 2 22 | -------------------------------------------------------------------------------- /django/thunderstore/utils/tests/test_makemigrations.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from thunderstore.utils.makemigrations import StubStorage, is_migrate_check 4 | 5 | 6 | def test_utils_stub_storage(): 7 | storage = StubStorage() 8 | with pytest.raises(NotImplementedError): 9 | storage.save("test", b"test") 10 | 11 | 12 | def test_utils_is_migrate_check_true(mocker): 13 | mocker.patch("sys.argv", ["manage.py", "migrate"]) 14 | assert is_migrate_check() is True 15 | mocker.patch("sys.argv", ["manage.py", "makemigrations"]) 16 | assert is_migrate_check() is True 17 | 18 | 19 | def test_utils_is_migrate_check_false(mocker): 20 | mocker.patch("sys.argv", ["manage.py", "runserver"]) 21 | assert is_migrate_check() is False 22 | -------------------------------------------------------------------------------- /django/thunderstore/webhooks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/webhooks/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/webhooks/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class WebhooksConfig(AppConfig): 5 | name = "thunderstore.webhooks" 6 | label = "webhooks" 7 | -------------------------------------------------------------------------------- /django/thunderstore/webhooks/forms/__init__.py: -------------------------------------------------------------------------------- 1 | from .webhook import * 2 | -------------------------------------------------------------------------------- /django/thunderstore/webhooks/migrations/0002_add_webhook_exclude_categories.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.4 on 2020-09-08 07:39 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("community", "0002_add_webhook_exclude_categories"), 10 | ("webhooks", "0001_initial"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name="webhook", 16 | name="exclude_categories", 17 | field=models.ManyToManyField( 18 | blank=True, related_name="webhooks", to="community.PackageCategory" 19 | ), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /django/thunderstore/webhooks/migrations/0003_add_webhook_nsfw_exclusion.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.4 on 2020-10-03 09:46 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("webhooks", "0002_add_webhook_exclude_categories"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="webhook", 15 | name="allow_nsfw", 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /django/thunderstore/webhooks/migrations/0005_add_community_site_relation.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.4 on 2020-12-12 15:00 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("community", "0007_add_community_links"), 11 | ("webhooks", "0004_add_webhook_inclusion_filters"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name="webhook", 17 | name="community_site", 18 | field=models.ForeignKey( 19 | blank=True, 20 | null=True, 21 | on_delete=django.db.models.deletion.CASCADE, 22 | related_name="webhooks", 23 | to="community.CommunitySite", 24 | ), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /django/thunderstore/webhooks/migrations/0008_remove_community_site.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2023-01-23 01:30 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("webhooks", "0007_add_community_to_webhook"), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name="webhook", 15 | name="community_site", 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /django/thunderstore/webhooks/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/webhooks/migrations/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/webhooks/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .audit import AuditWebhook 2 | -------------------------------------------------------------------------------- /django/thunderstore/webhooks/tasks/__init__.py: -------------------------------------------------------------------------------- 1 | from .audit import process_audit_event 2 | -------------------------------------------------------------------------------- /django/thunderstore/webhooks/tasks/audit.py: -------------------------------------------------------------------------------- 1 | from celery import shared_task 2 | 3 | from thunderstore.core.settings import CeleryQueues 4 | from thunderstore.core.tasks import celery_post 5 | from thunderstore.webhooks.audit import AuditEvent 6 | from thunderstore.webhooks.models import AuditWebhook 7 | 8 | 9 | @shared_task( 10 | name="thunderstore.webhooks.tasks.process_audit_event", queue=CeleryQueues.Default 11 | ) 12 | def process_audit_event(event_json: str): 13 | event = AuditEvent.parse_raw(event_json) 14 | rendered = AuditWebhook.render_event(event).json( 15 | exclude_unset=True, 16 | exclude_none=True, 17 | ) 18 | webhooks = AuditWebhook.get_for_event(event) 19 | for webhook in webhooks: 20 | celery_post.delay( 21 | webhook.webhook_url, 22 | data=rendered, 23 | headers={"Content-Type": "application/json"}, 24 | ) 25 | -------------------------------------------------------------------------------- /django/thunderstore/webhooks/tasks/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/webhooks/tasks/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/webhooks/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/webhooks/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/wiki/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = "thunderstore.wiki.apps.WikiAppConfig" 2 | -------------------------------------------------------------------------------- /django/thunderstore/wiki/admin/__init__.py: -------------------------------------------------------------------------------- 1 | from .page import * 2 | from .wiki import * 3 | -------------------------------------------------------------------------------- /django/thunderstore/wiki/admin/page.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from thunderstore.wiki.models import WikiPage 4 | 5 | 6 | @admin.register(WikiPage) 7 | class WikiPageAdmin(admin.ModelAdmin): 8 | search_fields = ( 9 | "pk", 10 | "wiki__title", 11 | "title", 12 | ) 13 | list_select_related = ("wiki",) 14 | list_display = ( 15 | "pk", 16 | "wiki", 17 | "title", 18 | "slug", 19 | ) 20 | 21 | def has_change_permission(self, *args, **kwargs) -> bool: 22 | return False 23 | 24 | def has_delete_permission(self, *args, **kwargs) -> bool: 25 | return False 26 | 27 | def has_add_permission(self, *args, **kwargs) -> bool: 28 | return False 29 | -------------------------------------------------------------------------------- /django/thunderstore/wiki/admin/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/wiki/admin/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/wiki/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/wiki/api/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/wiki/api/experimental/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/wiki/api/experimental/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/wiki/api/experimental/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/wiki/api/experimental/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/wiki/api/experimental/tests/test_views.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from rest_framework.test import APIClient 3 | 4 | from thunderstore.wiki.models import WikiPage 5 | 6 | 7 | @pytest.mark.django_db 8 | def test_api_experimental_wiki_page( 9 | api_client: APIClient, 10 | wiki_page: WikiPage, 11 | ): 12 | response = api_client.get( 13 | f"/api/experimental/wiki/page/{wiki_page.pk}/", 14 | HTTP_ACCEPT="application/json", 15 | ) 16 | assert response.status_code == 200 17 | result = response.json() 18 | assert result["markdown_content"] == wiki_page.markdown_content 19 | assert result["title"] == wiki_page.title 20 | assert result["slug"] == f"{wiki_page.full_slug}" 21 | -------------------------------------------------------------------------------- /django/thunderstore/wiki/api/experimental/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from thunderstore.wiki.api.experimental.views import WikiPageApiView 4 | 5 | urls = [ 6 | path( 7 | "page//", 8 | WikiPageApiView.as_view(), 9 | name="page.detail", 10 | ), 11 | ] 12 | -------------------------------------------------------------------------------- /django/thunderstore/wiki/api/experimental/views.py: -------------------------------------------------------------------------------- 1 | from drf_yasg.utils import swagger_auto_schema 2 | from rest_framework.generics import RetrieveAPIView 3 | 4 | from thunderstore.wiki.api.experimental.serializers import WikiPageSerializer 5 | from thunderstore.wiki.models import WikiPage 6 | 7 | 8 | class WikiPageApiView(RetrieveAPIView): 9 | queryset = WikiPage.objects.all() 10 | serializer_class = WikiPageSerializer 11 | 12 | @swagger_auto_schema( 13 | operation_id="experimental_wiki_page_read", 14 | operation_summary="Get a wiki page", 15 | operation_description="Returns a wiki page object", 16 | tags=["wiki"], 17 | ) 18 | def get(self, *args, **kwargs): 19 | return super().get(*args, **kwargs) 20 | -------------------------------------------------------------------------------- /django/thunderstore/wiki/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class WikiAppConfig(AppConfig): 5 | name = "thunderstore.wiki" 6 | label = "thunderstore_wiki" 7 | verbose_name = "thunderstore wiki" 8 | -------------------------------------------------------------------------------- /django/thunderstore/wiki/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from factory.django import DjangoModelFactory 3 | 4 | from thunderstore.wiki.models import Wiki, WikiPage 5 | 6 | 7 | class WikiFactory(DjangoModelFactory): 8 | class Meta: 9 | model = Wiki 10 | 11 | title = factory.Sequence(lambda n: f"Test Wiki {n}") 12 | 13 | 14 | class WikiPageFactory(DjangoModelFactory): 15 | class Meta: 16 | model = WikiPage 17 | 18 | wiki = factory.SubFactory(WikiFactory) 19 | title = factory.Sequence(lambda n: f"Test Page {n}") 20 | markdown_content = factory.Faker("sentence") 21 | -------------------------------------------------------------------------------- /django/thunderstore/wiki/migrations/0002_add_index.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2023-03-27 04:01 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("thunderstore_wiki", "0001_initial"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name="wikipage", 15 | options={"ordering": ("wiki", "slug")}, 16 | ), 17 | migrations.AddIndex( 18 | model_name="wikipage", 19 | index=models.Index( 20 | fields=["wiki", "slug"], name="thunderstor_wiki_id_9975db_idx" 21 | ), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /django/thunderstore/wiki/migrations/0003_add_index.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2023-03-27 05:36 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("thunderstore_wiki", "0002_add_index"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name="wiki", 15 | options={"ordering": ("datetime_updated",)}, 16 | ), 17 | migrations.AddIndex( 18 | model_name="wiki", 19 | index=models.Index( 20 | fields=["datetime_updated"], name="thunderstor_datetim_967b06_idx" 21 | ), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /django/thunderstore/wiki/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/wiki/migrations/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/wiki/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .wiki import * 2 | -------------------------------------------------------------------------------- /django/thunderstore/wiki/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/django/thunderstore/wiki/tests/__init__.py -------------------------------------------------------------------------------- /django/thunderstore/wiki/tests/test_models.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from thunderstore.wiki.factories import WikiPageFactory 4 | from thunderstore.wiki.models import Wiki, WikiPage 5 | 6 | 7 | @pytest.mark.django_db 8 | def test_wiki_datetime_updated_on_page_save( 9 | wiki: Wiki, 10 | wiki_page: WikiPage, 11 | ) -> None: 12 | ts1 = wiki.datetime_updated 13 | wiki_page.save() 14 | wiki.refresh_from_db() 15 | ts2 = wiki.datetime_updated 16 | assert ts1 < ts2 17 | WikiPageFactory(wiki=wiki) 18 | wiki.refresh_from_db() 19 | ts3 = wiki.datetime_updated 20 | assert ts2 < ts3 21 | wiki_page.delete() 22 | wiki.refresh_from_db() 23 | ts4 = wiki.datetime_updated 24 | assert ts3 < ts4 25 | -------------------------------------------------------------------------------- /docker/builder.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12-alpine 2 | 3 | RUN mkdir -p /home/node/app/node_modules && mkdir /home/node/app/build && chown -R node:node /home/node/app 4 | VOLUME /home/node/app/build 5 | WORKDIR /home/node/app 6 | COPY --chown=node:node ./builder/package.json ./builder/yarn.lock ./ 7 | USER node 8 | RUN yarn install --frozen-lockfile 9 | COPY --chown=node:node ./builder ./ 10 | 11 | ENTRYPOINT ["yarn", "run"] 12 | CMD ["watch"] 13 | -------------------------------------------------------------------------------- /loadtest/README.md: -------------------------------------------------------------------------------- 1 | # Loadtesting 2 | 3 | This is a simple tool that can be used to test the site locally or otherwise. 4 | 5 | Run `poetry install` to install, and then call `main.py --help` for usage 6 | instructions 7 | 8 | You probably want to run the server via gunicorn when using this 9 | 10 | # Locust 11 | 12 | A better load testing tool is locust, which can be run with 13 | 14 | ``` 15 | locust -f locustfile.py 16 | ``` 17 | -------------------------------------------------------------------------------- /loadtest/loadtest/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunderstore-io/Thunderstore/f3f3d1a7eb71cbc99048d1671cfc2fb9dedee533/loadtest/loadtest/__init__.py -------------------------------------------------------------------------------- /loadtest/locustfile.py: -------------------------------------------------------------------------------- 1 | from random import choice, random 2 | 3 | from locust import HttpUser, task 4 | 5 | 6 | BOOLS = ["deprecated", "nsfw"] 7 | ORDERING = ["last-updated", "most-downloaded", "newest", "top-rated"] 8 | QUERIES = ["character", "ror2", "unity"] 9 | SECTIONS = ["mods", "modpacks"] 10 | 11 | 12 | class AnonymousUser(HttpUser): 13 | @task 14 | def index(self): 15 | url = f"/?ordering={choice(ORDERING)}" 16 | url += f"§ion={choice(SECTIONS)}" if random() < 0.5 else "" 17 | url += f"&q={choice(QUERIES)}" if random() < 0.5 else "" 18 | url += f"&{choice(BOOLS)}=on" if random() < 0.5 else "" 19 | self.client.get(url) 20 | -------------------------------------------------------------------------------- /loadtest/main.py: -------------------------------------------------------------------------------- 1 | from loadtest.cli import execute_from_command_line 2 | 3 | 4 | def main(): 5 | execute_from_command_line() 6 | 7 | 8 | if __name__ == "__main__": 9 | main() 10 | -------------------------------------------------------------------------------- /loadtest/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "loadtest" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Mythic "] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.8" 9 | aiohttp = "^3.7.4" 10 | click = "^7.1.2" 11 | locust = "^1.4.3" 12 | 13 | [tool.poetry.dev-dependencies] 14 | 15 | [build-system] 16 | requires = ["poetry-core>=1.0.0"] 17 | build-backend = "poetry.core.masonry.api" 18 | 19 | [tool.isort] 20 | profile = "black" 21 | known_third_party = ["django"] 22 | 23 | [tool.black] 24 | target-version = ["py38"] 25 | exclude = ''' 26 | ( 27 | /( 28 | \.git 29 | | \.mypy_cache 30 | | \.venv 31 | | \.vscode 32 | | \.idea 33 | )/ 34 | ) 35 | ''' 36 | -------------------------------------------------------------------------------- /minio/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=linux/amd64 alpine as builder 2 | 3 | RUN apk add --no-cache wget 4 | RUN wget https://dl.min.io/client/mc/release/linux-amd64/mc -O /usr/bin/mc && \ 5 | chmod +x /usr/bin/mc 6 | 7 | FROM --platform=linux/amd64 minio/minio:RELEASE.2021-06-17T00-10-46Z@sha256:e6755f3359281f3a3032c26cdfa450945a5d88bdbce5f68a05bf96d900bf222e 8 | 9 | RUN microdnf install netcat 10 | 11 | COPY --from=builder /usr/bin/mc /usr/bin/mc 12 | COPY ./minio/thunderstore-entrypoint.sh /usr/bin/thunderstore-entrypoint.sh 13 | 14 | EXPOSE 9000 15 | 16 | ENTRYPOINT ["/usr/bin/thunderstore-entrypoint.sh"] 17 | 18 | VOLUME ["/data"] 19 | 20 | CMD ["minio"] 21 | -------------------------------------------------------------------------------- /minio/thunderstore-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | sh /usr/bin/docker-entrypoint.sh "$@" & 3 | MINIO_PID=$! 4 | while ! nc -z minio 9000; do echo 'Wait minio to startup...' && sleep 0.1; done; 5 | sleep 2 6 | mc alias set thunderstore http://127.0.0.1:9000 $MINIO_ROOT_USER $MINIO_ROOT_PASSWORD; 7 | mc mb thunderstore/thunderstore; 8 | mc mb thunderstore/test; 9 | mc anonymous set download thunderstore/thunderstore; 10 | mc anonymous set download thunderstore/test; 11 | kill $MINIO_PID 12 | wait $MINIO_PID 13 | source /usr/bin/docker-entrypoint.sh "$@" 14 | -------------------------------------------------------------------------------- /pgbouncer.ini: -------------------------------------------------------------------------------- 1 | ################## Auto generated ################## 2 | [databases] 3 | django = host=db port=5432 user=django 4 | test_django = host=db port=5432 user=django 5 | 6 | [pgbouncer] 7 | listen_addr = 0.0.0.0 8 | listen_port = 5432 9 | unix_socket_dir = 10 | user = postgres 11 | auth_file = /etc/pgbouncer/userlist.txt 12 | auth_type = md5 13 | pool_mode = transaction 14 | ignore_startup_parameters = extra_float_digits 15 | 16 | # Log settings 17 | admin_users = postgres 18 | 19 | # Connection sanity checks, timeouts 20 | 21 | # TLS settings 22 | 23 | # Dangerous timeouts 24 | ################## end file ################## 25 | --------------------------------------------------------------------------------