def delete_file(filename: str): """Deletes a file with a given filename. This runs a check on the user's token to see if they're allowed to delete the file.""" user = utils.check_user(token=request.headers.get("Authorization")) if user is None: return utils.respond(code=403, msg="Invalid API token.") file = utils.first(iterable=cache["files"], condition=lambda file: file.key == filename) if file is None: return utils.respond(code=404, msg="File not found.") # ======================================================== # - User isn't admin and isn't trying to delete their file # - Both admin but user is not superuser # ======================================================== if (not user.admin and user.id != file.owner.id) \ or (user.admin and file.owner.admin and user.id != file.owner.id and user.id != const.superuser.id): return utils.respond(code=403, msg="You don't own this file.") with postgres.cursor() as con: query = """DELETE FROM files WHERE key = %(key)s;""" con.execute(query, dict(key=filename)) os.remove(path=f"static/uploads/{filename}") cache["files"].remove(file) return utils.respond(code=200, msg="File has been deleted.")
def delete_url(url_key: str): """Deletes a URL with a given key. This runs a check on the user's token to see if they're allowed to delete the file.""" user = utils.check_user(token=request.headers.get("Authorization")) if user is None: return utils.respond(code=403, msg="Invalid API token.") found_url = utils.first(iterable=cache["urls"], condition=lambda url: url.key == url_key) if found_url is None: return utils.respond(code=404, msg="URL not found.") # ======================================================== # - User isn't admin and isn't trying to delete their file # - Both admin but user is not superuser # ======================================================== if (not user.admin and user.id != found_url.owner.id) \ or (user.admin and found_url.owner.admin and user.id != found_url.owner.id and user.id != const.superuser.id): return utils.respond(code=403, msg="You don't own this URL.") with postgres.cursor() as con: query = """DELETE FROM urls WHERE key = %(key)s;""" con.execute(query, dict(key=url_key)) cache["urls"].remove(found_url) return utils.respond(code=200, msg="URL has been deleted.")
def shorten_url(): """Takes a URL and shortens it. This is useful for particularly long URLs like Google Form links.""" user = utils.check_user(token=request.headers.get("Authorization")) if user is None: return utils.respond(code=403, msg="Invalid API token.") if not request.is_json: return utils.respond(code=403, msg="Missing JSON data.") to_shorten = request.json.get("url") if to_shorten is None: return utils.respond(code=403, msg="Missing JSON data.") found_url = utils.first(iterable=cache["urls"], condition=lambda url: url.url == to_shorten) if found_url: return utils.respond( code=409, msg="That URL has already been shortened.", link= f"https://{request.url_root.lstrip('http://')}u/{found_url.key}") if match(pattern=URL_REGEX, string=to_shorten) is None: return utils.respond(code=403, msg="That URL is invalid.") key = request.headers.get("URL-Name") if not (user.admin and config.url_shortening.custom_url.admin_only) or not key: key = utils.generate_key(cache_obj="urls") with postgres.cursor() as con: query = """INSERT INTO urls (owner_id, key, url, created_at) VALUES (%(owner_id)s, %(key)s, %(url)s, %(created_at)s) RETURNING id;""" con.execute( query, dict(owner_id=user.id, key=key, url=to_shorten, created_at=datetime.utcnow())) url_id = con.fetchone()[0] url_obj = URL(id=url_id, key=key, url=to_shorten, created_at=datetime.utcnow(), owner=user) cache["urls"].insert(0, url_obj) return f"https://{request.url_root.lstrip('http://')}u/{key}", 200
def get_link(link: str): """Redirects a user to a shortened URL if it exists.""" found_url = utils.first(iterable=cache["urls"], condition=lambda url: url.key == link) if found_url is None: abort(status=404) return redirect(location=found_url.url, code=303), 303
def authenticate(): """Queries the provided username and password to fetch a user.""" if not request.is_json: return utils.respond(code=422, msg="Missing JSON data.") username = request.json.get("username") password = request.json.get("password") if None in (username, password): return utils.respond(code=422, msg="Missing JSON data.") if username == const.superuser.username and password == const.superuser.password: return utils.respond(**dict(const.superuser)) user = utils.first(iterable=cache["users"], condition=lambda user: user.username == username and user.password == password) if user is None: return utils.respond(code=404, msg="User not found.") return utils.respond(**dict(user))
def get_file(filename: str): """Gets and returns an file if it exists.""" path = f"static/uploads/{filename}" if not os.path.exists(path): abort(status=404) file_type = utils.filetype(filename=filename) if file_type == "gif": file_type = "image" if file_type == "download": return send_file(filename_or_fp=path, as_attachment=True) if file_type == "text": with open(file=path) as file: content = file.read() return render_template( template_name_or_list="files/text.html", file=utils.first(iterable=cache.files, condition=lambda f: f.key == filename), config=config.meta, content=content, size=utils.bytes_4_humans(count=os.path.getsize(filename=path))) if file_type == "code": with open(file=path) as file: content = file.read() file_ext = utils.filext(filename=filename) lang = { "py": "python", "js": "javascript", "go": "go", "ts": "typescript", "cpp": "cpp", "html": "html", "css": "css", "json": "json" }.get(file_ext) return render_template( template_name_or_list="files/code.html", file=utils.first(iterable=cache.files, condition=lambda f: f.key == filename), config=config.meta, content=markdown(f"```{lang}\n{content}\n```"), size=utils.bytes_4_humans(count=os.path.getsize(filename=path)), lang=lang, lang_ext=file_ext) if file_type == "markdown": with open(file=path) as file: content = file.read() return render_template( template_name_or_list="files/markdown.html", file=utils.first(iterable=cache.files, condition=lambda f: f.key == filename), config=config.meta, content=markdown(content), size=utils.bytes_4_humans(count=os.path.getsize(filename=path))) return render_template( template_name_or_list=f"files/{file_type}.html", file=utils.first(iterable=cache.files, condition=lambda f: f.key == filename), config=config.meta, size=utils.bytes_4_humans(count=os.path.getsize(filename=path)))
def postgres_init(self): """Initialises the connection to the PostgreSQL server.""" constants.postgres = connect( dsn= "user={pg.user} password={pg.password} host={pg.host} port={pg.port} dbname={pg.database}" .format(pg=config.postgres)) console.info( text= "Connected to Postgres server at: {pg.user}@{pg.host}/{pg.database}" .format(pg=config.postgres)) constants.postgres.set_session(autocommit=True) # ================================= # Create the required schema tables # ================================= with constants.postgres.cursor() as con: queries = ( """CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, username TEXT UNIQUE, password TEXT, admin BOOLEAN, token TEXT, created_at TIMESTAMP);""", """CREATE TABLE IF NOT EXISTS files (id SERIAL PRIMARY KEY, owner_id INT, key TEXT UNIQUE, deleted BOOLEAN, created_at TIMESTAMP);""", """CREATE TABLE IF NOT EXISTS urls (id SERIAL PRIMARY KEY, owner_id INT, key TEXT UNIQUE, url TEXT, created_at TIMESTAMP);""" ) for query in queries: con.execute(query) constants.postgres.commit() # ================================= # Populate file, url and user cache # ================================= console.verbose(text="Beginning cache population...") console.verbose(text="Populating user cache...") query = """SELECT id, username, password, admin, token, created_at FROM users ORDER BY id ASC;""" con.execute(query) for user in con.fetchall(): cache["users"].append( User(id=user[0], username=user[1], password=user[2], admin=user[3], token=user[4], created_at=user[5])) console.verbose( text=f"Populated cache for user {user[0]} ({user[1]}).") console.verbose(text="Populating file cache...") query = """SELECT id, owner_id, key, deleted, created_at FROM files ORDER BY created_at DESC;""" con.execute(query) for file in con.fetchall(): cache["files"].append( File(id=file[0], key=file[2], deleted=file[3], created_at=file[4], owner=utils.first( iterable=cache["users"], condition=lambda user: user.id == file[1]))) console.verbose( text=f"Populated cache for file {file[0]} ({file[2]}).") console.verbose(text="Populating URL cache...") query = """SELECT id, owner_id, key, url, created_at FROM urls ORDER BY created_at DESC;""" con.execute(query) for url in con.fetchall(): cache["urls"].append( URL(id=url[0], key=url[2], url=url[3], created_at=url[4], owner=utils.first( iterable=cache["users"], condition=lambda user: user.id == url[1]))) console.verbose( text=f"Populated cache for url {url[0]} ({url[2]}).")