-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
19 changed files
with
225 additions
and
238 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
__pycache__ | ||
api/docs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# IDEAFAST-ETL access settings | ||
WP3API_AIRFLOW_PASS="" | ||
AIRFLOW_SERVER="airflow-webserver" | ||
|
||
# UCAM API | ||
UCAM_URI="" | ||
UCAM_USERNAME="" | ||
UCAM_PASSWORD="" | ||
|
||
# --- USERS --- | ||
# for local development, when changing log in values | ||
# be sure to clean the docker volumes before rebooting | ||
_MONGO_INITDB_ROOT_USERNAME= | ||
_MONGO_INITDB_ROOT_PASSWORD= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,14 @@ | ||
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9-slim | ||
|
||
RUN apt-get update && \ | ||
apt-get install -y --no-install-recommends git ssh \ | ||
&& rm -rf /var/lib/apt/lists/* | ||
|
||
WORKDIR /app | ||
|
||
COPY ./requirements.txt /app/requirements.txt | ||
|
||
RUN pip install --no-cache-dir -r /app/requirements.txt | ||
|
||
COPY ./scripts /app | ||
COPY ./api /app/api |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,46 +1,88 @@ | ||
# IDEA-FAST WP3 Public Facing API | ||
|
||
A public facing API used to feed the IDEA-FAST Study Dashboard. It includes some functionality embedded in the `IDEAFAST/middleware-service:consumer` repository, but has been started from scratch to interface with the `IDEAFAST/ideafast-etl` pipeline and newly set up Dashboard for simplicity and separation of tasks. | ||
A public facing API used to feed the IDEA-FAST Study Dashboard. It includes some functionality embedded in the `IDEAFAST/middleware-service:consumer` repository, but has been started from scratch to interface with the `IDEAFAST/ideafast-etl` pipeline and newly set up Dashboard for simplicity and separation of concerns. | ||
|
||
> The development setup, folder structure, and cli.py share a lot of commonalities with the `IDEAFAST/ideafast-etl` repository. | ||
The primary task fo the API is to feed the IDEAFAST Study Dashboard with | ||
|
||
## Run the API Locally | ||
- information about enrolled participants (provided though the `UCAM` api), | ||
- details about their (temporary) app logins, | ||
- the status of the [IDEAFAST ETL pipeline](https://github.com/ideafast/ideafast-etl), and | ||
- serving device documentation and FAQ pulled from a private GitHub repo. | ||
|
||
## Run the API locally and remotely using Docker | ||
|
||
By design, the API is containerised and can be easily deployed with a docker-compose as implemented for [ideafast/stack](https://github.com/ideafast/stack). As an image, it needs to be fed an `.env` file to authenticate with 3rd party services, and a `ssh key` to authenticate with the private GitHub repository to pull the latest documentation. | ||
|
||
### Setup | ||
|
||
Rename the `.env.example` to `.env` and update the variables/secrets as needed. | ||
1. Rename the `.env.example` to `.env` and update the variables/secrets as needed. | ||
2. Get the `ssh keys` from your colleague and store then in the [ssh](ssh) folder in this repository. If you, however, need a new one: | ||
- Navigate into the [ssh](ssh) folder _(cd ssh)_ | ||
- Generate a key with the command below. _Note that the `-N ''` parameter results in a ssh key **without** password, which is generally not advised, but useful in an non-interactive container application as this one. The `-f ed25519` parameter results in the key being generated in the folder you navigated to_. | ||
```shell | ||
ssh-keygen -t ed25519 -f id_ed25519 -C "[email protected]" -N '' | ||
``` | ||
- Go to the GitHub repository hosting the documentation (in this case github.com/ideafast/ideafast-devicesupportdocs-web, as you can see from the [scripts/prestart.sh](scripts/prestart.sh) script), navigate to _Settings_ and _Deploy keys_ and add this key as a read-only key. | ||
|
||
### Run | ||
|
||
### Containerised | ||
The API is available as a Docker image, and can be spun up with: | ||
Only meant as an example, but you can run the API locally with the example docker-compose file in this repository: | ||
|
||
```shell | ||
docker-compose up | ||
docker-compose -f example.docker-compose.yml up | ||
``` | ||
|
||
Open your browser and try out a few endpoints, e.g. | ||
- http://localhost:8000/patients | ||
- http://localhost:8000/docs | ||
- http://localhost:8000/pipeline | ||
- http://localhost/patients | ||
- http://localhost/docs/axivity | ||
- http://localhost/status | ||
|
||
The docker-compose file will also spin up a mongo container. After following the steps below outlined in <a name="Inserting participant credentials into the database">Inserting participant credentials into the database</a> to populate the database with some patient credentials content, you can try accessing this at | ||
|
||
- http://localhost/patients/credentials/*participant_id* | ||
|
||
Note that for the pipeline endpoint, the [`ideafast-etl`](https://github.com/ideafast/ideafast-etl) docker image also needs to be running, as access is only provided locally. | ||
|
||
### CLI | ||
Trigger a pull for new docs for the GET /docs endpoint by running | ||
|
||
```shell | ||
curl -X POST -H "Content-Type: application/json" -d '{"sample":"dict"}' http://localhost/docs/update | ||
``` | ||
|
||
Trigger a pull for new docs for the GET /docs endpoint by running | ||
```shell | ||
curl -X POST -H "Content-Type: application/json" -d '{"sample":"dict"}' http://localhost/docs/update | ||
``` | ||
|
||
When deploying this API remotely, please implement the appropriate safety protocol (e.g. basic authentication) for access. IDEAFAST uses a reverse proxy with [traefik](https://traefik.io/) and restricts access to the API (such as the endpoint above) with basic authentication. | ||
|
||
Note that all endpoints have dependencies on other (spun up) services with potential passwords (see [.example.env](.example.env)): | ||
- GET /patients on the UCAM API _(the first request can take a while due to the serverless architecture used by UCAM)_ | ||
- GET /status on the the [`ideafast-etl`](https://github.com/ideafast/ideafast-etl) service | ||
- GET /docs on the private GitHub repository, for which the ssh key is needed | ||
---- | ||
|
||
## Development and hot-reloading | ||
|
||
A CLI command sets up the API locally to enable hot-reloading as code changes, something the docker setup prevents. Please follow the advised steps below for local development. | ||
|
||
### Preparations | ||
|
||
[Poetry](https://python-poetry.org/) is used for dependency management during development and [pyenv](https://github.com/pyenv/pyenv) to manage python installations, so please install both on your local machine. We use python 3.8 by default, so please make sure this is installed via pyenv, e.g. | ||
|
||
```shell | ||
pyenv install 3.8.0 && pyenv global 3.8.0 | ||
``` | ||
|
||
Once done, clone the repo to your machine, install dependencies for this project, and quickstart the API which will watch for local changes _(useful for during development!)_: | ||
Add the required environmental variables in the `.env` file and quickstart the API which will watch for local changes: | ||
|
||
```shell | ||
poetry install | ||
poetry shell | ||
python api/main.py | ||
poetry run local | ||
``` | ||
|
||
> Note that running `poetry run local` overrides some settings (see [/api/main.py](/api/main.py)) which are normally set in the _docker-compose.yaml_ when running the Docker image. | ||
#### GET /docs | ||
The documentation endpoint relies on a private git repository that needs to be loaded into the docker container at boot. This is handled by the [preshart.sh](scripts/prestart.sh) script. Locally, however, running this script outside a docker container will interfere with your local git and ssh setup. Instead, download the (private) repo as a .zip and place it into the api/docs folder for local development and testing. | ||
|
||
## Local development | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,34 +1,71 @@ | ||
import codecs | ||
import subprocess # noqa | ||
from enum import Enum | ||
from pathlib import Path | ||
from typing import List | ||
|
||
from fastapi import APIRouter | ||
from fastapi import APIRouter, BackgroundTasks, HTTPException | ||
from fastapi.responses import HTMLResponse | ||
|
||
router = APIRouter() | ||
|
||
CURRENT_DIR = Path(__file__).parent | ||
FILES_PATH = CURRENT_DIR / "docs/html" | ||
|
||
|
||
class DEVICE(Enum): | ||
"""Enum for device types""" | ||
|
||
DRM = "dreem" | ||
VTP = "vitalpatch" | ||
AX6 = "axivity" | ||
SMP = "smartphone" | ||
WKS = "wildkeys" | ||
CTB = "cantab" | ||
DRM = "DRM" | ||
VTP = "VTP" | ||
AX6 = "AX6" | ||
SMP = "SMP" | ||
WKS = "WKS" | ||
CTB = "CTB" | ||
|
||
# we also want to host documentation about software platforms | ||
UCAM = "UCAM" | ||
DMP = "DMP" | ||
|
||
# referring to documentation about how to update this documentsion | ||
DOCS = "DOCS" | ||
|
||
|
||
def load_doc(device: DEVICE, type: str = "docs") -> str: | ||
"""Read the requested file into memory and return""" | ||
try: | ||
with codecs.open(f"{FILES_PATH}/{type}/{device.name}.html", "r") as f: | ||
content = f.read() | ||
return content | ||
except FileNotFoundError as e: | ||
raise HTTPException(status_code=500, detail="File not found") from e | ||
|
||
|
||
def retrieve_latest_docs() -> None: | ||
"""Run shell script to pull latest changes to the DOC/FAQ repo""" | ||
subprocess.run(["git", "-C", "api/docs/", "pull"]) # noqa | ||
|
||
|
||
@router.post("/update", status_code=202, include_in_schema=False) | ||
def update_docs(payload: dict, background_tasks: BackgroundTasks) -> dict: | ||
"""Trigger an update for the docs from Github Actions""" | ||
background_tasks.add_task(retrieve_latest_docs) | ||
return {"message": "Success: updating cache of Docs/API is scheduled"} | ||
|
||
|
||
@router.get("/") | ||
def docs() -> str: | ||
"""Get information about all device documentation""" | ||
return "got your some info about all devices" | ||
@router.get("/list", response_model=List[str]) | ||
def list_devices() -> List[str]: | ||
"""Return a list of possible devices/software to get docs for""" | ||
return [d.name for d in DEVICE] | ||
|
||
|
||
@router.get("/{device}") | ||
@router.get("/{device}", response_class=HTMLResponse) | ||
def device(device: DEVICE) -> str: | ||
"""Get information about the device documentation""" | ||
return f"got your some info about {device}" | ||
return load_doc(device) | ||
|
||
|
||
@router.get("/{device}/faq") | ||
@router.get("/{device}/faq", response_class=HTMLResponse) | ||
def faq(device: DEVICE) -> str: | ||
"""Get list of FAQ from device documentation""" | ||
return f"got your some FAQs about {device}" | ||
return load_doc(device, "faq") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.