├── api ├── __init__.py ├── jobs │ └── __init__.py ├── web │ ├── __init__.py │ ├── encoder.py │ └── errors.py ├── handlers │ ├── __init__.py │ ├── schemahandler.py │ ├── confighandler.py │ └── resolvehandler.py ├── dao │ ├── __init__.py │ └── dbutil.py ├── types.py ├── filetypes.json └── auth │ ├── userauth.py │ └── groupauth.py ├── tests ├── integration_tests │ ├── .npmignore │ ├── requirements-integration-test.txt │ ├── package.json │ └── python │ │ ├── test_errors.py │ │ ├── test_queue.py │ │ └── test_notes.py ├── bin │ └── setup-integration-tests-ubuntu.sh └── unit_tests │ └── python │ ├── test_key.py │ ├── test_request.py │ ├── test_files.py │ ├── test_gear_util.py │ └── test_db_upgrade.py ├── logs └── .gitignore ├── swagger ├── .gitignore ├── examples │ ├── output │ │ ├── container-delete.json │ │ ├── container-update.json │ │ ├── group-delete.json │ │ ├── user-delete.json │ │ ├── job-config.json │ │ ├── user-update.json │ │ ├── tag.json │ │ ├── job-new.json │ │ ├── group-new.json │ │ ├── collection-new.json │ │ ├── container-new.json │ │ ├── file-download.json │ │ ├── packfile-start.json │ │ ├── permission.json │ │ ├── project-groups-list.json │ │ ├── collection-curators-list.json │ │ ├── analysis-files-create-ticket.json │ │ ├── version.json │ │ ├── note.json │ │ ├── device.json │ │ ├── collection-list.json │ │ ├── collection.json │ │ ├── rule.json │ │ ├── project.json │ │ ├── session.json │ │ ├── session-template-recalc.json │ │ ├── group.json │ │ ├── report-site.json │ │ ├── groups-list.json │ │ ├── job.json │ │ ├── device-status.json │ │ ├── device-list.json │ │ ├── acquisition.json │ │ ├── rule-list.json │ │ ├── gear.json │ │ ├── gear-list.json │ │ ├── session-list.json │ │ ├── job-list.json │ │ └── project-list.json │ ├── input │ │ ├── file-update.json │ │ ├── job-update.json │ │ ├── project-update.json │ │ ├── rule-update.json │ │ ├── analysis-update.json │ │ ├── session-update.json │ │ ├── project.json │ │ ├── group-add-role.json │ │ ├── permission-update.json │ │ ├── avatars.json │ │ ├── group-add-permission.json │ │ ├── file-list.json │ │ ├── info_update.json │ │ ├── labelupload.json │ │ ├── uidmatchupload.json │ │ ├── session.json │ │ ├── subject.json │ │ ├── file.json │ │ ├── packfile.json │ │ ├── uidupload.json │ │ ├── analysis-legacy.json │ │ ├── rule-new.json │ │ ├── analysis.json │ │ └── project-template.json │ ├── rules_list.json │ ├── create_download_incomplete_and_dicom.json │ ├── job_stats.json │ ├── create_download_incomplete_or_not_dicom.json │ ├── user-list.json │ ├── user_jane_doe.json │ ├── scitran_config.json │ ├── scitran_config.js.txt │ └── file_info_list.json ├── support │ ├── jasmine.json │ ├── tasks │ │ └── lint-schemas.js │ └── walk.js ├── schemas │ ├── input │ │ ├── avatars.json │ │ ├── file-update.json │ │ ├── info_update.json │ │ ├── packfile.json │ │ ├── uidupload.json │ │ ├── labelupload.json │ │ ├── project-template.json │ │ ├── enginemetadata.json │ │ ├── file.json │ │ ├── file-list.json │ │ ├── uidmatchupload.json │ │ ├── subject.json │ │ ├── session-update.json │ │ ├── tag.json │ │ ├── rule-update.json │ │ ├── analysis-update.json │ │ ├── group-update.json │ │ ├── project-update.json │ │ ├── session.json │ │ ├── analysis.json │ │ ├── project.json │ │ ├── rule-new.json │ │ ├── analysis-legacy.json │ │ ├── job-logs.json │ │ ├── resolver.json │ │ ├── note.json │ │ ├── search-query.json │ │ ├── group-new.json │ │ ├── device.json │ │ ├── acquisition-update.json │ │ ├── user-update.json │ │ ├── permission.json │ │ ├── collection.json │ │ ├── download.json │ │ ├── user-new.json │ │ ├── acquisition.json │ │ ├── collection-update.json │ │ ├── propose-batch.json │ │ ├── job-new.json │ │ └── gear.json │ ├── mongo │ │ ├── avatars.json │ │ ├── tag.json │ │ ├── permission.json │ │ ├── note.json │ │ ├── collection.json │ │ ├── project.json │ │ ├── acquisition.json │ │ ├── container.json │ │ ├── group.json │ │ ├── session.json │ │ ├── file.json │ │ ├── subject.json │ │ ├── analysis.json │ │ └── user.json │ ├── output │ │ ├── tag.json │ │ ├── config.json │ │ ├── job-new.json │ │ ├── group-delete.json │ │ ├── group-new.json │ │ ├── report-site.json │ │ ├── user-delete.json │ │ ├── user-update.json │ │ ├── version.json │ │ ├── device.json │ │ ├── container-new.json │ │ ├── file-download.json │ │ ├── packfile-start.json │ │ ├── report-project.json │ │ ├── session-jobs.json │ │ ├── collection-new.json │ │ ├── device-status.json │ │ ├── groups-list.json │ │ ├── job.json │ │ ├── gear.json │ │ ├── job-list.json │ │ ├── note.json │ │ ├── session-template-recalc.json │ │ ├── device-list.json │ │ ├── project-groups-list.json │ │ ├── rule-list.json │ │ ├── user-self.json │ │ ├── analysis.json │ │ ├── analysis-files-create-ticket.json │ │ ├── gear-list.json │ │ ├── permission.json │ │ ├── file-list.json │ │ ├── logout-output.json │ │ ├── user-new.json │ │ ├── search-response-list.json │ │ ├── batch-cancel.json │ │ ├── collection.json │ │ ├── login-output.json │ │ ├── rule.json │ │ ├── collection-list.json │ │ ├── user.json │ │ ├── group.json │ │ ├── job-log.json │ │ ├── acquisition.json │ │ ├── project.json │ │ ├── session.json │ │ ├── session-list.json │ │ ├── project-list.json │ │ ├── acquisition-list.json │ │ ├── user-list.json │ │ ├── collection-curators-list.json │ │ ├── batch-proposal.json │ │ ├── file-info.json │ │ ├── batch.json │ │ ├── batch-list.json │ │ ├── analyses-list.json │ │ └── resolver.json │ └── definitions │ │ ├── created-modified.json │ │ ├── version.json │ │ ├── auth.json │ │ ├── avatars.json │ │ ├── tag.json │ │ ├── permission.json │ │ ├── project-template.json │ │ ├── info.json │ │ ├── note.json │ │ ├── common.json │ │ ├── container.json │ │ ├── rule.json │ │ ├── device.json │ │ └── resolver.json ├── paths │ ├── config.yaml │ ├── clean-packfiles.yaml │ ├── version.yaml │ ├── login.yaml │ ├── dataexplorer.yaml │ ├── upload-by-uid.yaml │ ├── report.yaml │ ├── upload-match-uid.yaml │ ├── upload-by-label.yaml │ ├── resolver.yaml │ ├── config-js.yaml │ ├── site-rules.yaml │ ├── upload-by-reaper.yaml │ ├── engine.yaml │ ├── download.yaml │ └── analyses.yaml ├── templates │ ├── packfile-start.yaml │ ├── permissions.yaml │ ├── file-list-upload.yaml │ ├── analysis-notes-item.yaml │ ├── notes.yaml │ ├── container-item-info.yaml │ ├── tags.yaml │ ├── packfile-end.yaml │ ├── analysis-notes.yaml │ ├── packfile.yaml │ ├── container.yaml │ ├── file-item-info.yaml │ ├── notes-note.yaml │ ├── permissions-user.yaml │ ├── container-item.yaml │ ├── tags-tag.yaml │ ├── analysis-files.yaml │ ├── analysis-item.yaml │ ├── analyses-list.yaml │ └── analysis-files-create-ticket-filename.yaml ├── package.json └── responses │ └── index.yaml ├── sphinx ├── .gitignore ├── requirements-docs.txt ├── source │ └── index.rst └── build-docs.sh ├── .github_deploy_key.enc ├── docker ├── requirements-docker.txt ├── uwsgi-config.ini ├── pymongo-cli.py ├── bootstrap-accounts.sh ├── inject_build_info.sh ├── build-trigger.sh ├── uwsgi-entrypoint.sh └── README.md ├── bin ├── api.wsgi ├── install-ubuntu.sh └── oneoffs │ ├── cas_statistic.py │ └── load_external_data.py ├── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .dockerignore ├── requirements.txt ├── bootstrap.sample.json ├── codecov.yml ├── LICENSE ├── TESTING.md ├── docs └── README.md ├── sample.config ├── .travis.yml └── README.md /api/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /api/jobs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/web/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/handlers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration_tests/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /logs/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | user_access.log 3 | -------------------------------------------------------------------------------- /swagger/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | node_modules/ 3 | 4 | -------------------------------------------------------------------------------- /swagger/examples/output/container-delete.json: -------------------------------------------------------------------------------- 1 | {"deleted":1} 2 | -------------------------------------------------------------------------------- /sphinx/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | source/api.* 3 | source/modules.rst 4 | -------------------------------------------------------------------------------- /swagger/examples/output/container-update.json: -------------------------------------------------------------------------------- 1 | {"modified":1} 2 | -------------------------------------------------------------------------------- /swagger/examples/output/group-delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "deleted": 1 3 | } -------------------------------------------------------------------------------- /swagger/examples/output/user-delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "deleted": 3 3 | } -------------------------------------------------------------------------------- /swagger/examples/input/file-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "dicom" 3 | } 4 | -------------------------------------------------------------------------------- /swagger/examples/output/job-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "speed": 5 3 | } 4 | -------------------------------------------------------------------------------- /swagger/examples/output/user-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "modified": 1 3 | } 4 | -------------------------------------------------------------------------------- /swagger/examples/output/tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "value":"example-tag" 3 | } 4 | -------------------------------------------------------------------------------- /sphinx/requirements-docs.txt: -------------------------------------------------------------------------------- 1 | Sphinx==1.4.1 2 | sphinxcontrib-httpdomain==1.4.0 3 | -------------------------------------------------------------------------------- /swagger/examples/input/job-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags":["spectroscopy"] 3 | } 4 | -------------------------------------------------------------------------------- /swagger/examples/input/project-update.json: -------------------------------------------------------------------------------- 1 | {"label":"test2-project-new-label"} 2 | -------------------------------------------------------------------------------- /swagger/examples/input/rule-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dcm2niix_v1" 3 | } 4 | -------------------------------------------------------------------------------- /swagger/examples/output/job-new.json: -------------------------------------------------------------------------------- 1 | { 2 | "_id": "5898cf5b035c0800150e1175" 3 | } -------------------------------------------------------------------------------- /.github_deploy_key.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scitran/core/HEAD/.github_deploy_key.enc -------------------------------------------------------------------------------- /swagger/examples/input/analysis-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "label":"New analysis label" 3 | } 4 | -------------------------------------------------------------------------------- /swagger/examples/input/session-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "an updated session" 3 | } 4 | -------------------------------------------------------------------------------- /swagger/examples/output/group-new.json: -------------------------------------------------------------------------------- 1 | { 2 | "_id":"57bb486a9e512c41c8b7fdd5" 3 | } 4 | -------------------------------------------------------------------------------- /api/dao/__init__.py: -------------------------------------------------------------------------------- 1 | def noop(*args, **kwargs): # pylint: disable=unused-argument 2 | pass 3 | -------------------------------------------------------------------------------- /swagger/examples/output/collection-new.json: -------------------------------------------------------------------------------- 1 | { 2 | "_id":"57bb486a9e512c41c8b7fdd5" 3 | } 4 | -------------------------------------------------------------------------------- /swagger/examples/output/container-new.json: -------------------------------------------------------------------------------- 1 | { 2 | "_id":"57bb486a9e512c41c8b7fdd5" 3 | } 4 | -------------------------------------------------------------------------------- /swagger/examples/output/file-download.json: -------------------------------------------------------------------------------- 1 | {"ticket": "1e975e3d-21e9-41f4-bb97-261f03d35ba1"} 2 | -------------------------------------------------------------------------------- /swagger/examples/output/packfile-start.json: -------------------------------------------------------------------------------- 1 | { 2 | "token":"57fe88cc9e512c5d2b5dcb0b" 3 | } 4 | -------------------------------------------------------------------------------- /docker/requirements-docker.txt: -------------------------------------------------------------------------------- 1 | # Service and support dependencies 2 | uWSGI==2.0.11 3 | ipython==4.0.2 4 | -------------------------------------------------------------------------------- /swagger/examples/input/project.json: -------------------------------------------------------------------------------- 1 | {"group":"scitran","label":"test2","description":"test project"} 2 | -------------------------------------------------------------------------------- /bin/api.wsgi: -------------------------------------------------------------------------------- 1 | # vim: filetype=python 2 | from api.web import start 3 | 4 | application = start.app_factory() 5 | -------------------------------------------------------------------------------- /swagger/examples/input/group-add-role.json: -------------------------------------------------------------------------------- 1 | {"access": "admin", "_id": "coltonlw@flywheel.io", "site":"local"} 2 | -------------------------------------------------------------------------------- /swagger/examples/output/permission.json: -------------------------------------------------------------------------------- 1 | { 2 | "access": "admin", 3 | "_id": "coltonlw@flywheel.io" 4 | } 5 | -------------------------------------------------------------------------------- /swagger/examples/input/permission-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "access": "ro", 3 | "_id": "coltonlw@flywheel.io" 4 | } 5 | -------------------------------------------------------------------------------- /swagger/examples/input/avatars.json: -------------------------------------------------------------------------------- 1 | { 2 | "gravatar": "https://gravatar.com/avatar/df68bc71e4c4875a311b734f68fe25e4" 3 | } -------------------------------------------------------------------------------- /swagger/examples/input/group-add-permission.json: -------------------------------------------------------------------------------- 1 | {"access": "admin", "_id": "coltonlw@flywheel.io", "site":"local"} 2 | -------------------------------------------------------------------------------- /swagger/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "support/spec", 3 | "spec_files": [ 4 | "*-spec.js" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /swagger/examples/input/file-list.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"name": "file_1.csv", "info": {"foo": "bar"}}, 3 | {"name": "file_2.zip"} 4 | ] 5 | -------------------------------------------------------------------------------- /swagger/examples/input/info_update.json: -------------------------------------------------------------------------------- 1 | { 2 | "set": { 3 | "BodyPartExamined": "BRAIN" 4 | }, 5 | "delete": [ "PatientName" ] 6 | } 7 | -------------------------------------------------------------------------------- /swagger/examples/output/project-groups-list.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "label": "Test Group", 4 | "_id": "test_group" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /swagger/examples/input/labelupload.json: -------------------------------------------------------------------------------- 1 | { 2 | "group": { 3 | "_id": "test_group" 4 | }, 5 | "project": { 6 | "label": "Neuroscience" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /swagger/schemas/input/avatars.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref":"../definitions/avatars.json#/definitions/avatars" 4 | } 5 | -------------------------------------------------------------------------------- /swagger/schemas/mongo/avatars.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref":"../definitions/avatars.json#/definitions/avatars" 4 | } 5 | -------------------------------------------------------------------------------- /swagger/schemas/output/tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "allOf":[{"$ref":"../definitions/tag.json#/definitions/tag"}] 4 | } 5 | -------------------------------------------------------------------------------- /swagger/examples/output/collection-curators-list.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "lastname": "LW", 4 | "_id": "coltonlw@flywheel.io", 5 | "firstname": "Colton" 6 | } 7 | ] 8 | -------------------------------------------------------------------------------- /swagger/schemas/output/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "../definitions/config.json#/definitions/config-output" 4 | } 5 | -------------------------------------------------------------------------------- /swagger/schemas/output/job-new.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "../definitions/common.json#/definitions/object-created" 4 | } 5 | -------------------------------------------------------------------------------- /docker/uwsgi-config.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | wsgi-file = bin/api.wsgi 3 | chdir=code/api 4 | pythonpath=code/data 5 | master = True 6 | die-on-term = True 7 | processes = 4 8 | threads = 2 9 | -------------------------------------------------------------------------------- /swagger/schemas/output/group-delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "../definitions/common.json#/definitions/deleted-count" 4 | } 5 | -------------------------------------------------------------------------------- /swagger/schemas/output/group-new.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "../definitions/group.json#/definitions/group-new-output" 4 | } 5 | -------------------------------------------------------------------------------- /swagger/schemas/output/report-site.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "../definitions/report.json#/definitions/report-site" 4 | } 5 | -------------------------------------------------------------------------------- /swagger/schemas/output/user-delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "../definitions/common.json#/definitions/deleted-count" 4 | } 5 | -------------------------------------------------------------------------------- /swagger/schemas/output/user-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "../definitions/common.json#/definitions/modified-count" 4 | } 5 | -------------------------------------------------------------------------------- /swagger/schemas/output/version.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "../definitions/version.json#/definitions/version-output" 4 | } 5 | -------------------------------------------------------------------------------- /swagger/schemas/input/file-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "allOf":[{"$ref":"../definitions/file.json#/definitions/file-update"}] 4 | } 5 | -------------------------------------------------------------------------------- /swagger/schemas/input/info_update.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "../definitions/info.json#/definitions/info-update-input" 4 | } 5 | -------------------------------------------------------------------------------- /swagger/schemas/input/packfile.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "../definitions/packfile.json#/definitions/packfile-input" 4 | } 5 | -------------------------------------------------------------------------------- /swagger/schemas/output/device.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "allOf":[{"$ref":"../definitions/device.json#/definitions/device-output"}] 4 | } 5 | -------------------------------------------------------------------------------- /swagger/examples/input/uidmatchupload.json: -------------------------------------------------------------------------------- 1 | { 2 | "session": { 3 | "uid": "5898cf5b106450a2d75e2c02" 4 | }, 5 | "acquisition": { 6 | "uid": "5898cf5b106450a2d75e2c03" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /swagger/schemas/input/uidupload.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "../definitions/enginemetadata.json#/definitions/uid-upload-input" 4 | } 5 | -------------------------------------------------------------------------------- /swagger/schemas/output/container-new.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "../definitions/container.json#/definitions/container-new-output" 4 | } 5 | -------------------------------------------------------------------------------- /swagger/schemas/output/file-download.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "../definitions/download.json#/definitions/download-ticket" 4 | } 5 | -------------------------------------------------------------------------------- /swagger/schemas/output/packfile-start.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "../definitions/packfile.json#/definitions/packfile-start" 4 | } 5 | -------------------------------------------------------------------------------- /swagger/schemas/output/report-project.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "../definitions/report.json#/definitions/report-project" 4 | } 5 | -------------------------------------------------------------------------------- /swagger/schemas/output/session-jobs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "../definitions/session.json#/definitions/session-jobs-output" 4 | } 5 | -------------------------------------------------------------------------------- /docker/pymongo-cli.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pymongo 3 | from bson import ObjectId 4 | db_uri = os.getenv('SCITRAN_PERSISTENT_DB_URI') 5 | db = pymongo.MongoClient(db_uri).get_default_database() 6 | -------------------------------------------------------------------------------- /swagger/examples/input/session.json: -------------------------------------------------------------------------------- 1 | { 2 | "subject": { 3 | "code": "a subject" 4 | }, 5 | "label": "a session", 6 | "project": "57e29f8aa479a44d25f28c47", 7 | "public": false 8 | } 9 | -------------------------------------------------------------------------------- /swagger/schemas/input/labelupload.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "../definitions/enginemetadata.json#/definitions/label-upload-input" 4 | } 5 | -------------------------------------------------------------------------------- /swagger/schemas/input/project-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref":"../definitions/project-template.json#/definitions/project-template" 4 | } 5 | -------------------------------------------------------------------------------- /swagger/schemas/output/collection-new.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "../definitions/collection.json#/definitions/collection-new-output" 4 | } 5 | -------------------------------------------------------------------------------- /swagger/schemas/output/device-status.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "allOf": [{"$ref":"../definitions/device.json#/definitions/device-status"}] 4 | } 5 | -------------------------------------------------------------------------------- /swagger/schemas/output/groups-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "allOf":[{"$ref":"../definitions/group.json#/definitions/group-output-list"}] 4 | } 5 | -------------------------------------------------------------------------------- /swagger/schemas/input/enginemetadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "../definitions/enginemetadata.json#/definitions/engine-upload-input" 4 | } 5 | -------------------------------------------------------------------------------- /swagger/schemas/input/file.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type":"object", 4 | "allOf":[{"$ref":"../definitions/file.json#/definitions/file-input"}] 5 | } 6 | -------------------------------------------------------------------------------- /swagger/schemas/output/job.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type":"object", 4 | "allOf":[{"$ref":"../definitions/job.json#/definitions/job-output"}] 5 | } 6 | -------------------------------------------------------------------------------- /swagger/examples/input/subject.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": "12/11/17", 3 | "firstname": "Anonymous", 4 | "lastname": "", 5 | "sex": "male", 6 | "age": 31, 7 | "info": { 8 | "some-key": 37 9 | } 10 | } -------------------------------------------------------------------------------- /swagger/examples/output/analysis-files-create-ticket.json: -------------------------------------------------------------------------------- 1 | { 2 | "ticket": "57f2af23-a94c-426d-8521-11b2e8782020", 3 | "filename": "exampledicom.zip", 4 | "file_cnt": 1, 5 | "size": 4525137 6 | } 7 | -------------------------------------------------------------------------------- /swagger/schemas/input/file-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "array", 4 | "items": {"$ref":"../definitions/file.json#/definitions/file-input"} 5 | } 6 | -------------------------------------------------------------------------------- /swagger/schemas/input/uidmatchupload.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "../definitions/enginemetadata.json#/definitions/uid-match-upload-input" 4 | } 5 | -------------------------------------------------------------------------------- /swagger/schemas/output/gear.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type":"object", 4 | "allOf":[{"$ref":"../definitions/gear.json#/definitions/gear-doc"}] 5 | } 6 | -------------------------------------------------------------------------------- /swagger/schemas/output/job-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "array", 4 | "items": {"$ref": "../definitions/job.json#/definitions/job-output"} 5 | } 6 | -------------------------------------------------------------------------------- /swagger/schemas/output/note.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "allOf":[{"$ref":"../definitions/note.json#/definitions/note-output"}] 5 | } 6 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Review Checklist 4 | 5 | - Tests were added to cover all code changes 6 | - Documentation was added / updated 7 | - Code and tests follow standards in CONTRIBUTING.md 8 | -------------------------------------------------------------------------------- /swagger/examples/input/file.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "4784_1_1_localizer_dicom.zip", 3 | "type": "dicom", 4 | "mimetype": "application/zip", 5 | "measurements": [], 6 | "tags": [], 7 | "info": {} 8 | } 9 | -------------------------------------------------------------------------------- /swagger/schemas/input/subject.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type":"object", 4 | "allOf":[{"$ref":"../definitions/subject.json#/definitions/subject-input"}] 5 | } 6 | -------------------------------------------------------------------------------- /swagger/schemas/output/session-template-recalc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "../definitions/session.json#/definitions/session-template-recalc-output" 4 | } 5 | -------------------------------------------------------------------------------- /swagger/schemas/output/device-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "array", 4 | "items": { "$ref":"../definitions/device.json#/definitions/device-output" } 5 | } 6 | -------------------------------------------------------------------------------- /swagger/schemas/output/project-groups-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "allOf":[{"$ref":"../definitions/group.json#/definitions/project-group-output-list"}] 4 | } 5 | 6 | -------------------------------------------------------------------------------- /swagger/schemas/output/rule-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "array", 4 | "items": { "$ref": "../definitions/rule.json#/definitions/rule-output" } 5 | } 6 | -------------------------------------------------------------------------------- /swagger/schemas/output/user-self.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type":"object", 4 | "allOf": [{"$ref":"../definitions/user.json#/definitions/user-output-api-key"}] 5 | } 6 | -------------------------------------------------------------------------------- /swagger/schemas/output/analysis.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type":"object", 4 | "allOf":[{"$ref":"../definitions/analysis.json#/definitions/analysis-output"}] 5 | } 6 | -------------------------------------------------------------------------------- /swagger/schemas/input/session-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "Session", 4 | "allOf":[{"$ref":"../definitions/session.json#/definitions/session-input"}] 5 | } 6 | -------------------------------------------------------------------------------- /swagger/schemas/input/tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "allOf":[{"$ref":"../definitions/tag.json#/definitions/tag"}], 4 | "example": { 5 | "value":"example-tag" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /swagger/schemas/output/analysis-files-create-ticket.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "../definitions/analysis.json#/definitions/analysis-files-create-ticket-output" 4 | } 5 | -------------------------------------------------------------------------------- /swagger/schemas/input/rule-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "Rule", 4 | "type": "object", 5 | "allOf": [{"$ref": "../definitions/rule.json#/definitions/rule-input"}] 6 | } 7 | -------------------------------------------------------------------------------- /swagger/schemas/mongo/tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "properties": { 5 | "value": {"type": "string"} 6 | }, 7 | "additionalProperties": false 8 | } 9 | -------------------------------------------------------------------------------- /swagger/schemas/output/gear-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type":"array", 4 | "items":{ 5 | "allOf":[{"$ref":"../definitions/gear.json#/definitions/gear-doc"}] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /swagger/schemas/output/permission.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "allOf":[{"$ref":"../definitions/permission.json#/definitions/permission-output-default-required"}] 5 | } 6 | -------------------------------------------------------------------------------- /tests/bin/setup-integration-tests-ubuntu.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eu 3 | unset CDPATH 4 | cd "$( dirname "${BASH_SOURCE[0]}" )/../.." 5 | 6 | sudo pip install -U -r "tests/integration_tests/requirements-integration-test.txt" 7 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .*sw[op] 3 | *.py[co] 4 | *.egg-info 5 | /persistent 6 | /runtime 7 | bootstrap.json 8 | .cache 9 | /.coverage* 10 | coverage.xml 11 | endpoints.json 12 | /htmlcov 13 | node_modules/ 14 | /bin/accesslog.csv 15 | -------------------------------------------------------------------------------- /swagger/schemas/input/analysis-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "Analysis", 4 | "type": "object", 5 | "allOf":[{"$ref":"../definitions/analysis.json#/definitions/analysis-update"}] 6 | } -------------------------------------------------------------------------------- /swagger/schemas/output/file-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type":"array", 4 | "items":{ 5 | "allOf":[{"$ref":"../definitions/file.json#/definitions/file-output"}] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /swagger/schemas/output/logout-output.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "allOf":[{"$ref":"../definitions/auth.json#/definitions/logout-output"}], 4 | "example": { 5 | "tokens_removed": 1 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /swagger/examples/rules_list.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "alg" : "dicom_mr_classifier", 4 | "all" : [ 5 | [ 6 | "file.type", 7 | "dicom" 8 | ] 9 | ] 10 | } 11 | ] 12 | -------------------------------------------------------------------------------- /swagger/schemas/output/user-new.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "allOf": [{"$ref": "../definitions/common.json#/definitions/object-created"}], 4 | "example": { 5 | "_id": "jane.doe@gmail.com" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /swagger/schemas/input/group-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "allOf":[{"$ref":"../definitions/group.json#/definitions/group-input"}], 4 | "example": { 5 | "label":"New group label" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /swagger/schemas/input/project-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "Project", 4 | "type": "object", 5 | "allOf": [{"$ref": "../definitions/project.json#/definitions/project-input"}] 6 | } 7 | -------------------------------------------------------------------------------- /swagger/schemas/mongo/permission.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "allOf":[{"$ref":"../definitions/permission.json#/definitions/permission"}], 4 | "required": ["_id", "access"], 5 | "key_fields": ["_id"] 6 | } 7 | -------------------------------------------------------------------------------- /swagger/schemas/output/search-response-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "array", 4 | "items": {"$ref":"../definitions/search.json#/definitions/search-response"}, 5 | "example": [] 6 | } 7 | -------------------------------------------------------------------------------- /swagger/examples/output/version.json: -------------------------------------------------------------------------------- 1 | { 2 | "_id": "version", 3 | "build": { 4 | "commit": "7d375e977aa023f537c1f1531f646fe65fc5d529", 5 | "branch": "master", 6 | "timestamp": "2017-01-12T15:38:09Z" 7 | }, 8 | "database": 40 9 | } 10 | -------------------------------------------------------------------------------- /swagger/schemas/input/session.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "Session", 4 | "allOf":[{"$ref":"../definitions/session.json#/definitions/session-input"}], 5 | "required": ["label", "project"] 6 | } 7 | -------------------------------------------------------------------------------- /swagger/examples/output/note.json: -------------------------------------------------------------------------------- 1 | { 2 | "_id":"580925ce9e512c57dc8a103b", 3 | "text":"some text", 4 | "created": "2016-10-21T17:19:40.899000+00:00", 5 | "modified": "2016-10-21T17:19:40.899000+00:00", 6 | "user": "coltonlw@flywheel.io" 7 | } 8 | -------------------------------------------------------------------------------- /swagger/examples/create_download_incomplete_and_dicom.json: -------------------------------------------------------------------------------- 1 | { 2 | "optional": true, 3 | "nodes": [{"level":"project", "_id":"57abe1589e512c513d42cb83"}], 4 | "filters":[{ 5 | "tags":{"+":["incomplete"]}, 6 | "types":{"plus":["dicom"]} 7 | }] 8 | } 9 | -------------------------------------------------------------------------------- /swagger/schemas/output/batch-cancel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "allOf": [{"$ref": "../definitions/batch.json#/definitions/batch-cancel-output"}], 5 | "example": { 6 | "number_cancelled": 4 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /swagger/schemas/output/collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "allOf":[{"$ref":"../definitions/collection.json#/definitions/collection-output"}], 4 | "required": ["_id", "label", "created", "modified", "permissions"] 5 | } 6 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | 3 | # --- Below Should mirror .gitignore, without leading '/' --- # 4 | .DS_Store 5 | .*sw[op] 6 | *.py[co] 7 | *.egg-info 8 | persistent 9 | runtime 10 | bootstrap.json 11 | .cache 12 | .coverage* 13 | coverage.xml 14 | htmlcov 15 | node_modules 16 | -------------------------------------------------------------------------------- /swagger/examples/job_stats.json: -------------------------------------------------------------------------------- 1 | { 2 | "by-state": { 3 | "pending":1, 4 | "running":3, 5 | "failed":0, 6 | "complete":17 7 | }, 8 | "by-tag": [ 9 | { 10 | "tags":["tag1"], 11 | "count":4 12 | } 13 | ], 14 | "permafailed": 0 15 | } -------------------------------------------------------------------------------- /swagger/schemas/input/analysis.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "Analysis", 4 | "type": "object", 5 | "allOf": [{"$ref":"../definitions/analysis.json#/definitions/analysis-input-any"}], 6 | "required": ["label"] 7 | } 8 | -------------------------------------------------------------------------------- /swagger/schemas/input/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "Project", 4 | "type": "object", 5 | "allOf": [{"$ref": "../definitions/project.json#/definitions/project-input"}], 6 | "required": ["label", "group"] 7 | } 8 | -------------------------------------------------------------------------------- /swagger/schemas/input/rule-new.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "Rule", 4 | "type": "object", 5 | "allOf": [{"$ref": "../definitions/rule.json#/definitions/rule-input"}], 6 | "required": ["alg", "name", "any", "all"] 7 | } 8 | -------------------------------------------------------------------------------- /swagger/examples/create_download_incomplete_or_not_dicom.json: -------------------------------------------------------------------------------- 1 | { 2 | "optional": true, 3 | "nodes": [{"level":"project", "_id":"57abe1589e512c513d42cb83"}], 4 | "filters":[{ 5 | "tags":{"+":["incomplete"]} 6 | }, 7 | { 8 | "types":{"-":["dicom"]} 9 | }] 10 | } 11 | -------------------------------------------------------------------------------- /swagger/examples/input/packfile.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": { 3 | "_id": "57e452791cff88b85f9f9c97" 4 | }, 5 | "session": { 6 | "label": "control_1" 7 | }, 8 | "acquisition": { 9 | "label": "4784_1_1_localizer" 10 | }, 11 | "packfile": { 12 | "type": "dicom" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /swagger/schemas/input/analysis-legacy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "Analysis", 4 | "type": "object", 5 | "allOf": [{"$ref":"../definitions/analysis.json#/definitions/analysis-input-legacy"}], 6 | "required": ["label"] 7 | } 8 | -------------------------------------------------------------------------------- /swagger/schemas/input/job-logs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "array", 4 | "items": {"$ref": "../definitions/job.json#/definitions/job-log-statement"}, 5 | "example": [ 6 | { "fd": 1, "msg": "Hello World!" } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /swagger/schemas/input/resolver.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "allOf": [{"$ref": "../definitions/resolver.json#/definitions/resolver-input"}], 5 | "example": { 6 | "path": ["scitran", "Neuroscience"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /swagger/examples/user-list.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "_id": "jane.doe@gmail.com", 3 | "firstname": "Jane", 4 | "lastname": "Doe", 5 | "email": "jane.doe@gmail.com", 6 | "root": true, 7 | "created": "2016-09-02T22:58:18.624000+00:00", 8 | "modified": "2016-09-02T22:58:18.624000+00:00" 9 | }] 10 | -------------------------------------------------------------------------------- /swagger/examples/user_jane_doe.json: -------------------------------------------------------------------------------- 1 | { 2 | "_id": "jane.doe@gmail.com", 3 | "firstname": "Jane", 4 | "lastname": "Doe", 5 | "email": "jane.doe@gmail.com", 6 | "root": false, 7 | "created": "2016-09-02T22:58:18.624000+00:00", 8 | "modified": "2016-09-02T22:58:18.624000+00:00" 9 | } 10 | -------------------------------------------------------------------------------- /swagger/schemas/input/note.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "allOf":[{"$ref":"../definitions/note.json#/definitions/note-input"}], 5 | "required": ["text"], 6 | "example": { 7 | "text":"Scitran core!" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /swagger/schemas/output/login-output.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "allOf":[{"$ref":"../definitions/auth.json#/definitions/login-output"}], 4 | "example": { 5 | "token": "MjeuawZcctfRdCOmx_C6oYXK4sLHd2Dhc_oZpkXPPkxHizhNgwFWcrrKGA49BEnK" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /swagger/schemas/output/rule.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "Rule", 4 | "type": "object", 5 | "allOf": [{"$ref": "../definitions/rule.json#/definitions/rule-output"}], 6 | "example": {"$ref": "../../examples/output/rule.json"} 7 | } 8 | -------------------------------------------------------------------------------- /swagger/examples/input/uidupload.json: -------------------------------------------------------------------------------- 1 | { 2 | "group": { 3 | "_id": "test_group" 4 | }, 5 | "project": { 6 | "label": "Neuroscience" 7 | }, 8 | "session": { 9 | "uid": "5898cf5b106450a2d75e2c02" 10 | }, 11 | "acquisition": { 12 | "uid": "5898cf5b106450a2d75e2c03" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sphinx/source/index.rst: -------------------------------------------------------------------------------- 1 | 2 | The SciTran project 3 | =============================== 4 | 5 | 6 | Contents: 7 | 8 | .. toctree:: 9 | :maxdepth: 3 10 | 11 | api 12 | 13 | 14 | Indices and tables 15 | ================== 16 | 17 | * :ref:`genindex` 18 | * :ref:`modindex` 19 | * :ref:`search` 20 | -------------------------------------------------------------------------------- /swagger/schemas/input/search-query.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "allOf":[{"$ref":"../definitions/search.json#/definitions/search-query"}], 5 | "example": { 6 | "return_type": "session", 7 | "search_string": "amyg" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/integration_tests/requirements-integration-test.txt: -------------------------------------------------------------------------------- 1 | attrdict==2.0.0 2 | coverage==4.0.3 3 | mock==2.0.0 4 | mongomock==3.8.0 5 | pdbpp==0.8.3 6 | pylint==1.5.3 7 | pytest-cov==2.2.0 8 | pytest-mock==1.6.0 9 | pytest-watch==3.8.0 10 | pytest==2.8.5 11 | requests_mock==1.3.0 12 | testfixtures==4.10.1 13 | -------------------------------------------------------------------------------- /swagger/schemas/input/group-new.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "allOf":[ 4 | {"$ref":"../definitions/group.json#/definitions/group-input"} 5 | ], 6 | "required": ["_id"], 7 | "example": { 8 | "label": "Example Group", 9 | "_id": "example_group" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /swagger/examples/output/device.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "basic": "info" 4 | }, 5 | "errors": [ 6 | "An Error" 7 | ], 8 | "name": "example_drone", 9 | "interval": 400, 10 | "_id": "test_method_megans_drone", 11 | "method": "test_method", 12 | "last_seen": "2016-11-28T23:14:06.004000+00:00" 13 | } 14 | -------------------------------------------------------------------------------- /swagger/schemas/output/collection-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type":"array", 4 | "items":{ 5 | "allOf":[{"$ref":"../definitions/collection.json#/definitions/collection-output"}], 6 | "required":["_id", "label", "created", "modified", "permissions"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /swagger/schemas/output/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "allOf":[ 5 | {"$ref":"../definitions/user.json#/definitions/user-output"} 6 | ], 7 | "required":[ 8 | "_id", "firstname", "lastname", 9 | "root", "email", "created", "modified" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /swagger/schemas/definitions/created-modified.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "definitions":{ 4 | "created": { 5 | "type": "string", 6 | "format": "date-time" 7 | }, 8 | "modified": { 9 | "type": "string", 10 | "format": "date-time" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /swagger/schemas/input/device.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "allOf":[{"$ref":"../definitions/device.json#/definitions/device-input"}], 4 | "example": { 5 | "info": { 6 | "basic": "info" 7 | }, 8 | "errors": [ 9 | "An Error" 10 | ], 11 | "interval": 400 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /swagger/schemas/output/group.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "allOf":[{"$ref":"../definitions/group.json#/definitions/group-output"}], 5 | "required": [ 6 | "permissions", 7 | "created", 8 | "modified", 9 | "_id" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /swagger/examples/output/collection-list.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "created": "2016-08-30T17:22:05.299000+00:00", 3 | "curator": "user@test.com", 4 | "label": "test", 5 | "modified": "2016-08-30T17:22:05.299000+00:00", 6 | "_id": "57c5c0bd9e512c606dd3df09", 7 | "permissions": [{ 8 | "access": "admin", 9 | "_id": "user@test.com" 10 | }] 11 | }] 12 | -------------------------------------------------------------------------------- /swagger/schemas/input/acquisition-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "Acquisition", 4 | "type": "object", 5 | "allOf": [{"$ref": "../definitions/acquisition.json#/definitions/acquisition-input"}], 6 | "example": { 7 | "label": "example-acquisition-new-label" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /swagger/examples/input/analysis-legacy.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Analysis label", 3 | "description": "This is an analysis description", 4 | "notes": [ 5 | {"text": "some text"} 6 | ], 7 | "inputs": [{"name": "input.csv", "info": {"example": true}}], 8 | "outputs": [{"name": "output.csv", "info": {"example": true}}] 9 | } 10 | -------------------------------------------------------------------------------- /swagger/schemas/output/job-log.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "allOf": [{"$ref": "../definitions/job.json#/definitions/job-log"}], 5 | "example": { 6 | "_id": "57ac7394c700190017123fb8", 7 | "logs": [ 8 | { "fd": 1, "msg": "Hello World!" } 9 | ] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /swagger/schemas/output/acquisition.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type":"object", 4 | "allOf":[{"$ref":"../definitions/acquisition.json#/definitions/acquisition-output"}], 5 | "required":[ 6 | "_id", "public", "label", "session", 7 | "created", "modified", "permissions" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /sphinx/build-docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | # change to parent dir 6 | unset CDPATH 7 | cd "$( dirname "${BASH_SOURCE[0]}" )/.." 8 | 9 | # Generate the rst files from the api package 10 | sphinx-apidoc -o docs/source -f -d 3 api 11 | 12 | # Transform those rst files into html docs 13 | sphinx-build -a -Q -b html docs/source docs/build 14 | -------------------------------------------------------------------------------- /swagger/schemas/input/user-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "User", 4 | "type": "object", 5 | "allOf":[ 6 | {"$ref":"../definitions/user.json#/definitions/user-input"} 7 | ], 8 | "example": { 9 | "firstname": "Jane", 10 | "lastname": "Smith", 11 | "email": "jane.smith@gmail.com" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /swagger/schemas/output/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "Project", 4 | "type": "object", 5 | "allOf": [{"$ref": "../definitions/project.json#/definitions/project-output"}], 6 | "required": [ 7 | "_id", "label", "group", 8 | "created", "modified", "permissions" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /swagger/schemas/output/session.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type":"object", 4 | "allOf":[{"$ref":"../definitions/session.json#/definitions/session-output"}], 5 | "required":[ 6 | "_id", "label", "project", "created", "modified", "permissions", 7 | "public", "group", "subject" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /swagger/examples/output/collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "created": "2016-08-30T17:22:05.299000+00:00", 3 | "curator": "user@test.com", 4 | "label": "test", 5 | "description": "Test collection", 6 | "modified": "2016-08-30T17:22:05.299000+00:00", 7 | "_id": "57c5c0bd9e512c606dd3df09", 8 | "permissions": [{ 9 | "access": "admin", 10 | "_id": "user@test.com" 11 | }] 12 | } 13 | -------------------------------------------------------------------------------- /swagger/paths/config.yaml: -------------------------------------------------------------------------------- 1 | /config: 2 | get: 3 | summary: Return public Scitran configuration information 4 | operationId: get_config 5 | responses: 6 | '200': 7 | description: '' 8 | schema: 9 | $ref: schemas/output/config.json 10 | examples: 11 | response: 12 | $ref: examples/scitran_config.json 13 | -------------------------------------------------------------------------------- /swagger/schemas/input/permission.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "allOf":[{"$ref":"../definitions/permission.json#/definitions/permission"}], 5 | "key_fields": ["_id"], 6 | "required": ["_id", "access"], 7 | "example": { 8 | "_id":"coltonlw@flywheel.io", 9 | "access":"admin" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /swagger/examples/output/rule.json: -------------------------------------------------------------------------------- 1 | { 2 | "_id": "5a12f2923306be0016179f47", 3 | "name": "dcm2niix", 4 | "alg": "dcm2niix", 5 | "any": [], 6 | "all": [ 7 | { 8 | "regex": true, 9 | "type": "file.measurements", 10 | "value": "^(?!non-image).+$" 11 | }, 12 | { 13 | "type": "file.type", 14 | "value": "nifti" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /swagger/paths/clean-packfiles.yaml: -------------------------------------------------------------------------------- 1 | /clean-packfiles: 2 | post: 3 | summary: Clean up expired upload tokens and invalid token directories. 4 | operationId: clean_packfiles 5 | responses: 6 | '200': 7 | description: 'Expired and invalid tokens have been cleaned' 8 | schema: 9 | example: 10 | tokens: 5 11 | directories: 3 -------------------------------------------------------------------------------- /tests/unit_tests/python/test_key.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from api.auth.apikeys import APIKey 3 | 4 | def test_api_key_preprocess(): 5 | assert APIKey._preprocess_key("key") == "key" 6 | assert APIKey._preprocess_key("preamble:key") == "key" 7 | assert APIKey._preprocess_key("preamble:37:key") == "key" 8 | assert APIKey._preprocess_key("preamble::key") == "key" 9 | -------------------------------------------------------------------------------- /swagger/examples/output/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "group": "scitran", 3 | "created": "2016-09-22T21:51:53.151000+00:00", 4 | "modified": "2016-09-22T21:51:53.151000+00:00", 5 | "label": "Neuroscience", 6 | "description": "A Project", 7 | "_id": "57e452791cff88b85f9f9c97", 8 | "public": false, 9 | "permissions": [{ 10 | "access": "admin", 11 | "_id": "coltonlw@flywheel.io" 12 | }] 13 | } 14 | -------------------------------------------------------------------------------- /swagger/paths/version.yaml: -------------------------------------------------------------------------------- 1 | /version: 2 | get: 3 | summary: Get server and database schema version info 4 | operationId: get_version 5 | responses: 6 | '200': 7 | description: '' 8 | schema: 9 | $ref: schemas/output/version.json 10 | examples: 11 | response: 12 | _id: "version" 13 | database: 2 14 | 15 | -------------------------------------------------------------------------------- /tests/integration_tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scitran-core-integration-tests", 3 | "version": "1.0.0", 4 | "description": "SciTran Core integation test node dependencies", 5 | "dependencies": { 6 | "abao":"git+https://github.com/flywheel-io/abao.git#better-jsonschema-ref", 7 | "chai": "~3.5.0" 8 | }, 9 | "bin":{ 10 | "abao":"node_modules/.bin/abao" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /swagger/schemas/input/collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "Collection", 4 | "type": "object", 5 | "allOf": [{"$ref": "../definitions/collection.json#/definitions/collection-input"}], 6 | "required": ["label"], 7 | "example": { 8 | "label":"control-group", 9 | "description":"Control group collection" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /swagger/schemas/input/download.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "../definitions/download.json#/definitions/download-input", 4 | 5 | "example": { 6 | "optional": true, 7 | "nodes": [{"level":"project", "_id":"57abe1589e512c513d42cb83"}], 8 | "filters":[{ 9 | "tags":{"+":["incomplete"]}, 10 | "types":{"+":["dicom"]} 11 | }] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /swagger/schemas/output/session-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type":"array", 4 | "items":{ 5 | "allOf":[{"$ref":"../definitions/session.json#/definitions/session-output"}], 6 | "required":[ 7 | "_id", "label", "project", "created", "modified", "permissions", 8 | "public", "group", "subject" 9 | ] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /swagger/schemas/output/project-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type":"array", 4 | "items":{ 5 | "type": "object", 6 | "allOf": [{"$ref": "../definitions/project.json#/definitions/project-output"}], 7 | "required": [ 8 | "_id", "label", "group", 9 | "created", "modified", "permissions" 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /swagger/schemas/input/user-new.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "allOf":[{"$ref":"../definitions/user.json#/definitions/user-input"}], 5 | "required":["_id", "firstname", "lastname"], 6 | "example": { 7 | "_id": "jane.doe@gmail.com", 8 | "firstname": "Jane", 9 | "lastname": "Doe", 10 | "email": "jane.doe@gmail.com", 11 | "root": false 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /swagger/examples/input/rule-new.json: -------------------------------------------------------------------------------- 1 | { 2 | "_id": "5a12f2923306be0016179f47", 3 | "project_id": "57e452791cff88b85f9f9c97", 4 | "alg": "dcm2niix", 5 | "name": "dcm2niix", 6 | "all": [ 7 | { 8 | "regex": true, 9 | "type": "file.measurements", 10 | "value": "^(?!non-image).+$" 11 | }, 12 | { 13 | "type": "file.type", 14 | "value": "nifti" 15 | } 16 | ], 17 | "any": [] 18 | } 19 | -------------------------------------------------------------------------------- /swagger/schemas/input/acquisition.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "Acquisition", 4 | "type": "object", 5 | "allOf": [{"$ref": "../definitions/acquisition.json#/definitions/acquisition-input"}], 6 | "required": ["label", "session"], 7 | "example": { 8 | "label": "example-acquisition", 9 | "session": "57e45328466d8e000e33a85b", 10 | "public": false 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /swagger/schemas/output/acquisition-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type":"array", 4 | "items":{ 5 | "type":"object", 6 | "allOf":[{"$ref":"../definitions/acquisition.json#/definitions/acquisition-output"}], 7 | "required":[ 8 | "_id", "public", "label", "session", 9 | "created", "modified", "permissions" 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | backports.tempfile==1.0 2 | django>=1.11.0,<1.12.0 3 | elasticsearch==5.3.0 4 | enum==0.4.6 5 | jsonschema==2.6.0 6 | Markdown==2.6.5 7 | pymongo==3.2 8 | pyOpenSSL >=17.1.0,<18.0 9 | python-dateutil==2.4.2 10 | pytz==2015.7 11 | requests==2.9.1 12 | rfc3987==1.3.4 13 | strict-rfc3339==0.7 14 | unicodecsv==0.9.0 15 | uwsgi==2.0.13.1 16 | webapp2==2.5.2 17 | WebOb==1.5.1 18 | git+https://github.com/flywheel-io/gears.git@v0.1.4#egg=gears 19 | -------------------------------------------------------------------------------- /swagger/schemas/output/user-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "array", 4 | "items": { 5 | "allOf":[ 6 | {"$ref":"../definitions/user.json#/definitions/user-output"} 7 | ], 8 | "required":[ 9 | "_id", "firstname", "lastname", 10 | "root", "email", "created", "modified" 11 | ] 12 | }, 13 | "example": { 14 | "$ref": "../../examples/user-list.json" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /swagger/schemas/input/collection-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "Collection", 4 | "type": "object", 5 | "allOf": [{"$ref": "../definitions/collection.json#/definitions/collection-input-with-contents"}], 6 | "example": { 7 | "contents": { 8 | "operation": "add", 9 | "nodes": [{ 10 | "level": "session", 11 | "_id": "57dc50b0931cefd8ac2a371e" 12 | }] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /swagger/schemas/input/propose-batch.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "allOf": [{"$ref":"../definitions/batch.json#/definitions/batch-proposal-input"}], 5 | "example": { 6 | "gear_id": "59b1b5b0e105c40019f50015", 7 | "config": {}, 8 | "tags": ["test-tag"], 9 | "targets": [{ 10 | "type": "session", 11 | "id": "deb1b5b0e105c40019f500af" 12 | }] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /swagger/schemas/mongo/note.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "properties": { 5 | "_id": {"type": "string"}, 6 | "created": {}, 7 | "modified": {}, 8 | "user": {"type": "string"}, 9 | "text": {"type": "string"} 10 | }, 11 | "required": ["_id", "user", "created", "modified", "text"], 12 | "additionalProperties": false 13 | } 14 | -------------------------------------------------------------------------------- /swagger/schemas/output/collection-curators-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type":"array", 4 | "items":{ 5 | "properties":{ 6 | "firstname":{"$ref":"../definitions/user.json#/definitions/firstname"}, 7 | "lastname":{"$ref":"../definitions/user.json#/definitions/lastname"}, 8 | "_id":{"$ref":"../definitions/common.json#/definitions/user-id"} 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /swagger/schemas/definitions/version.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "definitions": { 4 | "version-output": { 5 | "type": "object", 6 | "properties": { 7 | "_id": { 8 | "type": "string" 9 | }, 10 | "database": { 11 | "type": "integer" 12 | } 13 | }, 14 | "required": [ 15 | "_id", 16 | "database" 17 | ] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /swagger/schemas/definitions/auth.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "definitions": { 4 | "login-output": { 5 | "type": "object", 6 | "properties": { 7 | "token": {"type": "string"} 8 | }, 9 | "required": ["token"] 10 | }, 11 | "logout-output": { 12 | "type": "object", 13 | "properties": { 14 | "tokens_removed": {"type": "integer"} 15 | }, 16 | "required": ["tokens_removed"] 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /swagger/examples/output/session.json: -------------------------------------------------------------------------------- 1 | { 2 | "group": "scitran", 3 | "created": "2016-09-19T17:13:48.164000+00:00", 4 | "subject": { 5 | "code": "ex4784", 6 | "_id": "57e01cccb1dc04000fb83f02" 7 | }, 8 | "modified": "2016-09-19T17:13:48.164000+00:00", 9 | "label": "control_1", 10 | "project": "57e01cccf6b5d5edbcb4e1cf", 11 | "_id": "57e01cccb1dc04000fb83f03", 12 | "public": false, 13 | "permissions": [{ 14 | "access": "admin", 15 | "_id": "coltonlw@flywheel.io" 16 | }] 17 | } 18 | -------------------------------------------------------------------------------- /swagger/examples/output/session-template-recalc.json: -------------------------------------------------------------------------------- 1 | { 2 | "sessions_changed":[ 3 | "5818cc639e512c35c9e4d3a3", 4 | "5818cc639e512c35c9e4d3a4", 5 | "5818cc639e512c35c9e4d3a5", 6 | "5818cc639e512c35c9e4d3a6", 7 | "5818cc639e512c35c9e4d3a7", 8 | "5818cc639e512c35c9e4d3a8", 9 | "5818cc639e512c35c9e4d3a9", 10 | "5818cc639e512c35c9e4d3aa", 11 | "5818cc639e512c35c9e4d3ab", 12 | "5818cc639e512c35c9e4d3ac" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /api/types.py: -------------------------------------------------------------------------------- 1 | from . import util 2 | 3 | # Origin represents the different methods a request can be authenticated. 4 | Origin = util.Enum('Origin', { 5 | 'user': 'user', # An authenticated user 6 | 'device': 'device', # A connected device (reaper, script, etc) 7 | 'job': 'job', # Made on behalf of a job (downloading data, uploading results, etc) 8 | 'system': 'system', # Created by the system (a job initiated by a rule, etc) 9 | 'unknown': 'unknown' # Other or public 10 | }) 11 | -------------------------------------------------------------------------------- /swagger/schemas/definitions/avatars.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "definitions": { 4 | "avatars": { 5 | "title": "Avatar", 6 | "type": "object", 7 | "properties": { 8 | "gravatar": {"type": ["string", "null"], "format": "uri" }, 9 | "provider": {"type": ["string", "null"], "format": "uri" }, 10 | "custom": {"type": ["string", "null"], "pattern": "^https:", "format": "uri" } 11 | }, 12 | "additionalProperties": false 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /swagger/examples/output/group.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Test Group", 3 | "permissions": [ 4 | { 5 | "access": "admin", 6 | "_id": "group_admin@fakeuser.com" 7 | }, 8 | { 9 | "access": "rw", 10 | "_id": "group_member_read-write@fakeuser.com" 11 | }, 12 | { 13 | "access": "ro", 14 | "_id": "group_member_read-only@fakeuser.com" 15 | } 16 | ], 17 | "created": "2016-08-19T11:41:15.360000+00:00", 18 | "modified": "2016-08-19T11:41:15.360000+00:00", 19 | "_id": "test_group" 20 | } 21 | -------------------------------------------------------------------------------- /swagger/paths/login.yaml: -------------------------------------------------------------------------------- 1 | /login: 2 | post: 3 | summary: Login 4 | description: Scitran Authentication 5 | operationId: login 6 | responses: 7 | '200': 8 | description: '' 9 | schema: 10 | $ref: schemas/output/login-output.json 11 | /logout: 12 | post: 13 | summary: Log Out 14 | description: Remove authtokens for user 15 | operationId: logout 16 | responses: 17 | '200': 18 | description: '' 19 | schema: 20 | $ref: schemas/output/logout-output.json 21 | -------------------------------------------------------------------------------- /swagger/examples/input/analysis.json: -------------------------------------------------------------------------------- 1 | { 2 | "notes": [{ 3 | "text": "some text" 4 | }], 5 | "description": "This is an analysis description", 6 | "job": { 7 | "gear_id": "aex", 8 | "inputs": { 9 | "dicom": { 10 | "type": "acquisition", 11 | "id": "573c9e6a844eac7fc01747cd", 12 | "name" : "1_1_dicom.zip" 13 | } 14 | }, 15 | "config": { 16 | "two-digit multiple of ten": 20 17 | } 18 | }, 19 | "label": "Analysis label" 20 | } 21 | -------------------------------------------------------------------------------- /swagger/examples/output/report-site.json: -------------------------------------------------------------------------------- 1 | { 2 | "group_count": 4, 3 | "groups": [ 4 | { 5 | "project_count": 0, 6 | "label": "Unknown", 7 | "session_count": 0 8 | }, 9 | { 10 | "project_count": 0, 11 | "label": "Scientific Transparency", 12 | "session_count": 0 13 | }, 14 | { 15 | "project_count": 0, 16 | "label": "Test Group", 17 | "session_count": 0 18 | }, 19 | { 20 | "project_count": 1, 21 | "label": null, 22 | "session_count": 1 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /api/handlers/schemahandler.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | 4 | from ..web import base 5 | from .. import config 6 | 7 | 8 | class SchemaHandler(base.RequestHandler): 9 | 10 | def __init__(self, request=None, response=None): 11 | super(SchemaHandler, self).__init__(request, response) 12 | 13 | def get(self, schema): 14 | schema_path = os.path.join(config.schema_path, schema) 15 | try: 16 | with open(schema_path, 'rU') as f: 17 | return json.load(f) 18 | except IOError as e: 19 | self.abort(404, str(e)) 20 | -------------------------------------------------------------------------------- /bootstrap.sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "groups": [ 3 | { 4 | "_id": "unknown", 5 | "name": "Unknown", 6 | "roles": [ 7 | { 8 | "access": "admin", 9 | "_id": "user1@example.com" 10 | } 11 | ] 12 | } 13 | ], 14 | "users": [ 15 | { 16 | "_id": "user1@example.com", 17 | "email": "user1@example.com", 18 | "firstname": "First", 19 | "lastname": "User", 20 | "root": true 21 | } 22 | ], 23 | "drones": [ 24 | { 25 | "_id": "local", 26 | "type": "engine" 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /swagger/schemas/mongo/collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "Collection", 4 | "type": "object", 5 | "allOf": [{"$ref": "container.json"}], 6 | "properties": { 7 | "_id": {}, 8 | "created": {}, 9 | "modified": {}, 10 | "permissions": {}, 11 | "files": {}, 12 | "public": {}, 13 | "label": {}, 14 | "tags": {}, 15 | "info": {}, 16 | "description": {}, 17 | 18 | "curator": {"type": "string"} 19 | }, 20 | "additionalProperties": false 21 | } 22 | -------------------------------------------------------------------------------- /bin/install-ubuntu.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | unset CDPATH 6 | cd "$( dirname "${BASH_SOURCE[0]}" )/.." 7 | 8 | # Add the apt repo for modern node-js, this will run apt-get update 9 | curl -sL https://deb.nodesource.com/setup_8.x | sudo bash - 10 | 11 | sudo apt-get install -y \ 12 | build-essential \ 13 | ca-certificates \ 14 | libatlas3-base \ 15 | numactl \ 16 | python-dev \ 17 | libffi-dev \ 18 | libssl-dev \ 19 | libpcre3 \ 20 | libpcre3-dev \ 21 | git \ 22 | nodejs 23 | 24 | sudo pip install -U pip 25 | 26 | sudo pip install -r requirements.txt 27 | -------------------------------------------------------------------------------- /swagger/examples/output/groups-list.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "label": "Test Group", 4 | "permissions": [ 5 | { 6 | "access": "admin", 7 | "_id": "group_admin@fakeuser.com" 8 | }, 9 | { 10 | "access": "rw", 11 | "_id": "group_member_read-write@fakeuser.com" 12 | }, 13 | { 14 | "access": "ro", 15 | "_id": "group_member_read-only@fakeuser.com" 16 | } 17 | ], 18 | "created": "2016-08-19T11:41:15.360000+00:00", 19 | "modified": "2016-08-19T11:41:15.360000+00:00", 20 | "_id": "test_group" 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /swagger/schemas/definitions/tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "definitions": { 4 | "value": { 5 | "type": "string", 6 | "minLength": 1, 7 | "maxLength": 32, 8 | "x-sdk-positional": true 9 | }, 10 | "tag":{ 11 | "type": "object", 12 | "properties":{ 13 | "value":{"$ref":"#/definitions/value"} 14 | }, 15 | "additionalProperties": false, 16 | "required": ["value"] 17 | }, 18 | "tag-list":{ 19 | "type":"array", 20 | "items":{ 21 | "allOf":[{"type":"string"}] 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /swagger/schemas/input/job-new.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "allOf": [{"$ref": "../definitions/job.json#/definitions/job-input"}], 5 | "example": { 6 | "gear_id": "aex", 7 | "inputs": { 8 | "dicom": { 9 | "type": "acquisition", 10 | "id": "573c9e6a844eac7fc01747cd", 11 | "name" : "1_1_dicom.zip" 12 | } 13 | }, 14 | "config": { 15 | "two-digit multiple of ten": 20 16 | }, 17 | "destination": { 18 | "type": "acquisition", 19 | "id": "573c9e6a844eac7fc01747cd" 20 | }, 21 | "tags": [ 22 | "ad-hoc" 23 | ] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /swagger/schemas/output/batch-proposal.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "allOf": [{"$ref":"../definitions/batch.json#/definitions/batch-proposal"}], 5 | "example": { 6 | "_id": "5a33fa6352e95c001707489b", 7 | "gear_id": "59b1b5b0e105c40019f50015", 8 | "config": {}, 9 | "state": "pending", 10 | "origin": { 11 | "type": "user", 12 | "id": "justinehlert@flywheel.io" 13 | }, 14 | "proposal": {}, 15 | "created": "2017-12-15T16:37:55.538000+00:00", 16 | "modified": "2017-12-15T16:38:01.107000+00:00" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docker/bootstrap-accounts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | set -x 4 | 5 | echo "IN BOOTSTRAP ACCOUNTS" 6 | 7 | ( 8 | 9 | # Parse input parameters... 10 | # 11 | # bootstrap account file 12 | bootstrap_user_file=${1:-'/var/scitran/code/api/bootstrap.json.sample'} 13 | 14 | 15 | # Move to API folder for relative path assumptions later on 16 | # 17 | cd /var/scitran/code/api 18 | 19 | # Export PYTHONPATH for python script later on. 20 | # 21 | export PYTHONPATH=. 22 | 23 | 24 | # Bootstrap Users 25 | ./bin/load_users_drone_secret.py --insecure --secret "${SCITRAN_CORE_DRONE_SECRET}" "${SCITRAN_SITE_API_URL}" "${bootstrap_user_file}" 26 | 27 | 28 | ) 29 | -------------------------------------------------------------------------------- /swagger/examples/output/job.json: -------------------------------------------------------------------------------- 1 | { 2 | "inputs": { 3 | "dicom":{ 4 | "type": "acquisition", 5 | "id": "57ac7394c700190017123fb7", 6 | "name": "8403_4_1_t1_dicom.zip" 7 | } 8 | }, 9 | "attempt": 1, 10 | "gear_id": "dcm_convert", 11 | "tags": [ 12 | "ad-hoc", 13 | "dcm_convert" 14 | ], 15 | "destination": { 16 | "type": "acquisition", 17 | "id": "573c9e6a844eac7fc01747cd" 18 | }, 19 | "modified": "2016-08-11T13:02:09.055000+00:00", 20 | "created": "2016-08-11T13:02:09.055000+00:00", 21 | "state": "pending", 22 | "id": "57ac77515e325c0018cd17cf", 23 | "config":{ 24 | "speed":3 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /swagger/paths/dataexplorer.yaml: -------------------------------------------------------------------------------- 1 | /dataexplorer/search: 2 | post: 3 | summary: Perform a search query 4 | operationId: search 5 | parameters: 6 | - name: simple 7 | in: query 8 | type: boolean 9 | x-sdk-default: 'true' 10 | - name: limit 11 | in: query 12 | type: integer 13 | x-sdk-default: 100 14 | - name: body 15 | in: body 16 | required: true 17 | schema: 18 | $ref: schemas/input/search-query.json 19 | responses: 20 | '200': 21 | description: A list of results of the search query 22 | schema: 23 | $ref: schemas/output/search-response-list.json 24 | -------------------------------------------------------------------------------- /swagger/schemas/mongo/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "Project", 4 | "type": "object", 5 | "allOf": [{"$ref": "container.json"}], 6 | "properties": { 7 | "_id": {}, 8 | "created": {}, 9 | "modified": {}, 10 | "permissions": {}, 11 | "files": {}, 12 | "public": {}, 13 | "label": {}, 14 | "template": {"$ref":"../definitions/project-template.json#/definitions/project-template"}, 15 | "tags": {}, 16 | "info": {}, 17 | "description": {}, 18 | 19 | "group": {"type": "string"} 20 | }, 21 | "additionalProperties": false 22 | } 23 | -------------------------------------------------------------------------------- /swagger/examples/scitran_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "site": { 3 | "central_url": "https://sdmc.scitran.io/api", 4 | "ssl_cert": null, 5 | "api_url": "https://10.240.0.2:443/api", 6 | "registered": false, 7 | "id": "local", 8 | "name": "BaliDemo" 9 | }, 10 | "modified": "2016-03-31T16:30:00.852000+00:00", 11 | "auth": { 12 | "id_endpoint": "https://www.googleapis.com/plus/v1/people/me/openIdConnect", 13 | "verify_endpoint": "https://www.googleapis.com/oauth2/v1/tokeninfo", 14 | "client_id": "949263322061-6q4fqi0m4ihkp1v5n6v8q9bef4gd0f1k.apps.googleusercontent.com", 15 | "auth_endpoint": "https://accounts.google.com/o/oauth2/auth" 16 | }, 17 | "created": "2016-03-31T16:30:00.852000+00:00" 18 | } -------------------------------------------------------------------------------- /swagger/examples/output/device-status.json: -------------------------------------------------------------------------------- 1 | { 2 | "bootstrapper_Bootstrapper": { 3 | "status": "ok", 4 | "last_seen": "2016-11-28T18:54:11.880000+00:00" 5 | }, 6 | "test_method_example_drone": { 7 | "status": "error", 8 | "errors": [ 9 | "An Error" 10 | ], 11 | "last_seen": "2016-11-28T23:14:06.004000+00:00" 12 | }, 13 | "gear-register_flywheel-utility": { 14 | "status": "ok", 15 | "last_seen": "2016-11-29T18:51:39.836000+00:00" 16 | }, 17 | "cron_Cron": { 18 | "status": "ok", 19 | "last_seen": "2016-11-28T23:17:01.261000+00:00" 20 | }, 21 | "importer_admin_import": { 22 | "status": "unknown", 23 | "last_seen": "2016-11-28T18:57:27.104000+00:00" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /swagger/schemas/mongo/acquisition.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "Acquisition", 4 | "type": "object", 5 | "allOf": [{"$ref": "container.json"}], 6 | "properties": { 7 | "created": {}, 8 | "modified": {}, 9 | "permissions": {}, 10 | "files": {}, 11 | "public": {}, 12 | "label": {}, 13 | "tags": {}, 14 | "info": {}, 15 | 16 | "session": {}, 17 | "collections": {"type": "array", "items": {"type": "string" }}, 18 | "uid": {"type": "string"}, 19 | "timestamp": {}, 20 | "timezone": {"type": "string"} 21 | }, 22 | "additionalProperties": false 23 | } 24 | -------------------------------------------------------------------------------- /swagger/schemas/output/file-info.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type":"object", 4 | "allOf":[{"$ref":"../definitions/file.json#/definitions/file-output"}], 5 | "example": { 6 | "origin": { 7 | "type": "job", 8 | "id": "58063f24e5dc5b001657a87f" 9 | }, 10 | "mimetype": "application/octet-stream", 11 | "hash": "v0-sha384-12188e00a26650b2baa3f0195337dcf504f4362bb2136eef0cdbefb57159356b1355a0402fca0ab5ab081f21c305e5c2", 12 | "name": "cortical_surface_right_hemisphere.obj", 13 | "tags": [], 14 | "measurements": [], 15 | "modified": "2016-10-18T15:26:35.701000+00:00", 16 | "modality": null, 17 | "size": 21804112, 18 | "type": "None", 19 | "info": {} 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docker/inject_build_info.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ( 3 | 4 | set -e 5 | 6 | # Set cwd 7 | unset CDPATH 8 | cd "$( dirname "${BASH_SOURCE[0]}" )" 9 | 10 | # Dump the build info into version.json so it can be displayed in the footer 11 | # of the site pages. 12 | 13 | # { 14 | # "commit": "5683785e8cd6efdfd794a79828b2cccd2424ed21", 15 | # "timestamp": "January 12, 2016 at 2:46:23 PM CST", 16 | # "branch": "ng-constant" 17 | # } 18 | 19 | 20 | BRANCH_NAME=${1} 21 | COMMIT_HASH=${2} 22 | BUILD_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") 23 | 24 | echo "{ 25 | \"commit\": \"${COMMIT_HASH}\", 26 | \"timestamp\": \"${BUILD_TIMESTAMP}\", 27 | \"branch\": \"${BRANCH_NAME}\" 28 | }" > version.json 29 | 30 | cat version.json 31 | 32 | ) 33 | -------------------------------------------------------------------------------- /swagger/schemas/mongo/container.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "properties": { 5 | "_id": {"type": "string"}, 6 | "created": {}, 7 | "modified": {}, 8 | "notes": {"type": "array", "items": {"$ref": "note.json"}}, 9 | "permissions": {"type": "array", "items": {"$ref": "../definitions/permission.json#/definitions/permission"}}, 10 | "files": {"type": "array", "items": {"$ref": "file.json"}}, 11 | "public": {"type": "boolean"}, 12 | "label": {"type": "string"}, 13 | "tags": {"type": "array", "items": {"type": "string"}}, 14 | "info": {"type": "object"} 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /swagger/schemas/output/batch.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "allOf": [{"$ref":"../definitions/batch.json#/definitions/batch"}], 5 | "example": { 6 | "origin": { 7 | "type": "user", 8 | "id": "justinehlert@flywheel.io" 9 | }, 10 | "jobs": [ 11 | "5a33fa6652e95c001707489c", 12 | "5a33fa6652e95c001707489d", 13 | "5a33fa6652e95c001707489e", 14 | "5a33fa6652e95c001707489f" 15 | ], 16 | "created": "2017-12-15T16:37:55.538000+00:00", 17 | "modified": "2017-12-15T16:38:01.107000+00:00", 18 | "state": "complete", 19 | "gear_id": "59b1b5b0e105c40019f50015", 20 | "_id": "5a33fa6352e95c001707489b", 21 | "config": {} 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | precision: 2 3 | round: down 4 | range: "70...100" # range for red-green gradient in visualizations 5 | 6 | status: 7 | project: no 8 | patch: 9 | default: 10 | enabled: yes 11 | # basic 12 | target: 100% # we always want 100% coverage for new code 13 | threshold: null 14 | base: pr 15 | changes: 16 | default: 17 | enabled: yes 18 | # basic 19 | base: pr 20 | comment: 21 | layout: "diff" 22 | behavior: default 23 | require_changes: false # if true: only post the comment if coverage changes 24 | require_base: no # [yes :: must have a base report to post] 25 | require_head: yes # [yes :: must have a head report to post] 26 | branches: null 27 | -------------------------------------------------------------------------------- /swagger/schemas/output/batch-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "array", 4 | "items": {"$ref":"../definitions/batch.json#/definitions/batch"}, 5 | "example": [{ 6 | "origin": { 7 | "type": "user", 8 | "id": "justinehlert@flywheel.io" 9 | }, 10 | "jobs": [ 11 | "5a33fa6652e95c001707489c", 12 | "5a33fa6652e95c001707489d", 13 | "5a33fa6652e95c001707489e", 14 | "5a33fa6652e95c001707489f" 15 | ], 16 | "created": "2017-12-15T16:37:55.538000+00:00", 17 | "modified": "2017-12-15T16:38:01.107000+00:00", 18 | "state": "complete", 19 | "gear_id": "59b1b5b0e105c40019f50015", 20 | "_id": "5a33fa6352e95c001707489b", 21 | "config": {} 22 | }] 23 | } 24 | -------------------------------------------------------------------------------- /swagger/templates/packfile-start.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: resource 3 | type: string 4 | required: true 5 | - name: parameter 6 | type: string 7 | required: true 8 | - name: tag 9 | type: string 10 | required: true 11 | template: | 12 | parameters: 13 | - required: true 14 | type: string 15 | in: path 16 | name: '{{parameter}}' 17 | post: 18 | summary: Start a packfile upload to {{resource}} 19 | operationId: start_{{resource}}_packfile_upload 20 | tags: 21 | - '{{tag}}' 22 | responses: 23 | '200': 24 | description: '' 25 | schema: 26 | $ref: schemas/output/packfile-start.json 27 | examples: 28 | response: 29 | $ref: examples/output/packfile-start.json 30 | -------------------------------------------------------------------------------- /swagger/paths/upload-by-uid.yaml: -------------------------------------------------------------------------------- 1 | /upload/uid: 2 | post: 3 | summary: Multipart form upload with N file fields, each with their desired filename. 4 | description: > 5 | Same behavior as /api/upload/label, 6 | except the metadata field must be uid format 7 | See api/schemas/input/uidupload.json for the format of this metadata. 8 | operationId: upload_by_uid 9 | tags: 10 | - files 11 | responses: 12 | '200': 13 | description: '' 14 | schema: 15 | example: 16 | $ref: examples/file_info_list.json 17 | '402': 18 | description: Uploads must be from an authorized drone 19 | consumes: 20 | - multipart/form-data 21 | parameters: 22 | - in: formData 23 | name: formData 24 | type: string -------------------------------------------------------------------------------- /swagger/templates/permissions.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: resource 3 | type: string 4 | required: true 5 | - name: parameter 6 | type: string 7 | required: true 8 | - name: tag 9 | type: string 10 | required: true 11 | template: | 12 | parameters: 13 | - required: true 14 | type: string 15 | in: path 16 | name: '{{parameter}}' 17 | post: 18 | summary: Add a permission 19 | operationId: add_{{resource}}_permission 20 | tags: 21 | - '{{tag}}' 22 | parameters: 23 | - in: body 24 | name: body 25 | schema: 26 | $ref: schemas/input/permission.json 27 | responses: 28 | '200': 29 | $ref: "#/responses/200:modified-with-count" 30 | '400': 31 | $ref: '#/responses/400:invalid-body-json' 32 | -------------------------------------------------------------------------------- /swagger/examples/scitran_config.js.txt: -------------------------------------------------------------------------------- 1 | config = { 2 | "auth": { 3 | "auth_endpoint": "https://accounts.google.com/o/oauth2/auth", 4 | "client_id": "949263322061-6q4fqi0m4ihkp1v5n6v8q9bef4gd0f1k.apps.googleusercontent.com", 5 | "id_endpoint": "https://www.googleapis.com/plus/v1/people/me/openIdConnect", 6 | "verify_endpoint": "https://www.googleapis.com/oauth2/v1/tokeninfo" 7 | }, 8 | "created": "2016-03-31T16:30:00.852000+00:00", 9 | "modified": "2016-03-31T16:30:00.852000+00:00", 10 | "site": { 11 | "api_url": "https://10.240.0.2:443/api", 12 | "central_url": "https://sdmc.scitran.io/api", 13 | "id": "local", 14 | "name": "BaliDemo", 15 | "registered": false, 16 | "ssl_cert": null 17 | } -------------------------------------------------------------------------------- /swagger/templates/file-list-upload.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: resource 3 | type: string 4 | required: true 5 | - name: parameter 6 | type: string 7 | required: true 8 | - name: tag 9 | type: string 10 | required: true 11 | template: | 12 | parameters: 13 | - name: '{{parameter}}' 14 | in: path 15 | type: string 16 | required: true 17 | 18 | post: 19 | summary: Upload a file to {{resource}}. 20 | operationId: upload_file_to_{{resource}} 21 | tags: 22 | - '{{tag}}' 23 | consumes: 24 | - multipart/form-data 25 | parameters: 26 | - name: file 27 | in: formData 28 | type: file 29 | required: true 30 | description: The file to upload 31 | responses: 32 | '200': 33 | description: '' 34 | -------------------------------------------------------------------------------- /swagger/templates/analysis-notes-item.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: resource 3 | type: string 4 | required: true 5 | - name: parameter 6 | type: string 7 | required: true 8 | - name: tag 9 | type: string 10 | required: true 11 | template: | 12 | parameters: 13 | - required: true 14 | type: string 15 | in: path 16 | name: '{{parameter}}' 17 | - required: true 18 | type: string 19 | in: path 20 | name: AnalysisId 21 | - required: true 22 | type: string 23 | in: path 24 | name: NoteId 25 | delete: 26 | summary: Remove a note from {{resource}} analysis. 27 | operationId: delete_{{resource}}_analysis_note 28 | tags: 29 | - '{{tag}}' 30 | responses: 31 | '200': 32 | $ref: '#/responses/200:modified-with-count' 33 | -------------------------------------------------------------------------------- /swagger/templates/notes.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: resource 3 | type: string 4 | required: true 5 | - name: parameter 6 | type: string 7 | required: true 8 | - name: tag 9 | type: string 10 | required: true 11 | template: | 12 | parameters: 13 | - required: true 14 | type: string 15 | in: path 16 | name: '{{parameter}}' 17 | post: 18 | summary: Add a note to {{resource}}. 19 | operationId: add_{{resource}}_note 20 | tags: 21 | - '{{tag}}' 22 | parameters: 23 | - name: body 24 | in: body 25 | required: true 26 | schema: 27 | $ref: schemas/input/note.json 28 | responses: 29 | '200': 30 | $ref: '#/responses/200:modified-with-count' 31 | '400': 32 | $ref: '#/responses/400:invalid-body-json' 33 | -------------------------------------------------------------------------------- /swagger/templates/container-item-info.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: resource 3 | type: string 4 | required: true 5 | - name: parameter 6 | type: string 7 | required: true 8 | - name: tag 9 | type: string 10 | required: true 11 | template: | 12 | parameters: 13 | - required: true 14 | type: string 15 | in: path 16 | name: '{{parameter}}' 17 | post: 18 | summary: Update or replace info for a {{resource}}. 19 | operationId: modify_{{resource}}_info 20 | x-sdk-modify-info: true 21 | tags: 22 | - '{{tag}}' 23 | parameters: 24 | - name: body 25 | in: body 26 | required: true 27 | schema: 28 | $ref: schemas/input/info_update.json 29 | responses: 30 | '200': 31 | description: 'The info was updated successfully' 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/unit_tests/python/test_request.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import mock 4 | from testfixtures import LogCapture 5 | 6 | import api.web.request 7 | 8 | class TestRequest(unittest.TestCase): 9 | def setUp(self): 10 | self.log_capture = LogCapture() 11 | self.request = api.web.request.SciTranRequest({}) 12 | 13 | def tearDown(self): 14 | LogCapture.uninstall_all() 15 | 16 | def test_request_id(self): 17 | self.assertEqual(len(self.request.id), 19) 18 | 19 | def test_request_logger_adapter(self): 20 | test_log_message = "test log message" 21 | self.request.logger.error(test_log_message) 22 | expected_log_output = "{0} request_id={1}".format( 23 | test_log_message, self.request.id 24 | ) 25 | self.log_capture.check(('scitran.api', 'ERROR', expected_log_output)) 26 | -------------------------------------------------------------------------------- /swagger/schemas/mongo/group.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "Group", 4 | "type": "object", 5 | "properties": { 6 | "_id": { 7 | "title": "ID", 8 | "type": "string" 9 | }, 10 | "created": {}, 11 | "modified": {}, 12 | "label": {}, 13 | "permissions": { 14 | "type": "array", 15 | "items": {"$ref": "../definitions/permission.json#/definitions/permission"}, 16 | "title": "Permissions", 17 | "default": [], 18 | "uniqueItems": true 19 | } 20 | }, 21 | "additionalProperties": false, 22 | "required": ["_id", "created", "modified"] 23 | } 24 | -------------------------------------------------------------------------------- /swagger/templates/tags.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: resource 3 | type: string 4 | required: true 5 | - name: parameter 6 | type: string 7 | required: true 8 | - name: tag 9 | type: string 10 | required: true 11 | template: | 12 | parameters: 13 | - required: true 14 | type: string 15 | in: path 16 | name: '{{parameter}}' 17 | post: 18 | summary: Add a tag to {{resource}}. 19 | description: Progates changes to projects, sessions and acquisitions 20 | operationId: add_{{resource}}_tag 21 | tags: 22 | - '{{tag}}' 23 | parameters: 24 | - name: body 25 | in: body 26 | required: true 27 | schema: 28 | $ref: schemas/input/tag.json 29 | responses: 30 | '200': 31 | $ref: '#/responses/200:modified-with-count' 32 | '400': 33 | $ref: '#/responses/400:invalid-body-json' 34 | -------------------------------------------------------------------------------- /swagger/schemas/definitions/permission.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "definitions": { 4 | "access": { 5 | "type": "string", 6 | "enum": ["ro", "rw", "admin"] 7 | }, 8 | "permission":{ 9 | "type":"object", 10 | "properties":{ 11 | "_id":{"$ref":"common.json#/definitions/user-id"}, 12 | "access":{"$ref":"#/definitions/access"} 13 | }, 14 | "additionalProperties": false, 15 | "x-sdk-model": "permission" 16 | }, 17 | "permission-output-default-required":{ 18 | "allOf":[{"$ref":"#/definitions/permission"}], 19 | "required":["_id", "access"], 20 | "x-sdk-model": "permission" 21 | }, 22 | "permission-output-list": { 23 | "type": "array", 24 | "items": {"$ref": "#/definitions/permission-output-default-required"}, 25 | "uniqueItems": true 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /swagger/schemas/mongo/session.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "Session", 4 | "type": "object", 5 | "allOf": [{"$ref": "container.json"}], 6 | "properties": { 7 | "_id": {}, 8 | "created": {}, 9 | "modified": {}, 10 | "permissions": {}, 11 | "files": {}, 12 | "public": {}, 13 | "label": {}, 14 | "satisfies_template": {"type": "boolean"}, 15 | "project_has_template": {"type": "boolean"}, 16 | "tags": {}, 17 | "info": {}, 18 | "operator": {}, 19 | "group": {"type": "string"}, 20 | "project": {}, 21 | "uid": {"type": "string"}, 22 | "timestamp": {}, 23 | "timezone": {"type": "string"}, 24 | "subject": {"$ref": "subject.json"} 25 | }, 26 | "additionalProperties": false 27 | } 28 | -------------------------------------------------------------------------------- /swagger/support/tasks/lint-schemas.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | var SchemaLinter = require('../schema-lint'); 5 | 6 | /** 7 | * This task flattens the nested swagger yaml into a single flat file. 8 | * It does not resolve the JSON schema links. 9 | * @param {object} data Task data (See schema-lint.js for options) 10 | */ 11 | grunt.registerMultiTask('lintSchemas', 'Lint JSON schemas', function() { 12 | var i, errors; 13 | var linter = new SchemaLinter(this.data); 14 | 15 | try { 16 | errors = linter.lint(); 17 | } catch( e ) { 18 | grunt.log.writeln('Error running lint:', e); 19 | return false; 20 | } 21 | 22 | for( i = 0; i < errors.length; i++ ) { 23 | grunt.log.error(errors[i].toString()); 24 | } 25 | 26 | if( errors.length && this.data.failOnError !== false ) { 27 | return false; 28 | } 29 | return true; 30 | }); 31 | }; 32 | 33 | 34 | -------------------------------------------------------------------------------- /swagger/templates/packfile-end.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: resource 3 | type: string 4 | required: true 5 | - name: parameter 6 | type: string 7 | required: true 8 | - name: tag 9 | type: string 10 | required: true 11 | template: | 12 | parameters: 13 | - required: true 14 | type: string 15 | in: path 16 | name: '{{parameter}}' 17 | get: 18 | summary: End a packfile upload 19 | operationId: end_{{resource}}_packfile_upload 20 | tags: 21 | - '{{tag}}' 22 | produces: 23 | - text/event-stream 24 | parameters: 25 | - name: token 26 | in: query 27 | type: string 28 | required: true 29 | - name: metadata 30 | in: query 31 | type: string 32 | required: true 33 | description: string-encoded metadata json object. 34 | responses: 35 | '200': 36 | description: '' 37 | -------------------------------------------------------------------------------- /swagger/paths/report.yaml: -------------------------------------------------------------------------------- 1 | /report/site: 2 | get: 3 | operationId: get_site_report 4 | tags: 5 | - 'reports' 6 | responses: 7 | '200': 8 | description: '' 9 | schema: 10 | $ref: schemas/output/report-site.json 11 | 12 | /report/project: 13 | get: 14 | operationId: get_project_report 15 | tags: 16 | - 'reports' 17 | parameters: 18 | - in: query 19 | type: string 20 | name: projects 21 | description: Specify multiple times to include projects in the report 22 | - in: query 23 | type: string 24 | name: start_date 25 | description: Report start date 26 | - in: query 27 | type: string 28 | name: end_date 29 | description: Report end date 30 | responses: 31 | '200': 32 | description: '' 33 | schema: 34 | $ref: schemas/output/report-project.json 35 | -------------------------------------------------------------------------------- /swagger/paths/upload-match-uid.yaml: -------------------------------------------------------------------------------- 1 | /upload/uid-match: 2 | post: 3 | summary: Multipart form upload with N file fields, each with their desired filename. 4 | description: > 5 | Accepts uploads to an existing data hierarchy, matched via Session 6 | 7 | and Acquisition UID 8 | 9 | See api/schemas/input/uidmatchupload.json for the format of this metadata. 10 | operationId: upload_match_uid 11 | tags: 12 | - files 13 | responses: 14 | '200': 15 | description: '' 16 | schema: 17 | example: 18 | $ref: examples/file_info_list.json 19 | '402': 20 | description: Uploads must be from an authorized drone 21 | '404': 22 | description: Session or Acquisition with uid does not exist 23 | consumes: 24 | - multipart/form-data 25 | parameters: 26 | - in: formData 27 | name: formData 28 | type: string -------------------------------------------------------------------------------- /api/handlers/confighandler.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from ..web import encoder 4 | from ..web import base 5 | from .. import config 6 | 7 | class Config(base.RequestHandler): 8 | 9 | def get(self): 10 | """Return public Scitran configuration information.""" 11 | return config.get_public_config() 12 | 13 | def get_js(self): 14 | """Return scitran config in javascript format.""" 15 | self.response.write( 16 | 'config = ' + 17 | json.dumps( self.get(), sort_keys=True, indent=4, separators=(',', ': '), default=encoder.custom_json_serializer,) + 18 | ';' 19 | ) 20 | 21 | class Version(base.RequestHandler): 22 | 23 | def get(self): 24 | """Return database schema version""" 25 | resp = config.get_version() 26 | if resp != None: 27 | return resp 28 | else: 29 | self.abort(404, "Version document does not exist") 30 | -------------------------------------------------------------------------------- /swagger/schemas/mongo/file.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "properties": { 5 | "name": { "type": "string" }, 6 | "created": {}, 7 | "modified": {}, 8 | "type": { "type": "string" }, 9 | "mimetype": { "type": "string" }, 10 | "size": { "type": "integer" }, 11 | "hash": { "type": "string" }, 12 | "modality": { "type": "string" }, 13 | "measurements": { 14 | "items": { "type": "string"}, 15 | "type": "array", 16 | "uniqueItems": true 17 | }, 18 | "tags": { 19 | "items": { "type": "string"}, 20 | "type": "array", 21 | "uniqueItems": true 22 | }, 23 | "info": { 24 | "type": "object" 25 | } 26 | }, 27 | "required": ["name", "created", "modified", "size", "hash"], 28 | "key_fields": ["name"], 29 | "additionalProperties": false 30 | } 31 | -------------------------------------------------------------------------------- /swagger/templates/analysis-notes.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: resource 3 | type: string 4 | required: true 5 | - name: parameter 6 | type: string 7 | required: true 8 | - name: tag 9 | type: string 10 | required: true 11 | template: | 12 | parameters: 13 | - required: true 14 | type: string 15 | in: path 16 | name: '{{parameter}}' 17 | - required: true 18 | type: string 19 | in: path 20 | name: AnalysisId 21 | post: 22 | summary: Add a note to {{resource}} analysis. 23 | operationId: add_{{resource}}_analysis_note 24 | tags: 25 | - '{{tag}}' 26 | parameters: 27 | - name: body 28 | in: body 29 | required: true 30 | schema: 31 | $ref: schemas/input/note.json 32 | responses: 33 | '200': 34 | $ref: '#/responses/200:modified-with-count' 35 | '400': 36 | $ref: '#/responses/400:invalid-body-json' 37 | -------------------------------------------------------------------------------- /swagger/paths/upload-by-label.yaml: -------------------------------------------------------------------------------- 1 | /upload/label: 2 | post: 3 | summary: Multipart form upload with N file fields, each with their desired filename. 4 | description: > 5 | For technical reasons, no form field names can be repeated. Instead, use 6 | (file1, file2) and so forth. 7 | 8 | A non-file form field called "metadata" is also required, which must be 9 | a string containing JSON. 10 | 11 | See api/schemas/input/labelupload.json for the format of this metadata. 12 | operationId: upload_by_label 13 | tags: 14 | - files 15 | responses: 16 | '200': 17 | description: '' 18 | schema: 19 | example: 20 | $ref: examples/file_info_list.json 21 | '402': 22 | description: Uploads must be from an authorized drone 23 | consumes: 24 | - multipart/form-data 25 | parameters: 26 | - in: formData 27 | name: formData 28 | type: string -------------------------------------------------------------------------------- /swagger/paths/resolver.yaml: -------------------------------------------------------------------------------- 1 | /resolve: 2 | post: 3 | summary: Perform path based lookup of nodes in the Flywheel hierarchy 4 | description: | 5 | This will perform a deep lookup of a node (i.e. group/project/session/acquisition) and its children, 6 | including any files. The query path is an array of strings in the following order (by default): 7 | 8 | * group id 9 | * project label 10 | * session label 11 | * acquisition label 12 | 13 | An ID can be used instead of a label by formatting the string as ``. The full path 14 | to the node, and the node's children will be included in the response. 15 | operationId: resolve_path 16 | parameters: 17 | - name: body 18 | in: body 19 | required: true 20 | schema: 21 | $ref: schemas/input/resolver.json 22 | responses: 23 | '200': 24 | description: '' 25 | schema: 26 | $ref: schemas/output/resolver.json 27 | -------------------------------------------------------------------------------- /swagger/templates/packfile.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: resource 3 | type: string 4 | required: true 5 | - name: parameter 6 | type: string 7 | required: true 8 | - name: tag 9 | type: string 10 | required: true 11 | template: | 12 | parameters: 13 | - required: true 14 | type: string 15 | in: path 16 | name: '{{parameter}}' 17 | post: 18 | summary: Add files to an in-progress packfile 19 | operationId: '{{resource}}_packfile_upload' 20 | tags: 21 | - '{{tag}}' 22 | consumes: 23 | - multipart/form-data 24 | parameters: 25 | - name: token 26 | in: query 27 | type: string 28 | required: true 29 | - name: file 30 | in: formData 31 | type: file 32 | required: true 33 | responses: 34 | '200': 35 | description: '' 36 | schema: 37 | $ref: schemas/output/file-list.json 38 | examples: 39 | response: 40 | $ref: examples/file_info_list.json 41 | -------------------------------------------------------------------------------- /swagger/schemas/definitions/project-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "definitions": { 4 | "requirement": { 5 | "anyOf": [ 6 | {"required": ["minimum"]}, 7 | {"required": ["maximum"]} 8 | ], 9 | "properties": { 10 | "minimum": {"type": "integer", "minimum": 0}, 11 | "maximum": {"type": "integer", "minimum": 0} 12 | } 13 | }, 14 | "project-template": { 15 | "description": "A project's session template", 16 | "type": "object", 17 | "properties": { 18 | "session": { 19 | "type": "object" 20 | }, 21 | "acquisitions": { 22 | "type": "array", 23 | "minItems": 1, 24 | "items": {"$ref": "#/definitions/requirement"} 25 | } 26 | }, 27 | "additionalProperties": false 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /swagger/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scitran-core-swagger", 3 | "version": "0.0.1", 4 | "description": "REST API Documentation and Code Generation for the SciTran core SDK", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "node_modules/.bin/grunt", 8 | "lint": "node_modules/.bin/grunt lintSchemas", 9 | "test": "node_modules/.bin/jasmine --config=support/jasmine.json", 10 | "watch": "node_modules/.bin/grunt live", 11 | "coverage": "node_modules/.bin/grunt coverage" 12 | }, 13 | "author": "Justin Ehlert ", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "connect-livereload": "^0.6.0", 17 | "glob": "^7.1.2", 18 | "grunt": "^1.0.1", 19 | "grunt-contrib-connect": "^1.0.2", 20 | "grunt-contrib-copy": "^1.0.0", 21 | "grunt-contrib-watch": "^1.0.0", 22 | "jasmine": "^2.8.0", 23 | "js-yaml": "^3.10.0", 24 | "json-refs": "^3.0.2", 25 | "load-grunt-tasks": "^3.5.2", 26 | "lodash": "^4.17.4", 27 | "mustache": "^2.3.0", 28 | "pluralize": "^7.0.0", 29 | "swagger-tools": "^0.10.3", 30 | "swagger-ui-dist": "^3.7.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /swagger/schemas/mongo/subject.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "properties": { 5 | "_id": {}, 6 | "firstname": { "type": "string" }, 7 | "lastname": { "type": "string" }, 8 | 9 | "age": { "type": ["number", "null"] }, 10 | "sex": { "enum": ["male", "female", "other", "unknown", null] }, 11 | "race": { "enum": ["American Indian or Alaska Native", "Asian", "Native Hawaiian or Other Pacific Islander", "Black or African American", "White", "More Than One Race", "Unknown or Not Reported", null] }, 12 | "ethnicity": { "enum": ["Not Hispanic or Latino", "Hispanic or Latino", "Unknown or Not Reported", null] }, 13 | 14 | "code": { "type": "string" }, 15 | "tags": { "type": "array", "items": {"type": "string"} }, 16 | "files": { 17 | "type": ["array", "null"], 18 | "items": {"$ref": "file.json"} 19 | }, 20 | "info": { "type": "object" } 21 | }, 22 | "additionalProperties": false 23 | } 24 | -------------------------------------------------------------------------------- /swagger/templates/container.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: resource 3 | type: string 4 | required: true 5 | - name: tag 6 | type: string 7 | required: true 8 | - name: list-output-schema 9 | type: string 10 | required: true 11 | - name: create-input-schema 12 | type: string 13 | required: true 14 | template: | 15 | get: 16 | summary: Get a list of {{#pluralize}}{{resource}}{{/pluralize}} 17 | operationId: get_all_{{#pluralize}}{{resource}}{{/pluralize}} 18 | tags: 19 | - '{{tag}}' 20 | responses: 21 | '200': 22 | description: '' 23 | schema: 24 | $ref: {{{list-output-schema}}} 25 | post: 26 | summary: Create a new {{resource}} 27 | operationId: add_{{resource}} 28 | tags: 29 | - '{{tag}}' 30 | parameters: 31 | - in: body 32 | name: body 33 | required: true 34 | schema: 35 | $ref: {{{create-input-schema}}} 36 | responses: 37 | '200': 38 | description: '' 39 | schema: 40 | $ref: schemas/output/container-new.json 41 | '400': 42 | $ref: '#/responses/400:invalid-body-json' 43 | -------------------------------------------------------------------------------- /swagger/schemas/output/analyses-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "array", 4 | "items": {"$ref":"../definitions/analysis.json#/definitions/analysis-list-entry"}, 5 | "example": [{ 6 | "files": [{ 7 | "origin": { 8 | "type": "job", 9 | "id": "58063f24e5dc5b001657a87f" 10 | }, 11 | "mimetype": "application/octet-stream", 12 | "hash": "v0-sha384-12188e00a26650b2baa3f0195337dcf504f4362bb2136eef0cdbefb57159356b1355a0402fca0ab5ab081f21c305e5c2", 13 | "name": "cortical_surface_right_hemisphere.obj", 14 | "tags": [], 15 | "measurements": [], 16 | "modified": "2016-10-18T15:26:35.701000+00:00", 17 | "modality": null, 18 | "size": 21804112, 19 | "type": "None", 20 | "info": {} 21 | }], 22 | "created": "2016-10-18T17:45:11.778000+00:00", 23 | "modified": "2016-10-18T17:45:11.778000+00:00", 24 | "label": "cortex-demo 10/18/2016 13:45:5", 25 | "job": "58065fa7e5dc5b001457a882", 26 | "user": "canakgun@flywheel.io", 27 | "_id": "58065fa7e5dc5b001457a881" 28 | }] 29 | } 30 | -------------------------------------------------------------------------------- /swagger/examples/file_info_list.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "origin": { 4 | "type": "job", 5 | "id": "58063f24e5dc5b001657a87f" 6 | }, 7 | "mimetype": "application/octet-stream", 8 | "hash": "v0-sha384-12188e00a26650b2baa3f0195337dcf504f4362bb2136eef0cdbefb57159356b1355a0402fca0ab5ab081f21c305e5c2", 9 | "name": "cortical_surface_right_hemisphere.obj", 10 | "tags": [], 11 | "measurements": [], 12 | "modified": "2016-10-18T15:26:35.701000+00:00", 13 | "modality": null, 14 | "size": 21804112, 15 | "type": "None", 16 | "info": {} 17 | }, { 18 | "origin": { 19 | "type": "job", 20 | "id": "58065fa7e5dc5b001457a882" 21 | }, 22 | "mimetype": "application/octet-stream", 23 | "hash": "v0-sha384-12188e00a26650b2baa3f0195337dcf504f4362bb2136eef0cdbefb57159356b1355a0402fca0ab5ab081f21c305e5c2", 24 | "name": "cortical_surface_right_hemisphere.obj", 25 | "tags": [], 26 | "measurements": [], 27 | "modified": "2016-10-18T17:45:17.776000+00:00", 28 | "modality": null, 29 | "info": {}, 30 | "type": "None", 31 | "size": 21804112 32 | } 33 | ] 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 The Board of Trustees of the Leland Stanford Junior University 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /docker/build-trigger.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Triggers an auto-build on Docker Hub for the given source control reference. 4 | # 5 | # Example usage: ./build-trigger Tag 1.0.0 https://registry.hub.docker.com/u/scitran/reaper/trigger/11111111-2222-3333-4444-abcdefabcdef/ 6 | 7 | set -e 8 | 9 | if [ $# -ne 3 ] ; then 10 | >&2 echo "Usage: $( basename $0 ) " 11 | exit 1 12 | fi 13 | 14 | SOURCE_CONTROL_REF_TYPE="${1}" 15 | SOURCE_CONTROL_REF_NAME="${2}" 16 | TRIGGER_URL="${3}" 17 | 18 | if [ -z "${SOURCE_CONTROL_REF_TYPE}" ] ; then 19 | >&2 echo "Source control reference type not provided. Skipping build trigger." 20 | exit 1 21 | fi 22 | 23 | if [ -z "${SOURCE_CONTROL_REF_NAME}" ] ; then 24 | >&2 echo "Source control tag name not provided. Skipping build trigger." 25 | exit 1 26 | fi 27 | 28 | TRIGGER_PAYLOAD="{\"source_type\": \"${SOURCE_CONTROL_REF_TYPE}\", \"source_name\": \"${SOURCE_CONTROL_REF_NAME}\"}" 29 | curl -H "Content-Type: application/json" --data "${TRIGGER_PAYLOAD}" -X POST "${TRIGGER_URL}" 30 | >&2 echo 31 | >&2 echo "Docker Hub build for ${SOURCE_CONTROL_REF_TYPE} '${SOURCE_CONTROL_REF_NAME}' triggered." 32 | -------------------------------------------------------------------------------- /swagger/support/walk.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | 5 | /** 6 | * Deep-walk an object, invoking callback for each property. 7 | * Properties will be replaced with any value returned by callback. 8 | * @param obj The object to walk 9 | * @param {function} callback The callback function, that will be invoked with: 10 | * `propertyValue, path, state` 11 | * @param {path} The current path array (default is empty array) 12 | * @param {state} An optional state parameter 13 | */ 14 | module.exports = function objWalk(obj, callback, path, state) { 15 | var i, idx; 16 | state = state || {}; 17 | 18 | if( path ) { 19 | path = path.slice() 20 | } else { 21 | path = []; 22 | } 23 | idx = path.length; 24 | 25 | obj = callback(obj, path, state); 26 | 27 | if( _.isArray(obj) ) { 28 | for( i = 0; i < obj.length; i++ ) { 29 | path[idx] = '' + i; 30 | obj[i] = objWalk(obj[i], callback, path, _.cloneDeep(state)); 31 | } 32 | } else if( _.isObjectLike(obj) ) { 33 | for( i in obj ) { 34 | if( obj.hasOwnProperty(i) ) { 35 | path[idx] = i; 36 | obj[i] = objWalk(obj[i], callback, path, _.cloneDeep(state)); 37 | } 38 | } 39 | } 40 | 41 | return obj; 42 | } 43 | -------------------------------------------------------------------------------- /swagger/responses/index.yaml: -------------------------------------------------------------------------------- 1 | '200:deleted-with-count': 2 | description: The given number of records were deleted. 3 | schema: 4 | type: object 5 | properties: 6 | deleted: 7 | type: integer 8 | required: 9 | - deleted 10 | example: 11 | deleted: 1 12 | 13 | '200:modified-with-count': 14 | description: The given number of records were updated. 15 | schema: 16 | type: object 17 | properties: 18 | modified: 19 | type: integer 20 | required: 21 | - modified 22 | example: 23 | modified: 1 24 | 25 | '200:modified-with-count-and-jobs': 26 | description: The number of records modified and number of jobs started. 27 | schema: 28 | type: object 29 | properties: 30 | modified: 31 | type: integer 32 | jobs_triggered: 33 | type: integer 34 | required: 35 | - modified 36 | - jobs_triggered 37 | example: 38 | modified: 1 39 | jobs_triggered: 0 40 | 41 | '400:invalid-body-json': 42 | description: | 43 | JSON did not validate against schema for this endpoint. 44 | 45 | '404:resource-not-found': 46 | description: | 47 | The specified resource could not be found. -------------------------------------------------------------------------------- /swagger/examples/output/device-list.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "errors": null, 4 | "method": "bootstrapper", 5 | "interval": 500, 6 | "_id": "bootstrapper_Bootstrapper", 7 | "name": "Bootstrapper", 8 | "last_seen": "2016-11-28T18:54:11.880000+00:00" 9 | }, 10 | { 11 | "errors": null, 12 | "name": "flywheel-utility", 13 | "interval": 700, 14 | "_id": "gear-register_flywheel-utility", 15 | "method": "gear-register", 16 | "last_seen": "2016-11-29T18:51:39.836000+00:00" 17 | }, 18 | { 19 | "errors": null, 20 | "method": "importer", 21 | "_id": "importer_admin_import", 22 | "name": "admin import", 23 | "last_seen": "2016-11-28T18:57:27.104000+00:00" 24 | }, 25 | { 26 | "errors": null, 27 | "name": "Cron", 28 | "_id": "cron_Cron", 29 | "method": "cron", 30 | "last_seen": "2016-11-28T23:17:01.261000+00:00" 31 | }, 32 | { 33 | "info": { 34 | "basic": "info" 35 | }, 36 | "errors": [ 37 | "An Error" 38 | ], 39 | "name": "example_drone", 40 | "interval": 400, 41 | "_id": "test_method_megans_drone", 42 | "method": "test_method", 43 | "last_seen": "2016-11-28T23:14:06.004000+00:00" 44 | } 45 | ] 46 | -------------------------------------------------------------------------------- /swagger/templates/file-item-info.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: resource 3 | type: string 4 | required: true 5 | - name: parameter 6 | type: string 7 | required: true 8 | - name: tag 9 | type: string 10 | required: true 11 | template: | 12 | parameters: 13 | - required: true 14 | type: string 15 | in: path 16 | name: '{{parameter}}' 17 | - required: true 18 | type: string 19 | in: path 20 | name: FileName 21 | get: 22 | summary: Get info for a particular file. 23 | operationId: get_{{resource}}_file_info 24 | tags: 25 | - '{{tag}}' 26 | responses: 27 | '200': 28 | description: 'The file object, including info' 29 | schema: 30 | $ref: schemas/output/file-info.json 31 | post: 32 | summary: Update info for a particular file. 33 | operationId: modify_{{resource}}_file_info 34 | x-sdk-modify-info: true 35 | tags: 36 | - '{{tag}}' 37 | parameters: 38 | - name: body 39 | in: body 40 | required: true 41 | schema: 42 | $ref: schemas/input/info_update.json 43 | responses: 44 | '200': 45 | $ref: '#/responses/200:modified-with-count' 46 | 47 | 48 | -------------------------------------------------------------------------------- /swagger/schemas/definitions/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "definitions": { 4 | "info-add-remove": { 5 | "type": "object", 6 | "properties": { 7 | "set": {"type": "object", "minProperties": 1}, 8 | "delete": { 9 | "type": "array", 10 | "uniqueItems": true, 11 | "minItems": 1, 12 | "items": { 13 | "type": "string" 14 | } 15 | } 16 | }, 17 | "additionalProperties": false 18 | }, 19 | "info-replace": { 20 | "type": "object", 21 | "properties": { 22 | "replace": {"type": "object"} 23 | }, 24 | "additionalProperties": false 25 | }, 26 | "info-update-input": { 27 | "description": "Helper endpoint for editing an object's info key", 28 | "type": "object", 29 | "oneOf": [ 30 | {"$ref":"#/definitions/info-add-remove"}, 31 | {"$ref":"#/definitions/info-replace"} 32 | ] 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /swagger/examples/output/acquisition.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | { 4 | "origin": { 5 | "method": "importer", 6 | "type": "device", 7 | "id": "importer_Admin_Import", 8 | "name": "Admin Import" 9 | }, 10 | "mimetype": "application/zip", 11 | "measurements": [], 12 | "hash": "v0-sha384-dd3c97bfe0ad1fcba75ae6718c6e81038c59af4f447f5db194d52732efa4f955b28455db02eb64cad3e4e55f11e3679f", 13 | "name": "4784_1_1_localizer_dicom.zip", 14 | "tags": [], 15 | "created": "2016-09-21T14:56:09.943000+00:00", 16 | "modified": "2016-09-21T14:56:09.943000+00:00", 17 | "modality": null, 18 | "info": {}, 19 | "type": "dicom", 20 | "size": 989933 21 | } 22 | ], 23 | "created": "2016-09-21T14:56:10.026000+00:00", 24 | "timestamp": "2016-09-21T19:24:45.539570+00:00", 25 | "modified": "2016-09-21T14:56:10.026000+00:00", 26 | "label": "4784_1_1_localizer", 27 | "session": "57e29f8afab726000f7ec6b1", 28 | "collections": [ 29 | "57e2a026675ca0000f0eeb0c" 30 | ], 31 | "_id": "57e29f8afab726000f7ec6b2", 32 | "public": false, 33 | "permissions": [ 34 | { 35 | "access": "admin", 36 | "_id": "coltonlw@flywheel.io" 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /swagger/examples/output/rule-list.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "alg": "dcm2niix", 4 | "all": [ 5 | { 6 | "regex": true, 7 | "type": "file.measurements", 8 | "value": "^(?!non-image).+$" 9 | }, 10 | { 11 | "type": "file.type", 12 | "value": "nifti" 13 | } 14 | ], 15 | "_id": "5a12f2923306be0016179f47", 16 | "name": "dcm2niix", 17 | "any": [] 18 | }, 19 | { 20 | "alg": "dicom-mr-classifier", 21 | "all": [ 22 | { 23 | "type": "file.type", 24 | "value": "dicom" 25 | }, 26 | { 27 | "regex": true, 28 | "type": "file.measurements", 29 | "value": "^(?!non-image).+$" 30 | } 31 | ], 32 | "_id": "5a21be60a75c04001b7a268b", 33 | "name": "dicom classifier (all dicoms)", 34 | "any": [] 35 | }, 36 | { 37 | "alg": "mriqc", 38 | "all": [ 39 | { 40 | "type": "file.type", 41 | "value": "nifti" 42 | }, 43 | { 44 | "regex": true, 45 | "type": "file.measurements", 46 | "value": "(functional|anatomy_t1w|anatomy_t2w)" 47 | } 48 | ], 49 | "_id": "5a21be60a75c04001b7a268c", 50 | "name": "mriqc (nifti classified as functional or anatomical)", 51 | "any": [] 52 | } 53 | ] 54 | -------------------------------------------------------------------------------- /swagger/schemas/definitions/note.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "definitions": { 4 | "text": { 5 | "type": "string", 6 | "x-sdk-positional": true 7 | }, 8 | "note-input":{ 9 | "type":"object", 10 | "properties":{ 11 | "text":{"$ref":"#/definitions/text"} 12 | }, 13 | "additionalProperties": false, 14 | "x-sdk-model": "note" 15 | }, 16 | "notes-list-input": { 17 | "type": "array", 18 | "items":{"allOf":[{"$ref":"#/definitions/note-input"}]} 19 | }, 20 | "note-output":{ 21 | "type":"object", 22 | "properties":{ 23 | "_id":{"$ref":"common.json#/definitions/objectid"}, 24 | "text":{"$ref":"#/definitions/text"}, 25 | "created":{"$ref":"created-modified.json#/definitions/created"}, 26 | "modified":{"$ref":"created-modified.json#/definitions/modified"}, 27 | "user":{"$ref":"common.json#/definitions/user-id"} 28 | }, 29 | "additionalProperties": false, 30 | "required":["_id", "text", "created", "modified", "user"], 31 | "x-sdk-model": "note" 32 | }, 33 | "notes-list-output":{ 34 | "type":"array", 35 | "items":{"allOf":[{"$ref":"#/definitions/note-output"}]} 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /swagger/schemas/mongo/analysis.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "Analysis", 4 | "type": "object", 5 | "properties": { 6 | "_id": {"type": "string"}, 7 | "parent": { 8 | "type": "object", 9 | "properties": { 10 | "type": "string", 11 | "id": "string" 12 | } 13 | }, 14 | "created": {}, 15 | "modified": {}, 16 | "notes": {"type": "array", "items": {"$ref": "note.json"}}, 17 | "files": { 18 | "type": ["array", "null"], 19 | "items": { 20 | "$ref": "file.json", 21 | "additionalProperties": { 22 | "output": {"type": "boolean"}, 23 | "input": {"type": "boolean"} 24 | } 25 | } 26 | }, 27 | "job": {"type": "string"}, 28 | "label": {"type": "string", "minLength": 1, "maxLength": 256}, 29 | "user": {"type": "string"}, 30 | "permissions": {"type": "array", "items": {"$ref": "../definitions/permission.json#/definitions/permission"}}, 31 | "public": {"type": "boolean"} 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /swagger/paths/config-js.yaml: -------------------------------------------------------------------------------- 1 | /config.js: 2 | get: 3 | summary: Return public Scitran configuration information in javascript format. 4 | operationId: get_config_js 5 | produces: 6 | - text/html 7 | responses: 8 | '200': 9 | description: '' 10 | examples: 11 | 'application/javascript': | 12 | config = { 13 | "auth": { 14 | "auth_endpoint": "https://accounts.google.com/o/oauth2/auth", 15 | "client_id": "949263322061-6q4fqi0m4ihkp1v5n6v8q9bef4gd0f1k.apps.googleusercontent.com", 16 | "id_endpoint": "https://www.googleapis.com/plus/v1/people/me/openIdConnect", 17 | "verify_endpoint": "https://www.googleapis.com/oauth2/v1/tokeninfo" 18 | }, 19 | "created": "2016-03-31T16:30:00.852000+00:00", 20 | "modified": "2016-03-31T16:30:00.852000+00:00", 21 | "site": { 22 | "api_url": "https://10.240.0.2:443/api", 23 | "central_url": "https://sdmc.scitran.io/api", 24 | "id": "local", 25 | "name": "BaliDemo", 26 | "registered": false, 27 | "ssl_cert": null 28 | } 29 | -------------------------------------------------------------------------------- /api/dao/dbutil.py: -------------------------------------------------------------------------------- 1 | import random 2 | import time 3 | 4 | from pymongo.errors import DuplicateKeyError 5 | from ..web.errors import APIStorageException 6 | 7 | def try_replace_one(db, coll_name, query, update, upsert=False): 8 | """ 9 | Mongo does not see replace w/ upsert as an atomic action: 10 | https://jira.mongodb.org/browse/SERVER-14322 11 | 12 | This function will try a replace_one operation, returning the result and if the operation succeeded. 13 | """ 14 | 15 | try: 16 | result = db[coll_name].replace_one(query, update, upsert=upsert) 17 | except DuplicateKeyError: 18 | return result, False 19 | else: 20 | return result, True 21 | 22 | 23 | def fault_tolerant_replace_one(db, coll_name, query, update, upsert=False): 24 | """ 25 | Like try_replace_one, but will retry several times, waiting a random short duration each time. 26 | 27 | Raises an APIStorageException if the retry loop gives up. 28 | """ 29 | 30 | attempts = 0 31 | while attempts < 10: 32 | attempts += 1 33 | 34 | result, success = try_replace_one(db, coll_name, query, update, upsert) 35 | 36 | if success: 37 | return result 38 | else: 39 | time.sleep(random.uniform(0.01,0.05)) 40 | 41 | raise APIStorageException('Unable to replace object.') 42 | -------------------------------------------------------------------------------- /docker/uwsgi-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # FDM-831 workaround 4 | # https://github.com/docker/compose/issues/2454 5 | # remove after docker 1.10.0 becomes minim supported version. 6 | # 7 | # If /etc/hosts has lines starting with tab, it is corrupted, 8 | # exit to allow docker to restart. 9 | grep -P "^\t" /etc/hosts 10 | if [ "$?" == 0 ] ; then 11 | echo "Host mapping in /etc/hosts is buggy, fail contain start." 12 | exit 1 13 | fi 14 | 15 | 16 | set -e 17 | set -x 18 | 19 | export PYTHONPATH=/var/scitran/code/api 20 | 21 | export SCITRAN_PERSISTENT_PATH=/var/scitran/data 22 | export SCITRAN_PERSISTENT_DATA_PATH=/var/scitran/data 23 | 24 | # Get the RunAs user from the owner of the mapped folder. 25 | # This is a compromise to get permissions to work well with 26 | # host mapped volumes with docker-machine on OSX and production 27 | # without the vbox driver layer. 28 | RUNAS_USER=$(ls -ld "${SCITRAN_PERSISTENT_DATA_PATH}" | awk '{print $3}') 29 | 30 | 31 | if [ "${1:0:1}" = '-' ]; then 32 | set -- uwsgi "$@" 33 | fi 34 | 35 | # run $PRE_RUNAS_CMD as root if provided. Useful for things like JIT pip insalls. 36 | if [ ! -z "${PRE_RUNAS_CMD}" ]; then 37 | ${PRE_RUNAS_CMD} 38 | fi 39 | 40 | if [ "$1" = 'uwsgi' ]; then 41 | 42 | exec gosu ${RUNAS_USER} "$@" 43 | fi 44 | 45 | gosu ${RUNAS_USER} "$@" 46 | 47 | result=$? 48 | echo "Exit code was $result" 49 | -------------------------------------------------------------------------------- /TESTING.md: -------------------------------------------------------------------------------- 1 | ## Run the tests 2 | 3 | ### Ubuntu 4 | Run automated tests: 5 | ``` 6 | # Follow installation instructions in README first 7 | . /runtime/bin/activate # Or wherever your scitran virtualenv is 8 | ./test/bin/setup-integration-tests-ubuntu.sh 9 | ./test/bin/run-tests-ubuntu.sh 10 | ``` 11 | All tests are executed by default. Subsets can be run using the filtering options: 12 | 13 | * To run linting, use `--lint` (`-l`) 14 | * To run unit tests, use `--unit` (`-u`) 15 | * To run integration tests, use `--integ` (`-i`) 16 | * To pass any arguments to `py.test`, use `-- PYTEST_ARGS` 17 | 18 | See [py.test usage](https://docs.pytest.org/en/latest/usage.html) for more. 19 | 20 | ### Docker 21 | Build scitran-core image and run automated tests in a docker container: 22 | ``` 23 | ./tests/bin/run-tests-docker.sh 24 | ``` 25 | * To skip building the image, use `--no-build` (`-B`) 26 | * To pass any arguments to `run-tests-ubuntu.sh`, use `-- TEST_ARGS` 27 | 28 | 29 | #### Example 30 | Without rebuilding the image, run only integration tests matching `foo`, use the highest verbosity level for test output and jump into a python debugger session in case an assertion fails: 31 | ``` 32 | ./tests/bin/run-tests-docker.sh -B -- -i -- -k foo -vvv --pdb 33 | ``` 34 | 35 | **NOTE:** The mongodb version is pinned via the `MONGO_VERSION` variable in `tests/bin/run-tests-docker.sh`. 36 | -------------------------------------------------------------------------------- /swagger/examples/output/gear.json: -------------------------------------------------------------------------------- 1 | { 2 | "category": "converter", 3 | "gear": { 4 | "inputs": { 5 | "audio": { 6 | "base": "file", 7 | "description": "Any audio file. Plain speech suggested!" 8 | } 9 | }, 10 | "maintainer": "Nathaniel Kofalt", 11 | "description": "Detects the speech content of an audio file, using the machine-learning DeepSpeech library by Mozilla.", 12 | "license": "Other", 13 | "author": "Nathaniel Kofalt", 14 | "url": "", 15 | "label": "Speech Recognition", 16 | "source": "https://github.com/mozilla/DeepSpeech", 17 | "version": "1", 18 | "custom": { 19 | "gear-builder": { 20 | "image": "gear-builder-kdfqapbezk-20171219165918", 21 | "container": "c15189b625a0ea450cafbb24ef0df03c26cc8cf151181976ec4289801e191032" 22 | } 23 | }, 24 | "config": {}, 25 | "name": "speech-recognition" 26 | }, 27 | "created": "2017-12-20T00:09:50.381000+00:00", 28 | "exchange": { 29 | "git-commit": "local", 30 | "rootfs-hash": "sha384:e01d925f90b097b554be0f802ef6ebb9f07000d7a6a2a0c3a25dac26893d4ac2414381e2c8e60f4b58b27c7fe8e56099", 31 | "rootfs-url": "/api/gears/temp/5a39aa4e07a393001b663910" 32 | }, 33 | "modified": "2017-12-20T00:09:50.381000+00:00", 34 | "_id": "5a39aa4e07a393001b663910" 35 | } -------------------------------------------------------------------------------- /swagger/examples/output/gear-list.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "category": "converter", 3 | "gear": { 4 | "inputs": { 5 | "audio": { 6 | "base": "file", 7 | "description": "Any audio file. Plain speech suggested!" 8 | } 9 | }, 10 | "maintainer": "Nathaniel Kofalt", 11 | "description": "Detects the speech content of an audio file, using the machine-learning DeepSpeech library by Mozilla.", 12 | "license": "Other", 13 | "author": "Nathaniel Kofalt", 14 | "url": "", 15 | "label": "Speech Recognition", 16 | "source": "https://github.com/mozilla/DeepSpeech", 17 | "version": "1", 18 | "custom": { 19 | "gear-builder": { 20 | "image": "gear-builder-kdfqapbezk-20171219165918", 21 | "container": "c15189b625a0ea450cafbb24ef0df03c26cc8cf151181976ec4289801e191032" 22 | } 23 | }, 24 | "config": {}, 25 | "name": "speech-recognition" 26 | }, 27 | "created": "2017-12-20T00:09:50.381000+00:00", 28 | "exchange": { 29 | "git-commit": "local", 30 | "rootfs-hash": "sha384:e01d925f90b097b554be0f802ef6ebb9f07000d7a6a2a0c3a25dac26893d4ac2414381e2c8e60f4b58b27c7fe8e56099", 31 | "rootfs-url": "/api/gears/temp/5a39aa4e07a393001b663910" 32 | }, 33 | "modified": "2017-12-20T00:09:50.381000+00:00", 34 | "_id": "5a39aa4e07a393001b663910" 35 | }] -------------------------------------------------------------------------------- /tests/unit_tests/python/test_files.py: -------------------------------------------------------------------------------- 1 | 2 | import pytest 3 | from api import files 4 | 5 | 6 | def test_extension(): 7 | assert files.guess_type_from_filename('example.pdf') == 'pdf' 8 | 9 | def test_multi_extension(): 10 | assert files.guess_type_from_filename('example.zip') == 'archive' 11 | assert files.guess_type_from_filename('example.gephysio.zip') == 'gephysio' 12 | 13 | def test_nifti(): 14 | assert files.guess_type_from_filename('example.nii') == 'nifti' 15 | assert files.guess_type_from_filename('example.nii.gz') == 'nifti' 16 | assert files.guess_type_from_filename('example.nii.x.gz') == None 17 | 18 | def test_qa(): 19 | assert files.guess_type_from_filename('example.png') == 'image' 20 | assert files.guess_type_from_filename('example.qa.png') == 'qa' 21 | assert files.guess_type_from_filename('example.qa') == None 22 | assert files.guess_type_from_filename('example.qa.png.unknown') == None 23 | 24 | def test_tabular_data(): 25 | assert files.guess_type_from_filename('example.csv') == 'tabular data' 26 | assert files.guess_type_from_filename('example.csv.gz') == 'tabular data' 27 | assert files.guess_type_from_filename('example.tsv') == 'tabular data' 28 | assert files.guess_type_from_filename('example.tsv.gz') == 'tabular data' 29 | 30 | def test_unknown(): 31 | assert files.guess_type_from_filename('example.unknown') == None 32 | -------------------------------------------------------------------------------- /swagger/examples/output/session-list.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "group": "scitran", 3 | "created": "2016-09-19T17:13:48.164000+00:00", 4 | "subject": { 5 | "code": "ex4784", 6 | "_id": "57e01cccb1dc04000fb83f02" 7 | }, 8 | "modified": "2016-09-19T17:13:48.164000+00:00", 9 | "label": "control_1", 10 | "project": "57e01cccf6b5d5edbcb4e1cf", 11 | "_id": "57e01cccb1dc04000fb83f03", 12 | "public": false, 13 | "permissions": [{ 14 | "access": "admin", 15 | "_id": "coltonlw@flywheel.io" 16 | }] 17 | }, { 18 | "group": "scitran", 19 | "created": "2016-09-19T17:13:54.873000+00:00", 20 | "subject": { 21 | "code": "ex6879", 22 | "_id": "57e01cd2b1dc04000eb83f05" 23 | }, 24 | "modified": "2016-09-19T17:13:54.873000+00:00", 25 | "label": "control_2", 26 | "project": "57e01cccf6b5d5edbcb4e1cf", 27 | "_id": "57e01cd2b1dc04000eb83f06", 28 | "public": false, 29 | "permissions": [{ 30 | "access": "admin", 31 | "_id": "coltonlw@flywheel.io" 32 | }] 33 | }, { 34 | "group": "scitran", 35 | "created": "2016-09-19T17:14:02.228000+00:00", 36 | "subject": { 37 | "code": "ex8403", 38 | "_id": "57e01cdab1dc04000eb83f10" 39 | }, 40 | "modified": "2016-09-19T17:14:02.228000+00:00", 41 | "label": "patient_1", 42 | "project": "57e01cccf6b5d5edbcb4e1cf", 43 | "_id": "57e01cdab1dc04000eb83f11", 44 | "public": false, 45 | "permissions": [{ 46 | "access": "admin", 47 | "_id": "coltonlw@flywheel.io" 48 | }] 49 | }] 50 | -------------------------------------------------------------------------------- /api/filetypes.json: -------------------------------------------------------------------------------- 1 | { 2 | "bval": [ ".bval", ".bvals" ], 3 | "bvec": [ ".bvec", ".bvecs" ], 4 | "dicom": [ ".dcm", ".dcm.zip", ".dicom.zip" ], 5 | "eeg": [ ".eeg.zip" ], 6 | "gephysio": [ ".gephysio.zip" ], 7 | "ismrmrd": [ ".h5", ".hdf5" ], 8 | "MATLAB data": [ ".mat" ], 9 | "MGH data": [ ".mgh", ".mgz", ".mgh.gz" ], 10 | "nifti": [ ".nii.gz", ".nii" ], 11 | "parrec": [ ".parrec.zip", ".par-rec.zip" ], 12 | "pfile": [ ".7.gz", ".7", ".7.zip" ], 13 | "PsychoPy data": [ ".psydat" ], 14 | "qa": [ ".qa.png", ".qa.json", ".qa.html" ], 15 | 16 | "archive": [ ".zip", ".tbz2", ".tar.gz", ".tbz", ".tar.bz2", ".tgz", ".tar", ".txz", ".tar.xz" ], 17 | "document": [ ".docx", ".doc" ], 18 | "image": [ ".jpg", ".tif", ".jpeg", ".gif", ".bmp", ".png", ".tiff" ], 19 | "markup": [ ".html", ".htm", ".xml" ], 20 | "markdown": [ ".md", ".markdown" ], 21 | "log": [ ".log" ], 22 | "pdf": [ ".pdf" ], 23 | "presentation": [ ".ppt", ".pptx" ], 24 | "source code": [ ".c", ".py", ".cpp", ".js", ".m", ".json", ".java", ".php", ".css", ".toml", ".yaml", ".yml" ], 25 | "spreadsheet": [ ".xls", ".xlsx" ], 26 | "tabular data": [ ".csv.gz", ".csv", ".tsv.gz", ".tsv" ], 27 | "text": [ ".txt" ], 28 | "video": [ ".mpeg", ".mpg", ".mov", ".mp4", ".m4v", ".mts" ] 29 | } 30 | -------------------------------------------------------------------------------- /swagger/schemas/input/gear.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "Analysis", 4 | "type": "object", 5 | "allOf":[{"$ref":"../definitions/gear.json#/definitions/gear-doc"}], 6 | "example": { 7 | "category": "converter", 8 | "gear": { 9 | "inputs": { 10 | "audio": { 11 | "base": "file", 12 | "description": "Any audio file. Plain speech suggested!" 13 | } 14 | }, 15 | "maintainer": "Nathaniel Kofalt", 16 | "description": "Detects the speech content of an audio file, using the machine-learning DeepSpeech library by Mozilla.", 17 | "license": "Other", 18 | "author": "Nathaniel Kofalt", 19 | "url": "", 20 | "label": "Speech Recognition", 21 | "source": "https://github.com/mozilla/DeepSpeech", 22 | "version": "1", 23 | "custom": { 24 | "gear-builder": { 25 | "image": "gear-builder-kdfqapbezk-20171219165918", 26 | "container": "c15189b625a0ea450cafbb24ef0df03c26cc8cf151181976ec4289801e191032" 27 | } 28 | }, 29 | "config": {}, 30 | "name": "speech-recognition" 31 | }, 32 | "exchange": { 33 | "git-commit": "local", 34 | "rootfs-hash": "sha384:e01d925f90b097b554be0f802ef6ebb9f07000d7a6a2a0c3a25dac26893d4ac2414381e2c8e60f4b58b27c7fe8e56099", 35 | "rootfs-url": "/api/gears/temp/5a39aa4e07a393001b663910" 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /swagger/paths/site-rules.yaml: -------------------------------------------------------------------------------- 1 | /site/rules: 2 | get: 3 | summary: List all site rules. 4 | operationId: get_site_rules 5 | tags: 6 | - rules 7 | responses: 8 | '200': 9 | description: '' 10 | schema: 11 | $ref: schemas/output/rule-list.json 12 | post: 13 | summary: Create a new site rule. 14 | operationId: add_site_rule 15 | tags: 16 | - rules 17 | responses: 18 | default: 19 | description: '' 20 | parameters: 21 | - in: body 22 | name: body 23 | schema: 24 | $ref: schemas/input/rule-new.json 25 | 26 | /site/rules/{RuleId}: 27 | parameters: 28 | - name: RuleId 29 | type: string 30 | in: path 31 | required: true 32 | get: 33 | summary: Get a site rule. 34 | operationId: get_site_rule 35 | tags: 36 | - rules 37 | responses: 38 | '200': 39 | description: '' 40 | schema: 41 | $ref: schemas/output/rule.json 42 | put: 43 | summary: Update a site rule. 44 | operationId: modify_site_rule 45 | tags: 46 | - rules 47 | responses: 48 | default: 49 | description: '' 50 | parameters: 51 | - in: body 52 | name: body 53 | schema: 54 | $ref: schemas/input/rule-update.json 55 | delete: 56 | summary: Remove a site rule. 57 | operationId: remove_site_rule 58 | tags: 59 | - rules 60 | responses: 61 | '200': 62 | $ref: '#/responses/200:deleted-with-count' 63 | -------------------------------------------------------------------------------- /swagger/templates/notes-note.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: resource 3 | type: string 4 | required: true 5 | - name: parameter 6 | type: string 7 | required: true 8 | - name: tag 9 | type: string 10 | required: true 11 | template: | 12 | parameters: 13 | - required: true 14 | type: string 15 | in: path 16 | name: '{{parameter}}' 17 | - required: true 18 | type: string 19 | in: path 20 | name: NoteId 21 | get: 22 | summary: Get a note on {{resource}}. 23 | operationId: get_{{resource}}_note 24 | tags: 25 | - '{{tag}}' 26 | responses: 27 | '200': 28 | description: '' 29 | schema: 30 | $ref: schemas/output/note.json 31 | examples: 32 | response: 33 | $ref: examples/output/note.json 34 | put: 35 | summary: Update a note on {{resource}}. 36 | operationId: modify_{{resource}}_note 37 | tags: 38 | - '{{tag}}' 39 | parameters: 40 | - in: body 41 | name: body 42 | required: true 43 | schema: 44 | $ref: schemas/input/note.json 45 | responses: 46 | '200': 47 | $ref: '#/responses/200:modified-with-count' 48 | '400': 49 | $ref: '#/responses/400:invalid-body-json' 50 | delete: 51 | summary: Remove a note from {{resource}} 52 | operationId: delete_{{resource}}_note 53 | tags: 54 | - '{{tag}}' 55 | responses: 56 | '200': 57 | $ref: '#/responses/200:modified-with-count' 58 | -------------------------------------------------------------------------------- /swagger/templates/permissions-user.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: resource 3 | type: string 4 | required: true 5 | - name: parameter 6 | type: string 7 | required: true 8 | - name: tag 9 | type: string 10 | required: true 11 | template: | 12 | parameters: 13 | - required: true 14 | type: string 15 | in: path 16 | name: '{{parameter}}' 17 | - required: true 18 | type: string 19 | in: path 20 | name: UserId 21 | get: 22 | summary: List a user's permissions for this {{resource}}. 23 | operationId: get_{{resource}}_user_permission 24 | tags: 25 | - '{{tag}}' 26 | responses: 27 | '200': 28 | description: '' 29 | schema: 30 | $ref: schemas/output/permission.json 31 | examples: 32 | response: 33 | $ref: examples/output/permission.json 34 | put: 35 | summary: Update a user's permission for this {{resource}}. 36 | operationId: modify_{{resource}}_user_permission 37 | tags: 38 | - '{{tag}}' 39 | parameters: 40 | - in: body 41 | name: body 42 | schema: 43 | $ref: schemas/input/permission.json 44 | responses: 45 | '200': 46 | $ref: "#/responses/200:modified-with-count" 47 | '400': 48 | $ref: '#/responses/400:invalid-body-json' 49 | delete: 50 | summary: Delete a permission 51 | operationId: delete_{{resource}}_user_permission 52 | tags: 53 | - '{{tag}}' 54 | responses: 55 | '200': 56 | $ref: "#/responses/200:modified-with-count" 57 | -------------------------------------------------------------------------------- /tests/integration_tests/python/test_errors.py: -------------------------------------------------------------------------------- 1 | def test_extra_param(as_admin): 2 | label = 'extra_param' 3 | 4 | r = as_admin.post('/projects', json={ 5 | 'group': 'unknown', 6 | 'label': label, 7 | 'public': False, 8 | 'extra_param': 'some_value' 9 | }) 10 | assert r.status_code == 400 11 | 12 | r = as_admin.get('/projects') 13 | assert r.ok 14 | assert not any(project['label'] == label for project in r.json()) 15 | 16 | def test_error_response(as_admin, as_user, as_public, as_root, data_builder, api_db): 17 | 18 | group = data_builder.create_group() 19 | project = data_builder.create_project() 20 | 21 | # Test dao exception 22 | r = as_admin.post('/users', json={'_id': "admin@user.com", 'firstname': "Firstname", 'lastname': "Lastname"}) 23 | assert r.status_code == 409 24 | assert r.json().get('request_id') 25 | 26 | # Test schema exceptions 27 | r = as_admin.post('/groups', json={'foo':'bar'}) 28 | assert r.status_code == 400 29 | assert r.json().get('request_id') 30 | 31 | # Test Permission exception 32 | r = as_user.put('/projects/' + project, json={'label':'Project'}) 33 | assert r.status_code == 403 34 | assert r.json().get('request_id') 35 | 36 | # Test Key Error 37 | analysis = api_db.analyses.insert_one({'label':'no-parent'}).inserted_id 38 | r = as_root.get('/analyses/' + str(analysis)) 39 | assert r.status_code == 500 40 | assert r.json().get('message') == "Key 'parent' was not found" 41 | api_db.analyses.delete_one({'_id': analysis}) 42 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ### Updating api-console 2 | 3 | 1. Run a node container with the core root-dir bind-mounted: 4 | 5 | `$ docker run -itv $(pwd):/core -w /core node bash` 6 | 7 | 8 | 2. Install [api-console](https://github.com/mulesoft/api-console)'s recommended [cli build tool](https://github.com/mulesoft/api-console/blob/master/docs/build-tools.md): 9 | 10 | `$ npm install -g api-console-cli` 11 | 12 | 13 | 3. Generate "standalone" console into `./docs`: 14 | 15 | `$ api-console build https://raw.githubusercontent.com/scitran/core/master/raml/api.raml --output ./docs` 16 | 17 | Notes: 18 | * This can take a couple minutes 19 | * The `./docs` folder is recreated in the container 20 | - Owned by root - use `sudo chown -R $USER:$USER docs` on the host 21 | - Intermediate build artifacts can be removed: `$ rm -rf docs/bower_components docs/src` 22 | - This readme gets wiped - use `git checkout docs/README.md` 23 | 24 | 25 | 4. Enable branch selection via search & replace in `docs/index.html`: 26 | 27 | ```javascript 28 | document.querySelector("raml-js-parser").loadApi("https://raw.githubusercontent.com/scitran/core/master/raml/api.raml") 29 | ``` 30 | 31 | ```javascript 32 | var url=new URL(location.href);var branch=new URLSearchParams(url.search).get("branch")||"master";document.querySelector("raml-js-parser").loadApi("https://raw.githubusercontent.com/scitran/core/"+branch+"/raml/api.raml") 33 | ``` 34 | 35 | 36 | 5. Test it out: 37 | 38 | 1. `$ cd docs && python2 -m SimpleHTTPServer` 39 | 2. Visit http://localhost:8000 40 | -------------------------------------------------------------------------------- /swagger/schemas/definitions/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "definitions": { 4 | "label": { 5 | "type": "string", 6 | "minLength": 1, 7 | "maxLength": 256 8 | }, 9 | "description": { 10 | "type": "string" 11 | }, 12 | "objectid": { 13 | "type": "string", 14 | "pattern": "^[a-fA-F0-9]{24}$" 15 | }, 16 | "string-id": { 17 | "type": "string", 18 | "maxLength": 64, 19 | "minLength": 2, 20 | "pattern": "^[0-9a-z][0-9a-z.@_-]{0,30}[0-9a-z]$" 21 | }, 22 | "user-id": { 23 | "type": "string", 24 | "format": "email" 25 | }, 26 | "info": { 27 | "type": "object" 28 | }, 29 | "timestamp": { 30 | "type": ["string", "null"], 31 | "format": "date-time" 32 | }, 33 | "deleted-count": { 34 | "type":"object", 35 | "properties": { 36 | "deleted": { 37 | "type": "integer" 38 | } 39 | } 40 | }, 41 | "modified-count": { 42 | "type": "object", 43 | "properties": { 44 | "modified": { 45 | "type": "integer" 46 | } 47 | } 48 | }, 49 | "object-created": { 50 | "type": "object", 51 | "properties": { 52 | "_id": { 53 | "type": "string" 54 | } 55 | }, 56 | "x-sdk-return": "_id" 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /swagger/examples/output/job-list.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": { 4 | "dicom": { 5 | "type": "acquisition", 6 | "id": "57d75ec38e4eb9699b3c245e", 7 | "name": "1_1_dicom.zip" 8 | } 9 | }, 10 | "attempt": 1, 11 | "gear_id": "dcm-convert", 12 | "tags": [ 13 | "dcm-convert" 14 | ], 15 | "destination": { 16 | "type": "acquisition", 17 | "id": "57ac7394c700190017123fb7" 18 | }, 19 | "modified": "2016-08-11T12:46:12.593000+00:00", 20 | "created": "2016-08-11T12:46:12.593000+00:00", 21 | "state": "pending", 22 | "id": "57ac7394c700190017123fb8", 23 | "config":{ 24 | "speed":3 25 | } 26 | }, 27 | { 28 | "inputs": { 29 | "dicom": { 30 | "type": "acquisition", 31 | "id": "57ac7394c700190017123fb7", 32 | "name": "8403_4_1_t1_dicom.zip" 33 | } 34 | }, 35 | "attempt": 1, 36 | "gear_id": "dcm_convert", 37 | "tags": [ 38 | "ad-hoc", 39 | "dcm_convert" 40 | ], 41 | "destination": { 42 | "type": "acquisition", 43 | "id": "573c9e6a844eac7fc01747cd" 44 | }, 45 | "modified": "2016-08-11T13:02:09.055000+00:00", 46 | "created": "2016-08-11T13:02:09.055000+00:00", 47 | "state": "pending", 48 | "id": "57ac77515e325c0018cd17cf", 49 | "config":{ 50 | "speed":3 51 | } 52 | } 53 | ] 54 | -------------------------------------------------------------------------------- /swagger/templates/container-item.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: resource 3 | type: string 4 | required: true 5 | - name: tag 6 | type: string 7 | required: true 8 | - name: parameter 9 | type: string 10 | required: true 11 | - name: update-input-schema 12 | type: string 13 | required: true 14 | - name: get-output-schema 15 | type: string 16 | required: true 17 | - name: delete-description 18 | type: string 19 | template: | 20 | parameters: 21 | - in: path 22 | type: string 23 | name: '{{parameter}}' 24 | required: true 25 | get: 26 | summary: Get a single {{resource}} 27 | operationId: get_{{resource}} 28 | tags: 29 | - '{{tag}}' 30 | responses: 31 | '200': 32 | description: '' 33 | schema: 34 | $ref: {{{get-output-schema}}} 35 | put: 36 | summary: Update a {{resource}} 37 | operationId: modify_{{resource}} 38 | tags: 39 | - '{{tag}}' 40 | parameters: 41 | - in: body 42 | name: body 43 | required: true 44 | schema: 45 | $ref: {{{update-input-schema}}} 46 | responses: 47 | '200': 48 | $ref: '#/responses/200:modified-with-count' 49 | '400': 50 | $ref: '#/responses/400:invalid-body-json' 51 | delete: 52 | summary: Delete a {{resource}} 53 | operationId: delete_{{resource}} 54 | {{#delete-description}} 55 | description: {{{.}}} 56 | {{/delete-description}} 57 | tags: 58 | - '{{tag}}' 59 | responses: 60 | '200': 61 | $ref: '#/responses/200:deleted-with-count' 62 | -------------------------------------------------------------------------------- /swagger/templates/tags-tag.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: resource 3 | type: string 4 | required: true 5 | - name: parameter 6 | type: string 7 | required: true 8 | - name: tag 9 | type: string 10 | required: true 11 | template: | 12 | parameters: 13 | - required: true 14 | type: string 15 | in: path 16 | name: '{{parameter}}' 17 | - required: true 18 | description: The tag to interact with 19 | type: string 20 | in: path 21 | name: TagValue 22 | get: 23 | summary: Get the value of a tag, by name. 24 | operationId: get_{{resource}}_tag 25 | tags: 26 | - '{{tag}}' 27 | responses: 28 | '200': 29 | description: Returns a single tag by name 30 | schema: 31 | $ref: schemas/output/tag.json 32 | examples: 33 | response: 34 | $ref: examples/output/tag.json 35 | put: 36 | summary: Rename a tag. 37 | operationId: rename_{{resource}}_tag 38 | tags: 39 | - '{{tag}}' 40 | parameters: 41 | - name: body 42 | in: body 43 | schema: 44 | $ref: schemas/input/tag.json 45 | responses: 46 | '200': 47 | $ref: '#/responses/200:modified-with-count' 48 | '400': 49 | $ref: '#/responses/400:invalid-body-json' 50 | delete: 51 | summary: Delete a tag 52 | operationId: delete_{{resource}}_tag 53 | tags: 54 | - '{{tag}}' 55 | responses: 56 | '200': 57 | $ref: '#/responses/200:modified-with-count' 58 | -------------------------------------------------------------------------------- /sample.config: -------------------------------------------------------------------------------- 1 | # vim: filetype=sh 2 | 3 | # RUNTIME vars are only consumed by bin/run.sh and meant for environment setup 4 | #SCITRAN_RUNTIME_HOST="127.0.0.1" 5 | #SCITRAN_RUNTIME_PORT="8080" 6 | #SCITRAN_RUNTIME_PATH="./runtime" 7 | #SCITRAN_RUNTIME_SSL_PEM="*" 8 | #SCITRAN_RUNTIME_BOOTSTRAP="bootstrap.json" 9 | 10 | #SCITRAN_CORE_ACCESS_LOG_ENABLED=false # user access logging toggle 11 | #SCITRAN_CORE_DEBUG=false # emit stack trace on error 12 | #SCITRAN_CORE_INSECURE=false # accept user name as query param 13 | #SCITRAN_CORE_LOG_LEVEL=debug 14 | #SCITRAN_CORE_DRONE_SECRET="" 15 | 16 | #SCITRAN_SITE_ID="" 17 | #SCITRAN_SITE_INACTIVITY_TIMEOUT=3600 18 | #SCITRAN_SITE_NAME="" 19 | #SCITRAN_SITE_URL="" 20 | #SCITRAN_SITE_API_URL="" 21 | #SCITRAN_SITE_CENTRAL_URL="" 22 | #SCITRAN_SITE_REGISTERED="" 23 | #SCITRAN_SITE_SSL_CERT="" 24 | 25 | #SCITRAN_QUEUE_MAX_RETRIES=3, 26 | #SCITRAN_QUEUE_RETRY_ON_FAIL=false 27 | 28 | #SCITRAN_PERSISTENT_PATH="./persistent" 29 | #SCITRAN_PERSISTENT_DATA_PATH="./persistent/data" # for fine-grain control 30 | #SCITRAN_PERSISTENT_DB_PATH="./persistent/db" # for fine-grain control 31 | #SCITRAN_PERSISTENT_DB_PORT=9001 32 | #SCITRAN_PERSISTENT_DB_URI="mongodb://localhost:$SCITRAN_PERSISTENT_DB_PORT/scitran" 33 | #SCITRAN_PERSISTENT_DB_LOG_URI="mongodb://localhost:$SCITRAN_PERSISTENT_DB_PORT/logs" 34 | #SCITRAN_PERSISTENT_DB_CONNECT_TIMEOUT=2000 35 | #SCITRAN_PERSISTENT_DB_SERVER_SELECTION_TIMEOUT=3000 36 | 37 | #SCITRAN_AUTH_AUTH_TYPE="" 38 | #SCITRAN_AUTH_AUTH_ENDPOINT="" 39 | #SCITRAN_AUTH_CLIENT_ID="" 40 | #SCITRAN_AUTH_ID_ENDPOINT="" 41 | #SCITRAN_AUTH_VERIFY_ENDPOINT="" 42 | -------------------------------------------------------------------------------- /swagger/templates/analysis-files.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: resource 3 | type: string 4 | - name: parameter 5 | type: string 6 | - name: tag 7 | type: string 8 | required: true 9 | - name: filegroup 10 | type: string 11 | required: true 12 | template: | 13 | parameters: 14 | {{#parameter}} 15 | - required: true 16 | type: string 17 | in: path 18 | name: '{{.}}' 19 | {{/parameter}} 20 | - required: true 21 | type: string 22 | in: path 23 | name: AnalysisId 24 | get: 25 | summary: Download analysis {{filegroup}}. 26 | description: > 27 | If "ticket" query param is included and not empty, download {{filegroup}}. 28 | 29 | If "ticket" query param is included and empty, create a ticket for all 30 | {{filegroup}} in the anlaysis 31 | 32 | If no "ticket" query param is included, server error 500 33 | {{#resource}} 34 | operationId: download_{{resource}}_analysis_{{filegroup}} 35 | {{/resource}} 36 | {{^resource}} 37 | operationId: download_analysis_{{filegroup}} 38 | {{/resource}} 39 | tags: 40 | - '{{tag}}' 41 | produces: 42 | - application/json 43 | - application/octet-stream 44 | parameters: 45 | - description: ticket id of the {{filegroup}} to download 46 | type: string 47 | in: query 48 | name: ticket 49 | responses: 50 | '200': 51 | description: '' 52 | schema: 53 | $ref: schemas/output/analysis-files-create-ticket.json 54 | examples: 55 | response: 56 | $ref: examples/output/analysis-files-create-ticket.json 57 | -------------------------------------------------------------------------------- /swagger/examples/output/project-list.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "group": "scitran", 3 | "created": "2016-09-22T21:51:53.151000+00:00", 4 | "modified": "2016-09-22T21:51:53.151000+00:00", 5 | "label": "Neuroscience", 6 | "_id": "57e452791cff88b85f9f9c97", 7 | "public": false, 8 | "permissions": [{ 9 | "access": "admin", 10 | "_id": "coltonlw@flywheel.io" 11 | }] 12 | }, { 13 | "group": "scitran", 14 | "created": "2016-09-22T21:52:27.576000+00:00", 15 | "modified": "2016-09-22T21:52:27.576000+00:00", 16 | "label": "Psychology", 17 | "_id": "57e4529b1cff88b85f9f9c98", 18 | "public": false, 19 | "permissions": [{ 20 | "access": "admin", 21 | "_id": "coltonlw@flywheel.io" 22 | }] 23 | }, { 24 | "group": "scitran", 25 | "created": "2016-09-22T21:52:52.539000+00:00", 26 | "modified": "2016-09-22T21:52:52.539000+00:00", 27 | "label": "Testdata", 28 | "_id": "57e452b41cff88b85f9f9c99", 29 | "public": false, 30 | "permissions": [{ 31 | "access": "admin", 32 | "_id": "coltonlw@flywheel.io" 33 | }] 34 | }, { 35 | "group": "test-group", 36 | "created": "2016-09-22T21:54:47.925000+00:00", 37 | "modified": "2016-09-22T21:54:47.925000+00:00", 38 | "label": "test-project", 39 | "description": "Test Project", 40 | "_id": "57e45327466d8e000e33a859", 41 | "public": false, 42 | "permissions": [{ 43 | "access": "admin", 44 | "_id": "coltonlw@flywheel.io" 45 | }] 46 | }, { 47 | "group": "scitran", 48 | "created": "2016-09-29T18:53:06.199000+00:00", 49 | "modified": "2016-09-29T18:53:06.199000+00:00", 50 | "label": "test2", 51 | "description": "Test Project Two", 52 | "_id": "57ed6312466d8e01c91ee427", 53 | "permissions": [{ 54 | "access": "admin", 55 | "_id": "coltonlw@flywheel.io" 56 | }] 57 | }] 58 | -------------------------------------------------------------------------------- /swagger/templates/analysis-item.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: resource 3 | type: string 4 | - name: parameter 5 | type: string 6 | - name: tag 7 | type: string 8 | required: true 9 | - name: supportsDelete 10 | type: boolean 11 | required: true 12 | template: | 13 | parameters: 14 | {{#parameter}} 15 | - required: true 16 | type: string 17 | in: path 18 | name: '{{.}}' 19 | {{/parameter}} 20 | - required: true 21 | type: string 22 | in: path 23 | name: AnalysisId 24 | get: 25 | summary: Get an analysis. 26 | {{#resource}} 27 | operationId: get_{{.}}_analysis 28 | {{/resource}} 29 | {{^resource}} 30 | operationId: get_analysis 31 | {{/resource}} 32 | tags: 33 | - '{{tag}}' 34 | parameters: 35 | - name: inflate_job 36 | in: query 37 | type: boolean 38 | description: Return job as an object instead of an id 39 | x-sdk-default: 'true' 40 | responses: 41 | '200': 42 | description: '' 43 | schema: 44 | $ref: schemas/output/analysis.json 45 | examples: 46 | response: 47 | $ref: examples/output/analysis.json 48 | {{#supportsDelete}} 49 | delete: 50 | summary: Delete an anaylsis 51 | {{#resource}} 52 | operationId: delete_{{.}}_analysis 53 | {{/resource}} 54 | {{^resource}} 55 | operationId: delete_analysis 56 | {{/resource}} 57 | tags: 58 | - '{{tag}}' 59 | responses: 60 | '200': 61 | $ref: '#/responses/200:deleted-with-count' 62 | {{/supportsDelete}} 63 | -------------------------------------------------------------------------------- /swagger/schemas/mongo/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "User", 4 | "type": "object", 5 | "properties": { 6 | "_id": { 7 | "title": "ID", 8 | "type": "string" 9 | }, 10 | "created": {}, 11 | "modified": {}, 12 | "firstname": { 13 | "title": "First Name", 14 | "type": "string" 15 | }, 16 | "lastname": { 17 | "title": "Last Name", 18 | "type": "string" 19 | }, 20 | "email": { 21 | "title": "Email", 22 | "type": "string" 23 | }, 24 | "avatars": { "$ref": "../definitions/avatars.json#/definitions/avatars"}, 25 | "avatar": { 26 | "title": "Avatar", 27 | "type": "string" 28 | }, 29 | "root": { "type": "boolean"}, 30 | "disabled": { "type": "boolean"}, 31 | "preferences": { 32 | "title": "Preferences", 33 | "type": "object" 34 | }, 35 | "api_key": { 36 | "type":"object", 37 | "properties":{ 38 | "key": {"type": "string"}, 39 | "created": {}, 40 | "last_used": {} 41 | }, 42 | "additionalProperties": false 43 | }, 44 | "wechat": {} 45 | }, 46 | "additionalProperties": false, 47 | "required":["_id", "firstname", "lastname", "created", "modified", "root"] 48 | } 49 | -------------------------------------------------------------------------------- /tests/unit_tests/python/test_gear_util.py: -------------------------------------------------------------------------------- 1 | 2 | import copy 3 | from api.jobs import gears 4 | 5 | # DISCUSS: this basically asserts that the log helper doesn't throw, which is of non-zero but questionable value. 6 | # Could instead be marked for pytest et. al to ignore coverage? Desirability? Compatibility? 7 | def test_fill_defaults(): 8 | 9 | gear_config = { 10 | 'key_one': {'default': 1}, 11 | 'key_two': {'default': 2}, 12 | 'key_three': {'default': 3}, 13 | 'key_no_de': {} 14 | } 15 | 16 | gear = { 17 | 'gear': { 18 | 'config': gear_config 19 | } 20 | } 21 | 22 | # test sending in complete config does not change 23 | config = { 24 | 'key_one': 4, 25 | 'key_two': 5, 26 | 'key_three': 6 27 | } 28 | 29 | result = gears.fill_gear_default_values(gear, config) 30 | assert result['key_one'] == 4 31 | assert result['key_two'] == 5 32 | assert result['key_three'] == 6 33 | 34 | # test sending in empty config 35 | result = gears.fill_gear_default_values(gear, {}) 36 | assert result['key_one'] == 1 37 | assert result['key_two'] == 2 38 | assert result['key_three'] == 3 39 | 40 | # test sending in None config 41 | result = gears.fill_gear_default_values(gear, None) 42 | assert result['key_one'] == 1 43 | assert result['key_two'] == 2 44 | assert result['key_three'] == 3 45 | 46 | # test sending in semi-complete config 47 | config = { 48 | 'key_one': None, 49 | 'key_two': [] 50 | #'key_three': 6 # missing 51 | } 52 | 53 | result = gears.fill_gear_default_values(gear, config) 54 | assert result['key_one'] == None 55 | assert result['key_two'] == [] 56 | assert result['key_three'] == 3 57 | -------------------------------------------------------------------------------- /swagger/templates/analyses-list.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: resource 3 | type: string 4 | required: true 5 | - name: parameter 6 | type: string 7 | required: true 8 | - name: tag 9 | type: string 10 | required: true 11 | - name: allowCreate 12 | type: boolean 13 | required: false 14 | template: | 15 | parameters: 16 | - required: true 17 | type: string 18 | in: path 19 | name: '{{parameter}}' 20 | get: 21 | summary: Get analyses for {{resource}}. 22 | description: Returns analyses that directly belong to this resource. 23 | operationId: get_{{resource}}_analyses 24 | tags: 25 | - '{{tag}}' 26 | responses: 27 | '200': 28 | description: The list of analyses 29 | schema: 30 | $ref: schemas/output/analyses-list.json 31 | 32 | {{#allowCreate}} 33 | post: 34 | summary: Create an analysis and upload files. 35 | description: | 36 | When query param "job" is "true", send JSON to create 37 | an analysis and job. Otherwise, multipart/form-data 38 | to upload files and create an analysis. 39 | operationId: add_{{resource}}_analysis 40 | tags: 41 | - '{{tag}}' 42 | consumes: 43 | - application/json 44 | - multipart/form-data 45 | parameters: 46 | - in: body 47 | name: body 48 | required: true 49 | schema: 50 | $ref: schemas/input/analysis.json 51 | - name: job 52 | in: query 53 | type: boolean 54 | description: Return job as an object instead of an id 55 | x-sdk-default: 'true' 56 | responses: 57 | '200': 58 | description: Returns the id of the analysis that was created. 59 | schema: 60 | $ref: schemas/output/container-new.json 61 | {{/allowCreate}} 62 | -------------------------------------------------------------------------------- /swagger/paths/upload-by-reaper.yaml: -------------------------------------------------------------------------------- 1 | /upload/reaper: 2 | post: 3 | summary: Bottom-up UID matching of Multipart form upload with N file fields, each with their desired filename. 4 | description: | 5 | Upload data, allowing users to move sessions during scans without causing new data to be 6 | created in referenced project/group. 7 | 8 | 9 | ### Evaluation Order: 10 | 11 | * If a matching acquisition UID is found anywhere on the system, the related files will be placed under that acquisition. 12 | * **OR** If a matching session UID is found, a new acquistion is created with the specified UID under that Session UID. 13 | * **OR** If a matching group ID and project label are found, a new session and acquisition will be created within that project 14 | * **OR** If a matching group ID is found, a new project and session and acquisition will be created within that group. 15 | * **OR** A new session and acquisition will be created within a special "Unknown" group and project, which is only visible to system administrators. 16 | 17 | operationId: upload_by_reaper 18 | tags: 19 | - files 20 | responses: 21 | '200': 22 | description: 'Files uploaded successfully' 23 | schema: 24 | $ref: schemas/output/file-list.json 25 | examples: 26 | application/json: 27 | $ref: examples/file_info_list.json 28 | '402': 29 | description: Uploads must be from an authorized drone 30 | consumes: 31 | - multipart/form-data 32 | parameters: 33 | # TODO: Need to add ref to json input schema. Proper way not yet defined for Multipart form uploads. 34 | # See api/schemas/input/uidupload.json for the format of this metadata. 35 | - in: formData 36 | name: formData 37 | type: string -------------------------------------------------------------------------------- /api/web/encoder.py: -------------------------------------------------------------------------------- 1 | from pymongo.cursor import Cursor 2 | import bson.objectid 3 | import datetime 4 | import json 5 | import pytz 6 | 7 | from ..jobs.jobs import Job 8 | 9 | def custom_json_serializer(obj): 10 | if isinstance(obj, bson.objectid.ObjectId): 11 | return str(obj) 12 | elif isinstance(obj, datetime.datetime): 13 | return pytz.timezone('UTC').localize(obj).isoformat() 14 | elif isinstance(obj, Job): 15 | return obj.map() 16 | elif isinstance(obj, Cursor): 17 | return list(obj) 18 | raise TypeError(repr(obj) + " is not JSON serializable") 19 | 20 | 21 | def sse_pack(d): 22 | """ 23 | Format a map with Server-Sent-Event-meaningful keys into a string for transport. 24 | 25 | Happily borrowed from: http://taoofmac.com/space/blog/2014/11/16/1940 26 | For reading on web usage: http://www.html5rocks.com/en/tutorials/eventsource/basics 27 | For reading on the format: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format 28 | """ 29 | 30 | buffer_ = '' 31 | 32 | for k in ['retry', 'id', 'event', 'data']: 33 | if k in d.keys(): 34 | buffer_ += '%s: %s\n' % (k, d[k]) 35 | 36 | return buffer_ + '\n' 37 | 38 | def json_sse_pack(d): 39 | """ 40 | Variant of sse_pack that will json-encode your data blob. 41 | """ 42 | 43 | d['data'] = json.dumps(d['data'], default=custom_json_serializer) 44 | 45 | return sse_pack(d) 46 | 47 | def pseudo_consistent_json_encode(d): 48 | """ 49 | Some parts of our system rely upon consistently-produced JSON encoding. 50 | This implementation is not guaranteed to be consistent, but it's good enough for now. 51 | """ 52 | 53 | return json.dumps(d, sort_keys=True, indent=4, separators=(',', ': ')) + '\n' 54 | -------------------------------------------------------------------------------- /swagger/paths/engine.yaml: -------------------------------------------------------------------------------- 1 | /engine: 2 | post: 3 | summary: Upload a list of file fields. 4 | description: | 5 | ### Default behavior: 6 | >Uploads a list of file fields sent as file1, file2, etc to an existing 7 | container and updates fields of the files, the container and it's 8 | parents as specified in the metadata fileformfield using the 9 | engine placer class 10 | 11 | ### When ``level`` is ``analysis``: 12 | > Uploads a list of files to an existing analysis object, marking 13 | all files as ``output=true`` using the job-based analyses placer 14 | class. See schemas/input/analysis.json 15 | operationId: engine_upload 16 | responses: 17 | '200': 18 | description: A list of FileInfo objects 19 | schema: 20 | example: 21 | $ref: examples/file_info_list.json 22 | parameters: 23 | - in: body 24 | name: body 25 | description: > 26 | Object encoded as a JSON string. 27 | 28 | By default JSON must match the specified enginemetadata.json schema 29 | 30 | If ``level`` is ``analysis``, JSON must match AnalysisUploadMetadata schema 31 | schema: 32 | $ref: schemas/input/enginemetadata.json 33 | - required: true 34 | description: Which level to store files in 35 | enum: 36 | - project 37 | - session 38 | - acquisition 39 | - analysis 40 | type: string 41 | in: query 42 | name: level 43 | - required: true 44 | description: The ID of the container to place files in 45 | type: string 46 | in: query 47 | name: id 48 | - description: Required if ``level`` is ``analysis`` 49 | type: string 50 | in: query 51 | name: job 52 | required: true -------------------------------------------------------------------------------- /api/handlers/resolvehandler.py: -------------------------------------------------------------------------------- 1 | """ 2 | API request handlers for the jobs module 3 | """ 4 | 5 | from ..web import base 6 | from ..resolver import Resolver 7 | 8 | class ResolveHandler(base.RequestHandler): 9 | 10 | """Provide /resolve API route.""" 11 | 12 | def resolve(self): 13 | """Resolve a path through the hierarchy.""" 14 | 15 | if self.public_request: 16 | self.abort(403, 'Request requires login') 17 | 18 | doc = self.request.json 19 | result = Resolver.resolve(doc['path']) 20 | 21 | # Cancel the request if anything in the path is unauthorized; remove any children that are unauthorized. 22 | if not self.superuser_request: 23 | for x in result["path"]: 24 | ok = False 25 | if x['node_type'] in ['acquisition', 'session', 'project', 'group']: 26 | perms = x.get('permissions', []) 27 | for y in perms: 28 | if y.get('_id') == self.uid: 29 | ok = True 30 | break 31 | 32 | if not ok: 33 | self.abort(403, "Not authorized") 34 | 35 | filtered_children = [] 36 | for x in result["children"]: 37 | ok = False 38 | if x['node_type'] in ['acquisition', 'session', 'project', 'group']: 39 | perms = x.get('permissions', []) 40 | for y in perms: 41 | if y.get('_id') == self.uid: 42 | ok = True 43 | break 44 | else: 45 | ok = True 46 | 47 | if ok: 48 | filtered_children.append(x) 49 | 50 | result["children"] = filtered_children 51 | 52 | return result 53 | -------------------------------------------------------------------------------- /tests/integration_tests/python/test_queue.py: -------------------------------------------------------------------------------- 1 | 2 | def test_queue_search(data_builder, default_payload, as_admin, file_form): 3 | 4 | # Dupe of test_jobs.py 5 | gear_doc = default_payload['gear']['gear'] 6 | gear_doc['inputs'] = { 7 | 'dicom': { 8 | 'base': 'file' 9 | } 10 | } 11 | gear = data_builder.create_gear(gear=gear_doc, category='utility') 12 | project = data_builder.create_project() 13 | session = data_builder.create_session() 14 | acquisition = data_builder.create_acquisition() 15 | assert as_admin.post('/acquisitions/' + acquisition + '/files', files=file_form('test.zip')).ok 16 | 17 | job_data = { 18 | 'gear_id': gear, 19 | 'inputs': { 20 | 'dicom': { 21 | 'type': 'acquisition', 22 | 'id': acquisition, 23 | 'name': 'test.zip' 24 | } 25 | }, 26 | 'config': { 'two-digit multiple of ten': 20 }, 27 | 'destination': { 28 | 'type': 'acquisition', 29 | 'id': acquisition 30 | }, 31 | 'tags': [ 'test-tag' ] 32 | } 33 | 34 | r = as_admin.post('/jobs/add', json=job_data) 35 | assert r.ok 36 | utility_id = r.json()['_id'] 37 | 38 | r = as_admin.get('/sessions/' + session + '/jobs?join=gears') 39 | assert r.ok 40 | assert(any(x['id'] == utility_id for x in r.json()['jobs'])) 41 | 42 | ana_gear_id = data_builder.create_gear(gear=gear_doc, category='analysis') 43 | job_data['gear_id'] = ana_gear_id 44 | 45 | r = as_admin.post('/jobs/add', json=job_data) 46 | assert r.ok 47 | ana_id = r.json()['_id'] 48 | 49 | 50 | r = as_admin.get('/sessions/' + session + '/jobs?join=gears') 51 | assert r.ok 52 | assert(any(x['id'] == utility_id for x in r.json()['jobs'])) 53 | assert(any(x['id'] == ana_id for x in r.json()['jobs'])) 54 | -------------------------------------------------------------------------------- /swagger/paths/download.yaml: -------------------------------------------------------------------------------- 1 | /download: 2 | post: 3 | summary: Create a download ticket 4 | description: | 5 | Use filters in the payload to exclude/include files. 6 | To pass a single filter, each of its conditions should be satisfied. 7 | If a file pass at least one filter, it is included in the targets. 8 | operationId: create_download_ticket 9 | tags: 10 | - files 11 | responses: 12 | '200': 13 | description: '' 14 | schema: 15 | example: 16 | ticket: 579e97738120be2ada087feb 17 | file_cnt: 3 18 | size: 64523904 19 | parameters: 20 | - in: query 21 | type: string 22 | name: prefix 23 | description: | 24 | A string to customize the name of the download 25 | in the format _.tar.gz. 26 | Defaults to "scitran". 27 | - in: body 28 | name: body 29 | schema: 30 | $ref: schemas/input/download.json 31 | description: Download files with tag 'incomplete' OR type 'dicom' 32 | get: 33 | summary: Download files listed in the given ticket. 34 | description: | 35 | You can use POST to create a download ticket 36 | The files listed in the ticket are put into a tar archive, 37 | which is then compressed with gzip (.tar.gz) 38 | operationId: download_ticket 39 | tags: 40 | - files 41 | parameters: 42 | - required: true 43 | description: ID of the download ticket 44 | type: string 45 | in: query 46 | name: ticket 47 | produces: 48 | - application/octet-stream 49 | responses: 50 | '200': 51 | description: The requested tarball download as a binary stream 52 | '400': 53 | description: Ticket not for this source IP 54 | '404': 55 | description: No such ticket 56 | -------------------------------------------------------------------------------- /swagger/templates/analysis-files-create-ticket-filename.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: resource 3 | type: string 4 | - name: parameter 5 | type: string 6 | - name: tag 7 | type: string 8 | required: true 9 | - name: filegroup 10 | type: string 11 | required: true 12 | template: | 13 | parameters: 14 | {{#parameter}} 15 | - required: true 16 | type: string 17 | in: path 18 | name: '{{.}}' 19 | {{/parameter}} 20 | - required: true 21 | type: string 22 | in: path 23 | name: AnalysisId 24 | - required: true 25 | type: string 26 | in: path 27 | description: regex to select {{filegroup}} for download 28 | name: Filename 29 | get: 30 | summary: Download anaylsis {{filegroup}} with filter. 31 | description: > 32 | If "ticket" query param is included and not empty, download {{filegroup}}. 33 | 34 | If "ticket" query param is included and empty, create a ticket for matching 35 | {{filegroup}} in the anlaysis. 36 | 37 | If no "ticket" query param is included, {{filegroup}} will be downloaded directly. 38 | {{#resource}} 39 | operationId: download_{{.}}_analysis_{{filegroup}}_by_filename 40 | {{/resource}} 41 | {{^resource}} 42 | operationId: download_analysis_{{filegroup}}_by_filename 43 | {{/resource}} 44 | tags: 45 | - '{{tag}}' 46 | produces: 47 | - application/json 48 | - application/octet-stream 49 | parameters: 50 | - description: ticket id of the {{filegroup}} to download 51 | type: string 52 | in: query 53 | name: ticket 54 | responses: 55 | '200': 56 | description: '' 57 | schema: 58 | $ref: schemas/output/analysis-files-create-ticket.json 59 | examples: 60 | response: 61 | $ref: examples/output/analysis-files-create-ticket.json 62 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | 4 | services: 5 | - mongodb 6 | env: 7 | global: 8 | secure: HELJx6WPr+W2S0FV47KkRdlS9NCqlMcdRMK8xWgRqqrEPv24KEvNnHxCy0tRbzITqadYtyvI1MtqtmpG04uty8Gpkc7w6L6LMJ/OuLG0gVX7AnaUovYTlY04m1/L9oyzOrTDXk5J/BKbcyiz7uJtkTc/A8MMZAFfZh7hmhLID78= # BUILD_TRIGGER_URL 9 | 10 | install: 11 | - bin/install-ubuntu.sh 12 | - tests/bin/setup-integration-tests-ubuntu.sh 13 | 14 | script: 15 | - SCITRAN_PERSISTENT_DB_PORT=27017 tests/bin/run-tests-ubuntu.sh 16 | 17 | after_success: 18 | - if [ "$TRAVIS_EVENT_TYPE" == "push" -o "$TRAVIS_TAG" ]; then 19 | SSH_KEY_FILE=$(mktemp -p $HOME/.ssh/); 20 | 21 | openssl aes-256-cbc -K $encrypted_55750ae1fbc7_key -iv $encrypted_55750ae1fbc7_iv -in .github_deploy_key.enc -out "$SSH_KEY_FILE" -d; 22 | 23 | chmod 600 "$SSH_KEY_FILE" && printf "%s\n" \ 24 | "Host github.com" \ 25 | " IdentityFile $SSH_KEY_FILE" \ 26 | " LogLevel ERROR" >> ~/.ssh/config; 27 | 28 | git config --global user.email "travis@travis-ci.org"; 29 | git config --global user.name "Travis CI"; 30 | git config --global push.default simple; 31 | fi 32 | - if [ "$TRAVIS_BRANCH" == "master" -o "$TRAVIS_EVENT_TYPE" == "pull_request" ]; then 33 | bash <(curl -s https://codecov.io/bash) -cF python; 34 | fi 35 | - if [ "$TRAVIS_TAG" ]; then 36 | ./docker/build-trigger.sh Tag "$TRAVIS_TAG" "$BUILD_TRIGGER_URL"; 37 | ./bin/push-docs.sh "$GIT_REMOTE" tags "$TRAVIS_TAG" "Travis Core Docs Build - ${TRAVIS_BUILD_NUMBER}"; 38 | fi 39 | - if [ "$TRAVIS_EVENT_TYPE" == "push" -a "$TRAVIS_BRANCH" == "master" ]; then 40 | ./docker/build-trigger.sh Branch "$TRAVIS_BRANCH" "$BUILD_TRIGGER_URL"; 41 | fi 42 | - if [ "$TRAVIS_EVENT_TYPE" == "push" -a -z "$TRAVIS_TAG" ]; then 43 | ./bin/push-docs.sh "$GIT_REMOTE" branches "$TRAVIS_BRANCH" "Travis Core Docs Build - ${TRAVIS_BUILD_NUMBER}"; 44 | fi 45 | 46 | -------------------------------------------------------------------------------- /api/web/errors.py: -------------------------------------------------------------------------------- 1 | 2 | class APIAuthProviderException(Exception): 3 | pass 4 | 5 | # Creating mulitple containers with same id 6 | class APIConflictException(Exception): 7 | pass 8 | 9 | # For checking database consistency 10 | class APIConsistencyException(Exception): 11 | pass 12 | 13 | # API could not find what was requested 14 | class APINotFoundException(Exception): 15 | pass 16 | 17 | class APIPermissionException(Exception): 18 | def __init__(self, msg, errors=None): 19 | 20 | super(APIPermissionException, self).__init__(msg) 21 | self.errors = errors 22 | 23 | class APIRefreshTokenException(Exception): 24 | # Specifically alert a client when the user's refresh token expires 25 | # Requires client to ask for `offline=true` permission to receive a new one 26 | def __init__(self, msg): 27 | 28 | super(APIRefreshTokenException, self).__init__(msg) 29 | self.errors = {'core_status_code': 'invalid_refresh_token'} 30 | 31 | class APIReportException(Exception): 32 | pass 33 | 34 | # Invalid or missing parameters for a report request 35 | class APIReportParamsException(Exception): 36 | pass 37 | 38 | class APIStorageException(Exception): 39 | pass 40 | 41 | # User Id not found or disabled 42 | class APIUnknownUserException(Exception): 43 | pass 44 | 45 | class APIValidationException(Exception): 46 | def __init__(self, errors): 47 | 48 | super(APIValidationException, self).__init__('Validation Error.') 49 | self.errors = errors 50 | 51 | # Payload for a POST or PUT does not match mongo json schema 52 | class DBValidationException(Exception): 53 | pass 54 | 55 | class FileStoreException(Exception): 56 | pass 57 | 58 | # File Form for upload requests made by client is incorrect 59 | class FileFormException(Exception): 60 | pass 61 | 62 | # Payload for a POST or PUT does not match input json schema 63 | class InputValidationException(Exception): 64 | pass 65 | 66 | -------------------------------------------------------------------------------- /api/auth/userauth.py: -------------------------------------------------------------------------------- 1 | def default(handler, user=None): 2 | def g(exec_op): 3 | def f(method, _id=None, query=None, payload=None, projection=None): 4 | if handler.public_request: 5 | handler.abort(403, 'public request is not authorized') 6 | elif handler.superuser_request and not (method == 'DELETE' and _id == handler.uid): 7 | pass 8 | elif handler.user_is_admin and (method == 'DELETE' and not _id == handler.uid): 9 | pass 10 | elif method == 'PUT' and handler.uid == _id: 11 | if 'root' in payload and payload['root'] != user['root']: 12 | handler.abort(400, 'user cannot alter own admin privilege') 13 | elif 'disabled' in payload and payload['disabled'] != user.get('disabled'): 14 | handler.abort(400, 'user cannot alter own disabled status') 15 | else: 16 | pass 17 | elif method == 'PUT' and handler.user_is_admin: 18 | pass 19 | elif method == 'POST' and not handler.superuser_request and not handler.user_is_admin: 20 | handler.abort(403, 'only admins are allowed to create users') 21 | elif method == 'POST' and (handler.superuser_request or handler.user_is_admin): 22 | pass 23 | elif method == 'GET': 24 | pass 25 | else: 26 | handler.abort(403, 'not allowed to perform operation') 27 | return exec_op(method, _id=_id, query=query, payload=payload, projection=projection) 28 | return f 29 | return g 30 | 31 | def list_permission_checker(handler): 32 | def g(exec_op): 33 | def f(method, query=None, projection=None): 34 | if handler.public_request: 35 | handler.abort(403, 'public request is not authorized') 36 | return exec_op(method, query=query, projection=projection) 37 | return f 38 | return g 39 | -------------------------------------------------------------------------------- /tests/unit_tests/python/test_db_upgrade.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import sys 3 | 4 | from mock import Mock, patch 5 | import pytest 6 | 7 | bin_path = os.path.join(os.getcwd(), "bin") 8 | sys.path.insert(0, bin_path) 9 | import database 10 | 11 | from api import config 12 | 13 | CDV = database.CURRENT_DATABASE_VERSION 14 | 15 | def test_all_upgrade_scripts_exist(): 16 | for i in range(1, CDV): 17 | script_name = 'upgrade_to_{}'.format(i) 18 | assert hasattr(database, script_name) 19 | 20 | def test_CDV_was_bumped(): 21 | script_name = 'upgrade_to_{}'.format(CDV+1) 22 | assert hasattr(database, script_name) is False 23 | 24 | 25 | @patch('api.config.get_version', Mock(return_value={'database': 5})) 26 | def test_get_db_version_from_config(): 27 | assert database.get_db_version() == 5 28 | 29 | 30 | @pytest.fixture(scope='function') 31 | def database_mock_setup(): 32 | setattr(config.db.singletons, 'update_one', Mock()) 33 | for i in range(1, CDV): 34 | script_name = 'upgrade_to_{}'.format(i) 35 | setattr(database, script_name, Mock()) 36 | 37 | @patch('database.get_db_version', Mock(return_value=0)) 38 | def test_all_upgrade_scripts_ran(database_mock_setup): 39 | with pytest.raises(SystemExit): 40 | database.upgrade_schema() 41 | for i in range(1, CDV): 42 | script_name = 'upgrade_to_{}'.format(i) 43 | assert getattr(database, script_name).called 44 | 45 | @patch('database.get_db_version', Mock(return_value=CDV-4)) 46 | def test_necessary_upgrade_scripts_ran(database_mock_setup): 47 | with pytest.raises(SystemExit): 48 | database.upgrade_schema() 49 | # Assert the necessary scripts were called 50 | for i in range(CDV-3, CDV): 51 | script_name = 'upgrade_to_{}'.format(i) 52 | assert getattr(database, script_name).called 53 | # But not the scripts before it 54 | for i in range(1, CDV-4): 55 | script_name = 'upgrade_to_{}'.format(i) 56 | assert getattr(database, script_name).called is False 57 | -------------------------------------------------------------------------------- /tests/integration_tests/python/test_notes.py: -------------------------------------------------------------------------------- 1 | def test_notes(data_builder, as_admin): 2 | project = data_builder.create_project() 3 | 4 | # Add a note 5 | note_text = 'test note' 6 | r = as_admin.post('/projects/' + project + '/notes', json={'text': note_text}) 7 | assert r.ok 8 | 9 | # Verify note is present in project 10 | r = as_admin.get('/projects/' + project) 11 | assert r.ok 12 | assert len(r.json()['notes']) == 1 13 | note = r.json()['notes'][0]['_id'] 14 | 15 | r = as_admin.get('/projects/' + project + '/notes/' + note) 16 | assert r.ok 17 | assert r.json()['text'] == note_text 18 | 19 | # Modify note 20 | note_text_2 = 'modified note' 21 | r = as_admin.put('/projects/' + project + '/notes/' + note, json={'text': note_text_2}) 22 | assert r.ok 23 | 24 | # Verify modified note 25 | r = as_admin.get('/projects/' + project + '/notes/' + note) 26 | assert r.ok 27 | assert r.json()['text'] == note_text_2 28 | 29 | # Delete note 30 | r = as_admin.delete('/projects/' + project + '/notes/' + note) 31 | assert r.ok 32 | 33 | r = as_admin.get('/projects/' + project + '/notes/' + note) 34 | assert r.status_code == 404 35 | 36 | 37 | def test_analysis_notes(data_builder, file_form, as_admin): 38 | acquisition = data_builder.create_acquisition() 39 | 40 | # create acquisition analysis 41 | file_name = 'one.csv' 42 | r = as_admin.post('/acquisitions/' + acquisition + '/analyses', files=file_form( 43 | file_name, meta={'label': 'test analysis', 'inputs': [{'name': file_name}]})) 44 | assert r.ok 45 | analysis = r.json()['_id'] 46 | 47 | # create analysis note 48 | note_text = 'test note' 49 | r = as_admin.post('/acquisitions/' + acquisition + '/analyses/' + analysis + '/notes', json={ 50 | 'text': note_text 51 | }) 52 | assert r.ok 53 | 54 | # delete acquisition analysis 55 | r = as_admin.delete('/acquisitions/' + acquisition + '/analyses/' + analysis) 56 | assert r.ok 57 | -------------------------------------------------------------------------------- /api/auth/groupauth.py: -------------------------------------------------------------------------------- 1 | from . import _get_access, INTEGER_PERMISSIONS 2 | from .. import config 3 | 4 | log = config.log 5 | 6 | 7 | def default(handler, group=None): 8 | def g(exec_op): 9 | def f(method, _id=None, query=None, payload=None, projection=None): 10 | if handler.superuser_request: 11 | pass 12 | elif handler.public_request: 13 | handler.abort(400, 'public request is not valid') 14 | elif handler.user_is_admin: 15 | pass 16 | elif method in ['DELETE', 'POST']: 17 | handler.abort(403, 'not allowed to perform operation') 18 | elif _get_access(handler.uid, group) >= INTEGER_PERMISSIONS['admin']: 19 | pass 20 | elif method == 'GET' and _get_access(handler.uid, group) >= INTEGER_PERMISSIONS['ro']: 21 | pass 22 | else: 23 | handler.abort(403, 'not allowed to perform operation') 24 | return exec_op(method, _id=_id, query=query, payload=payload, projection=projection) 25 | return f 26 | return g 27 | 28 | def list_permission_checker(handler, uid=None): 29 | def g(exec_op): 30 | def f(method, query=None, projection=None): 31 | if uid is not None: 32 | if uid != handler.uid and not handler.superuser_request and not handler.user_is_admin: 33 | handler.abort(403, 'User ' + handler.uid + ' may not see the Groups of User ' + uid) 34 | query = query or {} 35 | query['permissions._id'] = uid 36 | projection = projection or {} 37 | projection['permissions.$'] = 1 38 | else: 39 | if not handler.superuser_request: 40 | query = query or {} 41 | projection = projection or {} 42 | query['permissions._id'] = handler.uid 43 | 44 | return exec_op(method, query=query, projection=projection) 45 | return f 46 | return g 47 | -------------------------------------------------------------------------------- /swagger/examples/input/project-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "session": { 3 | "schema": { 4 | "$schema": "http://json-schema.org/draft-04/schema#", 5 | "properties": { 6 | "label": { 7 | "type": "string", 8 | "pattern": "^test_pattern$" } 9 | } 10 | } 11 | }, 12 | "acquisitions": [ 13 | { 14 | "schema": { 15 | "$schema": "http://json-schema.org/draft-04/schema#", 16 | "type": "object", 17 | "properties": { 18 | "measurement": { 19 | "type": "string", 20 | "pattern": "^[aA]natomical$" } 21 | }, 22 | "required": ["measurement"] 23 | }, 24 | "minimum": 2 25 | }, 26 | { 27 | "schema": { 28 | "$schema": "http://json-schema.org/draft-04/schema#", 29 | "type": "object", 30 | "properties": { 31 | "measurement": { 32 | "type": "string", 33 | "pattern": "^functional$" } 34 | }, 35 | "required": ["measurement"] 36 | }, 37 | "minimum": 1 38 | }, 39 | { 40 | "schema": { 41 | "$schema": "http://json-schema.org/draft-04/schema#", 42 | "type": "object", 43 | "properties": { 44 | "measurement": { "enum": ["Localizer"] }, 45 | "label": { 46 | "type": "string", 47 | "pattern": "t1" 48 | } 49 | }, 50 | "required": ["label", "measurement"] 51 | }, 52 | "minimum": 1 53 | } 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Examples 4 | Following the examples below will server up scitran/core with uwsgi on port 8080 5 | with auto-reload enabled. This will not utilize HTTPS, thus is meant only for 6 | development. 7 | 8 | The below examples do not account for complexities of docker volumes, and 9 | preserving their contents across container instances. 10 | 11 | 12 | ``` 13 | # Build Example: 14 | docker build -t scitran-core . 15 | 16 | # Run Example: 17 | # First start mongodb 18 | docker run --name some-mongo -d mongo 19 | 20 | # Then startup scitran-core, attaching to linked mongo. 21 | docker run \ 22 | --name scitran-core \ 23 | -e "SCITRAN_PERSISTENT_DB_URI=mongodb://some-mongo:27017/scitran" \ 24 | -e "SCITRAN_CORE_INSECURE=true" \ 25 | -e "SCITRAN_CORE_DRONE_SECRET=change-me" \ 26 | -e "SCITRAN_SITE_API_URL=http://localhost:8080/api" \ 27 | -v $(pwd)/persistent/data:/var/scitran/data \ 28 | -v $(pwd):/var/scitran/code/api \ 29 | --link some-mongo \ 30 | -p 0.0.0.0:8080:8080 \ 31 | scitran-core \ 32 | uwsgi \ 33 | --ini /var/scitran/config/uwsgi-config.ini \ 34 | --http 0.0.0.0:8080 \ 35 | --http-keepalive \ 36 | --python-autoreload 1 37 | 38 | 39 | # Bootstrap Account Example: 40 | docker run \ 41 | -e "SCITRAN_SITE_API_URL=http://scitran-core:8080/api" \ 42 | -e "SCITRAN_CORE_DRONE_SECRET=change-me" \ 43 | --link scitran-core \ 44 | --rm \ 45 | -v /dev/bali.prod/docker/uwsgi/bootstrap-dev.json:/accounts.json \ 46 | scitran-core \ 47 | /var/scitran/code/api/docker/bootstrap-accounts.sh \ 48 | /accounts.json 49 | 50 | 51 | # Bootstrap Data Example: 52 | docker run \ 53 | -e "SCITRAN_SITE_API_URL=http://scitran-core:8080/api" \ 54 | -e "SCITRAN_CORE_DRONE_SECRET=change-me" \ 55 | -e "PRE_RUNAS_CMD=/var/scitran/code/api/docker/bootstrap-data.sh" \ 56 | --link scitran-core \ 57 | --volumes-from scitran-core \ 58 | --rm \ 59 | scitran-core \ 60 | echo "Data bootstrap complete." 61 | ``` 62 | -------------------------------------------------------------------------------- /swagger/schemas/definitions/container.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "definitions": { 4 | "_id": {"type": "string"}, 5 | "public": {"type": "boolean"}, 6 | "info": {"$ref": "common.json#/definitions/info"}, 7 | "info_exists": {"type": "boolean"}, 8 | "uid": {"type": "string"}, 9 | "timestamp": {"type": ["string", "null"], "format": "date-time"}, 10 | "timezone": {"type": "string"}, 11 | "container-type": { 12 | "type": "string", 13 | "enum": ["group", "project", "session", "acquisition", "collection", "analysis"], 14 | "description": "The type of container (e.g. session)" 15 | }, 16 | 17 | "container-new-output": { 18 | "type": "object", 19 | "properties": { 20 | "_id": {"$ref":"#/definitions/_id"} 21 | }, 22 | "required": ["_id"], 23 | "x-sdk-return": "_id" 24 | }, 25 | "container-reference": { 26 | "type": "object", 27 | "properties": { 28 | "type": {"$ref":"#/definitions/container-type"}, 29 | "id": {"$ref":"#/definitions/_id"} 30 | }, 31 | "required": [ "type", "id" ], 32 | "additionalProperties":false, 33 | "description": "A reference to an individual container, by type and id" 34 | }, 35 | "container-output-with-files": { 36 | "type": "object", 37 | "properties": { 38 | "_id": {"$ref":"#/definitions/_id"}, 39 | "label": {"$ref":"common.json#/definitions/label"}, 40 | "files":{ 41 | "type":"array", 42 | "items":{"$ref":"file.json#/definitions/file-output"} 43 | }, 44 | "created": {"$ref":"created-modified.json#/definitions/created"}, 45 | "modified": {"$ref":"created-modified.json#/definitions/modified"} 46 | }, 47 | "description": "Generic container output with files" 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /swagger/schemas/definitions/rule.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "definitions": { 4 | "rule-items": { 5 | "type": "array", 6 | "items": { 7 | "type": "object", 8 | "properties": { 9 | "type": { 10 | "type": "string", 11 | "enum": [ 12 | "file.type", 13 | "file.name", 14 | "file.measurements", 15 | "container.has-type", 16 | "container.has-measurement" 17 | ] 18 | }, 19 | "value": { "type": "string" }, 20 | "regex": { "type": "boolean" } 21 | }, 22 | "required": [ "type", "value" ], 23 | "additionalProperties": false 24 | } 25 | }, 26 | 27 | "rule-input": { 28 | "type": "object", 29 | "properties": { 30 | "_id": { "type": "string" }, 31 | "project_id": { "type": "string" }, 32 | "alg": { "type": "string" }, 33 | "name": { "type": "string" }, 34 | "any": { "$ref": "#/definitions/rule-items" }, 35 | "all": { "$ref": "#/definitions/rule-items" }, 36 | "disabled": { "type": "boolean" } 37 | }, 38 | "additionalProperties": false, 39 | "x-sdk-model": "rule" 40 | }, 41 | 42 | "rule-output": { 43 | "type": "object", 44 | "properties": { 45 | "_id": { "type": "string" }, 46 | "alg": { "type": "string" }, 47 | "name": { "type": "string" }, 48 | "any": { "$ref": "#/definitions/rule-items" }, 49 | "all": { "$ref": "#/definitions/rule-items" }, 50 | "disabled": { "type": "boolean" } 51 | }, 52 | "x-sdk-model": "rule" 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /bin/oneoffs/cas_statistic.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pymongo 3 | from collections import Counter 4 | 5 | db_uri = os.getenv('SCITRAN_PERSISTENT_DB_URI', 'localhost:9001') 6 | db = pymongo.MongoClient(db_uri).get_database('scitran') 7 | 8 | COLLECTIONS = ['projects', 'acquisitions', 'analyses'] 9 | COLLECTIONS_WITH_EMBEDDED = [('sessions', 'subject')] 10 | 11 | 12 | def files_of_collection(collection, embedded_doc=None): 13 | hash_size_pairs = [] 14 | cursor = db.get_collection(collection).find({}) 15 | for document in cursor: 16 | hash_size_pairs += files_of_document(document) 17 | if embedded_doc: 18 | hash_size_pairs += files_of_document(document.get(embedded_doc, {})) 19 | 20 | return hash_size_pairs 21 | 22 | 23 | def files_of_document(document): 24 | hash_size_pairs = [] 25 | files = document.get('files', []) 26 | for f in files: 27 | hash_size_pairs.append((f['hash'], f['size'])) 28 | 29 | return hash_size_pairs 30 | 31 | 32 | def main(): 33 | hash_size_pairs = [] 34 | for collection in COLLECTIONS: 35 | hash_size_pairs += files_of_collection(collection) 36 | 37 | for collection, embedded_doc in COLLECTIONS_WITH_EMBEDDED: 38 | hash_size_pairs += files_of_collection(collection, embedded_doc) 39 | 40 | counter = Counter(hash_size_pairs) 41 | size_with_cas = 0 42 | size_wo_cas = 0 43 | file_count_cas = len(counter) 44 | file_count_wo_cas = 0 45 | 46 | for hash_size_pair in counter: 47 | size_with_cas += hash_size_pair[1] 48 | size_wo_cas += hash_size_pair[1] * counter[hash_size_pair] 49 | file_count_wo_cas += counter[hash_size_pair] 50 | 51 | saved_disk_space = size_wo_cas - size_with_cas 52 | 53 | print('Total size (CAS): %s Bytes' % size_with_cas) 54 | print('Total size (wo CAS): %s Bytes' % size_wo_cas) 55 | print('Number of files (CAS): %s' % file_count_cas) 56 | print('Number of files (wo CAS): %s' % file_count_wo_cas) 57 | print('Saved disk space: %s Bytes (%s%%)' % ( 58 | saved_disk_space, round(saved_disk_space / float(size_wo_cas) * 100, 2))) 59 | 60 | 61 | if __name__ == '__main__': 62 | main() 63 | -------------------------------------------------------------------------------- /swagger/schemas/definitions/device.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "definitions": { 4 | "errors": { 5 | "type": ["array", "null"], 6 | "items": { 7 | "type": "string" 8 | } 9 | }, 10 | "interval": {"type": "integer"}, 11 | "status-value": { 12 | "type": "string", 13 | "enum": ["ok", "missing", "error", "unknown"] 14 | }, 15 | "device-status-entry": { 16 | "type": "object", 17 | "properties":{ 18 | "errors": {"$ref":"#/definitions/errors"}, 19 | "last_seen": {"$ref":"common.json#/definitions/timestamp"}, 20 | "status": {"$ref":"#/definitions/status-value"} 21 | }, 22 | "additionalProperties":false, 23 | "required": ["last_seen", "status"] 24 | }, 25 | "device": { 26 | "type": "object", 27 | "properties": { 28 | "_id": {"type":"string"}, 29 | "method": {"type":"string"}, 30 | "name": {"type":"string"}, 31 | "errors": {"$ref":"#/definitions/errors"}, 32 | "info": {"$ref":"common.json#/definitions/info"}, 33 | "interval": {"$ref":"#/definitions/interval"}, 34 | "last_seen": {"$ref":"common.json#/definitions/timestamp"} 35 | }, 36 | "x-sdk-model": "device", 37 | "additionalProperties": false 38 | }, 39 | "device-input":{ 40 | "type": "object", 41 | "properties": { 42 | "interval": {"$ref":"#/definitions/interval"}, 43 | "errors": {"$ref":"#/definitions/errors"}, 44 | "info": {"$ref":"common.json#/definitions/info"} 45 | }, 46 | "x-sdk-model": "device", 47 | "additionalProperties": false 48 | }, 49 | "device-output": { 50 | "type": "object", 51 | "allOf": [{"$ref":"#/definitions/device"}], 52 | "required": ["_id", "name", "method", "last_seen"], 53 | "x-sdk-model": "device" 54 | }, 55 | "device-status": { 56 | "type":"object", 57 | "patternProperties": { 58 | "^[0-9a-z.@_-]*$":{ 59 | "$ref": "#/definitions/device-status-entry" 60 | } 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/scitran/core.svg?branch=master)](https://travis-ci.org/scitran/core) 2 | [![Coverage Status](https://codecov.io/gh/scitran/core/branch/master/graph/badge.svg)](https://codecov.io/gh/scitran/core/branch/master) 3 | [![Code Climate](https://codeclimate.com/github/scitran/core/badges/gpa.svg)](https://codeclimate.com/github/scitran/core) 4 | 5 | #### Flywheel has been the primary contributor to this repository for quite some time. We have now decided to fork scitran/core into [flywheel-io/core](https://github.com/flywheel-io/core), where we are planning to maintain and develop the code as open source, under the existing MIT license. All open issues in this repo have been closed, and corresponding issues have been opened in flywheel-io/core. 6 | 7 | # SciTran – Scientific Transparency 8 | 9 | ### Overview 10 | 11 | SciTran Core is a RESTful HTTP API, written in Python and backed by MongoDB. It is the central component of the [SciTran data management system](https://scitran.github.io). Its purpose is to enable scientific transparency through secure data management and reproducible processing. 12 | 13 | 14 | ### [Documentation](https://scitran.github.io/core) 15 | 16 | API documentation for branches and tags can be found at `https://scitran.github.io/core/branches/` and 17 | `https://scitran.github.io/core/tags/`. 18 | 19 | ### [Contributing](https://github.com/scitran/core/blob/master/CONTRIBUTING.md) 20 | 21 | ### [Testing](https://github.com/scitran/core/blob/master/TESTING.md) 22 | 23 | ### [License](https://github.com/scitran/core/blob/master/LICENSE) 24 | 25 | 26 | ### Usage 27 | **Currently Python 2 Only** 28 | 29 | #### OSX 30 | ``` 31 | $ ./bin/run-dev-osx.sh --help 32 | ``` 33 | 34 | For the best experience, please upgrade to a recent version of bash. 35 | ``` 36 | brew install bash bash-completion 37 | sudo dscl . -create /Users/$(whoami) UserShell /usr/local/bin/bash 38 | ``` 39 | 40 | #### Ubuntu 41 | ``` 42 | mkvirtualenv scitran-core 43 | ./bin/install-ubuntu.sh 44 | uwsgi --http :8080 --master --wsgi-file bin/api.wsgi -H $VIRTUAL_ENV \ 45 | --env SCITRAN_PERSISTENT_DB_URI="mongodb://localhost:27017/scitran-core" 46 | ``` 47 | -------------------------------------------------------------------------------- /swagger/schemas/definitions/resolver.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "definitions": { 4 | "resolver-input": { 5 | "type": "object", 6 | "properties": { 7 | "path": { 8 | "type": "array", 9 | "minLength": 1, 10 | "items": { 11 | "type": "string" 12 | } 13 | } 14 | }, 15 | "required": ["path"] 16 | }, 17 | "resolver-output": { 18 | "type": "object", 19 | "properties": { 20 | "path": { "$ref": "#/definitions/resolver-node-list" }, 21 | "children": { "$ref": "#/definitions/resolver-node-list" } 22 | }, 23 | "required": ["path"] 24 | }, 25 | "resolver-node": { 26 | "type": "object", 27 | "properties": { 28 | "node_type": { 29 | "type": "string" 30 | } 31 | }, 32 | "discriminator": "node_type", 33 | "required": ["node_type"] 34 | }, 35 | "resolver-node-list": { 36 | "type": "array", 37 | "items": { "$ref": "#/definitions/resolver-node" } 38 | }, 39 | "group-node": { 40 | "allOf": [ 41 | {"$ref":"#/definitions/resolver-node"}, 42 | {"$ref":"group.json#/definitions/group-output"} 43 | ], 44 | "x-discriminator-value": "group" 45 | }, 46 | "project-node": { 47 | "allOf": [ 48 | {"$ref":"#/definitions/resolver-node"}, 49 | {"$ref":"project.json#/definitions/project-output"} 50 | ], 51 | "x-discriminator-value": "project" 52 | }, 53 | "session-node": { 54 | "allOf": [ 55 | {"$ref":"#/definitions/resolver-node"}, 56 | {"$ref":"session.json#/definitions/session-output"} 57 | ], 58 | "x-discriminator-value": "session" 59 | }, 60 | "acquisition-node": { 61 | "allOf": [ 62 | {"$ref":"#/definitions/resolver-node"}, 63 | {"$ref":"acquisition.json#/definitions/acquisition-output"} 64 | ], 65 | "x-discriminator-value": "acquisition" 66 | }, 67 | "file-node": { 68 | "allOf": [ 69 | {"$ref":"#/definitions/resolver-node"}, 70 | {"$ref":"file.json#/definitions/file-output"} 71 | ], 72 | "x-discriminator-value": "file" 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /swagger/schemas/output/resolver.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type":"object", 4 | "allOf": [{ "$ref": "../definitions/resolver.json#/definitions/resolver-output" }], 5 | "example": { 6 | "path": [ 7 | { 8 | "node_type": "group", 9 | "_id": "scitran", 10 | "label": "Scitran", 11 | "permissions": [ 12 | { 13 | "access": "admin", 14 | "_id": "coltonlw@flywheel.io" 15 | } 16 | ], 17 | "created": "2016-08-19T11:41:15.360000+00:00", 18 | "modified": "2016-08-19T11:41:15.360000+00:00" 19 | }, 20 | { 21 | "node_type": "project", 22 | "_id": "57e452791cff88b85f9f9c97", 23 | "label": "Neuroscience", 24 | "group": "scitran", 25 | "created": "2016-09-22T21:51:53.151000+00:00", 26 | "modified": "2016-09-22T21:51:53.151000+00:00", 27 | "public": false, 28 | "permissions": [{ 29 | "access": "admin", 30 | "_id": "coltonlw@flywheel.io" 31 | }] 32 | } 33 | ], 34 | "children": [ 35 | { 36 | "node_type": "session", 37 | "_id": "57e01cccb1dc04000fb83f03", 38 | "label": "control_1", 39 | "group": "scitran", 40 | "created": "2016-09-19T17:13:48.164000+00:00", 41 | "subject": { 42 | "code": "ex4784", 43 | "_id": "57e01cccb1dc04000fb83f02" 44 | }, 45 | "modified": "2016-09-19T17:13:48.164000+00:00", 46 | "project": "57e01cccf6b5d5edbcb4e1cf", 47 | "public": false, 48 | "permissions": [{ 49 | "access": "admin", 50 | "_id": "coltonlw@flywheel.io" 51 | }] 52 | }, 53 | { 54 | "node_type": "file", 55 | "origin": { 56 | "method": "importer", 57 | "type": "device", 58 | "id": "importer_Admin_Import", 59 | "name": "Admin Import" 60 | }, 61 | "mimetype": "application/zip", 62 | "measurements": [], 63 | "hash": "v0-sha384-dd3c97bfe0ad1fcba75ae6718c6e81038c59af4f447f5db194d52732efa4f955b28455db02eb64cad3e4e55f11e3679f", 64 | "name": "4784_1_1_localizer_dicom.zip", 65 | "tags": [], 66 | "created": "2016-09-21T14:56:09.943000+00:00", 67 | "modified": "2016-09-21T14:56:09.943000+00:00", 68 | "modality": null, 69 | "info": {}, 70 | "type": "dicom", 71 | "size": 989933 72 | } 73 | ] 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /bin/oneoffs/load_external_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import bson 4 | import copy 5 | import datetime 6 | import dateutil.parser 7 | import json 8 | 9 | from api import config 10 | 11 | ## DEFAULTS ## 12 | 13 | USER_ID = "meganhenning@flywheel.io" 14 | SAFE_FILE_HASH = "v0-sha384-a8d0d1bd9368e5385f31d3582db07f9bc257537d5e1f207d36a91fdd3d2f188fff56616c0874bb3535c37fdf761a446c" 15 | PROJECT_ID = "5a26e049c6fa4a00161e4a1a" 16 | GROUP_ID = 'scitran' 17 | 18 | # Some day maybe this can use the SDK/API calls to get the proper test data 19 | # For now, paste it in 20 | 21 | SESSIONS = [] 22 | 23 | ACQUISITIONS = [] 24 | 25 | def handle_permissions(obj): 26 | obj['permissions'] = [{ 27 | "access": "admin", 28 | "_id": USER_ID 29 | }] 30 | 31 | def handle_dates(obj): 32 | if obj.get('timestamp'): 33 | obj['timestamp'] = dateutil.parser.parse(obj['timestamp']) 34 | if obj.get('created'): 35 | obj['created'] = dateutil.parser.parse(obj['created']) 36 | if obj.get('modified'): 37 | obj['modified'] = dateutil.parser.parse(obj['modified']) 38 | 39 | def handle_file(f): 40 | handle_dates(f) 41 | f.pop('info_exists', None) 42 | f.pop('join_origin', None) 43 | f['hash'] = SAFE_FILE_HASH 44 | 45 | 46 | for i, s in enumerate(SESSIONS): 47 | print "Processing session {} of {} sessions".format(i+1, len(SESSIONS)) 48 | 49 | s.pop('join-origin', None) 50 | 51 | s['_id'] = bson.ObjectId(s['_id']) 52 | s['project'] = bson.ObjectId(str(PROJECT_ID)) 53 | s['group'] = GROUP_ID 54 | handle_dates(s) 55 | handle_permissions(s) 56 | 57 | for f in s.get('files', []): 58 | handle_file(f) 59 | 60 | 61 | config.db.sessions.delete_many({'_id': s['_id']}) 62 | config.db.sessions.insert(s) 63 | 64 | for i, a in enumerate(ACQUISITIONS): 65 | print "Processing acquisition {} of {} acquisitions".format(i+1, len(ACQUISITIONS)) 66 | 67 | a['_id'] = bson.ObjectId(a['_id']) 68 | a['session'] = bson.ObjectId(a['session']) 69 | 70 | a.pop('join-origin', None) 71 | 72 | handle_dates(a) 73 | handle_permissions(a) 74 | 75 | for f in a.get('files', []): 76 | handle_file(f) 77 | 78 | config.db.acquisitions.delete_many({'_id': a['_id']}) 79 | config.db.acquisitions.insert(a) 80 | -------------------------------------------------------------------------------- /swagger/paths/analyses.yaml: -------------------------------------------------------------------------------- 1 | $template_arguments: 2 | tag: 'analyses' 3 | 4 | /analyses/{AnalysisId}: 5 | $template: templates/analysis-item.yaml 6 | arguments: 7 | supportsDelete: false 8 | 9 | /analyses/{AnalysisId}/inputs: 10 | $template: templates/analysis-files.yaml 11 | arguments: 12 | filegroup: inputs 13 | 14 | /analyses/{AnalysisId}/inputs/{Filename}: 15 | $template: templates/analysis-files-create-ticket-filename.yaml 16 | arguments: 17 | filegroup: inputs 18 | 19 | /analyses/{AnalysisId}/files: 20 | $template: templates/analysis-files.yaml 21 | arguments: 22 | filegroup: outputs 23 | 24 | /analyses/{AnalysisId}/files/{Filename}: 25 | $template: templates/analysis-files-create-ticket-filename.yaml 26 | arguments: 27 | filegroup: outputs 28 | 29 | /{ContainerName}/{ContainerId}/{SubcontainerName}/analyses: 30 | parameters: 31 | - name: ContainerName 32 | in: path 33 | type: string 34 | required: true 35 | enum: 36 | - groups 37 | - projects 38 | - sessions 39 | - acquisitions 40 | - collections 41 | description: The parent container type 42 | - name: ContainerId 43 | in: path 44 | type: string 45 | required: true 46 | description: The parent container id 47 | - name: SubcontainerName 48 | in: path 49 | type: string 50 | required: true 51 | enum: 52 | - all 53 | - projects 54 | - sessions 55 | - acquisitions 56 | description: The sub container type 57 | get: 58 | summary: Get nested analyses for a container 59 | description: > 60 | Returns analyses that belong to containers of the specified type that belong 61 | to ContainerId. 62 | 63 | For example: `projects/{ProjectId}/acquisitions/analyses` will return any analyses 64 | that have an acquisition that is under that project as a parent. 65 | 66 | The `all` keyword is also supported, for example: projects/{ProjectId}/all/analyses 67 | will return any analyses that have any session or acquisition or the project itself as a parent. 68 | operationId: get_analyses 69 | tags: 70 | - analyses 71 | responses: 72 | '200': 73 | description: The list of analyses 74 | schema: 75 | $ref: schemas/output/analyses-list.json 76 | 77 | --------------------------------------------------------------------------------