Esempio n. 1
0
async def get_user_statistics_in_group(
    group_id: str, user_id: int, db: Session = Depends(get_db)) -> UserGroup:
    """
    Get a user's statistic in a group (last read, hidden, etc.), including the group information.

    **Potential error codes in response:** 
    * `600`: if the user is not in the group,
    * `601`: if the group does not exist,
    * `250`: if an unknown error occurred.
    """
    try:
        message_amount = await environ.env.rest.group.count_messages_in_group(
            group_id)
        user_group_stats = await environ.env.rest.group.get_user_group_stats(
            group_id, user_id, db)

        query = GroupInfoQuery(count_messages=False)
        group_info = await environ.env.rest.group.get_group(
            group_id, query, db, message_amount=message_amount)

        return UserGroup(group=group_info, stats=user_group_stats)

    except NoSuchGroupException as e:
        log_error_and_raise_known(ErrorCodes.NO_SUCH_GROUP, sys.exc_info(), e)
    except UserNotInGroupException as e:
        log_error_and_raise_known(ErrorCodes.USER_NOT_IN_GROUP, sys.exc_info(),
                                  e)
    except Exception as e:
        log_error_and_raise_unknown(sys.exc_info(), e)
Esempio n. 2
0
async def delete_attachments_in_group_for_user(
    group_id: str,
    user_id: int,
    query: DeleteAttachmentQuery,
    db: Session = Depends(get_db)) -> Response:
    """
    Delete all attachments in this group for this user.

    This API is run asynchronously, and returns a `201 Created` instead of
    `200 OK`.

    **Potential error codes in response:**
    * `250`: if an unknown error occurred.
    """
    def _delete_attachments_in_group_for_user(group_id_, user_id_, query_,
                                              db_):
        environ.env.rest.group.delete_attachments_in_group_for_user(
            group_id_, user_id_, query_, db_)

    try:
        task = BackgroundTask(
            _delete_attachments_in_group_for_user,
            group_id_=group_id,
            user_id_=user_id,
            query_=query,
            db_=db,
        )
        return Response(background=task, status_code=HTTP_201_CREATED)
    except Exception as e:
        log_error_and_raise_unknown(sys.exc_info(), e)
Esempio n. 3
0
async def create_action_log_in_all_groups_for_user(
    user_id: int, query: ActionLogQuery, db: Session = Depends(get_db)
) -> Response:
    """
    Create an action log in all groups this user has joined.

    Only the `payload` field in the request body will be used by this API,
    any other fields that are specified will be ignored.

    This API is run asynchronously, and returns a `201 Created` instead of
    `200 OK`.

    **Potential error codes in response:**
    * `250`: if an unknown error occurred.
    """

    def _create_action_logs(user_id_, query_, db_):
        environ.env.rest.user.create_action_log_in_all_groups(user_id_, query_, db_)

    try:
        task = BackgroundTask(
            _create_action_logs, user_id_=user_id, query_=query, db_=db
        )
        return Response(background=task, status_code=HTTP_201_CREATED)
    except Exception as e:
        log_error_and_raise_unknown(sys.exc_info(), e)
Esempio n. 4
0
async def get_user_statistics(
    user_id: int, query: UserStatsQuery, db: Session = Depends(get_db)
) -> UserStats:
    """
    Get a user's statistics globally (not only for one group).

    Request body can specify `hidden` (default will count both
    hidden and not hidden), `only_unread` (default True), and
    `count_unread` (default True).

    If `hidden=true`, the `one_to_one_amount` and `group_amount`
    fields will ONLY include hidden groups. If `hidden=false` the
    fields will only include NOT HIDDEN groups. If not specified,
    both will be included.

    If `count_unread=false` the flags `unread_amount` and
    `unread_groups_amount` will both be `-1`. Default value is
    `true`.

    If `only_unread=true`, only groups with unread messages will
    be counted. Defaults to `true`. The fields `group_amount` and
    `one_to_one_amount` will be `-1`. Defaults to `true`.

    **Potential error codes in response:**
    * `250`: if an unknown error occurred.
    """
    try:
        return await environ.env.rest.user.get_user_stats(user_id, query, db)
    except Exception as e:
        log_error_and_raise_unknown(sys.exc_info(), e)
Esempio n. 5
0
async def send_message_to_user(
    user_id: int, query: SendMessageQuery, db: Session = Depends(get_db)
) -> Message:
    """
    User sends a message in a **1-to-1** conversation. It is not always known on client side if a
    **1-to-1** group exists between two users, so this API can then be used; Dino will do a group
    lookup and see if a group with `group_type=1` exists for them, send a message to it and return
    the group_id.

    If no group exists, Dino will create a __new__ **1-to-1** group, send the message and return the
    `group_id`.

    This API should NOT be used for EVERY **1-to-1** message. It should only be used if the client
    doesn't know if a group exists for them or not. After this API has been called once, the client
    should use the `POST /v1/groups/{group_id}/users/{user_id}/send` API for future messages as
    much as possible.

    When listing recent history, the client will know the group_id for recent **1-to-1** conversations
    (since the groups that are **1-to-1** have `group_type=1`), and should thus use the other send API.

    **Potential error codes in response:**
    * `604`: if the user does not exist,
    * `250`: if an unknown error occurred.
    """
    try:
        return await environ.env.rest.message.send_message_to_user(user_id, query, db)
    except NoSuchUserException as e:
        log_error_and_raise_known(ErrorCodes.NO_SUCH_USER, sys.exc_info(), e)
    except Exception as e:
        log_error_and_raise_unknown(sys.exc_info(), e)
Esempio n. 6
0
async def get_groups_updated_since(
    user_id: int, query: GroupUpdatesQuery, db: Session = Depends(get_db)
) -> List[UserGroup]:
    """
    Get a list of groups for this user that has changed since a certain time, sorted
    by last message sent. Used to sync changes to mobile apps.

    If `count_unread` is False, the field `unread` will have the value `-1`, and
    similarly if `receiver_unread` is False, the field `receiver_unread` will have
    the value `-1`.

    If `receiver_stats` is True, the following fields will be set:

    * `receiver_unread`
    * `receiver_delete_before`
    * `receiver_hide`
    * `receiver_deleted`

    The `receiver_stats` field is False by default, since not all queries needs this
    information. If it's False, the above fields will be `null`, except for
    `receiver_unread`, which will be -1.

    One receiver stat that is always returned (even if `receiver_stats` is False),
    is `receiver_highlight_time`. Default value is 789000000.0, (means "long ago",
    translates to 1995-01-01 22:40:00 UTC).

    **Potential error codes in response:**
    * `250`: if an unknown error occurred.
    """
    try:
        return await environ.env.rest.user.get_groups_updated_since(user_id, query, db)
    except Exception as e:
        log_error_and_raise_unknown(sys.exc_info(), e)
Esempio n. 7
0
async def create_a_new_group(
    user_id: int, query: CreateGroupQuery, db: Session = Depends(get_db)
) -> Group:
    """
    Create a new group. A list of user IDs can be specified to make them auto-join
    this new group.

    **Potential error codes in response:**
    * `250`: if an unknown error occurred.
    """
    try:
        return await environ.env.rest.group.create_new_group(user_id, query, db)
    except Exception as e:
        log_error_and_raise_unknown(sys.exc_info(), e)
Esempio n. 8
0
async def get_one_to_one_information(
    user_id: int, query: OneToOneQuery, db: Session = Depends(get_db)
) -> OneToOneStats:
    """
    Get details about a 1v1 group.

    **Potential error codes in response:**
    * `601`: if the group does not exist,
    * `250`: if an unknown error occurred.
    """
    try:
        return await environ.env.rest.group.get_1v1_info(user_id, query.receiver_id, db)
    except NoSuchGroupException as e:
        log_error_and_raise_known(ErrorCodes.NO_SUCH_GROUP, sys.exc_info(), e)
    except Exception as e:
        log_error_and_raise_unknown(sys.exc_info(), e)
Esempio n. 9
0
async def edit_group_information(
    group_id, query: UpdateGroupQuery, db: Session = Depends(get_db)) -> None:
    """
    Update group details.

    **Potential error codes in response:**
    * `601`: if the group does not exist,
    * `250`: if an unknown error occurred.
    """
    try:
        await environ.env.rest.group.update_group_information(
            group_id, query, db)
    except NoSuchGroupException as e:
        log_error_and_raise_known(ErrorCodes.NO_SUCH_GROUP, sys.exc_info(), e)
    except Exception as e:
        log_error_and_raise_unknown(sys.exc_info(), e)
Esempio n. 10
0
async def join_group(
    group_id: str, query: JoinGroupQuery,
    db: Session = Depends(get_db)) -> None:
    """
    Join a group.

    **Potential error codes in response:**
    * `601`: if the group does not exist,
    * `250`: if an unknown error occurred.
    """
    try:
        return await environ.env.rest.group.join_group(group_id, query, db)
    except NoSuchGroupException as e:
        log_error_and_raise_known(ErrorCodes.NO_SUCH_GROUP, sys.exc_info(), e)
    except Exception as e:
        log_error_and_raise_unknown(sys.exc_info(), e)
Esempio n. 11
0
async def leave_group(
    user_id: int,
    group_id: str,
    query: CreateActionLogQuery,
    db: Session = Depends(get_db)) -> None:
    """
    Leave a group.

    **Potential error codes in response:** 
    * `601`: if the group does not exist,
    * `250`: if an unknown error occurred.
    """
    try:
        return environ.env.rest.group.leave_group(group_id, user_id, query, db)
    except NoSuchGroupException as e:
        log_error_and_raise_known(ErrorCodes.NO_SUCH_GROUP, sys.exc_info(), e)
    except Exception as e:
        log_error_and_raise_unknown(sys.exc_info(), e)
Esempio n. 12
0
async def get_message_info(
        user_id: int, message_id: str, query: MessageInfoQuery
) -> Message:
    """
    Get details about a message. The `created_at` field on the query is
    needed to avoid large table scans when trying to find the message
    in Cassandra.

    **Potential error codes in response:**
    * `602`: if the message doesn't exist for the given group and user,
    * `250`: if an unknown error occurred.
    """
    try:
        return await environ.env.rest.message.get_message_info(user_id, message_id, query)
    except NoSuchMessageException as e:
        log_error_and_raise_known(ErrorCodes.NO_SUCH_MESSAGE, sys.exc_info(), e)
    except Exception as e:
        log_error_and_raise_unknown(sys.exc_info(), e)
Esempio n. 13
0
async def update_user_statistics_in_group(
        group_id: str,
        user_id: int,
        query: UpdateUserGroupStats,
        db: Session = Depends(get_db),
) -> None:
    """
    Update user statistic in a group. Only values specified in the query
    will be updated (if a field is blank in the query it won't be updated).

    This API should __NOT__ be used to update `last_read_time` when opening
    a conversation. The `last_read_time` is updated automatically when a
    user calls the `/v1/groups/{group_id}/user/{user_id}/histories` API for
    a group.

    **Can be used for updating the following:**

    * `last_read_time`: should be creating time of last received message,
    * `delete_before`: when a user deletes a conversation, set to the creation time of the last received message,
    * `highlight_time`: until when should this conversation be highlighted for this user,
    * `highlight_limit`: max number of highlights to allow (will cancel the oldest highlight time if this call causes the highlights to exceed the limit)
    * `hide`: whether to hide/show a conversation,
    * `bookmark`: whether to bookmark a conversation or not,
    * `pin`: whether to pin a conversation or not,
    * `rating`: a user can rate a conversation (1v1 usually).

    When setting `bookmark` to false, it will set the unread count to 0,
    and `last_read_time` will be `last_message_time`.

    **Potential error codes in response:**
    * `600`: if the user is not in the group,
    * `601`: if the group does not exist,
    * `250`: if an unknown error occurred.
    """
    try:
        return await environ.env.rest.group.update_user_group_stats(
            group_id, user_id, query, db)
    except NoSuchGroupException as e:
        log_error_and_raise_known(ErrorCodes.NO_SUCH_GROUP, sys.exc_info(), e)
    except UserNotInGroupException as e:
        log_error_and_raise_known(ErrorCodes.USER_NOT_IN_GROUP, sys.exc_info(),
                                  e)
    except Exception as e:
        log_error_and_raise_unknown(sys.exc_info(), e)
Esempio n. 14
0
async def get_attachment_info_from_file_id(
    group_id: str, query: AttachmentQuery, db: Session = Depends(get_db)
) -> Message:
    """
    Get attachment info from `file_id`.

    **Potential error codes in response:**
    * `601`: if the group does not exist,
    * `604`: if the attachment does not exist,
    * `250`: if an unknown error occurred.
    """
    try:
        return await environ.env.rest.message.get_attachment_info(group_id, query, db)
    except NoSuchGroupException as e:
        log_error_and_raise_known(ErrorCodes.NO_SUCH_GROUP, sys.exc_info(), e)
    except NoSuchAttachmentException as e:
        log_error_and_raise_known(ErrorCodes.NO_SUCH_ATTACHMENT, sys.exc_info(), e)
    except Exception as e:
        log_error_and_raise_unknown(sys.exc_info(), e)
Esempio n. 15
0
async def mark_all_groups_as_read(
    user_id: int, db: Session = Depends(get_db)) -> Response:
    """
    Mark all groups as read, including removing any bookmarks done by the
    user.

    This API is run asynchronously, and returns a 201 Created instead of
    200 OK.

    **Potential error codes in response:**
    * `250`: if an unknown error occurred.
    """
    def set_read_time(user_id_, db_):
        environ.env.rest.group.mark_all_as_read(user_id_, db_)

    try:
        task = BackgroundTask(set_read_time, user_id_=user_id, db_=db)
        return Response(background=task, status_code=HTTP_201_CREATED)
    except Exception as e:
        log_error_and_raise_unknown(sys.exc_info(), e)
Esempio n. 16
0
async def edit_message(
    user_id: int,
    message_id: str,
    query: EditMessageQuery,
    db: Session = Depends(get_db)) -> Message:
    """
    Edit the context of a message. Returns the ActionLog for the edit.

    **Potential error codes in response:**
    * `602`: if the message doesn't exist for the given group and user,
    * `250`: if an unknown error occurred.
    """
    try:
        return await environ.env.rest.message.edit(user_id, message_id, query,
                                                   db)
    except NoSuchMessageException as e:
        log_error_and_raise_known(ErrorCodes.NO_SUCH_MESSAGE, sys.exc_info(),
                                  e)
    except Exception as e:
        log_error_and_raise_unknown(sys.exc_info(), e)
Esempio n. 17
0
async def get_group_information(
    group_id: str, query: GroupInfoQuery, db: Session = Depends(get_db)
) -> Group:
    """
    Get details about one group.

    If `count_messages` is set to `true`, a count of all messages in the group
    will be returned in `message_amount`. If `count_messages` is set to `false`,
    `message_amount` will be `-1`. Default value is `false`.

    **Potential error codes in response:**
    * `601`: if the group does not exist,
    * `250`: if an unknown error occurred.
    """
    try:
        return await environ.env.rest.group.get_group(group_id, query, db)
    except NoSuchGroupException as e:
        log_error_and_raise_known(ErrorCodes.NO_SUCH_GROUP, sys.exc_info(), e)
    except Exception as e:
        log_error_and_raise_unknown(sys.exc_info(), e)
Esempio n. 18
0
async def get_group_history_for_user(
    group_id: str, user_id: int, query: MessageQuery, db: Session = Depends(get_db)
) -> Histories:
    """
    Get user visible history in a group for a user. Sorted by creation time in descendent.
    Response contains a list of messages sent in the group, a list of action logs, and a list
    the last read time for each user in the group.

    Calling this API will update `last_read_time` in this group for this user, and
    broadcast the new read time to every one else in the group that is online.

    History can be filtered by `message_type` to e.g. only list images sent in the group.

    Only one of `since` and `until` can be used at the same time. At least one needs to be
    specified.

    If `only_sender=true` (default is `false`), the API will only return messages history in
    this group that were sent by `user_id`. This can be combined with `until` and `per_page`,
    to paginate through all the messages for a user, but setting the next query's `until` to
    the `created_at` time of the last message returned from the previous query.

    If `admin_id` is set, and is greater than `0`, the read status will not be updated. Useful
    for getting history in admin UI without updating `last_read_time` of the user.

    **Potential error codes in response:**
    * `600`: if the user is not in the group,
    * `601`: if the group does not exist,
    * `605`: if the since/until parameters are not valid,
    * `250`: if an unknown error occurred.
    """
    try:
        return await environ.env.rest.group.histories(group_id, user_id, query, db)
    except NoSuchGroupException as e:
        log_error_and_raise_known(ErrorCodes.NO_SUCH_GROUP, sys.exc_info(), e)
    except UserNotInGroupException as e:
        log_error_and_raise_known(ErrorCodes.USER_NOT_IN_GROUP, sys.exc_info(), e)
    except InvalidRangeException as e:
        log_error_and_raise_known(ErrorCodes.WRONG_PARAMETERS, sys.exc_info(), e)
    except Exception as e:
        log_error_and_raise_unknown(sys.exc_info(), e)
Esempio n. 19
0
async def create_action_log(
    user_id: int, query: ActionLogQuery, db: Session = Depends(get_db)
) -> None:
    """
    Create an action log in a group.

    If `receiver_id` is specified, the 1-to-1 group will be
    automatically created if it doesn't already exist. One case when this is
    desirable is when user A sends a friend request to used B; the action log
    is "user A requested to be a friend of user B", but the group needs to
    be created first, and to avoid doing two API calls, the group is
    automatically created.

    Multi-user groups are NOT automatically created when this API is called.

    **Potential error codes in response:**
    * `250`: if an unknown error occurred.
    """
    try:
        return environ.env.rest.group.create_action_log(query, db, user_id=user_id)
    except Exception as e:
        log_error_and_raise_unknown(sys.exc_info(), e)
Esempio n. 20
0
async def update_user_stats(
    user_id: int, db: Session = Depends(get_db)) -> Response:
    """
    Update user status, e.g. because the user got blocked, is a bot, was
    force fake-checked, etc. Will set `last_updated_at` on all user group
    stats that has had an interaction with this user (including this
    user's user group stats).

    This API is run asynchronously, and returns a 201 Created instead of
    200 OK.

    **Potential error codes in response:**
    * `250`: if an unknown error occurred.
    """
    def set_last_updated(user_id_, db_):
        environ.env.rest.group.set_last_updated_at_on_all_stats_related_to_user(
            user_id_, db_)

    try:
        task = BackgroundTask(set_last_updated, user_id_=user_id, db_=db)
        return Response(background=task, status_code=HTTP_201_CREATED)
    except Exception as e:
        log_error_and_raise_unknown(sys.exc_info(), e)
Esempio n. 21
0
async def send_message_to_group(
    group_id: str, user_id: int, query: SendMessageQuery, db: Session = Depends(get_db)
) -> Message:
    """
    User sends a message in a group. This API should also be used for **1-to-1** conversations
    if the client knows the `group_id` for the **1-to-1** conversations. Otherwise the
    `POST /v1/users/{user_id}/send` API can be used to send a message and get the `group_id`.

    **Potential error codes in response:**
    * `600`: if the user is not in the group,
    * `601`: if the group does not exist,
    * `250`: if an unknown error occurred.
    """
    try:
        return await environ.env.rest.message.send_message_to_group(
            group_id, user_id, query, db
        )
    except NoSuchGroupException as e:
        log_error_and_raise_known(ErrorCodes.NO_SUCH_GROUP, sys.exc_info(), e)
    except UserNotInGroupException as e:
        log_error_and_raise_known(ErrorCodes.USER_NOT_IN_GROUP, sys.exc_info(), e)
    except Exception as e:
        log_error_and_raise_unknown(sys.exc_info(), e)
Esempio n. 22
0
async def delete_all_groups_for_user(
    user_id: int, query: CreateActionLogQuery,
    db: Session = Depends(get_db)) -> Response:
    """
    When a user removes his/her profile, make the user leave all groups.

    This API is run asynchronously, and returns a `201 Created` instead of
    `200 OK`.

    **Potential error codes in response:**
    * `250`: if an unknown error occurred.
    """
    def leave_all_groups(user_id_, query_, db_):
        environ.env.rest.group.delete_all_groups_for_user(
            user_id_, query_, db_)

    try:
        task = BackgroundTask(leave_all_groups,
                              user_id_=user_id,
                              query_=query,
                              db_=db)
        return Response(background=task, status_code=HTTP_201_CREATED)
    except Exception as e:
        log_error_and_raise_unknown(sys.exc_info(), e)
Esempio n. 23
0
async def create_an_attachment(
    user_id: int,
    message_id: str,
    query: CreateAttachmentQuery,
    db: Session = Depends(get_db),
) -> Message:
    """
    Create an attachment.

    When a user sends an image or video, first call the "send message API" with the
    `message_type` set to `image` (or similar). When the image has finished processing
    in the backend, call this API to create the actual attachment meta data for it.

    First we create the "empty" message so indicate to all relevant users that someone
    has sent something, usually the client application will show a loading icon for this
    "empty" message. When this API is called after the image processing is done, Dino
    will broadcast an update to the clients with a reference to the ID of the "empty"
    message, so the real image can replace the loading icon.

    **Potential error codes in response:**
    * `601`: if the group does not exist,
    * `602`: if the message does not exist,
    * `250`: if an unknown error occurred.
    """
    try:
        return await environ.env.rest.message.create_attachment(
            user_id, message_id, query, db
        )
    except QueryValidationError as e:
        log_error_and_raise_known(ErrorCodes.WRONG_PARAMETERS, sys.exc_info(), e)
    except NoSuchGroupException as e:
        log_error_and_raise_known(ErrorCodes.NO_SUCH_GROUP, sys.exc_info(), e)
    except NoSuchMessageException as e:
        log_error_and_raise_known(ErrorCodes.NO_SUCH_MESSAGE, sys.exc_info(), e)
    except Exception as e:
        log_error_and_raise_unknown(sys.exc_info(), e)
Esempio n. 24
0
async def get_attachments_in_group_for_user(
    group_id: str, user_id: int, query: MessageQuery, db: Session = Depends(get_db)
) -> List[Message]:
    """
    Get all attachments in this group for this user.

    Only one of `since` and `until` can be used at the same time. At least one needs to be
    specified.

    **Potential error codes in response:**
    * `600`: if the user is not in the group,
    * `601`: if the group does not exist,
    * `250`: if an unknown error occurred.
    """
    try:
        return await environ.env.rest.group.get_attachments_in_group_for_user(
            group_id, user_id, query, db
        )
    except NoSuchGroupException as e:
        log_error_and_raise_known(ErrorCodes.NO_SUCH_GROUP, sys.exc_info(), e)
    except UserNotInGroupException as e:
        log_error_and_raise_known(ErrorCodes.USER_NOT_IN_GROUP, sys.exc_info(), e)
    except Exception as e:
        log_error_and_raise_unknown(sys.exc_info(), e)
Esempio n. 25
0
async def notify_users(query: NotificationQuery, db: Session = Depends(get_db)) -> None:
    try:
        return await environ.env.rest.broadcast.broadcast_event(query, db)
    except Exception as e:
        log_error_and_raise_unknown(sys.exc_info(), e)
Esempio n. 26
0
async def get_message_count_for_user_in_group(
    group_id: str, user_id: int, query: Optional[OnlySenderQuery] = None, db: Session = Depends(get_db)
) -> MessageCount:
    """
    Count the number of messages in a group since a user's `delete_before`.

    If `only_sender` is set to False (default value), the messages for all
    users in the groups will be counted. If set to True, only messages sent
    by the specified `user_id` will be counted. When set to True, only
    messages send by this user _after_ his/her `delete_before` and _before_
    his/her `last_sent_time` will be counted.

    Note: setting `only_sender=true` is slow. Around 2 seconds for a group
    of 6k messages. This is because we can not filter by `user_id` in
    Cassandra, and have to instead batch query for all messages in the group
    and filter out and count afterwards.

    **Potential error codes in response:**
    * `600`: if the user is not in the group,
    * `601`: if the group does not exist,
    * `250`: if an unknown error occurred.
    """
    try:
        group_info: UserGroupStatsBase = environ.env.db.get_user_stats_in_group(group_id, user_id, db)

        # can't filter by user id in cassandra without restricting 'created_at', so
        # use the cached value from the rdbms
        if query and query.only_sender:
            # can return both None and -1; -1 means we've checked the db before, but it has not
            # yet been counted, to avoid checking the db every time a new message is sent
            message_count = environ.env.db.get_sent_message_count(group_id, user_id, db)

            # if it hasn't been counted before, count from cassandra in batches (could be slow)
            if message_count is None or message_count == -1:
                # until isn't inclusive, so the last message sent won't be counted otherwise;
                until = group_info.last_sent
                until += timedelta(milliseconds=1)

                message_count = environ.env.storage.count_messages_in_group_from_user_since(
                    group_id,
                    user_id,
                    until=until,
                    since=group_info.delete_before
                )
                environ.env.db.set_sent_message_count(group_id, user_id, message_count, db)

        else:
            message_count = environ.env.storage.count_messages_in_group_since(
                group_id, group_info.delete_before
            )

        return MessageCount(
            group_id=group_id,
            user_id=user_id,
            delete_before=to_ts(group_info.delete_before),
            message_count=message_count
        )

    except NoSuchGroupException as e:
        log_error_and_raise_known(ErrorCodes.NO_SUCH_GROUP, sys.exc_info(), e)
    except UserNotInGroupException as e:
        log_error_and_raise_known(ErrorCodes.USER_NOT_IN_GROUP, sys.exc_info(), e)
    except Exception as e:
        log_error_and_raise_unknown(sys.exc_info(), e)