diff options
author | syn <isaqtm@gmail.com> | 2021-03-08 20:11:17 +0300 |
---|---|---|
committer | syn <isaqtm@gmail.com> | 2021-03-08 20:11:17 +0300 |
commit | 82b3f4c5e419bd6fdaa0dae14c92586b51aee8d7 (patch) | |
tree | 1f1a5b02549d65565237bb3362253f7d5486228b /back | |
download | upnet-82b3f4c5e419bd6fdaa0dae14c92586b51aee8d7.tar.gz |
mvp
Diffstat (limited to 'back')
-rw-r--r-- | back/app.py | 106 | ||||
-rw-r--r-- | back/config.py | 12 | ||||
-rw-r--r-- | back/requirements.txt | 17 |
3 files changed, 135 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) diff --git a/back/config.py b/back/config.py new file mode 100644 index 0000000..a3185ee --- /dev/null +++ b/back/config.py @@ -0,0 +1,12 @@ +import os +import sys + +modpath = __file__ + +# Turn pyc files into py files if we can +if modpath.endswith('.pyc') and os.path.exists(modpath[:-1]): + modpath = modpath[:-1] + +# Sort out symlinks +APP_DIR = os.path.realpath(os.path.dirname(modpath)) +REDIS_HOST = 'localhost' diff --git a/back/requirements.txt b/back/requirements.txt new file mode 100644 index 0000000..13fade3 --- /dev/null +++ b/back/requirements.txt @@ -0,0 +1,17 @@ +aiofiles==0.6.0 +aioredis==1.3.1 +async-timeout==3.0.1 +certifi==2020.12.5 +h11==0.9.0 +hiredis==1.1.0 +httpcore==0.11.1 +httptools==0.1.1 +httpx==0.15.4 +idna==3.1 +multidict==5.1.0 +rfc3986==1.4.0 +sanic==20.12.2 +sniffio==1.2.0 +ujson==4.0.2 +uvloop==0.14.0 +websockets==8.1 |