mirror of
https://github.com/etesync/server
synced 2024-11-24 01:38:18 +00:00
SimpleMessage: add a new endpoint for sending messages.
This will probably be replaced with a more robust mechanism that supports a better encryption mechanisms. This is just here for the meanwhile to support pressing needs for a way to message users. Once this is replaced, we will probably also replace the invitation mechanism to use the new messaging mechanism.
This commit is contained in:
parent
d55204b96d
commit
d21c76ec5a
28
django_etebase/migrations/0037_simplemessage.py
Normal file
28
django_etebase/migrations/0037_simplemessage.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 3.1.1 on 2020-12-24 13:03
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('django_etebase', '0036_auto_20201214_1128'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SimpleMessage',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('uid', models.CharField(db_index=True, max_length=43, unique=True, validators=[django.core.validators.RegexValidator(message='Not a valid UID', regex='^[a-zA-Z0-9\\-_]{20,}$')])),
|
||||||
|
('version', models.PositiveSmallIntegerField(default=1)),
|
||||||
|
('content', models.BinaryField()),
|
||||||
|
('fromUser', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='outgoing_simple_messages', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('toUser', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='incoming_simple_messages', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
@ -236,6 +236,26 @@ class CollectionInvitation(models.Model):
|
|||||||
return self.fromMember.collection
|
return self.fromMember.collection
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleMessage(models.Model):
|
||||||
|
uid = models.CharField(
|
||||||
|
db_index=True, unique=True, blank=False, null=False, max_length=43, validators=[UidValidator]
|
||||||
|
)
|
||||||
|
version = models.PositiveSmallIntegerField(default=1)
|
||||||
|
|
||||||
|
fromUser = models.ForeignKey(
|
||||||
|
settings.AUTH_USER_MODEL, related_name="outgoing_simple_messages", on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
toUser = models.ForeignKey(
|
||||||
|
settings.AUTH_USER_MODEL, related_name="incoming_simple_messages", on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
content = models.BinaryField(editable=False, blank=False, null=False)
|
||||||
|
|
||||||
|
objects: models.BaseManager["SimpleMessage"]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "{} {} -> {}".format(self.uid, self.fromUser, self.toUser)
|
||||||
|
|
||||||
|
|
||||||
class UserInfo(models.Model):
|
class UserInfo(models.Model):
|
||||||
owner = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True)
|
owner = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True)
|
||||||
version = models.PositiveSmallIntegerField(default=1)
|
version = models.PositiveSmallIntegerField(default=1)
|
||||||
|
@ -11,6 +11,7 @@ from .routers.authentication import authentication_router
|
|||||||
from .routers.collection import collection_router, item_router
|
from .routers.collection import collection_router, item_router
|
||||||
from .routers.member import member_router
|
from .routers.member import member_router
|
||||||
from .routers.invitation import invitation_incoming_router, invitation_outgoing_router
|
from .routers.invitation import invitation_incoming_router, invitation_outgoing_router
|
||||||
|
from .routers.simplemessage import simplemessage_router
|
||||||
|
|
||||||
|
|
||||||
def create_application(prefix="", middlewares=[]):
|
def create_application(prefix="", middlewares=[]):
|
||||||
@ -36,6 +37,7 @@ def create_application(prefix="", middlewares=[]):
|
|||||||
app.include_router(
|
app.include_router(
|
||||||
invitation_outgoing_router, prefix=f"{BASE_PATH}/invitation/outgoing", tags=["outgoing invitation"]
|
invitation_outgoing_router, prefix=f"{BASE_PATH}/invitation/outgoing", tags=["outgoing invitation"]
|
||||||
)
|
)
|
||||||
|
app.include_router(simplemessage_router, prefix=f"{BASE_PATH}/simplemessage", tags=["simple message"])
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
from etebase_fastapi.routers.test_reset_view import test_reset_view_router
|
from etebase_fastapi.routers.test_reset_view import test_reset_view_router
|
||||||
|
145
etebase_fastapi/routers/simplemessage.py
Normal file
145
etebase_fastapi/routers/simplemessage.py
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import typing as t
|
||||||
|
|
||||||
|
from django.db import transaction, IntegrityError
|
||||||
|
from django.db.models import QuerySet
|
||||||
|
from fastapi import APIRouter, Depends, status, Request
|
||||||
|
|
||||||
|
from django_etebase import models
|
||||||
|
from django_etebase.utils import get_user_queryset, CallbackContext
|
||||||
|
from myauth.models import UserType, get_typed_user_model
|
||||||
|
from .authentication import get_authenticated_user
|
||||||
|
from ..msgpack import MsgpackRoute
|
||||||
|
from ..exceptions import HttpError
|
||||||
|
from ..utils import (
|
||||||
|
get_object_or_404,
|
||||||
|
Context,
|
||||||
|
BaseModel,
|
||||||
|
permission_responses,
|
||||||
|
PERMISSIONS_READ,
|
||||||
|
PERMISSIONS_READWRITE,
|
||||||
|
)
|
||||||
|
|
||||||
|
User = get_typed_user_model()
|
||||||
|
simplemessage_router = APIRouter(route_class=MsgpackRoute, responses=permission_responses)
|
||||||
|
SimpleMessageQuerySet = QuerySet[models.SimpleMessage]
|
||||||
|
default_queryset: SimpleMessageQuerySet = models.SimpleMessage.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
def get_queryset(user: UserType = Depends(get_authenticated_user)) -> SimpleMessageQuerySet:
|
||||||
|
return default_queryset.filter(toUser=user)
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleMessageCommon(BaseModel):
|
||||||
|
uid: str
|
||||||
|
version: int
|
||||||
|
toUsername: str
|
||||||
|
content: bytes
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleMessageIn(SimpleMessageCommon):
|
||||||
|
def validate_db(self, context: Context):
|
||||||
|
user = context.user
|
||||||
|
if user is not None and (user.username == self.toUsername.lower()):
|
||||||
|
raise HttpError("no_self_invite", "Inviting yourself is not allowed")
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleMessageOut(SimpleMessageCommon):
|
||||||
|
fromUsername: str
|
||||||
|
fromPubkey: bytes
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
orm_mode = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_orm(cls: t.Type["SimpleMessageOut"], obj: models.SimpleMessage) -> "SimpleMessageOut":
|
||||||
|
return cls(
|
||||||
|
uid=obj.uid,
|
||||||
|
version=obj.version,
|
||||||
|
toUsername=obj.toUser.username,
|
||||||
|
fromUsername=obj.fromUser.username,
|
||||||
|
fromPubkey=bytes(obj.fromUser.userinfo.pubkey),
|
||||||
|
content=bytes(obj.content),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleMessageListResponse(BaseModel):
|
||||||
|
data: t.List[SimpleMessageOut]
|
||||||
|
iterator: t.Optional[str]
|
||||||
|
done: bool
|
||||||
|
|
||||||
|
|
||||||
|
@simplemessage_router.get(
|
||||||
|
"/",
|
||||||
|
response_model=SimpleMessageListResponse,
|
||||||
|
dependencies=[Depends(get_authenticated_user), *PERMISSIONS_READ],
|
||||||
|
)
|
||||||
|
def simplemessage_list(
|
||||||
|
iterator: t.Optional[str] = None,
|
||||||
|
limit: int = 50,
|
||||||
|
queryset: SimpleMessageQuerySet = Depends(get_queryset),
|
||||||
|
):
|
||||||
|
queryset = queryset.order_by("id")
|
||||||
|
|
||||||
|
if iterator is not None:
|
||||||
|
iterator_obj = get_object_or_404(queryset, uid=iterator)
|
||||||
|
queryset = queryset.filter(id__lt=iterator_obj.id)
|
||||||
|
|
||||||
|
result = list(queryset[: limit + 1])
|
||||||
|
if len(result) < limit + 1:
|
||||||
|
done = True
|
||||||
|
else:
|
||||||
|
done = False
|
||||||
|
result = result[:-1]
|
||||||
|
|
||||||
|
ret_data = [SimpleMessageOut.from_orm(revision) for revision in result]
|
||||||
|
iterator = ret_data[-1].uid if len(result) > 0 else None
|
||||||
|
|
||||||
|
return SimpleMessageListResponse(
|
||||||
|
data=ret_data,
|
||||||
|
iterator=iterator,
|
||||||
|
done=done,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@simplemessage_router.get(
|
||||||
|
"/{message_uid}/",
|
||||||
|
response_model=SimpleMessageListResponse,
|
||||||
|
dependencies=PERMISSIONS_READ,
|
||||||
|
)
|
||||||
|
def simplemessage_get(
|
||||||
|
message_uid: str,
|
||||||
|
queryset: SimpleMessageQuerySet = Depends(get_queryset),
|
||||||
|
):
|
||||||
|
obj = get_object_or_404(queryset, uid=message_uid)
|
||||||
|
return SimpleMessageOut.from_orm(obj)
|
||||||
|
|
||||||
|
|
||||||
|
@simplemessage_router.delete(
|
||||||
|
"/{message_uid}/",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=PERMISSIONS_READWRITE,
|
||||||
|
)
|
||||||
|
def simplemessage_delete(
|
||||||
|
message_uid: str,
|
||||||
|
queryset: SimpleMessageQuerySet = Depends(get_queryset),
|
||||||
|
):
|
||||||
|
obj = get_object_or_404(queryset, uid=message_uid)
|
||||||
|
obj.delete()
|
||||||
|
|
||||||
|
|
||||||
|
@simplemessage_router.post("/", status_code=status.HTTP_201_CREATED, dependencies=PERMISSIONS_READWRITE)
|
||||||
|
def simplemessage_create(
|
||||||
|
data: SimpleMessageIn,
|
||||||
|
request: Request,
|
||||||
|
user: UserType = Depends(get_authenticated_user),
|
||||||
|
):
|
||||||
|
to_user = get_object_or_404(
|
||||||
|
get_user_queryset(User.objects.all(), CallbackContext(request.path_params)), username=data.toUsername
|
||||||
|
)
|
||||||
|
with transaction.atomic():
|
||||||
|
try:
|
||||||
|
models.SimpleMessage.objects.create(**data.dict(exclude={"toUsername"}), toUser=to_user, fromUser=user)
|
||||||
|
except IntegrityError:
|
||||||
|
raise HttpError(
|
||||||
|
"unique_uid", "SimpleMessage with this uid already exists", status_code=status.HTTP_409_CONFLICT
|
||||||
|
)
|
@ -34,5 +34,6 @@ def reset(data: SignupIn, request: Request):
|
|||||||
user.collection_set.all().delete()
|
user.collection_set.all().delete()
|
||||||
user.collectionmember_set.all().delete()
|
user.collectionmember_set.all().delete()
|
||||||
user.incoming_invitations.all().delete()
|
user.incoming_invitations.all().delete()
|
||||||
|
user.incoming_simple_messages.all().delete()
|
||||||
|
|
||||||
# FIXME: also delete chunk files!!!
|
# FIXME: also delete chunk files!!!
|
||||||
|
Loading…
Reference in New Issue
Block a user