Exemplo n.º 1
0
    async def googleauth(request: Request):
        token = await oauth.google.authorize_access_token(request)
        user = await oauth.google.parse_id_token(request, token)
        if user['nonce'] != request.session['nonce']:
            raise HTTPException(status_code=401,
                                detail="Wrong nonce value, weird...")
        async with get_conn() as conn:
            found = await conn.fetchrow(
                """
                SELECT id, mail FROM
                    account_google
                    WHERE mail = $1
                """, user['email'])
            if found is None:
                res = await conn.fetchrow(
                    """
                        INSERT INTO account_google(mail)
                        VALUES ($1)
                        RETURNING id
                    """,
                    user['email'],
                )
                id_ = res['id']
            else:
                id_ = found['id']

            request.session['id'] = id_
            request.session['name'] = user['given_name']
            del request.session['nonce']
            return RedirectResponse(url='/')
Exemplo n.º 2
0
async def login(cred: UserLoginRequest, request: Request):
    """Login an user

    Sets the session and return OK when the user could log in,
    or return a JSON with the error field describing the problem.
    """
    async with get_conn() as conn:
        found = await conn.fetchrow(
            """
            SELECT id, password_hash, password_salt FROM
                account_internal
                WHERE username = $1
            """,
            cred.username)
        if found is None:
            return JSONResponse(
                dict(error='Invalid credentials'),
                status_code=status.HTTP_400_BAD_REQUEST,
            )
        hs = sha256(
            (cred.password + found['password_salt']).encode('utf-8')
            ).hexdigest()
        if hs == found['password_hash']:
            request.session['name'] = cred.username
            request.session['id'] = found['id']
            return 'OK'
        else:
            return JSONResponse(
                dict(error='Invalid credentials'),
                status_code=status.HTTP_400_BAD_REQUEST,
            )
Exemplo n.º 3
0
async def report_issue(issue: IssueReport, request: Request):
    current_user = request.session.get('id', 1)
    async with get_conn() as conn:
        await conn.fetchrow(
            """
            INSERT INTO card_trouble (
                from_id, to_id, account_id, ts, description, issue_type)
            VALUES
                ($1, $2, $3, current_timestamp, $4, $5)
            """,
            issue.from_id,
            issue.to_id,
            current_user,
            issue.description,
            issue.issue_type,
            )
        if current_user != 1:
            await conn.execute(
                """
                DELETE FROM card_user_state
                WHERE
                    from_id = $1
                    AND to_id = $2
                    AND account_id = $3
                """,
                issue.from_id,
                issue.to_id,
                current_user,
            )
    return 'OK'
Exemplo n.º 4
0
 async def stream_revlogs(current_user):
     print('Streaming for user', current_user)
     async with get_conn() as conn:
         async with conn.transaction():
             nl = '\n'.encode()
             async for record in conn.cursor(
                 get_sql('get_all_user_reviews'),
                     current_user):
                 print(record)
                 yield orjson.dumps(dict(record)) + nl
Exemplo n.º 5
0
async def register_answer(ans: CardAnswer, request: Request):
    """Register the answer an user gave to a card.

    This is used both to decide whether and when to show the card again and
    to collect information about which words and sentences are hard and which
    mistakes are the most common.
    """
    current_user = request.session.get('id', 1)
    async with get_conn() as conn:
        await conn.execute(
            get_sql('insert_revlog'),
            ans.from_id,
            ans.to_id,
            current_user,
            ans.given_answers,
            ans.expected_answers,
            ans.correct
        )
        # user 1 is the anonymous user, so there's no revision time update
        if current_user == 1:
            return 'OK'
        # repetitions in the same session do not affect further the state
        if not ans.repetition:
            if ans.correct:
                # correct, roughly equivalent to "Easy" in Anki
                # EF = EF + 0.15
                #  if new, EF is 2.5 + 0.15 = 2.65
                # I = 1 iif new card
                # I = 6 iif I = 1
                # I = round(I * EF) iif I > 1 and not new card
                # next review in I days
                await conn.execute(
                    get_sql('reschedule_correct_card'),
                    ans.from_id,
                    ans.to_id,
                    current_user
                )
            else:
                # not correct, roughly equivalent to "Again" in Anki
                # EF = max(1.3, EF - 0.2)
                #  if new, EF is assumed 2.5, so will become 2.5 - 0.2 = 2.3
                # I = 1
                await conn.execute(
                    get_sql('reschedule_wrong_card'),
                    ans.from_id,
                    ans.to_id,
                    current_user
                )
        return 'OK'
Exemplo n.º 6
0
async def my_revision_stats(request: Request):
    """Provide daily revision stats.

    The result is an array that for every day (UTC) reports how many
    reviews were there and how many were correct.
    """
    current_user = request.session.get('id', 1)
    if current_user == 1:
        return JSONResponse(
            dict(error='Not logged in, cannot get statistics'),
            status_code=status.HTTP_403_UNAUTHORIZED,
        )
    async with get_conn() as conn:
        return await conn.fetch(
            get_sql('get_user_stats'),
            current_user,
        )
Exemplo n.º 7
0
async def get_languages(request: Request):
    """Return list of all available languages and latest used ones."""
    current_user = request.session.get('id', 1)

    async with get_conn() as conn:
        langs = await conn.fetch("""
            SELECT iso693_3, name FROM language
            """)
        latest = await conn.fetchrow(
            """
            SELECT
                src_langs, tgt_lang
            FROM latest_language
                WHERE account_id = $1
            """,
            current_user)
        if latest is None:
            return dict(languages=langs)
        else:
            return dict(languages=langs, selected=latest)
Exemplo n.º 8
0
async def register_user(cred: UserCreationRequest, request: Request):
    """Register the user.

    Sets the session and return OK when the user could be created,
    or return a JSON with the error field describing the problem.
    """
    async with get_conn() as conn:
        found = await conn.fetchrow(
            """
            SELECT 1 FROM
                account_internal
                WHERE username = $1
            """,
            cred.username)
        if found is not None:
            return JSONResponse(
                dict(error='An user with this name already exists'),
                status_code=status.HTTP_409_CONFLICT,
            )
        alphabet = string.ascii_letters + string.digits
        salt = ''.join(secrets.choice(alphabet) for i in range(8))
        hs = sha256((cred.password + salt).encode('utf-8')).hexdigest()
        res = await conn.fetchrow(
            """
            INSERT INTO account_internal(
                username,
                password_hash,
                password_salt)
            VALUES ($1, $2, $3)
            RETURNING id
            """,
            cred.username,
            hs,
            salt)
        id_ = res['id']
        request.session['name'] = cred.username
        request.session['id'] = id_
        return 'OK'
Exemplo n.º 9
0
async def take_note(note: NoteAboutCard, request: Request):
    """Store a note about a card.

    The user can register a note about a card, to be shown next time they
    get the same sentence.
    There are two notes: the hint, which is shown before an answer, and the
    explanation, shown after.
    """
    current_user = request.session.get('id', 1)
    if current_user == 1:
        return JSONResponse(
            dict(error='Not logged in, cannot take notes'),
            status_code=status.HTTP_403_UNAUTHORIZED,
        )
    async with get_conn() as conn:
        await conn.execute(
            get_sql('upsert_card_notes'),
            note.from_id,
            note.to_id,
            current_user,
            note.hint,
            note.explanation,
        )
Exemplo n.º 10
0
async def draw_cards(qr: QuizRequest, request: Request):
    """Return the cards to test for this session.

    The selection contains both old cards to renew and a given number of
    brand new cards.
    """
    current_user = request.session.get('id', 1)

    async with get_conn() as conn:
        cards_new = await conn.fetch(
            get_sql('draw_new_cards'),
            qr.target_lang,
            qr.source_langs,
            current_user)
        if current_user == 1:
            return cards_new

        # store the selected language so next time the menu can show it directly
        await conn.execute(
            'DELETE FROM latest_language WHERE account_id=$1', current_user)
        await conn.execute(
            """
            INSERT INTO latest_language(
                account_id, src_langs, tgt_lang
                ) VALUES ($1, $2, $3)""",
            current_user,
            qr.source_langs,
            qr.target_lang
                )
        expired_cards = await conn.fetch(
            get_sql('get_expired_cards'),
            current_user)
        # TODO postgres insists in merging cards and languages before the join
        # with the user card state which is very selective
        # this is a workaround, the join is done in the backend
        languages_l = await conn.fetch(
            'SELECT id, name, iso693_3 FROM language')
        src_langs = {}
        to_lang_id = set()
        to_lang_name = None
        for lng in languages_l:
            if lng['iso693_3'] == qr.target_lang:
                to_lang_id = lng['id']
                to_lang_name = lng['name']

            if lng['iso693_3'] in qr.source_langs:
                src_langs[lng['id']] = (lng['iso693_3'], lng['name'])

        expired_cards = [
            dict(ec.items()) for ec in expired_cards
            if ec['from_lang'] in src_langs and ec['to_lang'] == to_lang_id]

        for ec in expired_cards:
            ec['from_language_code'] = src_langs[ec['from_lang']][0]
            ec['from_language'] = src_langs[ec['from_lang']][1]

            ec['to_language_code'] = qr.target_lang
            ec['to_language'] = to_lang_name

            del ec['from_lang']
            del ec['to_lang']

        return expired_cards + cards_new