Skip to content
This repository was archived by the owner on Dec 8, 2022. It is now read-only.

Commit b376840

Browse files
committed
store question assets in BLOB columns rather than disk
1 parent a536815 commit b376840

File tree

6 files changed

+28
-115
lines changed

6 files changed

+28
-115
lines changed

CodeChallenge/api/questions.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import os
2+
from hashlib import blake2s
13
from hmac import compare_digest as str_cmp
24

35
from flask import Blueprint, current_app, jsonify, request
@@ -52,10 +54,21 @@ def next_question():
5254
return jsonify(status="error",
5355
reason=f"no questions for rank {rank!r}"), 404
5456

57+
# make filename less predictable
58+
data = bytes(current_app.config["SECRET_KEY"] + str(q.rank), "ascii")
59+
filename = blake2s(data).hexdigest()
60+
61+
asset = f"assets/{filename}{q.asset_ext}"
62+
asset_path = os.path.join(current_app.config["APP_DIR"], asset)
63+
64+
if not os.path.isfile(asset_path):
65+
with open(asset_path, "wb") as fhandle:
66+
fhandle.write(q.asset)
67+
5568
return jsonify(status="success",
5669
question=q.title,
5770
rank=rank,
58-
asset=f"assets/{q.asset}"), 200
71+
asset=asset), 200
5972

6073

6174
def answer_limit_attempts():

CodeChallenge/cli/questions.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import os.path
2+
13
import click
24
from flask import Blueprint
35
from tabulate import tabulate
@@ -22,6 +24,7 @@ def q_add(title, answer, rank, asset):
2224
ASSET is a path to a file to upload for a question
2325
"""
2426

27+
asset = os.path.abspath(asset)
2528
qid = add_question(title, answer, rank, asset)
2629

2730
click.echo(f"added question id {qid}")
@@ -33,11 +36,11 @@ def q_ls(tablefmt):
3336
"""List all questions in the database"""
3437
table = []
3538

36-
for q in Question.query.all():
37-
table.append((q.id, q.title, q.answer, q.rank, q.asset))
39+
for q in Question.query.all(): # type: Question
40+
table.append((q.id, q.title, q.answer, q.rank, f"{len(q.asset)} length blob", q.asset_ext))
3841

3942
click.echo(tabulate(table,
40-
("id", "title", "answer", "rank", "asset"),
43+
("id", "title", "answer", "rank", "asset", "asset_ext"),
4144
tablefmt=tablefmt))
4245

4346
if not table:

CodeChallenge/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class ProductionConfig(DefaultConfig):
5353
class DevelopmentConfig(ProductionConfig):
5454
SQLALCHEMY_DATABASE_URI = "mysql://cc-user:password@localhost" \
5555
"/code_challenge_local"
56+
# SQLALCHEMY_DATABASE_URI = "mysql+mysqldb://codechallenge:cHALcw9Z0HqB2gD9B1Kkmy83GvTI19x0NzRNO3zqZhqbIKqY9P@learndb002.cm1f2l4z67tv.us-west-2.rds.amazonaws.com/code_challenge"
5657
JWT_COOKIE_SECURE = False
5758
CODE_CHALLENGE_START = os.getenv("CODE_CHALLENGE_START", "1578596347")
5859
JWT_SECRET_KEY = "SuperSecret"

CodeChallenge/manage.py

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
import os
2-
import secrets
3-
import shutil
4-
5-
from flask import current_app
62

73
from .models import Question, db
84

@@ -14,19 +10,14 @@ def add_question(title, answer, rank, asset) -> Question:
1410
if q is not None:
1511
raise ValueError(f"a question with rank {rank} already exists")
1612

17-
ext = os.path.splitext(asset)[1]
18-
asset_dir = os.path.join(current_app.config["APP_DIR"], "assets")
19-
20-
filename = secrets.token_urlsafe() + ext
21-
22-
save_path = os.path.join(asset_dir, filename)
23-
24-
shutil.copyfile(asset, save_path)
25-
2613
q = Question()
2714
q.title = title
2815
q.answer = answer
29-
q.asset = filename
16+
17+
with open(asset, "rb") as fhandle:
18+
q.asset = fhandle.read()
19+
20+
q.asset_ext = os.path.splitext(asset)[1]
3021
q.rank = rank
3122

3223
db.session.add(q)
@@ -42,17 +33,6 @@ def del_question(question_id):
4233
if q is None:
4334
return False
4435

45-
if q.asset is not None:
46-
47-
asset_dir = os.path.join(current_app.config["APP_DIR"], "assets")
48-
filename = os.path.join(asset_dir, q.asset)
49-
try:
50-
os.remove(filename)
51-
except FileNotFoundError:
52-
print("warning: could not delete asset; "
53-
f"file not found: {filename}")
54-
pass
55-
5636
Question.query.filter_by(id=q.id).delete()
5737

5838
return True

CodeChallenge/models.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ class Question(db.Model):
1616
title = db.Column(db.String(5000), nullable=False)
1717
answer = db.Column(db.String(255), nullable=False)
1818
rank = db.Column(db.Integer, nullable=False)
19-
asset = db.Column(db.String(255))
19+
asset = db.Column(db.BLOB)
20+
asset_ext = db.Column(db.String(10))
2021

2122
def __repr__(self):
2223
return '<Question %r>' % self.id

alembic.ini

Lines changed: 0 additions & 85 deletions
This file was deleted.

0 commit comments

Comments
 (0)