diff --git a/.gitignore b/.gitignore index b6e4761..980bf3a 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,4 @@ dmypy.json # Pyre type checker .pyre/ +data/ \ No newline at end of file diff --git a/LICENSE b/LICENSE index 041460c..aa1b832 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 lalanza808 +Copyright (c) 2023 lalanza808 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index 3c403cd..de4eb6b 100644 --- a/Makefile +++ b/Makefile @@ -10,8 +10,11 @@ setup: ## Establish local environment with dependencies installed python3 -m venv .venv .venv/bin/pip install -r requirements.txt -up: ## Build and run the required containers by fetching binaries - docker-compose -f docker-compose.yaml up -d +shell: + FLASK_SECRETS=config.py QUART_APP="flipbook:create_app()" .venv/bin/quart shell -shell: ## Start Flask CLI shell - FLASK_APP=app/app.py FLASK_SECRETS=config.py FLASK_DEBUG=0 FLASK_ENV=production .venv/bin/flask shell +dev: + FLASK_SECRETS=config.py QUART_APP="flipbook:create_app()" QUART_ENV=development .venv/bin/python3 run.py + +prod: + FLASK_SECRETS=config.py QUART_APP="flipbook:create_app()" QUART_ENV=production .venv/bin/hypercorn run diff --git a/README.md b/README.md index 486c901..7aa9f8f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1 @@ -# lza-quart-app -Template project for Quart (Python/Flask) applications. +# web3-flipbook \ No newline at end of file diff --git a/app.py b/app.py deleted file mode 100644 index 06a21e1..0000000 --- a/app.py +++ /dev/null @@ -1,7 +0,0 @@ -from myapp.factory import create_app - - -app = create_app() - -if __name__ == '__main__': - app.run() diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index 768e7e3..0000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,16 +0,0 @@ -version: '3' -services: - db: - image: postgres:9.6.15-alpine - ports: - - 127.0.0.1:5432:5432 - environment: - POSTGRES_PASSWORD: ${DB_PASS} - POSTGRES_USER: ${DB_USER:-myapp} - POSTGRES_DB: ${DB_NAME:-myapp} - volumes: - - ${DATA_DIR:-./data/postgresql}:/var/lib/postgresql/data - cache: - image: redis:latest - ports: - - 127.0.0.1:6379:6379 diff --git a/env-example b/env-example index f8ed753..3e65b24 100644 --- a/env-example +++ b/env-example @@ -1,16 +1,9 @@ DB_PASS=xxxxxxxxxxxxxxxxxxx -DB_USER=myapp -DB_NAME=myapp +DB_USER=flipbook +DB_NAME=flipbook DB_HOST=localhost - -XMR_WALLET_PATH=/data/xmr-wallet -XMR_WALLET_PASS=xxxxxxxxxxxxxxxxxxx -XMR_WALLET_RPC_USER=xxxxxxxxxx -XMR_WALLET_RPC_PASS=xxxxxxxxxxxxxxxxxxx -XMR_WALLET_RPC_ENDPOINT=http://localhost:9090 -XMR_DAEMON_URI=http://super.fast.node.xmr.pm:38089 - -SITE_NAME=myapp +WEB3_PROVIDER_URI=wss://ropsten.infura.io/ws/v3/xxxx +SITE_NAME=flipbook SECRET_KEY=xxxxxxxxxxxxxxxxxxx STATS_TOKEN=xxxxxxxxxxxxxxxxxxxx SERVER_NAME=localhost:5000 diff --git a/flipbook/__init__.py b/flipbook/__init__.py new file mode 100644 index 0000000..d6837b7 --- /dev/null +++ b/flipbook/__init__.py @@ -0,0 +1 @@ +from flipbook.factory import create_app diff --git a/flipbook/cli.py b/flipbook/cli.py new file mode 100644 index 0000000..d638494 --- /dev/null +++ b/flipbook/cli.py @@ -0,0 +1,30 @@ +import click +from quart import Blueprint, current_app + +# from flipbook.models import MyThing +from flipbook.factory import db + + +bp = Blueprint('filters', 'filters') + +@bp.cli.command('init') +def init(): + import app.models + db.create_all() + +# @bp.cli.command('delete') +# @click.argument('thing_id') +# def delete(thing_id): +# thing = MyThing.query.get(thing_id) +# if thing: +# db.session.delete(thing) +# db.session.commit() +# click.echo(f'MyThing {thing.id} was deleted') +# else: +# click.echo('MyThing ID does not exist') + +# @bp.cli.command('list') +# def list_things(): +# thing = MyThing.query.all() +# for i in thing: +# click.echo(i.id) diff --git a/flipbook/config.py b/flipbook/config.py new file mode 100644 index 0000000..180ccf6 --- /dev/null +++ b/flipbook/config.py @@ -0,0 +1,48 @@ +from pathlib import Path +from json import loads +from secrets import token_urlsafe +from os import getenv + +from dotenv import load_dotenv + + +load_dotenv() + +# Site meta +SITE_NAME = getenv('SITE_NAME', 'Flipbook') +SECRET_KEY = getenv('SECRET_KEY', token_urlsafe(12)) +SERVER_NAME = getenv('SERVER_NAME', '127.0.0.1:5000') + +# Web3 +WEB3_PROVIDER_URI = getenv('WEB3_PROVIDER_URI') +CONTRACT_ABI = loads(Path('flipbook/library/abi/flipbook.json').open().read()) +CONTRACT_ADDRESS = getenv('CONTRACT_ADDRESS') + +# Uploads +MAX_CONTENT_LENGTH = 50 * 1024 * 1024 +ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'svg'} +UPLOADS_PATH = getenv('UPLOADS_PATH', 'data/uploads') +SESSION_LIFETIME = getenv('SESSION_LIFETIME', 30) + +# Database +DB_HOST = getenv('DB_HOST', 'localhost') +DB_PORT = getenv('DB_PORT', 5432) +DB_NAME = getenv('DB_NAME', 'flipbook') +DB_USER = getenv('DB_USER', 'flipbook') +DB_PASS = getenv('DB_PASS') + +# Redis +REDIS_HOST = getenv('REDIS_HOST', 'localhost') +REDIS_PORT = getenv('REDIS_PORT', 6379) + +# Development +TEMPLATES_AUTO_RELOAD = True +DEBUG = False +if SERVER_NAME == '127.0.0.1:5000': + DEBUG = True + +# Twitter +TWITTER_CONSUMER_KEY = getenv('TWITTER_CONSUMER_KEY', None) +TWITTER_CONSUMER_SECRET = getenv('TWITTER_CONSUMER_SECRET', None) +TWITTER_ACCESS_TOKEN = getenv('TWITTER_ACCESS_TOKEN', None) +TWITTER_ACCESS_SECRET = getenv('TWITTER_ACCESS_SECRET', None) \ No newline at end of file diff --git a/flipbook/factory.py b/flipbook/factory.py new file mode 100644 index 0000000..46ebc4f --- /dev/null +++ b/flipbook/factory.py @@ -0,0 +1,37 @@ +import quart.flask_patch +from quart import Quart +from flask_login import LoginManager + +from flipbook import config + + +async def setup_db(app: Quart): + import peewee + import flipbook.models + models = peewee.Model.__subclasses__() + for m in models: + m.create_table() + +def create_app(): + app = Quart(__name__) + app.config.from_envvar('FLASK_SECRETS') + login_manager = LoginManager(app) + login_manager.logout_view = 'meta.logout' + + @login_manager.user_loader + def load_user(user_id): + from flipbook.models import Wallet + Wallet = Wallet.get(user_id) + return Wallet + + @app.before_serving + async def startup(): + from flipbook.routes import meta, api + from flipbook import filters + await setup_db(app) + app.register_blueprint(meta.bp) + app.register_blueprint(api.bp) + app.register_blueprint(filters.bp) + # app.register_blueprint(cli.bp) + + return app diff --git a/myapp/filters.py b/flipbook/filters.py similarity index 87% rename from myapp/filters.py rename to flipbook/filters.py index cb015bd..e63264c 100644 --- a/myapp/filters.py +++ b/flipbook/filters.py @@ -1,6 +1,6 @@ from datetime import datetime -from quart import Blueprint, current_app +from quart import Blueprint bp = Blueprint('filters', 'filters') diff --git a/flipbook/helpers.py b/flipbook/helpers.py new file mode 100644 index 0000000..f35f2a4 --- /dev/null +++ b/flipbook/helpers.py @@ -0,0 +1,11 @@ +from web3.auto import w3 +from eth_account.messages import encode_defunct + + +def verify_signature(message, signature, public_address): + msg = encode_defunct(text=message) + recovered = w3.eth.account.recover_message(msg, signature=signature) + if recovered.lower() == public_address.lower(): + return True + else: + return False \ No newline at end of file diff --git a/myapp/__init__.py b/flipbook/library/__init__.py similarity index 100% rename from myapp/__init__.py rename to flipbook/library/__init__.py diff --git a/flipbook/library/abi/flipbook.json b/flipbook/library/abi/flipbook.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/flipbook/library/abi/flipbook.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/myapp/library/cache.py b/flipbook/library/cache.py similarity index 91% rename from myapp/library/cache.py rename to flipbook/library/cache.py index 3372afa..fdf5ac9 100644 --- a/myapp/library/cache.py +++ b/flipbook/library/cache.py @@ -4,8 +4,8 @@ from datetime import timedelta from redis import Redis -from app.library.market import get_market_data -from app import config +from flipbook.library.market import get_market_data +from flipbook import config class Cache(object): diff --git a/myapp/library/market.py b/flipbook/library/market.py similarity index 100% rename from myapp/library/market.py rename to flipbook/library/market.py diff --git a/flipbook/models.py b/flipbook/models.py new file mode 100644 index 0000000..f972fdc --- /dev/null +++ b/flipbook/models.py @@ -0,0 +1,68 @@ +from datetime import datetime +from uuid import uuid4 + +from peewee import * +from PIL import Image +from flask_login import login_user + +from flipbook import config + + +db = SqliteDatabase(f"data/flipbook.sqlite") + + +def rand_id(): + return uuid4().hex + + +class Wallet(Model): + id = AutoField() + address = CharField(null=False, unique=True) + register_date = DateTimeField(default=datetime.utcnow) + login_date = DateTimeField(null=True) + opensea_handle = CharField(null=True) + twitter_handle = CharField(null=True) + nonce = CharField(default=rand_id()) + nonce_date = DateTimeField(default=datetime.utcnow) + + @property + def is_authenticated(self): + return True + + @property + def is_active(self): + return True + + @property + def is_anonymous(self): + return False + + def generate_nonce(self): + return rand_id() + + def change_nonce(self): + self.nonce = rand_id() + self.nonce_date = datetime.utcnow() + self.save() + + def login(self): + self.change_nonce() + self.last_login_date = datetime.utcnow() + login_user(self) + self.save() + + class Meta: + database = db + + +class Upload(Model): + id = AutoField() + token_id = IntegerField() + title = CharField() + text = CharField(null=True) + wallet = ForeignKeyField(Wallet) + image_name = CharField() + upload_date = DateTimeField(default=datetime.utcnow) + + class Meta: + database = db diff --git a/myapp/library/__init__.py b/flipbook/routes/__init__.py similarity index 100% rename from myapp/library/__init__.py rename to flipbook/routes/__init__.py diff --git a/flipbook/routes/api.py b/flipbook/routes/api.py new file mode 100644 index 0000000..c6bcf8e --- /dev/null +++ b/flipbook/routes/api.py @@ -0,0 +1,92 @@ +import json +from secrets import token_urlsafe + +from quart import Blueprint, jsonify, request +from flask_login import current_user + +from flipbook.helpers import verify_signature +from flipbook.models import Wallet, rand_id + + +bp = Blueprint('api', 'api', url_prefix='/api/v1') + + +@bp.route('/user_authenticated') +async def user_authenticated(): + """ + Check to see if sender is authenticated. + Useful for AJAX calls to "check" we're still authenticated + instead of assuming (especially with old, loaded forms/pages). + """ + return jsonify(current_user.is_authenticated) + + +@bp.route('/user_exists/') +async def user_exists(wallet_address): + """ + Check to see if a given wallet exists in the database. + This logic will help the login/connect MetaMask flow. + """ + nonce = rand_id() + wallet = Wallet.select().where( + Wallet.address == wallet_address.lower() + ).first() + if wallet: + nonce = wallet.nonce + return jsonify({ + 'user_exists': wallet is not None, + 'nonce': nonce, + 'success': True + }) + + +@bp.route('/authenticate/metamask', methods=['POST']) +async def authenticate_metamask(): + """ + This is the login/authenticate route for this dApp. + Users POST a `signedData` blob, a message signed by the user with MetaMask + (`personal_sign` method). + This route will verify the signed data against the user's public ETH + address. If no user exists, they get an entry in the database. + If user does exist, they get logged in. + """ + data = await request.get_data() + data = json.loads(data) + if current_user.is_authenticated: + return jsonify({ + 'success': False, + 'message': 'Already registered and authenticated.' + }) + + _u = Wallet.select().where( + Wallet.address == data['public_address'] + ).first() + + if _u: + if data['message'].endswith(_u.nonce): + if verify_signature(data['message'], data['signed_data'], data['public_address']): + _u.login() + return jsonify({ + 'success': True, + 'message': 'Logged in' + }) + else: + return jsonify({ + 'success': False, + 'message': 'Invalid signature' + }) + else: + return jsonify({ + 'success': False, + 'message': 'Invalid nonce in signed message' + }) + else: + w = Wallet( + address=data['public_address'].lower() + ) + w.save() + w.login() + return jsonify({ + 'success': True, + 'message': 'Registered' + }) \ No newline at end of file diff --git a/flipbook/routes/meta.py b/flipbook/routes/meta.py new file mode 100644 index 0000000..88c9adc --- /dev/null +++ b/flipbook/routes/meta.py @@ -0,0 +1,30 @@ +from quart import Blueprint, render_template, request, redirect, url_for, flash +from flask_login import logout_user + + +bp = Blueprint('meta', 'meta') + +@bp.route('/') +async def index(): + return await render_template('index.html') + + +@bp.route('/logout') +async def logout(): + """ + Log the current user out and redirect someplace within app if needed. + If 'next' is in the request args and is valid route, redirect there, + otherwise, redirect to peel off args and go home. + """ + logout_user() + if 'type' in request.args: + if request.args['type'] == 'accountsChanged': + flash('Metamask accounts have been changed, logging you out.', 'info') + if 'next' in request.args: + next_url = request.args['next'] + if next_url.startswith('/'): + return redirect(next_url) + else: + return redirect(url_for('meta.index')) + + return redirect(url_for('meta.index')) \ No newline at end of file diff --git a/myapp/static/css/main.css b/flipbook/static/css/main.css similarity index 100% rename from myapp/static/css/main.css rename to flipbook/static/css/main.css diff --git a/myapp/static/images/monero-logo.png b/flipbook/static/images/monero-logo.png similarity index 100% rename from myapp/static/images/monero-logo.png rename to flipbook/static/images/monero-logo.png diff --git a/myapp/static/js/main.js b/flipbook/static/js/main.js similarity index 100% rename from myapp/static/js/main.js rename to flipbook/static/js/main.js diff --git a/flipbook/templates/base.html b/flipbook/templates/base.html new file mode 100644 index 0000000..f031aac --- /dev/null +++ b/flipbook/templates/base.html @@ -0,0 +1,18 @@ + + + {% include 'includes/head.html' %} + + {% include 'includes/header.html' %} + {% block content %} +
+
+
+

Basic Page

+

This index.html page is a placeholder with the CSS, font and favicon. It's just waiting for you to add some content! If you need some help hit up the Skeleton documentation.

+
+
+
+ {% endblock %} + {% include 'includes/footer.html' %} + + \ No newline at end of file diff --git a/myapp/templates/includes/footer.html b/flipbook/templates/includes/footer.html similarity index 80% rename from myapp/templates/includes/footer.html rename to flipbook/templates/includes/footer.html index 9fc962b..8fd3fd4 100644 --- a/myapp/templates/includes/footer.html +++ b/flipbook/templates/includes/footer.html @@ -5,3 +5,5 @@
  • © {{ config.SITE_NAME }}. All rights reserved.
  • + +{% include 'includes/scripts.html' %} \ No newline at end of file diff --git a/myapp/templates/includes/head.html b/flipbook/templates/includes/head.html similarity index 100% rename from myapp/templates/includes/head.html rename to flipbook/templates/includes/head.html diff --git a/flipbook/templates/includes/header.html b/flipbook/templates/includes/header.html new file mode 100644 index 0000000..bbc01b4 --- /dev/null +++ b/flipbook/templates/includes/header.html @@ -0,0 +1,13 @@ + diff --git a/flipbook/templates/includes/scripts.html b/flipbook/templates/includes/scripts.html new file mode 100644 index 0000000..a8f9b8f --- /dev/null +++ b/flipbook/templates/includes/scripts.html @@ -0,0 +1,73 @@ + + +{% with messages = get_flashed_messages() %} + {% if messages %} + {% for message in messages %} +

    {{ message }}

    + {% endfor %} + {% endif %} +{% endwith %} + + + \ No newline at end of file diff --git a/flipbook/templates/index.html b/flipbook/templates/index.html new file mode 100644 index 0000000..1a1bf55 --- /dev/null +++ b/flipbook/templates/index.html @@ -0,0 +1,4 @@ +{% extends 'base.html' %} +{% block content %} +

    fuck this

    +{% endblock %} \ No newline at end of file diff --git a/myapp/cli.py b/myapp/cli.py deleted file mode 100644 index 5000486..0000000 --- a/myapp/cli.py +++ /dev/null @@ -1,30 +0,0 @@ -import click -from quart import Blueprint, current_app - -from myapp.models import MyThing -from myapp.factory import db - - -bp = Blueprint('filters', 'filters') - -@bp.cli.command('init') -def init(): - import app.models - db.create_all() - -@bp.cli.command('delete') -@click.argument('thing_id') -def delete(thing_id): - thing = MyThing.query.get(thing_id) - if thing: - db.session.delete(thing) - db.session.commit() - click.echo(f'MyThing {thing.id} was deleted') - else: - click.echo('MyThing ID does not exist') - -@bp.cli.command('list') -def list_things(): - thing = MyThing.query.all() - for i in thing: - click.echo(i.id) diff --git a/myapp/config.py b/myapp/config.py deleted file mode 100644 index 7efc5c0..0000000 --- a/myapp/config.py +++ /dev/null @@ -1,33 +0,0 @@ -from dotenv import load_dotenv -from secrets import token_urlsafe -from os import getenv - - -load_dotenv() - -# Site meta -SITE_NAME = getenv('SITE_NAME', 'MyApp') -SECRET_KEY = getenv('SECRET_KEY') -STATS_TOKEN = getenv('STATS_TOKEN', token_urlsafe(8)) -SERVER_NAME = getenv('SERVER_NAME', 'localhost:5000') - -# Crypto RPC -XMR_WALLET_PASS = getenv('XMR_WALLET_PASS') -XMR_WALLET_RPC_USER = getenv('XMR_WALLET_RPC_USER') -XMR_WALLET_RPC_PASS = getenv('XMR_WALLET_RPC_PASS') -XMR_WALLET_RPC_ENDPOINT = getenv('XMR_WALLET_RPC_ENDPOINT') -XMR_DAEMON_URI = getenv('XMR_DAEMON_URI') - -# Database -DB_HOST = getenv('DB_HOST', 'localhost') -DB_PORT = getenv('DB_PORT', 5432) -DB_NAME = getenv('DB_NAME', 'myapp') -DB_USER = getenv('DB_USER', 'myapp') -DB_PASS = getenv('DB_PASS') - -# Redis -REDIS_HOST = getenv('REDIS_HOST', 'localhost') -REDIS_PORT = getenv('REDIS_PORT', 6379) - -# Development -TEMPLATES_AUTO_RELOAD = True diff --git a/myapp/factory.py b/myapp/factory.py deleted file mode 100644 index a7c5a53..0000000 --- a/myapp/factory.py +++ /dev/null @@ -1,37 +0,0 @@ -import quart.flask_patch -from quart import Quart -from flask_sqlalchemy import SQLAlchemy - -from myapp import config - - -db = SQLAlchemy() - -async def _setup_db(app: Quart): - uri = 'postgresql+psycopg2://{user}:{pw}@{host}:{port}/{db}'.format( - user=config.DB_USER, - pw=config.DB_PASS, - host=config.DB_HOST, - port=config.DB_PORT, - db=config.DB_NAME - ) - app.config['SQLALCHEMY_DATABASE_URI'] = uri - app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False - db = SQLAlchemy(app) - -def create_app(): - app = Quart(__name__) - app.config.from_envvar('QUART_SECRETS') - - - @app.before_serving - async def startup(): - from myapp.routes import meta, api - from myapp import filters - await _setup_db(app) - app.register_blueprint(meta.bp) - app.register_blueprint(api.bp) - app.register_blueprint(filters.bp) - # app.register_blueprint(cli.bp) - - return app diff --git a/myapp/forms.py b/myapp/forms.py deleted file mode 100644 index 8849566..0000000 --- a/myapp/forms.py +++ /dev/null @@ -1,8 +0,0 @@ -from flask_wtf import FlaskForm -from wtforms import StringField, BooleanField -from wtforms.validators import DataRequired - - -class Login(FlaskForm): - email = StringField('Email Address:', validators=[DataRequired()], render_kw={"placeholder": "Email", "class": "form-control", "type": "email"}) - password = StringField('Password:', validators=[DataRequired()], render_kw={"placeholder": "Password", "class": "form-control", "type": "password"}) diff --git a/myapp/models.py b/myapp/models.py deleted file mode 100644 index f8f5f56..0000000 --- a/myapp/models.py +++ /dev/null @@ -1,38 +0,0 @@ -from datetime import datetime -from uuid import uuid4 - -from sqlalchemy.sql import func - -from myapp.factory import db -from myapp import config - - -def rand_id(): - return uuid4().hex - -class MyThing(db.Model): - __tablename__ = 'swaps' - - # Meta - id = db.Column(db.Integer, primary_key=True) - # id = db.Column(db.String(80), primary_key=True, default=rand_id) # hex based id - date = db.Column(db.DateTime, server_default=func.now()) - my_bool = db.Column(db.Boolean) - my_int = db.Column(db.Integer) - my_str = db.Column(db.String(150)) - completed = db.Column(db.Boolean, default=False) - completed_date = db.Column(db.DateTime, nullable=True) - - def __repr__(self): - return self.id - - def hours_elapsed(self): - now = datetime.utcnow() - if since_completed: - if self.completed_date: - diff = now - self.completed_date - else: - return 0 - else: - diff = now - self.date - return diff.total_seconds() / 60 / 60 diff --git a/myapp/routes/__init__.py b/myapp/routes/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/myapp/routes/api.py b/myapp/routes/api.py deleted file mode 100644 index 90b9088..0000000 --- a/myapp/routes/api.py +++ /dev/null @@ -1,11 +0,0 @@ -from quart import Blueprint, jsonify - - -bp = Blueprint('api', 'api') - -@bp.route('/api/test') -async def get_prices(): - return jsonify({ - 'test': True, - 'message': 'This is only a test.' - }) diff --git a/myapp/routes/meta.py b/myapp/routes/meta.py deleted file mode 100644 index 36cd545..0000000 --- a/myapp/routes/meta.py +++ /dev/null @@ -1,8 +0,0 @@ -from quart import Blueprint, render_template - - -bp = Blueprint('meta', 'meta') - -@bp.route('/') -async def index(): - return await render_template('index.html') diff --git a/myapp/templates/includes/header.html b/myapp/templates/includes/header.html deleted file mode 100644 index 07001a0..0000000 --- a/myapp/templates/includes/header.html +++ /dev/null @@ -1,9 +0,0 @@ - diff --git a/myapp/templates/includes/scripts.html b/myapp/templates/includes/scripts.html deleted file mode 100644 index c01fef3..0000000 --- a/myapp/templates/includes/scripts.html +++ /dev/null @@ -1,9 +0,0 @@ - - -{% with messages = get_flashed_messages() %} - {% if messages %} - {% for message in messages %} -

    {{ message }}

    - {% endfor %} - {% endif %} -{% endwith %} diff --git a/myapp/templates/index.html b/myapp/templates/index.html deleted file mode 100644 index a5fcffb..0000000 --- a/myapp/templates/index.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - {% include 'includes/head.html' %} - - -
    - - - - - - {% include 'includes/footer.html' %} - -
    - - {% include 'includes/scripts.html' %} - - - diff --git a/requirements.txt b/requirements.txt index d170a84..52d0915 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1,12 @@ Flask -Flask-SQLAlchemy -Flask-WTF flask-login -gunicorn +hypercorn Pillow -psycopg2-binary python-dotenv -qrcode redis requests -SQLAlchemy -WTForms quart +peewee +arrow +black +web3 diff --git a/run.py b/run.py new file mode 100644 index 0000000..f6be6ae --- /dev/null +++ b/run.py @@ -0,0 +1,7 @@ +from flipbook.factory import create_app + + +app = create_app() + +if __name__ == '__main__': + app.run(use_reloader=True)