from flask import (
    Flask, render_template, request, redirect, g, jsonify,
    url_for, session
import os
import json
import config
from time import strftime
from datetime import datetime, timedelta
import logging
from logging.handlers import RotatingFileHandler
from phone_scanner import AndroidScan, IosScan, TestScan
# import traceback
from privacy_scan_android import do_privacy_check
from db import (
    create_scan, save_note, update_appinfo,
    create_report, new_client_id, init_db, create_mult_appinfo,
    get_client_devices_from_db, get_device_from_db, update_mul_appinfo,
    get_serial_from_db, get_scan_res_from_db, get_app_info_from_db

#from flask_wtf import Form
#from sqlalchemy.orm import validates
from flask_migrate import Migrate
from flask_sqlalchemy import Model, SQLAlchemy
import json
import config
from time import strftime
from wtforms_alchemy import ModelForm
from sqlalchemy import *
from wtforms.validators import Email, InputRequired
from wtforms.fields import SelectMultipleField
from wtforms.widgets import CheckboxInput, ListWidget

app = Flask(__name__, static_folder='webstatic')
app.config['SQLALCHEMY_ECHO'] = True
app.secret_key = config.FLASK_SECRET # doesn't seem to be necessary
app.config['SECRET_KEY'] = config.FLASK_SECRET # doesn't seem to be necessary
app.config['SESSION_TYPE'] = 'filesystem'
Migrate(app, sa)
# sa.create_all() # run in init_db()

# If changes are made to this model, please run 
# `flask db migrate` and then delete the drops to other tables from the upgrade() method in 
# migrations/versions/<version>.py
# before running `flask db upgrade` and re-launching the server.
# if the migrations folder isn't present, run `flask db init` first.
# _order in ClientForm should be modified .
class Client(sa.Model):
    __tablename__ = 'clients_notes'
    _d = {'default': '', 'server_default': ''} # makes migrations smooth
    _d0 = {'default': '0', 'server_default': '0'}
    _lr = lambda label,req: {'label':label+'*' if req=='r' else label,'validators':InputRequired() if req=='r' else ''}
    id = sa.Column(sa.Integer, primary_key=True)
    created_at = sa.Column(
        # TODO: timestamp off by 4 hours? investigate.

    # TODO: link to session ClientID for scans, with foreignkey? across different db?
    # try using fieldstudy.db, creating table not dropping existing things. use ~test.
    clientid = sa.Column(sa.String(100), nullable=False, **_d)

    consultant_initials = sa.Column(sa.String(100), nullable=False,
            info=_lr('Consultant Names (separate with commas)','r'), **_d)

    fjc = sa.Column(sa.Enum('', 'Brooklyn', 'Queens', 'The Bronx', 'Manhattan', 'Staten Island'),
            nullable=False, info=_lr('FJC', 'r'), **_d)

    preferred_language = sa.Column(sa.String(100), nullable=False,
            info=_lr('Preferred language','r'), default='English', server_default='English')

    referring_professional = sa.Column(sa.String(100), nullable=False,
            info=_lr('Name of Referring Professional', 'r'), **_d)

    referring_professional_email = sa.Column(sa.String(255), nullable=True,
            info={'label': 'Email of Referring Professional (Optional)', 'validators':Email()})

    referring_professional_phone = sa.Column(sa.String(50), nullable=True,
            info={'label': 'Phone number of Referring Professional (Optional)'})

    caseworker_present = sa.Column(sa.Enum('', 'For entire consult', 'For part of the consult', 'No'),
            nullable=False, info=_lr('Caseworker present', 'r'), **_d)

    caseworker_present_safety_planning = sa.Column(sa.Enum('', 'Yes', 'No'),
            nullable=False, info=_lr('Caseworker present for safety planning', 'r'), **_d)

    caseworker_recorded = sa.Column(sa.Enum('', 'Yes', 'No'),
            nullable=False, info=_lr('If caseworker present, permission to audio-record them', 'r'), **_d)

    recorded = sa.Column(sa.Enum('', 'Yes', 'No'),
            nullable=False, info=_lr('Permission to audio-record clinic', 'r'), **_d)

    chief_concerns = sa.Column(sa.String(400), nullable=False,
            info=_lr('Chief concerns', 'r'), **_d)

    chief_concerns_other = sa.Column(sa.Text, nullable=False,
            info=_lr('Chief concerns if not listed above (Optional)', ''), **_d)

    android_phones = sa.Column(sa.Integer, nullable=False, 
            info=_lr('# of Android phones brought in','r'), **_d0)

    android_tablets = sa.Column(sa.Integer, nullable=False, 
            info=_lr('# of Android tablets brought in','r'), **_d0)

    iphone_devices = sa.Column(sa.Integer, nullable=False, 
            info=_lr('# of iPhones brought in','r'), **_d0)

    ipad_devices = sa.Column(sa.Integer, nullable=False, 
            info=_lr('# of iPads brought in','r'), **_d0)

    macbook_devices = sa.Column(sa.Integer, nullable=False, 
            info=_lr('# of MacBooks brought in','r'), **_d0)

    windows_devices = sa.Column(sa.Integer, nullable=False, 
            info=_lr('# of Windows laptops brought in','r'), **_d0)

    echo_devices = sa.Column(sa.Integer, nullable=False, 
            info=_lr('# of Amazon Echoes brought in','r'), **_d0)

    other_devices = sa.Column(sa.String(400), nullable=True,
            info=_lr('Other devices brought in if not listed above (Optional)', ''), **_d)

    # consider adding checkboxes for this
    checkups = sa.Column(sa.String(400), nullable=True,
            info=_lr('List apps/accounts manually checked (Optional)', ''), **_d)

    checkups_other = sa.Column(sa.String(400), nullable=True,
            info=_lr('Other apps/accounts manually checked (Optional)', ''), **_d)

    vulnerabilities = sa.Column(sa.String(600), nullable=False,
            info=_lr('Vulnerabilities discovered', 'r'), **_d)

    vulnerabilities_trusted_devices = sa.Column(sa.Text, nullable=True,
            info=_lr('List accounts with unknown trusted devices if discovered (Optional)', ''), **_d)

    vulnerabilities_other = sa.Column(sa.Text, nullable=True,
            info=_lr('Other vulnerabilities discovered (Optional)', ''), **_d)

    safety_planning_onsite = sa.Column(sa.Enum('', 'Yes', 'No', 'Not applicable'),
            nullable=False, info=_lr('Safety planning conducted onsite', 'r'), **_d)

    changes_made_onsite = sa.Column(sa.Text, nullable=True,
            info=_lr('Changes made onsite (Optional)', ''), **_d)

    unresolved_issues = sa.Column(sa.Text, nullable=True,
            info=_lr('Unresolved issues (Optional)', ''), **_d)

    follow_ups_todo = sa.Column(sa.Text, nullable=True,
            info=_lr('Follow-ups To-do (Optional)', ''), **_d)

    general_notes = sa.Column(sa.Text, nullable=True,
            info=_lr('General notes (Optional)', ''), **_d)

    case_summary = sa.Column(sa.Text, nullable=True,
            info=_lr('Case Summary (Can fill out after consult, see "Edit previous forms")', ''), **_d)

    # way to edit data/add case summaries afterwards? Or keep text files.

    def __repr__(self):
        return 'client seen on {}'.format(self.created_at)

from wtforms import TextAreaField

class ClientForm(ModelForm): 
    class Meta:
        model = Client

    chief_concerns = SelectMultipleField('Chief concerns*', choices=[
        ('spyware','Worried about spyware/tracking'),
        ('hacked','Abuser hacked accounts or knows secrets'),
        ('location','Worried abuser was tracking their location'),
        ('glitchy','Phone is glitchy'),
        ('unknown_calls','Abuser calls/texts from unknown numbers'),
        ('social_media','Social media concerns (e.g., fake accounts, harassment)'),
        ('child_devices','Concerns about child device(s), e.g., unknown apps'),
        ('financial_concerns','Financial concerns, e.g., fraud, money missing from bank account'),
        ('curious','Curious and want to learn about privacy'),
        ('sms','SMS texts'),
        ('other','Other chief concern (write in next question)')],
        coerce = str, option_widget = CheckboxInput(), widget = ListWidget(prefix_label=False))

    checkups = SelectMultipleField('List apps/accounts manually checked (Optional)', choices=[
        ('google','Google (including GMail)'),
        ('other','Other apps/accounts (write in next question)')],
        coerce = str, option_widget = CheckboxInput(), widget = ListWidget(prefix_label=False))

    vulnerabilities = SelectMultipleField('Vulnerabilities discovered*', choices=[
        ('shared plan','Shared plan / abuser pays for plan'),
        ('password:observed compromise','Observed compromise (e.g., client reports abuser shoulder-surfed, or told them password)'),
        ('password:guessable','Surfaced guessable passwords'),
        ('cloud:stored passwords','Stored passwords in app that is synced to cloud (e.g., passwords written in Notes and backed up)'),
        ('cloud:passwords synced/password manager','Password syncing (e.g., iCloud Keychain)'),
        ('unknown trusted device','Found an account with an active login from a device not under client\'s control; trusted device'),
        ('ISDi:found dual-use apps/spyware','ISDi found dual-use apps/spyware'),
        ('ISDi:false positive','ISDi false positive as confirmed by client'),
        ('browser extension','Browser extension potential spyware'),
        ('desktop potential spyware','Desktop application potential spyware')
        coerce = str, option_widget = CheckboxInput(), widget = ListWidget(prefix_label=False))

    __order = ('fjc','consultant_initials','preferred_language','referring_professional','referring_professional_email',
            'referring_professional_phone', 'caseworker_present','caseworker_present_safety_planning',
            'unresolved_issues','follow_ups_todo','general_notes', 'case_summary')

    chief_concerns_other = TextAreaField('Chief concerns if not listed above (Optional)', render_kw={"rows": 5, "cols": 70})
    vulnerabilities_trusted_devices = TextAreaField('List accounts with unknown trusted devices if discovered (Optional)', render_kw={"rows": 5, "cols": 70})
    vulnerabilities_other = TextAreaField('Other vulnerabilities discovered (Optional)', render_kw={"rows": 5, "cols": 70})
    changes_made_onsite = TextAreaField('Changes made onsite (Optional)', render_kw={"rows": 5, "cols": 70})
    unresolved_issues = TextAreaField('Unresolved issues (Optional)', render_kw={"rows": 5, "cols": 70})
    follow_ups_todo = TextAreaField('Follow-ups To-do (Optional)', render_kw={"rows": 5, "cols": 70})
    general_notes = TextAreaField('General notes (Optional)', render_kw={"rows": 10, "cols": 70})
    case_summary = TextAreaField('Case Summary (Can fill out after consult, see "Edit previous forms")', render_kw={"rows": 10, "cols": 70})

    def __iter__(self): #
        fields = list(super(ClientForm, self).__iter__())
        get_field = lambda field_id: next((fld for fld in fields if == field_id))
        return (get_field(field_id) for field_id in self.__order)

# app.config['STATIC_FOLDER'] = 'webstatic'
android = AndroidScan()
ios = IosScan()
test = TestScan()

def get_device(k):
    return {
        'android': android,
        'ios': ios,
        'test': test

def make_session_permanent():
    session.permanent = True
    # expires at midnight of new day
    app.permanent_session_lifetime = \
            ( + timedelta(days=1)).replace(hour=0, minute=0, second=0) -
    #app.permanent_session_lifetime = timedelta(seconds=1)

def close_connection(exception):
    db = getattr(g, '_database', None)
    if db is not None:

@app.route("/", methods=['GET'])
def index():
    #clientid = request.form.get('clientid', request.args.get('clientid'))
    #if not clientid: # if not coming from notes

    newid = request.args.get('newid')
    # if it's a new day (see app.permenant_session_lifetime), 
    # or the client devices are all scanned (newid),
    # ask the DB for a new client ID (additional checks in DB).
    if 'clientid' not in session or (newid is not None):

    return render_template(
            'Android': android.devices(),
            'iOS': ios.devices(),
            'Test': test.devices()

@app.route('/form/', methods=['GET', 'POST'])
def client_forms():
    if 'clientid' not in session:
        return redirect(url_for('index'))

    prev_submitted = Client.query.filter_by(clientid=session['clientid']).first()
    if prev_submitted:
        return redirect(url_for('edit_forms'))

    # retrieve form defaults from db schema
    client = Client()
    form = ClientForm(request.form)

    if request.method == 'POST':
            if form.validate():
                # convert checkbox lists to json-friendly strings
                for field in form:
                    if field.type == 'SelectMultipleField':
               = json.dumps(
                client.clientid = session['clientid']
                return render_template('main.html', task="form", formdone='yes', title=config.TITLE)
        except Exception as e:
            print('NOT VALIDATED')

    #clients_list = Client.query.all()
    return render_template('main.html', task="form", form=form, title=config.TITLE, clientid=session['clientid'])

@app.route('/form/edit/', methods=['GET', 'POST'])
def edit_forms():
    if request.method == 'POST':
        clientnote = request.form.get('clientnote', request.args.get('clientnote'))

        if clientnote: # if requesting a form to edit
            session['form_edit_pk'] = clientnote # set session cookie
            form_obj = Client.query.get(clientnote)
            form = ClientForm(obj=form_obj)
            for field in form:
                if field.type == 'SelectMultipleField':
           = json.loads(''.join(
            return render_template('main.html', task="form",form=form, title=config.TITLE, clientid=form_obj.clientid)
        else: # if edits were submitted
            form_obj = Client.query.get(session['form_edit_pk'])
            cid = form_obj.clientid # preserve before populate_obj
            form = ClientForm(request.form)
            if form.validate():
                # convert checkbox lists to json-friendly strings
                for field in form:
                    if field.type == 'SelectMultipleField':
               = json.dumps(
            form_obj.clientid = cid
            return render_template('main.html', task="form", formdone='yes', title=config.TITLE)

    clients = Client.query.all()
    return render_template('main.html', clients=clients, task="formedit", title=config.TITLE)

@app.route('/details/app/<device>', methods=['GET'])
def app_details(device):
    sc = get_device(device)
    appid = request.args.get('appId')
    ser = request.args.get('serial')
    d, info = sc.app_details(ser, appid)
    d = d.fillna('')
    d = d.to_dict(orient='index').get(0, {})
    d['appId'] = appid

    # detect apple and put the key into d.permissions
    # if "Ios" in str(type(sc)):
    #    print("apple iphone")
    # else:
    #    print(type(sc))

    return render_template(
        'main.html', task="app",

@app.route('/instruction', methods=['GET'])
def instruction():
    return render_template('main.html', task="instruction",

@app.route('/kill', methods=['POST', 'GET'])
def killme():
    func = request.environ.get('werkzeug.server.shutdown')
    if func is None:
        raise RuntimeError('Not running with the Werkzeug Server')
    return ("The app has been closed!")

def is_success(b, msg_succ="", msg_err=""):
    if b:
        return msg_succ if msg_succ else "Success!", 200
        return msg_err if msg_err else "Failed", 401

def first_element_or_none(l):
    if l and len(l) > 0:
        return l[0]

@app.route("/privacy", methods=['GET'])
def privacy():
    TODO: Privacy scan. Think how should it flow.
    Privacy is a seperate page.
    return render_template(
        'main.html', task="privacy",

@app.route("/privacy/<device>/<cmd>", methods=['GET'])
def privacy_scan(device, cmd):
    sc = get_device(device)
    res = do_privacy_check(sc.serialno, cmd)
    return res

@app.route("/view_results", methods=['POST', 'GET'])
def view_results():
    print("WORK IN PROGRESS")
    #clientid = request.form.get('clientid', request.args.get('clientid'))
    # hmac'ed serial of results we want to view
    scan_res_pk = request.form.get('scan_res', request.args.get('scan_res'))

    # TODO: maybe unneccessary, but likely nice for returning without
    # re-drawing screen.
    last_serial = request.form.get(
        'last_serial', request.args.get('last_serial'))
    template_d = dict(
        device_primary_user=config.DEVICE_PRIMARY_USER,   # TODO: Why is this sent
    apps = sc.find_spyapps(serialno=ser).fillna('').to_dict(orient='index')

              "<strong class='text-danger'>Yes.</strong> Reason(s): {}"
              .format(rooted_reason) if rooted
              else "Don't know" if rooted is None
              else "No"
          sysapps=set(),  # sc.get_system_apps(serialno=ser)),
          # TODO: make this a map of model:link to display scan results for that
          # scan.

    if scan_res_pk == last_serial:
        print('Should return same template as before.')
        print("scan_res:  {}".format(scan_res_pk))
        print("last_serial: {}".format(last_serial))
        print('Should return results of scan_res.')
        print("scan_res: {}".format(scan_res_pk))
        print("last_serial: {}".format(last_serial))
    return redirect(url_for('index'))

@app.route("/scan", methods=['POST', 'GET'])
def scan():
    Needs three attribute for a device
    :param device: "android" or "ios" or test
    :return: a flask view template
    #clientid = request.form.get('clientid', request.args.get('clientid'))
    if 'clientid' not in session:
        return redirect(url_for('index'))

    device_primary_user = request.form.get(
    device = request.form.get('device', request.args.get('device'))
    action = request.form.get('action', request.args.get('action'))
    device_owner = request.form.get(
        'device_owner', request.args.get('device_owner'))

    currently_scanned = get_client_devices_from_db(session['clientid'])
    template_d = dict(
        device_primary_user=config.DEVICE_PRIMARY_USER,   # TODO: Why is this sent
    # lookup devices scanned so far here. need to add this by model rather
    # than by serial.
    print('CURRENTLY SCANNED: {}'.format(currently_scanned))
    print('DEVICE OWNER IS: {}'.format(device_owner))
    print('PRIMARY USER IS: {}'.format(device_primary_user))
    print('-' * 80)
    print('CLIENT ID IS: {}'.format(session['clientid']))
    print('-' * 80)
    print("--> Action = ", action)

    sc = get_device(device)
    if not sc:
        template_d["error"] = "Please choose one device to scan."
        return render_template("main.html", **template_d), 201
    if not device_owner:
        template_d["error"] = "Please give the device a nickname."
        return render_template("main.html", **template_d), 201

    ser = sc.devices()

    print("Devices: {}".format(ser))
    if not ser:
        # FIXME: add pkexec scripts/ workflow for iOS if
        # needed.
        error = "<b>A device wasn't detected. Please follow the "\
            "<a href='/instruction' target='_blank' rel='noopener'>"\
            "setup instructions here.</a></b>"
        template_d["error"] = error
        return render_template("main.html", **template_d), 201

    ser = first_element_or_none(ser)
    # clientid = new_client_id()
    print(">>>scanning_device", device, ser, "<<<<<")

    if device == "ios":
        error = "If an iPhone is connected, open iTunes, click through the "\
                "connection dialog and wait for the \"Trust this computer\" "\
                "prompt to pop up in the iPhone, and then scan again."
        error = "If an Android device is connected, disconnect and reconnect "\
                "the device, make sure developer options is activated and USB "\
                "debugging is turned on on the device, and then scan again."
    error += "{} <b>Please follow the <a href='/instruction' target='_blank'"\
             " rel='noopener'>setup instructions here,</a> if needed.</b>"
    if device == 'ios':
        # go through pairing process and do not scan until it is successful.
        isconnected, reason = sc.setup()
        template_d["error"] = error.format(reason)
        template_d["currently_scanned"] = currently_scanned
        if not isconnected:
            return render_template("main.html", **template_d), 201

    # TODO: model for 'devices scanned so far:' device_name_map['model']
    # and save it to scan_res along with device_primary_user.
    device_name_print, device_name_map = sc.device_info(serial=ser)

    # Finds all the apps in the device
    # @apps have appid, title, flags, TODO: add icon
    apps = sc.find_spyapps(serialno=ser).fillna('').to_dict(orient='index')
    if len(apps) <= 0:
        print("The scanning failed for some reason.")
        error = "The scanning failed. This could be due to many reasons. Try"\
            " rerunning the scan from the beginning. If the problem persists,"\
            " please report it in the file. <code></code> in the<code>"\
            "phone_scanner/</code> directory. Checn the phone manually. Sorry for"\
            " the inconvenience."
        template_d["error"] = error
        return render_template("main.html", **template_d), 201

    scan_d = {
        'clientid': session['clientid'],
        'serial': config.hmac_serial(ser),
        'device': device,
        'device_model': device_name_map.get('model', '<Unknown>').strip(),
        'device_version': device_name_map.get('version', '<Unknown>').strip(),
        'device_primary_user': device_owner,

    if device == 'ios':
        scan_d['device_manufacturer'] = 'Apple'
        scan_d['last_full_charge'] = 'unknown'
        scan_d['device_manufacturer'] = device_name_map.get(
            'brand', "<Unknown>").strip()
        scan_d['last_full_charge'] = device_name_map.get(
            'last_full_charge', "<Unknown>")

    rooted, rooted_reason = sc.isrooted(ser)
    scan_d['is_rooted'] = rooted
    scan_d['rooted_reasons'] = json.dumps(rooted_reason)

    # TODO: here, adjust client session.
    scanid = create_scan(scan_d)

    if device == 'ios':
        pii_fpath = sc.dump_path(ser, 'Device_Info')
        print('Revelant info saved to db. Deleting {} now.'.format(pii_fpath))
        cmd = os.unlink(pii_fpath)
        # s = catch_err(run_command(cmd), msg="Delete pii failed", cmd=cmd)
        print('iOS PII deleted.')

    print("Creating appinfo...")
    create_mult_appinfo([(scanid, appid, json.dumps(
        info['flags']), '', '<new>') for appid, info in apps.items()])

    currently_scanned = get_client_devices_from_db(session['clientid'])
            "<strong class='text-info'>Maybe (this is possibly just a bug with our scanning tool).</strong> Reason(s): {}"
            .format(rooted_reason) if rooted
            else "Don't know" if rooted is None 
            else "No"
        sysapps=set(),  # sc.get_system_apps(serialno=ser)),
        # TODO: make this a map of model:link to display scan results for that
        # scan.
    return render_template("main.html", **template_d), 200
##############  RECORD DATA PART  ###############################

@app.route("/delete/app/<scanid>", methods=["POST", "GET"])
def delete_app(scanid):
    device = get_device_from_db(scanid)
    serial = get_serial_from_db(scanid)
    sc = get_device(device)
    appid = request.form.get('appid')
    remark = request.form.get('remark')
    action = "delete"
    # TODO: Record the uninstall and note
    r = sc.uninstall(serial=serial, appid=appid)
    if r:
        r = update_appinfo(
            scanid=scanid, appid=appid, remark=remark, action=action
        print("Update appinfo failed! r={}".format(r))
        print("Uninstall failed. r={}".format(r))
    return is_success(r, "Success!", config.error())

# @app.route('/save/appnote/<device>', methods=["POST"])
# def save_app_note(device):
#     sc = get_device(device)
#     serial = request.form.get('serial')
#     appId = request.form.get('appId')
#     note = request.form.get('note')
# return is_success('appinfo', serial=serial, appId=appId,
# note=note))

@app.route('/saveapps/<scanid>', methods=["POST"])
def record_applist(scanid):
    device = get_device_from_db(scanid)
    sc = get_device(device)
    d = request.form
    update_mul_appinfo([(remark, scanid, appid)
                        for appid, remark in d.items()])
    return "Success", 200

@app.route('/savescan/<scanid>', methods=["POST"])
def record_scanres(scanid):
    device = get_device_from_db(scanid)
    sc = get_device(device)
    note = request.form.get('notes')
    r = save_note(scanid, note)
    return is_success(
        "Could not save the form. See logs in the terminal.")

################# For logging ##############################################
def get_nothing():
    """ Route for intentional error. """
    return "foobar"  # intentional non-existent variable

def after_request(response):
    """ Logging after every request. """
    # This avoids the duplication of registry in the log,
    # since that 500 is already logged via @app.errorhandler.
    if response.status_code != 500:
        ts = strftime('[%Y-%b-%d %H:%M]')
        logger.error('%s %s %s %s %s %s',
    return response

# @app.errorhandler(Exception)
# def exceptions(e):
#     """ Logging after every Exception. """
#     ts = strftime('[%Y-%b-%d %H:%M]')
#     tb = traceback.format_exc()
#     logger.error('%s %s %s %s %s 5xx INTERNAL SERVER ERROR\n%s',
#                   ts,
#                   request.remote_addr,
#                   request.method,
#                   request.scheme,
#                   request.full_path,
#                   tb)
#     print(e, file=sys.stderr)
#     return "Internal server error", 500

if __name__ == "__main__":
    import sys
    if 'TEST' in sys.argv[1:] or 'test' in sys.argv[1:]:
        print("Running in test mode.")
        print("Checking mode = {}\nApp flags: {}\nSQL_DB: {}"
              .format(config.TEST, config.APP_FLAGS_FILE,
    init_db(app, sa, force=config.TEST)
    handler = RotatingFileHandler('logs/app.log', maxBytes=100000,
    logger = logging.getLogger(__name__)
    port = 5000 if not config.TEST else 5002"", port=port, debug=config.DEBUG)