Skip to content
Snippets Groups Projects
Unverified Commit ad508e1d authored by Sandro Lutz's avatar Sandro Lutz
Browse files

Add basics for telegram bot

parent bb42675c
No related branches found
No related tags found
No related merge requests found
Showing
with 89 additions and 61 deletions
......@@ -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" ]
......@@ -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
......
......@@ -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
......
......@@ -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)
......
class AppContext(object):
def init_app(self, new_app):
self.app = new_app
def app_context(self):
return self.app.app_context()
......@@ -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))
from .record import RecordController
from .free_spot import FreeSpotController
from .free_workplaces import FreeWorkplacesController
from .reservation import ReservationController
from .user import UserController
......@@ -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
import threading
class Lock():
spot_lock = threading.Lock()
workplaces_lock = threading.Lock()
......@@ -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()
......@@ -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()
......@@ -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():
......
......@@ -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
......
......@@ -3,7 +3,7 @@ from datetime import datetime, timedelta
class Reservation(db.Model):
"""
Spot reservation entry.
Workplace reservation entry.
"""
__tablename__ = 'reservations'
......
......@@ -86,7 +86,7 @@ p.copyright {
.error {
text-align: center;
}
.free_spots {
.free_workplaces {
font-size: 1.2em;
font-weight: bold;
text-decoration: underline;
......
......@@ -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>
......
......@@ -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>
......
......@@ -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">
......
......@@ -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>
......
from .bot import create_bot
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment