From bb960d99fde46db04a7dfe82c54299337f31b4b9 Mon Sep 17 00:00:00 2001 From: marco Date: Sun, 7 Aug 2022 20:15:02 +0800 Subject: [PATCH] post and get slates --- .gitignore | 10 +++ LICENSE | 9 ++ README.md | 48 +++++++++++ funding/__init__.py | 0 funding/bin/__init__.py | 0 funding/bin/utils.py | 7 ++ funding/bin/utils_request.py | 28 ++++++ funding/bin/utils_time.py | 163 +++++++++++++++++++++++++++++++++++ funding/cache.py | 47 ++++++++++ funding/factory.py | 81 +++++++++++++++++ funding/orm.py | 114 ++++++++++++++++++++++++ funding/routes.py | 64 ++++++++++++++ requirements.txt | 17 ++++ run_dev.py | 8 ++ settings.py_example | 36 ++++++++ 15 files changed, 632 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 funding/__init__.py create mode 100644 funding/bin/__init__.py create mode 100644 funding/bin/utils.py create mode 100644 funding/bin/utils_request.py create mode 100644 funding/bin/utils_time.py create mode 100644 funding/cache.py create mode 100644 funding/factory.py create mode 100644 funding/orm.py create mode 100644 funding/routes.py create mode 100644 requirements.txt create mode 100644 run_dev.py create mode 100644 settings.py_example diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..55b4e4a --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*__pycache__* +settings.py +.cache +.env +htmlcov +.coverage +funding/static/qr/* +yourvirtualenviornment/ +venv/ +.idea/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..50f7271 --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2018 Wownero Inc., a Monero Enterprise Alliance partner company + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3a25569 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# Epic Post + +A simple alternative to epic box. + +### Install postgres +``` +sudo apt install aptitude +sudo aptitude install postgresql postgresql-contrib +``` +https://tecadmin.net/how-to-install-postgresql-in-ubuntu-20-04/ + + +### Web application + +Download application and configure. + +``` + +sudo apt install libjpeg-dev libpng-dev python3 redis-server postgresql-server-dev-* +sudo apt install python3-virtualenv +sudo apt install python3-venv +git clone https://github.com/firoorg/fcs.git +cd epicpost +python3 -m venv yourvirtualenviornment +source yourvirtualenviornment/bin/activate +pip uninstall pillow +pip install wheel +pip install -r requirements.txt +CC="cc -mavx2" pip install -U --force-reinstall pillow-simd +cp settings.py_example settings.py +- change settings.py accordingly +``` +eg change the psql_pass to your database password, psql_db to your database name + +Run the application: + +```bash +python run_dev.py +``` + +Beware `run_dev.py` is meant as a development server. + +When running behind nginx/apache, inject `X-Forwarded-For`. + + +### License + +© 2022 WTFPL – Do What the Fuck You Want to Public License diff --git a/funding/__init__.py b/funding/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/funding/bin/__init__.py b/funding/bin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/funding/bin/utils.py b/funding/bin/utils.py new file mode 100644 index 0000000..580df07 --- /dev/null +++ b/funding/bin/utils.py @@ -0,0 +1,7 @@ +from datetime import datetime, date + + +def json_encoder(obj): + if isinstance(obj, (datetime, date)): + return obj.isoformat() + raise TypeError("Type %s not serializable" % type(obj)) diff --git a/funding/bin/utils_request.py b/funding/bin/utils_request.py new file mode 100644 index 0000000..b4bd07e --- /dev/null +++ b/funding/bin/utils_request.py @@ -0,0 +1,28 @@ +from flask import request +import settings +from funding.factory import app, db + + +@app.before_request +def before_request(): + pass + + +@app.after_request +def after_request(res): + res.headers.add('Accept-Ranges', 'bytes') + + if request.full_path.startswith('/api/'): + res.headers.add('Access-Control-Allow-Origin', '*') + + if settings.DEBUG: + res.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate' + res.headers['Pragma'] = 'no-cache' + res.headers['Expires'] = '0' + res.headers['Cache-Control'] = 'public, max-age=0' + return res + + +@app.errorhandler(404) +def error(err): + return 'Error', 404 diff --git a/funding/bin/utils_time.py b/funding/bin/utils_time.py new file mode 100644 index 0000000..3848b07 --- /dev/null +++ b/funding/bin/utils_time.py @@ -0,0 +1,163 @@ +from datetime import datetime, date +from dateutil import parser +import math +import calendar + + +class TimeMagic(): + def __init__(self): + self.now = datetime.now() + self.weekdays_en = { + 0: 'monday', + 1: 'tuesday', + 2: 'wednesday', + 3: 'thursday', + 4: 'friday', + 5: 'saturday', + 6: 'sunday' + } + self.months_en = { + 0: 'january', + 1: 'february', + 2: 'march', + 3: 'april', + 4: 'may', + 5: 'june', + 6: 'july', + 7: 'august', + 8: 'september', + 9: 'october', + 10: 'november', + 11: 'december' + } + + def get_weekday_from_datetime(self, dt): + n = dt.today().weekday() + return n + + def week_number_get(self): + now = datetime.now() + return int(now.strftime("%V")) + + def week_number_verify(self, week_nr): + if week_nr > 0 or week_nr <= 53: + return True + + def get_weeknr_from_date(self, date): + return date.strftime("%V") + + def year_verify(self, year): + if isinstance(year, str): + try: + year = int(year) + except Exception as ex: + return False + + if 2000 <= year <= 2030: + return True + + def get_day_number(self): + dt = datetime.now() + return dt.today().weekday() + + def get_month_nr(self): + return datetime.now().strftime("%m") + + def get_daynr_from_weekday(self, weekday): + for k, v in self.weekdays_en.items(): + if v == weekday: + return k + + def get_day_from_daynr(self, nr): + return self.weekdays_en[nr] + + def get_month_from_weeknr(self, nr): + nr = float(nr) / float(4) + if nr.is_integer(): + nr -= 1 + else: + nr = math.floor(nr) + if nr < 0: + nr = 0 + + return self.months_en[nr] + + def get_month_nr_from_month(self, month): + for k, v in self.months_en.items(): + if v == month: + return k + + def get_year(self): + return date.today().year + + def get_month(self): + return date.today().month + + def get_amount_of_days_from_month_nr(self, month_nr): + try: + max_days = calendar.monthrange(self.get_year(), int(month_nr))[1] + return max_days + except Exception as e: + pass + + def from_till(self): + m = self.get_month() + d = self.get_amount_of_days_from_month_nr(m) + y = self.get_year() + + if len(str(d)) == 1: + d = '0' + str(d) + else: + d = str(d) + + if len(str(m)) == 1: + m = '0' + str(m) + else: + m = str(m) + + f = '%s/01/%s' % (m, y) + t = '%s/%s/%s' % (m, d, y) + + return {'date_from': f, 'date_till': t} + + def ago_dt(self, datetime): + return self.ago(datetime) + + def ago_str(self, date_str): + date = parser.parse(date_str) + return self.ago(date) + + def ago(self, datetime=None, epoch=None): + import math + + if epoch: + td = int(epoch) + else: + if datetime: + td = (self.now - datetime).total_seconds() + else: + return None + + if td < 60: + if td == 1: + return '%s second ago' + else: + return 'Just now' + elif 60 <= td < 3600: + if 60 <= td < 120: + return '1 minute ago' + else: + return '%s minutes ago' % str(int(math.floor(td / 60))) + elif 3600 <= td < 86400: + if 3600 <= td < 7200: + return '1 hour ago' + else: + return '%s hours ago' % str(int(math.floor(td / 60 / 60))) + elif td >= 86400: + if td <= 86400 < 172800: + return '1 day ago' + else: + x = int(math.floor(td / 24 / 60 / 60)) + if x == 1: + return '1 day ago' + return '%s days ago' % str(x) diff --git a/funding/cache.py b/funding/cache.py new file mode 100644 index 0000000..e303c2e --- /dev/null +++ b/funding/cache.py @@ -0,0 +1,47 @@ +import json + +import redis +from flask_session import RedisSessionInterface + +import settings +from funding.bin.utils import json_encoder + + +def redis_args(): + args = { + "host": settings.REDIS_HOST, + "port": settings.REDIS_PORT, + 'socket_connect_timeout': 2, + 'socket_timeout': 2, + 'retry_on_timeout': True, + 'decode_responses': True + } + if settings.REDIS_PASSWD: + args["password"] = settings.REDIS_PASSWD + return args + + +class JsonRedisSerializer: + @staticmethod + def loads(val): + try: + return json.loads(val).get("wow", {}) + except ValueError: + return + + @staticmethod + def dumps(val): + try: + return json.dumps({"wow": val}) + except ValueError: + return + + +class JsonRedis(RedisSessionInterface): + serializer = JsonRedisSerializer + + def __init__(self, key_prefix, use_signer=False, decode_responses=True): + super(JsonRedis, self).__init__( + redis=redis.Redis(**redis_args()), + key_prefix=key_prefix, + use_signer=use_signer) diff --git a/funding/factory.py b/funding/factory.py new file mode 100644 index 0000000..0667af8 --- /dev/null +++ b/funding/factory.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +import settings +from flask import Flask +from flask_caching import Cache +from flask_session import Session +from flask_sqlalchemy import SQLAlchemy +from werkzeug.middleware.proxy_fix import ProxyFix +import redis + +app = None +cache = None +db = None +bcrypt = None + + +def _setup_cache(app: Flask): + global cache + + cache_config = { + "CACHE_TYPE": "redis", + "CACHE_DEFAULT_TIMEOUT": 60, + "CACHE_KEY_PREFIX": "wow_cache_", + "CACHE_REDIS_PORT": settings.REDIS_PORT + } + + if settings.REDIS_PASSWD: + cache_config["CACHE_REDIS_PASSWORD"] = settings.REDIS_PASSWD + + app.config.from_mapping(cache_config) + cache = Cache(app) + + +def _setup_session(app: Flask): + app.config['SESSION_TYPE'] = 'redis' + app.config['SESSION_COOKIE_NAME'] = 'bar' + app.config['SESSION_REDIS'] = redis.from_url(settings.REDIS_URI) + Session(app) # defaults to timedelta(days=31) + + +def _setup_db(app: Flask): + global db + uri = 'postgresql+psycopg2://{user}:{pw}@{url}/{db}'.format( + user=settings.PSQL_USER, + pw=settings.PSQL_PASS, + url=settings.PSQL_HOST, + db=settings.PSQL_DB) + app.config['SQLALCHEMY_DATABASE_URI'] = uri + db = SQLAlchemy(app) + import funding.orm + db.create_all() + + +def create_app(): + global app + global db + global cache + global bcrypt + + app = Flask(import_name=__name__, + static_folder='static', + template_folder='templates') + app.config.from_object(settings) + app.config['PERMANENT_SESSION_LIFETIME'] = 2678400 + app.config['JSONIFY_PRETTYPRINT_REGULAR'] = False + app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 30 + app.secret_key = settings.SECRET + + app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1) + + _setup_cache(app) + _setup_session(app) + _setup_db(app) + + + + # import routes + from funding import routes + from funding.bin import utils_request + + app.app_context().push() + return app diff --git a/funding/orm.py b/funding/orm.py new file mode 100644 index 0000000..c8f56e0 --- /dev/null +++ b/funding/orm.py @@ -0,0 +1,114 @@ +from datetime import datetime +import string +import random + +import requests +from sqlalchemy.orm import relationship, backref +import sqlalchemy as sa +from sqlalchemy.orm import scoped_session, sessionmaker, relationship +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.types import Float +from sqlalchemy_json import MutableJson +import hashlib +import json + +import settings +from funding.factory import db, cache + +base = declarative_base(name="Model") + + +def val_address(address): + if len(address) != 52 or address[0] != 'e' or address[1] != 's': + raise Exception("invalid epic address") + + +class Address(db.Model): + __tablename__ = "addresses" + id = db.Column('user_id', db.Integer, primary_key=True) + address = db.Column(db.String(52), unique=True) + + @classmethod + def find_address(cls, address): + from funding.factory import db + q = cls.query + q = q.filter(Address.address == address) + result = q.first() + if not result: + return + return result + + def __repr__(self): + return "" % ( + self.address) + + @classmethod + def add(cls, address): + from funding.factory import db + + try: + previous = cls.query.filter(Address.address == address).first() + if previous is not None: + print("was inputted before") + return + # validate incoming username/email + val_address(address) + new_address = Address(address=address) + db.session.add(new_address) + db.session.commit() + db.session.flush() + return new_address + except Exception as ex: + db.session.rollback() + raise + + +class Slate(db.Model): + __tablename__ = "slates" + id = db.Column(db.Integer, primary_key=True) + slate = db.Column(db.String()) + # TODO: clear slatse that have been in the database past a certain time + posted_time = db.Column(db.DateTime) + receivingAddress = db.Column(db.String(52), db.ForeignKey('addresses.address'), nullable=False) + + def __init__(self, slate, receivingAddress): + from funding.factory import bcrypt + self.slate = slate + self.receivingAddress = receivingAddress + self.posted_time = datetime.utcnow() + + def __repr__(self): + return "