fix: working tasks after first tests
TS Lint / ESLint (push) Successful in 1m0s
Python Lint CI / ty (push) Successful in 1m6s
Python Lint CI / ruff (push) Successful in 1m7s
Python Lint CI / ruff-format-check (push) Successful in 1m12s
TS Lint / Oxlint (push) Failing after 4m55s
TS Lint / TypeScript (push) Successful in 5m15s

This commit is contained in:
2026-07-05 23:07:11 +02:00
parent 568f22e2d5
commit 56a41989f0
7 changed files with 50 additions and 64 deletions
+1 -1
View File
@@ -13,5 +13,5 @@ def main() -> int:
setup_logs(params) setup_logs(params)
logging.getLogger().info("%s %s", PKG_NAME, PKG_VERSION) logging.getLogger().info("%s %s", PKG_NAME, PKG_VERSION)
Database(params).init() Database(params).init()
Server(params).register_routes().start() Server(params).register_routes().run()
return 0 return 0
+1 -1
View File
@@ -15,7 +15,7 @@ class Migration(migrations.Migration):
('id', fields.UUIDField(primary_key=True, default=uuid4, unique=True, db_index=True)), ('id', fields.UUIDField(primary_key=True, default=uuid4, unique=True, db_index=True)),
('created_at', fields.DatetimeField(auto_now=False, auto_now_add=True)), ('created_at', fields.DatetimeField(auto_now=False, auto_now_add=True)),
('updated_at', fields.DatetimeField(auto_now=True, auto_now_add=False)), ('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)), ('name', fields.TextField(unique=False)),
('reset_cron', fields.CharField(null=True, max_length=32)), ('reset_cron', fields.CharField(null=True, max_length=32)),
('check_date', fields.DatetimeField(null=True, auto_now=False, auto_now_add=False)), ('check_date', fields.DatetimeField(null=True, auto_now=False, auto_now_add=False)),
+5 -1
View File
@@ -1,4 +1,5 @@
import tortoise import typing
import tortoise.models import tortoise.models
@@ -9,3 +10,6 @@ class AbstractModel(tortoise.models.Model):
class Meta: class Meta:
abstract = True abstract = True
def serialize(self) -> dict[str, typing.Any]:
return dict(self) # ty:ignore[no-matching-overload]
+24 -27
View File
@@ -1,50 +1,47 @@
import typing import typing
import tortoise import tortoise
import tortoise.validators
from app.models.base import AbstractModel from app.models.base import AbstractModel
# https://stackoverflow.com/a/57639657 # 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): 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) 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) check_date = tortoise.fields.DatetimeField(null=True)
previous_check_date = tortoise.fields.DatetimeField(null=True) previous_check_date = tortoise.fields.DatetimeField(null=True)
@classmethod @classmethod
def validate_create(cls, data: dict[str, typing.Any]) -> dict | None: def validate_create(cls, data: dict[str, typing.Any]) -> dict | None:
out_data = {} out_data = {}
try: for field in [
for field in [ "list_name",
"list", "name",
"name", "reset_cron",
"reset_cron", "check_date",
"check_date", "previous_check_date",
"previous_check_date", ]:
]: out_data[field] = data.get(field)
getattr(cls, field).validate(data.get(field))
out_data[field] = data.get(field)
except tortoise.exceptions.ValidationError:
return None
return out_data return out_data
@classmethod @classmethod
def validate_update(cls, data: dict[str, typing.Any]) -> dict | None: def validate_update(cls, data: dict[str, typing.Any]) -> dict | None:
out_data = {} out_data = {}
try: for field in [
for field in [ "name",
"name", "reset_cron",
"reset_cron", "check_date",
"check_date", "previous_check_date",
"previous_check_date", ]:
]: if field in data:
if field in data: out_data[field] = data["field"]
getattr(cls, field).validate(data["field"])
out_data[field] = data["field"]
except tortoise.exceptions.ValidationError:
return None
return out_data return out_data
+13 -27
View File
@@ -53,41 +53,40 @@ class Server(gunicorn.app.base.BaseApplication):
@self.app.route("/api/lists/<list_name>/tasks", methods=["GET"]) @self.app.route("/api/lists/<list_name>/tasks", methods=["GET"])
async def get_tasks(list_name: str) -> list: 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/<list_name>/tasks", methods=["POST"]) @self.app.route("/api/lists/<list_name>/tasks", methods=["POST"])
async def post_task(list_name: str) -> typing.Any: async def post_task(list_name: str) -> typing.Any:
data = flask.request.get_json() 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) parsed_data = Task.validate_create(data)
if parsed_data is None: if parsed_data is None:
flask.abort(400) flask.abort(400)
return (await Task.create(**parsed_data)).get().values() return (await Task.create(**parsed_data)).serialize()
@self.app.route("/api/lists/<list_name>/tasks/<task_uuid>", methods=["PUT"]) @self.app.route("/api/tasks/<task_uuid>", methods=["PUT"])
async def put_task(list_name: str, task_uuid: str) -> typing.Any: async def put_task(task_uuid: str) -> typing.Any:
data = flask.request.get_json() data = flask.request.get_json()
data["list"] = list_name
parsed_data = Task.validate_update(data) parsed_data = Task.validate_update(data)
if parsed_data is None: if parsed_data is None:
flask.abort(400) flask.abort(400)
try: try:
task = await Task.get(id=task_uuid) task = await Task.get(id=task_uuid)
await task.update_from_dict(parsed_data) 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: except tortoise.exceptions.ObjectDoesNotExistError:
flask.abort(404) flask.abort(404)
@self.app.route("/api/lists/<list_name>/tasks/<task_uuid>", methods=["DELETE"]) @self.app.route("/api/tasks/<task_uuid>", methods=["DELETE"])
async def delete_task(list_name: str, task_uuid: str) -> typing.Any: async def delete_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: try:
task = await Task.get(id=task_uuid) task = await Task.get(id=task_uuid)
await task.delete() await task.delete()
except tortoise.exceptions.ValidationError:
flask.abort(400)
except tortoise.exceptions.ObjectDoesNotExistError: except tortoise.exceptions.ObjectDoesNotExistError:
flask.abort(404) flask.abort(404)
return "" return ""
@@ -106,16 +105,3 @@ class Server(gunicorn.app.base.BaseApplication):
def load(self) -> flask.Flask: def load(self) -> flask.Flask:
return self.app 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,
)
+5 -6
View File
@@ -11,27 +11,26 @@ export async function createTask(
): Promise<RawTask> { ): Promise<RawTask> {
const response = await fetch(`/api/lists/${list}/tasks`, { const response = await fetch(`/api/lists/${list}/tasks`, {
method: "POST", method: "POST",
body: JSON.stringify({ data }), body: JSON.stringify({ ...data }),
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
}); });
return await response.json(); return await response.json();
} }
export async function updateTask( export async function updateTask(
list: string,
taskId: string, taskId: string,
data: TaskUpdateData, data: TaskUpdateData,
): Promise<RawTask> { ): Promise<RawTask> {
const response = await fetch(`/api/lists/${list}/tasks/${taskId}`, { const response = await fetch(`/api/tasks/${taskId}`, {
method: "PUT", method: "PUT",
body: JSON.stringify({ data }), body: JSON.stringify({ ...data }),
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
}); });
return await response.json(); return await response.json();
} }
export async function deleteTask(list: string, taskId: string): Promise<void> { export async function deleteTask(taskId: string): Promise<void> {
await fetch(`/api/lists/${list}/tasks/${taskId}`, { await fetch(`/api/tasks/${taskId}`, {
method: "DELETE", method: "DELETE",
}); });
} }
+1 -1
View File
@@ -9,7 +9,7 @@ export interface RawTask {
id: string; id: string;
created_at: string; created_at: string;
updated_at: string; updated_at: string;
list: string; list_name: string;
name: string; name: string;
reset_cron: string | null; reset_cron: string | null;
check_date: string | null; check_date: string | null;