Repository: LibraryOfCongress/concordia Branch: main Commit: 80cc6e1b2573 Files: 673 Total size: 3.0 MB Directory structure: gitextract_frv4ewgf/ ├── .cfnlintrc.yaml ├── .dockerignore ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ ├── dependabot.yml │ └── workflows/ │ ├── black.yml │ ├── build.yml │ ├── codeql.yml │ ├── db_ops.yml │ ├── dev-main-deploy.yml │ ├── feature-branch-deploy.yml │ ├── pip-audit.yml │ ├── prod-deploy.yml │ ├── renew_coverage.yml │ ├── stage-hotfix-rel-deploy.yml │ ├── stage-image-refresh.yml │ ├── stage-release-deploy.yml │ ├── test-main-deploy.yml │ └── test.yml ├── .gitignore ├── Dockerfile ├── LICENSE.md ├── Loadtesting.md ├── MANIFEST.in ├── Makefile ├── Pipfile ├── README.md ├── build_containers.sh ├── celerybeat/ │ ├── Dockerfile │ └── entrypoint.sh ├── cloudformation/ │ ├── LICENSE │ ├── NOTICE │ ├── README.md │ ├── add_cloudflare_ips_to_sgs.py │ ├── create_secrets.sh │ ├── featurebranch.yaml │ ├── images/ │ │ └── architecture-overview.graffle/ │ │ └── data.plist │ ├── infrastructure/ │ │ ├── bastion-hosts.yaml │ │ ├── data-load.yaml │ │ ├── elasticache-feature.yaml │ │ ├── elasticache.yaml │ │ ├── elasticsearch.yaml │ │ ├── fargate-cluster.yaml │ │ ├── fargate-featurebranch.yaml │ │ ├── jenkins-server.yaml │ │ ├── network-acl.yaml │ │ ├── opensearch.yaml │ │ ├── rds.yaml │ │ ├── search-proxy-task.yaml │ │ ├── security-groups.yaml │ │ └── vpc.yaml │ ├── master.yaml │ ├── stack_drift.sh │ ├── sync_templates.sh │ └── tests/ │ └── validate-templates.sh ├── concordia/ │ ├── __init__.py │ ├── admin/ │ │ ├── __init__.py │ │ ├── actions.py │ │ ├── filters.py │ │ ├── forms.py │ │ ├── utils.py │ │ └── views.py │ ├── admin_site.py │ ├── api/ │ │ ├── __init__.py │ │ └── schemas.py │ ├── api_views.py │ ├── apps.py │ ├── asgi.py │ ├── authentication_backends.py │ ├── celery.py │ ├── consumers.py │ ├── context_processors.py │ ├── contextmanagers.py │ ├── converters.py │ ├── decorators.py │ ├── documents.py │ ├── exceptions.py │ ├── forms.py │ ├── logging.py │ ├── maintenance.py │ ├── management/ │ │ ├── __init__.py │ │ └── commands/ │ │ ├── __init__.py │ │ ├── calculate_difficulty_values.py │ │ ├── create_load_test_fixtures.py │ │ ├── ensure_initial_site_configuration.py │ │ ├── import_site_reports.py │ │ ├── prepare_load_test_db.py │ │ └── print_frontend_test_urls.py │ ├── middleware.py │ ├── migrations/ │ │ ├── 0001_initial.py │ │ ├── 0001_squashed_0040_remove_campaign_is_active.py │ │ ├── 0002_auto_20181004_1848.py │ │ ├── 0003_auto_20181004_2103.py │ │ ├── 0004_auto_20181010_1715.py │ │ ├── 0005_campaign_short_description.py │ │ ├── 0006_campaignresource.py │ │ ├── 0007_thumbnail_images.py │ │ ├── 0008_auto_20181015_1711.py │ │ ├── 0009_project_description.py │ │ ├── 0010_auto_20181021_1659.py │ │ ├── 0010_auto_20181022_1530.py │ │ ├── 0011_auto_20181022_1532.py │ │ ├── 0012_merge_20181022_1554.py │ │ ├── 0013_auto_20181031_1305.py │ │ ├── 0014_auto_20181115_1411.py │ │ ├── 0015_auto_20181115_1436.py │ │ ├── 0016_auto_20181115_1803.py │ │ ├── 0017_change_transcription_supersedes_related_name.py │ │ ├── 0018_auto_20181128_1611.py │ │ ├── 0018_simplepage.py │ │ ├── 0019_merge_20181128_1715.py │ │ ├── 0020_auto_20181128_1718.py │ │ ├── 0021_sitereport.py │ │ ├── 0022_auto_20181211_1310.py │ │ ├── 0023_auto_20190130_1555.py │ │ ├── 0024_add_site_report_ordering.py │ │ ├── 0024_auto_20190211_1420.py │ │ ├── 0025_auto_20190329_1705.py │ │ ├── 0025_unicode_slugs.py │ │ ├── 0026_update_published_field_definition.py │ │ ├── 0027_merge_20190423_1657.py │ │ ├── 0028_asset_year.py │ │ ├── 0029_assettranscriptionreservation_reservation_token.py │ │ ├── 0030_auto_20190503_1559.py │ │ ├── 0031_auto_20190509_1142.py │ │ ├── 0032_topic_ordering.py │ │ ├── 0033_simple_content_blocks.py │ │ ├── 0034_auto_20190627_1438.py │ │ ├── 0035_auto_20190627_1455.py │ │ ├── 0036_auto_20190703_1203.py │ │ ├── 0037_carouselslide.py │ │ ├── 0038_sitereport_topic.py │ │ ├── 0039_auto_20200129_1536.py │ │ ├── 0040_auto_20200130_1756.py │ │ ├── 0041_auto_20200203_1351.py │ │ ├── 0042_auto_20200316_1623.py │ │ ├── 0043_auto_20200323_1729.py │ │ ├── 0044_auto_20200323_1827.py │ │ ├── 0045_auto_20200323_1832.py │ │ ├── 0046_auto_20200323_1907.py │ │ ├── 0047_auto_20200324_1103.py │ │ ├── 0048_auto_20200324_1820.py │ │ ├── 0049_auto_20200324_2004.py │ │ ├── 0050_auto_20210920_1544.py │ │ ├── 0051_asset_storage_image.py │ │ ├── 0052_auto_20220531_1331.py │ │ ├── 0053_banner.py │ │ ├── 0054_banner_active.py │ │ ├── 0055_campaign_status.py │ │ ├── 0056_auto_20220922_1508.py │ │ ├── 0057_resource_resource_type.py │ │ ├── 0058_banner_slug.py │ │ ├── 0059_resourcefile.py │ │ ├── 0060_alter_resourcefile_resource.py │ │ ├── 0061_auto_20230201_1453.py │ │ ├── 0061_sitereport_registered_contributors.py │ │ ├── 0062_resourcefile_updated_on.py │ │ ├── 0062_userretiredcampaign.py │ │ ├── 0063_banner_alert_status.py │ │ ├── 0064_alter_banner_alert_status.py │ │ ├── 0065_alter_userretiredcampaign_unique_together.py │ │ ├── 0066_auto_20230217_1302.py │ │ ├── 0066_campaignretirementprogress.py │ │ ├── 0067_alter_campaignretirementprogress_campaign.py │ │ ├── 0068_campaignretirementprogress_complete.py │ │ ├── 0069_merge_20230224_1446.py │ │ ├── 0070_alter_campaign_options.py │ │ ├── 0071_auto_20230306_1456.py │ │ ├── 0072_merge_20230313_1047.py │ │ ├── 0073_auto_20230314_1327.py │ │ ├── 0074_auto_20230314_1341.py │ │ ├── 0075_auto_20230327_1333.py │ │ ├── 0076_sitereport_report_name.py │ │ ├── 0077_alter_sitereport_report_name.py │ │ ├── 0078_alter_sitereport_report_name.py │ │ ├── 0079_auto_20230601_1234.py │ │ ├── 0080_auto_20230602_0920.py │ │ ├── 0081_sitereport_review_actions.py │ │ ├── 0082_delete_userretiredcampaign.py │ │ ├── 0083_sitereport_daily_active_users.py │ │ ├── 0084_rename_review_actions_sitereport_daily_review_actions.py │ │ ├── 0085_auto_20231016_1432.py │ │ ├── 0086_auto_20231215_1311.py │ │ ├── 0087_auto_20240213_0756.py │ │ ├── 0088_alter_simplepage_body.py │ │ ├── 0089_campaign_image_alt_text.py │ │ ├── 0090_auto_20240408_1334.py │ │ ├── 0091_guide_simple_page.py │ │ ├── 0092_auto_20240509_1522.py │ │ ├── 0093_asset_campaign.py │ │ ├── 0094_alter_asset_campaign.py │ │ ├── 0095_transcription_rolled_back_and_more.py │ │ ├── 0096_transcription_source.py │ │ ├── 0097_alter_sitereport_options_userprofile_review_count_and_more.py │ │ ├── 0098_userprofile_create_and_population.py │ │ ├── 0099_alter_campaign_display_on_homepage_and_more.py │ │ ├── 0100_researchcenter.py │ │ ├── 0101_auto_20241119_1215.py │ │ ├── 0102_campaign_research_centers.py │ │ ├── 0103_alter_item_title.py │ │ ├── 0104_nexttranscribabletopicasset_and_more.py │ │ ├── 0105_nextreviewablecampaignasset_concordia_n_transcr_aafdba_gin_and_more.py │ │ ├── 0106_alter_nextreviewablecampaignasset_options_and_more.py │ │ ├── 0107_alter_nextreviewablecampaignasset_options_and_more.py │ │ ├── 0108_add_next_asset_cache_periodic_task.py │ │ ├── 0109_alter_nextreviewablecampaignasset_asset_and_more.py │ │ ├── 0110_remove_asset_media_url_alter_asset_storage_image.py │ │ ├── 0111_auto_20250428_1023.py │ │ ├── 0112_projecttopic_url_filter_alter_projecttopic_id.py │ │ ├── 0113_create_asset_status_periodic_task.py │ │ ├── 0114_create_daily_activity_periodic_task.py │ │ ├── 0115_alter_asset_storage_image_alter_banner_link_and_more.py │ │ ├── 0116_item_thumbnail_image.py │ │ ├── 0117_alter_projecttopic_options_projecttopic_ordering.py │ │ ├── 0118_asset_concordia_a_item_id_f10916_idx_and_more.py │ │ ├── 0119_remove_asset_concordia_a_id_137ca8_idx_and_more.py │ │ ├── 0120_sitereport_assets_started.py │ │ ├── 0121_keymetricsreport.py │ │ ├── 0122_alter_item_title.py │ │ ├── 0123_alter_campaignretirementprogress_options.py │ │ ├── 0124_update_periodic_task_paths.py │ │ ├── 0125_update_userprofile_tasks.py │ │ ├── 0126_concordiafile_helpfullink_remove_resource_campaign_and_more.py │ │ ├── 0127_alter_campaignretirementprogress_options_and_more.py │ │ ├── 0128_alter_campaignretirementprogress_options.py │ │ └── __init__.py │ ├── models.py │ ├── parser.py │ ├── passwords/ │ │ ├── LICENSE │ │ ├── __init__.py │ │ └── validators.py │ ├── routing.py │ ├── secrets.py │ ├── settings_dev.py │ ├── settings_docker.py │ ├── settings_ecs.py │ ├── settings_loadtest.py │ ├── settings_local_test.py │ ├── settings_template.py │ ├── settings_test.py │ ├── signals/ │ │ ├── __init__.py │ │ ├── handlers.py │ │ └── signals.py │ ├── static/ │ │ ├── admin/ │ │ │ ├── custom-inline.js │ │ │ └── editor-preview.js │ │ ├── js/ │ │ │ └── src/ │ │ │ ├── about-accordions.js │ │ │ ├── asset-reservation.js │ │ │ ├── banner.js │ │ │ ├── base.js │ │ │ ├── campaign-selection.js │ │ │ ├── contribute.js │ │ │ ├── filter-assets.js │ │ │ ├── guide.js │ │ │ ├── homepage-carousel.js │ │ │ ├── modules/ │ │ │ │ ├── accessible-colors.js │ │ │ │ ├── chroma-esm.js │ │ │ │ ├── concordia-visualization.js │ │ │ │ ├── quick-tips.js │ │ │ │ ├── turnstile.js │ │ │ │ └── visualization-errors.js │ │ │ ├── ocr.js │ │ │ ├── password-validation.js │ │ │ ├── profile-fields.js │ │ │ ├── quick-tips-setup.js │ │ │ ├── recent-pages.js │ │ │ ├── viewer-split.js │ │ │ ├── viewer.js │ │ │ └── visualizations/ │ │ │ ├── asset-status-by-campaign.js │ │ │ ├── asset-status-overview.js │ │ │ └── daily-activity.js │ │ ├── scss/ │ │ │ ├── _variables.scss │ │ │ └── base.scss │ │ └── vendor/ │ │ └── jquery.cookie.js │ ├── storage.py │ ├── storage_backends.py │ ├── tasks/ │ │ ├── __init__.py │ │ ├── assets.py │ │ ├── blog.py │ │ ├── housekeeping.py │ │ ├── next_asset/ │ │ │ ├── __init__.py │ │ │ ├── renew.py │ │ │ ├── reviewable.py │ │ │ └── transcribable.py │ │ ├── reports/ │ │ │ ├── __init__.py │ │ │ ├── backfill.py │ │ │ ├── key_metrics.py │ │ │ └── sitereport.py │ │ ├── reservations.py │ │ ├── retirement.py │ │ ├── search_index.py │ │ ├── thumbnails.py │ │ ├── unusualactivity.py │ │ ├── useractivity.py │ │ └── visualizations.py │ ├── templates/ │ │ ├── 404.html │ │ ├── 429.html │ │ ├── 500.html │ │ ├── 503.html │ │ ├── account/ │ │ │ ├── account_deletion.html │ │ │ ├── email_reconfirmation_failed.html │ │ │ └── profile.html │ │ ├── admin/ │ │ │ ├── auth/ │ │ │ │ └── user/ │ │ │ │ └── change_form.html │ │ │ ├── base_site.html │ │ │ ├── bulk_change.html │ │ │ ├── bulk_import.html │ │ │ ├── bulk_review.html │ │ │ ├── celery_task.html │ │ │ ├── clear_cache.html │ │ │ ├── concordia/ │ │ │ │ ├── asset/ │ │ │ │ │ ├── change_form.html │ │ │ │ │ └── change_list.html │ │ │ │ ├── campaign/ │ │ │ │ │ ├── change_form.html │ │ │ │ │ └── retire.html │ │ │ │ ├── item/ │ │ │ │ │ └── change_form.html │ │ │ │ ├── project/ │ │ │ │ │ ├── change_form.html │ │ │ │ │ └── item_import.html │ │ │ │ ├── simplepage/ │ │ │ │ │ └── change_form.html │ │ │ │ └── transcription/ │ │ │ │ └── change_form.html │ │ │ ├── index.html │ │ │ ├── long_name_filter.html │ │ │ ├── process_bagit.html │ │ │ └── project_level_export.html │ │ ├── base.html │ │ ├── django_registration/ │ │ │ ├── activation_complete.html │ │ │ ├── activation_email_body.txt │ │ │ ├── activation_email_subject.txt │ │ │ ├── activation_failed.html │ │ │ ├── registration_closed.html │ │ │ ├── registration_complete.html │ │ │ └── registration_form.html │ │ ├── documents/ │ │ │ └── service_letter.html │ │ ├── emails/ │ │ │ ├── delete_account_body.txt │ │ │ ├── delete_account_subject.txt │ │ │ ├── email_reconfirmation_body.txt │ │ │ ├── email_reconfirmation_subject.txt │ │ │ ├── unusual_activity.html │ │ │ ├── unusual_activity.txt │ │ │ ├── welcome_email_body.html │ │ │ ├── welcome_email_body.txt │ │ │ └── welcome_email_subject.txt │ │ ├── error.html │ │ ├── forms/ │ │ │ └── widgets/ │ │ │ ├── email.html │ │ │ └── turnstile_widget.html │ │ ├── fragments/ │ │ │ ├── _filter-buttons.html │ │ │ ├── _modal_footer.html │ │ │ ├── activity-filter-sort.html │ │ │ ├── codemirror.html │ │ │ ├── common-stylesheets.html │ │ │ ├── featured_blog_posts.html │ │ │ ├── recent-pages.html │ │ │ ├── sharing-button-group.html │ │ │ ├── standard-pagination.html │ │ │ ├── transcription-progress-bar.html │ │ │ ├── transcription-progress-row.html │ │ │ └── transcription-status-filters.html │ │ ├── home.html │ │ ├── registration/ │ │ │ ├── activate.html │ │ │ ├── login.html │ │ │ ├── password_change_done.html │ │ │ ├── password_change_form.html │ │ │ ├── password_reset_complete.html │ │ │ ├── password_reset_confirm.html │ │ │ ├── password_reset_done.html │ │ │ ├── password_reset_email.html │ │ │ ├── password_reset_form.html │ │ │ └── password_reset_subject.txt │ │ ├── static-page.html │ │ └── transcriptions/ │ │ ├── asset_detail/ │ │ │ ├── asset_reservation_failure_modal.html │ │ │ ├── editor.html │ │ │ ├── error_modal.html │ │ │ ├── guide.html │ │ │ ├── language_selection_modal.html │ │ │ ├── navigation.html │ │ │ ├── nothing_to_transcribe_modal.html │ │ │ ├── ocr_help_modal.html │ │ │ ├── ocr_transcription_modal.html │ │ │ ├── quick_tips_modal.html │ │ │ ├── review_accepted_modal.html │ │ │ ├── successful_submission_modal.html │ │ │ ├── tags.html │ │ │ ├── viewer.html │ │ │ └── viewer_filters.html │ │ ├── asset_detail.html │ │ ├── campaign_detail.html │ │ ├── campaign_detail_completed.html │ │ ├── campaign_detail_retired.html │ │ ├── campaign_list.html │ │ ├── campaign_list_small_blocks.html │ │ ├── campaign_report.html │ │ ├── campaign_small_block.html │ │ ├── campaign_topic_list.html │ │ ├── completed_campaigns_section.html │ │ ├── item_detail.html │ │ ├── project_detail.html │ │ ├── topic_detail.html │ │ └── transcription.html │ ├── templatetags/ │ │ ├── __init__.py │ │ ├── concordia_filtering_tags.py │ │ ├── concordia_media_tags.py │ │ ├── concordia_querystring.py │ │ ├── concordia_sharing_tags.py │ │ ├── concordia_text_tags.py │ │ ├── custom_math.py │ │ ├── group_list.py │ │ ├── reject_filter.py │ │ ├── truncation.py │ │ └── visualization.py │ ├── tests/ │ │ ├── README.md │ │ ├── __init__.py │ │ ├── axe.py │ │ ├── data/ │ │ │ └── site_reports.csv │ │ ├── test_account_views.py │ │ ├── test_admin.py │ │ ├── test_admin_actions.py │ │ ├── test_admin_filters.py │ │ ├── test_admin_forms.py │ │ ├── test_admin_views.py │ │ ├── test_api_views.py │ │ ├── test_authentication.py │ │ ├── test_celery.py │ │ ├── test_consumers.py │ │ ├── test_contextmanagers.py │ │ ├── test_decorators.py │ │ ├── test_fields.py │ │ ├── test_logging.py │ │ ├── test_maintenance.py │ │ ├── test_management_commands.py │ │ ├── test_models.py │ │ ├── test_parser.py │ │ ├── test_registration_views.py │ │ ├── test_s3.py │ │ ├── test_selenium.py │ │ ├── test_sentry.py │ │ ├── test_signals.py │ │ ├── test_tasks_assets.py │ │ ├── test_tasks_blog.py │ │ ├── test_tasks_housekeeping.py │ │ ├── test_tasks_next_asset.py │ │ ├── test_tasks_reports_backfill.py │ │ ├── test_tasks_reports_key_metrics.py │ │ ├── test_tasks_reports_sitereport.py │ │ ├── test_tasks_retirement.py │ │ ├── test_tasks_search_index.py │ │ ├── test_tasks_thumbnails.py │ │ ├── test_tasks_unusualactivity.py │ │ ├── test_tasks_useractivity.py │ │ ├── test_tasks_visualizations.py │ │ ├── test_templatetags.py │ │ ├── test_top_level_views.py │ │ ├── test_utils_celery.py │ │ ├── test_utils_logging.py │ │ ├── test_utils_next_asset_reviewable_campaign.py │ │ ├── test_utils_next_asset_reviewable_topic.py │ │ ├── test_utils_next_asset_transcribable_campaign.py │ │ ├── test_utils_next_asset_transcribable_topic.py │ │ ├── test_validators.py │ │ ├── test_view_decorators.py │ │ ├── test_views.py │ │ ├── test_views_asset_reservation.py │ │ ├── test_views_redirect_next_reviewable.py │ │ ├── test_views_redirect_next_transcribable.py │ │ ├── test_views_tags.py │ │ ├── test_views_topics.py │ │ ├── test_views_transcription_review.py │ │ ├── test_views_transcription_save.py │ │ ├── test_views_transcription_submit.py │ │ ├── test_views_utils.py │ │ ├── test_widgets.py │ │ └── utils.py │ ├── turnstile/ │ │ ├── LICENSE │ │ ├── __init__.py │ │ ├── context_processors.py │ │ ├── fields.py │ │ └── widgets.py │ ├── urls.py │ ├── utils/ │ │ ├── __init__.py │ │ ├── celery.py │ │ ├── constants.py │ │ └── next_asset/ │ │ ├── __init__.py │ │ ├── reviewable/ │ │ │ ├── __init__.py │ │ │ ├── campaign.py │ │ │ └── topic.py │ │ └── transcribable/ │ │ ├── __init__.py │ │ ├── campaign.py │ │ └── topic.py │ ├── validators.py │ ├── version.py │ ├── views/ │ │ ├── README.md │ │ ├── __init__.py │ │ ├── accounts.py │ │ ├── ajax.py │ │ ├── assets.py │ │ ├── campaigns.py │ │ ├── decorators.py │ │ ├── items.py │ │ ├── maintenance_mode.py │ │ ├── projects.py │ │ ├── rate_limit.py │ │ ├── simple_pages.py │ │ ├── topics.py │ │ ├── utils.py │ │ └── visualizations.py │ ├── widgets.py │ └── wsgi.py ├── configuration/ │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── management/ │ │ ├── __init__.py │ │ └── commands/ │ │ ├── __init__.py │ │ └── configcache.py │ ├── migrations/ │ │ ├── 0001_initial.py │ │ ├── 0002_populate_configurations.py │ │ ├── 0003_populate_retry_configurations.py │ │ ├── 0004_alter_configuration_options.py │ │ ├── 0005_alter_configuration_data_type.py │ │ ├── 0006_populate_next_asset_rate_limit.py │ │ └── __init__.py │ ├── models.py │ ├── signals.py │ ├── templates/ │ │ └── admin/ │ │ └── configuration_confirm_update.html │ ├── templatetags/ │ │ ├── __init__.py │ │ └── configuration_tags.py │ ├── tests/ │ │ ├── __init__.py │ │ ├── test_admin.py │ │ ├── test_models.py │ │ ├── test_signals.py │ │ ├── test_templatetags.py │ │ ├── test_utils.py │ │ └── test_validation.py │ ├── utils.py │ ├── validation.py │ └── views.py ├── db_scripts/ │ ├── Dockerfile │ ├── dump.sh │ └── restore.sh ├── development/ │ ├── Containerfile │ ├── README.md │ └── compose.yml ├── docker-compose.yml ├── docs/ │ ├── accessibility-goals.md │ ├── accessibility-techniques.md │ ├── design-principles.md │ ├── for-developers.md │ └── how-we-work.md ├── entrypoint.sh ├── exporter/ │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── exceptions.py │ ├── migrations/ │ │ └── __init__.py │ ├── models.py │ ├── tabular_export/ │ │ ├── admin.py │ │ └── core.py │ ├── templates/ │ │ └── admin/ │ │ └── exporter/ │ │ └── unacceptable_character_report.html │ ├── tests/ │ │ ├── __init__.py │ │ ├── test_exceptions.py │ │ ├── test_tabular_export.py │ │ ├── test_utils.py │ │ └── test_views.py │ ├── utils.py │ └── views.py ├── fixtures/ │ └── original-static-pages.json ├── frontend/ │ ├── .gitignore │ ├── README.md │ ├── eslint.config.js │ ├── index.html │ ├── package.json │ ├── src/ │ │ ├── App.jsx │ │ ├── ViewerSplit.jsx │ │ ├── config.js │ │ ├── editor/ │ │ │ ├── Buttons.jsx │ │ │ ├── Editor.jsx │ │ │ ├── Header.jsx │ │ │ ├── StatusMessages.jsx │ │ │ ├── TranscriptionTextarea.jsx │ │ │ └── buttons/ │ │ │ ├── Editable.jsx │ │ │ ├── Redo.jsx │ │ │ ├── Review.jsx │ │ │ ├── Save.jsx │ │ │ ├── Submit.jsx │ │ │ └── Undo.jsx │ │ ├── main.jsx │ │ ├── ocr/ │ │ │ ├── Button.jsx │ │ │ ├── ConfirmModal.jsx │ │ │ ├── Handler.jsx │ │ │ ├── HelpModal.jsx │ │ │ ├── LanguageModal.jsx │ │ │ └── Section.jsx │ │ └── viewer/ │ │ ├── Controls.jsx │ │ ├── FilterTabNav.jsx │ │ ├── GammaFilterForm.jsx │ │ ├── ImageFilters.jsx │ │ ├── InvertFilterForm.jsx │ │ ├── KeyboardHelpModal.jsx │ │ ├── KeyboardShortcutRow.jsx │ │ ├── ThresholdFilterForm.jsx │ │ └── Viewer.jsx │ └── vite.config.js ├── importer/ │ ├── Dockerfile │ ├── README.md │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── celery.py │ ├── config.py │ ├── entrypoint.sh │ ├── exceptions.py │ ├── migrations/ │ │ ├── 0001_initial.py │ │ ├── 0001_squashed_0015_auto_20180925_1851.py │ │ ├── 0002_auto_20180709_0833.py │ │ ├── 0003_auto_20180709_0933.py │ │ ├── 0004_auto_20180812_1007.py │ │ ├── 0005_auto_20180816_1702.py │ │ ├── 0006_auto_20180912_0229.py │ │ ├── 0007_auto_20180917_1654.py │ │ ├── 0008_campaigntaskdetails_project.py │ │ ├── 0009_convert_project_text_to_keys.py │ │ ├── 0010_auto_20180920_2013.py │ │ ├── 0011_auto_20180922_0208.py │ │ ├── 0012_auto_20180923_0231.py │ │ ├── 0013_auto_20180924_1318.py │ │ ├── 0014_auto_20180924_1943.py │ │ ├── 0015_auto_20180925_1851.py │ │ ├── 0016_importitem_failure_reason_and_more.py │ │ ├── 0017_importitem_failure_history_importitem_retry_count_and_more.py │ │ ├── 0018_importitem_status_history_and_more.py │ │ ├── 0019_alter_downloadassetimagejob_batch_and_more.py │ │ ├── 0020_alter_downloadassetimagejob_unique_together_and_more.py │ │ └── __init__.py │ ├── models.py │ ├── setup.py │ ├── tasks/ │ │ ├── __init__.py │ │ ├── assets.py │ │ ├── collections.py │ │ ├── decorators.py │ │ ├── images.py │ │ └── items.py │ ├── tests/ │ │ ├── README.md │ │ ├── __init__.py │ │ ├── test_admin.py │ │ ├── test_celery.py │ │ ├── test_models.py │ │ ├── test_tasks_assets.py │ │ ├── test_tasks_collections.py │ │ ├── test_tasks_core.py │ │ ├── test_tasks_decorators.py │ │ ├── test_tasks_images.py │ │ ├── test_tasks_items.py │ │ ├── test_utils.py │ │ └── utils.py │ └── utils/ │ ├── __init__.py │ ├── excel.py │ └── verify_images.py ├── load_test.sh ├── locustfile.py ├── manage.py ├── package.json ├── postgresql/ │ └── create-multiple-postgresql-databases.sh ├── prometheus_metrics/ │ ├── LICENSE │ ├── __init__.py │ ├── apps.py │ ├── middleware.py │ ├── models.py │ └── views.py ├── pylenium.json ├── pyproject.toml ├── setup.cfg ├── setup.py ├── src/ │ ├── about.js │ ├── main.js │ └── profile.js ├── static/ │ └── .gitignore ├── tools/ │ └── readme_symbol_check.py └── vite.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .cfnlintrc.yaml ================================================ # The W2001 check is used to ignore the featurebranch.yaml DataLoadStackName parameter in the nested # stack fargate-featurebranch.yaml used to signal when the DataLoadHost UserData commands are complete. # Check if Parameters are Used ignore_checks: - W2001 ================================================ FILE: .dockerignore ================================================ node_modules static-files ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve --- **What behavior did you observe? Please describe the bug** A clear and concise description of what you experienced. **How can we reproduce the bug?** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **What is the expected behavior?** A clear and concise description of what you expected to happen. **Got screenshots? This helps us identify the issue** Add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project --- **User story/persona** As {a user}, I want to {action} so that I can {goal} **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Additional context** Add any other context or screenshots about the feature request here. **Acceptance Criteria** Add a list items this new feature needs to meet. Ex: The user would not be able to submit a form if all the mandatory fields are not entered. **Acceptance Test:** Add a list of steps to test for a user to check if functionality satisfies the acceptance criteria. ================================================ FILE: .github/dependabot.yml ================================================ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file version: 2 updates: - package-ecosystem: 'github-actions' # See documentation for possible values directory: '/workflows' # Location of package manifests schedule: interval: 'weekly' - package-ecosystem: 'npm' # See documentation for possible values directory: '/' # Location of package manifests schedule: interval: 'daily' - package-ecosystem: 'pip' # See documentation for possible values directory: '/' # Location of package manifests schedule: interval: 'daily' ================================================ FILE: .github/workflows/black.yml ================================================ name: Lint on: workflow_dispatch: pull_request: branches: [main, 'feature-*', release] paths-ignore: - docs/** - README.md - .github/** - cloudformation/** - db_scripts/** - jenkins/** - search-proxy/** - postgresql/** jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: '3.12' - uses: psf/black@stable ================================================ FILE: .github/workflows/build.yml ================================================ name: 'Build' on: workflow_dispatch: pull_request: branches: [main, 'feature-*', release] paths-ignore: - docs/** - README.md - .github/** - cloudformation/** - db_scripts/** - jenkins/** - search-proxy/** - postgresql/** jobs: build: name: Build runs-on: ubuntu-latest steps: - name: Install system packages run: | sudo apt-get update -qy && sudo apt-get dist-upgrade -qy && sudo apt-get install -qy \ libmemcached-dev libz-dev libfreetype6-dev libtiff-dev \ libjpeg-dev libopenjp2-7-dev libwebp-dev zlib1g-dev libpq-dev - name: Install node and npm uses: actions/setup-node@v6 with: node-version: '20' - name: Checkout repository uses: actions/checkout@v6 - name: Set up Python 3.12 uses: actions/setup-python@v6 with: # Semantic version range syntax or exact version of a Python version python-version: '3.12' # Optional - x64 or x86 architecture, defaults to x64 architecture: 'x64' - name: Display Python version run: python -c "import sys; print(sys.version)" - name: build containers run: | docker build -t concordia . docker build -t concordia/importer --file importer/Dockerfile . docker build -t concordia/celerybeat --file celerybeat/Dockerfile . ================================================ FILE: .github/workflows/codeql.yml ================================================ name: 'CodeQL Advanced' on: workflow_dispatch: push: branches: [main, 'feature-*'] pull_request: branches: [main, 'feature-*', release] paths-ignore: - docs/** - README.md - cloudformation/** - db_scripts/** - jenkins/** - search-proxy/** - postgresql/** schedule: - cron: '20 23 * * 2' jobs: analyze: name: Analyze (${{ matrix.language }}) runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write packages: read strategy: fail-fast: false matrix: include: - language: javascript-typescript build-mode: none - language: python build-mode: none steps: - name: Install system packages run: | sudo apt-get update -qy && sudo apt-get dist-upgrade -qy && sudo apt-get install -qy \ libmemcached-dev libz-dev libfreetype6-dev libtiff-dev \ libjpeg-dev libopenjp2-7-dev libwebp-dev zlib1g-dev libpq-dev - name: Checkout repository uses: actions/checkout@v6 - if: matrix.language == 'python' name: Setup python uses: actions/setup-python@v6 with: python-version: '3.12' # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - if: matrix.language == 'python' run: | pip install -U packaging pip install -U setuptools pip install pipenv pipenv install --dev --deploy - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 with: category: '/language:${{matrix.language}}' ================================================ FILE: .github/workflows/db_ops.yml ================================================ name: DB Operations Multi-Repo Pipeline on: workflow_dispatch: inputs: action_type: description: 'Action' required: true default: 'build_test' type: choice options: - build_test - promote_to_latest operation: description: 'Operation' required: true default: 'dump' type: choice options: - dump - restore env: AWS_REGION: us-east-1 # Mapping the operation to the specific ECR Repo Name DUMP_REPO: crowd-db-dump RESTORE_REPO: crowd-db-restore jobs: process: runs-on: ubuntu-latest steps: - name: Checkout Code uses: actions/checkout@v6 - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v6 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ${{ env.AWS_REGION }} role-session-name: github_to_aws_deploy - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v2 # LOGIC: Determine Repo Name and Docker Stage Target - name: Set Variables id: vars run: | if [[ "${{ github.event.inputs.operation }}" == "dump" ]]; then echo "REPO_NAME=${{ env.DUMP_REPO }}" >> $GITHUB_OUTPUT echo "STAGE_TARGET=dump" >> $GITHUB_OUTPUT else echo "REPO_NAME=${{ env.RESTORE_REPO }}" >> $GITHUB_OUTPUT echo "STAGE_TARGET=restore" >> $GITHUB_OUTPUT fi # ACTION 1: BUILD AND PUSH 'test' - name: Build and Push Test if: ${{ github.event.inputs.action_type == 'build_test' }} uses: docker/build-push-action@v7 with: # context: defines where the 'COPY' commands look for files context: ./db_scripts # file: path to the actual Dockerfile relative to repo root file: ./db_scripts/Dockerfile # target: tells Docker to stop at the 'dump' or 'restore' stage target: ${{ steps.vars.outputs.STAGE_TARGET }} push: true tags: ${{ steps.login-ecr.outputs.registry }}/${{ steps.vars.outputs.REPO_NAME }}:test # ACTION 2: PROMOTE 'test' to 'latest' - name: Promote Test to Latest if: ${{ github.event.inputs.action_type == 'promote_to_latest' }} run: | REPO=${{ steps.vars.outputs.REPO_NAME }} MANIFEST=$(aws ecr batch-get-image \ --repository-name $REPO \ --image-ids imageTag=test \ --query 'images[0].imageManifest' \ --output text) aws ecr put-image \ --repository-name $REPO \ --image-tag latest \ --image-manifest "$MANIFEST" ================================================ FILE: .github/workflows/dev-main-deploy.yml ================================================ name: 'Deploy to dev' on: workflow_dispatch: push: branches: [main] paths-ignore: - docs/** - README.md - .github/** - cloudformation/** - db_scripts/** - jenkins/** - search-proxy/** - postgresql/** - cloudformation/tests/** - concordia/tests/** - exporter/tests/** - importer/tests/** env: AWS_REGION: us-east-1 permissions: id-token: write contents: read jobs: deploy: name: Deploy to Dev runs-on: ubuntu-latest environment: name: development steps: - name: Install system packages run: | sudo apt-get update -qy && sudo apt-get dist-upgrade -qy && sudo apt-get install -qy \ libmemcached-dev libz-dev libfreetype6-dev libtiff-dev \ libjpeg-dev libopenjp2-7-dev libwebp-dev zlib1g-dev libpq-dev - name: Install node and npm uses: actions/setup-node@v6 with: node-version: '20' - name: Checkout repository uses: actions/checkout@v6 with: fetch-depth: 0 fetch-tags: 'true' - name: Set up Python 3.12 uses: actions/setup-python@v6 with: # Semantic version range syntax or exact version of a Python version python-version: '3.12' # Optional - x64 or x86 architecture, defaults to x64 architecture: 'x64' - name: Install Python Dependencies and Retrieve Version Number id: python-build run: | python3 -m pip install --upgrade pip pip3 install -U setuptools pip3 install -U setuptools-scm FULL_VERSION_NUMBER="$(python3 -m setuptools_scm)" echo "version_number=$(echo "${FULL_VERSION_NUMBER}" | cut -d '+' -f 1)" >> $GITHUB_ENV - name: configure aws credentials uses: aws-actions/configure-aws-credentials@v6 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ${{ env.AWS_REGION }} role-session-name: github_to_aws_deploy - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v2 - name: Build, tag and push docker images ECR env: REGISTRY: ${{ steps.login-ecr.outputs.registry }} IMAGE_TAG: ${{ secrets.IMAGE_TAG }} CLUSTER: ${{ secrets.CLUSTER }} TARGET_SERVICE: ${{ secrets.TARGET_SERVICE }} run: | docker build -t concordia . docker build -t concordia/importer --file importer/Dockerfile . docker build -t concordia/celerybeat --file celerybeat/Dockerfile . docker tag concordia:latest $REGISTRY/concordia:$version_number docker tag concordia:latest $REGISTRY/concordia:$IMAGE_TAG docker tag concordia/importer:latest $REGISTRY/concordia/importer:$version_number docker tag concordia/importer:latest $REGISTRY/concordia/importer:$IMAGE_TAG docker tag concordia/celerybeat:latest $REGISTRY/concordia/celerybeat:$version_number docker tag concordia/celerybeat:latest $REGISTRY/concordia/celerybeat:$IMAGE_TAG docker push $REGISTRY/concordia:$version_number docker push $REGISTRY/concordia:$IMAGE_TAG docker push $REGISTRY/concordia/importer:$version_number docker push $REGISTRY/concordia/importer:$IMAGE_TAG docker push $REGISTRY/concordia/celerybeat:$version_number docker push $REGISTRY/concordia/celerybeat:$IMAGE_TAG aws ecs update-service --region ${{ env.AWS_REGION }} --force-new-deployment --cluster $CLUSTER --service $TARGET_SERVICE ================================================ FILE: .github/workflows/feature-branch-deploy.yml ================================================ name: 'Deploy feature branch to test' on: workflow_dispatch: push: branches: ['feature-*'] paths-ignore: - docs/** - README.md - .github/** - cloudformation/** - db_scripts/** - jenkins/** - search-proxy/** - postgresql/** env: AWS_REGION: us-east-1 permissions: id-token: write contents: read jobs: deploy: name: Deploy Feature Branch to Test runs-on: ubuntu-latest environment: name: feature steps: - name: Install system packages run: | sudo apt-get update -qy && sudo apt-get dist-upgrade -qy && sudo apt-get install -qy \ libmemcached-dev libz-dev libfreetype6-dev libtiff-dev \ libjpeg-dev libopenjp2-7-dev libwebp-dev zlib1g-dev libpq-dev - name: Install node and npm uses: actions/setup-node@v6 with: node-version: '20' - name: Checkout repository uses: actions/checkout@v6 with: ref: ${{ vars.FEATURE_BRANCH }} fetch-depth: 0 fetch-tags: 'true' - name: Set up Python 3.12 uses: actions/setup-python@v6 with: # Semantic version range syntax or exact version of a Python version python-version: '3.12' # Optional - x64 or x86 architecture, defaults to x64 architecture: 'x64' - name: Install Python Dependencies and Retrieve Version Number id: python-build run: | python3 -m pip install --upgrade pip pip3 install -U setuptools pip3 install -U setuptools-scm FULL_VERSION_NUMBER="$(python3 -m setuptools_scm)" echo "version_number=$(echo "${FULL_VERSION_NUMBER}" | cut -d '+' -f 1)" >> $GITHUB_ENV - name: configure aws credentials uses: aws-actions/configure-aws-credentials@v6 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ${{ env.AWS_REGION }} role-session-name: github_to_aws_deploy - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v2 - name: Build, tag and push docker images ECR env: REGISTRY: ${{ steps.login-ecr.outputs.registry }} IMAGE_TAG: ${{ secrets.IMAGE_TAG }} CLUSTER: ${{ secrets.CLUSTER }} TARGET_SERVICE: ${{ secrets.TARGET_SERVICE }} run: | docker build -t concordia . docker build -t concordia/importer --file importer/Dockerfile . docker build -t concordia/celerybeat --file celerybeat/Dockerfile . docker tag concordia:latest $REGISTRY/concordia:$version_number docker tag concordia:latest $REGISTRY/concordia:$IMAGE_TAG docker tag concordia/importer:latest $REGISTRY/concordia/importer:$version_number docker tag concordia/importer:latest $REGISTRY/concordia/importer:$IMAGE_TAG docker tag concordia/celerybeat:latest $REGISTRY/concordia/celerybeat:$version_number docker tag concordia/celerybeat:latest $REGISTRY/concordia/celerybeat:$IMAGE_TAG docker push $REGISTRY/concordia:$version_number docker push $REGISTRY/concordia:$IMAGE_TAG docker push $REGISTRY/concordia/importer:$version_number docker push $REGISTRY/concordia/importer:$IMAGE_TAG docker push $REGISTRY/concordia/celerybeat:$version_number docker push $REGISTRY/concordia/celerybeat:$IMAGE_TAG aws ecs update-service --region ${{ env.AWS_REGION }} --force-new-deployment --cluster $CLUSTER --service $TARGET_SERVICE ================================================ FILE: .github/workflows/pip-audit.yml ================================================ name: pip-audit on: workflow_dispatch: pull_request: branches: [main, release] paths-ignore: - docs/** - README.md - .github/** - cloudformation/** - db_scripts/** - jenkins/** - search-proxy/** - postgresql/** jobs: pip-audit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: '3.12' - name: 'Generate requirements.txt' run: | pipx run pipfile-requirements Pipfile.lock > requirements.txt - uses: pypa/gh-action-pip-audit@v1.1.0 with: inputs: requirements.txt ignore-vulns: | PYSEC-2023-312 ================================================ FILE: .github/workflows/prod-deploy.yml ================================================ name: 'Deploy to production' on: workflow_dispatch: env: AWS_REGION: us-east-1 permissions: id-token: write contents: read jobs: deploy: name: Deploy to Production runs-on: ubuntu-latest environment: name: production steps: - name: configure aws credentials uses: aws-actions/configure-aws-credentials@v6 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ${{ env.AWS_REGION }} role-session-name: github_to_aws_deploy - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v2 - name: Pull, tag and push docker images ECR env: REGISTRY: ${{ steps.login-ecr.outputs.registry }} IMAGE_TAG_PULL: ${{ secrets.IMAGE_TAG_PULL }} IMAGE_TAG: ${{ secrets.IMAGE_TAG }} CLUSTER: ${{ secrets.CLUSTER }} TARGET_SERVICE_A: ${{ secrets.TARGET_SERVICE_A }} TARGET_SERVICE: ${{ secrets.TARGET_SERVICE }} run: | docker pull $REGISTRY/concordia:$IMAGE_TAG_PULL docker pull $REGISTRY/concordia/importer:$IMAGE_TAG_PULL docker pull $REGISTRY/concordia/celerybeat:$IMAGE_TAG_PULL docker tag $REGISTRY/concordia:$IMAGE_TAG_PULL $REGISTRY/concordia:$IMAGE_TAG docker tag $REGISTRY/concordia/importer:$IMAGE_TAG_PULL $REGISTRY/concordia/importer:$IMAGE_TAG docker tag $REGISTRY/concordia/celerybeat:$IMAGE_TAG_PULL $REGISTRY/concordia/celerybeat:$IMAGE_TAG docker push $REGISTRY/concordia:$IMAGE_TAG docker push $REGISTRY/concordia/importer:$IMAGE_TAG docker push $REGISTRY/concordia/celerybeat:$IMAGE_TAG aws ecs update-service --region ${{ env.AWS_REGION }} --force-new-deployment --cluster $CLUSTER --service $TARGET_SERVICE_A aws ecs update-service --region ${{ env.AWS_REGION }} --force-new-deployment --cluster $CLUSTER --service $TARGET_SERVICE ================================================ FILE: .github/workflows/renew_coverage.yml ================================================ name: Renew Coverage Cache on: schedule: - cron: '0 0 */5 * *' # Runs every 5 days at midnight UTC workflow_dispatch: # The renew_coverage.yml action is used to keep the cached release coverage value by # accessing it every five days. Normally, cached values are discarded after they're # not accessed for seven days. To avoid that, the task simply accessing the value so # it's not lost in case we have a seven-day period with no pull requests. jobs: renew-cache: runs-on: ubuntu-latest steps: - name: Access Coverage Cache to Renew Expiration uses: actions/cache@v5 with: path: coverage.txt key: release-coverage restore-keys: | release-coverage ================================================ FILE: .github/workflows/stage-hotfix-rel-deploy.yml ================================================ name: 'Deploy hotfix to stage' on: workflow_dispatch: env: AWS_REGION: us-east-1 permissions: id-token: write contents: read jobs: deploy: name: Deploy Release to Stage runs-on: ubuntu-latest environment: name: stage steps: - name: Install system packages run: | sudo apt-get update -qy && sudo apt-get dist-upgrade -qy && sudo apt-get install -qy \ libmemcached-dev libz-dev libfreetype6-dev libtiff-dev \ libjpeg-dev libopenjp2-7-dev libwebp-dev zlib1g-dev libpq-dev - name: Install node and npm uses: actions/setup-node@v6 with: node-version: '20' - name: Checkout repository uses: actions/checkout@v6 with: ref: release fetch-depth: 0 fetch-tags: 'true' - name: Set up Python 3.12 uses: actions/setup-python@v6 with: # Semantic version range syntax or exact version of a Python version python-version: '3.12' # Optional - x64 or x86 architecture, defaults to x64 architecture: 'x64' - name: Get version from Git run: | # Get latest version tag number (e.g. release was tagged in GitHub for this hot fix) HOTFIX_VERSION_NUMBER="$(git describe --tags)" echo "version_number=$(echo "${HOTFIX_VERSION_NUMBER}" | cut -d '-' -f 1 | cut -c 2- )" >> $GITHUB_ENV - name: configure aws credentials uses: aws-actions/configure-aws-credentials@v6 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ${{ env.AWS_REGION }} role-session-name: github_to_aws_deploy - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v2 - name: Build, tag and push docker images ECR env: REGISTRY: ${{ steps.login-ecr.outputs.registry }} IMAGE_TAG: ${{ secrets.IMAGE_TAG }} CLUSTER: ${{ secrets.CLUSTER }} TARGET_SERVICE: ${{ secrets.TARGET_SERVICE }} run: | echo "version number: $version_number" docker build -t concordia . docker build -t concordia/importer --file importer/Dockerfile . docker build -t concordia/celerybeat --file celerybeat/Dockerfile . docker tag concordia:latest $REGISTRY/concordia:$version_number docker tag concordia:latest $REGISTRY/concordia:$IMAGE_TAG docker tag concordia/importer:latest $REGISTRY/concordia/importer:$version_number docker tag concordia/importer:latest $REGISTRY/concordia/importer:$IMAGE_TAG docker tag concordia/celerybeat:latest $REGISTRY/concordia/celerybeat:$version_number docker tag concordia/celerybeat:latest $REGISTRY/concordia/celerybeat:$IMAGE_TAG docker push $REGISTRY/concordia:$version_number docker push $REGISTRY/concordia:$IMAGE_TAG docker push $REGISTRY/concordia/importer:$version_number docker push $REGISTRY/concordia/importer:$IMAGE_TAG docker push $REGISTRY/concordia/celerybeat:$version_number docker push $REGISTRY/concordia/celerybeat:$IMAGE_TAG aws ecs update-service --region ${{ env.AWS_REGION }} --force-new-deployment --cluster $CLUSTER --service $TARGET_SERVICE ================================================ FILE: .github/workflows/stage-image-refresh.yml ================================================ name: 'Deploy image refresh to stage' on: workflow_dispatch: env: AWS_REGION: us-east-1 permissions: id-token: write contents: read jobs: deploy: name: Deploy Container Environment Update runs-on: ubuntu-latest environment: name: stage steps: - name: Install system packages run: | sudo apt-get update -qy && sudo apt-get dist-upgrade -qy && sudo apt-get install -qy \ libmemcached-dev libz-dev libfreetype6-dev libtiff-dev \ libjpeg-dev libopenjp2-7-dev libwebp-dev zlib1g-dev libpq-dev - name: Install node and npm uses: actions/setup-node@v6 with: node-version: '20' - name: Checkout repository uses: actions/checkout@v6 with: ref: release fetch-depth: 0 fetch-tags: 'true' - name: Set up Python 3.12 uses: actions/setup-python@v6 with: # Semantic version range syntax or exact version of a Python version python-version: '3.12' # Optional - x64 or x86 architecture, defaults to x64 architecture: 'x64' - name: Create image tags run: | # Get latest version tag number (e.g. main was tagged in GitHub for this Release) FULL_VERSION_NUMBER="$(git describe --tags `git rev-list --tags --max-count=1`)" echo "version_number=$(echo "${FULL_VERSION_NUMBER}" | cut -c2- )" >> $GITHUB_ENV # Create image tag for image being being replaced/refreshed/updated echo "tag_stale_image=$(echo "${FULL_VERSION_NUMBER}" | cut -c2- )_$(date +%Y%m%dT%H%M%S)" >> $GITHUB_ENV - name: configure aws credentials uses: aws-actions/configure-aws-credentials@v6 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ${{ env.AWS_REGION }} role-session-name: github_to_aws_deploy - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v2 - name: Build, tag and push docker images ECR env: REGISTRY: ${{ steps.login-ecr.outputs.registry }} IMAGE_TAG: ${{ secrets.IMAGE_TAG }} CLUSTER: ${{ secrets.CLUSTER }} TARGET_SERVICE: ${{ secrets.TARGET_SERVICE }} run: | docker build -t concordia . docker build -t concordia/importer --file importer/Dockerfile . docker build -t concordia/celerybeat --file celerybeat/Dockerfile . docker tag concordia:latest $REGISTRY/concordia:$version_number docker tag concordia:latest $REGISTRY/concordia:$IMAGE_TAG docker tag concordia/importer:latest $REGISTRY/concordia/importer:$version_number docker tag concordia/importer:latest $REGISTRY/concordia/importer:$IMAGE_TAG docker tag concordia/celerybeat:latest $REGISTRY/concordia/celerybeat:$version_number docker tag concordia/celerybeat:latest $REGISTRY/concordia/celerybeat:$IMAGE_TAG docker push $REGISTRY/concordia:$version_number docker push $REGISTRY/concordia:$IMAGE_TAG docker push $REGISTRY/concordia/importer:$version_number docker push $REGISTRY/concordia/importer:$IMAGE_TAG docker push $REGISTRY/concordia/celerybeat:$version_number docker push $REGISTRY/concordia/celerybeat:$IMAGE_TAG aws ecs update-service --region ${{ env.AWS_REGION }} --force-new-deployment --cluster $CLUSTER --service $TARGET_SERVICE - name: Tag existing images env: IT_TAG: ${{ secrets.IT_IMAGE_TAG }} run: | # Add a new tag to existing concordia images to preserve the history of images after final deployment # Tag concordia APP_MANIFEST="$(aws ecr batch-get-image --repository-name concordia --image-ids imageTag=${IT_TAG} --output json | jq --raw-output --join-output '.images[0].imageManifest')" aws ecr put-image --repository-name concordia --image-tag $tag_stale_image --image-manifest "$APP_MANIFEST" # Tag concordia/celerybeat BEAT_MANIFEST="$(aws ecr batch-get-image --repository-name concordia/celerybeat --image-ids imageTag=${IT_TAG} --output json | jq --raw-output --join-output '.images[0].imageManifest')" aws ecr put-image --repository-name concordia/celerybeat --image-tag $tag_stale_image --image-manifest "$BEAT_MANIFEST" # Tag concordia/importer IMPORT_MANIFEST="$(aws ecr batch-get-image --repository-name concordia/importer --image-ids imageTag=${IT_TAG} --output json | jq --raw-output --join-output '.images[0].imageManifest')" aws ecr put-image --repository-name concordia/importer --image-tag $tag_stale_image --image-manifest "$IMPORT_MANIFEST" ================================================ FILE: .github/workflows/stage-release-deploy.yml ================================================ name: 'Deploy release to stage' on: workflow_dispatch: push: branches: [release] paths-ignore: - docs/** - README.md - .github/** - cloudformation/** - db_scripts/** - jenkins/** - search-proxy/** - postgresql/** - cloudformation/tests/** - concordia/tests/** - exporter/tests/** - importer/tests/** env: AWS_REGION: us-east-1 permissions: id-token: write contents: read jobs: deploy: name: Deploy Release to Stage runs-on: ubuntu-latest environment: name: stage steps: - name: Install system packages run: | sudo apt-get update -qy && sudo apt-get dist-upgrade -qy && sudo apt-get install -qy \ libmemcached-dev libz-dev libfreetype6-dev libtiff-dev \ libjpeg-dev libopenjp2-7-dev libwebp-dev zlib1g-dev libpq-dev - name: Install node and npm uses: actions/setup-node@v6 with: node-version: '20' - name: Checkout repository uses: actions/checkout@v6 with: ref: release fetch-depth: 0 fetch-tags: 'true' - name: Set up Python 3.12 uses: actions/setup-python@v6 with: # Semantic version range syntax or exact version of a Python version python-version: '3.12' # Optional - x64 or x86 architecture, defaults to x64 architecture: 'x64' - name: Get version from Git run: | # Get latest version tag number (e.g. main was tagged in GitHub for this Release) FULL_VERSION_NUMBER="$(git describe --tags `git rev-list --tags --max-count=1`)" echo "version_number=$(echo "${FULL_VERSION_NUMBER}" | cut -c2- )" >> $GITHUB_ENV - name: configure aws credentials uses: aws-actions/configure-aws-credentials@v6 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ${{ env.AWS_REGION }} role-session-name: github_to_aws_deploy - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v2 - name: Build, tag and push docker images ECR env: REGISTRY: ${{ steps.login-ecr.outputs.registry }} IMAGE_TAG: ${{ secrets.IMAGE_TAG }} CLUSTER: ${{ secrets.CLUSTER }} TARGET_SERVICE: ${{ secrets.TARGET_SERVICE }} run: | docker build -t concordia . docker build -t concordia/importer --file importer/Dockerfile . docker build -t concordia/celerybeat --file celerybeat/Dockerfile . docker tag concordia:latest $REGISTRY/concordia:$version_number docker tag concordia:latest $REGISTRY/concordia:$IMAGE_TAG docker tag concordia/importer:latest $REGISTRY/concordia/importer:$version_number docker tag concordia/importer:latest $REGISTRY/concordia/importer:$IMAGE_TAG docker tag concordia/celerybeat:latest $REGISTRY/concordia/celerybeat:$version_number docker tag concordia/celerybeat:latest $REGISTRY/concordia/celerybeat:$IMAGE_TAG docker push $REGISTRY/concordia:$version_number docker push $REGISTRY/concordia:$IMAGE_TAG docker push $REGISTRY/concordia/importer:$version_number docker push $REGISTRY/concordia/importer:$IMAGE_TAG docker push $REGISTRY/concordia/celerybeat:$version_number docker push $REGISTRY/concordia/celerybeat:$IMAGE_TAG aws ecs update-service --region ${{ env.AWS_REGION }} --force-new-deployment --cluster $CLUSTER --service $TARGET_SERVICE ================================================ FILE: .github/workflows/test-main-deploy.yml ================================================ name: 'Deploy to test' on: workflow_dispatch: env: AWS_REGION: us-east-1 permissions: id-token: write contents: read jobs: deploy: name: Deploy to Test runs-on: ubuntu-latest environment: name: test steps: - name: configure aws credentials uses: aws-actions/configure-aws-credentials@v6 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ${{ env.AWS_REGION }} role-session-name: github_to_aws_deploy - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v2 - name: Pull, tag and push docker images ECR env: REGISTRY: ${{ steps.login-ecr.outputs.registry }} IMAGE_TAG_PULL: ${{ secrets.IMAGE_TAG_PULL }} IMAGE_TAG: ${{ secrets.IMAGE_TAG }} CLUSTER: ${{ secrets.CLUSTER }} TARGET_SERVICE: ${{ secrets.TARGET_SERVICE }} TARGET_SERVICE_B: ${{ secrets.TARGET_SERVICE_B }} run: | docker pull $REGISTRY/concordia:$IMAGE_TAG_PULL docker pull $REGISTRY/concordia/importer:$IMAGE_TAG_PULL docker pull $REGISTRY/concordia/celerybeat:$IMAGE_TAG_PULL docker tag $REGISTRY/concordia:$IMAGE_TAG_PULL $REGISTRY/concordia:$IMAGE_TAG docker tag $REGISTRY/concordia/importer:$IMAGE_TAG_PULL $REGISTRY/concordia/importer:$IMAGE_TAG docker tag $REGISTRY/concordia/celerybeat:$IMAGE_TAG_PULL $REGISTRY/concordia/celerybeat:$IMAGE_TAG docker push $REGISTRY/concordia:$IMAGE_TAG docker push $REGISTRY/concordia/importer:$IMAGE_TAG docker push $REGISTRY/concordia/celerybeat:$IMAGE_TAG aws ecs update-service --region ${{ env.AWS_REGION }} --force-new-deployment --cluster $CLUSTER --service $TARGET_SERVICE aws ecs update-service --region ${{ env.AWS_REGION }} --force-new-deployment --cluster $CLUSTER --service $TARGET_SERVICE_B ================================================ FILE: .github/workflows/test.yml ================================================ name: Test on: workflow_dispatch: pull_request: branches: [main, 'feature-*', release] paths-ignore: - docs/** - README.md - .github/** - cloudformation/** - db_scripts/** - jenkins/** - search-proxy/** - postgresql/** env: PIPENV_IGNORE_VIRTUALENVS: 1 DJANGO_SETTINGS_MODULE: concordia.settings_test jobs: test: runs-on: ubuntu-latest services: # Label used to access the service container postgres: # Docker Hub image image: postgres # Provide the password for postgres env: POSTGRES_DB: concordia POSTGRES_PASSWORD: postgres # Set health checks to wait until postgres has started options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 ports: # Maps tcp port 5432 on service container to the host - 5432:5432 steps: - name: Remove Firefox run: sudo apt-get purge firefox - name: Install system packages run: | sudo apt-get update -qy && sudo apt-get dist-upgrade -qy && sudo apt-get install -qy \ libmemcached-dev libz-dev libfreetype6-dev libtiff-dev \ libjpeg-dev libopenjp2-7-dev libwebp-dev zlib1g-dev libpq-dev \ tesseract-ocr tesseract-ocr-all - name: Install node and npm uses: actions/setup-node@v6 with: node-version: '20' - name: Checkout repository uses: actions/checkout@v6 - name: Set up Python 3.12 uses: actions/setup-python@v6 with: python-version: '3.12' architecture: 'x64' cache: 'pipenv' - name: Display Python version run: python -c "import sys; print(sys.version)" - name: Install Python Dependencies run: | python3 -m pip install --upgrade pip pip3 install -U packaging pip3 install -U setuptools pip3 install -U pipenv pipenv install --dev --deploy pipenv install tblib # For parallel test debugging - name: Install Node Dependencies #and Add .bin to Path run: npm install # echo "PATH=$PWD/node_modules/.bin:$PATH" >> $GITHUB_ENV - name: Configure Logs run: | mkdir logs touch ./logs/concordia-celery.log - name: Bundle, Build (Vite) and Collect Static Files run: | npm run build pipenv run ./manage.py collectstatic --no-input --no-post-process # - name: Install Chrome for Testing and Set Path # run: | # chromepath=$(npx @puppeteer/browsers install chrome@latest) # chromepath=${chromepath#* } # echo "Chrome installed at: $chromepath" # $chromepath --version # chromepath=${chromepath%/chrome} # Remove the binary so we can add it to the PATH # # Update PATH for subsequent steps # echo "PATH=$chromepath:$PATH" >> $GITHUB_ENV - name: Run Tests with Coverage run: | mkdir -p coverage_report pipenv run coverage run --parallel-mode ./manage.py test --parallel auto pipenv run coverage combine # Merge results from parallel test workers # Save full report to coverage_report/coverage.txt and just the total coverage percent to pr_coverage.txt pipenv run coverage report | tee coverage_report/coverage.txt | grep 'TOTAL' | awk '{print $6}' > pr_coverage.txt echo "Stored PR coverage:" cat pr_coverage.txt # Debugging output to verify correct storage pipenv run coverage html mv htmlcov coverage_report/html # Move HTML report into a separate directory env: PGPASSWORD: postgres # The hostname used to communicate with the PostgreSQL service container POSTGRES_HOST: localhost # The default PostgreSQL port POSTGRES_PORT: 5432 # COMMIT_RANGE: ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} # Store coverage results if running on the release branch - name: Store Release Coverage (if running on release branch) if: github.ref == 'refs/heads/release' run: cp pr_coverage.txt coverage.txt # Cache coverage results if running on the release branch - name: Cache Release Coverage (if running on release branch) if: github.ref == 'refs/heads/release' uses: actions/cache@v5 with: path: coverage.txt key: release-coverage # Upload full coverage report as an artifact - name: Upload Full Coverage Report uses: actions/upload-artifact@v7 with: name: coverage-report path: coverage_report # Download the stored release branch coverage for PR comparison, if it exists - name: Restore Release Coverage (if running on PR) if: github.event_name == 'pull_request' uses: actions/cache@v5 with: path: coverage.txt key: release-coverage restore-keys: | release-coverage # Compare PR coverage against stored release coverage - name: Compare Coverage (if running on PR) if: github.event_name == 'pull_request' run: | echo "Reading PR coverage from pr_coverage.txt..." cat pr_coverage.txt || echo "⚠️ ERROR: pr_coverage.txt not found or empty" PR_COVERAGE=$(cat pr_coverage.txt) if [ -z "$PR_COVERAGE" ]; then echo "⚠️ ERROR: PR_COVERAGE is empty!" PR_COVERAGE="N/A" fi echo "PR Coverage: $PR_COVERAGE" if [ -f "coverage.txt" ]; then RELEASE_COVERAGE=$(cat coverage.txt) COMPARISON_AVAILABLE=true else COMPARISON_AVAILABLE=false RELEASE_COVERAGE="N/A" fi if [ "$COMPARISON_AVAILABLE" = true ]; then # Strip '%' from PR_COVERAGE and RELEASE_COVERAGE for numerical comparison PR_COVERAGE_NUM=${PR_COVERAGE%\%} RELEASE_COVERAGE_NUM=${RELEASE_COVERAGE%\%} if (( $(echo "$PR_COVERAGE_NUM > $RELEASE_COVERAGE_NUM" | bc -l) )); then CHANGE="🔼 Coverage increased (+$(echo "$PR_COVERAGE_NUM - $RELEASE_COVERAGE_NUM" | bc -l)%)!" elif (( $(echo "$PR_COVERAGE_NUM < $RELEASE_COVERAGE_NUM" | bc -l) )); then CHANGE="🔽 Coverage decreased (-$(echo "$RELEASE_COVERAGE_NUM - $PR_COVERAGE_NUM" | bc -l)%)!" else CHANGE="✅ Coverage remained the same." fi else CHANGE="⚠️ No baseline coverage available from 'release' branch." fi echo "COVERAGE_CHANGE=$CHANGE" >> $GITHUB_ENV printf "RELEASE_COVERAGE=%s\n" "$RELEASE_COVERAGE" >> $GITHUB_ENV printf "PR_COVERAGE=%s\n" "$PR_COVERAGE" >> $GITHUB_ENV # Generate and store command for display on the Action UI and PR (if any) - name: Generate Coverage Report Comment run: | echo "**🛡 Test Coverage Report 🛡**" > coverage_comment.txt echo "- **Current PR Coverage:** ${{ env.PR_COVERAGE }}" >> coverage_comment.txt echo "- **Release Branch Coverage:** ${{ env.RELEASE_COVERAGE }}" >> coverage_comment.txt echo "- **${{ env.COVERAGE_CHANGE }}**" >> coverage_comment.txt echo "- 📊 **[Download Full Coverage Report (Under "Artifacts")](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}#artifacts)**" >> coverage_comment.txt echo "" >> coverage_comment.txt echo "
" >> coverage_comment.txt echo "📜 Click to view full text coverage report" >> coverage_comment.txt echo "" >> coverage_comment.txt echo '```text' >> coverage_comment.txt cat coverage_report/coverage.txt >> coverage_comment.txt echo '```' >> coverage_comment.txt echo "
" >> coverage_comment.txt # Display the coverage summary in the GitHub Actions UI - name: Post Coverage Summary run: cat coverage_comment.txt >> $GITHUB_STEP_SUMMARY # Post a comment on the PR with the coverage results - name: Comment Coverage Change on PR if: github.event_name == 'pull_request' uses: mshick/add-pr-comment@v3 with: message-path: coverage_comment.txt ================================================ FILE: .gitignore ================================================ node_modules/ bin/ target/ local/ build/ .project .classpath .settings/ *.pyc buildstatus.log deploystatus.log .metadata/ artifacts/ /.* !.gitignore !.cfnlintrc.yaml !.github !.dockerignore .DS_Store docs/build env.ini .venv *.sqlite3 *.egg-info/ /temp/ /emails/ /logs/* env-dev.ini docs/_build docs/modules dist/ profile_pics/ mss* *.swp config-optional-override.json env/ concordia/settings_dev_*.py concordia/settings_test_*.py concordia/settings_loadtest_*.py version.txt static-files ================================================ FILE: Dockerfile ================================================ # Base runtime: Debian 12 (bookworm) slim + Python 3.12. FROM python:3.12-slim-bookworm # Major Node.js version to install (e.g., 20, 22). This is used to select the # NodeSource APT repository "node_.x". ARG NODE_MAJOR=20 # Include a small "wait for dependencies" helper used by the container command. # This is downloaded at build time and placed at /wait. ## Add the wait script to the image ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.2.1/wait /wait RUN chmod +x /wait # Prevent interactive prompts during apt operations. ENV DEBIAN_FRONTEND="noninteractive" # Bootstrap minimal tooling needed later in the build: # - curl: download files/keys # - ca-certificates: validate HTTPS endpoints # - gnupg: import and dearmor APT repository signing keys RUN apt-get update -qy && apt-get install -qy curl ca-certificates gnupg # Trust the Library's certificate authority so the HTTPS tampering proxy does # not break TLS validation for clients inside the container. # # This downloads the CA certificate, converts it to PEM, and refreshes the # OpenSSL certificate hashes so it is recognized by OpenSSL-based clients. # Ensure that the Library's certificate authority is trusted so the tampering # proxy will not break TLS validation. See # https://staff.loc.gov/wikis/display/SE/Configuring+HTTPS+clients+for+the+HTTPS+tampering+proxy. RUN curl -fso /etc/ssl/certs/LOC-ROOT-CA-1.crt http://crl.loc.gov/LOC-ROOT-CA-1.crt && openssl x509 -inform der -in /etc/ssl/certs/LOC-ROOT-CA-1.crt -outform pem -out /etc/ssl/certs/LOC-ROOT-CA-1.pem && c_rehash # Install Node.js via the NodeSource APT repository (manual setup; no setup # script). Debian bookworm ships Node 18; adding this repo allows installing a # newer major version (e.g., Node 20) via apt. # # This step: # - creates a dedicated keyring directory under /etc/apt/keyrings # - downloads and installs the NodeSource signing key into a keyring file # - registers the NodeSource repository for the selected Node.js major line # # Note: When installing Node.js from NodeSource, the `nodejs` package includes # npm (and npm comes with node-gyp), so there is no separate `npm` or # `node-gyp` APT package to install here. # # References: NodeSource "Repository Manual Installation" guide. https://github.com/nodesource/distributions/wiki/Repository-Manual-Installation RUN \ # Create a dedicated directory for third-party APT keyrings. mkdir -p /etc/apt/keyrings && \ # Download the NodeSource repository signing key and store it as a keyring # file that apt can use to verify NodeSource packages. curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \ | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \ # Register the NodeSource repository for the selected Node.js major version. # The "signed-by=" option scopes trust to just this repository entry. echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${NODE_MAJOR}.x nodistro main" \ > /etc/apt/sources.list.d/nodesource.list # Bring the base OS packages fully up to date, then install system dependencies # needed to build and run the application. # # Notes: # - dist-upgrade pulls in security and point-release updates for the base image. # - --force-confnew ensures updated config files are accepted when prompted. # - autoremove/autoclean reduce image size after installing packages. RUN apt-get update -qy && apt-get dist-upgrade -qy && apt-get install -o Dpkg::Options::='--force-confnew' -qy \ build-essential \ git \ libmemcached-dev \ # Pillow/Imaging: https://pillow.readthedocs.io/en/latest/installation.html#external-libraries libz-dev libfreetype6-dev \ libtiff-dev libjpeg-dev libopenjp2-7-dev libwebp-dev zlib1g-dev \ # Postgres client library to build psycopg libpq-dev \ locales \ # Weasyprint requirements libpango-1.0-0 libharfbuzz0b libpangoft2-1.0-0 \ # Tesseract tesseract-ocr tesseract-ocr-all \ # Node.js runtime (from NodeSource) and build tooling for native addons. nodejs && apt-get -qy autoremove && apt-get -qy autoclean # Generate and configure a UTF-8 locale for consistent string handling. RUN locale-gen en_US.UTF-8 ENV LC_ALL=en_US.UTF-8 ENV LANG=en_US.UTF-8 ENV LANGUAGE=en_US.UTF-8 # Python runtime settings: # - unbuffered output for log visibility in containers # - add /app to PYTHONPATH for module resolution ENV PYTHONUNBUFFERED=1 \ PYTHONPATH=/app # Default Django settings module for container runtime (can be overridden). ENV DJANGO_SETTINGS_MODULE=${DJANGO_SETTINGS_MODULE:-concordia.settings_docker} # Ensure an up-to-date pip and install pipenv for dependency management. RUN pip install --upgrade pip RUN pip install --no-cache-dir pipenv # Copy application code into the image. WORKDIR /app COPY . /app # Front-end build and asset pipeline: # - update npm to a known major version # - Install all JS dependencies (including devDependencies for plugins) RUN npm install --silent --global npm@10 && npm install --silent # Additional JS build step for Vite. # - Build assets (Vite) complile scss, bundle, hash and compress js # - This populates concordia/static/dist with hashed and compressed files. RUN npm run build # Create Log Directory # - Required for Django logging initialization when running collecstatic. RUN mkdir -p /app/logs # Install Python dependencies into the system environment using Pipenv and # - Bake static files into the image (Fast, no post-processing) # - remove Pipenv cache to reduce image size. RUN pipenv install --system --dev --deploy && \ python manage.py collectstatic --no-input --no-post-process && \ rm -rf ~/.cache/ # - Clean up node artifacts to reduce image size RUN rm -rf node_modules && rm -rf ~/.cache/ # Container listens on port 80. EXPOSE 80 # Wait for dependencies (via /wait) and then run the application entrypoint. CMD /wait && /bin/bash entrypoint.sh ================================================ FILE: LICENSE.md ================================================ As a work of the United States Government, this project is in the public domain within the United States. Additionally, we waive copyright and related rights in the work worldwide through the CC0 1.0 Universal public domain dedication. ## CC0 1.0 Universal Summary This is a human-readable summary of the [Legal Code (read the full text)](https://creativecommons.org/publicdomain/zero/1.0/legalcode). ### No Copyright The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. ### Other Information In no way are the patent or trademark rights of any person affected by CC0, nor are the rights that other persons may have in the work or in how the work is used, such as publicity or privacy rights. Unless expressly stated otherwise, the person who associated a work with this deed makes no warranties about the work, and disclaims liability for all uses of the work, to the fullest extent permitted by applicable law. When using or citing the work, you should not imply endorsement by the author or the affirmer. ================================================ FILE: Loadtesting.md ================================================ # Load Testing Mode This document describes the current (incomplete but runnable) "load testing mode" implementation and how to run it end-to-end manually. Load testing mode consists of: - A fixture generator that builds a single JSON fixture from an existing DB - A DB preparation command that creates a fresh load test DB, migrates it, and loads the fixture while suppressing all Django signals - A load test settings file that points the app at the load test DB and disables Turnstile blocking - A Locust script (`locustfile.py`) plus a wrapper shell script (`load_test.sh`) to run a headless load test The intended lifecycle is: 1. Generate a fixture from a DB with real-ish data 2. Create and populate a fresh `concordia_lt` database from that fixture 3. Run the web app against `concordia_lt` using load test settings 4. Run Locust against that host The load test database is intended to be single-use. ## Files - `concordia/management/commands/create_load_test_fixtures.py` - `concordia/management/commands/prepare_load_test_db.py` - `concordia/settings_loadtest.py` (or your own `concordia/settings_loadtest_.py`) - `locustfile.py` (repo root) - `load_test.sh` (repo root) ## Safety notes - `create_load_test_fixtures` is read-only against the source DB and only writes a JSON file. It is safe to run against production, though it is normally run against a refreshed copy of production. - `prepare_load_test_db` creates and optionally drops a separate database (`concordia_lt`), runs migrations, and loads fixtures into it. - It requires PostgreSQL credentials with `CREATE DATABASE` privileges. - If recreating or dropping, it terminates active connections to the target DB. - During fixture load, all Django signals are suppressed to avoid side effects (Celery tasks, storage writes, cache updates, derived fields, etc). - Storage in load test mode is configured to use dev/staging buckets for safety. The workflow is designed to avoid writes to external systems. - Locust defaults to a non-production host to reduce risk. ## Prerequisites - VPN access to the target environment - PostgreSQL credentials available via environment variables - The DB user must be able to connect to `dbname=postgres` and create databases - Python environment with the normal dev dependencies installed (Locust is a dev dependency) - Ability to restart the web app with a different settings module - A reachable host running the app in load test mode ## Fixture contents The fixture generated by `create_load_test_fixtures` contains: - Up to 2 published Topics, chosen by ascending `ordering` - Up to 5 published Campaigns, preferring Topic-linked Campaigns and filling with additional published Campaigns by ascending `ordering` - Up to `--assets-limit` Assets (default 10,000), collected by walking: - Topic-linked Projects first, then - Campaign-linked Projects if needed - Closure of referenced Items, Projects, Campaigns and Topics for the chosen Assets - All Transcriptions for selected Assets - Anonymized fixtures for any Users referenced by those Transcriptions (`user` and `reviewed_by`) - A synthetic pool of test users: - Default: 10,000 users named `locusttest00001`..`locusttest10000` - All share the same password: `locustpass123` - Email: `@example.test` - Users are created with explicit PKs beyond the existing fixture user PKs to avoid collisions - ProjectTopic rows for selected Topic+Project links (preserves the M2M) Notes: - Selection is best-effort. If there are fewer than `--assets-limit` Assets, the fixture is still written. - The output is a single JSON file (default `loadtest_fixture.json`). - By default, the command validates the fixture by calling `prepare_load_test_db` unless `--no-validate` is provided. ## Commands ### 1) Create the fixture Run against a DB with real data (usually a refreshed prod copy): ```bash python manage.py create_load_test_fixtures ``` Common options: ```bash python manage.py create_load_test_fixtures \ --assets-limit 10000 \ --test-users 10000 \ --test-user-prefix locusttest \ --test-user-password locustpass123 \ --output loadtest_fixture.json ``` Validation options: - `--no-validate` to skip validation - `--validate-db-name NAME` to override the validation DB name - `--validate-recreate` to recreate the validation DB if it exists - `--validate-drop` to drop the validation DB after loading ### 2) Create and populate the load test DB Standard DB name: `concordia_lt` ```bash python manage.py prepare_load_test_db \ --db-name concordia_lt \ --recreate \ --fixtures loadtest_fixture.json ``` Behavior: - Creates or recreates `concordia_lt` - Runs migrations - Loads fixtures with all signals suppressed by default ## Running the app in load test mode ### Settings file `concordia/settings_loadtest.py` is an override layer on top of `settings_template.py`. It: - Points the DB at `concordia_lt` - Disables rate limiting - Forces Turnstile to always-pass test keys by default - Uses console email backend - Uses dev buckets for safety - Adjusts logging to be visible in common run contexts If you need a different DB name, do not edit `settings_loadtest.py` directly. Create a personal settings file, following the local dev convention: - `concordia/settings_loadtest_.py` - Override `DATABASES["default"]["NAME"]` (and any other local overrides) ### Selecting settings at runtime Local example: ```bash DJANGO_SETTINGS_MODULE=concordia.settings_loadtest \ python manage.py runserver 0.0.0.0:8000 ``` Server/container example: - Set `DJANGO_SETTINGS_MODULE=concordia.settings_loadtest` - Restart/redeploy the web process so it actually uses the load test settings Important: - Creating `concordia_lt` does not affect any running web process. You must restart the app with the load test settings selected. ## Locust ### Overview The load test simulates three flows: - Anonymous browsing/transcription page interactions - Authenticated users who transcribe - Authenticated users who review The script uses these endpoints: - `/` (homepage) - `/next-transcribable-asset/` (redirect to next asset) - `/next-reviewable-asset/` (redirect to next reviewable asset) - `/account/login/` (login) - `/account/ajax-status/` and `/account/ajax-messages/` (simulates normal page load) The script parses asset pages to find: - The transcription form action (`
`) - Reservation endpoint (` {{ block.super }} {% vite_asset 'src/profile.js' %} {% endblock body_scripts %} ================================================ FILE: concordia/templates/admin/auth/user/change_form.html ================================================ {% extends "admin/change_form.html" %} {% load i18n admin_urls humanize %} {% block object-tools-items %} {% if original.pk %}
  • Transcriptions
  • Reviews
  • {% endif %} {{ block.super }} {% endblock object-tools-items %} ================================================ FILE: concordia/templates/admin/base_site.html ================================================ {% extends "admin/base.html" %} {% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %} {% block branding %}

    {{ site_header|default:_('Django administration') }}

    {% endblock %} {% block nav-global %}{% endblock %} {% block extrahead %} {% endblock %} {% block messages %} {% if messages %}
      {% for message in messages %} {# Remove mark-safe from tags since that's for controlling template behavior #} {% with message.tags|reject:"mark-safe"|join:" " as cleaned_tags %}
    • {% endif %} {% if "marked-safe" in message.tags %} {{ message|safe|capfirst }} {% else %} {{ message|capfirst }} {% endif %}
    • {% endwith %} {% endfor %}
    {% endif %} {% endblock messages %} ================================================ FILE: concordia/templates/admin/bulk_change.html ================================================ {% extends "admin/base_site.html" %} {% block messages %} {% comment %} This is displayed elswhere {% endcomment %} {% endblock messages %} {% block extrahead %} {{ block.super }} {% endblock %} {% block content %}
    1. The spreadsheet should be in xlsx (not xls or csv) format.
    2. The spreadsheet should have a header row. One of the columns headers should be "asset__slug", and another should be "New Status".
    3. If the "user" column is set, that value will be used as the submitting user. Otherwise, the anonymouse user will be used.
    {% csrf_token %} {{ form.as_table }}
    {% if messages %}

    Messages

      {% for message in messages %}
    • {{ message }}
    • {% endfor %}
    {% endif %}
    {% endblock content %} ================================================ FILE: concordia/templates/admin/bulk_import.html ================================================ {% extends "admin/base.html" %} {% block messages %} {% comment %} This is displayed elswhere {% endcomment %} {% endblock messages %} {% block extrahead %} {{ block.super }} {% endblock %} {% block content %}
    {% if import_jobs %}

    Import Tasks

    {% else %}

    The spreadsheet must follow this convention:

    1. A header row must include the columns Campaign, Campaign Short Description, Campaign Long Description, Campaign Slug, Project Slug, Project, Project Description, and Import URLs
    2. The header names are case sensitive but may occur in any order and other columns will be ignored
    3. Project titles (in the Project column) must be 80 characters or less.
    4. The Campaign, Project, and Import URLs columns must have values or the row will be skipped
    5. The Import URLs column may contain one or more URLs separated by spaces or newlines. Do not include commas or semicolons as those are valid URL characters and will be treated as part of the URL.
    6. Campaigns and Projects will be created if they do not exist but existing records will not be modified. If you want to recreate them, delete the old records before running the importer.
    7. Items will be added to projects but items which have already been imported into that project will be skipped. (Unless the redownload option is checked below.) This means that you can add multiple items to a project both by having the “Import URLs” cell contain multiple URLs or by duplicating the row with new ”Import URLs” values.

    {% csrf_token %} {{ form.as_p }}
    {% endif %} {% if messages %}

    Messages

      {% for message in messages %}
    • {{ message }}
    • {% endfor %}
    {% endif %}
    {% endblock content %} ================================================ FILE: concordia/templates/admin/bulk_review.html ================================================ {% extends "admin/base.html" %} {% block messages %} {% comment %} This is displayed elswhere {% endcomment %} {% endblock messages %} {% block extrahead %} {{ block.super }} {% endblock %} {% block content %}
    {% if import_jobs %}

    Import Tasks

    {% else %}

    The spreadsheet must follow this convention:

    1. A header row must include the columns Campaign, Campaign Short Description, Campaign Long Description, Campaign Slug, Project Slug, Project, Project Description, and Import URLs
    2. The header names are case sensitive but may occur in any order and other columns will be ignored
    3. Project titles (in the Project column) must be 80 characters or less.
    4. The Campaign, Project, and Import URLs columns must have values or the row will be skipped
    5. The Import URLs column may contain one or more URLs separated by spaces or newlines. Do not include commas or semicolons as those are valid URL characters and will be treated as part of the URL.
    6. Campaigns and Projects will be created if they do not exist but existing records will not be modified. If you want to recreate them, delete the old records before running the importer.
    7. Items will be added to projects but items which have already been imported into that project will be skipped. This means that you can add multiple items to a project both by having the “Import URLs” cell contain multiple URLs or by duplicating the row with new ”Import URLs” values.

    {% csrf_token %} {{ form.as_p }}
    {% endif %} {% if messages %}

    Messages

      {% for message in messages %}
    • {{ message }}
    • {% endfor %}
    {% endif %}
    {% endblock content %} ================================================ FILE: concordia/templates/admin/celery_task.html ================================================ {% extends "admin/base.html" %} {% block messages %} {% comment %} This is displayed elswhere {% endcomment %} {% endblock messages %} {% block extrahead %} {{ block.super }} {% endblock %} {% block content %}
    {% if campaigns %}

    Importer Progress

      {% for campaign in campaigns %} {% endfor %}
      Campaign Title
      {{ campaign.title }} Check Progress
    {% else %} {% if projects %} All Campaigns

    Projects

    Total Assets: {{ totalassets }} {% endif %} {% endif %} {% if messages %}

    Messages

      {% for message in messages %}
    • {{ message }}
    • {% endfor %}
    {% endif %}
    {% endblock content %} ================================================ FILE: concordia/templates/admin/clear_cache.html ================================================ {% extends "admin/base.html" %} {% block extrahead %} {{ block.super }} {% endblock %} {% block content %}

    Don't do this if you don't know what you're doing.

    Don't do this if you don't know what you're doing.

    {% csrf_token %} {{ form.as_p }}
    {% endblock content %} ================================================ FILE: concordia/templates/admin/concordia/asset/change_form.html ================================================ {% extends "admin/change_form.html" %} {% load i18n admin_urls humanize %} {% block object-tools-items %} {% if original.pk %}
  • Campaign
  • Project
  • Item
  • Transcriptions
  • {% endif %} {{ block.super }} {% endblock object-tools-items %} {% block content %} {% if original %}
    {% csrf_token %} {# Manually create the hidden PK field #} {# We use this to send the user back to this page instead of leaving them on the changelist #} {{ status_action_form.action }}
    {% endif %}

    Current status: {{ original.get_transcription_status_display }}

    {{ block.super }}

    Current status: {{ original.transcription_status }}

    {% if transcriptions %} {% for t in transcriptions %} {% endfor %}
    Transcription History
    ID Creator Created Updated Submitted Review Status
    {{ t.id }} {{ t.user }} {{ t.created_on|naturaltime }} {{ t.updated_on|naturaltime }} {{ t.submitted|naturaltime|default:'' }} {% if t.rejected %} Rejected {% elif t.accepted %} Accepted {% endif %} {% if t.rejected or t.accepted %} by {{ t.reviewed_by }} {% endif %} {% if t.rejected %} {{ t.rejected|naturaltime }} {% elif t.accepted %} {{ t.accepted|naturaltime }} {% endif %}
    {% endif %} {% endblock content %} ================================================ FILE: concordia/templates/admin/concordia/asset/change_list.html ================================================ {% extends "admin/change_list.html" %} {% block result_list %} {% block pagination %} {{ block.super }} {% endblock %} {{ block.super }} {% endblock %} ================================================ FILE: concordia/templates/admin/concordia/campaign/change_form.html ================================================ {% extends "admin/change_form.html" %} {% load i18n admin_urls static %} {% block object-tools-items %} {% if original.pk %}
  • Export CSV
  • Export BagIt
  • Report
  • {% if perms.concordia.retire_campaign and original.status != 1 %} {# Hide if campaign is active #}
  • Retire
  • {% endif %}
  • Projects
  • Items
  • Assets
  • {% endif %} {{ block.super }} {% endblock %} ================================================ FILE: concordia/templates/admin/concordia/campaign/retire.html ================================================ {% extends "admin/base_site.html" %} {% load i18n admin_urls static %} {% block extrahead %} {{ block.super }} {{ media }} {% endblock %} {% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} delete-confirmation{% endblock %} {% block breadcrumbs %} {% endblock %} {% block content %} {% block delete_confirm %}

    Are you sure you want to retire the {{ object_name }} "{{ object }}"? All of the following related items will be deleted:

    {% include "admin/includes/object_delete_summary.html" %}
    {% csrf_token %}
    {% endblock %} {% endblock content %} ================================================ FILE: concordia/templates/admin/concordia/item/change_form.html ================================================ {% extends "admin/change_form.html" %} {% load i18n admin_urls static %} {% block object-tools-items %} {% if original.pk %}
  • Export BagIt
  • Campaign
  • Project
  • Assets
  • {% endif %} {{ block.super }} {% endblock %} ================================================ FILE: concordia/templates/admin/concordia/project/change_form.html ================================================ {% extends "admin/change_form.html" %} {% load i18n admin_urls static %} {% block object-tools-items %} {% if original.pk %}
  • Export CSV
  • Export BagIt
  • Import Items
  • Campaign
  • Items
  • Assets
  • {% endif %} {{ block.super }} {% endblock %} ================================================ FILE: concordia/templates/admin/concordia/project/item_import.html ================================================ {% extends "admin/change_form.html" %} {% load i18n admin_urls %} {% block content %}
    {% if import_job %}

    Task ID {{ import_job }} created to import {{ form.cleaned_data.import_url }}

    {% else %}
    {% csrf_token %} {{ form.non_field_errors }} {% if form.errors %}

    Please fix the errors below:

    {% endif %}
    {{ form.import_url }}
      {% for error in form.import_url.errors %}
    • {{ error }}
    • {% endfor %}
    • https://www.loc.gov/item/mss859430231
    • https://www.loc.gov/collections/branch-rickey-papers/
    • https://www.loc.gov/item/mss859430231
    • https://www.loc.gov/search/?q=group%3Amal&fa=online-format!%3Aonline+text
    {% endif %}
    {% endblock content %} ================================================ FILE: concordia/templates/admin/concordia/simplepage/change_form.html ================================================ {% extends "admin/change_form.html" %} {% block extrahead %} {{ block.super }} {% include 'fragments/codemirror.html' %} {% endblock extrahead %} {% block content %} {{ block.super }} {% endblock content %} ================================================ FILE: concordia/templates/admin/concordia/transcription/change_form.html ================================================ {% extends "admin/change_form.html" %} {% load i18n admin_urls humanize %} {% block object-tools-items %} {% if original.pk %}
  • Campaign
  • Project
  • Item
  • Asset
  • {% if original.supersedes_id %}
  • Previous Version
  • {% endif %} {% with original.superseded_by.first as superseded_by %} {% if superseded_by %}
  • Next Version
  • {% endif %} {% endwith %} {% endif %} {{ block.super }} {% endblock object-tools-items %} ================================================ FILE: concordia/templates/admin/index.html ================================================ {% extends 'admin/index.html' %} {% load static i18n %} {% block sidebar %} {% endblock sidebar%} ================================================ FILE: concordia/templates/admin/long_name_filter.html ================================================ {% load i18n %}

    {% blocktranslate with filter_title=title %} By {{ filter_title }} {% endblocktranslate %}

    ================================================ FILE: concordia/templates/admin/process_bagit.html ================================================ {% extends "admin/base.html" %} {% block messages %} {% comment %} This is displayed elswhere {% endcomment %} {% endblock messages %} {% block extrahead %} {{ block.super }} {% endblock %} {% block content %}

    This feature will accept a zip file, process and convert to Loc.gov structure and re-zip it back

    {% csrf_token %} {{ form.as_p }}
    {% if messages %}

    Messages

      {% for message in messages %}
    • {{ message }}
    • {% endfor %}
    {% endif %}
    {% endblock content %} ================================================ FILE: concordia/templates/admin/project_level_export.html ================================================ {% extends "admin/base.html" %} {% block messages %} {% comment %} This is displayed elswhere {% endcomment %} {% endblock messages %} {% block extrahead %} {{ block.super }} {% endblock %} {% block content %}
    {% if campaigns %}

    Campaigns

      {% for campaign in campaigns %} {% endfor %}
      Campaign Title
      {{ campaign.title }} List Projects
    {% else %} {% if projects %} All Campaigns

    Projects

    {% csrf_token %}
      {% for project in projects %} {% endfor %}
      Project Title
      {{ project.title }}
    {% endif %} {% endif %} {% if messages %}

    Messages

      {% for message in messages %}
    • {{ message }}
    • {% endfor %}
    {% endif %}
    {% endblock content %} ================================================ FILE: concordia/templates/base.html ================================================ {% spaceless %} {% load static django_vite %} {% endspaceless %} {% block full_title %}By the People {% block title %} {% if title %} {{ title }}{% else %}Untitled {% endif %} {% endblock title %} {% endblock full_title %} {% include "fragments/common-stylesheets.html" %} {% block prefetch %} {% if CONCORDIA_ENVIRONMENT == "production" %} {% endif %} {% endblock prefetch %} {% block head_content %} {% vite_hmr_client %} {% endblock head_content %} {% block extra_scripts %}{% endblock %} {% comment %} Adobe's tag manager requires this script to be placed at the top even though it's bad for performance: {% endcomment %} {% if CONCORDIA_ENVIRONMENT == "production" %} {% else %} {% endif %} {% block site-header %} {% endblock site-header %} {% block breadcrumbs-container %} {% endblock breadcrumbs-container %} {% block site-main %}
    {% block messages-container %}