Skip to content

Commit

Permalink
Merge pull request #11 from gnosischain/feature/add-migrations
Browse files Browse the repository at this point in the history
Feature/add migrations
  • Loading branch information
giacomognosis authored Feb 28, 2024
2 parents 2ef35b6 + f2972e6 commit efbaa44
Show file tree
Hide file tree
Showing 16 changed files with 115 additions and 57 deletions.
29 changes: 17 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,38 @@

A simple python implementation of an EVM compatible faucet.

## API
## Python API

### Requirements

Python +3.x, NodeJS v18.x
Python +3.x

### Python API
### Installation

```
cd api
python3 -m venv .venv
. .venv/bin/activate
pip3 install -r requirements.txt
python3 -m flask --app api run --port 8000
pip3 install -r requirements-dev.txt
```

#### Run application
### Run application

Check .env.example for reference.

```
cd api
python3 -m flask --app api run --port 8000
```


#### Run tests
### Run tests

```
cd api
python3 -m pytest -s
```

#### Run Flake8 and isort
### Run Flake8 and isort

```
cd api
Expand All @@ -43,7 +42,13 @@ isort **/*.py --atomic
python3 -m flake8
```

### ReactJS Frontend
## ReactJS Frontend

### Requirements

NodeJS v18.x

### Installation

```
nvm use
Expand All @@ -52,7 +57,7 @@ cd app
yarn
```

#### Run application
### Run application

```
cd app
Expand Down
1 change: 1 addition & 0 deletions api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ FAUCET_RPC_URL=https://rpc.chiadochain.net
FAUCET_CHAIN_ID=10200
FAUCET_ENABLED_TOKENS="[{\"address\": \"0x19C653Da7c37c66208fbfbE8908A5051B57b4C70\", \"name\":\"GNO\", \"maximumAmount\": 0.5}]"
# FAUCET_ENABLED_TOKENS=
FAUCET_DATABASE_URI=sqlite:///:memory
CAPTCHA_VERIFY_ENDPOINT=https://api.hcaptcha.com/siteverify
CAPTCHA_SECRET_KEY=0x0000000000000000000000000000000000000000
2 changes: 1 addition & 1 deletion api/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ RUN pip install --no-cache-dir -r /tmp/requirements.txt
COPY . /api
WORKDIR /api

CMD ["gunicorn", "--bind", "0.0.0.0:8000", "api:create_app()"]
ENTRYPOINT ["/sbin/tini", "--"]
11 changes: 7 additions & 4 deletions api/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

from flask import Flask
from flask_cors import CORS
from flask_migrate import Migrate

from .manage import create_access_keys_cmd
from .routes import apiv1
from .services import Cache, Web3Singleton
from .services.database import db, migrate
from .services.database import db


def setup_logger(log_level):
Expand Down Expand Up @@ -36,13 +38,14 @@ def create_app():
app.config['FAUCET_CACHE'] = Cache(app.config['FAUCET_RATE_LIMIT_TIME_LIMIT_SECONDS'])
# Initialize API Routes
app.register_blueprint(apiv1, url_prefix="/api/v1")
# Add cli commands
app.cli.add_command(create_access_keys_cmd)

with app.app_context():
db.init_app(app)
migrate.init_app(app, db)
db.create_all() # Create database tables for our data models
Migrate(app, db)

# Initialize Web3 class
# Initialize Web3 class for latter usage
w3 = Web3Singleton(app.config['FAUCET_RPC_URL'], app.config['FAUCET_PRIVATE_KEY'])

setup_cors(app)
Expand Down
19 changes: 19 additions & 0 deletions api/api/manage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import logging

import click
from flask.cli import with_appcontext

from .services.database import AccessKey
from .utils import generate_access_key


@click.command(name='create_access_keys')
@with_appcontext
def create_access_keys_cmd():
access_key_id, secret_access_key = generate_access_key()
access_key = AccessKey()
access_key.access_key_id = access_key_id
access_key.secret_access_key = secret_access_key
access_key.save()
logging.info(f'Access Key ID : ${access_key_id}')
logging.info(f'Secret access key: ${secret_access_key}')
10 changes: 6 additions & 4 deletions api/api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,14 @@ def _ask(request_data, validate_captcha):

@apiv1.route("/ask", methods=["POST"])
def ask():
return _ask(request.get_json(), validate_captcha=True)
data, status_code = _ask(request.get_json(), validate_captcha=True)
return data, status_code


@apiv1.route("/cli/ask", methods=["POST"])
def cli_ask():
access_key_id = request.headers.get('FAUCET_ACCESS_KEY_ID', None)
secret_access_key = request.headers.get('FAUCET_SECRET_ACCESS_KEY', None)
access_key_id = request.headers.get('X-faucet-access-key-id', None)
secret_access_key = request.headers.get('X-faucet-secret-access-key', None)

validation_errors = []

Expand All @@ -127,4 +128,5 @@ def cli_ask():
validation_errors.append('Access denied')
return jsonify(errors=validation_errors), 403

return _ask(request.get_json(), validate_captcha=False)
data, status_code = _ask(request.get_json(), validate_captcha=False)
return data, status_code
6 changes: 2 additions & 4 deletions api/api/services/database.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import sqlite3

from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()
migrate = Migrate()


class Database:
Expand Down Expand Up @@ -68,8 +66,8 @@ def delete(self, commit=True):
class AccessKey(BaseModel):
__tablename__ = "access_keys"
access_key_id = db.Column(db.String(16), primary_key=True)
secret_access_key = db.Column(db.String(32))
enabled = db.Column(db.Boolean(), default=True)
secret_access_key = db.Column(db.String(32), nullable=False)
enabled = db.Column(db.Boolean(), default=True, nullable=False)

def __repr__(self):
return f"<Access Key {self.access_key_id}>"
32 changes: 32 additions & 0 deletions api/migrations/versions/71441c34724e_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""empty message
Revision ID: 71441c34724e
Revises:
Create Date: 2024-02-28 14:11:13.601403
"""
import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = '71441c34724e'
down_revision = None
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('access_keys',
sa.Column('access_key_id', sa.String(length=16), nullable=False),
sa.Column('secret_access_key', sa.String(length=32), nullable=False),
sa.Column('enabled', sa.Boolean(), nullable=False),
sa.PrimaryKeyConstraint('access_key_id')
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('access_keys')
# ### end Alembic commands ###
14 changes: 14 additions & 0 deletions api/scripts/local_run_migrations.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/bash

set -x

# DB MIGRATIONS:
FLASK_APP=api FAUCET_DATABASE_URI=sqlite:///:memory python3 -m flask db init # only the first time we initialize the DB
FLASK_APP=api FAUCET_DATABASE_URI=sqlite:///:memory python3 -m flask db migrate
# Reflect migrations into the database:
# FLASK_APP=api python3 -m flask db upgrade

# Valid SQLite URL forms are:
# sqlite:///:memory: (or, sqlite://)
# sqlite:///relative/path/to/file.db
# sqlite:////absolute/path/to/file.db
10 changes: 10 additions & 0 deletions api/scripts/production_run_api.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash

set -euo pipefail


echo "==> $(date +%H:%M:%S) ==> Migrating DB models... "
FLASK_APP=api python -m flask db upgrade

echo "==> $(date +%H:%M:%S) ==> Running Gunicorn... "
exec gunicorn --bind 0.0.0.0:8000 "api:create_app()"
1 change: 0 additions & 1 deletion api/scripts/run.sh

This file was deleted.

23 changes: 0 additions & 23 deletions api/scripts/run_test_env.sh

This file was deleted.

2 changes: 2 additions & 0 deletions api/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os

import pytest
from flask_migrate import upgrade
from temp_env_var import (CAPTCHA_TEST_RESPONSE_TOKEN, ERC20_TOKEN_ADDRESS,
ERC20_TOKEN_AMOUNT, NATIVE_TOKEN_ADDRESS,
NATIVE_TOKEN_AMOUNT, NATIVE_TRANSFER_TX_HASH,
Expand Down Expand Up @@ -30,6 +31,7 @@ def app(self, mocker):
mocker = self._mock(mocker, TEMP_ENV_VARS)
app = self._create_app()
with app.app_context():
upgrade()
yield app

@pytest.fixture
Expand Down
2 changes: 1 addition & 1 deletion api/tests/temp_env_var.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
'FAUCET_PRIVATE_KEY': token_bytes(32).hex(),
'FAUCET_RATE_LIMIT_TIME_LIMIT_SECONDS': '10',
'FAUCET_ENABLED_TOKENS': json.dumps(FAUCET_ENABLED_TOKENS),
'FAUCET_DATABASE_URI': 'sqlite:///', # run in-memory
'FAUCET_DATABASE_URI': 'sqlite:///:memory', # run in-memory
'CAPTCHA_SECRET_KEY': CAPTCHA_TEST_SECRET_KEY
}

Expand Down
4 changes: 2 additions & 2 deletions api/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ class TestCliAPI(BaseTest):
def test_ask_route_parameters(self, client):
access_key_id, secret_access_key = generate_access_key()
http_headers = {
'FAUCET_ACCESS_KEY_ID': access_key_id,
'FAUCET_SECRET_ACCESS_KEY': secret_access_key
'X-faucet-access-key-id': access_key_id,
'X-faucet-secret-access-key': secret_access_key
}

response = client.post(api_prefix + '/cli/ask', json={})
Expand Down
6 changes: 1 addition & 5 deletions api/tests/test_database.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
from conftest import BaseTest
# from mock import patch
from temp_env_var import (CAPTCHA_TEST_RESPONSE_TOKEN, ERC20_TOKEN_ADDRESS,
ERC20_TOKEN_AMOUNT, NATIVE_TOKEN_ADDRESS,
NATIVE_TOKEN_AMOUNT, NATIVE_TRANSFER_TX_HASH,
TEMP_ENV_VARS, TOKEN_TRANSFER_TX_HASH, ZERO_ADDRESS)

from api.services.database import AccessKey
from api.utils import generate_access_key


class TestDatabase(BaseTest):

def test_models(self, client):
access_key_id, secret_access_key = generate_access_key()
assert len(access_key_id) == 16
Expand Down

0 comments on commit efbaa44

Please sign in to comment.