diff --git a/CodeChallenge/api/vote.py b/CodeChallenge/api/vote.py index 9685f44..f919381 100644 --- a/CodeChallenge/api/vote.py +++ b/CodeChallenge/api/vote.py @@ -2,6 +2,7 @@ from flask_jwt_extended import get_current_user, jwt_optional from flask_mail import Message from itsdangerous import URLSafeSerializer +from sqlalchemy import or_ from .. import core from ..auth import Users @@ -49,25 +50,14 @@ def get_contestants(): contestants = [] for ans in p.items: # type: Answer - display = None - if ans.user.studentfirstname \ - and ans.user.studentlastname: - display = f"{ans.user.studentfirstname} " \ - f"{ans.user.studentlastname[0]}." - - confirmed_votes = [] - for v in ans.votes: - if v.confirmed: - confirmed_votes.append(v) - contestants.append(dict( id=ans.id, text=ans.text, - numVotes=len(confirmed_votes), + numVotes=ans.confirmed_votes(), firstName=ans.user.studentfirstname, lastName=ans.user.studentlastname, username=ans.user.username, - display=display + display=ans.user.display() )) return jsonify( @@ -188,3 +178,51 @@ def vote_confirm(): return jsonify(status="success", reason="vote confirmed") + + +@bp.route("/search", methods=["GET"]) +def search(): + keyword = request.args.get("q") + try: + page = int(request.args.get("page", 1)) + per = int(request.args.get("per", 20)) + except ValueError: + return jsonify(status="error", + reason="invalid 'page' or 'per' parameter"), 400 + + if keyword is None: + return jsonify(status="error", reason="missing 'q' parameter"), 400 + + keyword = f"%{keyword}%" + + p = Answer.query \ + .join(Answer.question) \ + .join(Answer.user) \ + .filter(Question.rank == core.max_rank(), + Answer.correct, or_(Users.username.ilike(keyword), Users.studentlastname.ilike(keyword), + Users.studentlastname.ilike(keyword))) \ + .paginate(page=page, per_page=per) + + results = [] + + for ans in p.items: # type: Answer + results.append(dict( + id=ans.id, + text=ans.text, + numVotes=ans.confirmed_votes(), + firstName=ans.user.studentfirstname, + lastName=ans.user.studentlastname, + username=ans.user.username, + display=ans.user.display() + )) + + return jsonify( + items=results, + totalItems=p.total, + page=p.page, + totalPages=p.pages, + hasNext=p.has_next, + nextNum=p.next_num, + hasPrev=p.has_prev, + prevNum=p.prev_num + ) diff --git a/CodeChallenge/auth.py b/CodeChallenge/auth.py index eb9a1bd..248cf86 100644 --- a/CodeChallenge/auth.py +++ b/CodeChallenge/auth.py @@ -53,6 +53,13 @@ def votes(self): .all() return v + def display(self): + if self.studentfirstname is not None \ + and self.studentlastname is not None \ + and len(self.studentlastname): + return f"{self.studentfirstname} " \ + f"{self.studentlastname[0]}." + def hash_password(plaintext): ph = argon2.PasswordHasher() diff --git a/CodeChallenge/models.py b/CodeChallenge/models.py index 395ec01..48a556b 100644 --- a/CodeChallenge/models.py +++ b/CodeChallenge/models.py @@ -40,6 +40,14 @@ class Answer(db.Model): votes = db.relationship("Vote", cascade="all,delete", lazy=True, uselist=True) + def confirmed_votes(self) -> int: + confirmed = 0 + for vote in self.votes: + if vote.confirmed: + confirmed += 1 + + return confirmed + class Vote(db.Model): id = db.Column(db.Integer, primary_key=True) diff --git a/tests/test_question.py b/tests/test_question.py index 1e06a3f..71b401d 100644 --- a/tests/test_question.py +++ b/tests/test_question.py @@ -380,6 +380,24 @@ def test_cast_vote(client_challenge_lastq): assert rv.json["status"] == "success" +@pytest.mark.skipif(not os.getenv("SANDBOX_API_URL"), reason="no final question") +def test_vote_search(client_challenge_lastq): + rv = client_challenge_lastq.get("/api/v1/vote/search?q=sam") + assert rv.status_code == 200 + + results = rv.json["items"] + assert len(results) == 1 + assert results[0]["username"] == "cwhqsam" + assert results[0]["numVotes"] == 1 + + rv2 = client_challenge_lastq.get("/api/v1/vote/search?q=hOffMan") + assert rv2.status_code == 200 + + results2 = rv.json["items"] + assert len(results2) == 1 + assert results2[0]["username"] == "cwhqsam" + + @pytest.mark.skipif(not os.getenv("SANDBOX_API_URL"), reason="no final question") def test_cast_notregistered(client_challenge_lastq): client_challenge_lastq.cookie_jar.clear() # logout