diff --git a/README.md b/README.md index 4071a55..7cd86e9 100644 --- a/README.md +++ b/README.md @@ -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 @@ -43,7 +42,13 @@ isort **/*.py --atomic python3 -m flake8 ``` -### ReactJS Frontend +## ReactJS Frontend + +### Requirements + +NodeJS v18.x + +### Installation ``` nvm use @@ -52,7 +57,7 @@ cd app yarn ``` -#### Run application +### Run application ``` cd app diff --git a/api/tox.ini b/api/.tox.ini similarity index 100% rename from api/tox.ini rename to api/.tox.ini diff --git a/api/api/const.py b/api/api/const.py index ec46c65..5c6a67e 100644 --- a/api/api/const.py +++ b/api/api/const.py @@ -1 +1 @@ -NATIVE_TOKEN_ADDRESS = 'native' +NATIVE_TOKEN_ADDRESS='native' diff --git a/api/api/routes.py b/api/api/routes.py index c7d444d..157eb37 100644 --- a/api/api/routes.py +++ b/api/api/routes.py @@ -127,4 +127,4 @@ def cli_ask(): validation_errors.append('Access denied') return jsonify(errors=validation_errors), 403 - return _ask(request.get_json(), validate_captcha=False) + return _ask(request.get_json(), validate_captcha=False) \ No newline at end of file diff --git a/api/api/services/cache.py b/api/api/services/cache.py index aff61b9..00a3317 100644 --- a/api/api/services/cache.py +++ b/api/api/services/cache.py @@ -30,4 +30,4 @@ def ttl(self, hours=False): if hours: # 3600 seconds = 1h return self._ttl // 3600 - return self._ttl + return self._ttl \ No newline at end of file diff --git a/api/api/settings.py b/api/api/settings.py index bbcdf82..97e6210 100644 --- a/api/api/settings.py +++ b/api/api/settings.py @@ -37,4 +37,4 @@ CORS_ALLOWED_ORIGINS = os.getenv('CORS_ALLOWED_ORIGINS', '*') CAPTCHA_VERIFY_ENDPOINT = os.getenv('CAPTCHA_VERIFY_ENDPOINT') -CAPTCHA_SECRET_KEY = os.getenv('CAPTCHA_SECRET_KEY') +CAPTCHA_SECRET_KEY = os.getenv('CAPTCHA_SECRET_KEY') \ No newline at end of file diff --git a/api/api/utils.py b/api/api/utils.py index 1827d19..2440279 100644 --- a/api/api/utils.py +++ b/api/api/utils.py @@ -63,4 +63,4 @@ def is_amount_valid(amount, token_address, tokens_list): def generate_access_key(): access_key_id = secrets.token_hex(8) # returns a 16 chars long string secret_access_key = secrets.token_hex(16) # returns a 32 chars long string - return access_key_id, secret_access_key + return access_key_id, secret_access_key \ No newline at end of file diff --git a/api/migrations/README b/api/migrations/README deleted file mode 100644 index 0e04844..0000000 --- a/api/migrations/README +++ /dev/null @@ -1 +0,0 @@ -Single-database configuration for Flask. diff --git a/api/migrations/alembic.ini b/api/migrations/alembic.ini deleted file mode 100644 index ec9d45c..0000000 --- a/api/migrations/alembic.ini +++ /dev/null @@ -1,50 +0,0 @@ -# A generic, single database configuration. - -[alembic] -# template used to generate migration files -# file_template = %%(rev)s_%%(slug)s - -# set to 'true' to run the environment during -# the 'revision' command, regardless of autogenerate -# revision_environment = false - - -# Logging configuration -[loggers] -keys = root,sqlalchemy,alembic,flask_migrate - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = WARN -handlers = console -qualname = - -[logger_sqlalchemy] -level = WARN -handlers = -qualname = sqlalchemy.engine - -[logger_alembic] -level = INFO -handlers = -qualname = alembic - -[logger_flask_migrate] -level = INFO -handlers = -qualname = flask_migrate - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(levelname)-5.5s [%(name)s] %(message)s -datefmt = %H:%M:%S diff --git a/api/migrations/env.py b/api/migrations/env.py deleted file mode 100644 index 0749ebf..0000000 --- a/api/migrations/env.py +++ /dev/null @@ -1,112 +0,0 @@ -import logging -from logging.config import fileConfig - -from alembic import context -from flask import current_app - -# this is the Alembic Config object, which provides -# access to the values within the .ini file in use. -config = context.config - -# Interpret the config file for Python logging. -# This line sets up loggers basically. -fileConfig(config.config_file_name) -logger = logging.getLogger('alembic.env') - - -def get_engine(): - try: - # this works with Flask-SQLAlchemy<3 and Alchemical - return current_app.extensions['migrate'].db.get_engine() - except (TypeError, AttributeError): - # this works with Flask-SQLAlchemy>=3 - return current_app.extensions['migrate'].db.engine - - -def get_engine_url(): - try: - return get_engine().url.render_as_string(hide_password=False).replace( - '%', '%%') - except AttributeError: - return str(get_engine().url).replace('%', '%%') - - -# add your model's MetaData object here -# for 'autogenerate' support -# from myapp import mymodel -# target_metadata = mymodel.Base.metadata -config.set_main_option('sqlalchemy.url', get_engine_url()) -target_db = current_app.extensions['migrate'].db - -# other values from the config, defined by the needs of env.py, -# can be acquired: -# my_important_option = config.get_main_option("my_important_option") -# ... etc. - - -def get_metadata(): - if hasattr(target_db, 'metadatas'): - return target_db.metadatas[None] - return target_db.metadata - - -def run_migrations_offline(): - """Run migrations in 'offline' mode. - - This configures the context with just a URL - and not an Engine, though an Engine is acceptable - here as well. By skipping the Engine creation - we don't even need a DBAPI to be available. - - Calls to context.execute() here emit the given string to the - script output. - - """ - url = config.get_main_option("sqlalchemy.url") - context.configure( - url=url, target_metadata=get_metadata(), literal_binds=True - ) - - with context.begin_transaction(): - context.run_migrations() - - -def run_migrations_online(): - """Run migrations in 'online' mode. - - In this scenario we need to create an Engine - and associate a connection with the context. - - """ - - # this callback is used to prevent an auto-migration from being generated - # when there are no changes to the schema - # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html - def process_revision_directives(context, revision, directives): - if getattr(config.cmd_opts, 'autogenerate', False): - script = directives[0] - if script.upgrade_ops.is_empty(): - directives[:] = [] - logger.info('No changes in schema detected.') - - conf_args = current_app.extensions['migrate'].configure_args - if conf_args.get("process_revision_directives") is None: - conf_args["process_revision_directives"] = process_revision_directives - - connectable = get_engine() - - with connectable.connect() as connection: - context.configure( - connection=connection, - target_metadata=get_metadata(), - **conf_args - ) - - with context.begin_transaction(): - context.run_migrations() - - -if context.is_offline_mode(): - run_migrations_offline() -else: - run_migrations_online() diff --git a/api/migrations/script.py.mako b/api/migrations/script.py.mako deleted file mode 100644 index 2c01563..0000000 --- a/api/migrations/script.py.mako +++ /dev/null @@ -1,24 +0,0 @@ -"""${message} - -Revision ID: ${up_revision} -Revises: ${down_revision | comma,n} -Create Date: ${create_date} - -""" -from alembic import op -import sqlalchemy as sa -${imports if imports else ""} - -# revision identifiers, used by Alembic. -revision = ${repr(up_revision)} -down_revision = ${repr(down_revision)} -branch_labels = ${repr(branch_labels)} -depends_on = ${repr(depends_on)} - - -def upgrade(): - ${upgrades if upgrades else "pass"} - - -def downgrade(): - ${downgrades if downgrades else "pass"} diff --git a/api/migrations/versions/71441c34724e_.py b/api/migrations/versions/71441c34724e_.py new file mode 100644 index 0000000..5360806 --- /dev/null +++ b/api/migrations/versions/71441c34724e_.py @@ -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 ### diff --git a/api/scripts/local_run_migrations.sh b/api/scripts/local_run_migrations.sh new file mode 100644 index 0000000..daa87d0 --- /dev/null +++ b/api/scripts/local_run_migrations.sh @@ -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 \ No newline at end of file diff --git a/api/scripts/production_run_api.sh b/api/scripts/production_run_api.sh new file mode 100644 index 0000000..3a749b6 --- /dev/null +++ b/api/scripts/production_run_api.sh @@ -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()" \ No newline at end of file diff --git a/api/scripts/run.sh b/api/scripts/run.sh deleted file mode 100644 index 26db72c..0000000 --- a/api/scripts/run.sh +++ /dev/null @@ -1 +0,0 @@ -python3 -m flask --app api run --port 8000 \ No newline at end of file diff --git a/api/scripts/run_test_env.sh b/api/scripts/run_test_env.sh deleted file mode 100644 index 9ca35df..0000000 --- a/api/scripts/run_test_env.sh +++ /dev/null @@ -1,23 +0,0 @@ -touch /tmp/faucet_test.db - -# !! PRIVATE KEY FOR TEST PURPOSE ONLY !! -# 0x21b1ae205147d4e2fcdee7b3fc762aa21f955f3dace8a185306ac104be797081 -# !! DO NOT USE IN ANY OTHER CONTEXTS !! - -FAUCET_RPC_URL=https://rpc.chiadochain.net \ -FAUCET_PRIVATE_KEY="0x21b1ae205147d4e2fcdee7b3fc762aa21f955f3dace8a185306ac104be797081" \ -FAUCET_CHAIN_ID=10200 \ -FAUCET_DATABASE_URI=sqlite:///tmp/faucet_test.db \ -CAPTCHA_VERIFY_ENDPOINT=localhost \ -CAPTCHA_SECRET_KEY=testkey \ -python3 -m flask --app api run --port 8000 - -# DB MIGRATIONS: -## Generate migrations -### ENV_VARIABLES python3 -m flask --app api db init -### ENV_VARIABLES python3 -m flask --app api db migrate - -# Valid SQLite URL forms are: -# sqlite:///:memory: (or, sqlite://) -# sqlite:///relative/path/to/file.db -# sqlite:////absolute/path/to/file.db \ No newline at end of file