from fastapi import FastAPI from fastapi.responses import JSONResponse from datetime import datetime from uuid import uuid4 import sqlite3 # use database residing here DB_LOCATION = ( "../testbox/photovoter.dblite" # Q: any allowances for this being not OUR database? ) app = FastAPI() con = sqlite3.connect(DB_LOCATION) con.row_factory = sqlite3.Row cur = con.cursor() # NB! single is enough for now, we might require multiple later @app.get("/new_session", responses={503: {"description": "Unable to initiate session"}}) async def new_session(): """Start a new session""" # add session to the database time = datetime.utcnow().replace(microsecond=0).isoformat() tries = 3 # something is very wrong with our random, if we miss 3 times for i in range(tries): try: # generate a cookie cookie = uuid4().hex cur.execute( """INSERT INTO sessions(cookie, time) VALUES(:cookie, :time) """, {"cookie": cookie, "time": time}, ) con.commit() except sqlite3.IntegrityError as e: if i < tries - 1 and str(e) == "UNIQUE constraint failed: sessions.cookie": continue elif str(e) == "UNIQUE constraint failed: sessions.cookie": return JSONResponse(status_code=503) else: raise break # return new session cookie return {"cookie": cookie} @app.get( "/next_picture/{cookie}", responses={ 204: {"description": "All available images have been appraised"}, 409: {"description": "Uninitiated session"}, }, ) async def next_picture(cookie: str): """Request new picture to rate.""" # check if the cookie is valid cur.execute( """SELECT sessionid FROM sessions WHERE cookie = :cookie LIMIT 1""", {"cookie": cookie}, ) sessionid = cur.fetchone() if sessionid is None: return JSONResponse(status_code=409) # take not rated picture from the database # do not insert anything in the database yet # return this picture # SELECT all images EXCEPT images with marks from the current session -> # -> SELECT paths for these images # FIXME[0]: can this be done better? cur.execute( """SELECT imgid, resizedpath FROM images WHERE imgid IN (SELECT imgid FROM images EXCEPT SELECT imgid FROM marks WHERE sessionid = :sessionid) LIMIT 1 """, {"sessionid": sessionid["sessionid"]}, ) r = cur.fetchone() if r is not None: return {"picture_id": r["imgid"], "picture_uri": r["resizedpath"]} else: # All available pics have been voted for by this sessionid return JSONResponse(status_code=204) @app.get( "/rate_picture/{cookie}/{picture_id}/{mark}", responses={ 406: {"description": "Already appraised"}, 409: {"description": "Uninitiated session"}, }, ) async def rate_picture(cookie: str, picture_id: int, mark: int): """Submit a rating for the picture""" # check if the cookie is valid cur.execute( """SELECT sessionid FROM sessions WHERE cookie = :cookie LIMIT 1""", {"cookie": cookie}, ) sessionid = cur.fetchone() if sessionid is None: return JSONResponse(status_code=409) # add new mark to the session table try: cur.execute( """INSERT INTO marks(imgid, sessionid, mark) VALUES(:imgid,:sessionid,:mark) """, {"imgid": picture_id, "sessionid": sessionid["sessionid"], "mark": mark}, ) con.commit() except sqlite3.IntegrityError as e: if str(e) == "UNIQUE constraint failed: marks.imgid, marks.sessionid": return JSONResponse(status_code=406) return {"status": "success"}