Example #1
0
def index(request):
    try:
        if getenv("ENV") == "dev":
            update_cache()

        db = SafeFirestore()

        if request.path.endswith("main.js"):
            return main_js

        if request.path == "/" or request.path == "/admin/":
            return main_html

        if request.path.endswith("is_valid"):
            return jsonify({
                "success":
                is_admin(get_email(request), request.json["course"])
            })

        if "api" in request.path:
            method = request.path.split("/")[-1]
            return handle_api_call(method, request.json)

        if not is_admin(get_email(request), request.json["course"]):
            abort(401)

        course = request.json["course"]

        if request.path.endswith("list_exams"):
            exams = db.collection("exams").document(
                "all").get().to_dict()["exam-list"]
            return jsonify(
                [exam for exam in exams if exam.startswith(course + "-")])

        if request.path.endswith("get_exam"):
            exam = request.json["exam"]
            if not exam.startswith(course):
                abort(401)
            exam_json = db.collection("exams").document(exam).get().to_dict()
            secret = exam_json.pop("secret")
            return jsonify({
                "exam": exam_json,
                "secret": secret[:-1],
            })

    except:
        return jsonify({"success": False})

    return request.path
Example #2
0
def get_email(request):
    if getenv("ENV") == "dev":
        return request.json.get("loginas") or DEV_EMAIL

    token = request.json["token"]

    # validate token
    id_info = id_token.verify_oauth2_token(token, g_requests.Request(),
                                           CLIENT_ID)

    if id_info["iss"] not in [
            "accounts.google.com", "https://accounts.google.com"
    ]:
        raise ValueError("Wrong issuer.")

    email = id_info["email"]

    if "loginas" in request.json:
        exam = request.json["exam"]
        course = exam.split("-")[0]
        if not is_admin(email, course):
            raise PermissionError
        email = request.json["loginas"]

    return email
Example #3
0
def index(request):
    try:
        if getenv("ENV") == "dev":
            update_cache()

        db = firestore.Client()

        if request.path.endswith("main.js"):
            return main_js

        if request.path.endswith("list_exams"):
            return jsonify(
                db.collection("exam-alerts").document("all").get().to_dict()
                ["exam-list"])

        if request.path == "/" or request.json is None:
            return main_html

        if request.path.endswith("upload_ok_exam"):
            process_ok_exam_upload(db, request.json["data"],
                                   request.json["secret"])
            return jsonify({"success": True})

        exam = request.json["exam"]
        course = exam.split("-")[0]

        if request.path.endswith("fetch_data"):
            received_audio = request.json.get("receivedAudio")
            email = get_email(request)
            student_data = (db.collection("exam-alerts").document(
                exam).collection("students").document(email).get().to_dict())
            announcements = list(
                db.collection("exam-alerts").document(exam).collection(
                    "announcements").stream())
            return jsonify({
                "success":
                True,
                "exam_type":
                "ok-exam",
                "questions": [],
                # "questions": [
                #     {
                #         "questionName": question["student_question_name"],
                #         "startTime": question["start_time"],
                #         "endTime": question["end_time"],
                #     }
                #     for question in student_data["questions"]
                # ],
                "announcements":
                get_announcements(
                    student_data,
                    announcements,
                    received_audio,
                    lambda x: (db.collection("exam-alerts").document(
                        exam).collection("announcement_audio").document(x).get(
                        ).to_dict() or {}).get("audio"),
                ),
            })

        # only staff endpoints from here onwards
        email = get_email_from_secret(
            request.json["secret"]) if "secret" in request.json else get_email(
                request)
        if not is_admin(email, course):
            abort(401)

        if request.path.endswith("fetch_staff_data"):
            pass
        elif request.path.endswith("add_announcement"):
            announcement = request.json["announcement"]
            announcement["timestamp"] = time.time()
            ref = (db.collection("exam-alerts").document(exam).collection(
                "announcements").document())
            ref.set(announcement)
            spoken_message = (announcement.get("spoken_message")
                              or announcement["message"])
            audio = generate_audio(spoken_message)
            db.collection("exam-alerts").document(exam).collection(
                "announcement_audio").document(ref.id).set({"audio": audio})
        elif request.path.endswith("clear_announcements"):
            clear_collection(
                db,
                db.collection("exam-alerts").document(exam).collection(
                    "announcements"),
            )
            clear_collection(
                db,
                db.collection("exam-alerts").document(exam).collection(
                    "announcement_audio"),
            )
        elif request.path.endswith("delete_announcement"):
            target = request.json["id"]
            db.collection("exam-alerts").document(exam).collection(
                "announcements").document(target).delete()
        else:
            abort(404)

        # all staff endpoints return an updated state
        exam_data = db.collection("exam-alerts").document(exam).get().to_dict()
        announcements = sorted(
            ({
                "id": announcement.id,
                **announcement.to_dict()
            } for announcement in db.collection("exam-alerts").document(
                exam).collection("announcements").stream()),
            key=lambda announcement: announcement["timestamp"],
            reverse=True)
        return jsonify({
            "success": True,
            "exam": exam_data,
            "announcements": announcements
        })

    except Exception as e:
        if getenv("ENV") == "dev":
            raise
        print(e)
        print(dict(request.json))
        return jsonify({"success": False})
Example #4
0
def index(request):
    try:
        if getenv("ENV") == "dev":
            update_cache()

        db = SafeFirestore()

        if request.path.endswith("main.js"):
            return main_js

        if request.path.endswith("list_exams"):
            return jsonify(
                db.collection("exam-alerts").document("all").get().to_dict()
                ["exam-list"])

        if request.path == "/" or request.json is None:
            return main_html

        exam = request.json["exam"]
        course = exam.split("-")[0]
        prev_latest_timestamp = float(request.json["latestTimestamp"])

        def get_message(id):
            message = (db.collection("exam-alerts").document(exam).collection(
                "messages").document(id).get())

            return {
                **message.to_dict(),
                "id": message.id,
            }

        student_reply = False

        if request.path.endswith("ask_question"):
            email = get_email(request)
            student_question_name = request.json["question"]
            message = request.json["message"]

            student_data = get_student_data(db, email, exam)

            if student_question_name is not None:
                canonical_question_name = get_canonical_question_name(
                    student_data, student_question_name)
                if canonical_question_name is None:
                    return abort(400)
            else:
                canonical_question_name = None

            db.collection("exam-alerts").document(exam).collection(
                "messages").document().set(
                    dict(
                        question=canonical_question_name,
                        message=message,
                        email=email,
                        timestamp=time.time(),
                    ))
            student_reply = True

        if request.path.endswith("fetch_data") or student_reply:
            received_audio = request.json.get("receivedAudio")
            email = get_email(request)
            exam_data = db.collection("exam-alerts").document(
                exam).get().to_dict()
            student_data = get_student_data(db, email, exam)
            announcements = list(
                db.collection("exam-alerts").document(exam).collection(
                    "announcements").stream())
            messages = [{
                **message.to_dict(), "id": message.id
            } for message in (
                db.collection("exam-alerts").document(exam).collection(
                    "messages").where("timestamp", ">", prev_latest_timestamp
                                      ).where("email", "==", email).stream())
                        if message.to_dict()["email"] == email]

            messages, latest_timestamp = group_messages(messages, get_message)
            messages = messages[email]
            latest_timestamp = max(latest_timestamp, prev_latest_timestamp)

            for message in messages:
                if message["question"] is not None:
                    message["question"] = get_student_question_name(
                        student_data, message["question"])

            return jsonify({
                "success":
                True,
                "exam_type":
                "ok-exam",
                "enableClarifications":
                exam_data.get("enable_clarifications", False),
                "startTime":
                student_data["start_time"],
                "endTime":
                student_data["end_time"],
                "timestamp":
                time.time(),
                "questions": [
                    question["student_question_name"]
                    for question in student_data["questions"]
                ] if time.time() > student_data["start_time"] else [],
                "announcements":
                get_announcements(
                    student_data,
                    announcements,
                    messages,
                    received_audio,
                    lambda x: (db.collection("exam-alerts").document(
                        exam).collection("announcement_audio").document(x).get(
                        ).to_dict() or {}).get("audio"),
                ),
                "messages":
                sorted(
                    [{
                        "id": message["id"],
                        "message": message["message"],
                        "timestamp": message["timestamp"],
                        "question": message["question"] or "Overall Exam",
                        "responses": message["responses"],
                    } for message in messages],
                    key=lambda message: message["timestamp"],
                    reverse=True,
                ),
                "latestTimestamp":
                latest_timestamp,
            })

        # only staff endpoints from here onwards
        email = (get_email_from_secret(request.json["secret"])
                 if "secret" in request.json else get_email(request))
        if not is_admin(email, course):
            abort(401)

        if request.path.endswith("fetch_staff_data"):
            pass
        elif request.path.endswith("add_announcement"):
            announcement = request.json["announcement"]
            announcement["timestamp"] = time.time()
            ref = (db.collection("exam-alerts").document(exam).collection(
                "announcements").document())
            ref.set(announcement)
            spoken_message = announcement.get("spoken_message",
                                              announcement["message"])

            if spoken_message:
                audio = generate_audio(spoken_message)
                db.collection("exam-alerts").document(exam).collection(
                    "announcement_audio").document(ref.id).set(
                        {"audio": audio})

        elif request.path.endswith("clear_announcements"):
            clear_collection(
                db,
                db.collection("exam-alerts").document(exam).collection(
                    "announcements"),
            )
            clear_collection(
                db,
                db.collection("exam-alerts").document(exam).collection(
                    "announcement_audio"),
            )
        elif request.path.endswith("delete_announcement"):
            target = request.json["id"]
            db.collection("exam-alerts").document(exam).collection(
                "announcements").document(target).delete()
        elif request.path.endswith("send_response"):
            message_id = request.json["id"]
            reply = request.json["reply"]
            message = (db.collection("exam-alerts").document(exam).collection(
                "messages").document(message_id).get())
            ref = (db.collection("exam-alerts").document(exam).collection(
                "messages").document())
            ref.set({
                "timestamp": time.time(),
                "email": message.to_dict()["email"],
                "reply_to": message.id,
                "message": reply,
            })
            audio = generate_audio(
                reply, prefix="A staff member sent the following reply: ")
            db.collection("exam-alerts").document(exam).collection(
                "announcement_audio").document(ref.id).set({"audio": audio})
        elif request.path.endswith("get_question"):
            question_title = request.json["id"]
            student = request.json["student"]
            student_data = get_student_data(db, student, exam)
            question_title = get_student_question_name(student_data,
                                                       question_title)
            exam = db.collection("exams").document(exam).get().to_dict()
            questions = extract_questions(scramble(student, exam),
                                          include_groups=True)
            for question in questions:
                if get_name(question).strip() == question_title.strip():
                    return jsonify({"success": True, "question": question})
            abort(400)
        else:
            abort(404)

        # (almost) all staff endpoints return an updated state
        exam_data = db.collection("exam-alerts").document(exam).get().to_dict()
        announcements = sorted(
            ({
                "id": announcement.id,
                **announcement.to_dict()
            } for announcement in db.collection("exam-alerts").document(
                exam).collection("announcements").stream()),
            key=lambda announcement: announcement["timestamp"],
            reverse=True,
        )
        grouped_messages, latest_timestamp = group_messages(
            [{
                **message.to_dict(), "id": message.id
            } for message in db.collection(
                "exam-alerts").document(exam).collection("messages").where(
                    "timestamp", ">", prev_latest_timestamp).stream()],
            get_message,
        )
        latest_timestamp = max(prev_latest_timestamp, latest_timestamp)
        messages = sorted(
            [{
                "email": email,
                **message
            } for email, messages in grouped_messages.items()
             for message in messages],
            key=lambda message: (
                len(message["responses"]) > 0,
                -message["timestamp"],
                message["email"],
            ),
        )

        return jsonify({
            "success": True,
            "exam": exam_data,
            "announcements": announcements,
            "messages": messages,
            "latestTimestamp": latest_timestamp,
        })

    except Exception as e:
        if getenv("ENV") == "dev":
            raise
        print(e)
        print(dict(request.json))
        return jsonify({"success": False})