ows-charts
==========

Provides charts data endpoints. 
Python flask application

## Install

Ensure python3.11.2 is installed with pyenv.
- the specific current version used by the project is set in `.python-version`

```
$ brew update
$ brew install pyenv
$ pyenv install 3.11.2
```

If you use Windows, just ask for a remote VM that has it installed for you.

You'll next want to install the python packages used by the project:

```bash
$ cd ows-charts
$ make pip_dev
```

Did you see something like this?
```bash
No local packages or working download links found for setuptools>=xx.x.x
```
If so, try this
```bash
env/bin/pip3.11 install --upgrade -vv setuptools
```
Then try again

## Run

Copy `.env.shadow` to `.env` and fill in variables. Then run the application with

```bash
make dev
```

## Test

You must have a region in your default aws settings i.e. your 
`~/.aws/credentials` must have something like `region = us-east-1`

There are three kinds of tests: unit, lint, and integration.

### Unit

Simply do

```bash
make test
```

To produce coverage reports, run `make test_unit`.

### Lint

Run lint checks with

```bash
make lint
```

### Integration

Make sure the application is running, then do

```bash
make test_integration
```

## Clean up 

Remove the virtual env directory by running

```bash
make clean
```

## Snowflake authentication using key pair

To authenticate using SSH use following documentation:
https://docs.snowflake.net/manuals/user-guide/snowsql-start.html#using-key-pair-authentication

TL;DR:
```bash
mkdir ~/.ssh/snowflake && cd ~/.ssh/snowflake # recommended
openssl genrsa 2048 | openssl pkcs8 -topk8 -inform PEM -out rsa_key.p8
openssl rsa -in rsa_key.p8 -pubout -out rsa_key.pub
```

Make sure to set a password and then put this into the `SNOWFLAKE_KEY_PASSPHRASE` environment
variable in your `.env` file.

Then send newly generated public key to systems@theorchard.com and create SYS ticket.
Note: if you put key not in ~/.ssh/snowflake, then enter location in SNOWFLAKE_PRIVATE_KEY_PATH variable.

### Grass Access Validation

The boilerplate includes module `validation.access` which contains functionality for validating Grass headers in the context of a Flask request. This is valuable for making microservice endpoints compatible for both Grass and non-Grass (i.e. microservice-to-microservice) requests. It makes it easier to facilitate endpoint reuse.

For example, suppose you need to validate that a request where Grass headers are required. Additionally, the `vendor_id` passed in the route must match - thus the Grass Account Type must be 'vendor' and the Grass Account Id must match `vendor_id`. You would use as follows:

```python
@app.route('/vendor/<vendor_id>/something')
def do_something_only_for_vendors(vendor_id):
    """Do something only for vendors.

    Args:
        vendor_id (int): unique identifier for vendor.
    """

    validation = access.verify_grass_access(
        request, required=True, vendor=vendor_id)
    if not validation:
        return validation

    // do something
```

Another example - suppose Grass headers are not required. Additionally, the `subaccount_id` passed in the route must match. The only acceptable Account Type is subaccount:

```python
@app.route('/subaccount/<subaccount_id>/something')
def do_something_only_for_subaccounts(subaccount_id):
    """Do something only for subaccounts.

    Args:
        subaccount_id (int): unique identifier for subaccount.
    """

    account_type, account_id = access.get_grass_headers(request)
    validation = access.verify_grass_access(
        request, required=False, subaccount=subaccount_id)
    if not validation:
        return validation

    // do something
```

Now, building on the last example, we can accept Account Type of vendor AND subaccount:

```python
@app.route('/subaccount/<subaccount_id>/something')
def do_something(subaccount_id):
    """Do something.

    Args:
        subaccount_id (int): unique identifier for subaccount.
    """

    account_type, account_id = access.get_grass_headers(request)
    validation = access.verify_grass_access(
        request, required=False, vendor=account_id, subaccount=subaccount_id)
    if not validation:
        return validation

    // do something
```

## Cache

Redis is used for caching. To use it, you must have a Redis server running. You can run one locally with Docker:

```bash
make docker/up-redis
```

By default, the application will look for a Redis server at `localhost:6379`. You can change this by setting the `REDIS_HOST` and `REDIS_PORT` environment variables in your `.env` file.

The cache will expire after 1 hour. You can change this by setting the `REDIS_CACHE_TTL` environment variable in your `.env` file (or config.py). The value should be in seconds.

There is no warmup script. The cache is populated on demand.
