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,7 +2,8 @@ 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
from snek.system.object import Object from snek.system.object import Object
@ -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,11 +1,11 @@
from snek.system.model import BaseModel, ModelField from snek.system.model import BaseModel, ModelField
class ChannelModel(BaseModel):
label = ModelField(name="label", required=True,kind=str)
description = ModelField(name="description", required=False,kind=str)
tag = ModelField(name="tag", required=False,kind=str)
created_by_uid = ModelField(name="created_by_uid", required=True,kind=str)
is_private = ModelField(name="is_private", required=True,kind=bool,value=False)
is_listed = ModelField(name="is_listed", required=True,kind=bool,value=True)
index = ModelField(name="index", required=True,kind=int,value=1000)
class ChannelModel(BaseModel):
label = ModelField(name="label", required=True, kind=str)
description = ModelField(name="description", required=False, kind=str)
tag = ModelField(name="tag", required=False, kind=str)
created_by_uid = ModelField(name="created_by_uid", required=True, kind=str)
is_private = ModelField(name="is_private", required=True, kind=bool, value=False)
is_listed = ModelField(name="is_listed", required=True, kind=bool, value=True)
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_muted = ModelField(name="is_muted", required=True,kind=bool,value=False) )
is_banned = ModelField(name="is_banned", 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_banned = ModelField(name="is_banned", required=True, kind=bool, value=False)

View File

@ -1,9 +1,7 @@
from snek.system.model import BaseModel, ModelField from snek.system.model import BaseModel, ModelField
class ChannelMessageModel(BaseModel): class ChannelMessageModel(BaseModel):
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)
message = ModelField(name="message", required=True,kind=str) message = ModelField(name="message", required=True, kind=str)

View File

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

View File

@ -1,21 +1,20 @@
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),
} }
) )
def get_service(name, app=None): def get_service(name, app=None):

View File

@ -1,31 +1,48 @@
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}.")
async def ensure_public_channel(self, created_by_uid): async def ensure_public_channel(self, created_by_uid):
model = await self.get(is_listed=True,tag="public") model = await self.get(is_listed=True, tag="public")
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,35 +1,36 @@
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
self.cache = {} self.cache = {}
self.max_items = max_items self.max_items = max_items
self.lru = [] self.lru = []
self.version = ((42+420+1984+1990+10+6+71+3004+7245)^1337)+4 self.version = ((42 + 420 + 1984 + 1990 + 10 + 6 + 71 + 3004 + 7245) ^ 1337) + 4
async def get(self, args): async def get(self, args):
try: try:
self.lru.pop(self.lru.index(args)) self.lru.pop(self.lru.index(args))
except: except:
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)
return self.cache[args] return self.cache[args]
def json_default(self, value): def json_default(self, value):
#if hasattr(value, "to_json"): # if hasattr(value, "to_json"):
# return value.to_json() # return value.to_json()
try: try:
return json.dumps(value.__dict__, default=str) return json.dumps(value.__dict__, default=str)
@ -37,24 +38,30 @@ 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))
except(ValueError, IndexError): except (ValueError, IndexError):
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()
if is_new: if is_new:
self.version += 1 self.version += 1
print("New version:",self.version,flush=True) print("New version:", self.version, flush=True)
async def delete(self, args): async def delete(self, args):
if args in self.cache: if args in self.cache:
@ -64,24 +71,23 @@ class Cache:
pass pass
del self.cache[args] del self.cache[args]
def async_cache(self,func): def async_cache(self, func):
@functools.wraps(func) @functools.wraps(func)
async def wrapper(*args,**kwargs): async def wrapper(*args, **kwargs):
cache_key = await self.create_cache_key(args,kwargs) cache_key = await self.create_cache_key(args, kwargs)
cached = await self.get(cache_key) cached = await self.get(cache_key)
if cached: if cached:
return cached return cached
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):
cache_key = await self.create_cache_key(args,kwargs) cache_key = await self.create_cache_key(args, kwargs)
if cache_key in self.cache: if cache_key in self.cache:
try: try:
self.lru.pop(self.lru.index(cache_key)) self.lru.pop(self.lru.index(cache_key))

View File

@ -1,10 +1,8 @@
class Object: class Object:
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
for arg in args: for arg in args:
if isinstance(arg,dict): if isinstance(arg, dict):
self.__dict__.update(arg) self.__dict__.update(arg)
self.__dict__.update(kwargs) self.__dict__.update(kwargs)

View File

@ -19,11 +19,11 @@ class BaseService:
else: else:
self.mapper = None self.mapper = None
async def exists(self,uid=None, **kwargs): async def exists(self, uid=None, **kwargs):
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):
@ -32,23 +32,23 @@ class BaseService:
async def new(self, **kwargs): async def new(self, **kwargs):
return await self.mapper.new() return await self.mapper.new()
async def get(self,uid=None, **kwargs): async def get(self, uid=None, **kwargs):
if uid: if uid:
if not kwargs: if not kwargs:
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

@ -20,8 +20,8 @@ class BaseView(web.View):
def db(self): def db(self):
return self.app.db return self.app.db
async def json_response(self, data,**kwargs): async def json_response(self, data, **kwargs):
return web.json_response(data,**kwargs) return web.json_response(data, **kwargs)
@property @property
def session(self): def session(self):

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
),
} }
) )