Skip to content

Commit

Permalink
Implement user messaging (#182)
Browse files Browse the repository at this point in the history
  • Loading branch information
bkis authored Mar 22, 2024
2 parents 3019d66 + 2302af5 commit c08226f
Show file tree
Hide file tree
Showing 58 changed files with 2,753 additions and 369 deletions.
835 changes: 781 additions & 54 deletions Tekst-API/openapi.json

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions Tekst-API/tekst/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
from tekst.email import TemplateIdentifier, broadcast_admin_notification, send_email
from tekst.logging import log
from tekst.models.content import ContentBaseDocument
from tekst.models.message import MessageDocument
from tekst.models.resource import ResourceBaseDocument
from tekst.models.user import UserCreate, UserDocument, UserRead, UserUpdate

Expand Down Expand Up @@ -228,16 +229,19 @@ async def on_before_delete(
ResourceBaseDocument.owner_id == user.id, with_children=True
).to_list()
owned_resources_ids = [resource.id for resource in resources_docs]

# delete contents of owned resources
await ContentBaseDocument.find(
In(ContentBaseDocument.resource_id, owned_resources_ids),
with_children=True,
).delete()

# delete owned resources
await ResourceBaseDocument.find_one(
In(ResourceBaseDocument.id, owned_resources_ids),
with_children=True,
).delete()

# remove user ID from resource shares
await ResourceBaseDocument.find(
ResourceBaseDocument.shared_read == user.id,
Expand All @@ -252,6 +256,9 @@ async def on_before_delete(
Pull(ResourceBaseDocument.shared_write == user.id),
)

# delete user messages sent by user
await MessageDocument.find(MessageDocument.sender == user.id).delete()

async def on_after_delete(self, user: UserDocument, request: Request | None = None):
send_email(
user,
Expand Down
2 changes: 2 additions & 0 deletions Tekst-API/tekst/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from tekst.models.bookmark import BookmarkDocument
from tekst.models.content import ContentBaseDocument
from tekst.models.location import LocationDocument
from tekst.models.message import MessageDocument
from tekst.models.resource import ResourceBaseDocument
from tekst.models.segment import ClientSegmentDocument
from tekst.models.settings import PlatformSettingsDocument
Expand Down Expand Up @@ -48,6 +49,7 @@ async def init_odm(db: Database = get_db()) -> None:
PlatformSettingsDocument,
ClientSegmentDocument,
UserDocument,
MessageDocument,
BookmarkDocument,
AccessToken,
LocksStatus,
Expand Down
6 changes: 6 additions & 0 deletions Tekst-API/tekst/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,12 @@ def error_instance(
msg="Referenced resource ID in updates doesn't match the one in target content",
)

E_400_MESSAGE_TO_SELF = error_instance(
status_code=status.HTTP_400_BAD_REQUEST,
key="messageToSelf",
msg="You're not supposed to send a message to yourself",
)

E_400_CONTENT_TYPE_MISMATCH = error_instance(
status_code=status.HTTP_400_BAD_REQUEST,
key="contentTypeMismatch",
Expand Down
76 changes: 76 additions & 0 deletions Tekst-API/tekst/models/message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from datetime import datetime
from typing import Annotated

from pydantic import Field, StringConstraints

from tekst.models.common import (
DocumentBase,
ModelBase,
ModelFactoryMixin,
PydanticObjectId,
ReadBase,
)
from tekst.models.user import UserReadPublic
from tekst.utils import validators as val


class Message(ModelBase, ModelFactoryMixin):
sender: Annotated[
PydanticObjectId | None,
Field(
description="ID of the sender or None if this is a system message",
),
] = None
recipient: Annotated[
PydanticObjectId,
Field(
description="ID of the recipient",
),
]
content: Annotated[
str,
StringConstraints(
min_length=1,
max_length=1000,
strip_whitespace=True,
),
val.CleanupMultiline,
Field(
description="Content of the message",
),
]
time: Annotated[
datetime | None,
Field(
description="Time when the message was sent",
),
] = None
read: Annotated[
bool,
Field(
description="Whether the message has been read by the recipient",
),
] = False
deleted: Annotated[
PydanticObjectId | None,
Field(
description="ID of the user who deleted the message or None if not deleted",
),
] = None


class MessageDocument(Message, DocumentBase):
class Settings(DocumentBase.Settings):
name = "messages"
indexes = [
"recipient",
"sender",
]


class MessageRead(Message, ReadBase):
sender_user: UserReadPublic | None
recipient_user: UserReadPublic


MessageCreate = Message.create_model()
24 changes: 23 additions & 1 deletion Tekst-API/tekst/models/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def from_es_results(
)


class GeneralSearchSettings(ModelBase):
class PaginationSettings(ModelBase):
page: Annotated[
int,
conint(ge=1),
Expand All @@ -91,6 +91,28 @@ class GeneralSearchSettings(ModelBase):
description="Page size",
),
] = 10

def es_from(self) -> int:
return (self.page - 1) * self.page_size

def es_size(self) -> int:
return self.page_size

def mongo_skip(self) -> int:
return (self.page - 1) * self.page_size if self.page > 0 else 0

def mongo_limit(self) -> int:
return self.page_size


class GeneralSearchSettings(ModelBase):
pagination: Annotated[
PaginationSettings,
Field(
alias="pgn",
description="Pagination settings",
),
] = PaginationSettings()
sorting_preset: Annotated[
SortingPreset | None,
Field(
Expand Down
15 changes: 14 additions & 1 deletion Tekst-API/tekst/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,9 @@ class UserReadPublic(ModelBase):
affiliation: str | None = None
avatar_url: str | None = None
bio: str | None = None
is_active: bool
is_superuser: bool
public_fields: MaybePrivateUserFields = []
public_fields: MaybePrivateUserFields

@model_validator(mode="after")
def model_postprocess(self):
Expand Down Expand Up @@ -161,3 +162,15 @@ class UserCreate(User, schemas.BaseUserCreate):


UserUpdate = User.update_model(schemas.BaseUserUpdate)


class UsersSearchResult(ModelBase):
users: Annotated[list[UserRead], Field(description="Paginated users data")] = []
total: Annotated[int, Field(description="Total number of search hits")] = 0


class PublicUsersSearchResult(ModelBase):
users: Annotated[
list[UserReadPublic], Field(description="Paginated public users data")
] = []
total: Annotated[int, Field(description="Total number of search hits")] = 0
8 changes: 8 additions & 0 deletions Tekst-API/tekst/openapi/tags_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ def get_tags_metadata(documentation_url: str) -> list[dict[str, Any]]:
"url": documentation_url,
},
},
{
"name": "messages",
"description": "Messages users send and receive on the platform",
"externalDocs": {
"description": "View full documentation",
"url": documentation_url,
},
},
{
"name": "bookmarks",
"description": "The current user's bookmarks",
Expand Down
Loading

0 comments on commit c08226f

Please sign in to comment.