├── .dockerignore ├── .editorconfig ├── .env.example ├── .git-blame-ignore-revs ├── .gitguardian.yaml ├── .github ├── CONTRIBUTING.MD ├── FUNDING.YML ├── WORKFLOW.MD └── workflows │ ├── backend_check.yml │ └── build-image.yml ├── .gitignore ├── .gitmodules ├── .idea ├── .gitignore ├── icon.svg └── runConfigurations │ ├── build.xml │ ├── collectstatic.xml │ ├── compilemessages.xml │ ├── dev.xml │ ├── dev_server.xml │ ├── generate_causes.xml │ ├── generate_donations.xml │ ├── generate_orgs.xml │ ├── generate_orgs_valid.xml │ ├── makemessages.xml │ ├── makemigrations.xml │ ├── migrate.xml │ ├── qcluster.xml │ ├── seed_groups.xml │ ├── server.xml │ └── shell.xml ├── .vscode ├── launch.json └── settings.json ├── LICENSE ├── Makefile ├── README.md ├── TODO.md ├── backend ├── .nvmrc ├── assets │ ├── combobox.js │ ├── imageUpload.js │ ├── main.css │ ├── main.js │ ├── ngoSearch.js │ ├── select.js │ ├── signature.js │ └── twoPercentForm.js ├── donations │ ├── __init__.py │ ├── admin │ │ ├── __init__.py │ │ ├── byof.py │ │ ├── causes.py │ │ ├── common.py │ │ ├── donors.py │ │ ├── download_jobs.py │ │ ├── jobs.py │ │ └── ngos.py │ ├── apps.py │ ├── callbacks │ │ └── __init__.py │ ├── common │ │ ├── __init__.py │ │ ├── models_hashing.py │ │ └── validation │ │ │ ├── __init__.py │ │ │ ├── clean_slug.py │ │ │ ├── phone_number.py │ │ │ ├── registration_number.py │ │ │ └── validate_slug.py │ ├── context_processors.py │ ├── forms │ │ ├── __init__.py │ │ ├── account.py │ │ ├── common.py │ │ ├── ngo_account.py │ │ └── redirection.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ ├── clean_ngo_county_region.py │ │ │ ├── download_donations.py │ │ │ ├── generate_donations.py │ │ │ ├── generate_orgs.py │ │ │ ├── generate_other_causes.py │ │ │ └── registration_numbers_cleanup.py │ ├── middleware.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_initial.py │ │ ├── 0003_alter_ngo_name_alter_ngo_slug.py │ │ ├── 0004_alter_donor_date_created_alter_ngo_date_created.py │ │ ├── 0005_alter_donor_ngo.py │ │ ├── 0006_remove_job_url_job_date_finished_job_zip.py │ │ ├── 0007_alter_donor_date_created_alter_ngo_date_created.py │ │ ├── 0008_alter_job_options_alter_job_ngo_alter_job_owner.py │ │ ├── 0009_alter_donor_date_created_alter_ngo_date_created.py │ │ ├── 0010_alter_donor_date_created_alter_ngo_date_created.py │ │ ├── 0011_alter_donor_first_name_alter_donor_last_name.py │ │ ├── 0012_remove_ngo_form_url_remove_ngo_image_and_more.py │ │ ├── 0013_rename_has_special_status_ngo_is_social_service_viable_and_more.py │ │ ├── 0014_rename_first_name_donor_l_name.py │ │ ├── 0015_rename_last_name_donor_f_name.py │ │ ├── 0016_remove_donor_pdf_url.py │ │ ├── 0017_donor_anaf_gdpr.py │ │ ├── 0018_alter_ngo_is_accepting_forms.py │ │ ├── 0019_add_romanian_unaccent.py │ │ ├── 0020_ngo_display_email_ngo_display_phone.py │ │ ├── 0021_ngo_locality.py │ │ ├── 0022_cause_donor_cause_job_cause.py │ │ ├── 0023_auto_20250218_1306.py │ │ ├── 0024_alter_cause_prefilled_form.py │ │ ├── 0025_alter_donor_f_name_alter_donor_l_name_and_more.py │ │ ├── 0026_job_number_of_donations.py │ │ ├── 0027_donor_is_available.py │ │ ├── 0028_cause_is_main_cause_ngo_main_cause_unique.py │ │ ├── 0029_cause_visibility.py │ │ ├── 0030_alter_cause_is_main.py │ │ ├── 0031_cause_notification_email.py │ │ ├── 0032_initialize_notifications_email.py │ │ ├── 0033_auto_20250409_1226.py │ │ ├── 0034_cause_filename_cache.py │ │ ├── 0035_alter_ngo_bank_account_alter_ngo_description_and_more.py │ │ ├── 0036_ownformsupload_redirectionsdownloadjob.py │ │ ├── 0037_remove_ngo_slug__unique_remove_ngo_bank_account_and_more.py │ │ └── __init__.py │ ├── models │ │ ├── __init__.py │ │ ├── byof.py │ │ ├── common.py │ │ ├── donors.py │ │ ├── downloads.py │ │ ├── jobs.py │ │ └── ngos.py │ ├── pdf.py │ ├── templatetags │ │ ├── __init__.py │ │ ├── cause_action_menu.py │ │ ├── dictionary_management.py │ │ ├── elided_pagination.py │ │ ├── redirection_action_menu.py │ │ ├── redirection_helpers.py │ │ ├── text_format.py │ │ └── url_extras.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_donor_encryption.py │ │ ├── test_ngo_cif_validation.py │ │ ├── test_ngo_updates_remove_prefilled_forms.py │ │ └── test_redirection_form_signature.py │ ├── urls_ngo_account.py │ ├── views │ │ ├── __init__.py │ │ ├── account_management.py │ │ ├── api.py │ │ ├── api_download.py │ │ ├── base.py │ │ ├── common │ │ │ ├── __init__.py │ │ │ ├── misc.py │ │ │ └── search.py │ │ ├── dashboard │ │ │ ├── __init__.py │ │ │ ├── admin_dashboard.py │ │ │ ├── dashboard.py │ │ │ ├── helpers.py │ │ │ └── ngo_dashboard.py │ │ ├── download_donations │ │ │ ├── __init__.py │ │ │ ├── build_xml.py │ │ │ ├── byof.py │ │ │ ├── common.py │ │ │ └── main.py │ │ ├── errors.py │ │ ├── ngo_account │ │ │ ├── __init__.py │ │ │ ├── byof.py │ │ │ ├── causes.py │ │ │ ├── common.py │ │ │ ├── my_organization.py │ │ │ ├── redirections.py │ │ │ └── user_settings.py │ │ ├── ngo_account_filters.py │ │ ├── redirections.py │ │ └── site.py │ └── workers │ │ ├── __init__.py │ │ └── update_organization.py ├── frequent_questions │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── data │ │ ├── seed-data.html │ │ └── seed-data.json │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_alter_question_options_alter_section_options.py │ │ └── __init__.py │ ├── models.py │ └── views.py ├── importer │ ├── __init__.py │ ├── apps.py │ ├── extract.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_alter_importjob_import_type.py │ │ ├── 0003_alter_importjob_csv_file.py │ │ ├── 0004_alter_importjob_options_remove_importjob_csv_file_and_more.py │ │ ├── 0005_delete_importjob.py │ │ └── __init__.py │ └── tasks │ │ ├── __init__.py │ │ ├── donor_forms.py │ │ ├── repair_addresses.py │ │ └── utils.py ├── locale │ ├── en │ │ └── LC_MESSAGES │ │ │ └── django.po │ ├── hy │ │ └── LC_MESSAGES │ │ │ └── django.po │ └── ro │ │ └── LC_MESSAGES │ │ └── django.po ├── locale_localflavor │ ├── en │ │ └── LC_MESSAGES │ │ │ └── django.po │ └── ro │ │ └── LC_MESSAGES │ │ └── django.po ├── manage.py ├── package-lock.json ├── package.json ├── partners │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── context_processors.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ └── generate_partners.py │ ├── middleware.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_alter_partner_ngos.py │ │ ├── 0003_partner_display_ordering_alter_partner_subdomain.py │ │ ├── 0004_partnerngo.py │ │ ├── 0005_auto_20250211_1317.py │ │ ├── 0006_remove_partner_ngos.py │ │ ├── 0007_partner_ngos.py │ │ ├── 0008_alter_partner_display_ordering.py │ │ ├── 0009_partnercause_partner_causes_and_more.py │ │ ├── 0010_auto_20250219_1900.py │ │ ├── 0011_partner_custom_cta.py │ │ ├── 0012_auto_20250512_1408.py │ │ └── __init__.py │ ├── models.py │ ├── tests │ │ ├── __init__.py │ │ └── test_middleware.py │ └── views.py ├── postcss.config.js ├── pyproject.toml ├── q_heartbeat │ ├── __init__.py │ ├── apps.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ ├── qheartbeat.py │ │ │ └── schedule_qheartbeat.py │ └── migrations │ │ └── __init__.py ├── redirectioneaza │ ├── __init__.py │ ├── asgi.py │ ├── callbacks.py │ ├── common │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── app_url.py │ │ ├── async_wrapper.py │ │ ├── cache.py │ │ ├── clean.py │ │ ├── filters.py │ │ ├── messaging.py │ │ ├── testing.py │ │ └── validators.py │ ├── context_processors │ │ ├── __init__.py │ │ ├── feature_flags.py │ │ ├── headers.py │ │ └── variables.py │ ├── settings │ │ ├── __init__.py │ │ ├── app_configs.py │ │ ├── auth.py │ │ ├── base.py │ │ ├── cache.py │ │ ├── captcha_analytics.py │ │ ├── constants.py │ │ ├── database.py │ │ ├── django_q.py │ │ ├── email.py │ │ ├── environment.py │ │ ├── feature_flags.py │ │ ├── i18n.py │ │ ├── logging.py │ │ ├── storages.py │ │ ├── templates.py │ │ └── unfold.py │ ├── social_adapters.py │ ├── urls.py │ ├── views.py │ └── wsgi.py ├── requirements-dev.in ├── requirements-dev.txt ├── requirements.in ├── requirements.txt ├── static_extras │ ├── files │ │ └── sample.csv │ ├── font │ │ └── opensans.ttf │ ├── images │ │ ├── checkmark-verified.svg │ │ ├── code4romania-inversed.png │ │ ├── code4romania.png │ │ ├── envelope-received.svg │ │ ├── favicon │ │ │ ├── android-chrome-192x192.png │ │ │ ├── android-chrome-512x512.png │ │ │ ├── apple-touch-icon.png │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── favicon.ico │ │ │ └── site.webmanifest │ │ ├── first_page.jpg │ │ ├── footer-logo.png │ │ ├── formular-2019.jpg │ │ ├── formular-2021.jpg │ │ ├── formular-2025.jpg │ │ ├── hero-partner.svg │ │ ├── hero.svg │ │ ├── hub.png │ │ ├── icons │ │ │ ├── envelope.svg │ │ │ ├── question-mark-circle.svg │ │ │ ├── search.svg │ │ │ └── star.svg │ │ ├── logo-homepage.png │ │ ├── logo-smaller.png │ │ ├── logo.png │ │ ├── logo.svg │ │ ├── logo_bw.png │ │ ├── logo_gray.png │ │ ├── ngohub-screenshot.png │ │ ├── placeholder.png │ │ ├── second_page.jpg │ │ ├── semnatura.png │ │ ├── social-icons │ │ │ ├── facebook.png │ │ │ ├── github.png │ │ │ └── instagram.png │ │ └── thank_you.jpg │ └── js │ │ └── pristine.min.js ├── tailwind.config.js ├── templates │ ├── v2 │ │ ├── DESCRIERE.html │ │ ├── account │ │ │ ├── components │ │ │ │ ├── email-input.html │ │ │ │ └── password-input.html │ │ │ ├── errors │ │ │ │ ├── base.html │ │ │ │ └── login │ │ │ │ │ ├── app_missing.html │ │ │ │ │ ├── base_login.html │ │ │ │ │ ├── multiple_ngos.html │ │ │ │ │ ├── unknown_error.html │ │ │ │ │ └── unknown_role.html │ │ │ ├── login.html │ │ │ ├── register.html │ │ │ ├── reset-password.html │ │ │ ├── set-password.html │ │ │ ├── signup-confirmation.html │ │ │ ├── signup-verification.html │ │ │ └── snippets │ │ │ │ ├── cta-ngohub.html │ │ │ │ ├── errors.html │ │ │ │ ├── login-form.html │ │ │ │ ├── register-form.html │ │ │ │ └── third-party.html │ │ ├── admin │ │ │ └── base.html │ │ ├── allauth │ │ │ └── layouts │ │ │ │ └── base.html │ │ ├── base.html │ │ ├── components │ │ │ ├── action-menu │ │ │ │ ├── item-action.html │ │ │ │ ├── item_href.html │ │ │ │ └── main.html │ │ │ ├── badge.html │ │ │ ├── buttons │ │ │ │ └── link.html │ │ │ ├── copy-to-clipboard │ │ │ │ ├── base.html │ │ │ │ ├── icon.html │ │ │ │ └── text.html │ │ │ ├── cta-redirection.html │ │ │ ├── form │ │ │ │ └── legend.html │ │ │ ├── info-banner.html │ │ │ ├── input │ │ │ │ ├── base.html │ │ │ │ ├── checkbox.html │ │ │ │ ├── combobox.html │ │ │ │ ├── county.html │ │ │ │ ├── filter.html │ │ │ │ ├── help-text.html │ │ │ │ ├── image.html │ │ │ │ ├── input.html │ │ │ │ ├── search-box.html │ │ │ │ ├── select.html │ │ │ │ └── textarea.html │ │ │ ├── logo-or-default.html │ │ │ └── notifications │ │ │ │ ├── message.html │ │ │ │ └── messages.html │ │ ├── emails │ │ │ ├── account │ │ │ │ ├── activate-account │ │ │ │ │ ├── main.html │ │ │ │ │ └── main.txt │ │ │ │ ├── ignore-email.html │ │ │ │ ├── ignore-email.txt │ │ │ │ ├── invite-partner │ │ │ │ │ ├── main.html │ │ │ │ │ └── main.txt │ │ │ │ ├── ngohub-notification │ │ │ │ │ ├── main.html │ │ │ │ │ └── main.txt │ │ │ │ └── reset-password │ │ │ │ │ ├── main.html │ │ │ │ │ └── main.txt │ │ │ ├── admin │ │ │ │ └── new-ngo │ │ │ │ │ ├── main.html │ │ │ │ │ └── main.txt │ │ │ ├── base-footer.html │ │ │ ├── base-header.html │ │ │ ├── base.html │ │ │ ├── components │ │ │ │ ├── action.html │ │ │ │ ├── extra-hand.html │ │ │ │ ├── extra-hand.txt │ │ │ │ ├── misdelivered.html │ │ │ │ ├── misdelivered.txt │ │ │ │ ├── panel.html │ │ │ │ └── subcopy.html │ │ │ ├── donor │ │ │ │ ├── redirection-sans-signature │ │ │ │ │ ├── main.html │ │ │ │ │ └── main.txt │ │ │ │ ├── redirection-with-signature │ │ │ │ │ ├── main.html │ │ │ │ │ └── main.txt │ │ │ │ └── removed-redirection │ │ │ │ │ ├── main.html │ │ │ │ │ └── main.txt │ │ │ ├── ngo │ │ │ │ ├── download-archive │ │ │ │ │ ├── main.html │ │ │ │ │ └── main.txt │ │ │ │ ├── download-csv │ │ │ │ │ ├── main.html │ │ │ │ │ └── main.txt │ │ │ │ └── new-form-received │ │ │ │ │ ├── main.html │ │ │ │ │ └── main.txt │ │ │ └── styles.css │ │ ├── errors │ │ │ ├── 400.html │ │ │ ├── 403.html │ │ │ ├── 404.html │ │ │ ├── 500.html │ │ │ ├── base.html │ │ │ └── other.html │ │ ├── form │ │ │ ├── header │ │ │ │ ├── main.html │ │ │ │ └── other-form.html │ │ │ ├── modals │ │ │ │ ├── confirmation.html │ │ │ │ └── signature.html │ │ │ ├── redirection-closed.html │ │ │ ├── redirection-header.html │ │ │ ├── redirection-open.html │ │ │ ├── redirection.html │ │ │ └── success │ │ │ │ ├── main.html │ │ │ │ ├── signed.html │ │ │ │ └── unsigned.html │ │ ├── layouts │ │ │ └── content-and-image-page.html │ │ ├── ngo-account │ │ │ ├── archives │ │ │ │ ├── list-header.html │ │ │ │ ├── list-items.html │ │ │ │ ├── listing.html │ │ │ │ └── main.html │ │ │ ├── banners │ │ │ │ ├── form-emails-banner.html │ │ │ │ ├── form-ngohub.html │ │ │ │ └── form-notifications-banner.html │ │ │ ├── base-listing.html │ │ │ ├── base.html │ │ │ ├── byof │ │ │ │ ├── list-header.html │ │ │ │ ├── list-items.html │ │ │ │ ├── listing.html │ │ │ │ ├── main.html │ │ │ │ ├── modal.html │ │ │ │ └── upload.html │ │ │ ├── cause │ │ │ │ ├── form.html │ │ │ │ └── main.html │ │ │ ├── causes │ │ │ │ ├── list-header.html │ │ │ │ ├── list-items.html │ │ │ │ ├── listing.html │ │ │ │ ├── main.html │ │ │ │ └── visibility-badge.html │ │ │ ├── components │ │ │ │ ├── filters.html │ │ │ │ ├── form-title.html │ │ │ │ ├── ngo-switch-tab-script.html │ │ │ │ └── tab.html │ │ │ ├── my-organization │ │ │ │ ├── base.html │ │ │ │ ├── ngo-form.html │ │ │ │ └── ngo-presentation.html │ │ │ ├── redirections-downloads │ │ │ │ ├── list-header.html │ │ │ │ ├── list-items.html │ │ │ │ ├── listing.html │ │ │ │ └── main.html │ │ │ ├── redirections │ │ │ │ ├── base.html │ │ │ │ ├── download-filtered │ │ │ │ │ └── button.html │ │ │ │ ├── generate-archive │ │ │ │ │ ├── button.html │ │ │ │ │ ├── multiple-button.html │ │ │ │ │ ├── multiple-dropdown.html │ │ │ │ │ └── single-button.html │ │ │ │ ├── list-header.html │ │ │ │ ├── list-items.html │ │ │ │ ├── listing.html │ │ │ │ └── main.html │ │ │ └── settings-account │ │ │ │ └── main.html │ │ ├── public │ │ │ ├── all-causes.html │ │ │ ├── all-ngos.html │ │ │ ├── articles │ │ │ │ ├── about.html │ │ │ │ ├── base.html │ │ │ │ ├── note.html │ │ │ │ ├── policy.html │ │ │ │ └── terms.html │ │ │ ├── components │ │ │ │ ├── cause-search.html │ │ │ │ ├── faq-question.html │ │ │ │ ├── grid-card.html │ │ │ │ ├── home │ │ │ │ │ ├── dropdown-search.html │ │ │ │ │ ├── explore.html │ │ │ │ │ ├── hero-stats.html │ │ │ │ │ └── hero.html │ │ │ │ └── pagination.html │ │ │ ├── faq.html │ │ │ └── home.html │ │ ├── redirect │ │ │ ├── commitglobal-banner.html │ │ │ ├── footer.html │ │ │ └── header │ │ │ │ ├── desktop-item.html │ │ │ │ ├── desktop.html │ │ │ │ ├── dropdown-item.html │ │ │ │ ├── main.html │ │ │ │ ├── mobile-item.html │ │ │ │ └── mobile.html │ │ └── socialaccount │ │ │ └── login.html │ └── v3 │ │ ├── admin │ │ ├── announcements │ │ │ ├── base.html │ │ │ └── work_in_progress.html │ │ ├── base_admin.html │ │ ├── dashboard_components │ │ │ ├── header_stats.html │ │ │ ├── monthly_forms_chart.html │ │ │ ├── table_stats.html │ │ │ └── yearly_stats.html │ │ ├── forms │ │ │ └── action.html │ │ └── index.html │ │ ├── redirect │ │ └── components │ │ │ ├── card.html │ │ │ └── dropdown_navigation.html │ │ └── robots.txt ├── users │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── context_processors.py │ ├── groups_management.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ ├── _private │ │ │ ├── __init__.py │ │ │ └── seed_user.py │ │ │ ├── schedule_session_cleanup.py │ │ │ ├── seed_groups.py │ │ │ ├── seed_superuser.py │ │ │ └── wait_for_db.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_user_date_created_user_date_updated_and_more.py │ │ ├── 0003_alter_user_date_created.py │ │ ├── 0004_alter_user_date_created.py │ │ ├── 0005_alter_user_date_created.py │ │ ├── 0006_alter_user_date_created.py │ │ ├── 0007_alter_user_options.py │ │ ├── 0008_groupproxy.py │ │ ├── 0009_user_is_ngohub_user.py │ │ ├── 0010_user_partner.py │ │ ├── 0011_alter_user_is_ngohub_user.py │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py └── vite.config.js ├── docker-compose.base.yml ├── docker-compose.dbless.yml ├── docker-compose.prod.yml ├── docker-compose.yml ├── docker ├── dockerfiles │ ├── Dockerfile │ └── Dockerfile.dev ├── nginx │ └── nginx.conf └── s6-rc.d │ ├── backend │ ├── dependencies │ ├── run │ └── type │ ├── cron │ ├── dependencies │ ├── run │ └── type │ ├── frontend_dev │ ├── dependencies │ ├── run │ └── type │ ├── init │ ├── init.sh │ ├── type │ └── up │ ├── nginx │ ├── dependencies │ ├── run │ └── type │ ├── qcluster │ ├── dependencies │ ├── run │ └── type │ └── user │ └── contents.d │ ├── backend │ ├── frontend_dev │ ├── init │ ├── nginx │ └── qcluster └── terraform ├── .gitignore ├── .terraform.lock.hcl ├── README.md ├── acm.tf ├── cloudfront.tf ├── data.tf ├── database.tf ├── ec2_bastion.tf ├── ecs.tf ├── functions └── www-redirect.js ├── iam.tf ├── lb.tf ├── locals.tf ├── main.tf ├── modules ├── ecs-cluster │ ├── assets │ │ └── user_data.sh │ ├── cloudwatch.tf │ ├── data.tf │ ├── iam.tf │ ├── locals.tf │ ├── main.tf │ ├── outputs.tf │ ├── service_discovery.tf │ └── variables.tf ├── ecs-service │ ├── autoscaling.tf │ ├── data.tf │ ├── ecs_task_definition.tf │ ├── iam.tf │ ├── lb.tf │ ├── locals.tf │ ├── main.tf │ ├── outputs.tf │ ├── route53.tf │ ├── service_discovery.tf │ └── variables.tf └── s3 │ ├── main.tf │ ├── outputs.tf │ ├── random.tf │ └── variables.tf ├── networking_eips.tf ├── networking_gateways.tf ├── networking_routing.tf ├── networking_subnets.tf ├── networking_vpc.tf ├── outputs.tf ├── providers.tf ├── random.tf ├── route53.tf ├── ses.tf └── variables.tf /.dockerignore: -------------------------------------------------------------------------------- 1 | _appengine_legacy 2 | 3 | .db_sqlite/* 4 | 5 | **/media 6 | **/static 7 | **/bower_components 8 | **/dist 9 | 10 | **/node_modules 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | max_line_length = 120 10 | tab_width = 2 11 | trim_trailing_whitespace = true 12 | 13 | [*.html] 14 | max_line_length = 240 15 | 16 | [Makefile] 17 | tab_width = 4 18 | indent_style = tab 19 | max_line_length = 240 20 | 21 | [*.py] 22 | indent_size = 4 23 | tab_width = 4 24 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Add all the commits you would like to be omitted from the `git blame` output here. 2 | # Add one commit per line and a comment with the reason for the omission. 3 | 4 | # In order to use this file, you either need to add it to your config: 5 | # git config blame.ignoreRevsFile ".git-blame-ignore-revs" 6 | # or pass it as an argument to `git blame`: 7 | # git blame --ignore-revs-file ".git-blame-ignore-revs" 8 | 9 | # To remove the settings from your config: 10 | # git config --unset blame.ignoreRevsFile 11 | # or pass the --ignore-revs-file option with an empty value: 12 | # git blame --ignore-revs-file="" 13 | 14 | # While this setting isn't automatically picked up git, it is used by GitHub based on the file name 15 | 16 | ######### COMMITS IGNORED FROM BLAME ######### 17 | 18 | # General formatting of all the template files 19 | 6677d4a6b3a13c690fa4d22d3fb214fc151e8853 20 | 21 | # Remove closing tags 22 | 4e85a0ffdb47a1d1fceb81c46147ffc87fe1aa87 23 | -------------------------------------------------------------------------------- /.gitguardian.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | secret: 4 | ignored-paths: 5 | - '**/tests.py' 6 | 7 | ignored-matches: 8 | - name: reCAPTCHA Key 9 | match: "6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe" 10 | - name: reCAPTCHA Key 11 | match: "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI" 12 | - name: Django Secret Key 13 | match: "django-insecure-#*_9knm02y(3)+mq41ns1%*!iz((u-wnbq62f(%i(qh7fyu9@+" 14 | - name: Django Secret Key 15 | match: "django-insecure-demo-key" 16 | -------------------------------------------------------------------------------- /.github/FUNDING.YML: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | custom: https://code4.ro/en/donate/ 4 | -------------------------------------------------------------------------------- /.github/workflows/build-image.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - 'main' 5 | - 'refactor/frontend' 6 | - 'feature/multi-forms/main' 7 | tags: 8 | - 'v*' 9 | paths: 10 | - 'backend/**' 11 | - 'docker/**' 12 | - '.github/workflows/build-image.yml' 13 | 14 | name: Build Docker image 15 | 16 | jobs: 17 | 18 | build: 19 | name: Build Docker image 20 | uses: code4romania/.github/.github/workflows/build-push-image.yml@main 21 | with: 22 | images: code4romania/redirectioneaza 23 | context: ./ 24 | dockerfile: ./docker/dockerfiles/Dockerfile 25 | secrets: 26 | username: ${{ secrets.DOCKER_HUB_USERNAME }} 27 | token: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} 28 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/redirectioneaza/a917780e65f4923798b75a12abc1b1d93bdda82e/.gitmodules -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all root XMLs 2 | /*.xml 3 | 4 | /codeStyles/ 5 | /inspectionProfiles/ 6 | /libraries/ 7 | 8 | # Default ignored files, generated by the IDE 9 | /shelf/ 10 | /workspace.xml 11 | # Editor-based HTTP Client requests 12 | /httpRequests/ 13 | # Datasource local storage ignored files 14 | /dataSources/ 15 | /dataSources.local.xml 16 | -------------------------------------------------------------------------------- /.idea/runConfigurations/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | {{ form.media }} 11 | {% endblock %} 12 | 13 | {% block content %} 14 |
15 |
16 | {% csrf_token %} 17 | 18 | {% for field in form %} 19 | {% include "unfold/helpers/field.html" with field=field %} 20 | {% endfor %} 21 |
22 | 23 |
24 | {% component "unfold/components/button.html" with submit=1 %} 25 | {% trans "Send the form" %} 26 | {% endcomponent %} 27 |
28 |
29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /backend/templates/v3/admin/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'admin/base_admin.html' %} 2 | 3 | {% load unfold i18n %} 4 | 5 | {% block body %} 6 | {% component "unfold/components/container.html" %} 7 | 8 | {% if header_stats %} 9 | {% include "admin/dashboard_components/header_stats.html" with header_stats=header_stats %} 10 | {% endif %} 11 | 12 | 13 | {% if forms_per_month_chart %} 14 | {% include "admin/dashboard_components/monthly_forms_chart.html" with forms_per_month_chart=forms_per_month_chart %} 15 | {% endif %} 16 | 17 | 18 | {% if yearly_stats %} 19 | {% include "admin/dashboard_components/yearly_stats.html" with yearly_stats=yearly_stats %} 20 | {% endif %} 21 | 22 | 23 | {% if table_stats %} 24 | {% include "admin/dashboard_components/table_stats.html" with table_stats=table_stats %} 25 | {% endif %} 26 | 27 | {% endcomponent %} 28 | 29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /backend/templates/v3/redirect/components/card.html: -------------------------------------------------------------------------------- 1 |
2 | {% if title %} 3 |

4 | {{ title }} 5 |

6 | {% endif %} 7 | 8 |
9 | {{ children }} 10 | 11 | {% if label %} 12 |
13 | {% include "unfold/helpers/label.html" with text=label type="primary" %} 14 |
15 | {% endif %} 16 | 17 | {% if icon %} 18 | {{ icon }} 19 | {% endif %} 20 |
21 | 22 | {% if footer %} 23 | 26 | {% endif %} 27 |
28 | -------------------------------------------------------------------------------- /backend/templates/v3/redirect/components/dropdown_navigation.html: -------------------------------------------------------------------------------- 1 | {% if items %} 2 | 19 | 20 | {% endif %} 21 | -------------------------------------------------------------------------------- /backend/templates/v3/robots.txt: -------------------------------------------------------------------------------- 1 | User-Agent: * 2 | Disallow: /admin/ 3 | Disallow: /cron/ 4 | Disallow: /allauth/ 5 | Disallow: /api/ 6 | Disallow: /contul-meu/ 7 | Disallow: /organizatia-mea/ 8 | Disallow: /ngo-forms/ 9 | Disallow: /donation-forms/ 10 | 11 | User-agent: GPTBot 12 | Disallow: / 13 | -------------------------------------------------------------------------------- /backend/users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/redirectioneaza/a917780e65f4923798b75a12abc1b1d93bdda82e/backend/users/__init__.py -------------------------------------------------------------------------------- /backend/users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | from django.utils.translation import gettext_lazy as _ 3 | 4 | 5 | class UsersConfig(AppConfig): 6 | default_auto_field = "django.db.models.BigAutoField" 7 | name = "users" 8 | verbose_name = _("Users") 9 | -------------------------------------------------------------------------------- /backend/users/context_processors.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.http import HttpRequest 3 | 4 | 5 | def get_admin_properties(request: HttpRequest): 6 | user = request.user 7 | return { 8 | "is_admin": user.has_perm("users.can_view_old_dashboard"), 9 | "is_staff": user.is_staff, 10 | "SOCIALACCOUNT_ENABLED": settings.SOCIALACCOUNT_ENABLED, 11 | "SOCIALACCOUNT_ONLY": settings.SOCIALACCOUNT_ONLY, 12 | } 13 | -------------------------------------------------------------------------------- /backend/users/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/redirectioneaza/a917780e65f4923798b75a12abc1b1d93bdda82e/backend/users/management/__init__.py -------------------------------------------------------------------------------- /backend/users/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/redirectioneaza/a917780e65f4923798b75a12abc1b1d93bdda82e/backend/users/management/commands/__init__.py -------------------------------------------------------------------------------- /backend/users/management/commands/_private/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/redirectioneaza/a917780e65f4923798b75a12abc1b1d93bdda82e/backend/users/management/commands/_private/__init__.py -------------------------------------------------------------------------------- /backend/users/management/commands/seed_superuser.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from django.conf import settings 4 | from django.contrib.auth.models import Group 5 | 6 | from users.groups_management import MAIN_ADMIN 7 | from ._private.seed_user import CommonCreateUserCommand 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | class Command(CommonCreateUserCommand): 13 | help = "Command to create a superuser" 14 | 15 | def handle(self, *args, **kwargs): 16 | kwargs["last_name"] = "Super" 17 | kwargs["first_name"] = "User" 18 | 19 | super_admin = self._get_or_create_user( 20 | new_email=settings.DJANGO_ADMIN_EMAIL, 21 | password=settings.DJANGO_ADMIN_PASSWORD, 22 | is_superuser=True, 23 | is_staff=True, 24 | first_name=kwargs.get("first_name", ""), 25 | last_name=kwargs.get("last_name", ""), 26 | ) 27 | logger.info("Super admin created successfully") 28 | 29 | admin_group = Group.objects.get(name=MAIN_ADMIN) 30 | super_admin.groups.add(admin_group) 31 | 32 | return 0 33 | -------------------------------------------------------------------------------- /backend/users/migrations/0002_user_date_created_user_date_updated_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.10 on 2024-02-15 20:39 2 | 3 | from django.db import migrations, models 4 | import django.utils.timezone 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("users", "0001_initial"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name="user", 16 | name="date_created", 17 | field=models.DateTimeField( 18 | auto_now_add=True, db_index=True, default=django.utils.timezone.now, verbose_name="date created" 19 | ), 20 | preserve_default=False, 21 | ), 22 | migrations.AddField( 23 | model_name="user", 24 | name="date_updated", 25 | field=models.DateTimeField(auto_now=True, db_index=True, verbose_name="date updated"), 26 | ), 27 | migrations.AddField( 28 | model_name="user", 29 | name="old_password", 30 | field=models.CharField(blank=True, max_length=128, null=True, verbose_name="old password"), 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /backend/users/migrations/0003_alter_user_date_created.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.10 on 2024-02-20 10:26 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("users", "0002_user_date_created_user_date_updated_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="user", 15 | name="date_created", 16 | field=models.DateTimeField(verbose_name="date created"), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/users/migrations/0004_alter_user_date_created.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.10 on 2024-02-21 14:58 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("users", "0003_alter_user_date_created"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="user", 15 | name="date_created", 16 | field=models.DateTimeField(auto_now_add=True, db_index=True, verbose_name="date created"), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/users/migrations/0005_alter_user_date_created.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.10 on 2024-02-23 09:21 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("users", "0004_alter_user_date_created"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="user", 15 | name="date_created", 16 | field=models.DateTimeField(db_index=True, verbose_name="date created"), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/users/migrations/0006_alter_user_date_created.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.10 on 2024-02-23 09:22 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("users", "0005_alter_user_date_created"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="user", 15 | name="date_created", 16 | field=models.DateTimeField(auto_now_add=True, db_index=True, verbose_name="date created"), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/users/migrations/0007_alter_user_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.10 on 2024-02-28 09:49 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("users", "0006_alter_user_date_created"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name="user", 15 | options={"permissions": (("can_view_old_dashboard", "Can view the old dashboard"),)}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/users/migrations/0008_groupproxy.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.1 on 2024-09-12 10:08 2 | 3 | import django.contrib.auth.models 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("auth", "0012_alter_user_first_name_max_length"), 11 | ("users", "0007_alter_user_options"), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name="GroupProxy", 17 | fields=[], 18 | options={ 19 | "verbose_name": "Group", 20 | "verbose_name_plural": "Groups", 21 | "proxy": True, 22 | "indexes": [], 23 | "constraints": [], 24 | }, 25 | bases=("auth.group",), 26 | managers=[ 27 | ("objects", django.contrib.auth.models.GroupManager()), 28 | ], 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /backend/users/migrations/0009_user_is_ngohub_user.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.13 on 2024-08-09 09:58 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("users", "0008_groupproxy"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="user", 15 | name="is_ngohub_user", 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/users/migrations/0010_user_partner.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.5 on 2025-02-11 13:59 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 | ("partners", "0008_alter_partner_display_ordering"), 11 | ("users", "0009_user_is_ngohub_user"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name="user", 17 | name="partner", 18 | field=models.ForeignKey( 19 | blank=True, 20 | null=True, 21 | on_delete=django.db.models.deletion.SET_NULL, 22 | related_name="users", 23 | to="partners.partner", 24 | verbose_name="Partner", 25 | ), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /backend/users/migrations/0011_alter_user_is_ngohub_user.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.1.6 on 2025-02-19 08:52 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("users", "0010_user_partner"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="user", 15 | name="is_ngohub_user", 16 | field=models.BooleanField(db_index=True, default=False, verbose_name="is ngohub user"), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/users/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/redirectioneaza/a917780e65f4923798b75a12abc1b1d93bdda82e/backend/users/migrations/__init__.py -------------------------------------------------------------------------------- /backend/users/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | -------------------------------------------------------------------------------- /docker-compose.dbless.yml: -------------------------------------------------------------------------------- 1 | name: redirect_dev 2 | 3 | services: 4 | 5 | webapp_local_psql: 6 | extends: 7 | file: docker-compose.base.yml 8 | service: redirect_dev_base 9 | environment: 10 | - "DATABASE_HOST=127.0.0.1" 11 | - "DATABASE_PORT=5432" 12 | -------------------------------------------------------------------------------- /docker-compose.prod.yml: -------------------------------------------------------------------------------- 1 | name: redirect_prod 2 | 3 | services: 4 | 5 | webapp: 6 | extends: 7 | file: docker-compose.base.yml 8 | service: redirect_base 9 | container_name: redirect_prod 10 | build: 11 | context: . 12 | dockerfile: ./docker/dockerfiles/Dockerfile 13 | volumes: 14 | - ./backend/media:/var/www/redirect/backend/media 15 | environment: 16 | - "ENVIRONMENT=production" 17 | ports: 18 | - "${WEBAPP_PORT:-8080}:80" 19 | depends_on: 20 | - db 21 | 22 | db: 23 | extends: 24 | file: docker-compose.base.yml 25 | service: db_base_psql 26 | container_name: redirect_psql 27 | 28 | volumes: 29 | redirect_psql: 30 | node_modules: 31 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | name: redirect_dev 2 | 3 | services: 4 | 5 | webapp_psql: 6 | extends: 7 | file: docker-compose.base.yml 8 | service: redirect_dev_base 9 | environment: 10 | - "DATABASE_HOST=redirect_db_dev" 11 | - "DATABASE_PORT=5432" 12 | depends_on: 13 | - db_psql_dev 14 | 15 | db_psql_dev: 16 | extends: 17 | file: docker-compose.base.yml 18 | service: db_base_psql 19 | volumes: 20 | - ./docker/init-psql:/docker-entrypoint-initdb.d 21 | 22 | volumes: 23 | redirect_psql: 24 | node_modules: 25 | -------------------------------------------------------------------------------- /docker/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen [::]:80 default_server; 3 | listen 80 default_server; 4 | server_name _; 5 | 6 | client_max_body_size 200M; 7 | 8 | location / { 9 | # proxy_set_header X-Forwarded-Proto https; 10 | proxy_set_header X-Url-Scheme $scheme; 11 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 12 | proxy_set_header Host $http_host; 13 | proxy_redirect off; 14 | proxy_pass http://unix:/run/gunicorn.sock; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docker/s6-rc.d/backend/dependencies: -------------------------------------------------------------------------------- 1 | init 2 | -------------------------------------------------------------------------------- /docker/s6-rc.d/backend/type: -------------------------------------------------------------------------------- 1 | longrun 2 | -------------------------------------------------------------------------------- /docker/s6-rc.d/cron/dependencies: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/redirectioneaza/a917780e65f4923798b75a12abc1b1d93bdda82e/docker/s6-rc.d/cron/dependencies -------------------------------------------------------------------------------- /docker/s6-rc.d/cron/run: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | touch /etc/crontab /etc/cron.*/* 4 | 5 | 6 | # Check if we have cron (Debian) or crond (Alpine) 7 | 8 | if ! type "cron" > /dev/null; then 9 | crond -f 10 | else 11 | cron -f 12 | fi 13 | -------------------------------------------------------------------------------- /docker/s6-rc.d/cron/type: -------------------------------------------------------------------------------- 1 | longrun 2 | -------------------------------------------------------------------------------- /docker/s6-rc.d/frontend_dev/dependencies: -------------------------------------------------------------------------------- 1 | init 2 | -------------------------------------------------------------------------------- /docker/s6-rc.d/frontend_dev/run: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv sh 2 | set -e 3 | 4 | cd /var/www/redirect/backend/ || exit 1 5 | 6 | [ "${ENVIRONMENT}" = "development" ] && npm run dev 7 | -------------------------------------------------------------------------------- /docker/s6-rc.d/frontend_dev/type: -------------------------------------------------------------------------------- 1 | longrun 2 | -------------------------------------------------------------------------------- /docker/s6-rc.d/init/type: -------------------------------------------------------------------------------- 1 | oneshot 2 | -------------------------------------------------------------------------------- /docker/s6-rc.d/init/up: -------------------------------------------------------------------------------- 1 | /etc/s6-overlay/s6-rc.d/init/init.sh 2 | -------------------------------------------------------------------------------- /docker/s6-rc.d/nginx/dependencies: -------------------------------------------------------------------------------- 1 | backend 2 | -------------------------------------------------------------------------------- /docker/s6-rc.d/nginx/run: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | set -e 4 | 5 | nginx -g "daemon off;" 6 | -------------------------------------------------------------------------------- /docker/s6-rc.d/nginx/type: -------------------------------------------------------------------------------- 1 | longrun 2 | -------------------------------------------------------------------------------- /docker/s6-rc.d/qcluster/dependencies: -------------------------------------------------------------------------------- 1 | init 2 | -------------------------------------------------------------------------------- /docker/s6-rc.d/qcluster/run: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv sh 2 | 3 | set -e 4 | 5 | cd /var/www/redirect/backend 6 | 7 | if [ "${ENVIRONMENT}" = "debug" ]; then 8 | echo "*********************************************" 9 | echo "*** Starting the qcluster in DEBUG mode ***" 10 | echo "*********************************************" 11 | python3 -Xfrozen_modules=off -m debugpy --listen 0.0.0.0:5677 manage.py qcluster 12 | elif [ "${ENVIRONMENT}" = "debugwait" ]; then 13 | echo "*********************************************" 14 | echo "*** Starting the qcluster in DEBUG mode ***" 15 | echo "*** Waiting for debugger connection... ***" 16 | echo "*********************************************" 17 | python3 -Xfrozen_modules=off -m debugpy --wait-for-client --listen 0.0.0.0:5677 manage.py qcluster 18 | else 19 | echo "Starting the qcluster in production mode" 20 | 21 | python3 manage.py qcluster 22 | fi 23 | 24 | -------------------------------------------------------------------------------- /docker/s6-rc.d/qcluster/type: -------------------------------------------------------------------------------- 1 | longrun 2 | -------------------------------------------------------------------------------- /docker/s6-rc.d/user/contents.d/backend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/redirectioneaza/a917780e65f4923798b75a12abc1b1d93bdda82e/docker/s6-rc.d/user/contents.d/backend -------------------------------------------------------------------------------- /docker/s6-rc.d/user/contents.d/frontend_dev: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/redirectioneaza/a917780e65f4923798b75a12abc1b1d93bdda82e/docker/s6-rc.d/user/contents.d/frontend_dev -------------------------------------------------------------------------------- /docker/s6-rc.d/user/contents.d/init: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/redirectioneaza/a917780e65f4923798b75a12abc1b1d93bdda82e/docker/s6-rc.d/user/contents.d/init -------------------------------------------------------------------------------- /docker/s6-rc.d/user/contents.d/nginx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/redirectioneaza/a917780e65f4923798b75a12abc1b1d93bdda82e/docker/s6-rc.d/user/contents.d/nginx -------------------------------------------------------------------------------- /docker/s6-rc.d/user/contents.d/qcluster: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/redirectioneaza/a917780e65f4923798b75a12abc1b1d93bdda82e/docker/s6-rc.d/user/contents.d/qcluster -------------------------------------------------------------------------------- /terraform/.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # Crash log files 9 | crash.log 10 | crash.*.log 11 | 12 | # Ignore any .tfvars files that are generated automatically for each Terraform run. Most 13 | # .tfvars files are managed as part of configuration and so should be included in 14 | # version control. 15 | *.tfvars 16 | *.tfvars.json 17 | 18 | # Ignore override files as they are usually used to override resources locally and so 19 | # are not checked in 20 | override.tf 21 | override.tf.json 22 | *_override.tf 23 | *_override.tf.json 24 | 25 | # Include override files you do wish to add to version control using negated pattern 26 | # 27 | # !example_override.tf 28 | 29 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 30 | *tfplan* 31 | 32 | # Ignore CLI configuration files 33 | .terraformrc 34 | terraform.rc 35 | 36 | .infracost 37 | -------------------------------------------------------------------------------- /terraform/README.md: -------------------------------------------------------------------------------- 1 | # terraform instructions 2 | 3 | 1. Go to [ECS Account settings](https://eu-central-1.console.aws.amazon.com/ecs/v2/account-settings?region=eu-central-1) for the region you're deploying in and make sure AWSVPC Trunking is turned on. 4 | 5 | 2. Replace the backend configuration in `providers.tf` with your own bucket name, key and region. 6 | 7 | 3. Configure the required variables from `variables.tf`. If you've ever created a cluster in this AWS account, make sure to set the `create_iam_service_linked_role` to false. 8 | 9 | Example configuration: 10 | ``` 11 | route_53_zone_id = "Z1Q2W3E4R5T6Y7" 12 | domain_name = "test.example.com" 13 | bastion_public_key = "ssh-ed25519 AAAAC3NzaC1lZD..." 14 | seed_admin_email = "test@example.com" 15 | seed_admin_password = "super_secure_random_password" 16 | create_iam_service_linked_role = false 17 | ``` 18 | 19 | 4. Run `terraform apply` and wait for the cluster to be created. 20 | 21 | 5. Once everything is done, you should be able to login at `https://[domain_name]/staff/users/login/` using the `seed_admin_email` and `seed_admin_password` you've configured. 22 | -------------------------------------------------------------------------------- /terraform/acm.tf: -------------------------------------------------------------------------------- 1 | resource "aws_acm_certificate" "main" { 2 | provider = aws.acm 3 | validation_method = "DNS" 4 | domain_name = var.domain_name 5 | subject_alternative_names = [ 6 | "*.${var.domain_name}", 7 | ] 8 | 9 | lifecycle { 10 | create_before_destroy = true 11 | } 12 | } 13 | 14 | resource "aws_route53_record" "acm_validation" { 15 | for_each = { 16 | for dvo in aws_acm_certificate.main.domain_validation_options : dvo.domain_name => { 17 | name = dvo.resource_record_name 18 | record = dvo.resource_record_value 19 | type = dvo.resource_record_type 20 | } 21 | } 22 | 23 | allow_overwrite = true 24 | name = each.value.name 25 | records = [each.value.record] 26 | ttl = 60 27 | type = each.value.type 28 | zone_id = data.aws_route53_zone.main.zone_id 29 | } 30 | -------------------------------------------------------------------------------- /terraform/data.tf: -------------------------------------------------------------------------------- 1 | data "aws_availability_zones" "current" {} 2 | 3 | data "aws_region" "current" {} 4 | 5 | data "aws_route53_zone" "main" { 6 | zone_id = var.route_53_zone_id 7 | } 8 | 9 | data "aws_ecr_repository" "this" { 10 | name = "redirectioneaza" 11 | } 12 | -------------------------------------------------------------------------------- /terraform/functions/www-redirect.js: -------------------------------------------------------------------------------- 1 | function handler(event) { 2 | if (!event.request.headers.hasOwnProperty('host')) { 3 | return { 4 | statusCode: 404, 5 | statusDescription: 'Not Found', 6 | } 7 | } 8 | 9 | if (event.request.headers.host.value.startsWith('www.')) { 10 | return { 11 | statusCode: 301, 12 | statusDescription: 'Moved Permanently', 13 | headers: { 14 | location: { 15 | value: `https://${event.request.headers.host.value.replace('www.', '')}${event.request.uri}`, 16 | }, 17 | }, 18 | } 19 | } 20 | 21 | return event.request 22 | } 23 | -------------------------------------------------------------------------------- /terraform/locals.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | namespace = "redirectioneaza-${var.env}" 3 | image_repo = "code4romania/redirectioneaza" 4 | image_tag = "3.4.12" 5 | 6 | availability_zone = data.aws_availability_zones.current.names[0] 7 | 8 | domains = [ 9 | var.domain_name, 10 | "*.${var.domain_name}", 11 | ] 12 | 13 | ecs = { 14 | instance_types = { 15 | "m5.large" = "" 16 | "m5a.large" = "" 17 | } 18 | } 19 | 20 | db = { 21 | name = "redirectioneaza" 22 | instance_class = var.env == "production" ? "db.t4g.medium" : "db.t4g.micro" 23 | } 24 | 25 | networking = { 26 | cidr_block = "10.0.0.0/16" 27 | 28 | public_subnets = [ 29 | "10.0.1.0/24", 30 | "10.0.2.0/24", 31 | "10.0.3.0/24" 32 | ] 33 | 34 | private_subnets = [ 35 | "10.0.4.0/24", 36 | "10.0.5.0/24", 37 | "10.0.6.0/24" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /terraform/modules/ecs-cluster/cloudwatch.tf: -------------------------------------------------------------------------------- 1 | ### CloudWatch Log group for container logs 2 | resource "aws_cloudwatch_log_group" "ecs" { 3 | name = "${var.name}-container-logs" 4 | retention_in_days = var.ecs_cloudwatch_log_retention 5 | 6 | tags = var.tags 7 | } 8 | 9 | resource "aws_cloudwatch_log_group" "userdata" { 10 | name = "${var.name}-ecs-nodes-userdata" 11 | retention_in_days = var.userdata_cloudwatch_log_retention 12 | 13 | tags = var.tags 14 | } 15 | -------------------------------------------------------------------------------- /terraform/modules/ecs-cluster/data.tf: -------------------------------------------------------------------------------- 1 | ### Latest Amazon Linux ECS Optimized AMI 2 | data "aws_ami" "this" { 3 | most_recent = true 4 | 5 | owners = ["amazon"] 6 | 7 | filter { 8 | name = "name" 9 | values = ["amzn2-ami-ecs-kernel-5.10-hvm-*-ebs"] 10 | } 11 | 12 | filter { 13 | name = "owner-alias" 14 | values = ["amazon"] 15 | } 16 | 17 | filter { 18 | name = "architecture" 19 | values = ["x86_64"] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /terraform/modules/ecs-cluster/iam.tf: -------------------------------------------------------------------------------- 1 | ### IAM Resources 2 | data "aws_iam_policy_document" "ecs" { 3 | statement { 4 | actions = ["sts:AssumeRole"] 5 | 6 | principals { 7 | type = "Service" 8 | identifiers = ["ec2.amazonaws.com"] 9 | } 10 | } 11 | } 12 | 13 | resource "aws_iam_role" "ecs" { 14 | name = "${var.name}-ecs-instance" 15 | path = var.iam_path 16 | assume_role_policy = data.aws_iam_policy_document.ecs.json 17 | managed_policy_arns = [ 18 | "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role", 19 | "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore", 20 | "arn:aws:iam::aws:policy/CloudWatchAgentAdminPolicy" 21 | ] 22 | 23 | tags = var.tags 24 | } 25 | 26 | resource "aws_iam_instance_profile" "ecs" { 27 | name = "${var.name}-ecs-instance" 28 | role = aws_iam_role.ecs.name 29 | } 30 | -------------------------------------------------------------------------------- /terraform/modules/ecs-cluster/locals.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | tags = concat( 3 | [ 4 | { 5 | key = "Name" 6 | value = "${var.name}-ecs-node" 7 | propagate_at_launch = true 8 | }, 9 | { 10 | key = "AmazonECSManaged" 11 | value = "true" 12 | propagate_at_launch = true 13 | } 14 | ], 15 | var.asg_tags, 16 | local.tags_asg_format, 17 | ) 18 | 19 | tags_asg_format = null_resource.tags_as_list_of_maps.*.triggers 20 | } 21 | 22 | resource "null_resource" "tags_as_list_of_maps" { 23 | count = length(keys(var.tags)) 24 | 25 | triggers = { 26 | "key" = keys(var.tags)[count.index] 27 | "value" = values(var.tags)[count.index] 28 | "propagate_at_launch" = "true" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /terraform/modules/ecs-cluster/outputs.tf: -------------------------------------------------------------------------------- 1 | output "cluster_id" { 2 | value = aws_ecs_cluster.ecs.id 3 | } 4 | 5 | output "cluster_name" { 6 | value = aws_ecs_cluster.ecs.name 7 | } 8 | 9 | output "log_group_name" { 10 | value = aws_cloudwatch_log_group.ecs.name 11 | } 12 | 13 | output "asg_arn" { 14 | value = aws_autoscaling_group.ecs.arn 15 | } 16 | 17 | output "asg_name" { 18 | value = aws_autoscaling_group.ecs.name 19 | } 20 | 21 | output "service_discovery_namespace_id" { 22 | value = aws_service_discovery_private_dns_namespace.ecs.id 23 | } 24 | -------------------------------------------------------------------------------- /terraform/modules/ecs-cluster/service_discovery.tf: -------------------------------------------------------------------------------- 1 | resource "aws_service_discovery_private_dns_namespace" "ecs" { 2 | name = var.service_discovery_domain 3 | vpc = var.vpc_id 4 | } 5 | -------------------------------------------------------------------------------- /terraform/modules/ecs-service/autoscaling.tf: -------------------------------------------------------------------------------- 1 | resource "aws_appautoscaling_target" "this" { 2 | count = local.fixed_capacity ? 0 : 1 3 | 4 | service_namespace = "ecs" 5 | resource_id = "service/${data.aws_ecs_cluster.this.cluster_name}/${aws_ecs_service.this.name}" 6 | scalable_dimension = "ecs:service:DesiredCount" 7 | min_capacity = var.min_capacity 8 | max_capacity = var.max_capacity 9 | } 10 | 11 | resource "aws_appautoscaling_policy" "this" { 12 | count = local.fixed_capacity ? 0 : 1 13 | 14 | name = "${var.name}-target-scaling" 15 | resource_id = aws_appautoscaling_target.this.0.resource_id 16 | scalable_dimension = aws_appautoscaling_target.this.0.scalable_dimension 17 | service_namespace = aws_appautoscaling_target.this.0.service_namespace 18 | policy_type = "TargetTrackingScaling" 19 | 20 | target_tracking_scaling_policy_configuration { 21 | target_value = var.target_value 22 | scale_in_cooldown = var.scale_in_cooldown 23 | scale_out_cooldown = var.scale_out_cooldown 24 | 25 | predefined_metric_specification { 26 | predefined_metric_type = var.predefined_metric_type 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /terraform/modules/ecs-service/data.tf: -------------------------------------------------------------------------------- 1 | data "aws_region" "current" {} 2 | 3 | data "aws_ecs_cluster" "this" { 4 | cluster_name = var.cluster_name 5 | } 6 | -------------------------------------------------------------------------------- /terraform/modules/ecs-service/lb.tf: -------------------------------------------------------------------------------- 1 | resource "aws_lb_target_group" "this" { 2 | count = var.use_load_balancer ? 1 : 0 3 | 4 | name = var.name 5 | port = var.container_port 6 | protocol = "HTTP" 7 | vpc_id = var.lb_vpc_id 8 | target_type = "ip" 9 | 10 | health_check { 11 | enabled = var.lb_health_check_enabled 12 | healthy_threshold = var.lb_healthy_threshold 13 | interval = var.lb_interval 14 | protocol = var.lb_protocol 15 | matcher = var.lb_matcher 16 | timeout = var.lb_timeout 17 | path = var.lb_path 18 | unhealthy_threshold = var.lb_unhealthy_threshold 19 | } 20 | } 21 | 22 | resource "aws_lb_listener_rule" "routing" { 23 | count = var.use_load_balancer ? 1 : 0 24 | 25 | listener_arn = var.lb_listener_arn 26 | 27 | action { 28 | type = "forward" 29 | target_group_arn = aws_lb_target_group.this.0.arn 30 | } 31 | 32 | condition { 33 | host_header { 34 | values = var.lb_hosts 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /terraform/modules/ecs-service/outputs.tf: -------------------------------------------------------------------------------- 1 | output "task_arn" { 2 | value = aws_ecs_task_definition.this.arn 3 | } 4 | 5 | output "service_name" { 6 | value = aws_ecs_service.this.name 7 | } 8 | 9 | output "service_arn" { 10 | value = aws_ecs_service.this.id 11 | } 12 | -------------------------------------------------------------------------------- /terraform/modules/ecs-service/route53.tf: -------------------------------------------------------------------------------- 1 | # # A record 2 | # resource "aws_route53_record" "ipv4" { 3 | # count = length(var.lb_hosts) 4 | 5 | # zone_id = var.lb_domain_zone_id 6 | # name = var.lb_hosts[count.index] 7 | # type = "A" 8 | 9 | # alias { 10 | # name = var.lb_dns_name 11 | # zone_id = var.lb_zone_id 12 | # evaluate_target_health = true 13 | # } 14 | # } 15 | 16 | # # AAAA record 17 | # resource "aws_route53_record" "ipv6" { 18 | # count = length(var.lb_hosts) 19 | 20 | # zone_id = var.lb_domain_zone_id 21 | # name = var.lb_hosts[count.index] 22 | # type = "AAAA" 23 | 24 | # alias { 25 | # name = var.lb_dns_name 26 | # zone_id = var.lb_zone_id 27 | # evaluate_target_health = true 28 | # } 29 | # } 30 | -------------------------------------------------------------------------------- /terraform/modules/ecs-service/service_discovery.tf: -------------------------------------------------------------------------------- 1 | resource "aws_service_discovery_service" "this" { 2 | name = var.name 3 | 4 | dynamic "dns_config" { 5 | for_each = var.service_discovery_namespace_id == null ? [] : [1] 6 | 7 | content { 8 | namespace_id = var.service_discovery_namespace_id 9 | routing_policy = "MULTIVALUE" 10 | 11 | dns_records { 12 | ttl = 10 13 | type = "A" 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /terraform/modules/s3/outputs.tf: -------------------------------------------------------------------------------- 1 | output "id" { 2 | value = aws_s3_bucket.this.id 3 | } 4 | 5 | output "arn" { 6 | value = aws_s3_bucket.this.arn 7 | } 8 | 9 | output "bucket" { 10 | value = aws_s3_bucket.this.bucket 11 | } 12 | 13 | output "bucket_regional_domain_name" { 14 | value = aws_s3_bucket.this.bucket_regional_domain_name 15 | } 16 | -------------------------------------------------------------------------------- /terraform/modules/s3/random.tf: -------------------------------------------------------------------------------- 1 | resource "random_string" "suffix" { 2 | length = 4 3 | special = false 4 | upper = false 5 | numeric = false 6 | } 7 | -------------------------------------------------------------------------------- /terraform/modules/s3/variables.tf: -------------------------------------------------------------------------------- 1 | variable "name" { 2 | description = "Name to be used throughout the resources" 3 | type = string 4 | } 5 | 6 | variable "enable_versioning" { 7 | description = "Whether Amazon S3 should enable versioning for this bucket." 8 | type = bool 9 | default = false 10 | } 11 | 12 | variable "block_public_acls" { 13 | description = "Whether Amazon S3 should block public ACLs for this bucket." 14 | type = bool 15 | default = true 16 | } 17 | 18 | variable "block_public_policy" { 19 | description = "Whether Amazon S3 should block public bucket policies for this bucket." 20 | type = bool 21 | default = true 22 | } 23 | 24 | variable "ignore_public_acls" { 25 | description = "Whether Amazon S3 should ignore public ACLs for this bucket." 26 | type = bool 27 | default = true 28 | } 29 | 30 | variable "restrict_public_buckets" { 31 | description = "Whether Amazon S3 should restrict public bucket policies for this bucket." 32 | type = bool 33 | default = true 34 | } 35 | 36 | variable "policy" { 37 | description = "(Optional) A valid bucket policy JSON document." 38 | type = string 39 | } 40 | -------------------------------------------------------------------------------- /terraform/networking_eips.tf: -------------------------------------------------------------------------------- 1 | resource "aws_eip" "nat_gateway" { 2 | domain = "vpc" 3 | tags = { 4 | Name = "${local.namespace}-nat-gateway" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /terraform/networking_gateways.tf: -------------------------------------------------------------------------------- 1 | resource "aws_internet_gateway" "main" { 2 | vpc_id = aws_vpc.main.id 3 | 4 | tags = { 5 | Name = "${local.namespace}-internet-gateway" 6 | } 7 | } 8 | 9 | resource "aws_nat_gateway" "nat_gateway" { 10 | allocation_id = aws_eip.nat_gateway.id 11 | subnet_id = aws_subnet.public.0.id 12 | 13 | tags = { 14 | Name = "${local.namespace}-nat-gateway" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /terraform/networking_routing.tf: -------------------------------------------------------------------------------- 1 | # Public 2 | resource "aws_route_table" "public" { 3 | vpc_id = aws_vpc.main.id 4 | 5 | route { 6 | cidr_block = "0.0.0.0/0" 7 | gateway_id = aws_internet_gateway.main.id 8 | } 9 | 10 | tags = { 11 | Name = "${local.namespace}-public" 12 | } 13 | } 14 | 15 | resource "aws_route_table_association" "public" { 16 | count = length(data.aws_availability_zones.current.names) 17 | subnet_id = element(aws_subnet.public.*.id, count.index) 18 | route_table_id = aws_route_table.public.id 19 | } 20 | 21 | # Private 22 | resource "aws_route_table" "private" { 23 | vpc_id = aws_vpc.main.id 24 | 25 | route { 26 | cidr_block = "0.0.0.0/0" 27 | nat_gateway_id = aws_nat_gateway.nat_gateway.id 28 | } 29 | 30 | tags = { 31 | Name = "${local.namespace}-private" 32 | } 33 | } 34 | 35 | resource "aws_route_table_association" "private" { 36 | count = length(data.aws_availability_zones.current.names) 37 | subnet_id = element(aws_subnet.private.*.id, count.index) 38 | route_table_id = aws_route_table.private.id 39 | } 40 | -------------------------------------------------------------------------------- /terraform/networking_vpc.tf: -------------------------------------------------------------------------------- 1 | resource "aws_vpc" "main" { 2 | cidr_block = local.networking.cidr_block 3 | enable_dns_hostnames = true 4 | enable_dns_support = true 5 | 6 | tags = { 7 | Name = "${local.namespace}-vpc" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /terraform/outputs.tf: -------------------------------------------------------------------------------- 1 | output "bastion_ip" { 2 | value = aws_instance.bastion.public_ip 3 | } 4 | -------------------------------------------------------------------------------- /terraform/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~> 1.5" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = "~> 5.16" 8 | } 9 | } 10 | 11 | cloud { 12 | organization = "code4romania" 13 | 14 | workspaces { 15 | name = "redirectioneaza-production" 16 | project = "redirectioneaza" 17 | } 18 | } 19 | } 20 | 21 | provider "aws" { 22 | region = var.region 23 | 24 | default_tags { 25 | tags = { 26 | app = "redirectioneaza" 27 | env = var.env 28 | } 29 | } 30 | } 31 | 32 | provider "aws" { 33 | alias = "acm" 34 | region = "us-east-1" 35 | 36 | default_tags { 37 | tags = { 38 | app = "redirectioneaza" 39 | env = var.env 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /terraform/random.tf: -------------------------------------------------------------------------------- 1 | resource "random_string" "secrets_suffix" { 2 | length = 8 3 | special = false 4 | upper = false 5 | numeric = false 6 | 7 | lifecycle { 8 | ignore_changes = [ 9 | length, 10 | special, 11 | upper, 12 | numeric, 13 | ] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /terraform/route53.tf: -------------------------------------------------------------------------------- 1 | # A record 2 | resource "aws_route53_record" "ipv4" { 3 | count = length(local.domains) 4 | 5 | zone_id = data.aws_route53_zone.main.zone_id 6 | name = local.domains[count.index] 7 | type = "A" 8 | 9 | alias { 10 | name = aws_cloudfront_distribution.main.domain_name 11 | zone_id = aws_cloudfront_distribution.main.hosted_zone_id 12 | evaluate_target_health = true 13 | } 14 | } 15 | 16 | # AAAA record 17 | resource "aws_route53_record" "ipv6" { 18 | count = length(local.domains) 19 | 20 | zone_id = data.aws_route53_zone.main.zone_id 21 | name = local.domains[count.index] 22 | type = "AAAA" 23 | 24 | alias { 25 | name = aws_cloudfront_distribution.main.domain_name 26 | zone_id = aws_cloudfront_distribution.main.hosted_zone_id 27 | evaluate_target_health = true 28 | } 29 | } 30 | --------------------------------------------------------------------------------