TooAngel/democratic-collaboration

View on GitHub
src/server.py

Summary

Maintainability
B
4 hrs
Test Coverage
import os
from werkzeug.middleware.proxy_fix import ProxyFix
from flask import Flask, request, redirect, url_for, session, g, Response, render_template, send_file
import flask_restful
from flask_compress import Compress
from flask_session import Session
from flask_sslify import SSLify
import github
from apscheduler.schedulers.background import BackgroundScheduler
import requests
import sys
from random import randrange
from flask_github import GitHub
import logging
from PullRequest import PullRequest as PR, check_pull_requests
import json
from datetime import timedelta
from flask_sockets import Sockets
import re
import hashlib
import gevent

from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate, upgrade

from routes.static import static
import apiendpoint
from routes import githubWebHook

from models import Repository, User, db
from flask_session import SqlAlchemySessionInterface

logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s %(message)s',
                    handlers=[logging.StreamHandler()])

app = Flask(
    __name__,
    static_folder='../static',
    template_folder='../templates'
)
app.wsgi_app = ProxyFix(app.wsgi_app)


SESSION_TYPE = 'sqlalchemy'

app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('JAWSDB_MARIA_URL', 'mysql://worlddriven:password@127.0.0.1/worlddriven')
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {'pool_size': 20, 'max_overflow': 10}

db.init_app(app)
migrate = Migrate(app, db)

# SqlAlchemySessionInterface(app, db, "sessions", "sess_")
with app.app_context():
    upgrade()

app.config.from_object(__name__)
Session(app)

app.register_blueprint(static)

if not os.getenv('DEBUG'):
    sslify = SSLify(app, permanent=True)

sockets = Sockets(app)
Compress(app)

api = flask_restful.Api(app)

app.config['GITHUB_CLIENT_ID'] = os.getenv('GITHUB_CLIENT_ID')
app.config['GITHUB_CLIENT_SECRET'] = os.getenv('GITHUB_CLIENT_SECRET')
github_oauth = GitHub(app)


@app.before_request
def before_request():
    g.user = None
    if 'user_id' in session:

        logging.info(session['user_id'])
        user = User.query.filter_by(id=session['user_id']).first()
        g.user = user


@github_oauth.access_token_getter
def token_getter():
    user = g.user
    if user is not None:
        user = user.github_access_token
        return user
    else:
        logging.info('No g user')


def get_pull_requests(repository):
    pull_requests = repository.get_pulls(state='open')
    return [
        {'number': pull_request.number, 'title': pull_request.title}
        for pull_request in pull_requests
    ]


@app.route('/v1/repositories', strict_slashes=False)
def repositories():
    if not g.user:
        return 401

    github_client = github.Github(g.user.github_access_token)
    user = github_client.get_user()
    github_repositories = user.get_repos(type='owner')

    repositoryNames = []
    repositories = {}
    for repository in github_repositories:
        repositories[repository.full_name] = {
            'configured': False,
            'pull_requests': get_pull_requests(repository),
            'description': repository.description,
            'html_url': repository.html_url,
        }
        repositoryNames.append(repository.full_name)

    organizations = user.get_orgs()
    for organization in organizations:
        for repository in organization.get_repos('public'):
            repositories[repository.full_name] = {
                'configured': False,
                'pull_requests': get_pull_requests(repository),
                'description': repository.description,
                'html_url': repository.html_url,
            }
            repositoryNames.append(repository.full_name)

    db_repositories = Repository.query.filter(Repository.full_name.in_(repositoryNames)).all()
    for db_repository in db_repositories:
        repositories[db_repository.full_name]['configured'] = True

    response = []
    for key, value in repositories.items():
        response.append({
            'full_name': key,
            'configured': value['configured'],
            'pull_requests': value['pull_requests'],
            'description': value['description'],
            'html_url': value['html_url'],
        })
    response = sorted(response, key=lambda i: i['full_name'])
    return Response(json.dumps(response), mimetype='application/json')


@app.route('/login/')
def login():
    if session.get('user_id', None) is None:
        return github_oauth.authorize(scope='public_repo,read:org,admin:repo_hook')
    else:
        return redirect('/dashboard')


@app.route('/logout/')
def logout():
    session.clear()
    return redirect('/')


@app.route('/github-callback/')
@github_oauth.authorized_handler
def authorized(oauth_token):
    if oauth_token is None:
        logging.info("Authorization failed.")
        return redirect('/')

    user = User.query.filter_by(github_access_token=oauth_token).first()
    if not user:
        user = User(github_access_token=oauth_token)
        db.session.add(user)
        db.session.commit()

    session['user_id'] = user.id
    return redirect('/dashboard')


@app.route('/v1/user', strict_slashes=False)
def user():
    return Response(
        json.dumps(github_oauth.get('user')),
        mimetype='application/json'
    )


api.add_resource(githubWebHook.GithubWebHook, '/github/')

api.add_resource(
    apiendpoint.APIPullRequest,
    '/v1/<string:org>/<string:repo>/pull/<int:pull>/'
)
api.add_resource(
    apiendpoint.APIRepository,
    '/v1/<string:org>/<string:repo>/'
)


class WebsocketPing(gevent.Greenlet):
    def __init__(self, ws):
        self.running = True
        self.ws = ws
        gevent.Greenlet.__init__(self)

    def _run(self):
        while self.running:
            self.ws.send_frame('ping', self.ws.OPCODE_PING)
            gevent.sleep(20)


@sockets.route('/admin/logs')
def ws_admin_logs(ws):
    logging.info('websocket connection started')
    url = 'https://api.heroku.com/apps/worlddriven/log-sessions'
    headers = {
        'accept': 'application/vnd.heroku+json; version=3',
    }
    data = {
        'tail': True,
    }

    auth = (os.environ['HEROKU_EMAIL'], os.environ['HEROKU_TOKEN'])
    session_response = requests.post(
        url,
        headers=headers,
        auth=auth,
        data=data
    )
    log_session = session_response.json()
    log = requests.get(
        log_session['logplex_url'],
        headers=headers,
        auth=auth,
        stream=True
    )
    ping = WebsocketPing(ws)
    ping.start()

    for line in log.iter_lines():
        if line:
            decoded_line = line.decode('utf-8')
            if ws.closed:
                break
            try:
                ws.send(decoded_line + '\n')
            except Exception as e:
                logging.error('error args {} errno {} strerror {}'.format(e.args, e.errno, e.strerror))
                logging.error('/admin/logs ws.send() exception {}'.format(e))
                break

    ping.running = False

    logging.info('websocket connection ended')


@app.route('/admin')
def admin():
    return app.send_static_file('admin.html')


# TODO this can be removed, as soon as /admin is working fine
@app.route('/admin/logs')
def admin_logs():
    url = 'https://api.heroku.com/apps/worlddriven/log-sessions'
    headers = {
        'accept': 'application/vnd.heroku+json; version=3',
    }
    data = {
        'source': 'app',
        'tail': True,
    }
    auth = (os.environ['HEROKU_EMAIL'], os.environ['HEROKU_TOKEN'])
    session_response = requests.post(
        url,
        headers=headers,
        auth=auth,
        data=data
    )
    log_session = session_response.json()
    log = requests.get(
        log_session['logplex_url'],
        headers=headers,
        auth=auth,
        stream=True
    )

    def hashIp(matchObject):
        return hashlib.sha224(matchObject.group(0).encode('utf-8')).hexdigest()[0:15]

    def generate():
        for line in log.iter_lines():
            if line:
                decoded_line = line.decode('utf-8')
                response = re.sub(r"(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", hashIp, decoded_line)
                yield response + '\n'
    return Response(generate(), mimetype='text/plain')


sched = BackgroundScheduler()
if os.getenv('DISABLE_WORKER') != 'true':
    sched.add_job(check_pull_requests, 'interval', minutes=51)
    sched.start()


app.secret_key = os.getenv('SESSION_SECRET')

app.debug = os.getenv('DEBUG', 'false').lower() == 'true'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=int(os.getenv("PORT", 5001)))
    # from gevent import pywsgi
    # from geventwebsocket.handler import WebSocketHandler
    # server = pywsgi.WSGIServer(('0.0.0.0', 5001), app, handler_class=WebSocketHandler)
    # server.serve_forever()