From 02a0253c1d9c73d2918fecb8f52c4c7739c867f5 Mon Sep 17 00:00:00 2001 From: retoor Date: Fri, 9 May 2025 01:33:41 +0200 Subject: [PATCH] YEah.. --- src/snek/__main__.py | 17 ++++++++----- src/snek/app.py | 40 ++++++++++++++++++++++++------ src/snek/docs/app.py | 2 +- src/snek/mapper/user.py | 13 ++++++++++ src/snek/model/user.py | 2 ++ src/snek/service/channel_member.py | 7 ++++-- src/snek/service/socket.py | 6 +++-- src/snek/service/user.py | 9 +++++++ src/snek/service/user_property.py | 14 ++++++----- src/snek/system/cache.py | 28 +++++++++++++-------- src/snek/system/http.py | 3 +-- src/snek/system/markdown.py | 3 +-- src/snek/system/terminal.py | 14 +++++------ src/snek/system/view.py | 4 ++- src/snek/view/index.py | 4 ++- src/snek/view/search_user.py | 1 + src/snek/view/settings/profile.py | 20 +++++++-------- src/snek/view/stats.py | 11 +++++--- src/snek/view/upload.py | 4 +-- src/snek/view/user.py | 17 +++++++------ src/snek/webdav.py | 3 +-- 21 files changed, 148 insertions(+), 74 deletions(-) diff --git a/src/snek/__main__.py b/src/snek/__main__.py index 1f3e1e9..c4996aa 100644 --- a/src/snek/__main__.py +++ b/src/snek/__main__.py @@ -1,32 +1,37 @@ import argparse + from aiohttp import web from snek.app import Application + def main(): parser = argparse.ArgumentParser(description="Run the web application.") parser.add_argument( "--port", type=int, default=8081, - help="Port to run the application on (default: 8081)" + help="Port to run the application on (default: 8081)", ) parser.add_argument( "--host", type=str, default="0.0.0.0", - help="Host to run the application on (default: 0.0.0.0)" + help="Host to run the application on (default: 0.0.0.0)", ) parser.add_argument( "--db_path", type=str, default="snek.db", - help="Database path for the application (default: sqlite:///snek.db)" + help="Database path for the application (default: sqlite:///snek.db)", ) - + args = parser.parse_args() - - web.run_app(Application(db_path='sqlite:///' + args.db_path), port=args.port, host=args.host) + + web.run_app( + Application(db_path="sqlite:///" + args.db_path), port=args.port, host=args.host + ) + if __name__ == "__main__": main() diff --git a/src/snek/app.py b/src/snek/app.py index a1c8938..e65264e 100644 --- a/src/snek/app.py +++ b/src/snek/app.py @@ -17,8 +17,9 @@ from aiohttp_session import ( setup as session_setup, ) from aiohttp_session.cookie_storage import EncryptedCookieStorage - from app.app import Application as BaseApplication +from jinja2 import FileSystemLoader + from snek.docs.app import Application as DocsApplication from snek.mapper import get_mappers from snek.service import get_services @@ -40,12 +41,12 @@ from snek.view.rpc import RPCView from snek.view.search_user import SearchUserView from snek.view.settings.index import SettingsIndexView from snek.view.settings.profile import SettingsProfileView +from snek.view.stats import StatsView from snek.view.status import StatusView from snek.view.terminal import TerminalSocketView, TerminalView from snek.view.upload import UploadView -from snek.view.web import WebView -from snek.view.stats import StatsView from snek.view.user import UserView +from snek.view.web import WebView from snek.webdav import WebdavApplication SESSION_KEY = b"c79a0c5fda4b424189c427d28c9f7c34" @@ -204,7 +205,6 @@ class Application(BaseApplication): channels = [] if not context: context = {} - context["rid"] = str(uuid.uuid4()) if request.session.get("uid"): async for subscribed_channel in self.services.channel_member.find( @@ -231,7 +231,6 @@ class Application(BaseApplication): item["uid"] = subscribed_channel["channel_uid"] item["new_count"] = subscribed_channel["new_count"] - print(item) channels.append(item) channels.sort(key=lambda x: x["last_message_on"] or "", reverse=True) @@ -239,10 +238,37 @@ class Application(BaseApplication): context["channels"] = channels if "user" not in context: context["user"] = await self.services.user.get( - uid=request.session.get("uid") + request.session.get("uid") ) - return await super().render_template(template, request, context) + self.template_path.joinpath(template) + + await self.services.user.get_template_path(request.session.get("uid")) + + self.original_loader = self.jinja2_env.loader + + self.jinja2_env.loader = await self.get_user_template_loader( + request.session.get("uid") + ) + + rendered = await super().render_template(template, request, context) + + self.jinja2_env.loader = self.original_loader + + return rendered + + async def get_user_template_loader(self, uid=None): + template_paths = [] + for admin_uid in self.services.user.get_admin_uids(): + user_template_path = await self.services.user.get_template_path(admin_uid) + template_paths.append(user_template_path) + + if uid: + user_template_path = await self.services.user.get_template_path(uid) + template_paths.append(user_template_path) + + template_paths.append(self.template_path) + return FileSystemLoader(template_paths) app = Application(db_path="sqlite:///snek.db") diff --git a/src/snek/docs/app.py b/src/snek/docs/app.py index dcbd6f8..50a4245 100644 --- a/src/snek/docs/app.py +++ b/src/snek/docs/app.py @@ -1,8 +1,8 @@ import pathlib from aiohttp import web - from app.app import Application as BaseApplication + from snek.system.markdown import MarkdownExtension diff --git a/src/snek/mapper/user.py b/src/snek/mapper/user.py index c388abc..e0df494 100644 --- a/src/snek/mapper/user.py +++ b/src/snek/mapper/user.py @@ -5,3 +5,16 @@ from snek.system.mapper import BaseMapper class UserMapper(BaseMapper): table_name = "user" model_class = UserModel + + def get_admin_uids(self): + try: + return [ + user["uid"] + for user in self.db.query( + "SELECT uid FROM user WHERE is_admin = :is_admin", + {"is_admin": True}, + ) + ] + except Exception as ex: + print(ex) + return [] diff --git a/src/snek/model/user.py b/src/snek/model/user.py index 89d46ba..9869456 100644 --- a/src/snek/model/user.py +++ b/src/snek/model/user.py @@ -29,6 +29,8 @@ class UserModel(BaseModel): last_ping = ModelField(name="last_ping", required=False, kind=str) + is_admin = ModelField(name="is_admin", required=False, kind=bool) + async def get_property(self, name): prop = await self.app.services.user_property.find_one( user_uid=self["uid"], name=name diff --git a/src/snek/service/channel_member.py b/src/snek/service/channel_member.py index a2300b6..df96786 100644 --- a/src/snek/service/channel_member.py +++ b/src/snek/service/channel_member.py @@ -11,9 +11,12 @@ class ChannelMemberService(BaseService): return await self.save(channel_member) async def get_user_uids(self, channel_uid): - async for model in self.mapper.query("SELECT user_uid FROM channel_member WHERE channel_uid=:channel_uid", {"channel_uid": channel_uid}): + async for model in self.mapper.query( + "SELECT user_uid FROM channel_member WHERE channel_uid=:channel_uid", + {"channel_uid": channel_uid}, + ): yield model["user_uid"] - + async def create( self, channel_uid, diff --git a/src/snek/service/socket.py b/src/snek/service/socket.py index c084eb9..a3654d2 100644 --- a/src/snek/service/socket.py +++ b/src/snek/service/socket.py @@ -15,7 +15,7 @@ class SocketService(BaseService): return False try: await self.ws.send_json(data) - except Exception as ex: + except Exception: self.is_connected = False return self.is_connected @@ -56,7 +56,9 @@ class SocketService(BaseService): async def broadcast(self, channel_uid, message): try: - async for user_uid in self.services.channel_member.get_user_uids(channel_uid): + async for user_uid in self.services.channel_member.get_user_uids( + channel_uid + ): print(user_uid, flush=True) await self.send_to_user(user_uid, message) except Exception as ex: diff --git a/src/snek/service/user.py b/src/snek/service/user.py index c527361..c0734dc 100644 --- a/src/snek/service/user.py +++ b/src/snek/service/user.py @@ -39,6 +39,15 @@ class UserService(BaseService): model = await self.get(username=username, deleted_at=None) return model + def get_admin_uids(self): + return self.mapper.get_admin_uids() + + async def get_template_path(self, user_uid): + path = pathlib.Path(f"./drive/{user_uid}/snek/templates") + if not path.exists(): + return None + return path + async def get_home_folder(self, user_uid): folder = pathlib.Path(f"./drive/{user_uid}") if not folder.exists(): diff --git a/src/snek/service/user_property.py b/src/snek/service/user_property.py index 49a120d..4d11fa8 100644 --- a/src/snek/service/user_property.py +++ b/src/snek/service/user_property.py @@ -9,16 +9,18 @@ class UserPropertyService(BaseService): async def set(self, user_uid, name, value): self.mapper.db["user_property"].upsert( { - "user_uid": user_uid, - "name": name, - "value": json.dumps(value, default=str) + "user_uid": user_uid, + "name": name, + "value": json.dumps(value, default=str), }, - ["user_uid", "name"] + ["user_uid", "name"], ) - + async def get(self, user_uid, name): try: - return json.loads((await super().get(user_uid=user_uid, name=name))["value"]) + return json.loads( + (await super().get(user_uid=user_uid, name=name))["value"] + ) except Exception as ex: print(ex) return None diff --git a/src/snek/system/cache.py b/src/snek/system/cache.py index 0ecfd47..eed888a 100644 --- a/src/snek/system/cache.py +++ b/src/snek/system/cache.py @@ -18,7 +18,7 @@ class Cache: self.version = ((42 + 420 + 1984 + 1990 + 10 + 6 + 71 + 3004 + 7245) ^ 1337) + 4 async def get(self, args): - await self.update_stat(args, 'get') + await self.update_stat(args, "get") try: self.lru.pop(self.lru.index(args)) except: @@ -34,20 +34,28 @@ class Cache: async def get_stats(self): all_ = [] for key in self.lru: - all_.append({'key': key, 'set': self.stats[key]['set'], 'get': self.stats[key]['get'], 'delete': self.stats[key]['delete'],'value': str(self.serialize(self.cache[key].record))}) + all_.append( + { + "key": key, + "set": self.stats[key]["set"], + "get": self.stats[key]["get"], + "delete": self.stats[key]["delete"], + "value": str(self.serialize(self.cache[key].record)), + } + ) return all_ def serialize(self, obj): cpy = obj.copy() - cpy.pop('created_at', None) - cpy.pop('deleted_at', None) - cpy.pop('email', None) - cpy.pop('password', None) + cpy.pop("created_at", None) + cpy.pop("deleted_at", None) + cpy.pop("email", None) + cpy.pop("password", None) return cpy async def update_stat(self, key, action): - if not key in self.stats: - self.stats[key] = {'set':0, 'get':0, 'delete':0} + if key not in self.stats: + self.stats[key] = {"set": 0, "get": 0, "delete": 0} self.stats[key][action] = self.stats[key][action] + 1 def json_default(self, value): @@ -70,7 +78,7 @@ class Cache: async def set(self, args, result): is_new = args not in self.cache self.cache[args] = result - await self.update_stat(args, 'set') + await self.update_stat(args, "set") try: self.lru.pop(self.lru.index(args)) except (ValueError, IndexError): @@ -86,7 +94,7 @@ class Cache: # print(f"Cache store! {len(self.lru)} items. New version:", self.version, flush=True) async def delete(self, args): - await self.update_stat(args, 'delete') + await self.update_stat(args, "delete") if args in self.cache: try: self.lru.pop(self.lru.index(args)) diff --git a/src/snek/system/http.py b/src/snek/system/http.py index 2b59636..a1e87a4 100644 --- a/src/snek/system/http.py +++ b/src/snek/system/http.py @@ -32,9 +32,8 @@ from urllib.parse import urljoin import aiohttp import imgkit -from bs4 import BeautifulSoup - from app.cache import time_cache_async +from bs4 import BeautifulSoup async def crc32(data): diff --git a/src/snek/system/markdown.py b/src/snek/system/markdown.py index 53b5db8..82a222e 100644 --- a/src/snek/system/markdown.py +++ b/src/snek/system/markdown.py @@ -2,13 +2,12 @@ from types import SimpleNamespace +from app.cache import time_cache_async from mistune import HTMLRenderer, Markdown from pygments import highlight from pygments.formatters import html from pygments.lexers import get_lexer_by_name -from app.cache import time_cache_async - class MarkdownRenderer(HTMLRenderer): diff --git a/src/snek/system/terminal.py b/src/snek/system/terminal.py index 2120bf4..c5410b6 100644 --- a/src/snek/system/terminal.py +++ b/src/snek/system/terminal.py @@ -16,12 +16,12 @@ commands = { class TerminalSession: def __init__(self, command): - self.master, self.slave = None,None + self.master, self.slave = None, None self.process = None self.sockets = [] self.history = b"" self.history_size = 1024 * 20 - self.command = command + self.command = command self.start_process(self.command) def start_process(self, command): @@ -29,7 +29,7 @@ class TerminalSession: if self.master: os.close(self.master) os.close(self.slave) - self.master = None + self.master = None self.slave = None self.master, self.slave = pty.openpty() @@ -45,7 +45,7 @@ class TerminalSession: def is_running(self): if not self.process: return False - loop = asyncio.get_event_loop() + asyncio.get_event_loop() return self.process.poll() is None async def add_websocket(self, ws): @@ -78,7 +78,7 @@ class TerminalSession: self.sockets.remove(ws) except Exception: await self.close() - break + break async def close(self): print("Terminating process") @@ -88,8 +88,8 @@ class TerminalSession: if self.master: os.close(self.master) os.close(self.slave) - self.master = None - self.slave = None + self.master = None + self.slave = None print("Terminated process") for ws in self.sockets: diff --git a/src/snek/system/view.py b/src/snek/system/view.py index 981a2e5..70379ef 100644 --- a/src/snek/system/view.py +++ b/src/snek/system/view.py @@ -8,7 +8,9 @@ class BaseView(web.View): login_required = False async def _iter(self): - if self.login_required and (not self.session.get("logged_in") or not self.session.get("uid")): + if self.login_required and ( + not self.session.get("logged_in") or not self.session.get("uid") + ): return web.HTTPFound("/") return await super()._iter() diff --git a/src/snek/view/index.py b/src/snek/view/index.py index c8b1409..2f44443 100644 --- a/src/snek/view/index.py +++ b/src/snek/view/index.py @@ -10,9 +10,11 @@ # MIT License -from snek.system.view import BaseView from aiohttp import web +from snek.system.view import BaseView + + class IndexView(BaseView): async def get(self): if self.session.get("uid"): diff --git a/src/snek/view/search_user.py b/src/snek/view/search_user.py index d6d93c8..1f09a26 100644 --- a/src/snek/view/search_user.py +++ b/src/snek/view/search_user.py @@ -35,6 +35,7 @@ from snek.system.view import BaseFormView class SearchUserView(BaseFormView): form = SearchUserForm login_required = True + async def get(self): users = [] query = self.request.query.get("query") diff --git a/src/snek/view/settings/profile.py b/src/snek/view/settings/profile.py index 52f46df..164c526 100644 --- a/src/snek/view/settings/profile.py +++ b/src/snek/view/settings/profile.py @@ -17,24 +17,22 @@ class SettingsProfileView(BaseFormView): return web.json_response(await form.to_json()) + profile = await self.services.user_property.get( + self.session.get("uid"), "profile" + ) - - profile = await self.services.user_property.get(self.session.get("uid"), "profile") - user = await self.services.user.get(uid=self.session.get("uid")) return await self.render_template( - "settings/profile.html", {"form": await form.to_json(), "user": user, "profile": profile or ''} + "settings/profile.html", + {"form": await form.to_json(), "user": user, "profile": profile or ""}, ) async def post(self): data = await self.request.post() user = await self.services.user.get(uid=self.session.get("uid")) - - user['nick'] = data['nick'] - await self.services.user.save(user) - await self.services.user_property.set(user["uid"],"profile", data['profile']) + + user["nick"] = data["nick"] + await self.services.user.save(user) + await self.services.user_property.set(user["uid"], "profile", data["profile"]) return web.HTTPFound("/settings/profile.html") - - - diff --git a/src/snek/view/stats.py b/src/snek/view/stats.py index 73714ce..1680c5c 100644 --- a/src/snek/view/stats.py +++ b/src/snek/view/stats.py @@ -1,10 +1,13 @@ -from snek.system.view import BaseView -import json +import json + from aiohttp import web +from snek.system.view import BaseView + + class StatsView(BaseView): - + async def get(self): data = await self.app.cache.get_stats() data = json.dumps({"total": len(data), "stats": data}, default=str, indent=1) - return web.Response(text=data, content_type='application/json') + return web.Response(text=data, content_type="application/json") diff --git a/src/snek/view/upload.py b/src/snek/view/upload.py index 01c0ee7..cf01948 100644 --- a/src/snek/view/upload.py +++ b/src/snek/view/upload.py @@ -38,9 +38,9 @@ class UploadView(BaseView): user_uid = self.request.session.get("uid") upload_dir = await self.services.user.get_home_folder(user_uid) - upload_dir = upload_dir.joinpath("upload") + upload_dir = upload_dir.joinpath("upload") upload_dir.mkdir(parents=True, exist_ok=True) - + channel_uid = None drive = await self.services.drive.get_or_create( diff --git a/src/snek/view/user.py b/src/snek/view/user.py index d29b8f3..312f7bf 100644 --- a/src/snek/view/user.py +++ b/src/snek/view/user.py @@ -2,13 +2,14 @@ from snek.system.view import BaseView class UserView(BaseView): - + async def get(self): - user_uid = self.request.match_info.get('user') + user_uid = self.request.match_info.get("user") user = await self.services.user.get(uid=user_uid) - profile_content = await self.services.user_property.get(user['uid'],'profile') or '' - return await self.render_template('user.html', { - 'user_uid': user_uid, - 'user': user.record, - 'profile': profile_content - }) + profile_content = ( + await self.services.user_property.get(user["uid"], "profile") or "" + ) + return await self.render_template( + "user.html", + {"user_uid": user_uid, "user": user.record, "profile": profile_content}, + ) diff --git a/src/snek/webdav.py b/src/snek/webdav.py index 6025038..4c57fab 100755 --- a/src/snek/webdav.py +++ b/src/snek/webdav.py @@ -12,9 +12,8 @@ import uuid import aiofiles import aiohttp import aiohttp.web -from lxml import etree - from app.cache import time_cache_async +from lxml import etree @aiohttp.web.middleware