diff --git a/README.md b/README.md index d4a8daa..375e5e4 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,10 @@ ## ToDo -1. Implement infinity scroll -2. Implement S3 backend client -3. Implement FTP backend client +1. Implement cursor pagination on backends +2. Implement infinity scroll +3. Implement S3 backend client +4. Implement FTP backend client ## User Flow diff --git a/soul_diary/backend/api/senses/handlers.py b/soul_diary/backend/api/senses/handlers.py index 42916a7..b9d33cc 100644 --- a/soul_diary/backend/api/senses/handlers.py +++ b/soul_diary/backend/api/senses/handlers.py @@ -6,7 +6,6 @@ from soul_diary.backend.database.models import Sense, Session from .dependencies import is_auth, sense from .schemas import ( CreateSenseRequest, - Pagination, SenseListResponse, SenseResponse, UpdateSenseRequest, @@ -16,15 +15,9 @@ from .schemas import ( async def get_sense_list( database: DatabaseService = fastapi.Depends(database), user_session: Session = fastapi.Depends(is_auth), - pagination: Pagination = fastapi.Depends(Pagination), ) -> SenseListResponse: async with database.transaction() as session: - senses = await database.get_sense_list( - session=session, - user=user_session.user, - page=pagination.page, - limit=pagination.limit, - ) + senses = await database.get_senses(session=session, user=user_session.user) return SenseListResponse(data=senses) diff --git a/soul_diary/backend/api/senses/schemas.py b/soul_diary/backend/api/senses/schemas.py index d818612..7d8e0f6 100644 --- a/soul_diary/backend/api/senses/schemas.py +++ b/soul_diary/backend/api/senses/schemas.py @@ -1,7 +1,7 @@ import uuid from datetime import datetime -from pydantic import AwareDatetime, BaseModel, ConfigDict, PositiveInt +from pydantic import BaseModel, ConfigDict class CreateSenseRequest(BaseModel): @@ -20,10 +20,5 @@ class SenseResponse(BaseModel): created_at: datetime -class Pagination(BaseModel): - page: PositiveInt = 1 - limit: PositiveInt = 10 - - class SenseListResponse(BaseModel): data: list[SenseResponse] diff --git a/soul_diary/backend/database/service.py b/soul_diary/backend/database/service.py index 63729d9..bb247b4 100644 --- a/soul_diary/backend/database/service.py +++ b/soul_diary/backend/database/service.py @@ -99,18 +99,8 @@ class DatabaseService(ServiceMixin): return user_session - async def get_sense_list( - self, - session: AsyncSession, - user: User, - page: int = 1, - limit: int = 10, - ) -> list[Sense]: - query = ( - select(Sense).where(Sense.user == user) - .order_by(Sense.created_at.desc()) - .limit(limit).offset((page - 1) * limit) - ) + async def get_senses(self, session: AsyncSession, user: User) -> list[Sense]: + query = select(Sense).where(Sense.user == user).order_by(Sense.created_at.desc()) result = await session.execute(query) senses = result.scalars().all() diff --git a/soul_diary/ui/app/backend/base.py b/soul_diary/ui/app/backend/base.py index fa0c77d..c5f64a4 100644 --- a/soul_diary/ui/app/backend/base.py +++ b/soul_diary/ui/app/backend/base.py @@ -6,9 +6,9 @@ from typing import Any from Cryptodome.Cipher import AES -from soul_diary.ui.app.backend.models import SenseBackendData from soul_diary.ui.app.local_storage import LocalStorage -from soul_diary.ui.app.models import BackendType, Emotion, Options, Sense +from soul_diary.ui.app.models import BackendType, Emotion, Sense +from .models import EncryptedSense, EncryptedSenseList, SenseList, Options class BaseBackend: @@ -68,7 +68,7 @@ class BaseBackend: return data_decoded - def convert_sense_data_to_sense(self, sense_data: SenseBackendData) -> Sense: + def convert_encrypted_sense_to_sense(self, sense_data: EncryptedSense) -> Sense: return Sense( id=sense_data.id, created_at=sense_data.created_at, @@ -110,12 +110,13 @@ class BaseBackend: def is_auth(self) -> bool: return all((self._token, self._encryption_key)) - async def get_sense_list(self, page: int = 1, limit: int = 10) -> list[Sense]: - sense_data_list = await self.fetch_sense_list(page=page, limit=limit) - return [ - self.convert_sense_data_to_sense(sense_data) - for sense_data in sense_data_list + async def get_sense_list(self) -> SenseList: + encrypted_sense_list = await self.fetch_sense_list() + senses = [ + self.convert_encrypted_sense_to_sense(encrypted_sense) + for encrypted_sense in encrypted_sense_list.senses ] + return SenseList(senses=senses) async def create_sense( self, @@ -132,13 +133,13 @@ class BaseBackend: } encoded_data = self.encode(data) - sense_data = await self.pull_sense_data(data=encoded_data) + encrypted_sense = await self.pull_sense_data(data=encoded_data) - return self.convert_sense_data_to_sense(sense_data) + return self.convert_encrypted_sense_to_sense(encrypted_sense) async def get_sense(self, sense_id: uuid.UUID) -> Sense: - sense_data = await self.fetch_sense(sense_id=sense_id) - return self.convert_sense_data_to_sense(sense_data) + encrypted_sense = await self.fetch_sense(sense_id=sense_id) + return self.convert_encrypted_sense_to_sense(encrypted_sense) async def edit_sense( self, @@ -156,9 +157,9 @@ class BaseBackend: } encoded_data = self.encode(data) - sense_data = await self.pull_sense_data(data=encoded_data, sense_id=sense_id) + encrypted_sense = await self.pull_sense_data(data=encoded_data, sense_id=sense_id) - return self.convert_sense_data_to_sense(sense_data) + return self.convert_encrypted_sense_to_sense(encrypted_sense) def get_backend_data(self) -> dict[str, Any]: raise NotImplementedError @@ -175,21 +176,17 @@ class BaseBackend: async def get_options(self) -> Options: raise NotImplementedError - async def fetch_sense_list( - self, - page: int = 1, - limit: int = 10, - ) -> list[SenseBackendData]: + async def fetch_sense_list(self) -> EncryptedSenseList: raise NotImplementedError - async def fetch_sense(self, sense_id: uuid.UUID) -> SenseBackendData: + async def fetch_sense(self, sense_id: uuid.UUID) -> EncryptedSense: raise NotImplementedError async def pull_sense_data( self, data: str, sense_id: uuid.UUID | None = None, - ) -> SenseBackendData: + ) -> EncryptedSense: raise NotImplementedError async def delete_sense(self, sense_id: uuid.UUID): diff --git a/soul_diary/ui/app/backend/local.py b/soul_diary/ui/app/backend/local.py index 0e2ef00..1722714 100644 --- a/soul_diary/ui/app/backend/local.py +++ b/soul_diary/ui/app/backend/local.py @@ -3,7 +3,7 @@ import uuid from datetime import datetime from typing import Any -from soul_diary.ui.app.models import BackendType, Options +from soul_diary.ui.app.models import BackendType from .base import BaseBackend from .exceptions import ( IncorrectCredentialsException, @@ -11,7 +11,7 @@ from .exceptions import ( SenseNotFoundException, UserAlreadyExistsException, ) -from .models import SenseBackendData +from .models import EncryptedSense, EncryptedSenseList, Options class LocalBackend(BaseBackend): @@ -60,48 +60,39 @@ class LocalBackend(BaseBackend): async def get_options(self) -> Options: return Options(registration_enabled=True) - async def _fetch_sense_list(self) -> list[SenseBackendData]: + async def fetch_sense_list(self) -> EncryptedSenseList: if not self.is_auth: raise NonAuthenticatedException() sense_list_key = self.SENSE_LIST_KEY_TEMPLATE.format(username=self._username) sense_list = await self._local_storage.raw_read(sense_list_key) or [] - return [SenseBackendData.model_validate(sense) for sense in sense_list] + senses = [EncryptedSense.model_validate(sense) for sense in sense_list] + return EncryptedSenseList(senses=senses) - async def fetch_sense_list( - self, - page: int = 1, - limit: int = 10, - ) -> list[SenseBackendData]: - sense_list = await self._fetch_sense_list() - sense_list_filtered = sense_list[(page - 1) * limit:page * limit] + async def fetch_sense(self, sense_id: uuid.UUID) -> EncryptedSense: + sense_list = await self.fetch_sense_list() - return sense_list_filtered - - async def fetch_sense(self, sense_id: uuid.UUID) -> SenseBackendData: - sense_list = await self._fetch_sense_list() - - for sense in sense_list: + for sense in sense_list.senses: if sense.id == sense_id: return sense raise SenseNotFoundException() - async def pull_sense_data(self, data: str, sense_id: uuid.UUID | None = None) -> SenseBackendData: + async def pull_sense_data(self, data: str, sense_id: uuid.UUID | None = None) -> EncryptedSense: sense_list_key = self.SENSE_LIST_KEY_TEMPLATE.format(username=self._username) - sense_list = await self._fetch_sense_list() + sense_list = await self.fetch_sense_list() if sense_id is None: - sense_ids = {sense.id for sense in sense_list} + sense_ids = {sense.id for sense in sense_list.senses} sense_id = uuid.uuid4() while sense_id in sense_ids: sense_id = uuid.uuid4() - sense = SenseBackendData( + sense = EncryptedSense( id=sense_id, data=data, created_at=datetime.now().astimezone(), ) - sense_list.insert(0, sense) + sense_list.senses.insert(0, sense) else: for index, sense in enumerate(sense_list): if sense.id == sense_id: @@ -109,20 +100,20 @@ class LocalBackend(BaseBackend): else: raise SenseNotFoundException() - sense = sense_list[index] + sense = sense_list.senses[index] sense.data = data - sense_list[index] = sense + sense_list.senses[index] = sense await self._local_storage.raw_write( sense_list_key, - [sense.model_dump(mode="json") for sense in sense_list], + [sense.model_dump(mode="json") for sense in sense_list.senses], ) return sense async def delete_sense(self, sense_id: uuid.UUID): sense_list_key = self.SENSE_LIST_KEY_TEMPLATE.format(username=self._username) - sense_list = await self._fetch_sense_list() + sense_list = await self.fetch_sense_list() for index, sense in enumerate(sense_list): if sense.id == sense_id: diff --git a/soul_diary/ui/app/backend/models.py b/soul_diary/ui/app/backend/models.py index 184d607..efe6e41 100644 --- a/soul_diary/ui/app/backend/models.py +++ b/soul_diary/ui/app/backend/models.py @@ -3,8 +3,22 @@ from datetime import datetime from pydantic import BaseModel +from soul_diary.ui.app.models import Sense -class SenseBackendData(BaseModel): + +class EncryptedSense(BaseModel): id: uuid.UUID data: str created_at: datetime + + +class EncryptedSenseList(BaseModel): + senses: list[EncryptedSense] + + +class SenseList(BaseModel): + senses: list[Sense] + + +class Options(BaseModel): + registration_enabled: bool diff --git a/soul_diary/ui/app/backend/soul.py b/soul_diary/ui/app/backend/soul.py index 4348fbb..a1b907d 100644 --- a/soul_diary/ui/app/backend/soul.py +++ b/soul_diary/ui/app/backend/soul.py @@ -5,7 +5,7 @@ import httpx import yarl from soul_diary.ui.app.local_storage import LocalStorage -from soul_diary.ui.app.models import BackendType, Options +from soul_diary.ui.app.models import BackendType from .base import BaseBackend from .exceptions import ( IncorrectCredentialsException, @@ -14,7 +14,7 @@ from .exceptions import ( SenseNotFoundException, UserAlreadyExistsException, ) -from .models import SenseBackendData +from .models import EncryptedSense, EncryptedSenseList, Options class SoulBackend(BaseBackend): @@ -117,20 +117,15 @@ class SoulBackend(BaseBackend): return Options.model_validate(response) - async def fetch_sense_list( - self, - page: int = 1, - limit: int = 10, - ) -> list[SenseBackendData]: + async def fetch_sense_list(self) -> EncryptedSenseList: path = "/senses/" - params = {"page": page, "limit": limit} - response = await self.request(method="GET", path=path, params=params) - senses = [SenseBackendData.model_validate(sense) for sense in response["data"]] + response = await self.request(method="GET", path=path) + senses = [EncryptedSense.model_validate(sense) for sense in response["data"]] - return senses + return EncryptedSenseList(senses=senses) - async def fetch_sense(self, sense_id: uuid.UUID) -> SenseBackendData: + async def fetch_sense(self, sense_id: uuid.UUID) -> EncryptedSense: path = f"/senses/{sense_id}" try: @@ -141,13 +136,13 @@ class SoulBackend(BaseBackend): else: raise exc - return SenseBackendData.model_validate(response) + return EncryptedSense.model_validate(response) async def pull_sense_data( self, data: str, sense_id: uuid.UUID | None = None, - ) -> SenseBackendData: + ) -> EncryptedSense: path = "/senses/" if sense_id is None else f"/senses/{sense_id}" request_data = {"data": data} @@ -159,7 +154,7 @@ class SoulBackend(BaseBackend): else: raise exc - return SenseBackendData.model_validate(response) + return EncryptedSense.model_validate(response) async def delete_sense(self, sense_id: uuid.UUID): path = f"/senses/{sense_id}" diff --git a/soul_diary/ui/app/models.py b/soul_diary/ui/app/models.py index 168ff86..615d026 100644 --- a/soul_diary/ui/app/models.py +++ b/soul_diary/ui/app/models.py @@ -26,7 +26,3 @@ class Sense(BaseModel): body: constr(min_length=1, strip_whitespace=True) desires: constr(min_length=1, strip_whitespace=True) created_at: datetime - - -class Options(BaseModel): - registration_enabled: bool diff --git a/soul_diary/ui/app/pages/sense_add/desires.py b/soul_diary/ui/app/pages/sense_add/desires.py index 7f0f83e..45d446f 100644 --- a/soul_diary/ui/app/pages/sense_add/desires.py +++ b/soul_diary/ui/app/pages/sense_add/desires.py @@ -79,7 +79,7 @@ class DesiresPage(BasePage): body=body, ).apply() - @callback_error_handle + # @callback_error_handle async def callback_add_sense(self, event: flet.ControlEvent, desires_field: flet.TextField): if self.desires is None or not self.desires.strip(): desires_field.error_text = "Коротко опиши свои желания" diff --git a/soul_diary/ui/app/pages/sense_list.py b/soul_diary/ui/app/pages/sense_list.py index bafc55e..dcd47cf 100644 --- a/soul_diary/ui/app/pages/sense_list.py +++ b/soul_diary/ui/app/pages/sense_list.py @@ -65,12 +65,13 @@ class SenseListPage(BasePage): ) async def did_mount_async(self): + backend_client = await get_backend_client(self.local_storage) + sense_list = await backend_client.get_sense_list() + self.senses = sense_list.senses await self.render_cards() async def render_cards(self): function = self.render_extend_card if self.extend else self.render_compact_card - backend_client = await get_backend_client(self.local_storage) - self.senses = await backend_client.get_sense_list() self.senses_cards.controls = [await function(sense) for sense in self.senses] await self.update_async() @@ -97,7 +98,7 @@ class SenseListPage(BasePage): content=flet.Card( content=flet.Container( content=flet.Column(controls=[feelings, bottom_row]), - padding=10, + padding=15, ), width=600, height=150, @@ -150,7 +151,7 @@ class SenseListPage(BasePage): content=flet.Container( content=flet.Column(controls=[title, emotions, feelings_container, body_container, desires_container]), - padding=10, + padding=15, ), width=600, )