diff options
Diffstat (limited to 'back/app.py')
-rw-r--r-- | back/app.py | 106 |
1 files changed, 106 insertions, 0 deletions
diff --git a/back/app.py b/back/app.py new file mode 100644 index 0000000..7d082d4 --- /dev/null +++ b/back/app.py @@ -0,0 +1,106 @@ +from __future__ import annotations +from sanic import Sanic +from sanic.response import json as response_json, file +from config import APP_DIR +from aioredis import create_redis_pool +from dataclasses import dataclass, fields, asdict +import json +from math import floor + + +app = Sanic(__name__) + +app.static("/", APP_DIR + "/../front/index.html") +app.static("/js/out.js", APP_DIR + "/../front/out.js") +app.static("/css/style.css", APP_DIR + "/../front/style.css") +app.static("/css/twemoji-awesome.css", APP_DIR + "/../front/twemoji-awesome.css") +app.static("/AnkaCoder/", APP_DIR + "/../front/AnkaCoder") +app.static("/AnkaCoderCondensed/", APP_DIR + "/../front/AnkaCoderCondensed") + + +def now_ms() -> int: + from datetime import datetime + return floor(datetime.utcnow().timestamp() * 1000) + + +@dataclass +class Todo: + desc: str + expires: int # utc milliseconds + created: int # utc milliseconds + done: int # utc milliseconds of finishing, or -1 + id: Optional[int] = None + + def marshal(self) -> str: + self_dict = dict() + for field in fields(self): + self_dict.update({ field.name: getattr(self, field.name) }) + return json.dumps(self_dict) + + @classmethod + def unmarshal_safe(cls, json_obj) -> Todo: + if isinstance(json_obj, str): + json_obj = json.loads(json_obj) + self_dict = dict(created=now_ms()) + for field in fields(cls): + if field.name in ["created"] or "Optional" in field.type: + continue + + # field.type is str, so there is no way to check field's type but string-comparing + # types representation + if type(json_obj[field.name]) == field.type: + raise TypeError(f"Todo.{field.name} must be instance of {field.type}") + self_dict.update({ field.name: json_obj[field.name] }) + + cls.verify_json(self_dict) + return cls(**self_dict) + + @staticmethod + def verify_json(self_dict): + if len(self_dict["desc"]) > 4096: + raise ValueError("desc cannot be more than 4K chars") + + @classmethod + async def from_redis(cls, redis, key): + json_str = await redis.get(key) + todo = cls.unmarshal_safe(json_str.decode("utf-8")) + todo.id = int(key.decode("utf-8").split(":")[-1]) + return todo + + +@app.listener("before_server_start") +async def init_redis(app, loop): + app.db = await create_redis_pool("redis://localhost") + +@app.listener("after_server_stop") +async def close_redis(app, loop): + from asyncio import sleep + app.db.close() + await app.db.wait_closed() + +@app.post("/new-todo") +async def new_todo(req): + todo = Todo.unmarshal_safe(req.json) + id = await req.app.db.incr("upnet_todo_id_seq") + await req.app.db.set(f"upnet:todo:{id}", todo.marshal()) + + return response_json(dict(status="ok", id=id)) + +@app.post("/delete-todo") +async def delete_todo(req): + id = req.json["id"] + await req.app.db.delete(f"upnet:todo:{id}") + return response_json(dict(status="ok", was=id)) + +@app.get("/todos") +async def get_todos(req): + all_todos_keys = await req.app.db.keys("upnet:todo:*") + + # TODO: await all together + todos = [asdict(await Todo.from_redis(req.app.db, todo_id)) + for todo_id in all_todos_keys + ] + return response_json({ "todos": todos }) + +if __name__ == "__main__": + app.go_fast(host="0.0.0.0", port=8000, debug=True) |