From ad508e1dc5f1196fdc9f6d4877456af1f41c9432 Mon Sep 17 00:00:00 2001 From: Sandro Lutz <code@temparus.ch> Date: Mon, 13 Jul 2020 10:21:43 +0200 Subject: [PATCH] Add basics for telegram bot --- Dockerfile | 9 ++-- Dockerfile.development | 6 +++ README.md | 3 ++ app/__init__.py | 8 ++++ app/app_context.py | 7 ++++ app/bouncer/views.py | 34 +++++++-------- app/controllers/__init__.py | 2 +- .../{free_spot.py => free_workplaces.py} | 34 +++++++-------- app/controllers/lock.py | 2 +- app/controllers/record.py | 12 +++--- app/controllers/reservation.py | 12 +++--- app/controllers/user.py | 2 +- app/exceptions.py | 4 +- app/models/reservation.py | 2 +- app/static/css/style.css | 2 +- app/templates/base_authenticated.html | 2 +- app/templates/bouncer/home_anonymous.html | 4 +- app/templates/bouncer/home_authenticated.html | 2 +- app/templates/login/register_success.html | 2 +- bot/__init__.py | 1 + bot/bot.py | 25 +++++++++++ bot/commands/free_workplaces.py | 10 +++++ bot/commands/help.py | 40 ++++++++++++++++++ entrypoint.sh | 2 +- instance/config.example.py | 4 +- manage.sh | 42 +++++++++++++++++-- requirements.in | 1 + requirements.txt | 9 +++- run_dev.py | 11 ++++- run_prod.py | 10 ++++- 30 files changed, 234 insertions(+), 70 deletions(-) create mode 100644 app/app_context.py rename app/controllers/{free_spot.py => free_workplaces.py} (56%) create mode 100644 bot/__init__.py create mode 100644 bot/bot.py create mode 100644 bot/commands/free_workplaces.py create mode 100644 bot/commands/help.py mode change 100644 => 100755 entrypoint.sh diff --git a/Dockerfile b/Dockerfile index 9066f4d..f479679 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,23 +8,26 @@ EXPOSE 8080 # Install bjoern and dependencies for install (we need to keep libev) RUN apk add --no-cache --virtual .deps \ - musl-dev python-dev gcc git && \ + musl-dev gcc git && \ apk add --no-cache libev-dev && \ apk add --no-cache libffi-dev libressl-dev && \ pip install bjoern # Copy files to /bastlibouncer directory, install requirements COPY ./ /bastlibouncer + RUN pip install -r /bastlibouncer/requirements.txt # Cleanup dependencies RUN apk del .deps # Update permissions for entrypoint -RUN chmod 755 entrypoint.sh +RUN chmod 755 /bastlibouncer/entrypoint.sh # Switch user USER bastlibouncer +ENTRYPOINT [ "/bastlibouncer/entrypoint.sh" ] + # Start application -CMD [ "./entrypoint.sh" ] +CMD [ "python3", "run_prod.py" ] diff --git a/Dockerfile.development b/Dockerfile.development index 38ed091..51a20ef 100644 --- a/Dockerfile.development +++ b/Dockerfile.development @@ -8,6 +8,12 @@ EXPOSE 5000 # Copy files to /bastlibouncer directory, install requirements COPY . /bastlibouncer + +# Install dependencies for install +RUN apk add --no-cache --virtual .deps \ + musl-dev gcc git && \ + apk add --no-cache libffi-dev libressl-dev + RUN pip install pip-tools && \ pip install -r /bastlibouncer/requirements.txt diff --git a/README.md b/README.md index c469e46..a1cefff 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,9 @@ This is an attendance manager/tracker to ensure COVID-19 restrictions. **IMPORTANT**: DO NOT CHANGE already commited migrations files! +All files related to data management and flask are in `./app`. +The files for the telegram bot are in `./bot`. + Use the script `manage.sh` for local development. ```shell diff --git a/app/__init__.py b/app/__init__.py index 4a689dc..6cbeca6 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -4,17 +4,25 @@ from flask_migrate import Migrate from flask_bootstrap import Bootstrap from flask_wtf.csrf import CSRFProtect from flask_mail import Mail +from .app_context import AppContext # global variable initialization db = SQLAlchemy() mail = Mail() csrf = CSRFProtect() +appctx = AppContext() + +def app_context(): + return appctx.app_context() def create_app(): # initialize flask app and configure app = Flask(__name__, instance_relative_config=True) app.config.from_pyfile('config.py') + # initialize AppContext for usage outside the flask application + appctx.init_app(app) + # initialize Mail mail.init_app(app) diff --git a/app/app_context.py b/app/app_context.py new file mode 100644 index 0000000..2f36294 --- /dev/null +++ b/app/app_context.py @@ -0,0 +1,7 @@ +class AppContext(object): + + def init_app(self, new_app): + self.app = new_app + + def app_context(self): + return self.app.app_context() diff --git a/app/bouncer/views.py b/app/bouncer/views.py index befea76..ed08ef8 100644 --- a/app/bouncer/views.py +++ b/app/bouncer/views.py @@ -2,12 +2,12 @@ from flask import flash, redirect, render_template, url_for, request, abort, mak from . import bouncer_bp from ..auth import login_required, is_authenticated, get_authenticated_user -from ..controllers import FreeSpotController, UserController, ReservationController, RecordController -from ..exceptions import NoFreeSpotError +from ..controllers import FreeWorkplacesController, UserController, ReservationController, RecordController +from ..exceptions import NoFreeWorkplaceError @bouncer_bp.route('/', methods=['GET', 'POST']) def home(): - free_spots = FreeSpotController.get_free_spots() + free_workplaces = FreeWorkplacesController.get_free_workplaces() if is_authenticated(): user = get_authenticated_user() @@ -20,23 +20,23 @@ def home(): UserController.start_record(user) flash('You are allowed to enter.') return redirect(url_for('bouncer.home')) - except NoFreeSpotError: - flash('No free spots available! Try again later.', 'error') + except NoFreeWorkplaceError: + flash('No free workspaces available! Try again later.', 'error') if request.form['action'] == 'reserve': try: UserController.reserve(user) flash('Your reservation has been recorded. Your request for entry will be granted immediately as long as your reservation is valid.') return redirect(url_for('bouncer.home')) - except NoFreeSpotError: - flash('No free spots available! Try again later.', 'error') + except NoFreeWorkplaceError: + flash('No free workspaces available! Try again later.', 'error') - return make_response(render_template('bouncer/home_authenticated.html', title='Home', free_spots=free_spots, user=user, reservation=reservation, record=record)) - return make_response(render_template('bouncer/home_anonymous.html', title='Home', free_spots=free_spots)) + return make_response(render_template('bouncer/home_authenticated.html', title='Home', free_workplaces=free_workplaces, user=user, reservation=reservation, record=record)) + return make_response(render_template('bouncer/home_anonymous.html', title='Home', free_workplaces=free_workplaces)) @bouncer_bp.route('/confirm/leave', methods=['GET', 'POST']) @login_required def confirm_leave(): - free_spots = FreeSpotController.get_free_spots() + free_workplaces = FreeWorkplacesController.get_free_workplaces() user = get_authenticated_user() record = RecordController.get_active_record(user) @@ -54,12 +54,12 @@ def confirm_leave(): flash('You have been checked out successfully.') return redirect(url_for('bouncer.home')) - return make_response(render_template('bouncer/confirm_leave.html', title='Confirm: leave', user=user, free_spots=free_spots)) + return make_response(render_template('bouncer/confirm_leave.html', title='Confirm: leave', user=user, free_workplaces=free_workplaces)) @bouncer_bp.route('/confirm/enter', methods=['GET', 'POST']) @login_required def confirm_enter(): - free_spots = FreeSpotController.get_free_spots() + free_workplaces = FreeWorkplacesController.get_free_workplaces() user = get_authenticated_user() if not user.is_confirmed: @@ -76,17 +76,17 @@ def confirm_enter(): UserController.start_record(user) flash('You are cleared for entry.') return redirect(url_for('bouncer.home')) - except NoFreeSpotError: - flash ('No free spots available! Try again later.', 'error') + except NoFreeWorkplaceError: + flash ('No free workplaces available! Try again later.', 'error') return redirect(url_for('bouncer.home')) - return make_response(render_template('bouncer/confirm_enter.html', title='Confirm: enter', user=user, free_spots=free_spots)) + return make_response(render_template('bouncer/confirm_enter.html', title='Confirm: enter', user=user, free_workplaces=free_workplaces)) @bouncer_bp.route('/confirm/cancel', methods=['GET', 'POST']) @login_required def confirm_cancel(): - free_spots = FreeSpotController.get_free_spots() + free_workplaces = FreeWorkplacesController.get_free_workplaces() user = get_authenticated_user() reservation = ReservationController.get_active_reservation(user) @@ -104,4 +104,4 @@ def confirm_cancel(): flash('Your reservation has been cancelled.') return redirect(url_for('bouncer.home')) - return make_response(render_template('bouncer/confirm_cancel.html', title='Confirm: cancel reservation', user=user, free_spots=free_spots)) + return make_response(render_template('bouncer/confirm_cancel.html', title='Confirm: cancel reservation', user=user, free_workplaces=free_workplaces)) diff --git a/app/controllers/__init__.py b/app/controllers/__init__.py index fb7217f..3ad6ce7 100644 --- a/app/controllers/__init__.py +++ b/app/controllers/__init__.py @@ -1,4 +1,4 @@ from .record import RecordController -from .free_spot import FreeSpotController +from .free_workplaces import FreeWorkplacesController from .reservation import ReservationController from .user import UserController diff --git a/app/controllers/free_spot.py b/app/controllers/free_workplaces.py similarity index 56% rename from app/controllers/free_spot.py rename to app/controllers/free_workplaces.py index bbd65ee..a6bd49a 100644 --- a/app/controllers/free_spot.py +++ b/app/controllers/free_workplaces.py @@ -5,35 +5,35 @@ from flask import current_app from app.models import Record, Reservation from app import db from .lock import Lock -from ..exceptions import NoFreeSpotError +from ..exceptions import NoFreeWorkplaceError -class FreeSpotController(): - spot_lock = Lock.spot_lock +class FreeWorkplacesController(): + workplaces_lock = Lock.workplaces_lock @classmethod - def has_free_spots(cls): - with cls.spot_lock: - return cls._has_free_spots() + def has_free_workplaces(cls): + with cls.workplaces_lock: + return cls._has_free_workplaces() @classmethod - def get_free_spots(cls): - with cls.spot_lock: - return cls._get_free_spots() + def get_free_workplaces(cls): + with cls.workplaces_lock: + return cls._get_free_workplaces() @classmethod - def check_free_spots(cls): - if not cls._has_free_spots: - raise NoFreeSpotError + def check_free_workplaces(cls): + if not cls._has_free_workplaces: + raise NoFreeWorkplaceError # ---------- PRIVATE METHODS BELOW ---------- @classmethod - def _get_free_spots(cls): - total_spots = current_app.config.get('TOTAL_SPOTS') + def _get_free_workplaces(cls): + total_workplaces = current_app.config.get('TOTAL_WORKPLACES') now = datetime.now() active_records_count = db.session.query(func.count(Record._id)) \ @@ -48,8 +48,8 @@ class FreeSpotController(): # check total of active records and valid reservations. - return total_spots - active_records_count - valid_reservations_count + return total_workplaces - active_records_count - valid_reservations_count @classmethod - def _has_free_spots(cls): - return cls._get_free_spots() > 0 \ No newline at end of file + def _has_free_workplaces(cls): + return cls._get_free_workplaces() > 0 \ No newline at end of file diff --git a/app/controllers/lock.py b/app/controllers/lock.py index 8f492dc..ec6fda5 100644 --- a/app/controllers/lock.py +++ b/app/controllers/lock.py @@ -1,4 +1,4 @@ import threading class Lock(): - spot_lock = threading.Lock() + workplaces_lock = threading.Lock() diff --git a/app/controllers/record.py b/app/controllers/record.py index 5970202..311de12 100644 --- a/app/controllers/record.py +++ b/app/controllers/record.py @@ -4,20 +4,20 @@ from sqlalchemy import DateTime, cast, func, or_ from app import db from app.models import Record from .lock import Lock -from .free_spot import FreeSpotController +from .free_workplaces import FreeWorkplacesController from ..exceptions import ReservationExpiredError, UserNotConfirmedError class RecordController(): - spot_lock = Lock.spot_lock + workplaces_lock = Lock.workplaces_lock @classmethod def create(cls, user): - with cls.spot_lock: + with cls.workplaces_lock: if not user.is_confirmed: raise UserNotConfirmedError - FreeSpotController.check_free_spots() + FreeWorkplacesController.check_free_workplaces() record = Record() record.user = user record.time_start = datetime.now() @@ -29,7 +29,7 @@ class RecordController(): @classmethod def create_from_reservation(cls, reservation): - with cls.spot_lock: + with cls.workplaces_lock: if not reservation.user.is_confirmed: raise UserNotConfirmedError @@ -64,6 +64,6 @@ class RecordController(): @classmethod def terminate(cls, record): - with cls.spot_lock: + with cls.workplaces_lock: record.time_end = datetime.now() db.session.commit() diff --git a/app/controllers/reservation.py b/app/controllers/reservation.py index 1047050..f6fd438 100644 --- a/app/controllers/reservation.py +++ b/app/controllers/reservation.py @@ -5,17 +5,17 @@ from flask import current_app from app import db from app.models import Reservation from .lock import Lock -from .free_spot import FreeSpotController -from ..exceptions import ReservationExpiredError, NoFreeSpotError, UserNotConfirmedError +from .free_workplaces import FreeWorkplacesController +from ..exceptions import ReservationExpiredError, NoFreeWorkplaceError, UserNotConfirmedError class ReservationController(): - spot_lock = Lock.spot_lock + workplaces_lock = Lock.workplaces_lock @classmethod def create(cls, user): - with cls.spot_lock: - FreeSpotController.check_free_spots() + with cls.workplaces_lock: + FreeWorkplacesController.check_free_workplaces() if not user.is_confirmed: raise UserNotConfirmedError @@ -45,6 +45,6 @@ class ReservationController(): @classmethod def cancel(cls, reservation): - with cls.spot_lock: + with cls.workplaces_lock: reservation.time_end = datetime.now() db.session.commit() diff --git a/app/controllers/user.py b/app/controllers/user.py index a98d07e..8f3ca6e 100644 --- a/app/controllers/user.py +++ b/app/controllers/user.py @@ -11,7 +11,7 @@ from .reservation import ReservationController from .record import RecordController from ..exceptions import ActiveReservationExistsError, \ ActiveRecordExistsError, NoActiveRecordError, ReservationExpiredError, \ - NoFreeSpotError, UserRegistrationInvalidDataError + NoFreeWorkplaceError, UserRegistrationInvalidDataError class UserController(): diff --git a/app/exceptions.py b/app/exceptions.py index 40cf14d..2d1a555 100644 --- a/app/exceptions.py +++ b/app/exceptions.py @@ -5,8 +5,8 @@ class Error(Exception): pass -class NoFreeSpotError(Error): - """Raised when no free spots are available""" +class NoFreeWorkplaceError(Error): + """Raised when no free workplaces are available""" pass diff --git a/app/models/reservation.py b/app/models/reservation.py index 6c994b9..855280e 100644 --- a/app/models/reservation.py +++ b/app/models/reservation.py @@ -3,7 +3,7 @@ from datetime import datetime, timedelta class Reservation(db.Model): """ - Spot reservation entry. + Workplace reservation entry. """ __tablename__ = 'reservations' diff --git a/app/static/css/style.css b/app/static/css/style.css index d6c7acd..d95a679 100644 --- a/app/static/css/style.css +++ b/app/static/css/style.css @@ -86,7 +86,7 @@ p.copyright { .error { text-align: center; } -.free_spots { +.free_workplaces { font-size: 1.2em; font-weight: bold; text-decoration: underline; diff --git a/app/templates/base_authenticated.html b/app/templates/base_authenticated.html index f10bdb3..4cc3223 100644 --- a/app/templates/base_authenticated.html +++ b/app/templates/base_authenticated.html @@ -8,7 +8,7 @@ <div class="center-narrow"> <h1>Bastli Bouncer</h1> <br/> - <p>We currently have <span class="free_spots">{{ free_spots }} free spots</span> in our workshop.</p> + <p>We currently have <span class="free_workplaces">{{ free_workplaces }} free workplaces</span> in our workshop.</p> <p> Welcome, {{ user.name }} ({{ user.email }}). <a class="btn btn-default" role="button" href="{{ url_for('login.logout') }}">Logout</a> diff --git a/app/templates/bouncer/home_anonymous.html b/app/templates/bouncer/home_anonymous.html index f6bc104..3baf77c 100644 --- a/app/templates/bouncer/home_anonymous.html +++ b/app/templates/bouncer/home_anonymous.html @@ -8,9 +8,9 @@ <div class="center-narrow"> <h1>Bastli Bouncer</h1> <br/> - <p>We currently have <span class="free_spots">{{ free_spots }} free spots</span> in our workshop.</p> + <p>We currently have <span class="free_workplaces">{{ free_workplaces }} free workplaces</span> in our workshop.</p> <br/> - <p>Register and reserve your spot here before you come by! Thank you.</p> + <p>Register and reserve your workplace here before you come by! Thank you.</p> <br/> <p> <a class="btn btn-default" role="button" href="{{ url_for('login.login') }}">Login</a> diff --git a/app/templates/bouncer/home_authenticated.html b/app/templates/bouncer/home_authenticated.html index 28b527f..ee0441b 100644 --- a/app/templates/bouncer/home_authenticated.html +++ b/app/templates/bouncer/home_authenticated.html @@ -17,7 +17,7 @@ <form action="" method="post"> <input type="hidden" name="action" value="reserve"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> - <input class="btn btn-primary" type="submit" value="Reserve a spot"> + <input class="btn btn-primary" type="submit" value="Reserve a workplace"> </form> or <form action="" method="post"> diff --git a/app/templates/login/register_success.html b/app/templates/login/register_success.html index d4fe5bb..c558d47 100644 --- a/app/templates/login/register_success.html +++ b/app/templates/login/register_success.html @@ -10,7 +10,7 @@ <br/> <h2>Registration successful!</h2> <br> - <p>Please check the inbox of your email address and follow the insttructions.</p> + <p>Please check the inbox of your email address and follow the instructions.</p> <p> <a class="btn btn-default" role="button" href="{{ url_for('login.login') }}">Login</a> <a class="btn btn-default" role="button" href="{{ url_for('bouncer.home') }}">Home</a> diff --git a/bot/__init__.py b/bot/__init__.py new file mode 100644 index 0000000..e288ec0 --- /dev/null +++ b/bot/__init__.py @@ -0,0 +1 @@ +from .bot import create_bot diff --git a/bot/bot.py b/bot/bot.py new file mode 100644 index 0000000..e9fd8f2 --- /dev/null +++ b/bot/bot.py @@ -0,0 +1,25 @@ +from telegram.ext import Updater, CommandHandler, MessageHandler, Filters + +from .commands.help import help_command, start_command +from .commands.free_workplaces import freeworkplaces_command + +# def echo(update, context): +# """Echo the user message.""" +# update.message.reply_text(update.message.text) + + +def create_bot(token): + updater = Updater(token, use_context=True) + + # Get the dispatcher to register handlers + dp = updater.dispatcher + + # on different commands - answer in Telegram + dp.add_handler(CommandHandler("start", start_command)) + dp.add_handler(CommandHandler("help", help_command)) + dp.add_handler(CommandHandler("freeworkplaces", freeworkplaces_command)) + + # on noncommand i.e message - echo the message on Telegram + # dp.add_handler(MessageHandler(Filters.text, echo)) + + return updater diff --git a/bot/commands/free_workplaces.py b/bot/commands/free_workplaces.py new file mode 100644 index 0000000..9613dc7 --- /dev/null +++ b/bot/commands/free_workplaces.py @@ -0,0 +1,10 @@ +from telegram import ParseMode +from app.controllers import FreeWorkplacesController +from app import app_context + +def freeworkplaces_command(update, context): + """Send a message when the command /freeworkplaces is issued.""" + with app_context(): + free_workplaces = FreeWorkplacesController.get_free_workplaces() + response = '*' + str(free_workplaces) + '* workplaces are currently available.' + update.message.reply_text(response, parse_mode=ParseMode.MARKDOWN) diff --git a/bot/commands/help.py b/bot/commands/help.py new file mode 100644 index 0000000..8870bf5 --- /dev/null +++ b/bot/commands/help.py @@ -0,0 +1,40 @@ +from telegram import ParseMode + +def start_command(update, context): + """Send a message when the command /start is issued.""" + start_text = 'Hi!\n\n' + start_text += 'I\'m the Bastli bouncer bot.\n' + start_text += 'My job is to ensure that the workshop doesn\'t get crowded and to maintain the user list.\n\n' + start_text += 'Please follow the rules below:\n\n' + start_text += '*Rule 1*\n' + start_text += 'Register yourself with /register [email] [name]\n\n' + start_text += '*Rule 2*\n' + start_text += 'Check for free workplaces and reserve one before you come here.\n\n' + start_text += '*Rule 3*\n' + start_text += 'Send /enter when you enter the workshop\n\n' + start_text += '*Rule 4*\n' + start_text += 'Send /leave when you leave the workshop\n\n\n' + start_text += 'See /help for more information about the commands.' + update.message.reply_text(start_text, parse_mode=ParseMode.MARKDOWN) + + +def help_command(update, context): + """Send a message when the command /help is issued.""" + help_text = '*Bastli Bouncer bot usage:*\n\n' + help_text += '/freeworkplaces\n' + help_text += ' _Get available workplaces._\n' + help_text += '/register [email] [name]\n' + help_text += ' _Create an account._\n' + help_text += '/resetpassword\n' + help_text += ' _Request password reset._\n' + help_text += '/status\n' + help_text += ' _Get status about own user._\n' + help_text += '/reserve\n' + help_text += ' _Reserve a workplace._\n' + help_text += '/cancel\n' + help_text += ' _Cancel a workplace reservation._\n' + help_text += '/enter\n' + help_text += ' _Request for entry._\n' + help_text += '/leave\n' + help_text += ' _Free the workspace._\n' + update.message.reply_text(help_text, parse_mode=ParseMode.MARKDOWN) diff --git a/entrypoint.sh b/entrypoint.sh old mode 100644 new mode 100755 index df26477..80d2ef1 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -11,4 +11,4 @@ while true; do done unset FLASK_APP -python3 run_prod.py +"${@}" diff --git a/instance/config.example.py b/instance/config.example.py index 0085ab9..853c69a 100644 --- a/instance/config.example.py +++ b/instance/config.example.py @@ -1,7 +1,7 @@ # Example Configuration File from datetime import timedelta -TOTAL_SPOTS = 3 +TOTAL_WORKPLACES = 3 RESERVATION_DURATION = timedelta(hours=1) RECORD_TIMEOUT = timedelta(hours=24) USER_TOKEN_TIMEOUT = timedelta(hours=12) @@ -15,6 +15,8 @@ MAIL_USERNAME = None MAIL_PASSWORD = None MAIL_DEFAULT_SENDER = 'Bastli Bouncer <noreply@bastli.ethz.ch>' +TELEGRAM_BOT_TOKEN = '<telegram-bot-token>' + DEBUG = False SQLALCHEMY_TRACK_MODIFICATIONS = False diff --git a/manage.sh b/manage.sh index 7fe794e..fd64728 100755 --- a/manage.sh +++ b/manage.sh @@ -6,20 +6,52 @@ else SUDO_COMMAND="sudo" fi +PROD_DOCKER_RUN_COMMAND="${SUDO_COMMAND} docker run -it \ + -p 8080:8080 \ + --network bastli-bouncer_backend \ + -v ${PWD}:/bastlibouncer \ + -v ${PWD}/instance/config.dev.py:/bastlibouncer/instance/config.py \ + -v /etc/localtime:/etc/localtime \ + bastlibouncer" + BASE_DOCKER_RUN_COMMAND="${SUDO_COMMAND} docker run -it \ -p 5000:5000 \ --network bastli-bouncer_backend \ -v ${PWD}:/bastlibouncer \ -v ${PWD}/instance/config.dev.py:/bastlibouncer/instance/config.py \ - -v /etc/localtime:/etc/localtime + -v /etc/localtime:/etc/localtime \ bastlibouncer-dev" case $1 in build) - $SUDO_COMMAND docker build -t bastlibouncer-dev -f Dockerfile.development . + case $2 in + dev) + $SUDO_COMMAND docker build -t bastlibouncer-dev -f Dockerfile.development . + ;; + prod) + $SUDO_COMMAND docker build -t bastlibouncer -f Dockerfile . + ;; + *) + echo "Unknown variant for command \"build\"." + echo "Available variants: prod, dev" + exit 1 + ;; + esac ;; run) - $BASE_DOCKER_RUN_COMMAND flask run --host=0.0.0.0 + case $2 in + dev) + $BASE_DOCKER_RUN_COMMAND flask run --host=0.0.0.0 + ;; + prod) + $PROD_DOCKER_RUN_COMMAND + ;; + *) + echo "Unknown variant for command \"run\"." + echo "Available variants: prod, dev" + exit 1 + ;; + esac ;; services) case $2 in @@ -34,6 +66,7 @@ case $1 in ;; *) echo "Unknown sub-command for command \"services\"." + echo "Available sub-commands: start, restart, stop" exit 1 ;; esac @@ -53,7 +86,8 @@ case $1 in echo " manage.sh [COMMAND]" echo "" echo "COMMAND:" - echo " build Build docker image for local development." + echo " build [dev|prod] Build docker image for given environment." + echo " run [dev|prod] Run docker container for given environment." echo " services [start|restart|stop] Start/stop service dependencies." echo " makemigrations Create new migration files." echo " migrate Apply migrations to local database." diff --git a/requirements.in b/requirements.in index ff5bbfa..a6e5db0 100644 --- a/requirements.in +++ b/requirements.in @@ -14,3 +14,4 @@ py3-validate-email pymysql # telegram bot +python-telegram-bot diff --git a/requirements.txt b/requirements.txt index c2f9e58..0fb569b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,11 @@ # alembic==1.4.2 # via flask-migrate blinker==1.4 # via flask-mail +certifi==2020.6.20 # via python-telegram-bot +cffi==1.14.0 # via cryptography click==7.1.2 # via flask +cryptography==2.9.2 # via python-telegram-bot +decorator==4.4.2 # via python-telegram-bot dnspython==1.16.0 # via py3-validate-email dominate==2.5.1 # via flask-bootstrap filelock==3.0.12 # via py3-validate-email @@ -22,11 +26,14 @@ jinja2==2.11.2 # via flask mako==1.1.3 # via alembic markupsafe==1.1.1 # via jinja2, mako, wtforms py3-validate-email==0.2.9 # via -r requirements.in +pycparser==2.20 # via cffi pymysql==0.9.3 # via -r requirements.in python-dateutil==2.8.1 # via alembic python-editor==1.0.4 # via alembic -six==1.15.0 # via python-dateutil +python-telegram-bot==12.8 # via -r requirements.in +six==1.15.0 # via cryptography, python-dateutil sqlalchemy==1.3.18 # via alembic, flask-sqlalchemy +tornado==6.0.4 # via python-telegram-bot visitor==0.1.3 # via flask-bootstrap werkzeug==1.0.1 # via flask wtforms==2.3.1 # via flask-wtf diff --git a/run_dev.py b/run_dev.py index 12ca988..db591fd 100644 --- a/run_dev.py +++ b/run_dev.py @@ -1,6 +1,15 @@ from app import create_app +from bot import create_bot app = create_app() +bot = create_bot(app.config.get('TELEGRAM_BOT_TOKEN')) if __name__ == '__main__': - app.run(host='0.0.0.0') + print('Starting telegram bot...', flush=True) + bot.start_polling() + print('Starting flask dev server on port 5000...', flush=True) + try: + app.run(host='0.0.0.0') + except BaseException as e: + print(e) + bot.stop() diff --git a/run_prod.py b/run_prod.py index fbe7b0f..38317f2 100644 --- a/run_prod.py +++ b/run_prod.py @@ -2,10 +2,18 @@ # [bjoern](https://github.com/jonashaag/bjoern) required. from app import create_app +from bot import create_bot import bjoern app = create_app() +bot = create_bot(app.config.get('TELEGRAM_BOT_TOKEN')) if __name__ == '__main__': + print('Starting telegram bot...', flush=True) + bot.start_polling() print('Starting bjoern on port 8080...', flush=True) - bjoern.run(app, '0.0.0.0', 8080) + try: + bjoern.run(app, '0.0.0.0', 8080) + except BaseException as e: + print(e) + bot.stop() -- GitLab