import os import re from flask import Flask, render_template, redirect, url_for, request, flash from flask_migrate import Migrate from werkzeug.security import generate_password_hash, check_password_hash from flask_login import (LoginManager, login_user, login_required, logout_user, current_user) from misc import datetime, date, time, currDay, prevDay, ZoneInfo, currVersion, currCommit from db import (db, Period, Task, Event, User) from sqlalchemy import inspect from forms import (TaskForm, EventForm, PeriodForm, SignupForm, LoginForm, SettingsForm) from log import appLogger from worker import runCreateEvents # init logger logger = appLogger('app') basedir = os.path.abspath(os.path.dirname(__file__)) app = Flask(__name__) app.config['SECRET_KEY'] = os.environ['SECRET_KEY'] app.config['SQLALCHEMY_DATABASE_URI'] =\ 'mysql://' + os.environ['MYSQL_USER'] + \ ':' + os.environ['MYSQL_PASSWORD'] + \ '@' + os.environ['MYSQL_HOST'] + \ ':' + os.environ['MYSQL_PORT'] + \ '/' + os.environ['MYSQL_DB'] app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False db.init_app(app) # init migration migrate = Migrate() migrate.init_app(app, db) # Authentication stuff login_manager = LoginManager() login_manager.login_view = 'login' login_manager.init_app(app) if 'SIGNUP_ENABLED' in os.environ: if os.environ['SIGNUP_ENABLED'] == "YES": signup_enabled = True else: signup_enabled = False else: signup_enabled = False @login_manager.user_loader def load_user(user_id): return User.query.get(int(user_id)) # Context processor injects current version and commit @app.context_processor def injectVerCommit(): return dict(currVersion=currVersion, currCommit=currCommit, datetime=datetime, ZoneInfo=ZoneInfo) # Error handling # Pass env variables for debugging in prod NODE_NAME = os.environ['NODE_NAME'] POD_NAME = os.environ['POD_NAME'] # Error Routes @app.errorhandler(404) def notFound(e): return render_template('errors/404.html', NODE_NAME=NODE_NAME, POD_NAME=POD_NAME), 404 @app.errorhandler(403) def forbidden(e): return render_template('errors/403.html', NODE_NAME=NODE_NAME, POD_NAME=POD_NAME), 403 @app.errorhandler(500) def ISEerror(e): return render_template('errors/500.html', NODE_NAME=NODE_NAME, POD_NAME=POD_NAME), 500 # Pod Status route @app.route('/podStatus') def podStatus(): blankPage = "" try: db.engine.connect().close() return blankPage, 200 except: return blankPage, 500 # Index route @app.route('/', methods=['GET', 'POST']) def index(): # Check for empty database, go to setup page if not setup global tablesSetup required_tables = ['period', 'task', 'event', 'user'] inspector = inspect(db.engine) tables = inspector.get_table_names() tablesSetup = all(table in tables for table in required_tables) if not tablesSetup: # Initial setup page, should only appear if database is ready but not set up db.create_all() logger.info('DB initialized on first run') return redirect(url_for('createAccount')) # Otherwise, redirect to /events else: return redirect('/events') # Authentication routes @app.route('/login', methods=['GET', 'POST']) def login(): sourceIP = request.environ.get('HTTP_X_REAL_IP', request.remote_addr) form = LoginForm() if form.validate_on_submit(): userName = form.userName.data password = form.password.data remember = form.rememberMe.data user = User.query.filter_by(userName=userName).first() if not user or not check_password_hash(user.password, password): flash('Credentials incorrect! Please try again') logger.info(f'User \'{userName}\' logon FAILED (bad password) from {sourceIP}') return redirect(url_for('login')) login_user(user, remember=remember) logger.info(f'User \'{userName}\' logged in successfully from {sourceIP}') return redirect(url_for('events')) return render_template('login.html', form=form) @app.route('/createaccount', methods=['GET', 'POST']) def createAccount(): sourceIP = request.environ.get('HTTP_X_REAL_IP', request.remote_addr) if signup_enabled == True: form = SignupForm() if form.validate_on_submit(): usernameExists = User.query.filter_by(userName=form.userName.data).first() if usernameExists: flash('Username already exists') return redirect(url_for('createAccount')) new_user = User(email=form.email.data, userName=form.userName.data, realName=form.realName.data, password=generate_password_hash(form.password.data, method='sha256')) db.session.add(new_user) db.session.commit() logger.info(f'New user \'{new_user.userName}\' created from {sourceIP}') return redirect(url_for('login')) return render_template('createAccount.html', form=form) else: return 'Account creation is currently disabled.
Set env var SIGNUP_ENABLED=YES to enable account creation' @app.route('/logout') @login_required def logout(): sourceIP = request.environ.get('HTTP_X_REAL_IP', request.remote_addr) logger.info(f'User \'{current_user.userName}\' logged out from {sourceIP} ') logout_user() return redirect(url_for('index')) @app.route('/settings', methods=('GET', 'POST')) @login_required def settings(): sourceIP = request.environ.get('HTTP_X_REAL_IP', request.remote_addr) user = User.query.get_or_404(current_user.id) form = SettingsForm(obj=user) if form.validate_on_submit(): user.realName = form.realName.data user.timezone = form.timezone.data if form.password.data != '': user.password = generate_password_hash(form.password.data, method='sha256') db.session.commit() logger.info(f'User \'{current_user.userName}\' settings changed from {sourceIP}') return render_template('settings.html', form=form) # Periods routes @app.route('/periods') @login_required def periods(): periods = Period.query.all() return render_template('periods.html', periods=periods, datetime=datetime) @app.route('/period/new', methods=('GET', 'POST')) @login_required def newPeriod(): sourceIP = request.environ.get('HTTP_X_REAL_IP', request.remote_addr) form = PeriodForm() if form.validate_on_submit(): period = Period(periodTime=form.periodTime.data, weekendSchedule=form.weekendSchedule.data ) db.session.add(period) db.session.commit() logger.info(f'New period added by \'{current_user.userName}\' from {sourceIP}') # Run createEvents upon adding new period runCreateEvents() return redirect(f'/period/edit/{period.period}') return render_template('newPeriod.html', form=form) @app.route('/period/edit/', methods=('GET', 'POST')) @login_required def editPeriod(periodNum): sourceIP = request.environ.get('HTTP_X_REAL_IP', request.remote_addr) period = Period.query.get_or_404(periodNum) form = PeriodForm(obj=period) if form.validate_on_submit(): period.periodTime = form.periodTime.data period.weekendSchedule = form.weekendSchedule.data db.session.commit() logger.info(f'Period {periodNum} edited by \'{current_user.userName}\' from {sourceIP}') return redirect(f'/period/edit/{periodNum}') return render_template('editPeriod.html', period=period, form=form, datetime=datetime) @app.post('/period/delete/') @login_required def delete_period(periodNum): sourceIP = request.environ.get('HTTP_X_REAL_IP', request.remote_addr) period = Period.query.get_or_404(periodNum) db.session.delete(period) db.session.commit() logger.info(f'Period {periodNum} deleted by \'{current_user.userName}\' from {sourceIP}') return redirect('/periods') # Events routes @app.route('/events') @login_required def events(): events = Event.query.all() periods = Period.query.all() return render_template('events.html', events=events, periods=periods, datetime=datetime, date=date, ZoneInfo=ZoneInfo, re=re) @app.route('/event/edit//', methods=('GET', 'POST')) @login_required def editEvent(event_id): sourceIP = request.environ.get('HTTP_X_REAL_IP', request.remote_addr) event = Event.query.get_or_404(event_id) form = EventForm(obj=event) if form.validate_on_submit(): if hasattr(form.selectedTask.data, 'id'): event.task_id = form.selectedTask.data.id else: event.task_id = None db.session.commit() logger.info(f'Event {event_id} edited by \'{current_user.userName}\' from {sourceIP}') return redirect('/events') return render_template('editEvent.html', event=event, form=form, datetime=datetime) # Tasks routes @app.route('/tasks') @login_required def tasks(): tasks = Task.query.all() return render_template('tasks.html', str=str, tasks=tasks, datetime=datetime, date=date, re=re) @app.route('/task//') @login_required def task(task_id): task = Task.query.get_or_404(task_id) return render_template('task.html', str=str, task=task, datetime=datetime, date=date, re=re) @app.route('/task/new', methods=('GET', 'POST')) @login_required def newTask(): sourceIP = request.environ.get('HTTP_X_REAL_IP', request.remote_addr) form = TaskForm() if form.validate_on_submit(): task = Task(title=form.title.data, description=form.description.data, created_timestamp=int(time.time())) db.session.add(task) db.session.commit() logger.info(f'New task added by \'{current_user.userName}\' from {sourceIP}') return redirect(f'/task/{task.id}') return render_template('newtask.html', form=form) @app.route('/task//edit', methods=('GET', 'POST')) @login_required def editTask(task_id): sourceIP = request.environ.get('HTTP_X_REAL_IP', request.remote_addr) task = Task.query.get_or_404(task_id) form = TaskForm(obj=task) if form.validate_on_submit(): task.title=form.title.data task.description=form.description.data db.session.commit() logger.info(f'Task {task_id} edited by \'{current_user.userName}\' from {sourceIP}') return redirect(f'/task/{task_id}') return render_template('edittask.html', task=task, form=form) @app.post('/task//delete') @login_required def delete_task(task_id): sourceIP = request.environ.get('HTTP_X_REAL_IP', request.remote_addr) task = Task.query.get_or_404(task_id) db.session.delete(task) db.session.commit() logger.info(f'Task {task_id} deleted by \'{current_user.userName}\' from {sourceIP}') return redirect('/tasks')