├── libs ├── __init__.py ├── utils │ ├── __init__.py │ └── compression.py ├── django_forms │ └── __init__.py ├── endpoint_helpers │ ├── __init__.py │ ├── researcher_helpers.py │ └── password_validation_helpers.py ├── file_processing │ └── __init__.py └── aes.py ├── tests ├── __init__.py ├── test_misc_download_endpoints.py ├── files │ ├── public_key │ └── private_key └── test_security.py ├── endpoints ├── __init__.py └── misc_download_endpoints.py ├── middleware ├── __init__.py ├── downtime_middleware.py └── abort_middleware.py ├── authentication └── __init__.py ├── database ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── create_default_login.py ├── migrations │ ├── __init__.py │ ├── 0019_merge_20190617_2050.py │ ├── 0077_delete_fileprocesslock.py │ ├── 0079_delete_decryptionkeyerror.py │ ├── 0070_delete_pipelineregistry.py │ ├── 0042_auto_20201204_2223.py │ ├── 0066_remove_researcher_is_batch_user.py │ ├── 0139_delete_lineencryptionerror.py │ ├── 0009_remove_survey_last_modified.py │ ├── 0068_survey_name.py │ ├── 0122_remove_participant_enable_heartbeat.py │ ├── 0138_remove_summarystatisticdaily_jasmine_gps_data_missing_duration.py │ ├── 0004_study_is_test.py │ ├── 0034_auto_20200506_2212.py │ ├── 0044_auto_20210115_2300.py │ ├── 0050_participant_unregistered.py │ ├── 0072_auto_20220323_2322.py │ ├── 0007_auto_20180413_2033.py │ ├── 0113_appheartbeats_message.py │ ├── 0038_auto_20200730_0413.py │ ├── 0058_auto_20210701_2222.py │ ├── 0059_devicesettings_ambient_audio.py │ ├── 0037_auto_20200729_0715.py │ ├── 0053_foresttask_forest_output_exists.py │ ├── 0052_foresttask_params_dict_cache.py │ ├── 0033_auto_20200423_0101.py │ ├── 0105_participant_unknown_timezone.py │ ├── 0106_researcher_most_recent_page.py │ ├── 0027_auto_20200304_2234.py │ ├── 0054_auto_20210520_1931.py │ ├── 0012_auto_20180525_0141.py │ ├── 0017_chunkregistry_file_size.py │ ├── 0025_auto_20200106_2153.py │ ├── 0104_participant_device_status_report.py │ ├── 0133_archivedevent_was_resend.py │ ├── 0014_devicesettings_use_gps_fuzzing.py │ ├── 0020_auto_20190618_1858.py │ ├── 0021_auto_20190716_0057.py │ ├── 0100_participant_last_get_latest_device_settings.py │ ├── 0125_participant_first_register_user.py │ ├── 0010_devicesettings_use_anonymized_hashing.py │ ├── 0115_participant_enable_extensive_device_info_tracking.py │ ├── 0136_scheduledevent_no_resend.py │ ├── 0071_auto_20220323_2245.py │ ├── 0080_summarystatisticdaily_timezone.py │ ├── 0091_alter_devicesettings_check_for_new_surveys_frequency_seconds.py │ ├── 0141_add_s3file_index_sha1.py │ ├── 0028_auto_20200305_2015.py │ ├── 0126_devicestatusreporthistory_created_on.py │ ├── 0029_manual.py │ ├── 0041_auto_20201008_2102.py │ ├── 0032_custom.py │ ├── 0082_alter_foresttask_forest_param.py │ ├── 0134_devicesettings_resend_period_minutes.py │ ├── 0067_auto_20220215_1932.py │ ├── 0086_alter_scheduledevent_most_recent_event.py │ ├── 0140_alter_s3file_created_on.py │ ├── 0121_alter_devicesettings_heartbeat_timer_minutes.py │ ├── 0096_alter_researcher_password_force_reset.py │ ├── 0085_auto_20221005_2203.py │ ├── 0107_alter_devicesettings_consent_form_text.py │ ├── 0073_auto_20220330_1856.py │ ├── 0127_study_end_date_study_manually_stopped.py │ ├── 0024_custom.py │ ├── 0074_auto_20220407_1840.py │ ├── 0063_auto_20211207_1841.py │ ├── 0016_auto_20181210_1757.py │ ├── 0065_auto_20220119_0223.py │ ├── 0132_participant_last_known_surveys_available_and_more.py │ ├── 0143_researcher_bad_login_attempts_and_more.py │ ├── 0031_auto_20200310_1741.py │ ├── 0099_auto_20230110_1859.py │ ├── 0144_dataprocessingstatus.py │ ├── 0097_auto_20221220_0041.py │ ├── 0040_fileastext.py │ ├── 0039_auto_20200819_1901.py │ ├── 0090_auto_20221025_0621.py │ ├── 0116_remove_summarystatisticdaily_beiwe_image_survey_bytes.py │ ├── 0102_forestversion.py │ ├── 0112_participantactionlog.py │ ├── 0002_auto_20170923_1949.py │ ├── 0124_remove_apikey_has_tableau_api_permissions_and_more.py │ ├── 0078_genericevent.py │ ├── 0045_auto_20210121_0301.py │ ├── 0076_auto_20220420_2045.py │ ├── 0135_remove_globalsettings_earliest_possible_time_of_push_notification_resend_and_more.py │ ├── 0006_auto_20180411_0453.py │ ├── 0035_auto_20200518_1908.py │ ├── 0069_filetoprocess_os_type.py │ ├── 0120_devicesettings_heartbeat_message_and_more.py │ ├── 0111_auto_20240118_2311.py │ ├── 0075_iosedecryptionkey.py │ ├── 0098_researchersession.py │ ├── 0087_auto_20221013_1412.py │ ├── 0114_devicestatusreporthistory.py │ ├── 0056_auto_20210525_1920.py │ ├── 0117_researcher_last_login_time_alter_survey_survey_type_and_more.py │ ├── 0101_participantdeletionevent.py │ ├── 0043_auto_20210111_2318.py │ ├── 0013_auto_20180530_0153.py │ ├── 0055_auto_20210524_2128.py │ ├── 0049_auto_20210223_2005.py │ ├── 0109_auto_20231206_1116.py │ ├── 0142_remove_s3file_compression_time_ns_and_more.py │ ├── 0088_auto_20221019_2057.py │ ├── 0118_filetoprocess_app_version_appversionhistory.py │ ├── 0008_auto_20180418_1701.py │ ├── 0093_dataaccessrecord.py │ ├── 0110_auto_20240110_1809.py │ ├── 0131_uuid_migration.py │ ├── 0047_auto_20210208_2249.py │ ├── 0095_auto_20221212_1336.py │ ├── 0064_cleanup_migration.py │ ├── 0005_add_admin_user.py │ ├── 0030_auto_20200310_1714.py │ ├── 0128_manual_archived_event_cleanup.py │ ├── 0018_auto_20190611_2127.py │ ├── 0129_remove_scheduledevent_checkin_time_and_more.py │ ├── _example_custom_migration.py │ └── 0026_custom.py ├── apps.py └── __init__.py ├── config ├── .gitignore ├── remote_db_env.py.dummy └── load_django.py ├── frontend ├── static │ ├── css │ │ ├── empty_css_for_tests.css │ │ └── survey_builder.css │ ├── images │ │ ├── sort_asc.png │ │ ├── sort_both.png │ │ ├── sort_desc.png │ │ ├── back_enabled.png │ │ ├── Sorting icons.psd │ │ ├── back_disabled.png │ │ ├── favicon-32x32.png │ │ ├── favicon-192x192.png │ │ ├── forward_disabled.png │ │ ├── forward_enabled.png │ │ ├── sort_asc_disabled.png │ │ ├── back_enabled_hover.png │ │ ├── sort_desc_disabled.png │ │ ├── exclamation_mark_red.png │ │ └── forward_enabled_hover.png │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ └── javascript │ │ ├── app │ │ ├── app.module.js │ │ └── survey-builder │ │ │ ├── directives │ │ │ ├── tooltip │ │ │ │ └── tooltip.js │ │ │ ├── question-summary │ │ │ │ └── question-summary.js │ │ │ ├── add-logic-buttons │ │ │ │ ├── add-logic-buttons.js │ │ │ │ └── add-logic-buttons.html │ │ │ ├── edit-question │ │ │ │ └── edit-question.js │ │ │ ├── logical-block │ │ │ │ └── logical-block.js │ │ │ └── conditional-block │ │ │ │ ├── conditional-block.js │ │ │ │ └── conditional-block.html │ │ │ ├── controllers │ │ │ ├── nav-bar-study-controller.js │ │ │ ├── filterable-list-controller.js │ │ │ └── data-access-web-form-controller.js │ │ │ └── services.js │ │ ├── edit_study.js │ │ ├── jqplot │ │ └── plugins │ │ │ ├── jqplot.ciParser.min.js │ │ │ └── jqplot.mobile.min.js │ │ └── participants_table.js └── templates │ ├── 404.html │ ├── footer.html │ ├── downtime.html │ ├── dashboard │ ├── dashboard_participant_javascript.html │ └── dashboard_stream_javascript.html │ ├── 500.html │ ├── notifications.html │ └── choose_study.html ├── Procfile ├── cluster_management ├── deployment_helpers │ ├── __init__.py │ └── aws │ │ └── __init__.py ├── general_configuration │ ├── .gitignore │ ├── aws_credentials.example.json │ ├── instance_assume_role_policy_document.json │ ├── global_configuration.example.json │ ├── beiwe_automation_policy.json │ ├── elasticbeanstalk_assume_role_policy_document.json │ └── beiwe_server_aws_access.json ├── pushed_files │ ├── upload_rewrite.conf │ ├── .inputrc │ ├── known_hosts │ ├── ami_apache.conf │ ├── ami_env_config.py │ ├── cron_root.txt │ ├── cron_worker.txt │ ├── rabbitmq_configuration.txt │ ├── cron_manager.txt │ └── htoprc ├── environment_configuration │ └── .gitignore └── launch_requirements.txt ├── data_access_api_reference └── .gitignore ├── beiwe-logo-color.png ├── .elasticbeanstalk └── .gitignore ├── requirements_testing.txt ├── .platform └── httpd │ └── conf.d │ └── timeouts.conf ├── services └── __init__.py ├── docker_management ├── nginx │ ├── dev.Dockerfile │ ├── prod.Dockerfile │ ├── dev.nginx.conf │ └── prod.nginx.conf ├── backend │ ├── dev.base.Dockerfile │ ├── prod.base.Dockerfile │ ├── dev.Dockerfile │ └── prod.Dockerfile ├── rabbitmq │ ├── dev.Dockerfile │ ├── prod.Dockerfile │ └── entrypoint.sh ├── .envs │ ├── .env.dev.template │ └── .env.prod.template └── celery │ ├── dev.Dockerfile │ └── prod.Dockerfile ├── .style.yapf ├── scripts ├── purge_participant_data.py ├── update_forest_version.py ├── repopulate_push_notifications.py ├── upload_logs.py ├── script_that_deletes_problem_uploads.py ├── run_custom_ondeploy_script.py └── taskrunner.py ├── constants ├── action_log_messages.py ├── raw_data_constants.py ├── url_constants.py ├── schedule_constants.py ├── security_constants.py ├── __init__.py ├── copy_study_constants.py └── celery_constants.py ├── wsgi.py ├── .gitignore ├── docs └── survey_settings_strings.txt ├── manage.py ├── .github └── ISSUE_TEMPLATE │ └── mano-bug-report.md ├── .isort.cfg ├── user_scripts └── kill_zombie_celery_tasks.py ├── requirements.in ├── SECURITY.md └── LICENSE.md /libs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /endpoints/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /middleware/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /authentication/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /database/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /database/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/django_forms/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/endpoint_helpers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/file_processing/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/.gitignore: -------------------------------------------------------------------------------- 1 | remote_db_env.py 2 | -------------------------------------------------------------------------------- /database/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/static/css/empty_css_for_tests.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn --config=gunicorn_conf.py -------------------------------------------------------------------------------- /cluster_management/deployment_helpers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cluster_management/deployment_helpers/aws/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data_access_api_reference/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | my_data_access_api_credentials.py -------------------------------------------------------------------------------- /beiwe-logo-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onnela-lab/beiwe-backend/HEAD/beiwe-logo-color.png -------------------------------------------------------------------------------- /cluster_management/general_configuration/.gitignore: -------------------------------------------------------------------------------- 1 | aws_credentials.json 2 | global_configuration.json 3 | -------------------------------------------------------------------------------- /.elasticbeanstalk/.gitignore: -------------------------------------------------------------------------------- 1 | private/ 2 | config.yml 3 | beiwe-application.app.yml 4 | saved_configs/ 5 | 6 | -------------------------------------------------------------------------------- /cluster_management/pushed_files/upload_rewrite.conf: -------------------------------------------------------------------------------- 1 | RewriteEngine on 2 | RewriteRule "^/upload" "/upload/" [PT] 3 | -------------------------------------------------------------------------------- /requirements_testing.txt: -------------------------------------------------------------------------------- 1 | mock 2 | coverage 3 | tblib 4 | redgreenunittest 5 | time-machine 6 | isort 7 | mypy 8 | deepdiff 9 | -------------------------------------------------------------------------------- /frontend/static/images/sort_asc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onnela-lab/beiwe-backend/HEAD/frontend/static/images/sort_asc.png -------------------------------------------------------------------------------- /frontend/static/images/sort_both.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onnela-lab/beiwe-backend/HEAD/frontend/static/images/sort_both.png -------------------------------------------------------------------------------- /frontend/static/images/sort_desc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onnela-lab/beiwe-backend/HEAD/frontend/static/images/sort_desc.png -------------------------------------------------------------------------------- /frontend/static/images/back_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onnela-lab/beiwe-backend/HEAD/frontend/static/images/back_enabled.png -------------------------------------------------------------------------------- /cluster_management/environment_configuration/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /frontend/static/images/Sorting icons.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onnela-lab/beiwe-backend/HEAD/frontend/static/images/Sorting icons.psd -------------------------------------------------------------------------------- /frontend/static/images/back_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onnela-lab/beiwe-backend/HEAD/frontend/static/images/back_disabled.png -------------------------------------------------------------------------------- /frontend/static/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onnela-lab/beiwe-backend/HEAD/frontend/static/images/favicon-32x32.png -------------------------------------------------------------------------------- /frontend/static/images/favicon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onnela-lab/beiwe-backend/HEAD/frontend/static/images/favicon-192x192.png -------------------------------------------------------------------------------- /frontend/static/images/forward_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onnela-lab/beiwe-backend/HEAD/frontend/static/images/forward_disabled.png -------------------------------------------------------------------------------- /frontend/static/images/forward_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onnela-lab/beiwe-backend/HEAD/frontend/static/images/forward_enabled.png -------------------------------------------------------------------------------- /frontend/static/images/sort_asc_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onnela-lab/beiwe-backend/HEAD/frontend/static/images/sort_asc_disabled.png -------------------------------------------------------------------------------- /frontend/static/images/back_enabled_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onnela-lab/beiwe-backend/HEAD/frontend/static/images/back_enabled_hover.png -------------------------------------------------------------------------------- /frontend/static/images/sort_desc_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onnela-lab/beiwe-backend/HEAD/frontend/static/images/sort_desc_disabled.png -------------------------------------------------------------------------------- /frontend/static/images/exclamation_mark_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onnela-lab/beiwe-backend/HEAD/frontend/static/images/exclamation_mark_red.png -------------------------------------------------------------------------------- /frontend/static/images/forward_enabled_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onnela-lab/beiwe-backend/HEAD/frontend/static/images/forward_enabled_hover.png -------------------------------------------------------------------------------- /frontend/static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onnela-lab/beiwe-backend/HEAD/frontend/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /frontend/static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onnela-lab/beiwe-backend/HEAD/frontend/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /frontend/static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onnela-lab/beiwe-backend/HEAD/frontend/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /frontend/static/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onnela-lab/beiwe-backend/HEAD/frontend/static/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /cluster_management/general_configuration/aws_credentials.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWS_ACCESS_KEY_ID" : "FILL ME IN!", 3 | "AWS_SECRET_ACCESS_KEY" : "AND ME TOO!" 4 | } 5 | -------------------------------------------------------------------------------- /.platform/httpd/conf.d/timeouts.conf: -------------------------------------------------------------------------------- 1 | Timeout 120 2 | KeepAlive On 3 | MaxKeepAliveRequests 100 4 | KeepAliveTimeout 120 5 | GracefulShutdownTimeout 5 6 | SetEnv proxy-initial-not-pooled 1 7 | -------------------------------------------------------------------------------- /services/__init__.py: -------------------------------------------------------------------------------- 1 | from os.path import abspath as _abspath 2 | from sys import path as _path 3 | _one_folder_up = _abspath(__file__).rsplit('/',2)[0] 4 | _path.insert(1, _one_folder_up) 5 | -------------------------------------------------------------------------------- /database/apps.py: -------------------------------------------------------------------------------- 1 | # This file is required by Django to recognize our "database" app. 2 | 3 | from django.apps import AppConfig 4 | 5 | 6 | class DatabaseConfig(AppConfig): 7 | name = 'database' 8 | -------------------------------------------------------------------------------- /database/__init__.py: -------------------------------------------------------------------------------- 1 | # Do not comment out! Loads django if it was not already loaded. 2 | # By placing the load django call here any import of database models loads the ORM. 3 | import config.load_django 4 | -------------------------------------------------------------------------------- /docker_management/nginx/dev.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:alpine-slim 2 | 3 | RUN mkdir -p /www/static && \ 4 | rm /etc/nginx/conf.d/default.conf 5 | 6 | WORKDIR /www 7 | 8 | COPY docker_management/nginx/dev.nginx.conf /etc/nginx/conf.d/default.conf -------------------------------------------------------------------------------- /cluster_management/pushed_files/.inputrc: -------------------------------------------------------------------------------- 1 | ## arrow up 2 | "\e[A":history-search-backward 3 | ## arrow down 4 | "\e[B":history-search-forward 5 | 6 | "\e[1;5C": forward-word 7 | "\e[1;5D": backward-word 8 | 9 | set completion-ignore-case on 10 | -------------------------------------------------------------------------------- /.style.yapf: -------------------------------------------------------------------------------- 1 | [style] 2 | based_on_style = google 3 | column_limit = 100 4 | indent_width = 4 5 | blank_line_before_nested_class_or_def = True 6 | dedent_closing_brackets = True 7 | indent_closing_brackets = False 8 | indent_blank_lines = True 9 | spaces_before_comment = 2 10 | -------------------------------------------------------------------------------- /frontend/static/javascript/app/app.module.js: -------------------------------------------------------------------------------- 1 | angular.module("surveyBuilder", []) 2 | .factory("_", function($window) { 3 | /** 4 | * Load lodash for use in angular 5 | */ 6 | if (!$window._) { 7 | location.reload(); 8 | } 9 | return $window._; 10 | }); 11 | -------------------------------------------------------------------------------- /cluster_management/launch_requirements.txt: -------------------------------------------------------------------------------- 1 | # Fabric is used for running bash commands on a remote server 2 | fabric3 3 | 4 | # We colorize log statements for legibility 5 | coloredlogs[cron] 6 | 7 | # we actually DO want the most recent version of boto3 due to the possible changes to AWS API 8 | boto3 9 | -------------------------------------------------------------------------------- /cluster_management/general_configuration/instance_assume_role_policy_document.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2008-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Principal": { 7 | "Service": "ec2.amazonaws.com" 8 | }, 9 | "Action": "sts:AssumeRole" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /frontend/static/javascript/app/survey-builder/directives/tooltip/tooltip.js: -------------------------------------------------------------------------------- 1 | angular.module("surveyBuilder") 2 | .directive("tooltip", function() { 3 | return { 4 | "restrict": "A", 5 | "link": function(scope, element, attributes) { 6 | $(element).tooltip(); 7 | } 8 | }; 9 | }); 10 | -------------------------------------------------------------------------------- /frontend/static/javascript/app/survey-builder/controllers/nav-bar-study-controller.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | angular 3 | .module('surveyBuilder') 4 | .controller('NavBarStudyController', ['$scope', '$window', function($scope, $window) { 5 | $scope.navBarStudies = $window.navBarStudies; 6 | }]); 7 | }()); 8 | -------------------------------------------------------------------------------- /scripts/purge_participant_data.py: -------------------------------------------------------------------------------- 1 | # this is a stub to run a single participant purge for targeting by the celery script runner, all 2 | # the logic is in libs.participant_purge 3 | from libs.participant_purge import run_next_queued_participant_data_deletion 4 | 5 | def main(): 6 | run_next_queued_participant_data_deletion() 7 | -------------------------------------------------------------------------------- /constants/action_log_messages.py: -------------------------------------------------------------------------------- 1 | HEARTBEAT_PUSH_NOTIFICATION_SENT = "heartbeat notification sent" 2 | # HEARTBEAT_RECEIVED = "heartbeat received" # uh, no, there's a whole database table for that 3 | 4 | PARTICIPANT_DELETION_EVENT_STARTED = "Participant data deletion started." 5 | PARTICIPANT_DELETION_EVENT_DONE = "Participant data deletion completed." 6 | 7 | -------------------------------------------------------------------------------- /frontend/static/javascript/app/survey-builder/controllers/filterable-list-controller.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | angular 3 | .module('surveyBuilder') 4 | .controller('FilterableListController', ['$scope', '$window', function($scope, $window) { 5 | $scope.filterableObjects = $window.filterableObjects; 6 | $scope.filterText = '' 7 | }]); 8 | }()); 9 | -------------------------------------------------------------------------------- /frontend/static/javascript/app/survey-builder/controllers/data-access-web-form-controller.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | angular 3 | .module('surveyBuilder') 4 | .controller('DataAccessWebFormController', ['$scope', '$window', function($scope, $window) { 5 | $scope.allowedStudies = allowedStudies; 6 | $scope.participantsByStudy = participantsByStudy; 7 | }]); 8 | }()); 9 | -------------------------------------------------------------------------------- /wsgi.py: -------------------------------------------------------------------------------- 1 | # This is the target file that is executed by the server to run the Beiwe Django application. 2 | import os 3 | import sys 4 | 5 | from django.core.wsgi import get_wsgi_application 6 | 7 | 8 | sys.stderr = sys.stdout 9 | 10 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.django_settings") 11 | application = get_wsgi_application() # variable must be named application 12 | -------------------------------------------------------------------------------- /constants/raw_data_constants.py: -------------------------------------------------------------------------------- 1 | # these are the fields required from a values query for use in the ZipGenerator class. 2 | 3 | # ZipGenerator is used in the data access api, and in the download task data endpoint for forest. 4 | CHUNK_FIELDS = ( 5 | "pk", "participant_id", "data_type", "chunk_path", "time_bin", "chunk_hash", 6 | "participant__patient_id", "study_id", "survey_id", "survey__object_id" 7 | ) -------------------------------------------------------------------------------- /database/migrations/0019_merge_20190617_2050.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.18 on 2019-06-17 20:50 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0018_auto_20190611_2127'), 10 | ('database', '0018_auto_20190613_1943'), 11 | ] 12 | 13 | operations = [ 14 | ] 15 | -------------------------------------------------------------------------------- /docker_management/nginx/prod.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:alpine-slim 2 | 3 | ARG PUBLIC_DOMAIN_NAME 4 | 5 | COPY docker_management/nginx/prod.nginx.conf /etc/nginx/conf.d/default.conf 6 | 7 | RUN apk update && \ 8 | apk add perl && \ 9 | mkdir -p /www/static && \ 10 | in="my.domain" out="$PUBLIC_DOMAIN_NAME" perl -pi -e 's/\Q$ENV{"in"}/$ENV{"out"}/g' ./etc/nginx/conf.d/default.conf 11 | 12 | WORKDIR /www 13 | -------------------------------------------------------------------------------- /database/migrations/0077_delete_fileprocesslock.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.13 on 2022-05-01 00:50 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0076_auto_20220420_2045'), 10 | ] 11 | 12 | operations = [ 13 | migrations.DeleteModel( 14 | name='FileProcessLock', 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /database/migrations/0079_delete_decryptionkeyerror.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.13 on 2022-05-10 03:56 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0078_genericevent'), 10 | ] 11 | 12 | operations = [ 13 | migrations.DeleteModel( 14 | name='DecryptionKeyError', 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /cluster_management/pushed_files/known_hosts: -------------------------------------------------------------------------------- 1 | github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== 2 | -------------------------------------------------------------------------------- /database/migrations/0070_delete_pipelineregistry.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.25 on 2022-03-03 21:01 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0069_filetoprocess_os_type'), 10 | ] 11 | 12 | operations = [ 13 | migrations.DeleteModel( 14 | name='PipelineRegistry', 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /docker_management/backend/dev.base.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8.19-slim AS beiwe-server-dev-base 2 | 3 | ENV PYTHONDONTWRITEBYTECODE=1 4 | ENV PYTHONUNBUFFERED=1 5 | 6 | WORKDIR /usr/src/app 7 | 8 | COPY ../../requirements.txt . 9 | RUN apt-get update && \ 10 | apt-get install -y git gcc && \ 11 | pip install --upgrade pip && \ 12 | pip wheel --no-cache-dir --no-deps --wheel-dir /usr/src/app/wheels -r requirements.txt -------------------------------------------------------------------------------- /cluster_management/general_configuration/global_configuration.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "DEPLOYMENT_KEY_NAME": "stick the name of the deployment key you created here", 3 | "DEPLOYMENT_KEY_FILE_PATH": "absolute file path to your private component of the key above", 4 | "VPC_ID": "stick the default vpc id for the region you are in here", 5 | "AWS_REGION": "something like us-east-2", 6 | "SYSTEM_ADMINISTRATOR_EMAIL": "example@example.com" 7 | } -------------------------------------------------------------------------------- /docker_management/backend/prod.base.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8.19-slim AS beiwe-server-prod-base 2 | 3 | ENV PYTHONDONTWRITEBYTECODE=1 4 | ENV PYTHONUNBUFFERED=1 5 | 6 | WORKDIR /usr/src/app 7 | 8 | COPY ../../requirements.txt . 9 | RUN apt-get update && \ 10 | apt-get install -y git gcc && \ 11 | pip install --upgrade pip && \ 12 | pip wheel --no-cache-dir --no-deps --wheel-dir /usr/src/app/wheels -r requirements.txt -------------------------------------------------------------------------------- /cluster_management/general_configuration/beiwe_automation_policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": [ 7 | "ec2:*", 8 | "elasticloadbalancing:*", 9 | "elasticbeanstalk:*", 10 | "autoscaling:*", 11 | "s3:*", 12 | "rds:*", 13 | "iam:*" 14 | ], 15 | "Resource": "*" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /frontend/static/javascript/app/survey-builder/directives/question-summary/question-summary.js: -------------------------------------------------------------------------------- 1 | angular.module("surveyBuilder") 2 | .directive("questionSummary", function() { 3 | return { 4 | "restrict": "E", 5 | // need to add a fake variable to the end of the templateUrl to force the browser to reload the template 6 | "templateUrl": "/static/javascript/app/survey-builder/directives/question-summary/question-summary.html?n=1" 7 | }; 8 | }); 9 | -------------------------------------------------------------------------------- /database/migrations/0042_auto_20201204_2223.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.14 on 2020-12-04 22:23 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0041_auto_20201008_2102'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterUniqueTogether( 14 | name='scheduledevent', 15 | unique_together=set(), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /docker_management/rabbitmq/dev.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rabbitmq:3-management-alpine 2 | 3 | ARG RABBITMQ_USERNAME 4 | ARG RABBITMQ_PASSWORD 5 | ENV RABBITMQ_PID_FILE $RABBITMQ_MNESIA_DIR.pid 6 | 7 | COPY ./cluster_management/pushed_files/rabbitmq_configuration.txt /etc/rabbitmq/rabbitmq-env.conf 8 | COPY ./docker_management/rabbitmq/entrypoint.sh . 9 | 10 | RUN sed -i 's/\r$//g' ./entrypoint.sh && \ 11 | chmod +x ./entrypoint.sh 12 | 13 | ENTRYPOINT ["./entrypoint.sh"] -------------------------------------------------------------------------------- /docker_management/rabbitmq/prod.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rabbitmq:3-management-alpine 2 | 3 | ARG RABBITMQ_USERNAME 4 | ARG RABBITMQ_PASSWORD 5 | ENV RABBITMQ_PID_FILE $RABBITMQ_MNESIA_DIR.pid 6 | 7 | COPY ./cluster_management/pushed_files/rabbitmq_configuration.txt /etc/rabbitmq/rabbitmq-env.conf 8 | COPY ./docker_management/rabbitmq/entrypoint.sh . 9 | 10 | RUN sed -i 's/\r$//g' ./entrypoint.sh && \ 11 | chmod +x ./entrypoint.sh 12 | 13 | ENTRYPOINT ["./entrypoint.sh"] -------------------------------------------------------------------------------- /cluster_management/general_configuration/elasticbeanstalk_assume_role_policy_document.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Principal": { 7 | "Service": "elasticbeanstalk.amazonaws.com" 8 | }, 9 | "Action": "sts:AssumeRole", 10 | "Condition": { 11 | "StringEquals": { 12 | "sts:ExternalId": "elasticbeanstalk" 13 | } 14 | } 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /database/migrations/0066_remove_researcher_is_batch_user.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.25 on 2022-02-02 20:22 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0065_auto_20220119_0223'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='researcher', 15 | name='is_batch_user', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /database/migrations/0139_delete_lineencryptionerror.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.21 on 2025-05-20 18:13 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0138_remove_summarystatisticdaily_jasmine_gps_data_missing_duration'), 10 | ] 11 | 12 | operations = [ 13 | migrations.DeleteModel( 14 | name='LineEncryptionError', 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /constants/url_constants.py: -------------------------------------------------------------------------------- 1 | from django.urls import URLPattern 2 | 3 | 4 | # used to indicate if a url redirect is safe to redirect to 5 | IGNORE = "IGNORE" 6 | SAFE = "SAFE" 7 | 8 | # These are declared here so that they can be imported, they are populated in urls.py. 9 | LOGIN_REDIRECT_IGNORE: list[URLPattern] = [] 10 | LOGIN_REDIRECT_SAFE: list[URLPattern] = [] 11 | # urlpatterns probably needs to be lowercase for django to find it 12 | urlpatterns: list[URLPattern] = [] 13 | -------------------------------------------------------------------------------- /tests/test_misc_download_endpoints.py: -------------------------------------------------------------------------------- 1 | from django.http.response import HttpResponseRedirect 2 | 3 | from tests.common import ResearcherSessionTest 4 | 5 | 6 | class TestPrivacyPolicy(ResearcherSessionTest): 7 | ENDPOINT_NAME = "misc_download_endpoints.download_privacy_policy" 8 | 9 | def test(self): 10 | # just test that it loads without breaking 11 | redirect = self.smart_get() 12 | self.assertIsInstance(redirect, HttpResponseRedirect) 13 | -------------------------------------------------------------------------------- /database/migrations/0009_remove_survey_last_modified.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.5 on 2018-04-18 17:02 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0008_auto_20180418_1701'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='survey', 15 | name='last_modified', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /tests/files/public_key: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiZlQkHRAWPC8DAWmMVfs 3 | wdt/BE4QGl+d8xecUc0oLOpTYH6ZTc8SGjyn8whCeS3n946FEgB+aW0BKeM5DfwP 4 | GoUyANdioApMiAFiBCDQzD2zm+cbIc8m2M4TFkkKil7K7B1wPCmYRiCpKbcC6AHl 5 | 0LvccpJNh+TGZXAC5Bmz7C0FN1uUkgaztRyz25DiM4BGDvyTL/YfACt53EUIJyIO 6 | u0cyNm0+KxCNsD814nHd7WMa9/lQIK0Oy9jiLr0jx8NopdOmTyHV6+5Rh0uZIyDr 7 | oF05++tysI2btsV4daPht9Xy9eSNPyTKXqsEnBYScIyzHwANZPy7zzX4+xbyH/oh 8 | 5wIDAQAB 9 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /database/migrations/0068_survey_name.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.25 on 2022-02-18 03:06 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0067_auto_20220215_1932'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='survey', 15 | name='name', 16 | field=models.TextField(blank=True, default=''), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0122_remove_participant_enable_heartbeat.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-05-29 14:18 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0121_alter_devicesettings_heartbeat_timer_minutes'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='participant', 15 | name='enable_heartbeat', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /database/migrations/0138_remove_summarystatisticdaily_jasmine_gps_data_missing_duration.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.20 on 2025-05-12 19:07 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0137_s3file'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='summarystatisticdaily', 15 | name='jasmine_gps_data_missing_duration', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /database/migrations/0004_study_is_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.5 on 2018-03-28 21:46 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0003_auto_20180320_2058'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='study', 15 | name='is_test', 16 | field=models.BooleanField(default=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0034_auto_20200506_2212.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.11 on 2020-05-06 22:12 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0033_auto_20200423_0101'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='lineencryptionerror', 15 | name='base64_decryption_key', 16 | field=models.TextField(), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0044_auto_20210115_2300.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.14 on 2021-01-15 23:00 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0043_auto_20210111_2318'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='interventiondate', 15 | name='date', 16 | field=models.DateField(blank=True, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0050_participant_unregistered.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.14 on 2021-03-22 17:29 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0049_auto_20210223_2005'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='participant', 15 | name='unregistered', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0072_auto_20220323_2322.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.25 on 2022-03-23 23:22 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0071_auto_20220323_2245'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='surveyarchive', 15 | name='archive_start', 16 | field=models.DateTimeField(db_index=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0007_auto_20180413_2033.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.5 on 2018-04-13 20:33 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0006_auto_20180411_0453'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='pipelineuploadtags', 15 | name='tag', 16 | field=models.TextField(), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0113_appheartbeats_message.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.23 on 2024-02-05 10:33 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0112_participantactionlog'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='appheartbeats', 15 | name='message', 16 | field=models.TextField(blank=True, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0038_auto_20200730_0413.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.14 on 2020-07-30 04:13 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0037_auto_20200729_0715'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='archivedevent', 15 | name='response_time', 16 | field=models.DateTimeField(blank=True, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0058_auto_20210701_2222.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.19 on 2021-07-01 22:22 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0057_auto_20210525_1929'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='foresttask', 15 | name='total_file_size', 16 | field=models.BigIntegerField(blank=True, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0059_devicesettings_ambient_audio.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.19 on 2021-07-06 19:58 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0058_auto_20210701_2222'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='devicesettings', 15 | name='ambient_audio', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /docker_management/backend/dev.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8.19 2 | 3 | ENV APP_HOME=/home/app/web 4 | 5 | RUN mkdir -p $APP_HOME && \ 6 | addgroup --system app && adduser --system --group app 7 | 8 | WORKDIR $APP_HOME 9 | 10 | COPY --from=beiwe-server-dev-base /usr/src/app/wheels /wheels 11 | COPY --from=beiwe-server-dev-base /usr/src/app/requirements.txt . 12 | 13 | COPY . $APP_HOME 14 | 15 | RUN pip install --upgrade pip && \ 16 | pip install --no-cache /wheels/* && \ 17 | chown -R app:app $APP_HOME 18 | 19 | USER app -------------------------------------------------------------------------------- /docker_management/backend/prod.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8.19 2 | 3 | ENV APP_HOME=/home/app/web 4 | 5 | RUN mkdir -p $APP_HOME && \ 6 | addgroup --system app && adduser --system --group app 7 | 8 | WORKDIR $APP_HOME 9 | 10 | COPY --from=beiwe-server-prod-base /usr/src/app/wheels /wheels 11 | COPY --from=beiwe-server-prod-base /usr/src/app/requirements.txt . 12 | 13 | COPY . $APP_HOME 14 | 15 | RUN pip install --upgrade pip && \ 16 | pip install --no-cache /wheels/* && \ 17 | chown -R app:app $APP_HOME 18 | 19 | USER app -------------------------------------------------------------------------------- /database/migrations/0037_auto_20200729_0715.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.14 on 2020-07-29 07:15 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0036_auto_20200624_1956'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='participantfcmhistory', 15 | name='unregistered', 16 | field=models.DateTimeField(blank=True, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0053_foresttask_forest_output_exists.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.19 on 2021-05-20 19:19 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0052_foresttask_params_dict_cache'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='foresttask', 15 | name='forest_output_exists', 16 | field=models.NullBooleanField(), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /docker_management/rabbitmq/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Create Rabbitmq user 4 | ( rabbitmqctl wait --timeout 60 $RABBITMQ_PID_FILE ; \ 5 | rabbitmqctl add_user $RABBITMQ_USERNAME $RABBITMQ_PASSWORD 2>/dev/null ; \ 6 | rabbitmqctl set_permissions -p / $RABBITMQ_USERNAME ".*" ".*" ".*" ; ) & 7 | 8 | # $"$@" is used to pass arguments to the rabbitmq-server command. 9 | # For example if you use it like this: docker run -d rabbitmq arg1 arg2, 10 | # it will be as you run in the container rabbitmq-server arg1 arg2 11 | rabbitmq-server "$@" -------------------------------------------------------------------------------- /frontend/static/javascript/edit_study.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | enable_import_file_button_if_file_selected(); 3 | 4 | $('#file_upload_selector').change(function(){ 5 | enable_import_file_button_if_file_selected(); 6 | }); 7 | }); 8 | 9 | /* The "Import Study Settings File" button is disabled until the user chooses a file to upload */ 10 | function enable_import_file_button_if_file_selected() { 11 | if ($('#file_upload_selector').val() != "") { 12 | $('#file_upload_button').prop('disabled', false); 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /database/migrations/0052_foresttask_params_dict_cache.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.19 on 2021-04-02 21:39 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0051_forest_model_updates_20210402_2045'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='foresttask', 15 | name='params_dict_cache', 16 | field=models.TextField(blank=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0033_auto_20200423_0101.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.11 on 2020-04-23 01:01 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0032_custom'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='participant', 15 | name='fcm_instance_id', 16 | field=models.CharField(blank=True, db_index=True, max_length=256, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0105_participant_unknown_timezone.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.20 on 2023-08-23 01:23 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0104_participant_device_status_report'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='participant', 15 | name='unknown_timezone', 16 | field=models.BooleanField(default=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0106_researcher_most_recent_page.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.20 on 2023-09-05 18:16 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0105_participant_unknown_timezone'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='researcher', 15 | name='most_recent_page', 16 | field=models.TextField(blank=True, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0027_auto_20200304_2234.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.28 on 2020-03-04 22:34 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0026_custom'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='survey', 15 | name='schedule_type', 16 | field=models.CharField(default='weekly', max_length=32, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0054_auto_20210520_1931.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.19 on 2021-05-20 19:31 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0053_foresttask_forest_output_exists'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='foresttask', 15 | name='process_download_end_time', 16 | field=models.DateTimeField(blank=True, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0012_auto_20180525_0141.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.5 on 2018-05-25 01:41 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0011_auto_20180523_0153'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='devicesettings', 15 | name='use_anonymized_hashing', 16 | field=models.BooleanField(default=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0017_chunkregistry_file_size.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.18 on 2019-05-17 23:56 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0016_auto_20181210_1757'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='chunkregistry', 15 | name='file_size', 16 | field=models.IntegerField(default=None, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0025_auto_20200106_2153.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.23 on 2020-01-06 21:53 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0024_custom'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='chunkregistry', 15 | name='chunk_path', 16 | field=models.CharField(db_index=True, max_length=256, unique=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0104_participant_device_status_report.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.20 on 2023-08-22 23:08 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0103_auto_20230623_1804'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='participant', 15 | name='device_status_report', 16 | field=models.TextField(blank=True, default=None, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0133_archivedevent_was_resend.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.15 on 2024-11-22 18:36 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0132_participant_last_known_surveys_available_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='archivedevent', 15 | name='was_resend', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Local Environment Settings 2 | .pydevproject 3 | .settings 4 | .project 5 | .idea/ 6 | .python-version 7 | .coverage 8 | .vscode 9 | htmlcov/ 10 | .ropeproject 11 | 12 | # A place to stick your files 13 | env/* 14 | private/* 15 | scratch 16 | scratch/* 17 | scraps 18 | scraps/* 19 | 20 | # Autogenerated files 21 | *.DS_Store 22 | *.pyc 23 | *__pycache__* 24 | *~ 25 | *.pem 26 | .directory 27 | 28 | # Celery local config 29 | manager_ip 30 | 31 | # Ignore all .env files 32 | docker_management/.envs/.env.dev 33 | docker_management/.envs/.env.prod 34 | -------------------------------------------------------------------------------- /database/migrations/0014_devicesettings_use_gps_fuzzing.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.15 on 2018-11-14 17:57 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0013_auto_20180530_0153'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='devicesettings', 15 | name='use_gps_fuzzing', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0020_auto_20190618_1858.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.18 on 2019-06-18 18:58 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0019_merge_20190617_2050'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='pipelineregistry', 15 | name='data_type', 16 | field=models.CharField(db_index=True, max_length=256), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0021_auto_20190716_0057.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.18 on 2019-07-16 00:57 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0020_auto_20190618_1858'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='chunkregistry', 15 | name='last_updated', 16 | field=models.DateTimeField(auto_now=True, db_index=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /docker_management/nginx/dev.nginx.conf: -------------------------------------------------------------------------------- 1 | upstream beiwe-backend { 2 | server web:8000; 3 | } 4 | 5 | server { 6 | listen 80; 7 | listen [::]:80; 8 | 9 | location / { 10 | proxy_pass http://beiwe-backend; 11 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 12 | proxy_set_header Host $host; 13 | proxy_redirect off; 14 | } 15 | 16 | location /static/ { 17 | autoindex on; 18 | alias /www/static/; 19 | } 20 | } -------------------------------------------------------------------------------- /database/migrations/0100_participant_last_get_latest_device_settings.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.16 on 2023-02-28 01:24 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0099_auto_20230110_1859'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='participant', 15 | name='last_get_latest_device_settings', 16 | field=models.DateTimeField(blank=True, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /frontend/static/javascript/app/survey-builder/directives/add-logic-buttons/add-logic-buttons.js: -------------------------------------------------------------------------------- 1 | angular.module("surveyBuilder") 2 | .directive("addLogicButtons", function() { 3 | return { 4 | "restrict": "E", 5 | "scope": { 6 | "surveyBuilder": "=", 7 | "newPath": "@" 8 | }, 9 | // need to add a fake variable to the end of the templateUrl to force the browser to reload the template 10 | "templateUrl": "/static/javascript/app/survey-builder/directives/add-logic-buttons/add-logic-buttons.html?n=1" 11 | }; 12 | }); 13 | -------------------------------------------------------------------------------- /database/migrations/0125_participant_first_register_user.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-06-04 06:00 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0124_remove_apikey_has_tableau_api_permissions_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='participant', 15 | name='first_register_user', 16 | field=models.DateTimeField(blank=True, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0010_devicesettings_use_anonymized_hashing.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.5 on 2018-05-16 22:24 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0009_remove_survey_last_modified'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='devicesettings', 15 | name='use_anonymized_hashing', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0115_participant_enable_extensive_device_info_tracking.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.24 on 2024-03-07 00:56 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0114_devicestatusreporthistory'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='participant', 15 | name='enable_extensive_device_info_tracking', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0136_scheduledevent_no_resend.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.15 on 2025-01-08 21:54 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0135_remove_globalsettings_earliest_possible_time_of_push_notification_resend_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='scheduledevent', 15 | name='no_resend', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0071_auto_20220323_2245.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.25 on 2022-03-23 22:45 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0070_delete_pipelineregistry'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='uploadtracking', 15 | name='created_on', 16 | ), 17 | migrations.RemoveField( 18 | model_name='uploadtracking', 19 | name='last_updated', 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /database/migrations/0080_summarystatisticdaily_timezone.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.13 on 2022-06-22 03:16 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0079_delete_decryptionkeyerror'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='summarystatisticdaily', 15 | name='timezone', 16 | field=models.CharField(default='UTC', max_length=10), 17 | preserve_default=False, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /database/migrations/0091_alter_devicesettings_check_for_new_surveys_frequency_seconds.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.15 on 2022-11-02 11:48 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0090_auto_20221025_0621'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='devicesettings', 15 | name='check_for_new_surveys_frequency_seconds', 16 | field=models.PositiveIntegerField(default=3600), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /database/migrations/0141_add_s3file_index_sha1.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.2.2 on 2025-06-13 09:18 2 | 3 | from django.db import migrations, models 4 | from django.contrib.postgres.operations import AddIndexConcurrently 5 | 6 | 7 | class Migration(migrations.Migration): 8 | atomic = False 9 | 10 | dependencies = [ 11 | ('database', '0140_alter_s3file_created_on'), 12 | ] 13 | 14 | operations = [ 15 | AddIndexConcurrently( 16 | model_name='s3file', 17 | index=models.Index(fields=['sha1'], name='s3file_sha1_idx'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /frontend/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html'%} 2 | 3 | {% block title %}404 Not Found{% endblock %} 4 | 5 | {# Only show the Navigation Bar if the user is logged in #} 6 | {% block navbar %} 7 | {% if is_logged_in %} 8 | {{ super() }} 9 | {% endif %} 10 | {% endblock %} 11 | 12 | {% block content %} 13 | 14 |
18 |

404 Page Not Found

19 |

Sorry, this page does not exist. Maybe you mis-typed the URL? Or maybe the object you're looking for is not in 20 | the database?

21 |
22 | 23 | {% endblock%} -------------------------------------------------------------------------------- /database/migrations/0028_auto_20200305_2015.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.28 on 2020-03-05 20:15 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0027_auto_20200304_2234'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='relativeschedule', 15 | name='participant', 16 | ), 17 | migrations.RemoveField( 18 | model_name='survey', 19 | name='schedule_type', 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /database/migrations/0126_devicestatusreporthistory_created_on.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-06-17 16:40 2 | 3 | from django.db import migrations, models 4 | import django.utils.timezone 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('database', '0125_participant_first_register_user'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='devicestatusreporthistory', 16 | name='created_on', 17 | field=models.DateTimeField(default=django.utils.timezone.now), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /scripts/update_forest_version.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | warnings.filterwarnings("ignore", category=DeprecationWarning, module="scripts.update_forest_version") 3 | # Deprecated, see https://setuptools.pypa.io/en/latest/pkg_resources.html 4 | import pkg_resources 5 | 6 | from database.system_models import ForestVersion 7 | from libs.utils.forest_utils import get_forest_git_hash 8 | 9 | 10 | def main(): 11 | forest_version = ForestVersion.singleton() 12 | forest_version.package_version = pkg_resources.get_distribution("beiwe-forest") 13 | forest_version.git_commit = get_forest_git_hash() 14 | forest_version.save() 15 | -------------------------------------------------------------------------------- /database/migrations/0029_manual.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.28 on 2020-03-05 20:15 3 | from django.db import migrations 4 | 5 | 6 | def fix_bad_survey_content(apps, schema_editor): 7 | Survey = apps.get_model('database', 'Survey') 8 | Survey.objects.filter(content="null").update(content="[]") 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | ('database', '0028_auto_20200305_2015'), 15 | ] 16 | 17 | operations = [ 18 | migrations.RunPython(fix_bad_survey_content, reverse_code=migrations.RunPython.noop) 19 | 20 | ] 21 | -------------------------------------------------------------------------------- /database/migrations/0041_auto_20201008_2102.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.14 on 2020-10-08 21:02 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('database', '0040_fileastext'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='participantfcmhistory', 16 | name='token', 17 | field=models.CharField(db_index=True, max_length=256, unique=True, validators=[django.core.validators.MinLengthValidator(1)]), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /database/migrations/0032_custom.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.11 on 2020-03-10 17:41 2 | 3 | from django.db import migrations 4 | 5 | # this is a dummy migration. Due to a schema change the location of this script moved to 6 | # database/migrations/0035_auto_20200518_1908.py 7 | 8 | def backfill_missing_survey_archives(*args, **kwargs): 9 | pass 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | ('database', '0031_auto_20200310_1741'), 15 | ] 16 | 17 | operations = [ 18 | migrations.RunPython(backfill_missing_survey_archives, reverse_code=migrations.RunPython.noop), 19 | ] 20 | -------------------------------------------------------------------------------- /database/migrations/0082_alter_foresttask_forest_param.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.15 on 2022-08-16 17:45 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('database', '0081_auto_20220811_2011'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='foresttask', 16 | name='forest_param', 17 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='database.forestparameters'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /database/migrations/0134_devicesettings_resend_period_minutes.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.15 on 2024-12-17 20:05 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('database', '0133_archivedevent_was_resend'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='devicesettings', 16 | name='resend_period_minutes', 17 | field=models.PositiveIntegerField(default=180, validators=[django.core.validators.MinValueValidator(0)]), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /database/migrations/0067_auto_20220215_1932.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.25 on 2022-02-15 19:32 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0066_remove_researcher_is_batch_user'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='pipelineuploadtags', 15 | name='pipeline_upload', 16 | ), 17 | migrations.DeleteModel( 18 | name='PipelineUpload', 19 | ), 20 | migrations.DeleteModel( 21 | name='PipelineUploadTags', 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /database/migrations/0086_alter_scheduledevent_most_recent_event.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.15 on 2022-10-06 15:56 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('database', '0085_auto_20221005_2203'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='scheduledevent', 16 | name='most_recent_event', 17 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='database.archivedevent'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /database/migrations/0140_alter_s3file_created_on.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.2.2 on 2025-06-13 08:20 2 | 3 | from django.contrib.postgres.operations import AddIndexConcurrently 4 | from django.db import migrations, models 5 | 6 | 7 | # from django.contrib.postgres.indexes 8 | 9 | class Migration(migrations.Migration): 10 | atomic = False 11 | 12 | dependencies = [ 13 | ('database', '0139_delete_lineencryptionerror'), 14 | ] 15 | 16 | operations = [ 17 | AddIndexConcurrently( 18 | model_name='s3file', 19 | index=models.Index(fields=['created_on'], name='s3file_created_on_idx'), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /frontend/templates/footer.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | SysAdmin: {{ SYSADMIN_EMAIL }} 6 | 7 | 8 | ©2014-{{ current_year }} Harvard University. 9 | Built by 10 | Onnela Lab 11 | & 12 | Zagaran, Inc. 13 | 14 | 15 | Looking for the Android App? click here to download. 19 | 20 |
-------------------------------------------------------------------------------- /database/migrations/0121_alter_devicesettings_heartbeat_timer_minutes.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-05-28 16:43 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('database', '0120_devicesettings_heartbeat_message_and_more'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='devicesettings', 16 | name='heartbeat_timer_minutes', 17 | field=models.PositiveIntegerField(default=60, validators=[django.core.validators.MinValueValidator(30)]), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /database/migrations/0096_alter_researcher_password_force_reset.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.16 on 2022-12-13 05:26 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0095_auto_20221212_1336'), 10 | ] 11 | # we don't want this default value to be applied to existing users so it is set to True in a 12 | # migration following the addition of the field 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='researcher', 16 | name='password_force_reset', 17 | field=models.BooleanField(default=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /database/migrations/0085_auto_20221005_2203.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.15 on 2022-10-05 22:03 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0084_auto_20220920_0029'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='participant', 15 | name='easy_enrollment', 16 | field=models.BooleanField(default=False), 17 | ), 18 | migrations.AddField( 19 | model_name='study', 20 | name='easy_enrollment', 21 | field=models.BooleanField(default=False), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /database/migrations/0107_alter_devicesettings_consent_form_text.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.20 on 2023-10-25 15:39 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0106_researcher_most_recent_page'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='devicesettings', 15 | name='consent_form_text', 16 | field=models.TextField(blank=True, default='I have read and understood the information about the study and all of my questions about the study have been answered by the study researchers.'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /frontend/templates/downtime.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% block head %} 5 | 6 | 7 | 8 | 9 | {# #} 10 | 11 | 12 | 13 | Beiwe (is down) 14 | {% endblock %} 15 | 16 | 17 |

The system is currently down

18 | -------------------------------------------------------------------------------- /database/migrations/0073_auto_20220330_1856.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.12 on 2022-03-30 18:56 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0072_auto_20220323_2322'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='forestparam', 15 | name='default', 16 | field=models.BooleanField(null=True, unique=True), 17 | ), 18 | migrations.AlterField( 19 | model_name='foresttask', 20 | name='forest_output_exists', 21 | field=models.BooleanField(null=True), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /cluster_management/general_configuration/beiwe_server_aws_access.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "statement1", 6 | "Effect": "Allow", 7 | "Action": "s3:*", 8 | "Resource": [ 9 | "arn:aws:s3:::%s*" 10 | ] 11 | }, 12 | { 13 | "Sid": "statement2", 14 | "Effect": "Allow", 15 | "Action": "ssm:PutParameter", 16 | "Resource": "*" 17 | }, 18 | { 19 | "Sid": "statement3", 20 | "Effect": "Allow", 21 | "Action": "batch:SubmitJob", 22 | "Resource": "*" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /docs/survey_settings_strings.txt: -------------------------------------------------------------------------------- 1 | survey settings strings 2 | 3 | trigger_on_first_download: Boolean 4 | set to true to make a survey display when it is first downloaded 5 | 6 | randomize: Boolean 7 | set to true to randomize a survey (by default with replacement, without memory). if not present the app defaults to false. 8 | 9 | randomize_with_memory: Boolean 10 | if present, modifies behavior of randomize to do so with memory. ignored if randomize is false or not present. 11 | 12 | number_of_random_questions: int 13 | sets the number of questions that will be displayed of the random set. set to 0 to display all. if not present the app defaults to 0. if greater than number of questions app will default to 0. -------------------------------------------------------------------------------- /libs/endpoint_helpers/researcher_helpers.py: -------------------------------------------------------------------------------- 1 | from django.db.models import F, Func 2 | 3 | from authentication.admin_authentication import ResearcherRequest 4 | from database.user_models_researcher import Researcher 5 | 6 | 7 | def get_administerable_researchers(request: ResearcherRequest) -> list[Researcher]: 8 | """ Site admins see all researchers, study admins see researchers on their studies. """ 9 | if request.session_researcher.site_admin: 10 | return Researcher.filter_alphabetical() 11 | else: 12 | return request.session_researcher.get_administered_researchers() \ 13 | .annotate(username_lower=Func(F('username'), function='LOWER')) \ 14 | .order_by('username_lower') -------------------------------------------------------------------------------- /database/migrations/0127_study_end_date_study_manually_stopped.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-06-28 15:42 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0126_devicestatusreporthistory_created_on'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='study', 15 | name='end_date', 16 | field=models.DateField(blank=True, null=True), 17 | ), 18 | migrations.AddField( 19 | model_name='study', 20 | name='manually_stopped', 21 | field=models.BooleanField(default=False), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /database/migrations/0024_custom.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.db import migrations 3 | 4 | 5 | def convert_batch_users(apps, schema_editor): 6 | # permanently removed feature, remove in any migration squash. 7 | # Researcher = apps.get_model('database', 'Researcher') 8 | # for researcher in Researcher.objects.all(): 9 | # if researcher.username.startswith("BATCH USER"): 10 | # researcher.is_batch_user = True 11 | # researcher.save() 12 | pass 13 | 14 | class Migration(migrations.Migration): 15 | dependencies = [ 16 | ('database', '0023_auto_20191003_1928'), 17 | ] 18 | 19 | operations = [ 20 | migrations.RunPython(convert_batch_users), 21 | ] 22 | -------------------------------------------------------------------------------- /database/migrations/0074_auto_20220407_1840.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.12 on 2022-04-07 18:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0073_auto_20220330_1856'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='forestparam', 15 | name='default', 16 | field=models.BooleanField(blank=True, null=True, unique=True), 17 | ), 18 | migrations.AlterField( 19 | model_name='foresttask', 20 | name='forest_output_exists', 21 | field=models.BooleanField(blank=True, null=True), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /cluster_management/pushed_files/ami_apache.conf: -------------------------------------------------------------------------------- 1 | LogLevel warn 2 | WSGIApplicationGroup %{GLOBAL} 3 | WSGIDaemonProcess beiwe user=ubuntu group=ubuntu threads=10 home=/home/ubuntu/beiwe-backend inactivity-timeout=600 4 | WSGIProcessGroup beiwe 5 | 6 | 7 | ServerName beiwe.org 8 | Alias /static /home/ubuntu/beiwe-backend/frontend/static 9 | WSGIScriptAlias / /home/ubuntu/beiwe-backend/wsgi.py 10 | 11 | Options -Indexes 12 | Require all granted 13 | Allow from all 14 | 15 | 16 | ServerAdmin webmaster@localhost 17 | ErrorLog ${APACHE_LOG_DIR}/error.log 18 | CustomLog ${APACHE_LOG_DIR}/access.log combined 19 | 20 | -------------------------------------------------------------------------------- /constants/schedule_constants.py: -------------------------------------------------------------------------------- 1 | class ScheduleTypes: 2 | absolute = "absolute" 3 | relative = "relative" 4 | weekly = "weekly" 5 | one_off = "one_off" 6 | 7 | @classmethod 8 | def choices(cls): 9 | return ( 10 | (cls.absolute, "Absolute Schedule"), 11 | (cls.relative, "Relative Schedule"), 12 | (cls.weekly, "Weekly Schedule"), 13 | (cls.one_off, "One-off Schedule"), 14 | ) 15 | 16 | 17 | # weekly timings ar a list of 7 lists, indicating sunday through friday with seconds-into-the-day integer values 18 | # Implemented as a lambda so that this static representative list is never mutated. 19 | EMPTY_WEEKLY_SURVEY_TIMINGS = lambda: [[], [], [], [], [], [], []] 20 | -------------------------------------------------------------------------------- /database/migrations/0063_auto_20211207_1841.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.24 on 2021-12-07 18:41 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0062_forest_rename'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='summarystatisticdaily', 15 | name='jasmine_gyration_radius', 16 | field=models.FloatField(blank=True, null=True), 17 | ), 18 | migrations.AlterField( 19 | model_name='summarystatisticdaily', 20 | name='jasmine_significant_location_entropy', 21 | field=models.FloatField(blank=True, null=True), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /database/migrations/0016_auto_20181210_1757.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.15 on 2018-12-10 17:57 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0015_auto_20181116_2121'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='devicesettings', 15 | name='call_clinician_button_enabled', 16 | field=models.BooleanField(default=True), 17 | ), 18 | migrations.AddField( 19 | model_name='devicesettings', 20 | name='call_research_assistant_button_enabled', 21 | field=models.BooleanField(default=True), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /database/migrations/0065_auto_20220119_0223.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.25 on 2022-01-19 02:23 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0064_cleanup_migration'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='summarystatisticdaily', 15 | name='willow_incoming_call_duration', 16 | field=models.FloatField(blank=True, null=True), 17 | ), 18 | migrations.AlterField( 19 | model_name='summarystatisticdaily', 20 | name='willow_outgoing_call_duration', 21 | field=models.FloatField(blank=True, null=True), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /database/migrations/0132_participant_last_known_surveys_available_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.15 on 2024-10-17 07:45 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0131_uuid_migration'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='participant', 15 | name='last_active_survey_ids', 16 | field=models.TextField(blank=True, default=None, null=True), 17 | ), 18 | migrations.AddField( 19 | model_name='participant', 20 | name='raw_notification_report', 21 | field=models.TextField(blank=True, default=None, null=True), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /docker_management/.envs/.env.dev.template: -------------------------------------------------------------------------------- 1 | DOMAIN_NAME="localhost" 2 | SYSADMIN_EMAILS="foo@foo.bar" 3 | 4 | RDS_USERNAME="foo" 5 | RDS_PASSWORD="bar" 6 | RDS_DB_NAME="foo" 7 | RDS_HOSTNAME="bar" 8 | RDS_PORT="5432" 9 | 10 | POSTGRES_USER="foo" 11 | POSTGRES_DB="bar" 12 | POSTGRES_PASSWORD="foo" 13 | 14 | SENTRY_ELASTIC_BEANSTALK_DSN="https://foo.ingest.us.sentry.io/bar" 15 | SENTRY_DATA_PROCESSING_DSN="https://foo.ingest.us.sentry.io/bar" 16 | SENTRY_JAVASCRIPT_DSN="https://foo.ingest.us.sentry.io/bar" 17 | 18 | S3_BUCKET="foo" 19 | S3_ENDPOINT="http://bar:9000" 20 | MINIO_ACCESS_KEY="foo" 21 | MINIO_SECRET_KEY="bar" 22 | 23 | FLASK_SECRET_KEY="foobar" 24 | 25 | RABBITMQ_DEFAULT_USER="foo" 26 | RABBITMQ_DEFAULT_PASS="bar" 27 | RABBITMQ_USERNAME="foo" 28 | RABBITMQ_PASSWORD="bar" 29 | RABBITMQ_PORT="50000" -------------------------------------------------------------------------------- /cluster_management/pushed_files/ami_env_config.py: -------------------------------------------------------------------------------- 1 | import os 2 | os.environ['SENTRY_ELASTIC_BEANSTALK_DSN'] = 'https://foo:foo@sentry.io/foo' 3 | os.environ['SENTRY_DATA_PROCESSING_DSN'] = 'https://foo:foo@sentry.io/foo' 4 | os.environ['FLASK_SECRET_KEY'] = 'replace_with_random_string' 5 | os.environ['SENTRY_JAVASCRIPT_DSN'] = 'https://foo@sentry.io/foo' 6 | os.environ['SYSADMIN_EMAILS'] = 'webmaster@localhost' 7 | os.environ['DOMAIN_NAME'] = 'beiwe.org' 8 | os.environ['RDS_PASSWORD'] = 'password' 9 | os.environ['RDS_USERNAME'] = 'beiweuser' 10 | os.environ['RDS_DB_NAME'] = 'beiweproject' 11 | os.environ['RDS_HOSTNAME'] = 'localhost' 12 | os.environ['S3_BUCKET'] = 's3_bucket_name' 13 | os.environ['BEIWE_SERVER_AWS_SECRET_ACCESS_KEY'] = 'somestring' 14 | os.environ['BEIWE_SERVER_AWS_ACCESS_KEY_ID'] = 'somestring' 15 | -------------------------------------------------------------------------------- /database/management/commands/create_default_login.py: -------------------------------------------------------------------------------- 1 | # This file is currently deprecated, uncomment it if you would like to use it locally, but it is not maintained 2 | 3 | # from django.core.management.base import BaseCommand 4 | # from database.user_models_researcher import Researcher 5 | 6 | 7 | # class Command(BaseCommand): 8 | # args = "" 9 | # help = "" 10 | 11 | # def handle(self, *args, **options): 12 | # new_researcher = Researcher.create_with_password("admin", "admin") 13 | # researcher = Researcher(username="admin", password="admin") 14 | # researcher.set_password("abcdefg1234567!@#$%") 15 | # new_researcher.elevate_to_site_admin() 16 | # researcher.update(password_force_reset=True) # should already be set by default 17 | # return researcher -------------------------------------------------------------------------------- /frontend/static/javascript/app/survey-builder/directives/edit-question/edit-question.js: -------------------------------------------------------------------------------- 1 | angular.module("surveyBuilder") 2 | .directive("editQuestion", function() { 3 | return { 4 | link: link(), 5 | restrict: "E", 6 | scope: { 7 | show: "@?", 8 | surveyBuilder: "=" 9 | }, 10 | // need to add a fake variable to the end of the templateUrl to force the browser to reload the template 11 | templateUrl: "/static/javascript/app/survey-builder/directives/edit-question/edit-question.html?n=5" 12 | }; 13 | 14 | //////// 15 | 16 | function link() { 17 | return function(scope) { 18 | if (scope.show) { 19 | // Default to showing the modal 20 | $('#editQuestionModal').modal("show"); 21 | } 22 | } 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /frontend/templates/dashboard/dashboard_participant_javascript.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docker_management/.envs/.env.prod.template: -------------------------------------------------------------------------------- 1 | DOMAIN_NAME="localhost" 2 | PUBLIC_DOMAIN_NAME="foo.bar" 3 | SYSADMIN_EMAILS="foo@foo.bar" 4 | 5 | RDS_USERNAME="foo" 6 | RDS_PASSWORD="bar" 7 | RDS_DB_NAME="foo" 8 | RDS_HOSTNAME="bar" 9 | RDS_PORT="5432" 10 | 11 | POSTGRES_USER="foo" 12 | POSTGRES_DB="bar" 13 | POSTGRES_PASSWORD="foo" 14 | 15 | SENTRY_ELASTIC_BEANSTALK_DSN="https://foo.ingest.us.sentry.io/bar" 16 | SENTRY_DATA_PROCESSING_DSN="https://foo.ingest.us.sentry.io/bar" 17 | SENTRY_JAVASCRIPT_DSN="https://foo.ingest.us.sentry.io/bar" 18 | 19 | S3_BUCKET="foo" 20 | BEIWE_SERVER_AWS_ACCESS_KEY_ID="foo" 21 | BEIWE_SERVER_AWS_SECRET_ACCESS_KEY="bar" 22 | 23 | FLASK_SECRET_KEY="foobar" 24 | 25 | RABBITMQ_DEFAULT_USER="foo" 26 | RABBITMQ_DEFAULT_PASS="bar" 27 | RABBITMQ_USERNAME="foo" 28 | RABBITMQ_PASSWORD="bar" 29 | RABBITMQ_PORT="50000" -------------------------------------------------------------------------------- /frontend/static/javascript/jqplot/plugins/jqplot.ciParser.min.js: -------------------------------------------------------------------------------- 1 | /* jqPlot 1.0.8r1250 | (c) 2009-2013 Chris Leonello | jplot.com 2 | jsDate | (c) 2010-2013 Chris Leonello 3 | */(function(a){a.jqplot.ciParser=function(g,l){var m=[],o,n,h,f,e,c;if(typeof(g)=="string"){g=a.jqplot.JSON.parse(g,d)}else{if(typeof(g)=="object"){for(e in g){for(h=0;h=0){i=/^\/Date\((-?[0-9]+)\)\/$/.exec(k);if(i){return parseInt(i[1],10)}}return k}}for(var b in g){o=[];n=g[b];switch(b){case"PriceTicks":for(h=0;h 18 |

500 Internal Server Error

19 |

Sorry, something broke when you tried to access this page. The web application has restarted, and no damage has 20 | been done. Please email hdawood@hsph.harvard.edu and describe what 21 | happened; if possible, please include the time that this page appeared, so that we can look at the error logs 22 | and figure out what broke.

23 | 24 | 25 | {% endblock %} -------------------------------------------------------------------------------- /constants/security_constants.py: -------------------------------------------------------------------------------- 1 | import string 2 | 3 | ## Password Check Regexes 4 | SYMBOL_REGEX = "[^a-zA-Z0-9]" 5 | LOWERCASE_REGEX = "[a-z]" 6 | UPPERCASE_REGEX = "[A-Z]" 7 | NUMBER_REGEX = "[0-9]" 8 | PASSWORD_REQUIREMENT_REGEX_LIST = [SYMBOL_REGEX, LOWERCASE_REGEX, UPPERCASE_REGEX, NUMBER_REGEX] 9 | 10 | EASY_ALPHANUMERIC_CHARS = string.ascii_lowercase + '123456789' # intentionally does not have 0 11 | 12 | BASE64_GENERIC_ALLOWED_CHARACTERS = string .ascii_lowercase + string.ascii_uppercase + string.digits + "/+" 13 | OBJECT_ID_ALLOWED_CHARS = string.ascii_uppercase + string.ascii_lowercase + string.digits 14 | 15 | ASYMMETRIC_KEY_LENGTH = 2048 # length of private/public keys 16 | 17 | # this is a set of integers (bytes, technically), it is faster than testing bytes 18 | URLSAFE_BASE64_CHARACTERS = set(b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-=") 19 | 20 | MFA_CREATED = "mfa_created" -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import multiprocessing 3 | import os 4 | import sys 5 | 6 | 7 | if __name__ == "__main__": 8 | try: 9 | command = sys.argv[1] 10 | except IndexError: 11 | command = "help" 12 | 13 | if command == "test" and sys.platform == "darwin": 14 | # Workaround for https://code.djangoproject.com/ticket/31169 15 | if os.environ.get("OBJC_DISABLE_INITIALIZE_FORK_SAFETY", "") != "YES": 16 | print("Set OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES in your" + 17 | " environment to work around use of forking in Django's" + " test runner.") 18 | sys.exit(1) 19 | multiprocessing.set_start_method("fork") 20 | 21 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.django_settings") 22 | from django.core.management import execute_from_command_line 23 | execute_from_command_line(sys.argv) 24 | -------------------------------------------------------------------------------- /database/migrations/0144_dataprocessingstatus.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.2.6 on 2025-09-18 22:20 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0143_researcher_bad_login_attempts_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='DataProcessingStatus', 15 | fields=[ 16 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 17 | ('created_on', models.DateTimeField(auto_now_add=True)), 18 | ('last_updated', models.DateTimeField(auto_now=True)), 19 | ('last_run', models.DateTimeField(blank=True, null=True)), 20 | ], 21 | options={ 22 | 'abstract': False, 23 | }, 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /database/migrations/0097_auto_20221220_0041.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.16 on 2022-12-20 00:41 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('database', '0096_alter_researcher_password_force_reset'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='researcher', 16 | name='password_min_length', 17 | field=models.SmallIntegerField(default=8, validators=[django.core.validators.MinValueValidator(8)]), 18 | ), 19 | migrations.AlterField( 20 | model_name='study', 21 | name='password_minimum_length', 22 | field=models.PositiveIntegerField(default=8, validators=[django.core.validators.MinValueValidator(8), django.core.validators.MaxValueValidator(20)]), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /database/migrations/0040_fileastext.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.11 on 2020-09-29 18:24 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0039_auto_20200819_1901'), 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='FileAsText', 15 | fields=[ 16 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 17 | ('created_on', models.DateTimeField(auto_now_add=True)), 18 | ('last_updated', models.DateTimeField(auto_now=True)), 19 | ('tag', models.CharField(db_index=True, max_length=256)), 20 | ('text', models.TextField()), 21 | ], 22 | options={ 23 | 'abstract': False, 24 | }, 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /middleware/downtime_middleware.py: -------------------------------------------------------------------------------- 1 | from types import FunctionType 2 | 3 | from django.http.request import HttpRequest 4 | from django.http.response import HttpResponse 5 | 6 | from database.system_models import GlobalSettings 7 | 8 | 9 | class DowntimeMiddleware: 10 | """ A very quick and dirty downtime middleware. If downtime is enabled, it returns a 503. """ 11 | 12 | def __init__(self, get_response: FunctionType): 13 | # just following the standard passthrough... 14 | self.get_response = get_response 15 | 16 | def __call__(self, request: HttpRequest): 17 | # if downtime is enabled, return a 503. 18 | if GlobalSettings.cached_downtime_enabled(): 19 | return HttpResponse( 20 | content="This server is currently undergoing maintenance, please try again later.", 21 | status=503 22 | ) 23 | return self.get_response(request) 24 | -------------------------------------------------------------------------------- /database/migrations/0039_auto_20200819_1901.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.14 on 2020-08-19 19:01 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0038_auto_20200730_0413'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='archivedevent', 15 | name='response_time', 16 | field=models.DateTimeField(blank=True, db_index=True, null=True), 17 | ), 18 | migrations.AlterField( 19 | model_name='archivedevent', 20 | name='schedule_type', 21 | field=models.CharField(db_index=True, max_length=32), 22 | ), 23 | migrations.AlterField( 24 | model_name='archivedevent', 25 | name='scheduled_time', 26 | field=models.DateTimeField(db_index=True), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /database/migrations/0090_auto_20221025_0621.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.15 on 2022-10-25 06:21 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0089_auto_20221021_0307'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='participant', 15 | name='last_os_version', 16 | field=models.CharField(blank=True, max_length=32, null=True), 17 | ), 18 | migrations.AddField( 19 | model_name='participant', 20 | name='last_version_code', 21 | field=models.CharField(blank=True, max_length=32, null=True), 22 | ), 23 | migrations.AddField( 24 | model_name='participant', 25 | name='last_version_name', 26 | field=models.CharField(blank=True, max_length=32, null=True), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /database/migrations/0116_remove_summarystatisticdaily_beiwe_image_survey_bytes.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-03-12 13:09 2 | 3 | from django.db import migrations 4 | 5 | 6 | # image survey was a type that was never fully developed. Any instances of it are to be removed. 7 | def remove_image_survey_chunkregistry(apps, schema_editor): 8 | ChunkRegistry = apps.get_model('database', 'ChunkRegistry') 9 | ChunkRegistry.objects.filter(data_type='image_survey').delete() 10 | 11 | 12 | class Migration(migrations.Migration): 13 | 14 | dependencies = [ 15 | ('database', '0115_participant_enable_extensive_device_info_tracking'), 16 | ] 17 | 18 | operations = [ 19 | migrations.RunPython(remove_image_survey_chunkregistry, reverse_code=migrations.RunPython.noop), 20 | migrations.RemoveField( 21 | model_name='summarystatisticdaily', 22 | name='beiwe_image_survey_bytes', 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /database/migrations/0102_forestversion.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.16 on 2023-04-25 06:50 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0101_participantdeletionevent'), 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='ForestVersion', 15 | fields=[ 16 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 17 | ('created_on', models.DateTimeField(auto_now_add=True)), 18 | ('last_updated', models.DateTimeField(auto_now=True)), 19 | ('package_version', models.TextField(blank=True, default='')), 20 | ('git_commit', models.TextField(blank=True, default='')), 21 | ], 22 | options={ 23 | 'abstract': False, 24 | }, 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /database/migrations/0112_participantactionlog.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.23 on 2024-01-27 09:44 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('database', '0111_auto_20240118_2311'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='ParticipantActionLog', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('timestamp', models.DateTimeField(db_index=True)), 19 | ('action', models.TextField()), 20 | ('participant', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='action_logs', to='database.participant')), 21 | ], 22 | options={ 23 | 'abstract': False, 24 | }, 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /database/migrations/0002_auto_20170923_1949.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.5 on 2017-09-23 19:49 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='survey', 15 | name='survey_type', 16 | field=models.CharField(choices=[('audio_survey', 'audio_survey'), ('tracking_survey', 'tracking_survey'), ('dummy', 'dummy')], help_text='What type of survey this is.', max_length=16), 17 | ), 18 | migrations.AlterField( 19 | model_name='surveyarchive', 20 | name='survey_type', 21 | field=models.CharField(choices=[('audio_survey', 'audio_survey'), ('tracking_survey', 'tracking_survey'), ('dummy', 'dummy')], help_text='What type of survey this is.', max_length=16), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /database/migrations/0124_remove_apikey_has_tableau_api_permissions_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-05-30 15:09 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('database', '0123_universal_api_keys'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='apikey', 16 | name='has_tableau_api_permissions', 17 | ), 18 | migrations.AddField( 19 | model_name='apikey', 20 | name='last_used', 21 | field=models.DateTimeField(blank=True, null=True), 22 | ), 23 | migrations.AlterField( 24 | model_name='apikey', 25 | name='researcher', 26 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='api_keys', to='database.researcher'), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /frontend/static/javascript/jqplot/plugins/jqplot.mobile.min.js: -------------------------------------------------------------------------------- 1 | /* jqPlot 1.0.8r1250 | (c) 2009-2013 Chris Leonello | jplot.com 2 | jsDate | (c) 2010-2013 Chris Leonello 3 | */(function(b){function a(e,d,c){this.bindCustomEvents=function(){this.eventCanvas._elem.bind("vclick",{plot:this},this.onClick);this.eventCanvas._elem.bind("dblclick",{plot:this},this.onDblClick);this.eventCanvas._elem.bind("taphold",{plot:this},this.onDblClick);this.eventCanvas._elem.bind("vmousedown",{plot:this},this.onMouseDown);this.eventCanvas._elem.bind("vmousemove",{plot:this},this.onMouseMove);this.eventCanvas._elem.bind("mouseenter",{plot:this},this.onMouseEnter);this.eventCanvas._elem.bind("mouseleave",{plot:this},this.onMouseLeave);if(this.captureRightClick){this.eventCanvas._elem.bind("vmouseup",{plot:this},this.onRightClick);this.eventCanvas._elem.get(0).oncontextmenu=function(){return false}}else{this.eventCanvas._elem.bind("vmouseup",{plot:this},this.onMouseUp)}};this.plugins.mobile=true}b.jqplot.postInitHooks.push(a)})(jQuery); -------------------------------------------------------------------------------- /config/load_django.py: -------------------------------------------------------------------------------- 1 | import builtins 2 | import os 3 | 4 | import django 5 | from django.conf import settings 6 | 7 | import config.django_settings 8 | 9 | 10 | builtins = list(vars(builtins).keys()) 11 | 12 | # When this file is imported we attempt to load django. If django is already loaded this action 13 | # throws a RuntimeError we catch that case and pass. 14 | try: 15 | # get all the variables declared in the django settings file, exclude builtins by _identity_. 16 | # Django 3.2 requires that all settings be upper case. 17 | django_config = { 18 | setting_name: setting_value 19 | for setting_name, setting_value in vars(config.django_settings).items() 20 | if setting_value not in builtins and setting_name.isupper() 21 | } 22 | 23 | # django setup file 24 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.django_settings") 25 | settings.configure(**django_config) 26 | django.setup() 27 | 28 | except RuntimeError as e: 29 | pass 30 | -------------------------------------------------------------------------------- /scripts/repopulate_push_notifications.py: -------------------------------------------------------------------------------- 1 | from django.utils import timezone 2 | 3 | from database.study_models import Study 4 | from database.system_models import GlobalSettings 5 | from libs.schedules import repopulate_all_survey_scheduled_events 6 | 7 | 8 | def main(): 9 | for study in Study.objects.all(): 10 | repopulate_all_survey_scheduled_events(study) 11 | 12 | # This flag eneables resends. This has to be set _After_ schedules are repopulated 13 | # because... on servers where the participants have updated the app before the server has 14 | # been updated will possibly be in a position where they receive a resend of every archived 15 | # event within that period. 16 | 17 | # - repopulating logic may generate events that were missed (because of old bugs) 18 | settings = GlobalSettings().singleton() 19 | if settings.push_notification_resend_enabled is None: 20 | settings.push_notification_resend_enabled = timezone.now() 21 | settings.save() 22 | -------------------------------------------------------------------------------- /database/migrations/0078_genericevent.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.13 on 2022-05-06 09:44 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('database', '0077_delete_fileprocesslock'), 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='GenericEvent', 15 | fields=[ 16 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 17 | ('created_on', models.DateTimeField(auto_now_add=True)), 18 | ('last_updated', models.DateTimeField(auto_now=True)), 19 | ('tag', models.CharField(db_index=True, max_length=256)), 20 | ('note', models.TextField()), 21 | ('stacktrace', models.TextField(blank=True, null=True)), 22 | ], 23 | options={ 24 | 'abstract': False, 25 | }, 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /constants/__init__.py: -------------------------------------------------------------------------------- 1 | # Todo: Once we upgrade to Django 3, use TextChoices(?) 2 | # we have this pattern used in a couple places, it is for choice fields. 3 | # intended for use only on classes with string values and nothing else. 4 | class DjangoDropdown: 5 | 6 | @classmethod 7 | def choices(cls) -> list[tuple[str, str]]: 8 | # this forces a dropdown to have specific choices of the same name as the attribute itself 9 | # with underscores -> spaces and title casing. 10 | return [(choice, choice.replace("_", " ").title()) for choice in cls.values()] 11 | 12 | @classmethod 13 | def values(cls) -> list[str]: 14 | # get the string VALUES of the attributes of the class for every attribute that does not start with an underscore 15 | ret = [] 16 | for property_name, property_value in vars(cls).items(): 17 | if not property_name.startswith("_") and isinstance(property_value, str): 18 | ret.append(property_value) 19 | return ret 20 | -------------------------------------------------------------------------------- /database/migrations/0045_auto_20210121_0301.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.14 on 2021-01-21 03:01 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | # this migration used to use an external library called django-timezone-field it uses a charfield, it 7 | # didn't work, we got rid of it, this (should) now be manually patched out. That field type 8 | # should just be a CharField with a max_length of 63. The default value was 'America/New_York'. 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | ('database', '0044_auto_20210115_2300'), 15 | ] 16 | 17 | operations = [ 18 | migrations.AddField( 19 | model_name='participant', 20 | name='push_notification_unreachable', 21 | field=models.SmallIntegerField(default=True), 22 | ), 23 | migrations.AddField( 24 | model_name='participant', 25 | name='timezone', 26 | field=models.CharField(default='America/New_York'), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /database/migrations/0076_auto_20220420_2045.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.12 on 2022-04-20 20:45 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('database', '0075_iosedecryptionkey'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RenameModel('IOSEDecryptionKey', 'IOSDecryptionKey'), 15 | migrations.AlterField( 16 | model_name='iosdecryptionkey', 17 | name='base64_encryption_key', 18 | field=models.CharField(max_length=24), 19 | ), 20 | migrations.AlterField( 21 | model_name='iosdecryptionkey', 22 | name='s3_file_path', 23 | field=models.CharField(db_index=True, max_length=80, unique=True), 24 | ), 25 | migrations.RenameField( 26 | model_name='iosdecryptionkey', 27 | old_name='s3_file_path', 28 | new_name='file_name', 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /frontend/templates/notifications.html: -------------------------------------------------------------------------------- 1 | {% for message in messages %} 2 | {# {% for message in [ {"message": "success", "tags": "success"}, {"message": "info", "tags": "info"}, {"message": "warning", "tags": "warning"}, {"message": "error", "tags": "error"}, ] %} #} 3 | {% if message.tags != "error" %} 4 |