diff --git a/legacy.py b/legacy.py new file mode 100644 index 0000000..55fbc10 --- /dev/null +++ b/legacy.py @@ -0,0 +1,343 @@ +from flask import Flask +from flask import make_response +from flask import jsonify +from flask import request +from flask import g +from flask import redirect + +from markupsafe import escape + +import json +import datetime +import logging +import os +import sqlite3 +import random + +MEETUP_INVITE_CLIENT_VERSION='v3.0.0' +DATABASE = '/.hmc-meetups/hmc-meetups.db' +AGREEMENT_SUCCESS_URL = 'https://handmadecities.com/meetups-success' + +# NOTE(jmug): This was a large list of invite codes, request the "invite-codes" +# string array secret from secrets server, client at: +# https://git.handmadecities.com/HMC/shhh/src/commit/36af43779460dcfd818d1481abeded14fa45e1a4/pkg/secretsclient/cli.go#L12-L34 +INVITE_CODES = [""] + +# NOTE(jmug): This was a map of "cities" to a shared secret corresponding to each city, request the "city-secrets" +# string map secret from secrets server, client at: +# https://git.handmadecities.com/HMC/shhh/src/commit/36af43779460dcfd818d1481abeded14fa45e1a4/pkg/secretsclient/cli.go#L12-L34 +shared_secrets = { + '': '', +} + +# Flask config + +def home(): + if os.name == 'nt': + return os.getenv('HOMEPATH') + else: + return os.getenv('HOME') + +app = Flask(__name__) +# NOTE(jmug): Flask uses this secret_key to validate cookies, among other things, +# we hsould not have a need for this in a Go port. +app.secret_key = '' + +start_time = datetime.datetime.now() +logname = start_time.strftime('flask-%Y-%m-%d-%Hh-%Mm-%Ss.log') +logging.basicConfig(filename=home() + '/.hmc-meetups/logs/' + logname, level=logging.INFO, format=f'%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)s') + +@app.before_request +def before_request(): + g.db = sqlite3.connect(home() + DATABASE) + g.db.row_factory = sqlite3.Row + +@app.teardown_request +def teardown_request(exception): + if hasattr(g, 'db'): + g.db.close() + +def insert_subscriber(email, agent, city): + cur = g.db.cursor() + creation_date = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + query = ''' + INSERT INTO subscribers ( + email, creation_date, suppressed, suppressed_date, user_agent, city + ) + VALUES (?, ?, ?, ?, ?, ?) + ''' + cur.execute(query, (email, creation_date, 0, None, agent, city)) + g.db.commit() # @robustness @abner catch exceptions + +@app.route('/v1/meetups/wishlist', methods=["POST"]) +def meetups_wishlist(): + address = request.form['email'] # @robustness @abner what if it's empty? + city = request.form['city'] # @robustness @abner + insert_subscriber(address, request.headers.get('User-Agent'), city); + app.logger.info("'" + address + "' has wishlisted '" + city + "'") + return redirect(AGREEMENT_SUCCESS_URL); + +@app.route('/v1/meetups/vienna', methods=["POST"]) +def meetups_vienna(): + address = request.form['email'] # @robustness @abner what if it's empty? + insert_subscriber(address, request.headers.get('User-Agent'), 'Vienna'); + app.logger.info("'" + address + "' has subscribed to 'Vienna'") + return redirect(AGREEMENT_SUCCESS_URL); + +@app.route('/v1/meetups/denmark', methods=["POST"]) +def meetups_denmark(): + address = request.form['email'] # @robustness @abner what if it's empty? + insert_subscriber(address, request.headers.get('User-Agent'), 'Denmark'); + app.logger.info("'" + address + "' has subscribed to 'Denmark'") + return redirect(AGREEMENT_SUCCESS_URL); + +@app.route('/v1/meetups/montreal', methods=["POST"]) +def meetups_montreal(): + address = request.form['email'] # @robustness @abner what if it's empty? + insert_subscriber(address, request.headers.get('User-Agent'), 'Montreal'); + app.logger.info("'" + address + "' has subscribed to 'Montreal'") + return redirect(AGREEMENT_SUCCESS_URL); + +@app.route('/v1/meetups/revolt', methods=["POST"]) +def meetups_revolt(): + address = request.form['email'] # @robustness @abner what if it's empty? + insert_subscriber(address, request.headers.get('User-Agent'), 'Revolt'); + app.logger.info("'" + address + "' has subscribed to 'Revolt'") + return redirect(AGREEMENT_SUCCESS_URL); + +@app.route('/v1/meetups/dc', methods=["POST"]) +def meetups_dc(): + address = request.form['email'] # @robustness @abner what if it's empty? + insert_subscriber(address, request.headers.get('User-Agent'), 'D.C.'); + app.logger.info("'" + address + "' has subscribed to 'D.C.'") + return redirect(AGREEMENT_SUCCESS_URL); + +@app.route('/v1/meetups/vancouver', methods=["POST"]) +def meetups_vancouver(): + address = request.form['email'] # @robustness @abner what if it's empty? + insert_subscriber(address, request.headers.get('User-Agent'), 'Vancouver'); + app.logger.info("'" + address + "' has subscribed to 'Vancouver'") + return redirect(AGREEMENT_SUCCESS_URL); + +@app.route('/v1/meetups/cambridge', methods=["POST"]) +def meetups_cambridge(): + address = request.form['email'] # @robustness @abner what if it's empty? + insert_subscriber(address, request.headers.get('User-Agent'), 'Cambridge'); + app.logger.info("'" + address + "' has subscribed to 'Cambridge'") + return redirect(AGREEMENT_SUCCESS_URL); + +@app.route('/v1/meetups/atlanta', methods=["POST"]) +def meetups_atlanta(): + address = request.form['email'] # @robustness @abner what if it's empty? + insert_subscriber(address, request.headers.get('User-Agent'), 'Atlanta'); + app.logger.info("'" + address + "' has subscribed to 'Atlanta'") + return redirect(AGREEMENT_SUCCESS_URL); + +@app.route('/v1/meetups/australia', methods=["POST"]) +def meetups_australia(): + address = request.form['email'] # @robustness @abner what if it's empty? + insert_subscriber(address, request.headers.get('User-Agent'), 'Australia'); + app.logger.info("'" + address + "' has subscribed to 'Australia'") + return redirect(AGREEMENT_SUCCESS_URL); + +@app.route('/v1/meetups/tokyo', methods=["POST"]) +def meetups_tokyo(): + address = request.form['email'] # @robustness @abner what if it's empty? + insert_subscriber(address, request.headers.get('User-Agent'), 'Tokyo'); + app.logger.info("'" + address + "' has subscribed to 'Tokyo'") + return redirect(AGREEMENT_SUCCESS_URL); + +@app.route('/v1/meetups/berlin', methods=["POST"]) +def meetups_berlin(): + address = request.form['email'] # @robustness @abner what if it's empty? + insert_subscriber(address, request.headers.get('User-Agent'), 'Berlin'); + app.logger.info("'" + address + "' has subscribed to 'Berlin'") + return redirect(AGREEMENT_SUCCESS_URL); + +@app.route('/v1/meetups/termina', methods=["POST"]) +def meetups_termina(): + address = request.form['email'] # @robustness @abner what if it's empty? + insert_subscriber(address, request.headers.get('User-Agent'), 'Termina'); + app.logger.info("'" + address + "' has subscribed to 'Termina'") + return redirect(AGREEMENT_SUCCESS_URL); + +@app.route('/v1/meetups/london', methods=["POST"]) +def meetups_london(): + address = request.form['email'] # @robustness @abner what if it's empty? + insert_subscriber(address, request.headers.get('User-Agent'), 'London'); + app.logger.info("'" + address + "' has subscribed to 'London'") + return redirect(AGREEMENT_SUCCESS_URL); + +@app.route('/v1/meetups/seattle', methods=["POST"]) +def meetups_seattle(): + address = request.form['email'] # @robustness @abner what if it's empty? + insert_subscriber(address, request.headers.get('User-Agent'), 'Seattle'); + app.logger.info("'" + address + "' has subscribed to 'Seattle'") + return redirect(AGREEMENT_SUCCESS_URL); + +@app.route('/v1/meetups/portland', methods=["POST"]) +def meetups_portland(): + address = request.form['email'] # @robustness @abner what if it's empty? + insert_subscriber(address, request.headers.get('User-Agent'), 'Portland'); + app.logger.info("'" + address + "' has subscribed to 'Portland'") + return redirect(AGREEMENT_SUCCESS_URL); + +@app.route('/v1/meetups/boston', methods=["POST"]) +def meetups_boston(): + address = request.form['email'] # @robustness @abner what if it's empty? + insert_subscriber(address, request.headers.get('User-Agent'), 'Boston'); + app.logger.info("'" + address + "' has subscribed to 'Boston'") + return redirect(AGREEMENT_SUCCESS_URL); + +@app.route('/v1/meetups/las_vegas', methods=["POST"]) +def meetups_las_vegas(): + address = request.form['email'] # @robustness @abner what if it's empty? + insert_subscriber(address, request.headers.get('User-Agent'), 'Las Vegas'); + app.logger.info("'" + address + "' has subscribed to 'Las Vegas'") + return redirect(AGREEMENT_SUCCESS_URL); + +@app.route('/v1/meetups/san_francisco', methods=["POST"]) +def meetups_san_francisco(): + address = request.form['email'] # @robustness @abner what if it's empty? + insert_subscriber(address, request.headers.get('User-Agent'), 'San Francisco'); + app.logger.info("'" + address + "' has subscribed to 'San Francisco'") + return redirect(AGREEMENT_SUCCESS_URL); + +@app.route('/v1/meetups/new_york_city', methods=["POST"]) +def meetups_new_york_city(): + address = request.form['email'] # @robustness @abner what if it's empty? + insert_subscriber(address, request.headers.get('User-Agent'), 'New York City'); + app.logger.info("'" + address + "' has subscribed to 'New York City'") + return redirect(AGREEMENT_SUCCESS_URL); + +@app.route('/v1/meetups/detroit', methods=["POST"]) +def meetups_detroit(): + address = request.form['email'] # @robustness @abner what if it's empty? + insert_subscriber(address, request.headers.get('User-Agent'), 'Detroit'); + app.logger.info("'" + address + "' has subscribed to 'Detroit'") + return redirect(AGREEMENT_SUCCESS_URL); + +@app.route('/v1/meetups/stockholm', methods=["POST"]) +def meetups_stockholm(): + address = request.form['email'] # @robustness @abner what if it's empty? + insert_subscriber(address, request.headers.get('User-Agent'), 'Stockholm'); + app.logger.info("'" + address + "' has subscribed to 'Stockholm'") + return redirect(AGREEMENT_SUCCESS_URL); + +@app.route('/v1/meetups/gothenburg', methods=["POST"]) +def meetups_gothenburg(): + address = request.form['email'] # @robustness @abner what if it's empty? + insert_subscriber(address, request.headers.get('User-Agent'), 'Gothenburg'); + app.logger.info("'" + address + "' has subscribed to 'Gothenburg'") + return redirect(AGREEMENT_SUCCESS_URL); + +@app.route('/v1/meetups/grenoble', methods=["POST"]) +def meetups_grenoble(): + address = request.form['email'] # @robustness @abner what if it's empty? + insert_subscriber(address, request.headers.get('User-Agent'), 'Grenoble'); + app.logger.info("'" + address + "' has subscribed to 'Grenoble'") + return redirect(AGREEMENT_SUCCESS_URL); + +@app.route('/v1/meetups/israel', methods=["POST"]) +def meetups_israel(): + address = request.form['email'] # @robustness @abner what if it's empty? + insert_subscriber(address, request.headers.get('User-Agent'), 'Israel'); + app.logger.info("'" + address + "' has subscribed to 'Israel'") + return redirect(AGREEMENT_SUCCESS_URL); + +@app.route('/v1/meetups/subscribers', methods=["POST"]) +def meetups_subscribers(): + # Verify headers + + content_type = ( + request.headers['Content-Type'] if 'Content-Type' in request.headers else '' + ) + + if 'application/json' in content_type: + payload = request.json + else: + resp = { 'error' : 'JSON payload expected'} + return make_response(jsonify(resp), 415) + + # Confirm client version is compatible + + if 'client' not in payload or payload['client'] != MEETUP_INVITE_CLIENT_VERSION: + resp = { 'error' : 'Outdated client. Download latest version: https://git.handmadecities.com/meetups/meetupinvite2000/releases' } + return make_response(jsonify(resp), 403) + + # Extract city from POST request + + if 'city' not in payload: + resp = { 'error' : 'city field missing' } + return make_response(jsonify(resp), 400) + + city = payload['city'] + + app.logger.info('Received subscribers request with city \'' + city + '\'') + + if city not in shared_secrets: + resp = { 'error' : 'There are no official Handmade meetups in ' + city } + return make_response(jsonify(resp), 400) + + auth_header = ( request.headers['Authorization'] if 'Authorization' in request.headers else '') + + if shared_secrets[city] not in auth_header: + resp = { 'error' : 'wrong shared secret for ' + city } + return make_response(jsonify(resp), 401) + + # Query emails given the city + cur = g.db.cursor() + cur.execute('SELECT email FROM Subscribers WHERE city="' + city + '"') + rows = cur.fetchall() + + emails = [row[0] for row in rows] + resp = { 'emails' : emails } + + return make_response(jsonify(resp), 200) + +@app.route('/v1/meetups/chat/invite', methods=["POST"]) +def meetups_chat_invite(): + content_type = ( + request.headers['Content-Type'] if 'Content-Type' in request.headers else '' + ) + + if 'application/json' not in content_type: + resp = { 'error': 'JSON payload expected' } + return make_response(jsonify(resp), 415) + + payload = request.json + + if not payload or 'city' not in payload: + resp = { 'error': 'city field missing' } + return make_response(jsonify(resp), 400) + + city = payload['city'] + app.logger.info('Received chat invite request for city \'' + city + '\'') + + if city not in shared_secrets: + resp = { 'error' : 'There are no official Handmade meetups in ' + city } + return make_response(jsonify(resp), 400) + + auth_header = ( request.headers['Authorization'] if 'Authorization' in request.headers else '') + + if shared_secrets[city] not in auth_header: + resp = { 'error' : 'wrong shared secret for ' + city } + return make_response(jsonify(resp), 401) + + # Pick a random invite code and return it + + code = random.choice(INVITE_CODES) + app.logger.info('Issued chat invite for ' + city + ': ' + code) + resp = { + 'invite_code': code, + # TODO(jmug): Verify with Abner is this is an actual secret. + # NOTE(jmug): Request "revolt-invite-link" secret. + 'invite_link': '', + # NOTE(jmug): Request postmark-server-token secret. + 'postmark_server_token': '', + 'postmark_template_id_register': 41923378, + 'postmark_template_id_join': 41930444 + } + + return make_response(jsonify(resp), 200) +