Formatting.

This commit is contained in:
retoor 2025-01-25 22:28:33 +01:00
parent b4f9ff2c62
commit f25feeeca3
21 changed files with 219 additions and 164 deletions

View File

@ -1,5 +1,4 @@
import pathlib import pathlib
from types import SimpleNamespace
from aiohttp import web from aiohttp import web
from aiohttp_session import ( from aiohttp_session import (
@ -21,10 +20,8 @@ from snek.view.about import AboutHTMLView, AboutMDView
from snek.view.docs import DocsHTMLView, DocsMDView from snek.view.docs import DocsHTMLView, DocsMDView
from snek.view.index import IndexView from snek.view.index import IndexView
from snek.view.login import LoginView from snek.view.login import LoginView
from snek.view.login_form import LoginFormView
from snek.view.logout import LogoutView from snek.view.logout import LogoutView
from snek.view.register import RegisterView from snek.view.register import RegisterView
from snek.view.register_form import RegisterFormView
from snek.view.status import StatusView from snek.view.status import StatusView
from snek.view.web import WebView from snek.view.web import WebView

View File

@ -1,5 +1,4 @@
import functools import functools
from types import SimpleNamespace
from snek.mapper.channel import ChannelMapper from snek.mapper.channel import ChannelMapper
from snek.mapper.channel_member import ChannelMemberMapper from snek.mapper.channel_member import ChannelMemberMapper
@ -11,11 +10,13 @@ from snek.system.object import Object
@functools.cache @functools.cache
def get_mappers(app=None): def get_mappers(app=None):
return Object( return Object(
**{"user": UserMapper(app=app), **{
'channel_member': ChannelMemberMapper(app=app), "user": UserMapper(app=app),
'channel': ChannelMapper(app=app), "channel_member": ChannelMemberMapper(app=app),
'channel_message': ChannelMessageMapper(app=app) "channel": ChannelMapper(app=app),
}) "channel_message": ChannelMessageMapper(app=app),
}
)
def get_mapper(name, app=None): def get_mapper(name, app=None):

View File

@ -2,6 +2,7 @@ import functools
from snek.model.channel import ChannelModel from snek.model.channel import ChannelModel
from snek.model.channel_member import ChannelMemberModel from snek.model.channel_member import ChannelMemberModel
# from snek.model.channel_message import ChannelMessageModel # from snek.model.channel_message import ChannelMessageModel
from snek.model.channel_message import ChannelMessageModel from snek.model.channel_message import ChannelMessageModel
from snek.model.user import UserModel from snek.model.user import UserModel
@ -10,10 +11,14 @@ from snek.system.object import Object
@functools.cache @functools.cache
def get_models(): def get_models():
return Object(**{"user": UserModel, return Object(
**{
"user": UserModel,
"channel_member": ChannelMemberModel, "channel_member": ChannelMemberModel,
"channel": ChannelModel, "channel": ChannelModel,
"channel_message": ChannelMessageModel}) "channel_message": ChannelMessageModel,
}
)
def get_model(name): def get_model(name):

View File

@ -1,5 +1,6 @@
from snek.system.model import BaseModel, ModelField from snek.system.model import BaseModel, ModelField
class ChannelModel(BaseModel): class ChannelModel(BaseModel):
label = ModelField(name="label", required=True, kind=str) label = ModelField(name="label", required=True, kind=str)
description = ModelField(name="description", required=False, kind=str) description = ModelField(name="description", required=False, kind=str)
@ -8,4 +9,3 @@ class ChannelModel(BaseModel):
is_private = ModelField(name="is_private", required=True, kind=bool, value=False) is_private = ModelField(name="is_private", required=True, kind=bool, value=False)
is_listed = ModelField(name="is_listed", required=True, kind=bool, value=True) is_listed = ModelField(name="is_listed", required=True, kind=bool, value=True)
index = ModelField(name="index", required=True, kind=int, value=1000) index = ModelField(name="index", required=True, kind=int, value=1000)

View File

@ -1,10 +1,15 @@
from snek.system.model import BaseModel, ModelField from snek.system.model import BaseModel, ModelField
class ChannelMemberModel(BaseModel): class ChannelMemberModel(BaseModel):
label = ModelField(name="label", required=True, kind=str) label = ModelField(name="label", required=True, kind=str)
channel_uid = ModelField(name="channel_uid", required=True, kind=str) channel_uid = ModelField(name="channel_uid", required=True, kind=str)
user_uid = ModelField(name="user_uid", required=True, kind=str) user_uid = ModelField(name="user_uid", required=True, kind=str)
is_moderator = ModelField(name="is_moderator", required=True,kind=bool,value=False) is_moderator = ModelField(
is_read_only = ModelField(name="is_read_only", required=True,kind=bool,value=False) name="is_moderator", required=True, kind=bool, value=False
)
is_read_only = ModelField(
name="is_read_only", required=True, kind=bool, value=False
)
is_muted = ModelField(name="is_muted", required=True, kind=bool, value=False) is_muted = ModelField(name="is_muted", required=True, kind=bool, value=False)
is_banned = ModelField(name="is_banned", required=True, kind=bool, value=False) is_banned = ModelField(name="is_banned", required=True, kind=bool, value=False)

View File

@ -1,5 +1,3 @@
from snek.system.model import BaseModel, ModelField from snek.system.model import BaseModel, ModelField

View File

@ -1,6 +1,3 @@
from snek.system.model import BaseModel, ModelField from snek.system.model import BaseModel, ModelField

View File

@ -1,19 +1,18 @@
import functools import functools
from snek.service.channel import ChannelService from snek.service.channel import ChannelService
from snek.service.user import UserService
from snek.service.channel_member import ChannelMemberService from snek.service.channel_member import ChannelMemberService
from types import SimpleNamespace from snek.service.user import UserService
from snek.system.object import Object from snek.system.object import Object
@functools.cache @functools.cache
def get_services(app): def get_services(app):
return Object( return Object(
**{ **{
"user": UserService(app=app), "user": UserService(app=app),
"channel_member": ChannelMemberService(app=app), "channel_member": ChannelMemberService(app=app),
'channel': ChannelService(app=app) "channel": ChannelService(app=app),
} }
) )

View File

@ -1,21 +1,30 @@
from snek.system.service import BaseService from snek.system.service import BaseService
class ChannelService(BaseService): class ChannelService(BaseService):
mapper_name = "channel" mapper_name = "channel"
async def create(self, label, created_by_uid, description=None, tag=None, is_private=False, is_listed=True): async def create(
self,
label,
created_by_uid,
description=None,
tag=None,
is_private=False,
is_listed=True,
):
if label[0] != "#" and is_listed: if label[0] != "#" and is_listed:
label = f"#{label}" label = f"#{label}"
count = await self.count(deleted_at=None) count = await self.count(deleted_at=None)
if not tag and not count: if not tag and not count:
tag = "public" tag = "public"
model = await self.new() model = await self.new()
model['label'] = label model["label"] = label
model['description'] = description model["description"] = description
model['tag'] = tag model["tag"] = tag
model['created_by_uid'] = created_by_uid model["created_by_uid"] = created_by_uid
model['is_private'] = is_private model["is_private"] = is_private
model['is_listed'] = is_listed model["is_listed"] = is_listed
if await self.save(model): if await self.save(model):
return model return model
raise Exception(f"Failed to create channel: {model.errors}.") raise Exception(f"Failed to create channel: {model.errors}.")
@ -25,7 +34,15 @@ class ChannelService(BaseService):
is_moderator = False is_moderator = False
if not model: if not model:
is_moderator = True is_moderator = True
model = await self.create("public", created_by_uid=created_by_uid, is_listed=True, tag="public") model = await self.create(
await self.app.services.channel_member.create(model['uid'], created_by_uid, is_moderator=is_moderator, is_read_only=False, is_muted=False, is_banned=False) "public", created_by_uid=created_by_uid, is_listed=True, tag="public"
)
await self.app.services.channel_member.create(
model["uid"],
created_by_uid,
is_moderator=is_moderator,
is_read_only=False,
is_muted=False,
is_banned=False,
)
return model return model

View File

@ -1,10 +1,19 @@
from snek.system.service import BaseService from snek.system.service import BaseService
class ChannelMemberService(BaseService): class ChannelMemberService(BaseService):
mapper_name = "channel_member" mapper_name = "channel_member"
async def create(self, channel_uid, user_uid, is_moderator=False, is_read_only=False, is_muted=False, is_banned=False): async def create(
self,
channel_uid,
user_uid,
is_moderator=False,
is_read_only=False,
is_muted=False,
is_banned=False,
):
model = await self.get(channel_uid=channel_uid, user_uid=user_uid) model = await self.get(channel_uid=channel_uid, user_uid=user_uid)
if model: if model:
if model.is_banned.value: if model.is_banned.value:
@ -12,13 +21,13 @@ class ChannelMemberService(BaseService):
return model return model
model = await self.new() model = await self.new()
channel = await self.services.channel.get(uid=channel_uid) channel = await self.services.channel.get(uid=channel_uid)
model['label'] = channel['label'] model["label"] = channel["label"]
model['channel_uid'] = channel_uid model["channel_uid"] = channel_uid
model['user_uid'] = user_uid model["user_uid"] = user_uid
model['is_moderator'] = is_moderator model["is_moderator"] = is_moderator
model['is_read_only'] = is_read_only model["is_read_only"] = is_read_only
model['is_muted'] = is_muted model["is_muted"] = is_muted
model['is_banned'] = is_banned model["is_banned"] = is_banned
if await self.save(model): if await self.save(model):
return model return model
raise Exception(f"Failed to create channel member: {model.errors}.") raise Exception(f"Failed to create channel member: {model.errors}.")

View File

@ -6,9 +6,9 @@ class ChannelMessageService(BaseService):
async def create(self, channel_uid, user_uid, message): async def create(self, channel_uid, user_uid, message):
model = await self.new() model = await self.new()
model['channel_uid'] = channel_uid model["channel_uid"] = channel_uid
model['user_uid'] = user_uid model["user_uid"] = user_uid
model['message'] = message model["message"] = message
if await self.save(model): if await self.save(model):
return model return model
raise Exception(f"Failed to create channel message: {model.errors}.") raise Exception(f"Failed to create channel message: {model.errors}.")

View File

@ -1,5 +1,3 @@
from snek.system.service import BaseService from snek.system.service import BaseService
@ -8,23 +6,32 @@ class NotificationService(BaseService):
async def create(self, object_uid, object_type, user_uid, message): async def create(self, object_uid, object_type, user_uid, message):
model = await self.new() model = await self.new()
model['object_uid'] = object_uid model["object_uid"] = object_uid
model['object_type'] = object_type model["object_type"] = object_type
model['user_uid'] = user_uid model["user_uid"] = user_uid
model['message'] = message model["message"] = message
if await self.save(model): if await self.save(model):
return model return model
raise Exception(f"Failed to create notification: {model.errors}.") raise Exception(f"Failed to create notification: {model.errors}.")
async def create_channel_message(self, channel_message_uid): async def create_channel_message(self, channel_message_uid):
channel_message = await self.services.channel_message.get(uid=channel_message_uid) channel_message = await self.services.channel_message.get(
user = await self.services.user.get(uid=channel_message['user_uid']) uid=channel_message_uid
async for channel_member in self.services.channel_member.find(channel_uid=channel_message['channel_uid'],is_banned=False,is_muted=False, deleted_at=None): )
user = await self.services.user.get(uid=channel_message["user_uid"])
async for channel_member in self.services.channel_member.find(
channel_uid=channel_message["channel_uid"],
is_banned=False,
is_muted=False,
deleted_at=None,
):
model = await self.new() model = await self.new()
model['object_uid'] = channel_message_uid model["object_uid"] = channel_message_uid
model['object_type'] = "channel_message" model["object_type"] = "channel_message"
model['user_uid'] = channel_member['user_uid'] model["user_uid"] = channel_member["user_uid"]
model['message'] = f"New message from {user['nick']} in {channel_member['label']}." model["message"] = (
f"New message from {user['nick']} in {channel_member['label']}."
)
if await self.save(model): if await self.save(model):
return model return model
raise Exception(f"Failed to create notification: {model.errors}.") raise Exception(f"Failed to create notification: {model.errors}.")

View File

@ -17,13 +17,15 @@ class UserService(BaseService):
if await self.exists(username=username): if await self.exists(username=username):
raise Exception("User already exists.") raise Exception("User already exists.")
model = await self.new() model = await self.new()
model['nick'] = username model["nick"] = username
model.email.value = email model.email.value = email
model.username.value = username model.username.value = username
model.password.value = await security.hash(password) model.password.value = await security.hash(password)
if await self.save(model): if await self.save(model):
if model: if model:
channel = await self.services.channel.ensure_public_channel(model['uid']) channel = await self.services.channel.ensure_public_channel(
model["uid"]
)
if not channel: if not channel:
raise Exception("Failed to create public channel.") raise Exception("Failed to create public channel.")
return model return model

View File

@ -1,12 +1,13 @@
import functools import functools
import json import json
import uuid
from snek.system import security from snek.system import security
cache = functools.cache cache = functools.cache
CACHE_MAX_ITEMS_DEFAULT = 5000 CACHE_MAX_ITEMS_DEFAULT = 5000
class Cache: class Cache:
def __init__(self, app, max_items=CACHE_MAX_ITEMS_DEFAULT): def __init__(self, app, max_items=CACHE_MAX_ITEMS_DEFAULT):
self.app = app self.app = app
@ -22,7 +23,7 @@ class Cache:
print("Cache miss!", args, flush=True) print("Cache miss!", args, flush=True)
return None return None
self.lru.insert(0, args) self.lru.insert(0, args)
while(len(self.lru) > self.max_items): while len(self.lru) > self.max_items:
self.cache.pop(self.lru[-1]) self.cache.pop(self.lru[-1])
self.lru.pop() self.lru.pop()
print("Cache hit!", args, flush=True) print("Cache hit!", args, flush=True)
@ -37,10 +38,16 @@ class Cache:
return str(value) return str(value)
async def create_cache_key(self, args, kwargs): async def create_cache_key(self, args, kwargs):
return await security.hash(json.dumps({"args": args, "kwargs": kwargs}, sort_keys=True,default=self.json_default)) return await security.hash(
json.dumps(
{"args": args, "kwargs": kwargs},
sort_keys=True,
default=self.json_default,
)
)
async def set(self, args, result): async def set(self, args, result):
is_new = not args in self.cache is_new = args not in self.cache
self.cache[args] = result self.cache[args] = result
try: try:
self.lru.pop(self.lru.index(args)) self.lru.pop(self.lru.index(args))
@ -48,7 +55,7 @@ class Cache:
pass pass
self.lru.insert(0, args) self.lru.insert(0, args)
while(len(self.lru) > self.max_items): while len(self.lru) > self.max_items:
self.cache.pop(self.lru[-1]) self.cache.pop(self.lru[-1])
self.lru.pop() self.lru.pop()
@ -74,10 +81,9 @@ class Cache:
result = await func(*args, **kwargs) result = await func(*args, **kwargs)
await self.set(cache_key, result) await self.set(cache_key, result)
return result return result
return wrapper return wrapper
def async_delete_cache(self, func): def async_delete_cache(self, func):
@functools.wraps(func) @functools.wraps(func)
async def wrapper(*args, **kwargs): async def wrapper(*args, **kwargs):

View File

@ -1,5 +1,3 @@
class Object: class Object:
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View File

@ -23,7 +23,7 @@ class BaseService:
if uid: if uid:
if not kwargs and await self.cache.get(uid): if not kwargs and await self.cache.get(uid):
return True return True
kwargs['uid'] = uid kwargs["uid"] = uid
return await self.count(**kwargs) > 0 return await self.count(**kwargs) > 0
async def count(self, **kwargs): async def count(self, **kwargs):
@ -38,17 +38,17 @@ class BaseService:
result = await self.cache.get(uid) result = await self.cache.get(uid)
if result: if result:
return result return result
kwargs['uid'] = uid kwargs["uid"] = uid
result = await self.mapper.get(**kwargs) result = await self.mapper.get(**kwargs)
if result: if result:
await self.cache.set(result['uid'], result) await self.cache.set(result["uid"], result)
return result return result
async def save(self, model: UserModel): async def save(self, model: UserModel):
# if model.is_valid: You Know why not # if model.is_valid: You Know why not
if await self.mapper.save(model): if await self.mapper.save(model):
await self.cache.set(model['uid'], model) await self.cache.set(model["uid"], model)
return True return True
errors = await model.errors errors = await model.errors
raise Exception(f"Couldn't save model. Errors: f{errors}") raise Exception(f"Couldn't save model. Errors: f{errors}")

View File

@ -1,7 +1,9 @@
from snek.form.login import LoginForm
from snek.system.view import BaseFormView, BaseView
from aiohttp import web from aiohttp import web
from snek.form.login import LoginForm
from snek.system.view import BaseFormView
class LoginView(BaseFormView): class LoginView(BaseFormView):
form = LoginForm form = LoginForm
@ -10,9 +12,7 @@ class LoginView(BaseFormView):
return web.HTTPFound("/web.html") return web.HTTPFound("/web.html")
if self.request.path.endswith(".json"): if self.request.path.endswith(".json"):
return await super().get() return await super().get()
return await self.render_template( return await self.render_template("login.html")
"login.html"
)
async def submit(self, form): async def submit(self, form):
if await form.is_valid: if await form.is_valid:
@ -21,5 +21,3 @@ class LoginView(BaseFormView):
self.session["uid"] = form.uid.value self.session["uid"] = form.uid.value
return {"redirect_url": "/web.html"} return {"redirect_url": "/web.html"}
return {"is_valid": False} return {"is_valid": False}

View File

@ -1,12 +1,13 @@
from snek.form.register import RegisterForm
from snek.system.view import BaseFormView, BaseView
from aiohttp import web from aiohttp import web
from snek.form.register import RegisterForm
from snek.system.view import BaseFormView
class RegisterView(BaseFormView): class RegisterView(BaseFormView):
form = RegisterForm form = RegisterForm
async def get(self): async def get(self):
if self.session.get("logged_in"): if self.session.get("logged_in"):
return web.HTTPFound("/web.html") return web.HTTPFound("/web.html")

View File

@ -1,5 +1,5 @@
from snek.system.view import BaseView from snek.system.view import BaseView
import json
class StatusView(BaseView): class StatusView(BaseView):
async def get(self): async def get(self):
@ -11,21 +11,36 @@ class StatusView(BaseView):
user = await self.app.services.user.get(uid=self.session.get("uid")) user = await self.app.services.user.get(uid=self.session.get("uid"))
if not user: if not user:
return await self.json_response({"error": "User not found"}, status=404) return await self.json_response({"error": "User not found"}, status=404)
async for model in self.app.services.channel_member.find(user_uid=self.session.get("uid"),deleted_at=None,is_banned=False): async for model in self.app.services.channel_member.find(
channel = await self.app.services.channel.get(uid=model['channel_uid']) user_uid=self.session.get("uid"), deleted_at=None, is_banned=False
memberships.append(dict(name=channel['label'],description=model['description'],user_uid=model['user_uid'],is_moderator=model['is_moderator'],is_read_only=model['is_read_only'],is_muted=model['is_muted'],is_banned=model['is_banned'],channel_uid=model['channel_uid'],uid=model['uid'])) ):
user = dict( channel = await self.app.services.channel.get(uid=model["channel_uid"])
username=user['username'], memberships.append(
email=user['email'], {
nick=user['nick'], "name": channel["label"],
uid=user['uid'], "description": model["description"],
memberships=memberships "user_uid": model["user_uid"],
"is_moderator": model["is_moderator"],
"is_read_only": model["is_read_only"],
"is_muted": model["is_muted"],
"is_banned": model["is_banned"],
"channel_uid": model["channel_uid"],
"uid": model["uid"],
}
) )
user = {
"username": user["username"],
"email": user["email"],
"nick": user["nick"],
"uid": user["uid"],
"memberships": memberships,
}
return await self.json_response( return await self.json_response(
{ {
"user": user, "user": user,
"cache": await self.app.cache.create_cache_key(self.app.cache.cache,None) "cache": await self.app.cache.create_cache_key(
self.app.cache.cache, None
),
} }
) )