From 56a41989f033e2adb151d9035171dbae817754a0 Mon Sep 17 00:00:00 2001 From: klemek Date: Sun, 5 Jul 2026 23:07:11 +0200 Subject: [PATCH] fix: working tasks after first tests --- app/__main__.py | 2 +- app/migrations/0002_auto_20260703_1716.py | 2 +- app/models/base.py | 6 ++- app/models/task.py | 51 +++++++++++------------ app/server.py | 40 ++++++------------ resources/ts/api/tasks.ts | 11 +++-- resources/ts/types.ts | 2 +- 7 files changed, 50 insertions(+), 64 deletions(-) diff --git a/app/__main__.py b/app/__main__.py index 2919d1c..c571566 100644 --- a/app/__main__.py +++ b/app/__main__.py @@ -13,5 +13,5 @@ def main() -> int: setup_logs(params) logging.getLogger().info("%s %s", PKG_NAME, PKG_VERSION) Database(params).init() - Server(params).register_routes().start() + Server(params).register_routes().run() return 0 diff --git a/app/migrations/0002_auto_20260703_1716.py b/app/migrations/0002_auto_20260703_1716.py index 1d49af4..3e35ad3 100644 --- a/app/migrations/0002_auto_20260703_1716.py +++ b/app/migrations/0002_auto_20260703_1716.py @@ -15,7 +15,7 @@ class Migration(migrations.Migration): ('id', fields.UUIDField(primary_key=True, default=uuid4, unique=True, db_index=True)), ('created_at', fields.DatetimeField(auto_now=False, auto_now_add=True)), ('updated_at', fields.DatetimeField(auto_now=True, auto_now_add=False)), - ('list', fields.CharField(db_index=True, max_length=32)), + ('list_name', fields.CharField(db_index=True, max_length=32)), ('name', fields.TextField(unique=False)), ('reset_cron', fields.CharField(null=True, max_length=32)), ('check_date', fields.DatetimeField(null=True, auto_now=False, auto_now_add=False)), diff --git a/app/models/base.py b/app/models/base.py index d1faf15..be74c12 100644 --- a/app/models/base.py +++ b/app/models/base.py @@ -1,4 +1,5 @@ -import tortoise +import typing + import tortoise.models @@ -9,3 +10,6 @@ class AbstractModel(tortoise.models.Model): class Meta: abstract = True + + def serialize(self) -> dict[str, typing.Any]: + return dict(self) # ty:ignore[no-matching-overload] diff --git a/app/models/task.py b/app/models/task.py index 6f9f9fb..f7ccb72 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -1,50 +1,47 @@ import typing import tortoise +import tortoise.validators from app.models.base import AbstractModel # https://stackoverflow.com/a/57639657 -CRONTAB_REGEX = r"/(@(annually|yearly|monthly|weekly|daily|hourly|reboot))|(@every (\d+(ns|us|µs|ms|s|m|h))+)|((((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,7})/" +CRONTAB_REGEX = r"^(@(annually|yearly|monthly|weekly|daily|hourly|reboot))|(@every (\d+(ns|us|µs|ms|s|m|h))+)|((((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,7})$" class Task(AbstractModel): - list = tortoise.fields.CharField(max_length=32, db_index=True, null=False) + list_name = tortoise.fields.CharField(max_length=32, db_index=True, null=False) name = tortoise.fields.TextField(null=False) - reset_cron = tortoise.fields.CharField(max_length=64, null=True) + reset_cron = tortoise.fields.CharField( + max_length=64, + null=True, + validators=[tortoise.validators.RegexValidator(CRONTAB_REGEX, 0)], + ) check_date = tortoise.fields.DatetimeField(null=True) previous_check_date = tortoise.fields.DatetimeField(null=True) @classmethod def validate_create(cls, data: dict[str, typing.Any]) -> dict | None: out_data = {} - try: - for field in [ - "list", - "name", - "reset_cron", - "check_date", - "previous_check_date", - ]: - getattr(cls, field).validate(data.get(field)) - out_data[field] = data.get(field) - except tortoise.exceptions.ValidationError: - return None + for field in [ + "list_name", + "name", + "reset_cron", + "check_date", + "previous_check_date", + ]: + out_data[field] = data.get(field) return out_data @classmethod def validate_update(cls, data: dict[str, typing.Any]) -> dict | None: out_data = {} - try: - for field in [ - "name", - "reset_cron", - "check_date", - "previous_check_date", - ]: - if field in data: - getattr(cls, field).validate(data["field"]) - out_data[field] = data["field"] - except tortoise.exceptions.ValidationError: - return None + for field in [ + "name", + "reset_cron", + "check_date", + "previous_check_date", + ]: + if field in data: + out_data[field] = data["field"] return out_data diff --git a/app/server.py b/app/server.py index 402515a..0d82d09 100644 --- a/app/server.py +++ b/app/server.py @@ -53,41 +53,40 @@ class Server(gunicorn.app.base.BaseApplication): @self.app.route("/api/lists//tasks", methods=["GET"]) async def get_tasks(list_name: str) -> list: - return await Task.filter(list=list_name).values() + return await Task.filter(list_name=list_name).values() @self.app.route("/api/lists//tasks", methods=["POST"]) async def post_task(list_name: str) -> typing.Any: data = flask.request.get_json() - data["list"] = list_name + logging.getLogger(self.__class__.__name__).info("%s", data) + data["list_name"] = list_name parsed_data = Task.validate_create(data) if parsed_data is None: flask.abort(400) - return (await Task.create(**parsed_data)).get().values() + return (await Task.create(**parsed_data)).serialize() - @self.app.route("/api/lists//tasks/", methods=["PUT"]) - async def put_task(list_name: str, task_uuid: str) -> typing.Any: + @self.app.route("/api/tasks/", methods=["PUT"]) + async def put_task(task_uuid: str) -> typing.Any: data = flask.request.get_json() - data["list"] = list_name parsed_data = Task.validate_update(data) if parsed_data is None: flask.abort(400) try: task = await Task.get(id=task_uuid) await task.update_from_dict(parsed_data) - return task.get().values() + return task.serialize() + except tortoise.exceptions.ValidationError: + flask.abort(400) except tortoise.exceptions.ObjectDoesNotExistError: flask.abort(404) - @self.app.route("/api/lists//tasks/", methods=["DELETE"]) - async def delete_task(list_name: str, task_uuid: str) -> typing.Any: - data = flask.request.get_json() - data["list"] = list_name - parsed_data = Task.validate_update(data) - if parsed_data is None: - flask.abort(400) + @self.app.route("/api/tasks/", methods=["DELETE"]) + async def delete_task(task_uuid: str) -> typing.Any: try: task = await Task.get(id=task_uuid) await task.delete() + except tortoise.exceptions.ValidationError: + flask.abort(400) except tortoise.exceptions.ObjectDoesNotExistError: flask.abort(404) return "" @@ -106,16 +105,3 @@ class Server(gunicorn.app.base.BaseApplication): def load(self) -> flask.Flask: return self.app - - def start(self) -> None: - if self.env == "production": - self.run() - else: - self.app.run( - host=self.bind, - port=self.port, - debug=self.debug, - load_dotenv=False, - use_evalex=False, - use_reloader=False, - ) diff --git a/resources/ts/api/tasks.ts b/resources/ts/api/tasks.ts index 2ed17d4..a827827 100644 --- a/resources/ts/api/tasks.ts +++ b/resources/ts/api/tasks.ts @@ -11,27 +11,26 @@ export async function createTask( ): Promise { const response = await fetch(`/api/lists/${list}/tasks`, { method: "POST", - body: JSON.stringify({ data }), + body: JSON.stringify({ ...data }), headers: { "Content-Type": "application/json" }, }); return await response.json(); } export async function updateTask( - list: string, taskId: string, data: TaskUpdateData, ): Promise { - const response = await fetch(`/api/lists/${list}/tasks/${taskId}`, { + const response = await fetch(`/api/tasks/${taskId}`, { method: "PUT", - body: JSON.stringify({ data }), + body: JSON.stringify({ ...data }), headers: { "Content-Type": "application/json" }, }); return await response.json(); } -export async function deleteTask(list: string, taskId: string): Promise { - await fetch(`/api/lists/${list}/tasks/${taskId}`, { +export async function deleteTask(taskId: string): Promise { + await fetch(`/api/tasks/${taskId}`, { method: "DELETE", }); } diff --git a/resources/ts/types.ts b/resources/ts/types.ts index c3c956b..15d2764 100644 --- a/resources/ts/types.ts +++ b/resources/ts/types.ts @@ -9,7 +9,7 @@ export interface RawTask { id: string; created_at: string; updated_at: string; - list: string; + list_name: string; name: string; reset_cron: string | null; check_date: string | null;