summaryrefslogtreecommitdiffstats
path: root/back
diff options
context:
space:
mode:
authorsyn <isaqtm@gmail.com>2021-03-08 20:11:17 +0300
committersyn <isaqtm@gmail.com>2021-03-08 20:11:17 +0300
commit82b3f4c5e419bd6fdaa0dae14c92586b51aee8d7 (patch)
tree1f1a5b02549d65565237bb3362253f7d5486228b /back
downloadupnet-82b3f4c5e419bd6fdaa0dae14c92586b51aee8d7.tar.gz
mvp
Diffstat (limited to 'back')
-rw-r--r--back/app.py106
-rw-r--r--back/config.py12
-rw-r--r--back/requirements.txt17
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