Remove pagination, refactoring

This commit is contained in:
2023-12-17 22:09:45 +03:00
parent f6c37b597b
commit 3b9d0d2ba6
11 changed files with 74 additions and 101 deletions

View File

@@ -2,9 +2,10 @@
## ToDo ## ToDo
1. Implement infinity scroll 1. Implement cursor pagination on backends
2. Implement S3 backend client 2. Implement infinity scroll
3. Implement FTP backend client 3. Implement S3 backend client
4. Implement FTP backend client
## User Flow ## User Flow

View File

@@ -6,7 +6,6 @@ from soul_diary.backend.database.models import Sense, Session
from .dependencies import is_auth, sense from .dependencies import is_auth, sense
from .schemas import ( from .schemas import (
CreateSenseRequest, CreateSenseRequest,
Pagination,
SenseListResponse, SenseListResponse,
SenseResponse, SenseResponse,
UpdateSenseRequest, UpdateSenseRequest,
@@ -16,15 +15,9 @@ from .schemas import (
async def get_sense_list( async def get_sense_list(
database: DatabaseService = fastapi.Depends(database), database: DatabaseService = fastapi.Depends(database),
user_session: Session = fastapi.Depends(is_auth), user_session: Session = fastapi.Depends(is_auth),
pagination: Pagination = fastapi.Depends(Pagination),
) -> SenseListResponse: ) -> SenseListResponse:
async with database.transaction() as session: async with database.transaction() as session:
senses = await database.get_sense_list( senses = await database.get_senses(session=session, user=user_session.user)
session=session,
user=user_session.user,
page=pagination.page,
limit=pagination.limit,
)
return SenseListResponse(data=senses) return SenseListResponse(data=senses)

View File

@@ -1,7 +1,7 @@
import uuid import uuid
from datetime import datetime from datetime import datetime
from pydantic import AwareDatetime, BaseModel, ConfigDict, PositiveInt from pydantic import BaseModel, ConfigDict
class CreateSenseRequest(BaseModel): class CreateSenseRequest(BaseModel):
@@ -20,10 +20,5 @@ class SenseResponse(BaseModel):
created_at: datetime created_at: datetime
class Pagination(BaseModel):
page: PositiveInt = 1
limit: PositiveInt = 10
class SenseListResponse(BaseModel): class SenseListResponse(BaseModel):
data: list[SenseResponse] data: list[SenseResponse]

View File

@@ -99,18 +99,8 @@ class DatabaseService(ServiceMixin):
return user_session return user_session
async def get_sense_list( async def get_senses(self, session: AsyncSession, user: User) -> list[Sense]:
self, query = select(Sense).where(Sense.user == user).order_by(Sense.created_at.desc())
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)
)
result = await session.execute(query) result = await session.execute(query)
senses = result.scalars().all() senses = result.scalars().all()

View File

@@ -6,9 +6,9 @@ from typing import Any
from Cryptodome.Cipher import AES 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.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: class BaseBackend:
@@ -68,7 +68,7 @@ class BaseBackend:
return data_decoded 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( return Sense(
id=sense_data.id, id=sense_data.id,
created_at=sense_data.created_at, created_at=sense_data.created_at,
@@ -110,12 +110,13 @@ class BaseBackend:
def is_auth(self) -> bool: def is_auth(self) -> bool:
return all((self._token, self._encryption_key)) return all((self._token, self._encryption_key))
async def get_sense_list(self, page: int = 1, limit: int = 10) -> list[Sense]: async def get_sense_list(self) -> SenseList:
sense_data_list = await self.fetch_sense_list(page=page, limit=limit) encrypted_sense_list = await self.fetch_sense_list()
return [ senses = [
self.convert_sense_data_to_sense(sense_data) self.convert_encrypted_sense_to_sense(encrypted_sense)
for sense_data in sense_data_list for encrypted_sense in encrypted_sense_list.senses
] ]
return SenseList(senses=senses)
async def create_sense( async def create_sense(
self, self,
@@ -132,13 +133,13 @@ class BaseBackend:
} }
encoded_data = self.encode(data) 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: async def get_sense(self, sense_id: uuid.UUID) -> Sense:
sense_data = await self.fetch_sense(sense_id=sense_id) encrypted_sense = await self.fetch_sense(sense_id=sense_id)
return self.convert_sense_data_to_sense(sense_data) return self.convert_encrypted_sense_to_sense(encrypted_sense)
async def edit_sense( async def edit_sense(
self, self,
@@ -156,9 +157,9 @@ class BaseBackend:
} }
encoded_data = self.encode(data) 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]: def get_backend_data(self) -> dict[str, Any]:
raise NotImplementedError raise NotImplementedError
@@ -175,21 +176,17 @@ class BaseBackend:
async def get_options(self) -> Options: async def get_options(self) -> Options:
raise NotImplementedError raise NotImplementedError
async def fetch_sense_list( async def fetch_sense_list(self) -> EncryptedSenseList:
self,
page: int = 1,
limit: int = 10,
) -> list[SenseBackendData]:
raise NotImplementedError raise NotImplementedError
async def fetch_sense(self, sense_id: uuid.UUID) -> SenseBackendData: async def fetch_sense(self, sense_id: uuid.UUID) -> EncryptedSense:
raise NotImplementedError raise NotImplementedError
async def pull_sense_data( async def pull_sense_data(
self, self,
data: str, data: str,
sense_id: uuid.UUID | None = None, sense_id: uuid.UUID | None = None,
) -> SenseBackendData: ) -> EncryptedSense:
raise NotImplementedError raise NotImplementedError
async def delete_sense(self, sense_id: uuid.UUID): async def delete_sense(self, sense_id: uuid.UUID):

View File

@@ -3,7 +3,7 @@ import uuid
from datetime import datetime from datetime import datetime
from typing import Any 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 .base import BaseBackend
from .exceptions import ( from .exceptions import (
IncorrectCredentialsException, IncorrectCredentialsException,
@@ -11,7 +11,7 @@ from .exceptions import (
SenseNotFoundException, SenseNotFoundException,
UserAlreadyExistsException, UserAlreadyExistsException,
) )
from .models import SenseBackendData from .models import EncryptedSense, EncryptedSenseList, Options
class LocalBackend(BaseBackend): class LocalBackend(BaseBackend):
@@ -60,48 +60,39 @@ class LocalBackend(BaseBackend):
async def get_options(self) -> Options: async def get_options(self) -> Options:
return Options(registration_enabled=True) 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: if not self.is_auth:
raise NonAuthenticatedException() raise NonAuthenticatedException()
sense_list_key = self.SENSE_LIST_KEY_TEMPLATE.format(username=self._username) sense_list_key = self.SENSE_LIST_KEY_TEMPLATE.format(username=self._username)
sense_list = await self._local_storage.raw_read(sense_list_key) or [] 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( async def fetch_sense(self, sense_id: uuid.UUID) -> EncryptedSense:
self, sense_list = await self.fetch_sense_list()
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]
return sense_list_filtered for sense in sense_list.senses:
async def fetch_sense(self, sense_id: uuid.UUID) -> SenseBackendData:
sense_list = await self._fetch_sense_list()
for sense in sense_list:
if sense.id == sense_id: if sense.id == sense_id:
return sense return sense
raise SenseNotFoundException() 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_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: 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() sense_id = uuid.uuid4()
while sense_id in sense_ids: while sense_id in sense_ids:
sense_id = uuid.uuid4() sense_id = uuid.uuid4()
sense = SenseBackendData( sense = EncryptedSense(
id=sense_id, id=sense_id,
data=data, data=data,
created_at=datetime.now().astimezone(), created_at=datetime.now().astimezone(),
) )
sense_list.insert(0, sense) sense_list.senses.insert(0, sense)
else: else:
for index, sense in enumerate(sense_list): for index, sense in enumerate(sense_list):
if sense.id == sense_id: if sense.id == sense_id:
@@ -109,20 +100,20 @@ class LocalBackend(BaseBackend):
else: else:
raise SenseNotFoundException() raise SenseNotFoundException()
sense = sense_list[index] sense = sense_list.senses[index]
sense.data = data sense.data = data
sense_list[index] = sense sense_list.senses[index] = sense
await self._local_storage.raw_write( await self._local_storage.raw_write(
sense_list_key, 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 return sense
async def delete_sense(self, sense_id: uuid.UUID): async def delete_sense(self, sense_id: uuid.UUID):
sense_list_key = self.SENSE_LIST_KEY_TEMPLATE.format(username=self._username) 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): for index, sense in enumerate(sense_list):
if sense.id == sense_id: if sense.id == sense_id:

View File

@@ -3,8 +3,22 @@ from datetime import datetime
from pydantic import BaseModel from pydantic import BaseModel
from soul_diary.ui.app.models import Sense
class SenseBackendData(BaseModel):
class EncryptedSense(BaseModel):
id: uuid.UUID id: uuid.UUID
data: str data: str
created_at: datetime created_at: datetime
class EncryptedSenseList(BaseModel):
senses: list[EncryptedSense]
class SenseList(BaseModel):
senses: list[Sense]
class Options(BaseModel):
registration_enabled: bool

View File

@@ -5,7 +5,7 @@ import httpx
import yarl import yarl
from soul_diary.ui.app.local_storage import LocalStorage 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 .base import BaseBackend
from .exceptions import ( from .exceptions import (
IncorrectCredentialsException, IncorrectCredentialsException,
@@ -14,7 +14,7 @@ from .exceptions import (
SenseNotFoundException, SenseNotFoundException,
UserAlreadyExistsException, UserAlreadyExistsException,
) )
from .models import SenseBackendData from .models import EncryptedSense, EncryptedSenseList, Options
class SoulBackend(BaseBackend): class SoulBackend(BaseBackend):
@@ -117,20 +117,15 @@ class SoulBackend(BaseBackend):
return Options.model_validate(response) return Options.model_validate(response)
async def fetch_sense_list( async def fetch_sense_list(self) -> EncryptedSenseList:
self,
page: int = 1,
limit: int = 10,
) -> list[SenseBackendData]:
path = "/senses/" path = "/senses/"
params = {"page": page, "limit": limit}
response = await self.request(method="GET", path=path, params=params) response = await self.request(method="GET", path=path)
senses = [SenseBackendData.model_validate(sense) for sense in response["data"]] 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}" path = f"/senses/{sense_id}"
try: try:
@@ -141,13 +136,13 @@ class SoulBackend(BaseBackend):
else: else:
raise exc raise exc
return SenseBackendData.model_validate(response) return EncryptedSense.model_validate(response)
async def pull_sense_data( async def pull_sense_data(
self, self,
data: str, data: str,
sense_id: uuid.UUID | None = None, sense_id: uuid.UUID | None = None,
) -> SenseBackendData: ) -> EncryptedSense:
path = "/senses/" if sense_id is None else f"/senses/{sense_id}" path = "/senses/" if sense_id is None else f"/senses/{sense_id}"
request_data = {"data": data} request_data = {"data": data}
@@ -159,7 +154,7 @@ class SoulBackend(BaseBackend):
else: else:
raise exc raise exc
return SenseBackendData.model_validate(response) return EncryptedSense.model_validate(response)
async def delete_sense(self, sense_id: uuid.UUID): async def delete_sense(self, sense_id: uuid.UUID):
path = f"/senses/{sense_id}" path = f"/senses/{sense_id}"

View File

@@ -26,7 +26,3 @@ class Sense(BaseModel):
body: constr(min_length=1, strip_whitespace=True) body: constr(min_length=1, strip_whitespace=True)
desires: constr(min_length=1, strip_whitespace=True) desires: constr(min_length=1, strip_whitespace=True)
created_at: datetime created_at: datetime
class Options(BaseModel):
registration_enabled: bool

View File

@@ -79,7 +79,7 @@ class DesiresPage(BasePage):
body=body, body=body,
).apply() ).apply()
@callback_error_handle # @callback_error_handle
async def callback_add_sense(self, event: flet.ControlEvent, desires_field: flet.TextField): async def callback_add_sense(self, event: flet.ControlEvent, desires_field: flet.TextField):
if self.desires is None or not self.desires.strip(): if self.desires is None or not self.desires.strip():
desires_field.error_text = "Коротко опиши свои желания" desires_field.error_text = "Коротко опиши свои желания"

View File

@@ -65,12 +65,13 @@ class SenseListPage(BasePage):
) )
async def did_mount_async(self): 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() await self.render_cards()
async def render_cards(self): async def render_cards(self):
function = self.render_extend_card if self.extend else self.render_compact_card 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] self.senses_cards.controls = [await function(sense) for sense in self.senses]
await self.update_async() await self.update_async()
@@ -97,7 +98,7 @@ class SenseListPage(BasePage):
content=flet.Card( content=flet.Card(
content=flet.Container( content=flet.Container(
content=flet.Column(controls=[feelings, bottom_row]), content=flet.Column(controls=[feelings, bottom_row]),
padding=10, padding=15,
), ),
width=600, width=600,
height=150, height=150,
@@ -150,7 +151,7 @@ class SenseListPage(BasePage):
content=flet.Container( content=flet.Container(
content=flet.Column(controls=[title, emotions, feelings_container, body_container, content=flet.Column(controls=[title, emotions, feelings_container, body_container,
desires_container]), desires_container]),
padding=10, padding=15,
), ),
width=600, width=600,
) )