from flask import Flask, request import psycopg2 as psql from os import environ as env import datetime import time import threading import uuid app = Flask(__name__) db_params = { 'database': env.get('DB_NAME'), 'user': env.get('DB_USER'), 'password': env.get('DB_PASS'), 'host': env.get('DB_HOST'), 'port': int(env.get('DB_PORT')) } db = None for _ in range(5): try: db = psql.connect(**db_params) except Exception as e: print(f"Failed to connect ({e}), retrying...") time.sleep(5) class PassQueue: def __init__(self): self.queue = [] def start(self): while True: if not self.queue: time.sleep(1) continue p = self.queue.pop(0) c = db.cursor() try: c.execute("BEGIN") c.execute(f"INSERT INTO public.pass (uuid, movie_uuid, user_first, user_last, user_email, pass_type, pass_price, pass_requested_at, payment_received)" f"VALUES ('{uuid.uuid4().hex}', '{p.movie_uuid}', '{p.first}', '{p.last}', '{p.email}', {p.type}, {p.price}, '{time.strftime('%Y-%m-%d %H:%M:%S')}', false)") c.execute("COMMIT") db.commit() except Exception as e: print(e) c.execute("ROLLBACK") db.rollback() pq = PassQueue() pq_thread = threading.Thread(target = pq.start, args = []) pq_thread.start() @app.route("/movies", methods = ['GET']) def movies(): c = db.cursor() fields = ['uuid', 'name', 'scheduled_datetime', 'movie_details', 'image_url', 'max_passes'] try: c.execute(f"SELECT {','.join(fields)} FROM public.movie") except Exception as e: c.close() print(e) return { "error": "Failed to list movies", "result": None }, 503 res = c.fetchall() c.close() return { "error": "", "result": [{fields[i]: v if type(v) != datetime.datetime else v.strftime("%Y-%m-%d %H:%M:%S") for i, v in enumerate(r)} for r in res] } @app.route("/movie/", methods = ['GET']) def movie_byid(movie_uuid): c = db.cursor() fields = ['name', 'scheduled_datetime', 'movie_details', 'image_url', 'max_passes'] try: c.execute(f"SELECT {','.join(fields)} FROM public.movie WHERE uuid='{movie_uuid}'") except Exception as e: c.close() print(e) return { "error": "Failed to search for a movie", "result": None }, 503 res = c.fetchone() c.close() if res: return { "error": "", "result": {fields[i]: v if type(v) != datetime.datetime else v.strftime("%Y-%m-%d %H:%M:%S") for i, v in enumerate(res)} } else: return { "error": f"No movie with uuid={movie_uuid} has been found", "result": None }, 404 @app.route("/passes", methods = ['GET']) def passes(): c = db.cursor() fields = ["uuid", "movie_uuid", "user_first", "user_last", "user_email", "pass_type", "pass_price", "pass_requested_at", "payment_received", "payment_received_at"] try: c.execute(f"SELECT {','.join(fields)} FROM public.pass") except Exception as e: c.close() print(e) return { "error": "Failed to list out passes", "result": None } res = c.fetchall() c.close() return { "error": "", "result": [{fields[i]: v if type(v) != datetime.datetime else v.strftime("%Y-%m-%d %H:%M:%S") for i, v in enumerate(r)} for r in res]} class PassValidator: def __init__(self): self.first = None self.last = None self.email = None self.type = None self.price = None self.movie_uuid = None def validate(self): if not self.first: return "First name missing" elif not self.last: return "Last name missing" elif not self.email: return "Email missing" elif not self.type: return "Pass type missing" elif not self.price: return "Pass price missing" elif not self.movie_uuid: return "Movie uuid missing" def queue(self): pq.queue.append(self) def fill_pass_from_form(p, f): print(f) def fill_pass_from_json(p, j): fields = ("first", "last", "email", "type", "price", "movie_uuid") for i in fields: exec(f"p.{i} = j.get('{i}')") @app.route("/apply_for_pass", methods = ['POST']) def apply_for_pass(): p = PassValidator() if request.form: fill_pass_from_form(p, request.form) elif request.is_json: fill_pass_from_json(p, request.json) else: return {"error": "only form and json interfaces are supported", "result": None}, 403 v = p.validate() if not v: p.queue() return {"error": None, "result": "OK"} else: return {"error": v, "result": None}