This commit is contained in:
retoor 2025-05-09 14:19:29 +02:00
parent 95ad49df43
commit 17c6124a57
86 changed files with 1885 additions and 5826 deletions

View File

@ -1 +0,0 @@

View File

@ -1,32 +1,21 @@
import click _D='Database path for the application'
import uvloop _C='snek.db'
_B='--db_path'
_A=True
import click,uvloop
from aiohttp import web from aiohttp import web
import asyncio import asyncio
from snek.app import Application from snek.app import Application
from IPython import start_ipython from IPython import start_ipython
@click.group() @click.group()
def cli(): def cli():0
pass
@cli.command() @cli.command()
@click.option('--port', default=8081, show_default=True, help='Port to run the application on') @click.option('--port',default=8081,show_default=_A,help='Port to run the application on')
@click.option('--host', default='0.0.0.0', show_default=True, help='Host to run the application on') @click.option('--host',default='0.0.0.0',show_default=_A,help='Host to run the application on')
@click.option('--db_path', default='snek.db', show_default=True, help='Database path for the application') @click.option(_B,default=_C,show_default=_A,help=_D)
def serve(port, host, db_path): def serve(port,host,db_path):asyncio.set_event_loop_policy(uvloop.EventLoopPolicy());web.run_app(Application(db_path=f"sqlite:///{db_path}"),port=port,host=host)
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
web.run_app(
Application(db_path=f"sqlite:///{db_path}"), port=port, host=host
)
@cli.command() @cli.command()
@click.option('--db_path', default='snek.db', show_default=True, help='Database path for the application') @click.option(_B,default=_C,show_default=_A,help=_D)
def shell(db_path): def shell(db_path):A=Application(db_path=f"sqlite:///{db_path}");start_ipython(argv=[],user_ns={'app':A})
app = Application(db_path=f"sqlite:///{db_path}") def main():cli()
start_ipython(argv=[], user_ns={'app': app}) if __name__=='__main__':main()
def main():
cli()
if __name__ == "__main__":
main()

View File

@ -1,37 +1,31 @@
import asyncio _G='name'
import logging _F='static'
import pathlib _E='user'
import time _D=None
import uuid _C=True
_B='channel_uid'
_A='uid'
import asyncio,logging,pathlib,time,uuid
from snek.view.threads import ThreadsView from snek.view.threads import ThreadsView
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from aiohttp import web from aiohttp import web
from aiohttp_session import ( from aiohttp_session import get_session as session_get,session_middleware,setup as session_setup
get_session as session_get,
session_middleware,
setup as session_setup,
)
from aiohttp_session.cookie_storage import EncryptedCookieStorage from aiohttp_session.cookie_storage import EncryptedCookieStorage
from app.app import Application as BaseApplication from app.app import Application as BaseApplication
from jinja2 import FileSystemLoader from jinja2 import FileSystemLoader
from snek.docs.app import Application as DocsApplication from snek.docs.app import Application as DocsApplication
from snek.mapper import get_mappers from snek.mapper import get_mappers
from snek.service import get_services from snek.service import get_services
from snek.system import http from snek.system import http
from snek.system.cache import Cache from snek.system.cache import Cache
from snek.system.markdown import MarkdownExtension from snek.system.markdown import MarkdownExtension
from snek.system.middleware import auth_middleware, cors_middleware from snek.system.middleware import auth_middleware,cors_middleware
from snek.system.profiler import profiler_handler from snek.system.profiler import profiler_handler
from snek.system.template import EmojiExtension, LinkifyExtension, PythonExtension from snek.system.template import EmojiExtension,LinkifyExtension,PythonExtension
from snek.view.about import AboutHTMLView, AboutMDView from snek.view.about import AboutHTMLView,AboutMDView
from snek.view.avatar import AvatarView from snek.view.avatar import AvatarView
from snek.view.docs import DocsHTMLView, DocsMDView from snek.view.docs import DocsHTMLView,DocsMDView
from snek.view.drive import DriveView from snek.view.drive import DriveView
from snek.view.index import IndexView from snek.view.index import IndexView
from snek.view.login import LoginView from snek.view.login import LoginView
@ -48,275 +42,79 @@ from snek.view.settings.index import SettingsIndexView
from snek.view.settings.profile import SettingsProfileView from snek.view.settings.profile import SettingsProfileView
from snek.view.stats import StatsView from snek.view.stats import StatsView
from snek.view.status import StatusView from snek.view.status import StatusView
from snek.view.terminal import TerminalSocketView, TerminalView from snek.view.terminal import TerminalSocketView,TerminalView
from snek.view.upload import UploadView from snek.view.upload import UploadView
from snek.view.user import UserView from snek.view.user import UserView
from snek.view.web import WebView from snek.view.web import WebView
from snek.webdav import WebdavApplication from snek.webdav import WebdavApplication
from snek.sgit import GitApplication from snek.sgit import GitApplication
SESSION_KEY=b'c79a0c5fda4b424189c427d28c9f7c34'
SESSION_KEY = b"c79a0c5fda4b424189c427d28c9f7c34"
@web.middleware @web.middleware
async def session_middleware(request, handler): async def session_middleware(request,handler):A=request;setattr(A,'session',await session_get(A));B=await handler(A);return B
setattr(request, "session", await session_get(request))
response = await handler(request)
return response
@web.middleware @web.middleware
async def trailing_slash_middleware(request, handler): async def trailing_slash_middleware(request,handler):
if request.path and not request.path.endswith("/"): A=request
# Redirect to the same path with a trailing slash if A.path and not A.path.endswith('/'):raise web.HTTPFound(A.path+'/')
raise web.HTTPFound(request.path + "/") return await handler(A)
return await handler(request)
class Application(BaseApplication): class Application(BaseApplication):
def __init__(A,*B,**C):D=[cors_middleware,web.normalize_path_middleware(merge_slashes=_C)];A.template_path=pathlib.Path(__file__).parent.joinpath('templates');A.static_path=pathlib.Path(__file__).parent.joinpath(_F);super().__init__(middlewares=D,template_path=A.template_path,client_max_size=5368709120*B,**C);session_setup(A,EncryptedCookieStorage(SESSION_KEY));A.tasks=asyncio.Queue();A._middlewares.append(session_middleware);A._middlewares.append(auth_middleware);A.jinja2_env.add_extension(MarkdownExtension);A.jinja2_env.add_extension(LinkifyExtension);A.jinja2_env.add_extension(PythonExtension);A.jinja2_env.add_extension(EmojiExtension);A.setup_router();A.executor=_D;A.cache=Cache(A);A.services=get_services(app=A);A.mappers=get_mappers(app=A);A.on_startup.append(A.prepare_asyncio);A.on_startup.append(A.prepare_database)
def __init__(self, *args, **kwargs): async def prepare_asyncio(A,app):app.executor=ThreadPoolExecutor(max_workers=200);app.loop.set_default_executor(A.executor)
middlewares = [ async def create_task(A,task):await A.tasks.put(task)
cors_middleware, async def task_runner(A):
web.normalize_path_middleware(merge_slashes=True), while _C:
] B=await A.tasks.get();A.db.begin()
self.template_path = pathlib.Path(__file__).parent.joinpath("templates") try:C=time.time();await B;D=time.time();print(f"Task {B} took {D-C} seconds");A.tasks.task_done()
self.static_path = pathlib.Path(__file__).parent.joinpath("static") except Exception as E:print(E)
super().__init__( A.db.commit()
middlewares=middlewares, template_path=self.template_path, client_max_size=1024*1024*1024*5 *args, **kwargs async def prepare_database(A,app):
) C='channel_message';D='channel_member';E='username';B='user_uid';A.db.query('PRAGMA journal_mode=WAL');A.db.query('PRAGMA syncnorm=off')
session_setup(self, EncryptedCookieStorage(SESSION_KEY))
self.tasks = asyncio.Queue()
self._middlewares.append(session_middleware)
self._middlewares.append(auth_middleware)
self.jinja2_env.add_extension(MarkdownExtension)
self.jinja2_env.add_extension(LinkifyExtension)
self.jinja2_env.add_extension(PythonExtension)
self.jinja2_env.add_extension(EmojiExtension)
self.setup_router()
self.executor = None
self.cache = Cache(self)
self.services = get_services(app=self)
self.mappers = get_mappers(app=self)
self.on_startup.append(self.prepare_asyncio)
self.on_startup.append(self.prepare_database)
async def prepare_asyncio(self, app):
# app.loop = asyncio.get_running_loop()
app.executor = ThreadPoolExecutor(max_workers=200)
app.loop.set_default_executor(self.executor)
async def create_task(self, task):
await self.tasks.put(task)
async def task_runner(self):
while True:
task = await self.tasks.get()
self.db.begin()
try: try:
task_start = time.time() if not A.db[_E].has_index(E):A.db[_E].create_index(E,unique=_C)
await task if not A.db[D].has_index([_B,B]):A.db[D].create_index([_B,B])
task_end = time.time() if not A.db[C].has_index([_B,B]):A.db[C].create_index([_B,B])
print(f"Task {task} took {task_end - task_start} seconds") except:pass
self.tasks.task_done() await app.services.drive.prepare_all();A.loop.create_task(A.task_runner())
except Exception as ex: def setup_router(A):A.router.add_get('/',IndexView);A.router.add_static('/',pathlib.Path(__file__).parent.joinpath(_F),name=_F,show_index=_C);A.router.add_view('/profiler.html',profiler_handler);A.router.add_view('/about.html',AboutHTMLView);A.router.add_view('/about.md',AboutMDView);A.router.add_view('/logout.json',LogoutView);A.router.add_view('/logout.html',LogoutView);A.router.add_view('/docs.html',DocsHTMLView);A.router.add_view('/docs.md',DocsMDView);A.router.add_view('/status.json',StatusView);A.router.add_view('/settings/index.html',SettingsIndexView);A.router.add_view('/settings/profile.html',SettingsProfileView);A.router.add_view('/settings/profile.json',SettingsProfileView);A.router.add_view('/web.html',WebView);A.router.add_view('/login.html',LoginView);A.router.add_view('/login.json',LoginView);A.router.add_view('/register.html',RegisterView);A.router.add_view('/register.json',RegisterView);A.router.add_view('/drive/{rel_path:.*}',DriveView);A.router.add_view('/drive.bin',UploadView);A.router.add_view('/drive.bin/{uid}.{ext}',UploadView);A.router.add_view('/search-user.html',SearchUserView);A.router.add_view('/search-user.json',SearchUserView);A.router.add_view('/avatar/{uid}.svg',AvatarView);A.router.add_get('/http-get',A.handle_http_get);A.router.add_get('/http-photo',A.handle_http_photo);A.router.add_get('/rpc.ws',RPCView);A.router.add_view('/channel/{channel}.html',WebView);A.router.add_view('/threads.html',ThreadsView);A.router.add_view('/terminal.ws',TerminalSocketView);A.router.add_view('/terminal.html',TerminalView);A.router.add_view('/drive.json',DriveView);A.router.add_view('/drive/{drive}.json',DriveView);A.router.add_view('/stats.json',StatsView);A.router.add_view('/user/{user}.html',UserView);A.router.add_view('/repository/{username}/{repo_name}',RepositoryView);A.router.add_view('/repository/{username}/{repo_name}/{rel_path:.*}',RepositoryView);A.router.add_view('/settings/repositories/index.html',RepositoriesIndexView);A.router.add_view('/settings/repositories/create.html',RepositoriesCreateView);A.router.add_view('/settings/repositories/repository/{name}/update.html',RepositoriesUpdateView);A.router.add_view('/settings/repositories/repository/{name}/delete.html',RepositoriesDeleteView);A.webdav=WebdavApplication(A);A.git=GitApplication(A);A.add_subapp('/webdav',A.webdav);A.add_subapp('/git',A.git)
print(ex) async def handle_test(A,request):return await A.render_template('test.html',request,context={_G:'retoor'})
self.db.commit() async def handle_http_get(C,request):A=request.query.get('url');B=await http.get(A);return web.Response(body=B)
async def handle_http_photo(C,request):A=request.query.get('url');B=await http.create_site_photo(A);return web.Response(body=B.read_bytes(),headers={'Content-Type':'image/png'})
async def prepare_database(self, app): async def render_template(A,template,request,context=_D):
self.db.query("PRAGMA journal_mode=WAL") I='channels';J='new_count';K='color';L=template;F='last_message_on';D=request;C=context;G=[]
self.db.query("PRAGMA syncnorm=off") if not C:C={}
C['rid']=str(uuid.uuid4())
try: if D.session.get(_A):
if not self.db["user"].has_index("username"): async for E in A.services.channel_member.find(user_uid=D.session.get(_A),deleted_at=_D,is_banned=False):
self.db["user"].create_index("username", unique=True) B={};M=await A.services.channel_member.get_other_dm_user(E[_B],D.session.get(_A));H=await E.get_channel();N=await H.get_last_message();O=_D
if not self.db["channel_member"].has_index(["channel_uid", "user_uid"]): if N:P=await N.get_user();O=P[K]
self.db["channel_member"].create_index(["channel_uid", "user_uid"]) B[K]=O;B[F]=H[F];B['is_private']=H['tag']=='dm'
if not self.db["channel_message"].has_index(["channel_uid", "user_uid"]): if M:B[_G]=M['nick'];B[_A]=E[_B]
self.db["channel_message"].create_index(["channel_uid", "user_uid"]) else:B[_G]=E['label'];B[_A]=E[_B]
except: B[J]=E[J];G.append(B)
pass G.sort(key=lambda x:x[F]or'',reverse=_C)
if I not in C:C[I]=G
await app.services.drive.prepare_all() if _E not in C:C[_E]=await A.services.user.get(D.session.get(_A))
self.loop.create_task(self.task_runner()) A.template_path.joinpath(L);await A.services.user.get_template_path(D.session.get(_A));A.original_loader=A.jinja2_env.loader;A.jinja2_env.loader=await A.get_user_template_loader(D.session.get(_A));Q=await super().render_template(L,D,C);A.jinja2_env.loader=A.original_loader;return Q
async def static_handler(B,request):
def setup_router(self): D=request;E=D.match_info.get('filename','');C=[];F=D.session.get(_A)
self.router.add_get("/", IndexView) if F:
self.router.add_static( A=await B.services.user.get_static_path(F)
"/", if A:C.append(A)
pathlib.Path(__file__).parent.joinpath("static"), for H in B.services.user.get_admin_uids():
name="static", A=await B.services.user.get_static_path(H)
show_index=True, if A:C.append(A)
) C.append(B.static_path)
self.router.add_view("/profiler.html", profiler_handler) for G in C:
self.router.add_view("/about.html", AboutHTMLView) if pathlib.Path(G).joinpath(E).exists():return web.FileResponse(pathlib.Path(G).joinpath(E))
self.router.add_view("/about.md", AboutMDView)
self.router.add_view("/logout.json", LogoutView)
self.router.add_view("/logout.html", LogoutView)
self.router.add_view("/docs.html", DocsHTMLView)
self.router.add_view("/docs.md", DocsMDView)
self.router.add_view("/status.json", StatusView)
self.router.add_view("/settings/index.html", SettingsIndexView)
self.router.add_view("/settings/profile.html", SettingsProfileView)
self.router.add_view("/settings/profile.json", SettingsProfileView)
self.router.add_view("/web.html", WebView)
self.router.add_view("/login.html", LoginView)
self.router.add_view("/login.json", LoginView)
self.router.add_view("/register.html", RegisterView)
self.router.add_view("/register.json", RegisterView)
self.router.add_view("/drive/{rel_path:.*}", DriveView)
self.router.add_view("/drive.bin", UploadView)
self.router.add_view("/drive.bin/{uid}.{ext}", UploadView)
self.router.add_view("/search-user.html", SearchUserView)
self.router.add_view("/search-user.json", SearchUserView)
self.router.add_view("/avatar/{uid}.svg", AvatarView)
self.router.add_get("/http-get", self.handle_http_get)
self.router.add_get("/http-photo", self.handle_http_photo)
self.router.add_get("/rpc.ws", RPCView)
self.router.add_view("/channel/{channel}.html", WebView)
self.router.add_view("/threads.html", ThreadsView)
self.router.add_view("/terminal.ws", TerminalSocketView)
self.router.add_view("/terminal.html", TerminalView)
self.router.add_view("/drive.json", DriveView)
self.router.add_view("/drive/{drive}.json", DriveView)
self.router.add_view("/stats.json", StatsView)
self.router.add_view("/user/{user}.html", UserView)
self.router.add_view("/repository/{username}/{repo_name}", RepositoryView)
self.router.add_view("/repository/{username}/{repo_name}/{rel_path:.*}", RepositoryView)
self.router.add_view("/settings/repositories/index.html", RepositoriesIndexView)
self.router.add_view("/settings/repositories/create.html", RepositoriesCreateView)
self.router.add_view("/settings/repositories/repository/{name}/update.html", RepositoriesUpdateView)
self.router.add_view("/settings/repositories/repository/{name}/delete.html", RepositoriesDeleteView)
self.webdav = WebdavApplication(self)
self.git = GitApplication(self)
self.add_subapp("/webdav", self.webdav)
self.add_subapp("/git",self.git)
#self.router.add_get("/{file_path:.*}", self.static_handler)
async def handle_test(self, request):
return await self.render_template(
"test.html", request, context={"name": "retoor"}
)
async def handle_http_get(self, request: web.Request):
url = request.query.get("url")
content = await http.get(url)
return web.Response(body=content)
async def handle_http_photo(self, request):
url = request.query.get("url")
path = await http.create_site_photo(url)
return web.Response(
body=path.read_bytes(), headers={"Content-Type": "image/png"}
)
# @time_cache_async(60)
async def render_template(self, template, request, context=None):
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(
user_uid=request.session.get("uid"), deleted_at=None, is_banned=False
):
item = {}
other_user = await self.services.channel_member.get_other_dm_user(
subscribed_channel["channel_uid"], request.session.get("uid")
)
parent_object = await subscribed_channel.get_channel()
last_message = await parent_object.get_last_message()
color = None
if last_message:
last_message_user = await last_message.get_user()
color = last_message_user["color"]
item["color"] = color
item["last_message_on"] = parent_object["last_message_on"]
item["is_private"] = parent_object["tag"] == "dm"
if other_user:
item["name"] = other_user["nick"]
item["uid"] = subscribed_channel["channel_uid"]
else:
item["name"] = subscribed_channel["label"]
item["uid"] = subscribed_channel["channel_uid"]
item["new_count"] = subscribed_channel["new_count"]
channels.append(item)
channels.sort(key=lambda x: x["last_message_on"] or "", reverse=True)
if "channels" not in context:
context["channels"] = channels
if "user" not in context:
context["user"] = await self.services.user.get(
request.session.get("uid")
)
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 static_handler(self, request):
file_name = request.match_info.get('filename', '')
paths = []
uid = request.session.get("uid")
if uid:
user_static_path = await self.services.user.get_static_path(uid)
if user_static_path:
paths.append(user_static_path)
for admin_uid in self.services.user.get_admin_uids():
user_static_path = await self.services.user.get_static_path(admin_uid)
if user_static_path:
paths.append(user_static_path)
paths.append(self.static_path)
for path in paths:
if pathlib.Path(path).joinpath(file_name).exists():
return web.FileResponse(pathlib.Path(path).joinpath(file_name))
return web.HTTPNotFound() return web.HTTPNotFound()
async def get_user_template_loader(B,uid=_D):
async def get_user_template_loader(self, uid=None): C=[]
template_paths = [] for D in B.services.user.get_admin_uids():
for admin_uid in self.services.user.get_admin_uids(): A=await B.services.user.get_template_path(D)
user_template_path = await self.services.user.get_template_path(admin_uid) if A:C.append(A)
if user_template_path:
template_paths.append(user_template_path)
if uid: if uid:
user_template_path = await self.services.user.get_template_path(uid) A=await B.services.user.get_template_path(uid)
if user_template_path: if A:C.append(A)
template_paths.append(user_template_path) C.append(B.template_path);return FileSystemLoader(C)
app=Application(db_path='sqlite:///snek.db')
async def main():await web._run_app(app,port=8081,host='0.0.0.0')
template_paths.append(self.template_path) if __name__=='__main__':asyncio.run(main())
return FileSystemLoader(template_paths)
app = Application(db_path="sqlite:///snek.db")
async def main():
await web._run_app(app, port=8081, host="0.0.0.0")
if __name__ == "__main__":
asyncio.run(main())

View File

@ -1,43 +1,14 @@
import pathlib import pathlib
from aiohttp import web from aiohttp import web
from app.app import Application as BaseApplication from app.app import Application as BaseApplication
from snek.system.markdown import MarkdownExtension from snek.system.markdown import MarkdownExtension
class Application(BaseApplication): class Application(BaseApplication):
def __init__(A,path=None,*B,**C):A.path=pathlib.Path(path);D=A.path;super().__init__(*B,template_path=D,**C);A.jinja2_env.add_extension(MarkdownExtension);A.router.add_get('/{tail:.*}',A.handle_document)
def __init__(self, path=None, *args, **kwargs): async def handle_document(B,request):
self.path = pathlib.Path(path) D='text/plain';E=b'Resource is not found on this server.';F='index.html';G=request;C=G.match_info['tail'].strip('/')
template_path = self.path if C=='':C=F
A=B.path.joinpath(C)
super().__init__(template_path=template_path, *args, **kwargs) if not A.exists():return web.Response(status=404,body=E,content_type=D)
self.jinja2_env.add_extension(MarkdownExtension) if A.is_dir():A=A.joinpath(F)
if not A.exists():return web.Response(status=404,body=E,content_type=D)
self.router.add_get("/{tail:.*}", self.handle_document) H=await B.render_template(str(A.relative_to(B.path)),G);return H
async def handle_document(self, request):
relative_path = request.match_info["tail"].strip("/")
if relative_path == "":
relative_path = "index.html"
document_path = self.path.joinpath(relative_path)
if not document_path.exists():
return web.Response(
status=404,
body=b"Resource is not found on this server.",
content_type="text/plain",
)
if document_path.is_dir():
document_path = document_path.joinpath("index.html")
if not document_path.exists():
return web.Response(
status=404,
body=b"Resource is not found on this server.",
content_type="text/plain",
)
response = await self.render_template(
str(document_path.relative_to(self.path)), request
)
return response

View File

@ -1,39 +1,12 @@
_B='created_at'
_A='uid'
import asyncio import asyncio
from snek.app import app from snek.app import app
async def fix_message(message):C='user';D='text';B='user_uid';A=message;A={_A:A[_A],B:A[B],D:A['message'],'sent':A[_B]};E=await app.services.user.get(uid=A[B]);A[C]=E and E['username']or None;return(A[C]or'')+': '+(A[D]or'')
async def fix_message(message):
message = {
"uid": message["uid"],
"user_uid": message["user_uid"],
"text": message["message"],
"sent": message["created_at"],
}
user = await app.services.user.get(uid=message["user_uid"])
message["user"] = user and user["username"] or None
return (message["user"] or "") + ": " + (message["text"] or "")
async def dump_public_channels(): async def dump_public_channels():
result = [] A=[]
for channel in app.db["channel"].find( for B in app.db['channel'].find(is_private=False,is_listed=True,tag='public'):print(f"Dumping channel: {B["label"]}.");A+=[await fix_message(A)for A in app.db['channel_message'].find(channel_uid=B[_A],order_by=_B)];print('Dump succesfull!')
is_private=False, is_listed=True, tag="public" print('Converting to json.');print('Converting succesful, now writing to dump.json')
): with open('dump.txt','w')as C:C.write('\n\n'.join(A))
print(f"Dumping channel: {channel['label']}.") print('Dump written to dump.json')
result += [ if __name__=='__main__':asyncio.run(dump_public_channels())
await fix_message(record)
for record in app.db["channel_message"].find(
channel_uid=channel["uid"], order_by="created_at"
)
]
print("Dump succesfull!")
print("Converting to json.")
print("Converting succesful, now writing to dump.json")
with open("dump.txt", "w") as f:
f.write("\n\n".join(result))
print("Dump written to dump.json")
if __name__ == "__main__":
asyncio.run(dump_public_channels())

View File

@ -1,51 +1,14 @@
from snek.system.form import Form, FormButtonElement, FormInputElement, HTMLElement _B='username'
_A='password'
from snek.system.form import Form,FormButtonElement,FormInputElement,HTMLElement
class AuthField(FormInputElement): class AuthField(FormInputElement):
@property @property
async def errors(self): async def errors(self):
result = await super().errors A=self;B=await super().errors
if self.model.password.value and self.model.username.value: if A.model.password.value and A.model.username.value:
if not await self.app.services.user.validate_login( if not await A.app.services.user.validate_login(A.model.username.value,A.model.password.value):return['Invalid username or password']
self.model.username.value, self.model.password.value return B
):
return ["Invalid username or password"]
return result
class LoginForm(Form): class LoginForm(Form):
title=HTMLElement(tag='h1',text='Login');username=AuthField(name=_B,required=True,min_length=2,max_length=20,regex='^[a-zA-Z0-9_-]+$',place_holder='Username',type='text');password=AuthField(name=_A,required=True,min_length=1,type=_A,place_holder='Password');action=FormButtonElement(name='action',value='submit',text='Login',type='button')
title = HTMLElement(tag="h1", text="Login")
username = AuthField(
name="username",
required=True,
min_length=2,
max_length=20,
regex=r"^[a-zA-Z0-9_-]+$",
place_holder="Username",
type="text",
)
password = AuthField(
name="password",
required=True,
min_length=1,
type="password",
place_holder="Password",
)
action = FormButtonElement(
name="action", value="submit", text="Login", type="button"
)
@property @property
async def is_valid(self): async def is_valid(self):A=self;return all([A[_B],A[_A],not await A.username.errors,not await A.password.errors])
return all(
[
self["username"],
self["password"],
not await self.username.errors,
not await self.password.errors,
]
)

View File

@ -1,44 +1,10 @@
from snek.system.form import Form, FormButtonElement, FormInputElement, HTMLElement _B='password'
_A='Register'
from snek.system.form import Form,FormButtonElement,FormInputElement,HTMLElement
class UsernameField(FormInputElement): class UsernameField(FormInputElement):
@property @property
async def errors(self): async def errors(self):
result = await super().errors A=self;B=await super().errors
if self.value and await self.app.services.user.count(username=self.value): if A.value and await A.app.services.user.count(username=A.value):B.append('Username is not available.')
result.append("Username is not available.") return B
return result class RegisterForm(Form):title=HTMLElement(tag='h1',text=_A);username=UsernameField(name='username',required=True,min_length=2,max_length=20,regex='^[a-zA-Z0-9_-]+$',place_holder='Username',type='text');email=FormInputElement(name='email',required=False,regex='^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$',place_holder='Email address',type='email');password=FormInputElement(name=_B,required=True,min_length=1,type=_B,place_holder='Password');action=FormButtonElement(name='action',value='submit',text=_A,type='button')
class RegisterForm(Form):
title = HTMLElement(tag="h1", text="Register")
username = UsernameField(
name="username",
required=True,
min_length=2,
max_length=20,
regex=r"^[a-zA-Z0-9_-]+$",
place_holder="Username",
type="text",
)
email = FormInputElement(
name="email",
required=False,
regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",
place_holder="Email address",
type="email",
)
password = FormInputElement(
name="password",
required=True,
min_length=1,
type="password",
place_holder="Password",
)
action = FormButtonElement(
name="action", value="submit", text="Register", type="button"
)

View File

@ -1,18 +1,2 @@
from snek.system.form import Form, FormButtonElement, FormInputElement, HTMLElement from snek.system.form import Form,FormButtonElement,FormInputElement,HTMLElement
class SearchUserForm(Form):title=HTMLElement(tag='h1',text='Search user');username=FormInputElement(name='username',required=True,min_length=1,max_length=128,place_holder='Username');action=FormButtonElement(name='action',value='submit',text='Search',type='button')
class SearchUserForm(Form):
title = HTMLElement(tag="h1", text="Search user")
username = FormInputElement(
name="username",
required=True,
min_length=1,
max_length=128,
place_holder="Username",
)
action = FormButtonElement(
name="action", value="submit", text="Search", type="button"
)

View File

@ -1,25 +1,5 @@
from snek.system.form import Form, FormButtonElement, FormInputElement, HTMLElement _C='button'
_B='submit'
_A='action'
class SettingsProfileForm(Form): from snek.system.form import Form,FormButtonElement,FormInputElement,HTMLElement
class SettingsProfileForm(Form):nick=FormInputElement(name='nick',required=True,place_holder='Your Nickname',min_length=1,max_length=20);action=FormButtonElement(name=_A,value=_B,text='Save',type=_C);title=HTMLElement(tag='h1',text='Profile');profile=FormInputElement(name='profile',place_holder='Tell about yourself.',required=False,max_length=300);action=FormButtonElement(name=_A,value=_B,text='Save',type=_C)
nick = FormInputElement(
name="nick",
required=True,
place_holder="Your Nickname",
min_length=1,
max_length=20,
)
action = FormButtonElement(
name="action", value="submit", text="Save", type="button"
)
title = HTMLElement(tag="h1", text="Profile")
profile = FormInputElement(
name="profile",
place_holder="Tell about yourself.",
required=False,
max_length=300,
)
action = FormButtonElement(
name="action", value="submit", text="Save", type="button"
)

View File

@ -1,3 +1,2 @@
from snek.app import app from snek.app import app
application=app
application = app

View File

@ -1,5 +1,4 @@
import functools import functools
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
from snek.mapper.channel_message import ChannelMessageMapper from snek.mapper.channel_message import ChannelMessageMapper
@ -10,24 +9,6 @@ from snek.mapper.user import UserMapper
from snek.mapper.user_property import UserPropertyMapper from snek.mapper.user_property import UserPropertyMapper
from snek.mapper.repository import RepositoryMapper from snek.mapper.repository import RepositoryMapper
from snek.system.object import Object from snek.system.object import Object
@functools.cache @functools.cache
def get_mappers(app=None): def get_mappers(app=None):A=app;return Object(**{'user':UserMapper(app=A),'channel_member':ChannelMemberMapper(app=A),'channel':ChannelMapper(app=A),'channel_message':ChannelMessageMapper(app=A),'notification':NotificationMapper(app=A),'drive_item':DriveItemMapper(app=A),'drive':DriveMapper(app=A),'user_property':UserPropertyMapper(app=A),'repository':RepositoryMapper(app=A)})
return Object( def get_mapper(name,app=None):return get_mappers(app=app)[name]
**{
"user": UserMapper(app=app),
"channel_member": ChannelMemberMapper(app=app),
"channel": ChannelMapper(app=app),
"channel_message": ChannelMessageMapper(app=app),
"notification": NotificationMapper(app=app),
"drive_item": DriveItemMapper(app=app),
"drive": DriveMapper(app=app),
"user_property": UserPropertyMapper(app=app),
"repository": RepositoryMapper(app=app),
}
)
def get_mapper(name, app=None):
return get_mappers(app=app)[name]

View File

@ -1,7 +1,3 @@
from snek.model.channel import ChannelModel from snek.model.channel import ChannelModel
from snek.system.mapper import BaseMapper from snek.system.mapper import BaseMapper
class ChannelMapper(BaseMapper):table_name='channel';model_class=ChannelModel
class ChannelMapper(BaseMapper):
table_name = "channel"
model_class = ChannelModel

View File

@ -1,7 +1,3 @@
from snek.model.channel_member import ChannelMemberModel from snek.model.channel_member import ChannelMemberModel
from snek.system.mapper import BaseMapper from snek.system.mapper import BaseMapper
class ChannelMemberMapper(BaseMapper):table_name='channel_member';model_class=ChannelMemberModel
class ChannelMemberMapper(BaseMapper):
table_name = "channel_member"
model_class = ChannelMemberModel

View File

@ -1,7 +1,3 @@
from snek.model.channel_message import ChannelMessageModel from snek.model.channel_message import ChannelMessageModel
from snek.system.mapper import BaseMapper from snek.system.mapper import BaseMapper
class ChannelMessageMapper(BaseMapper):model_class=ChannelMessageModel;table_name='channel_message'
class ChannelMessageMapper(BaseMapper):
model_class = ChannelMessageModel
table_name = "channel_message"

View File

@ -1,7 +1,3 @@
from snek.model.drive import DriveModel from snek.model.drive import DriveModel
from snek.system.mapper import BaseMapper from snek.system.mapper import BaseMapper
class DriveMapper(BaseMapper):table_name='drive';model_class=DriveModel
class DriveMapper(BaseMapper):
table_name = "drive"
model_class = DriveModel

View File

@ -1,8 +1,3 @@
from snek.model.drive_item import DriveItemModel from snek.model.drive_item import DriveItemModel
from snek.system.mapper import BaseMapper from snek.system.mapper import BaseMapper
class DriveItemMapper(BaseMapper):model_class=DriveItemModel;table_name='drive_item'
class DriveItemMapper(BaseMapper):
model_class = DriveItemModel
table_name = "drive_item"

View File

@ -1,7 +1,3 @@
from snek.model.notification import NotificationModel from snek.model.notification import NotificationModel
from snek.system.mapper import BaseMapper from snek.system.mapper import BaseMapper
class NotificationMapper(BaseMapper):table_name='notification';model_class=NotificationModel
class NotificationMapper(BaseMapper):
table_name = "notification"
model_class = NotificationModel

View File

@ -1,7 +1,3 @@
from snek.model.repository import RepositoryModel from snek.model.repository import RepositoryModel
from snek.system.mapper import BaseMapper from snek.system.mapper import BaseMapper
class RepositoryMapper(BaseMapper):model_class=RepositoryModel;table_name='repository'
class RepositoryMapper(BaseMapper):
model_class = RepositoryModel
table_name = "repository"

View File

@ -1,20 +1,7 @@
from snek.model.user import UserModel from snek.model.user import UserModel
from snek.system.mapper import BaseMapper from snek.system.mapper import BaseMapper
class UserMapper(BaseMapper): class UserMapper(BaseMapper):
table_name = "user" table_name='user';model_class=UserModel
model_class = UserModel def get_admin_uids(A):
try:return[A['uid']for A in A.db.query('SELECT uid FROM user WHERE is_admin = :is_admin',{'is_admin':True})]
def get_admin_uids(self): except Exception as B:print(B);return[]
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 []

View File

@ -1,7 +1,3 @@
from snek.model.user_property import UserPropertyModel from snek.model.user_property import UserPropertyModel
from snek.system.mapper import BaseMapper from snek.system.mapper import BaseMapper
class UserPropertyMapper(BaseMapper):table_name='user_property';model_class=UserPropertyModel
class UserPropertyMapper(BaseMapper):
table_name = "user_property"
model_class = UserPropertyModel

View File

@ -1,9 +1,6 @@
import functools 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.drive import DriveModel from snek.model.drive import DriveModel
from snek.model.drive_item import DriveItemModel from snek.model.drive_item import DriveItemModel
@ -12,24 +9,6 @@ from snek.model.user import UserModel
from snek.model.user_property import UserPropertyModel from snek.model.user_property import UserPropertyModel
from snek.model.repository import RepositoryModel from snek.model.repository import RepositoryModel
from snek.system.object import Object from snek.system.object import Object
@functools.cache @functools.cache
def get_models(): def get_models():return Object(**{'user':UserModel,'channel_member':ChannelMemberModel,'channel':ChannelModel,'channel_message':ChannelMessageModel,'drive_item':DriveItemModel,'drive':DriveModel,'notification':NotificationModel,'user_property':UserPropertyModel,'repository':RepositoryModel})
return Object( def get_model(name):return get_models()[name]
**{
"user": UserModel,
"channel_member": ChannelMemberModel,
"channel": ChannelModel,
"channel_message": ChannelMessageModel,
"drive_item": DriveItemModel,
"drive": DriveModel,
"notification": NotificationModel,
"user_property": UserPropertyModel,
"repository": RepositoryModel,
}
)
def get_model(name):
return get_models()[name]

View File

@ -1,30 +1,12 @@
_C='uid'
_B=False
_A=True
from snek.model.channel_message import ChannelMessageModel from snek.model.channel_message import ChannelMessageModel
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=_A,kind=str);description=ModelField(name='description',required=_B,kind=str);tag=ModelField(name='tag',required=_B,kind=str);created_by_uid=ModelField(name='created_by_uid',required=_A,kind=str);is_private=ModelField(name='is_private',required=_A,kind=bool,value=_B);is_listed=ModelField(name='is_listed',required=_A,kind=bool,value=_A);index=ModelField(name='index',required=_A,kind=int,value=1000);last_message_on=ModelField(name='last_message_on',required=_B,kind=str)
description = ModelField(name="description", required=False, kind=str) async def get_last_message(A):
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)
last_message_on = ModelField(name="last_message_on", required=False, kind=str)
async def get_last_message(self) -> ChannelMessageModel:
try: try:
async for model in self.app.services.channel_message.query( async for B in A.app.services.channel_message.query('SELECT uid FROM channel_message WHERE channel_uid=:channel_uid ORDER BY created_at DESC LIMIT 1',{'channel_uid':A[_C]}):return await A.app.services.channel_message.get(uid=B[_C])
"SELECT uid FROM channel_message WHERE channel_uid=:channel_uid ORDER BY created_at DESC LIMIT 1", except:pass
{"channel_uid": self["uid"]}, async def get_members(A):return await A.app.services.channel_member.find(channel_uid=A[_C],deleted_at=None,is_banned=_B)
):
return await self.app.services.channel_message.get(uid=model["uid"])
except:
pass
return None
async def get_members(self):
return await self.app.services.channel_member.find(
channel_uid=self["uid"], deleted_at=None, is_banned=False
)

View File

@ -1,41 +1,19 @@
from snek.system.model import BaseModel, ModelField _D='channel_uid'
_C='user_uid'
_B=False
_A=True
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=_A,kind=str);channel_uid=ModelField(name=_D,required=_A,kind=str);user_uid=ModelField(name=_C,required=_A,kind=str);is_moderator=ModelField(name='is_moderator',required=_A,kind=bool,value=_B);is_read_only=ModelField(name='is_read_only',required=_A,kind=bool,value=_B);is_muted=ModelField(name='is_muted',required=_A,kind=bool,value=_B);is_banned=ModelField(name='is_banned',required=_A,kind=bool,value=_B);new_count=ModelField(name='new_count',required=_B,kind=int,value=0)
channel_uid = ModelField(name="channel_uid", required=True, kind=str) async def get_user(A):return await A.app.services.user.get(uid=A[_C])
user_uid = ModelField(name="user_uid", required=True, kind=str) async def get_channel(A):return await A.app.services.channel.get(uid=A[_D])
is_moderator = ModelField( async def get_name(A):
name="is_moderator", required=True, kind=bool, value=False B=await A.get_channel()
) if B['tag']=='dm':C=await A.get_other_dm_user();return C['nick']
is_read_only = ModelField( return B['name']or A['label']
name="is_read_only", required=True, kind=bool, value=False async def get_other_dm_user(A):
) B='uid';C=await A.get_channel()
is_muted = ModelField(name="is_muted", required=True, kind=bool, value=False) if C['tag']!='dm':return
is_banned = ModelField(name="is_banned", required=True, kind=bool, value=False) async for D in A.app.services.channel_member.find(channel_uid=C[B]):
new_count = ModelField(name="new_count", required=False, kind=int, value=0) if D[B]!=A[B]:return await A.app.services.user.get(uid=D[_C])
return await A.get_user()
async def get_user(self):
return await self.app.services.user.get(uid=self["user_uid"])
async def get_channel(self):
return await self.app.services.channel.get(uid=self["channel_uid"])
async def get_name(self):
channel = await self.get_channel()
if channel["tag"] == "dm":
user = await self.get_other_dm_user()
return user["nick"]
return channel["name"] or self["label"]
async def get_other_dm_user(self):
channel = await self.get_channel()
if channel["tag"] != "dm":
return None
async for model in self.app.services.channel_member.find(
channel_uid=channel["uid"]
):
if model["uid"] != self["uid"]:
return await self.app.services.user.get(uid=model["user_uid"])
return await self.get_user()

View File

@ -1,15 +1,8 @@
_B='user_uid'
_A='channel_uid'
from snek.model.user import UserModel from snek.model.user import UserModel
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=_A,required=True,kind=str);user_uid=ModelField(name=_B,required=True,kind=str);message=ModelField(name='message',required=True,kind=str);html=ModelField(name='html',required=False,kind=str)
user_uid = ModelField(name="user_uid", required=True, kind=str) async def get_user(A):return await A.app.services.user.get(uid=A[_B])
message = ModelField(name="message", required=True, kind=str) async def get_channel(A):return await A.app.services.channel.get(uid=A[_A])
html = ModelField(name="html", required=False, kind=str)
async def get_user(self) -> UserModel:
return await self.app.services.user.get(uid=self["user_uid"])
async def get_channel(self):
return await self.app.services.channel.get(uid=self["channel_uid"])

View File

@ -1,14 +1,6 @@
from snek.system.model import BaseModel, ModelField from snek.system.model import BaseModel,ModelField
class DriveModel(BaseModel): class DriveModel(BaseModel):
user_uid=ModelField(name='user_uid',required=True);name=ModelField(name='name',required=False,type=str)
user_uid = ModelField(name="user_uid", required=True)
name = ModelField(name="name", required=False, type=str)
@property @property
async def items(self): async def items(self):
async for drive_item in self.app.services.drive_item.find( async for A in self.app.services.drive_item.find(drive_uid=self['uid']):yield A
drive_uid=self["uid"]
):
yield drive_item

View File

@ -1,21 +1,10 @@
_B='name'
_A=True
import mimetypes import mimetypes
from snek.system.model import BaseModel,ModelField
from snek.system.model import BaseModel, ModelField
class DriveItemModel(BaseModel): class DriveItemModel(BaseModel):
drive_uid = ModelField(name="drive_uid", required=True, kind=str) drive_uid=ModelField(name='drive_uid',required=_A,kind=str);name=ModelField(name=_B,required=_A,kind=str);path=ModelField(name='path',required=_A,kind=str);file_type=ModelField(name='file_type',required=_A,kind=str);file_size=ModelField(name='file_size',required=_A,kind=int);is_available=ModelField(name='is_available',required=_A,kind=bool,initial_value=_A)
name = ModelField(name="name", required=True, kind=str)
path = ModelField(name="path", required=True, kind=str)
file_type = ModelField(name="file_type", required=True, kind=str)
file_size = ModelField(name="file_size", required=True, kind=int)
is_available = ModelField(name="is_available", required=True, kind=bool, initial_value=True)
@property @property
def extension(self): def extension(self):return self[_B].split('.')[-1]
return self["name"].split(".")[-1]
@property @property
def mime_type(self): def mime_type(self):A,B=mimetypes.guess_type(self[_B]);return A
mimetype, _ = mimetypes.guess_type(self["name"])
return mimetype

View File

@ -1,9 +1,3 @@
from snek.system.model import BaseModel, ModelField _A=True
from snek.system.model import BaseModel,ModelField
class NotificationModel(BaseModel):object_uid=ModelField(name='object_uid',required=_A);object_type=ModelField(name='object_type',required=_A);message=ModelField(name='message',required=_A);user_uid=ModelField(name='user_uid',required=_A);read_at=ModelField(name='is_read',required=_A)
class NotificationModel(BaseModel):
object_uid = ModelField(name="object_uid", required=True)
object_type = ModelField(name="object_type", required=True)
message = ModelField(name="message", required=True)
user_uid = ModelField(name="user_uid", required=True)
read_at = ModelField(name="is_read", required=True)

View File

@ -1,14 +1,3 @@
from snek.model.user import UserModel from snek.model.user import UserModel
from snek.system.model import BaseModel, ModelField from snek.system.model import BaseModel,ModelField
class RepositoryModel(BaseModel):user_uid=ModelField(name='user_uid',required=True,kind=str);name=ModelField(name='name',required=True,kind=str);is_private=ModelField(name='is_private',required=False,kind=bool)
class RepositoryModel(BaseModel):
user_uid = ModelField(name="user_uid", required=True, kind=str)
name = ModelField(name="name", required=True, kind=str)
is_private = ModelField(name="is_private", required=False, kind=bool)

View File

@ -1,60 +1,17 @@
from snek.system.model import BaseModel, ModelField _D='^[a-zA-Z0-9_-+/]+$'
_C=False
_B=True
_A='uid'
from snek.system.model import BaseModel,ModelField
class UserModel(BaseModel): class UserModel(BaseModel):
username=ModelField(name='username',required=_B,min_length=2,max_length=20,regex=_D);nick=ModelField(name='nick',required=_B,min_length=2,max_length=20,regex=_D);color=ModelField(name='color',required=_B,regex='^#[0-9a-fA-F]{6}$',kind=str);email=ModelField(name='email',required=_C,regex='^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$');password=ModelField(name='password',required=_B,min_length=1);last_ping=ModelField(name='last_ping',required=_C,kind=str);is_admin=ModelField(name='is_admin',required=_C,kind=bool)
username = ModelField( async def get_property(A,name):
name="username", B=await A.app.services.user_property.find_one(user_uid=A[_A],name=name)
required=True, if B:return B['value']
min_length=2, async def has_property(A,name):return await A.app.services.user_property.exists(user_uid=A[_A],name=name)
max_length=20, async def set_property(A,name,value):
regex=r"^[a-zA-Z0-9_-+/]+$", C=value;B=name
) if not await A.has_property(B):await A.app.services.user_property.insert(user_uid=A[_A],name=B,value=C)
nick = ModelField( else:await A.app.services.user_property.update(user_uid=A[_A],name=B,value=C)
name="nick", async def get_channel_members(A):
required=True, async for B in A.app.services.channel_member.find(user_uid=A[_A],is_banned=_C,deleted_at=None):yield B
min_length=2,
max_length=20,
regex=r"^[a-zA-Z0-9_-+/]+$",
)
color = ModelField(
name="color", required=True, regex=r"^#[0-9a-fA-F]{6}$", kind=str
)
email = ModelField(
name="email",
required=False,
regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",
)
password = ModelField(name="password", required=True, min_length=1)
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
)
if prop:
return prop["value"]
async def has_property(self, name):
return await self.app.services.user_property.exists(
user_uid=self["uid"], name=name
)
async def set_property(self, name, value):
if not await self.has_property(name):
await self.app.services.user_property.insert(
user_uid=self["uid"], name=name, value=value
)
else:
await self.app.services.user_property.update(
user_uid=self["uid"], name=name, value=value
)
async def get_channel_members(self):
async for channel_member in self.app.services.channel_member.find(
user_uid=self["uid"], is_banned=False, deleted_at=None
):
yield channel_member

View File

@ -1,7 +1,2 @@
from snek.system.model import BaseModel, ModelField from snek.system.model import BaseModel,ModelField
class UserPropertyModel(BaseModel):user_uid=ModelField(name='user_uid',required=True,kind=str);name=ModelField(name='name',required=True,kind=str);value=ModelField(name='path',required=True,kind=str)
class UserPropertyModel(BaseModel):
user_uid = ModelField(name="user_uid", required=True, kind=str)
name = ModelField(name="name", required=True, kind=str)
value = ModelField(name="path", required=True, kind=str)

View File

@ -1,5 +1,4 @@
import functools import functools
from snek.service.channel import ChannelService from snek.service.channel import ChannelService
from snek.service.channel_member import ChannelMemberService from snek.service.channel_member import ChannelMemberService
from snek.service.channel_message import ChannelMessageService from snek.service.channel_message import ChannelMessageService
@ -13,27 +12,6 @@ from snek.service.user_property import UserPropertyService
from snek.service.util import UtilService from snek.service.util import UtilService
from snek.service.repository import RepositoryService from snek.service.repository import RepositoryService
from snek.system.object import Object from snek.system.object import Object
@functools.cache @functools.cache
def get_services(app): def get_services(app):A=app;return Object(**{'user':UserService(app=A),'channel_member':ChannelMemberService(app=A),'channel':ChannelService(app=A),'channel_message':ChannelMessageService(app=A),'chat':ChatService(app=A),'socket':SocketService(app=A),'notification':NotificationService(app=A),'util':UtilService(app=A),'drive':DriveService(app=A),'drive_item':DriveItemService(app=A),'user_property':UserPropertyService(app=A),'repository':RepositoryService(app=A)})
return Object( def get_service(name,app=None):return get_services(app=app)[name]
**{
"user": UserService(app=app),
"channel_member": ChannelMemberService(app=app),
"channel": ChannelService(app=app),
"channel_message": ChannelMessageService(app=app),
"chat": ChatService(app=app),
"socket": SocketService(app=app),
"notification": NotificationService(app=app),
"util": UtilService(app=app),
"drive": DriveService(app=app),
"drive_item": DriveItemService(app=app),
"user_property": UserPropertyService(app=app),
"repository": RepositoryService(app=app),
}
)
def get_service(name, app=None):
return get_services(app=app)[name]

View File

@ -1,108 +1,49 @@
_F='channel_uid'
_E='public'
_D=True
_C='uid'
_B=None
_A=False
from datetime import datetime from datetime import datetime
from snek.system.model import now from snek.system.model import now
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 get(E,uid=_B,**A):
async def get(self, uid=None, **kwargs): D='name';C=uid
if uid: if C:
kwargs["uid"] = uid A[_C]=C;B=await super().get(**A)
result = await super().get(**kwargs) if B:return B
if result: del A[_C];A[D]=C;B=await super().get(**A)
return result if B:return B
del kwargs["uid"] A[D]='#'+C;B=await super().get(**A)
kwargs["name"] = uid if B:return B
result = await super().get(**kwargs) return
if result: return await super().get(**A)
return result async def create(C,label,created_by_uid,description=_B,tag=_B,is_private=_A,is_listed=_D):
kwargs["name"] = "#" + uid E=is_listed;D=tag;B=label
result = await super().get(**kwargs) if B[0]!='#'and E:B=f"#{B}"
if result: F=await C.count(deleted_at=_B)
return result if not D and not F:D=_E
return None A=await C.new();A['label']=B;A['description']=description;A['tag']=D;A['created_by_uid']=created_by_uid;A['is_private']=is_private;A['is_listed']=E
return await super().get(**kwargs) if await C.save(A):return A
raise Exception(f"Failed to create channel: {A.errors}.")
async def create( async def get_dm(A,user1,user2):
self, C=user2;B=user1;D=await A.services.channel_member.get_dm(B,C)
label, if D:return await A.get(uid=D[_F])
created_by_uid, E=await A.create('DM',B,tag='dm');await A.services.channel_member.create_dm(E[_C],B,C);return E
description=None, async def get_users(A,channel_uid):
tag=None, async for C in A.services.channel_member.find(channel_uid=channel_uid,is_banned=_A,is_muted=_A,deleted_at=_B):
is_private=False, B=await A.services.user.get(uid=C['user_uid'])
is_listed=True, if B:yield B
): async def get_online_users(C,channel_uid):
if label[0] != "#" and is_listed: B='last_ping'
label = f"#{label}" async for A in C.get_users(channel_uid):
count = await self.count(deleted_at=None) if not A[B]:continue
if not tag and not count: if(datetime.fromisoformat(now())-datetime.fromisoformat(A[B])).total_seconds()<20:yield A
tag = "public" async def get_for_user(A,user_uid):
model = await self.new() async for B in A.services.channel_member.find(user_uid=user_uid,is_banned=_A,deleted_at=_B):C=await A.get(uid=B[_F]);yield C
model["label"] = label async def ensure_public_channel(B,created_by_uid):
model["description"] = description C=created_by_uid;A=await B.get(is_listed=_D,tag=_E);D=_A
model["tag"] = tag if not A:D=_D;A=await B.create(_E,created_by_uid=C,is_listed=_D,tag=_E)
model["created_by_uid"] = created_by_uid await B.app.services.channel_member.create(A[_C],C,is_moderator=D,is_read_only=_A,is_muted=_A,is_banned=_A);return A
model["is_private"] = is_private
model["is_listed"] = is_listed
if await self.save(model):
return model
raise Exception(f"Failed to create channel: {model.errors}.")
async def get_dm(self, user1, user2):
channel_member = await self.services.channel_member.get_dm(user1, user2)
if channel_member:
return await self.get(uid=channel_member["channel_uid"])
channel = await self.create("DM", user1, tag="dm")
await self.services.channel_member.create_dm(channel["uid"], user1, user2)
return channel
async def get_users(self, channel_uid):
async for channel_member in self.services.channel_member.find(
channel_uid=channel_uid,
is_banned=False,
is_muted=False,
deleted_at=None,
):
user = await self.services.user.get(uid=channel_member["user_uid"])
if user:
yield user
async def get_online_users(self, channel_uid):
async for user in self.get_users(channel_uid):
if not user["last_ping"]:
continue
if (
datetime.fromisoformat(now())
- datetime.fromisoformat(user["last_ping"])
).total_seconds() < 20:
yield user
async def get_for_user(self, user_uid):
async for channel_member in self.services.channel_member.find(
user_uid=user_uid,
is_banned=False,
deleted_at=None,
):
channel = await self.get(uid=channel_member["channel_uid"])
yield channel
async def ensure_public_channel(self, created_by_uid):
model = await self.get(is_listed=True, tag="public")
is_moderator = False
if not model:
is_moderator = True
model = await self.create(
"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

View File

@ -1,74 +1,28 @@
_C='user_uid'
_B='channel_uid'
_A=False
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 mark_as_read(A,channel_uid,user_uid):B=await A.get(channel_uid=channel_uid,user_uid=user_uid);B['new_count']=0;return await A.save(B)
async def get_user_uids(A,channel_uid):
async def mark_as_read(self, channel_uid, user_uid): async for B in A.mapper.query('SELECT user_uid FROM channel_member WHERE channel_uid=:channel_uid',{_B:channel_uid}):yield B[_C]
channel_member = await self.get(channel_uid=channel_uid, user_uid=user_uid) async def create(B,channel_uid,user_uid,is_moderator=_A,is_read_only=_A,is_muted=_A,is_banned=_A):
channel_member["new_count"] = 0 D='label';E='is_banned';F=user_uid;C=channel_uid;A=await B.get(channel_uid=C,user_uid=F)
return await self.save(channel_member) if A:
if A[E]:return _A
async def get_user_uids(self, channel_uid): return A
async for model in self.mapper.query( A=await B.new();G=await B.services.channel.get(uid=C);A[D]=G[D];A[_B]=C;A[_C]=F;A['is_moderator']=is_moderator;A['is_read_only']=is_read_only;A['is_muted']=is_muted;A[E]=is_banned
"SELECT user_uid FROM channel_member WHERE channel_uid=:channel_uid", if await B.save(A):return A
{"channel_uid": channel_uid}, raise Exception(f"Failed to create channel member: {A.errors}.")
): async def get_dm(D,from_user,to_user):
yield model["user_uid"] E='to_user';F='from_user';A=to_user;B=from_user
async for C in D.query("SELECT channel_member.* FROM channel_member INNER JOIN channel ON (channel.uid = channel_member.channel_uid and channel.tag = 'dm') INNER JOIN channel_member AS channel_member2 ON(channel_member2.channel_uid = channel.uid AND channel_member2.user_uid = :to_user) WHERE channel_member.user_uid=:from_user ",{F:B,E:A}):return C
async def create( if not B==A:return
self, async for C in D.query("SELECT channel_member.* FROM channel_member INNER JOIN channel ON (channel.uid = channel_member.channel_uid and channel.tag = 'dm') LEFT JOIN channel_member AS channel_member2 ON(channel_member2.channel_uid = NULL AND channel_member2.user_uid = NULL) WHERE channel_member.user_uid=:from_user ",{F:B,E:A}):return C
channel_uid, async def get_other_dm_user(A,channel_uid,user_uid):
user_uid, B='uid';C=channel_uid;D=await A.get(channel_uid=C,user_uid=user_uid);F=await A.services.channel.get(uid=D[_B])
is_moderator=False, if F['tag']!='dm':return
is_read_only=False, async for E in A.services.channel_member.find(channel_uid=C):
is_muted=False, if E[B]!=D[B]:return await A.services.user.get(uid=E[_C])
is_banned=False, async def create_dm(A,channel_uid,from_user_uid,to_user_uid):B=channel_uid;C=await A.create(B,from_user_uid);await A.create(B,to_user_uid);return C
):
model = await self.get(channel_uid=channel_uid, user_uid=user_uid)
if model:
if model["is_banned"]:
return False
return model
model = await self.new()
channel = await self.services.channel.get(uid=channel_uid)
model["label"] = channel["label"]
model["channel_uid"] = channel_uid
model["user_uid"] = user_uid
model["is_moderator"] = is_moderator
model["is_read_only"] = is_read_only
model["is_muted"] = is_muted
model["is_banned"] = is_banned
if await self.save(model):
return model
raise Exception(f"Failed to create channel member: {model.errors}.")
async def get_dm(self, from_user, to_user):
async for model in self.query(
"SELECT channel_member.* FROM channel_member INNER JOIN channel ON (channel.uid = channel_member.channel_uid and channel.tag = 'dm') INNER JOIN channel_member AS channel_member2 ON(channel_member2.channel_uid = channel.uid AND channel_member2.user_uid = :to_user) WHERE channel_member.user_uid=:from_user ",
{"from_user": from_user, "to_user": to_user},
):
return model
if not from_user == to_user:
return None
async for model in self.query(
"SELECT channel_member.* FROM channel_member INNER JOIN channel ON (channel.uid = channel_member.channel_uid and channel.tag = 'dm') LEFT JOIN channel_member AS channel_member2 ON(channel_member2.channel_uid = NULL AND channel_member2.user_uid = NULL) WHERE channel_member.user_uid=:from_user ",
{"from_user": from_user, "to_user": to_user},
):
return model
async def get_other_dm_user(self, channel_uid, user_uid):
channel_member = await self.get(channel_uid=channel_uid, user_uid=user_uid)
channel = await self.services.channel.get(uid=channel_member["channel_uid"])
if channel["tag"] != "dm":
return None
async for model in self.services.channel_member.find(channel_uid=channel_uid):
if model["uid"] != channel_member["uid"]:
return await self.services.user.get(uid=model["user_uid"])
async def create_dm(self, channel_uid, from_user_uid, to_user_uid):
result = await self.create(channel_uid, from_user_uid)
await self.create(channel_uid, to_user_uid)
return result

View File

@ -1,93 +1,33 @@
_I='user_nick'
_H='created_at'
_G='html'
_F='uid'
_E='message'
_D='color'
_C='username'
_B='user_uid'
_A='channel_uid'
from snek.system.service import BaseService from snek.system.service import BaseService
class ChannelMessageService(BaseService): class ChannelMessageService(BaseService):
mapper_name = "channel_message" mapper_name='channel_message'
async def create(B,channel_uid,user_uid,message):
async def create(self, channel_uid, user_uid, message): E=user_uid;A=await B.new();A[_A]=channel_uid;A[_B]=E;A[_E]=message;D={};F=A.record;D.update(F);C=await B.app.services.user.get(uid=E);D.update({_B:C[_F],_C:C[_C],_I:C['nick'],_D:C[_D]})
model = await self.new() try:G=B.app.jinja2_env.get_template('message.html');A[_G]=G.render(**D)
except Exception as H:print(H,flush=True)
model["channel_uid"] = channel_uid if await B.save(A):return A
model["user_uid"] = user_uid raise Exception(f"Failed to create channel message: {A.errors}.")
model["message"] = message async def to_extended_dict(C,message):
A=message;B=await C.services.user.get(uid=A[_B])
context = {} if not B:return{}
return{_F:A[_F],_D:B[_D],_B:A[_B],_A:A[_A],_I:B['nick'],_E:A[_E],_H:A[_H],_G:A[_G],_C:B[_C]}
record = model.record async def offset(D,channel_uid,page=0,timestamp=None,page_size=30):
context.update(record) J='timestamp';E='offset';F='page_size';G=timestamp;H=channel_uid;C=page_size;A=[];I=page*C
user = await self.app.services.user.get(uid=user_uid)
context.update(
{
"user_uid": user["uid"],
"username": user["username"],
"user_nick": user["nick"],
"color": user["color"],
}
)
try: try:
template = self.app.jinja2_env.get_template("message.html") if G:
model["html"] = template.render(**context) async for B in D.query('SELECT * FROM channel_message WHERE channel_uid=:channel_uid AND created_at < :timestamp ORDER BY created_at DESC LIMIT :page_size OFFSET :offset',{_A:H,F:C,E:I,J:G}):A.append(B)
except Exception as ex: elif page>0:
print(ex, flush=True) async for B in D.query('SELECT * FROM channel_message WHERE channel_uid=:channel_uid WHERE created_at < :timestamp ORDER BY created_at DESC LIMIT :page_size',{_A:H,F:C,E:I,J:G}):A.append(B)
if await self.save(model):
return model
raise Exception(f"Failed to create channel message: {model.errors}.")
async def to_extended_dict(self, message):
user = await self.services.user.get(uid=message["user_uid"])
if not user:
return {}
return {
"uid": message["uid"],
"color": user["color"],
"user_uid": message["user_uid"],
"channel_uid": message["channel_uid"],
"user_nick": user["nick"],
"message": message["message"],
"created_at": message["created_at"],
"html": message["html"],
"username": user["username"],
}
async def offset(self, channel_uid, page=0, timestamp=None, page_size=30):
results = []
offset = page * page_size
try:
if timestamp:
async for model in self.query(
"SELECT * FROM channel_message WHERE channel_uid=:channel_uid AND created_at < :timestamp ORDER BY created_at DESC LIMIT :page_size OFFSET :offset",
{
"channel_uid": channel_uid,
"page_size": page_size,
"offset": offset,
"timestamp": timestamp,
},
):
results.append(model)
elif page > 0:
async for model in self.query(
"SELECT * FROM channel_message WHERE channel_uid=:channel_uid WHERE created_at < :timestamp ORDER BY created_at DESC LIMIT :page_size",
{
"channel_uid": channel_uid,
"page_size": page_size,
"offset": offset,
"timestamp": timestamp,
},
):
results.append(model)
else: else:
async for model in self.query( async for B in D.query('SELECT * FROM channel_message WHERE channel_uid=:channel_uid ORDER BY created_at DESC LIMIT :page_size OFFSET :offset',{_A:H,F:C,E:I}):A.append(B)
"SELECT * FROM channel_message WHERE channel_uid=:channel_uid ORDER BY created_at DESC LIMIT :page_size OFFSET :offset", except:pass
{ A.sort(key=lambda x:x[_H]);return A
"channel_uid": channel_uid,
"page_size": page_size,
"offset": offset,
},
):
results.append(model)
except:
pass
results.sort(key=lambda x: x["created_at"])
return results

View File

@ -1,39 +1,7 @@
from snek.system.model import now from snek.system.model import now
from snek.system.service import BaseService from snek.system.service import BaseService
class ChatService(BaseService): class ChatService(BaseService):
async def send(A,user_uid,channel_uid,message):
async def send(self, user_uid, channel_uid, message): H='username';I='created_at';J='color';K='html';L='message';D='uid';E=user_uid;C=channel_uid;F=await A.services.channel.get(uid=C)
channel = await self.services.channel.get(uid=channel_uid) if not F:raise Exception('Channel not found.')
if not channel: B=await A.services.channel_message.create(C,E,message);M=B[D];G=await A.services.user.get(uid=E);F['last_message_on']=now();await A.services.channel.save(F);await A.services.socket.broadcast(C,{L:B[L],K:B[K],'user_uid':E,J:G[J],'channel_uid':C,I:B[I],'updated_at':None,H:G[H],D:B[D],'user_nick':G['nick']});await A.app.create_task(A.services.notification.create_channel_message(M));return True
raise Exception("Channel not found.")
channel_message = await self.services.channel_message.create(
channel_uid, user_uid, message
)
channel_message_uid = channel_message["uid"]
user = await self.services.user.get(uid=user_uid)
channel["last_message_on"] = now()
await self.services.channel.save(channel)
await self.services.socket.broadcast(
channel_uid,
{
"message": channel_message["message"],
"html": channel_message["html"],
"user_uid": user_uid,
"color": user["color"],
"channel_uid": channel_uid,
"created_at": channel_message["created_at"],
"updated_at": None,
"username": user["username"],
"uid": channel_message["uid"],
"user_nick": user["nick"],
},
)
await self.app.create_task(
self.services.notification.create_channel_message(channel_message_uid)
)
return True

View File

@ -1,153 +1,41 @@
_H='Documents'
_G='Archives'
_F='Videos'
_E='Pictures'
_D='uid'
_C='user_uid'
_B='My Drive'
_A='name'
from snek.system.service import BaseService from snek.system.service import BaseService
class DriveService(BaseService): class DriveService(BaseService):
mapper_name='drive';EXTENSIONS_PICTURES=['jpg','jpeg','png','gif','svg','webp','tiff'];EXTENSIONS_VIDEOS=['mp4','m4v','mov','wmv','webm','mkv','mpg','mpeg','avi','ogv','ogg','flv','3gp','3g2'];EXTENSIONS_ARCHIVES=['zip','rar','7z','tar','tar.gz','tar.xz','tar.bz2','tar.lzma','tar.lz'];EXTENSIONS_AUDIO=['mp3','wav','ogg','flac','m4a','wma','aac','opus','aiff','au','mid','midi'];EXTENSIONS_DOCS=['pdf','doc','docx','xls','xlsx','ppt','pptx','txt','md','json','csv','xml','html','css','js','py','sql','rs','toml','yml','yaml','ini','conf','config','log','csv','tsv','java','cs','csproj','scss','less','sass','json','lock','lock.json','jsonl']
mapper_name = "drive" async def get_drive_name_by_extension(B,extension):
A=extension
EXTENSIONS_PICTURES = ["jpg", "jpeg", "png", "gif", "svg", "webp", "tiff"] if A.startswith('.'):A=A[1:]
EXTENSIONS_VIDEOS = [ if A in B.EXTENSIONS_PICTURES:return _E
"mp4", if A in B.EXTENSIONS_VIDEOS:return _F
"m4v", if A in B.EXTENSIONS_ARCHIVES:return _G
"mov", if A in B.EXTENSIONS_AUDIO:return'Audio'
"wmv", if A in B.EXTENSIONS_DOCS:return _H
"webm", return _B
"mkv", async def get_drive_by_extension(A,user_uid,extension):B=await A.get_drive_name_by_extension(extension);return await A.get_or_create(user_uid=user_uid,name=B)
"mpg", async def get_by_user(C,user_uid,name=None):
"mpeg", B=name;D={_C:user_uid}
"avi", async for A in C.find(**D):
"ogv", if not B:yield A
"ogg", elif A[_A]==B:yield A
"flv", elif not A[_A]and B==_B:A[_A]=_B;await C.save(A);yield A
"3gp", async def get_or_create(B,user_uid,name=None,extensions=None):
"3g2", D=user_uid;C=name;E={_C:D}
] if C:E[_A]=C
EXTENSIONS_ARCHIVES = [ async for A in B.get_by_user(**E):return A
"zip", A=await B.new();A[_C]=D;A[_A]=C;await B.save(A);return A
"rar", async def prepare_default_drives(B):
"7z", C='drive_uid'
"tar", async for A in B.services.drive_item.find():
"tar.gz", E=A.extension;D=await B.get_drive_by_extension(A[_C],E)
"tar.xz", if not A[C]==D[_D]:A[C]=D[_D];await B.services.drive_item.save(A)
"tar.bz2", async def prepare_default_drives_for_user(A,user_uid):B=user_uid;await A.get_or_create(user_uid=B,name=_B);await A.get_or_create(user_uid=B,name='Shared Drive');await A.get_or_create(user_uid=B,name=_E);await A.get_or_create(user_uid=B,name=_F);await A.get_or_create(user_uid=B,name=_G);await A.get_or_create(user_uid=B,name=_H)
"tar.lzma", async def prepare_all(A):
"tar.lz", await A.prepare_default_drives()
] async for B in A.services.user.find():await A.prepare_default_drives_for_user(B[_D])
EXTENSIONS_AUDIO = [
"mp3",
"wav",
"ogg",
"flac",
"m4a",
"wma",
"aac",
"opus",
"aiff",
"au",
"mid",
"midi",
]
EXTENSIONS_DOCS = [
"pdf",
"doc",
"docx",
"xls",
"xlsx",
"ppt",
"pptx",
"txt",
"md",
"json",
"csv",
"xml",
"html",
"css",
"js",
"py",
"sql",
"rs",
"toml",
"yml",
"yaml",
"ini",
"conf",
"config",
"log",
"csv",
"tsv",
"java",
"cs",
"csproj",
"scss",
"less",
"sass",
"json",
"lock",
"lock.json",
"jsonl",
]
async def get_drive_name_by_extension(self, extension):
if extension.startswith("."):
extension = extension[1:]
if extension in self.EXTENSIONS_PICTURES:
return "Pictures"
if extension in self.EXTENSIONS_VIDEOS:
return "Videos"
if extension in self.EXTENSIONS_ARCHIVES:
return "Archives"
if extension in self.EXTENSIONS_AUDIO:
return "Audio"
if extension in self.EXTENSIONS_DOCS:
return "Documents"
return "My Drive"
async def get_drive_by_extension(self, user_uid, extension):
name = await self.get_drive_name_by_extension(extension)
return await self.get_or_create(user_uid=user_uid, name=name)
async def get_by_user(self, user_uid, name=None):
kwargs = {"user_uid": user_uid}
async for model in self.find(**kwargs):
if not name:
yield model
elif model["name"] == name:
yield model
elif not model["name"] and name == "My Drive":
model["name"] = "My Drive"
await self.save(model)
yield model
async def get_or_create(self, user_uid, name=None, extensions=None):
kwargs = {"user_uid": user_uid}
if name:
kwargs["name"] = name
async for model in self.get_by_user(**kwargs):
return model
model = await self.new()
model["user_uid"] = user_uid
model["name"] = name
await self.save(model)
return model
async def prepare_default_drives(self):
async for drive_item in self.services.drive_item.find():
extension = drive_item.extension
drive = await self.get_drive_by_extension(drive_item["user_uid"], extension)
if not drive_item["drive_uid"] == drive["uid"]:
drive_item["drive_uid"] = drive["uid"]
await self.services.drive_item.save(drive_item)
async def prepare_default_drives_for_user(self, user_uid):
await self.get_or_create(user_uid=user_uid, name="My Drive")
await self.get_or_create(user_uid=user_uid, name="Shared Drive")
await self.get_or_create(user_uid=user_uid, name="Pictures")
await self.get_or_create(user_uid=user_uid, name="Videos")
await self.get_or_create(user_uid=user_uid, name="Archives")
await self.get_or_create(user_uid=user_uid, name="Documents")
async def prepare_all(self):
await self.prepare_default_drives()
async for user in self.services.user.find():
await self.prepare_default_drives_for_user(user["uid"])

View File

@ -1,19 +1,7 @@
from snek.system.service import BaseService from snek.system.service import BaseService
class DriveItemService(BaseService): class DriveItemService(BaseService):
mapper_name='drive_item'
mapper_name = "drive_item" async def create(B,drive_uid,name,path,type_,size):
A=await B.new();A['drive_uid']=drive_uid;A['name']=name;A['path']=str(path);A['extension']=str(name).split('.')[-1];A['file_type']=type_;A['file_size']=size
async def create(self, drive_uid, name, path, type_, size): if await B.save(A):return A
model = await self.new() C=await A.errors;raise Exception(f"Failed to create drive item: {C}.")
model["drive_uid"] = drive_uid
model["name"] = name
model["path"] = str(path)
model["extension"] = str(name).split(".")[-1]
model["file_type"] = type_
model["file_size"] = size
if await self.save(model):
return model
errors = await model.errors
raise Exception(f"Failed to create drive item: {errors}.")

View File

@ -1,65 +1,28 @@
_E='message'
_D='object_type'
_C='object_uid'
_B=False
_A='user_uid'
from snek.system.model import now from snek.system.model import now
from snek.system.service import BaseService from snek.system.service import BaseService
class NotificationService(BaseService): class NotificationService(BaseService):
mapper_name = "notification" mapper_name='notification'
async def mark_as_read(B,user_uid,channel_message_uid):
async def mark_as_read(self, user_uid, channel_message_uid): A=await B.get(user_uid,object_uid=channel_message_uid)
model = await self.get(user_uid, object_uid=channel_message_uid) if not A:return _B
if not model: A['read_at']=now();await B.save(A);return True
return False async def get_unread_stats(A,user_uid):await A.query('SELECT object_type, COUNT(*) as count FROM notification WHERE user_uid=:user_uid AND read_at IS NULL GROUP BY object_type',{_A:user_uid})
model["read_at"] = now() async def create(B,object_uid,object_type,user_uid,message):
await self.save(model) A=await B.new();A[_C]=object_uid;A[_D]=object_type;A[_A]=user_uid;A[_E]=message
return True if await B.save(A):return A
raise Exception(f"Failed to create notification: {A.errors}.")
async def get_unread_stats(self, user_uid): async def create_channel_message(A,channel_message_uid):
await self.query( E=channel_message_uid;D='new_count';F=await A.services.channel_message.get(uid=E);G=await A.services.user.get(uid=F[_A]);A.app.db.begin()
"SELECT object_type, COUNT(*) as count FROM notification WHERE user_uid=:user_uid AND read_at IS NULL GROUP BY object_type", async for B in A.services.channel_member.find(channel_uid=F['channel_uid'],is_banned=_B,is_muted=_B,deleted_at=None):
{"user_uid": user_uid}, if not B[D]:B[D]=0
) B[D]+=1;H=await A.services.user.get(uid=B[_A])
if not H:continue
async def create(self, object_uid, object_type, user_uid, message): await A.services.channel_member.save(B);C=await A.new();C[_C]=E;C[_D]='channel_message';C[_A]=B[_A];C[_E]=f"New message from {G["nick"]} in {B["label"]}."
model = await self.new() try:await A.save(C)
model["object_uid"] = object_uid except Exception:raise Exception(f"Failed to create notification: {C.errors}.")
model["object_type"] = object_type A.app.db.commit()
model["user_uid"] = user_uid
model["message"] = message
if await self.save(model):
return model
raise Exception(f"Failed to create notification: {model.errors}.")
async def create_channel_message(self, channel_message_uid):
channel_message = await self.services.channel_message.get(
uid=channel_message_uid
)
user = await self.services.user.get(uid=channel_message["user_uid"])
self.app.db.begin()
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,
):
if not channel_member["new_count"]:
channel_member["new_count"] = 0
channel_member["new_count"] += 1
usr = await self.services.user.get(uid=channel_member["user_uid"])
if not usr:
continue
await self.services.channel_member.save(channel_member)
model = await self.new()
model["object_uid"] = channel_message_uid
model["object_type"] = "channel_message"
model["user_uid"] = channel_member["user_uid"]
model["message"] = (
f"New message from {user['nick']} in {channel_member['label']}."
)
try:
await self.save(model)
except Exception:
raise Exception(f"Failed to create notification: {model.errors}.")
self.app.db.commit()

View File

@ -1,52 +1,23 @@
_B='user_uid'
_A=False
from snek.system.service import BaseService from snek.system.service import BaseService
import asyncio import asyncio,shutil
import shutil
class RepositoryService(BaseService): class RepositoryService(BaseService):
mapper_name = "repository" mapper_name='repository'
async def delete(B,user_uid,name):
async def delete(self, user_uid, name): A=user_uid;C=asyncio.get_event_loop();D=(await B.services.user.get_repository_path(A)).joinpath(name)
loop = asyncio.get_event_loop() try:await C.run_in_executor(None,shutil.rmtree,D)
repository_path = (await self.services.user.get_repository_path(user_uid)).joinpath(name) except Exception as E:print(E)
try: await super().delete(user_uid=A,name=name)
await loop.run_in_executor(None, shutil.rmtree, repository_path) async def exists(B,user_uid,name,**A):A[_B]=user_uid;A['name']=name;return await super().exists(**A)
except Exception as ex: async def init(D,user_uid,name):
print(ex) B='.git';A=await D.services.user.get_repository_path(user_uid)
if not A.exists():A.mkdir(parents=True)
await super().delete(user_uid=user_uid, name=name) A=A.joinpath(name);A=str(A)
if not A.endswith(B):A+=B
E=['git','init','--bare',A];C=await asyncio.subprocess.create_subprocess_exec(*E,stdout=asyncio.subprocess.PIPE,stderr=asyncio.subprocess.PIPE);F,G=await C.communicate();return C.returncode==0
async def exists(self, user_uid, name, **kwargs): async def create(A,user_uid,name,is_private=_A):
kwargs["user_uid"] = user_uid C=name;D=user_uid
kwargs["name"] = name if await A.exists(user_uid=D,name=C):return _A
return await super().exists(**kwargs) if not await A.init(user_uid=D,name=C):return _A
B=await A.new();B[_B]=D;B['name']=C;B['is_private']=is_private;return await A.save(B)
async def init(self, user_uid, name):
repository_path = await self.services.user.get_repository_path(user_uid)
if not repository_path.exists():
repository_path.mkdir(parents=True)
repository_path = repository_path.joinpath(name)
repository_path = str(repository_path)
if not repository_path.endswith(".git"):
repository_path += ".git"
command = ['git', 'init', '--bare', repository_path]
process = await asyncio.subprocess.create_subprocess_exec(
*command,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await process.communicate()
return process.returncode == 0
async def create(self, user_uid, name,is_private=False):
if await self.exists(user_uid=user_uid, name=name):
return False
if not await self.init(user_uid=user_uid, name=name):
return False
model = await self.new()
model["user_uid"] = user_uid
model["name"] = name
model["is_private"] = is_private
return await self.save(model)

View File

@ -1,71 +1,36 @@
_B=False
_A=True
from snek.model.user import UserModel from snek.model.user import UserModel
from snek.system.service import BaseService from snek.system.service import BaseService
class SocketService(BaseService): class SocketService(BaseService):
class Socket: class Socket:
def __init__(self, ws, user: UserModel): def __init__(A,ws,user):A.ws=ws;A.is_connected=_A;A.user=user
self.ws = ws async def send_json(A,data):
self.is_connected = True if not A.is_connected:return _B
self.user = user try:await A.ws.send_json(data)
except Exception:A.is_connected=_B
async def send_json(self, data): return A.is_connected
if not self.is_connected: async def close(A):
return False if not A.is_connected:return _A
await A.ws.close();A.is_connected=_B;return _A
def __init__(A,app):super().__init__(app);A.sockets=set();A.users={};A.subscriptions={}
async def add(A,ws,user_uid):
B=user_uid;C=A.Socket(ws,await A.app.services.user.get(uid=B));A.sockets.add(C)
if not A.users.get(B):A.users[B]=set()
A.users[B].add(C)
async def subscribe(A,ws,channel_uid,user_uid):
B=channel_uid
if B not in A.subscriptions:A.subscriptions[B]=set()
C=A.Socket(ws,await A.app.services.user.get(uid=user_uid));A.subscriptions[B].add(C)
async def send_to_user(B,user_uid,message):
A=0
for C in B.users.get(user_uid,[]):
if await C.send_json(message):A+=1
return A
async def broadcast(A,channel_uid,message):
try: try:
await self.ws.send_json(data) async for B in A.services.channel_member.get_user_uids(channel_uid):print(B,flush=_A);await A.send_to_user(B,message)
except Exception: except Exception as C:print(C,flush=_A)
self.is_connected = False return _A
return self.is_connected async def delete(A,ws):
for B in[A for A in A.sockets if A.ws==ws]:await B.close();A.sockets.remove(B)
async def close(self):
if not self.is_connected:
return True
await self.ws.close()
self.is_connected = False
return True
def __init__(self, app):
super().__init__(app)
self.sockets = set()
self.users = {}
self.subscriptions = {}
async def add(self, ws, user_uid):
s = self.Socket(ws, await self.app.services.user.get(uid=user_uid))
self.sockets.add(s)
if not self.users.get(user_uid):
self.users[user_uid] = set()
self.users[user_uid].add(s)
async def subscribe(self, ws, channel_uid, user_uid):
if channel_uid not in self.subscriptions:
self.subscriptions[channel_uid] = set()
s = self.Socket(ws, await self.app.services.user.get(uid=user_uid))
self.subscriptions[channel_uid].add(s)
async def send_to_user(self, user_uid, message):
count = 0
for s in self.users.get(user_uid, []):
if await s.send_json(message):
count += 1
return count
async def broadcast(self, channel_uid, message):
try:
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:
print(ex, flush=True)
return True
async def delete(self, ws):
for s in [sock for sock in self.sockets if sock.ws == ws]:
await s.close()
self.sockets.remove(s)

View File

@ -1,91 +1,53 @@
_B='color'
_A=True
import pathlib import pathlib
from snek.system import security from snek.system import security
from snek.system.service import BaseService from snek.system.service import BaseService
class UserService(BaseService): class UserService(BaseService):
mapper_name = "user" mapper_name='user'
async def get_by_username(A,username):return await A.get(username=username)
async def get_by_username(self, username): async def search(C,query,**D):
return await self.get(username=username) A=query;A=A.strip().lower()
if not A:return[]
async def search(self, query, **kwargs): B=[]
query = query.strip().lower() async for E in C.find(username={'ilike':'%'+A+'%'},**D):B.append(E)
if not query: return B
return [] async def validate_login(C,username,password):
results = [] A=False;B=await C.get(username=username)
async for result in self.find(username={"ilike": "%" + query + "%"}, **kwargs): if not B:return A
results.append(result) if not await security.verify(password,B['password']):return A
return results return _A
async def save(B,user):
async def validate_login(self, username, password): A=user
model = await self.get(username=username) if not A[_B]:A[_B]=await B.services.util.random_light_hex_color()
if not model: return await super().save(A)
return False async def authenticate(B,username,password):
if not await security.verify(password, model["password"]): C=password;A=username;print(A,C,flush=_A);D=await B.validate_login(A,C);print(D,flush=_A)
return False if not D:return
return True E=await B.get(username=A,deleted_at=None);return E
def get_admin_uids(A):return A.mapper.get_admin_uids()
async def save(self, user): async def get_repository_path(A,user_uid):return pathlib.Path(f"./drive/repositories/{user_uid}")
if not user["color"]: async def get_static_path(B,user_uid):
user["color"] = await self.services.util.random_light_hex_color() A=pathlib.Path(f"./drive/{user_uid}/snek/static")
return await super().save(user) if not A.exists():return
return A
async def authenticate(self, username, password): async def get_template_path(B,user_uid):
print(username, password, flush=True) A=pathlib.Path(f"./drive/{user_uid}/snek/templates")
success = await self.validate_login(username, password) if not A.exists():return
print(success, flush=True) return A
if not success: async def get_home_folder(B,user_uid):
return None A=pathlib.Path(f"./drive/{user_uid}")
if not A.exists():
model = await self.get(username=username, deleted_at=None) try:A.mkdir(parents=_A,exist_ok=_A)
return model except:pass
return A
def get_admin_uids(self): async def register(B,email,username,password):
return self.mapper.get_admin_uids() C=username
if await B.exists(username=C):raise Exception('User already exists.')
async def get_repository_path(self, user_uid): A=await B.new();A['nick']=C;A[_B]=await B.services.util.random_light_hex_color();A.email.value=email;A.username.value=C;A.password.value=await security.hash(password)
return pathlib.Path(f"./drive/repositories/{user_uid}") if await B.save(A):
if A:
async def get_static_path(self, user_uid): D=await B.services.channel.ensure_public_channel(A['uid'])
path = pathlib.Path(f"./drive/{user_uid}/snek/static") if not D:raise Exception('Failed to create public channel.')
if not path.exists(): return A
return None raise Exception(f"Failed to create user: {A.errors}.")
return path
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():
try:
folder.mkdir(parents=True, exist_ok=True)
except:
pass
return folder
async def register(self, email, username, password):
if await self.exists(username=username):
raise Exception("User already exists.")
model = await self.new()
model["nick"] = username
model["color"] = await self.services.util.random_light_hex_color()
model.email.value = email
model.username.value = username
model.password.value = await security.hash(password)
if await self.save(model):
if model:
channel = await self.services.channel.ensure_public_channel(
model["uid"]
)
if not channel:
raise Exception("Failed to create public channel.")
return model
raise Exception(f"Failed to create user: {model.errors}.")

View File

@ -1,35 +1,15 @@
_A='user_property'
import json import json
from snek.system.service import BaseService from snek.system.service import BaseService
class UserPropertyService(BaseService): class UserPropertyService(BaseService):
mapper_name = "user_property" mapper_name=_A
async def set(C,user_uid,name,value):A='name';B='user_uid';C.mapper.db[_A].upsert({B:user_uid,A:name,'value':json.dumps(value,default=str)},[B,A])
async def set(self, user_uid, name, value): async def get(B,user_uid,name):
self.mapper.db["user_property"].upsert( try:return json.loads((await super().get(user_uid=user_uid,name=name))['value'])
{ except Exception as A:print(A);return
"user_uid": user_uid, async def search(C,query,**D):
"name": name, A=query;A=A.strip().lower()
"value": json.dumps(value, default=str), if not A:raise[]
}, B=[]
["user_uid", "name"], async for E in C.find(name={'ilike':'%'+A+'%'},**D):B.append(E)
) return B
async def get(self, user_uid, name):
try:
return json.loads(
(await super().get(user_uid=user_uid, name=name))["value"]
)
except Exception as ex:
print(ex)
return None
async def search(self, query, **kwargs):
query = query.strip().lower()
if not query:
raise []
results = []
async for result in self.find(name={"ilike": "%" + query + "%"}, **kwargs):
results.append(result)
return results

View File

@ -1,14 +1,4 @@
import random import random
from snek.system.service import BaseService from snek.system.service import BaseService
class UtilService(BaseService): class UtilService(BaseService):
async def random_light_hex_color(D):A=random.randint(128,255);B=random.randint(128,255);C=random.randint(128,255);return f"#{A:02x}{B:02x}{C:02x}"
async def random_light_hex_color(self):
r = random.randint(128, 255)
g = random.randint(128, 255)
b = random.randint(128, 255)
return f"#{r:02x}{g:02x}{b:02x}"

View File

@ -1,489 +1,207 @@
import os _O='branches'
import aiohttp _N='message'
_M='author'
_L='Invalid JSON data'
_K='origin'
_J='Repository not found'
_I='main'
_H='repository'
_G='branch'
_F='.git'
_E=None
_D='user'
_C='repo_name'
_B='username'
_A='repository_path'
import os,aiohttp
from aiohttp import web from aiohttp import web
import git import git,shutil,json,tempfile,asyncio,logging,base64,pathlib
import shutil logging.basicConfig(level=logging.INFO,format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
import json logger=logging.getLogger('git_server')
import tempfile
import asyncio
import logging
import base64
import pathlib
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger('git_server')
class GitApplication(web.Application): class GitApplication(web.Application):
def __init__(self, parent=None): def __init__(A,parent=_E):B='/branches/{repo_name}';A.parent=parent;super().__init__(client_max_size=5368709120);A.REPO_DIR='drive/repositories/3177f85e-dbb3-4406-993e-3d3748fea545';A.USERS={'x':'x','bob':'bobpass'};A.add_routes([web.post('/create/{repo_name}',A.create_repository),web.delete('/delete/{repo_name}',A.delete_repository),web.get('/clone/{repo_name}',A.clone_repository),web.post('/push/{repo_name}',A.push_repository),web.post('/pull/{repo_name}',A.pull_repository),web.get('/status/{repo_name}',A.status_repository),web.get('/list',A.list_repositories),web.get(B,A.list_branches),web.post(B,A.create_branch),web.get('/log/{repo_name}',A.commit_log),web.get('/file/{repo_name}/{file_path:.*}',A.file_content),web.get('/{path:.+}/info/refs',A.git_smart_http),web.post('/{path:.+}/git-upload-pack',A.git_smart_http),web.post('/{path:.+}/git-receive-pack',A.git_smart_http),web.get('/{repo_name}.git/info/refs',A.git_smart_http),web.post('/{repo_name}.git/git-upload-pack',A.git_smart_http),web.post('/{repo_name}.git/git-receive-pack',A.git_smart_http)])
self.parent = parent async def check_basic_auth(B,request):
super().__init__(client_max_size=1024*1024*1024*5) C='Basic ';A=request;D=A.headers.get('Authorization','')
self.REPO_DIR = "drive/repositories/3177f85e-dbb3-4406-993e-3d3748fea545" if not D.startswith(C):return _E,_E
self.USERS = { E=D.split(C)[1];F=base64.b64decode(E).decode();G,H=F.split(':',1);A[_D]=await B.parent.services.user.authenticate(username=G,password=H)
'x': 'x', if not A[_D]:return _E,_E
'bob': 'bobpass', A[_A]=await B.parent.services.user.get_repository_path(A[_D]['uid']);return A[_D][_B],A[_A]
}
self.add_routes([
web.post('/create/{repo_name}', self.create_repository),
web.delete('/delete/{repo_name}', self.delete_repository),
web.get('/clone/{repo_name}', self.clone_repository),
web.post('/push/{repo_name}', self.push_repository),
web.post('/pull/{repo_name}', self.pull_repository),
web.get('/status/{repo_name}', self.status_repository),
web.get('/list', self.list_repositories),
web.get('/branches/{repo_name}', self.list_branches),
web.post('/branches/{repo_name}', self.create_branch),
web.get('/log/{repo_name}', self.commit_log),
web.get('/file/{repo_name}/{file_path:.*}', self.file_content),
web.get('/{path:.+}/info/refs', self.git_smart_http),
web.post('/{path:.+}/git-upload-pack', self.git_smart_http),
web.post('/{path:.+}/git-receive-pack', self.git_smart_http),
web.get('/{repo_name}.git/info/refs', self.git_smart_http),
web.post('/{repo_name}.git/git-upload-pack', self.git_smart_http),
web.post('/{repo_name}.git/git-receive-pack', self.git_smart_http),
])
async def check_basic_auth(self, request):
auth_header = request.headers.get("Authorization", "")
if not auth_header.startswith("Basic "):
return None,None
encoded_creds = auth_header.split("Basic ")[1]
decoded_creds = base64.b64decode(encoded_creds).decode()
username, password = decoded_creds.split(":", 1)
request["user"] = await self.parent.services.user.authenticate(
username=username, password=password
)
if not request["user"]:
return None,None
request["repository_path"] = await self.parent.services.user.get_repository_path(
request["user"]["uid"]
)
return request["user"]['username'],request["repository_path"]
@staticmethod @staticmethod
def require_auth(handler): def require_auth(handler):
async def wrapped(self, request, *args, **kwargs): async def A(self,request,*D,**E):
username, repository_path = await self.check_basic_auth(request) A=request;B,C=await self.check_basic_auth(A)
if not username or not repository_path: if not B or not C:return web.Response(status=401,headers={'WWW-Authenticate':'Basic'},text='Authentication required')
return web.Response(status=401, headers={'WWW-Authenticate': 'Basic'}, text='Authentication required') A[_B]=B;A[_A]=C;return await handler(self,A,*D,**E)
request['username'] = username return A
request['repository_path'] = repository_path def repo_path(A,repository_path,repo_name):return repository_path.joinpath(repo_name+_F)
return await handler(self, request, *args, **kwargs) def check_repo_exists(A,repository_path,repo_name):
return wrapped B=A.repo_path(repository_path,repo_name)
if not os.path.exists(B):return web.Response(text=_J,status=404)
def repo_path(self, repository_path, repo_name):
return repository_path.joinpath(repo_name + '.git')
def check_repo_exists(self, repository_path, repo_name):
repo_dir = self.repo_path(repository_path, repo_name)
if not os.path.exists(repo_dir):
return web.Response(text="Repository not found", status=404)
return None
@require_auth @require_auth
async def create_repository(self, request): async def create_repository(self,request):
username = request['username'] B=request;E=B[_B];A=B.match_info[_C];F=B[_A]
repo_name = request.match_info['repo_name'] if not A or'/'in A or'..'in A:return web.Response(text='Invalid repository name',status=400)
repository_path = request['repository_path'] C=self.repo_path(F,A)
if not repo_name or '/' in repo_name or '..' in repo_name: if os.path.exists(C):return web.Response(text='Repository already exists',status=400)
return web.Response(text="Invalid repository name", status=400) try:git.Repo.init(C,bare=True);logger.info(f"Created repository: {A} for user {E}");return web.Response(text=f"Created repository {A}")
repo_dir = self.repo_path(repository_path, repo_name) except Exception as D:logger.error(f"Error creating repository {A}: {str(D)}");return web.Response(text=f"Error creating repository: {str(D)}",status=500)
if os.path.exists(repo_dir):
return web.Response(text="Repository already exists", status=400)
try:
git.Repo.init(repo_dir, bare=True)
logger.info(f"Created repository: {repo_name} for user {username}")
return web.Response(text=f"Created repository {repo_name}")
except Exception as e:
logger.error(f"Error creating repository {repo_name}: {str(e)}")
return web.Response(text=f"Error creating repository: {str(e)}", status=500)
@require_auth @require_auth
async def delete_repository(self, request): async def delete_repository(self,request):
username = request['username'] B=request;F=B[_B];A=B.match_info[_C];C=B[_A];D=self.check_repo_exists(C,A)
repo_name = request.match_info['repo_name'] if D:return D
repository_path = request['repository_path'] try:shutil.rmtree(self.repo_path(C,A));logger.info(f"Deleted repository: {A} for user {F}");return web.Response(text=f"Deleted repository {A}")
error_response = self.check_repo_exists(repository_path, repo_name) except Exception as E:logger.error(f"Error deleting repository {A}: {str(E)}");return web.Response(text=f"Error deleting repository: {str(E)}",status=500)
if error_response:
return error_response
#'''
try:
shutil.rmtree(self.repo_path(repository_path, repo_name))
logger.info(f"Deleted repository: {repo_name} for user {username}")
return web.Response(text=f"Deleted repository {repo_name}")
except Exception as e:
logger.error(f"Error deleting repository {repo_name}: {str(e)}")
return web.Response(text=f"Error deleting repository: {str(e)}", status=500)
@require_auth @require_auth
async def clone_repository(self, request): async def clone_repository(self,request):
username = request['username'] A=request;H=A[_B];B=A.match_info[_C];E=A[_A];C=self.check_repo_exists(E,B)
repo_name = request.match_info['repo_name'] if C:return C
repository_path = request['repository_path'] F=A.host;D=f"http://{F}/{B}.git";G={_H:B,'clone_command':f"git clone {D}",'clone_url':D};return web.json_response(G)
error_response = self.check_repo_exists(repository_path, repo_name)
if error_response:
return error_response
host = request.host
clone_url = f"http://{host}/{repo_name}.git"
response_data = {
"repository": repo_name,
"clone_command": f"git clone {clone_url}",
"clone_url": clone_url
}
return web.json_response(response_data)
@require_auth @require_auth
async def push_repository(self, request): async def push_repository(self,request):
username = request['username'] B=request;L=B[_B];C=B.match_info[_C];E=B[_A];F=self.check_repo_exists(E,C)
repo_name = request.match_info['repo_name'] if F:return F
repository_path = request['repository_path'] try:D=await B.json()
error_response = self.check_repo_exists(repository_path, repo_name) except json.JSONDecodeError:return web.Response(text=_L,status=400)
if error_response: M=D.get('commit_message','Update from server');G=D.get(_G,_I);H=D.get('changes',[])
return error_response if not H:return web.Response(text='No changes provided',status=400)
try: with tempfile.TemporaryDirectory()as I:
data = await request.json() A=git.Repo.clone_from(self.repo_path(E,C),I)
except json.JSONDecodeError: for J in H:
return web.Response(text="Invalid JSON data", status=400) K=os.path.join(I,J.get('file',''));N=J.get('content','');os.makedirs(os.path.dirname(K),exist_ok=True)
commit_message = data.get('commit_message', 'Update from server') with open(K,'w')as O:O.write(N)
branch = data.get('branch', 'main') A.git.add(A=True)
changes = data.get('changes', []) if not A.config_reader().has_section(_D):A.config_writer().set_value(_D,'name','Git Server').release();A.config_writer().set_value(_D,'email','git@server.local').release()
if not changes: A.index.commit(M);P=A.remote(_K);P.push(refspec=f"{G}:{G}")
return web.Response(text="No changes provided", status=400) logger.info(f"Pushed to repository: {C} for user {L}");return web.Response(text=f"Successfully pushed changes to {C}")
with tempfile.TemporaryDirectory() as temp_dir:
temp_repo = git.Repo.clone_from(self.repo_path(repository_path, repo_name), temp_dir)
for change in changes:
file_path = os.path.join(temp_dir, change.get('file', ''))
content = change.get('content', '')
os.makedirs(os.path.dirname(file_path), exist_ok=True)
with open(file_path, 'w') as f:
f.write(content)
temp_repo.git.add(A=True)
if not temp_repo.config_reader().has_section('user'):
temp_repo.config_writer().set_value("user", "name", "Git Server").release()
temp_repo.config_writer().set_value("user", "email", "git@server.local").release()
temp_repo.index.commit(commit_message)
origin = temp_repo.remote('origin')
origin.push(refspec=f"{branch}:{branch}")
logger.info(f"Pushed to repository: {repo_name} for user {username}")
return web.Response(text=f"Successfully pushed changes to {repo_name}")
@require_auth @require_auth
async def pull_repository(self, request): async def pull_repository(self,request):
username = request['username'] C=request;K=C[_B];A=C.match_info[_C];H=C[_A];I=self.check_repo_exists(H,A)
repo_name = request.match_info['repo_name'] if I:return I
repository_path = request['repository_path'] try:E=await C.json()
error_response = self.check_repo_exists(repository_path, repo_name) except json.JSONDecodeError:E={}
if error_response: B=E.get('remote_url');L=E.get(_G,_I)
return error_response if not B:return web.Response(text='Remote URL is required',status=400)
with tempfile.TemporaryDirectory()as M:
try: try:
data = await request.json() D=git.Repo.clone_from(self.repo_path(H,A),M);F='pull_source'
except json.JSONDecodeError: try:G=D.create_remote(F,B)
data = {} except git.GitCommandError:G=D.remote(F);G.set_url(B)
remote_url = data.get('remote_url') G.fetch();D.git.merge(f"{F}/{L}");N=D.remote(_K);N.push();logger.info(f"Pulled to repository {A} from {B} for user {K}");return web.Response(text=f"Successfully pulled changes from {B} to {A}")
branch = data.get('branch', 'main') except Exception as J:logger.error(f"Error pulling to {A}: {str(J)}");return web.Response(text=f"Error pulling changes: {str(J)}",status=500)
if not remote_url:
return web.Response(text="Remote URL is required", status=400)
with tempfile.TemporaryDirectory() as temp_dir:
try:
local_repo = git.Repo.clone_from(self.repo_path(repository_path, repo_name), temp_dir)
remote_name = "pull_source"
try:
remote = local_repo.create_remote(remote_name, remote_url)
except git.GitCommandError:
remote = local_repo.remote(remote_name)
remote.set_url(remote_url)
remote.fetch()
local_repo.git.merge(f"{remote_name}/{branch}")
origin = local_repo.remote('origin')
origin.push()
logger.info(f"Pulled to repository {repo_name} from {remote_url} for user {username}")
return web.Response(text=f"Successfully pulled changes from {remote_url} to {repo_name}")
except Exception as e:
logger.error(f"Error pulling to {repo_name}: {str(e)}")
return web.Response(text=f"Error pulling changes: {str(e)}", status=500)
@require_auth @require_auth
async def status_repository(self, request): async def status_repository(self,request):
username = request['username'] C=request;S=C[_B];B=C.match_info[_C];F=C[_A];G=self.check_repo_exists(F,B)
repo_name = request.match_info['repo_name'] if G:return G
repository_path = request['repository_path'] with tempfile.TemporaryDirectory()as D:
error_response = self.check_repo_exists(repository_path, repo_name)
if error_response:
return error_response
with tempfile.TemporaryDirectory() as temp_dir:
try: try:
temp_repo = git.Repo.clone_from(self.repo_path(repository_path, repo_name), temp_dir) E=git.Repo.clone_from(self.repo_path(F,B),D);L=[A.name for A in E.branches];M=E.active_branch.name;H=[]
branches = [b.name for b in temp_repo.branches] for A in list(E.iter_commits(max_count=5)):H.append({'id':A.hexsha,_M:f"{A.author.name} <{A.author.email}>",'date':A.committed_datetime.isoformat(),_N:A.message})
active_branch = temp_repo.active_branch.name I=[]
commits = [] for(J,T,N)in os.walk(D):
for commit in list(temp_repo.iter_commits(max_count=5)): if _F in J:continue
commits.append({ for O in N:P=os.path.join(J,O);Q=os.path.relpath(P,D);I.append(Q)
"id": commit.hexsha, R={_H:B,_O:L,'active_branch':M,'recent_commits':H,'files':I};return web.json_response(R)
"author": f"{commit.author.name} <{commit.author.email}>", except Exception as K:logger.error(f"Error getting status for {B}: {str(K)}");return web.Response(text=f"Error getting repository status: {str(K)}",status=500)
"date": commit.committed_datetime.isoformat(),
"message": commit.message
})
files = []
for root, dirs, filenames in os.walk(temp_dir):
if '.git' in root:
continue
for filename in filenames:
full_path = os.path.join(root, filename)
rel_path = os.path.relpath(full_path, temp_dir)
files.append(rel_path)
status_info = {
"repository": repo_name,
"branches": branches,
"active_branch": active_branch,
"recent_commits": commits,
"files": files
}
return web.json_response(status_info)
except Exception as e:
logger.error(f"Error getting status for {repo_name}: {str(e)}")
return web.Response(text=f"Error getting repository status: {str(e)}", status=500)
@require_auth @require_auth
async def list_repositories(self, request): async def list_repositories(self,request):
username = request['username'] D=request;G=D[_B]
try: try:
repos = [] A=[];B=self.REPO_DIR
user_dir = self.REPO_DIR if os.path.exists(B):
if os.path.exists(user_dir): for C in os.listdir(B):
for item in os.listdir(user_dir): F=os.path.join(B,C)
item_path = os.path.join(user_dir, item) if os.path.isdir(F)and C.endswith(_F):A.append(C[:-4])
if os.path.isdir(item_path) and item.endswith('.git'): if D.query.get('format')=='json':return web.json_response({'repositories':A})
repos.append(item[:-4]) else:return web.Response(text='\n'.join(A)if A else'No repositories found')
if request.query.get('format') == 'json': except Exception as E:logger.error(f"Error listing repositories: {str(E)}");return web.Response(text=f"Error listing repositories: {str(E)}",status=500)
return web.json_response({"repositories": repos})
else:
return web.Response(text="\n".join(repos) if repos else "No repositories found")
except Exception as e:
logger.error(f"Error listing repositories: {str(e)}")
return web.Response(text=f"Error listing repositories: {str(e)}", status=500)
@require_auth @require_auth
async def list_branches(self, request): async def list_branches(self,request):
username = request['username'] A=request;H=A[_B];B=A.match_info[_C];C=A[_A];D=self.check_repo_exists(C,B)
repo_name = request.match_info['repo_name'] if D:return D
repository_path = request['repository_path'] with tempfile.TemporaryDirectory()as E:F=git.Repo.clone_from(self.repo_path(C,B),E);G=[A.name for A in F.branches];return web.json_response({_O:G})
error_response = self.check_repo_exists(repository_path, repo_name)
if error_response:
return error_response
with tempfile.TemporaryDirectory() as temp_dir:
temp_repo = git.Repo.clone_from(self.repo_path(repository_path, repo_name), temp_dir)
branches = [b.name for b in temp_repo.branches]
return web.json_response({"branches": branches})
@require_auth @require_auth
async def create_branch(self, request): async def create_branch(self,request):
username = request['username'] B=request;I=B[_B];C=B.match_info[_C];D=B[_A];E=self.check_repo_exists(D,C)
repo_name = request.match_info['repo_name'] if E:return E
repository_path = request['repository_path'] try:F=await B.json()
error_response = self.check_repo_exists(repository_path, repo_name) except json.JSONDecodeError:return web.Response(text=_L,status=400)
if error_response: A=F.get('branch_name');J=F.get('start_point','HEAD')
return error_response if not A:return web.Response(text='Branch name is required',status=400)
try: with tempfile.TemporaryDirectory()as K:
data = await request.json() try:G=git.Repo.clone_from(self.repo_path(D,C),K);G.git.branch(A,J);G.git.push(_K,A);logger.info(f"Created branch {A} in repository {C} for user {I}");return web.Response(text=f"Created branch {A}")
except json.JSONDecodeError: except Exception as H:logger.error(f"Error creating branch {A} in {C}: {str(H)}");return web.Response(text=f"Error creating branch: {str(H)}",status=500)
return web.Response(text="Invalid JSON data", status=400)
branch_name = data.get('branch_name')
start_point = data.get('start_point', 'HEAD')
if not branch_name:
return web.Response(text="Branch name is required", status=400)
with tempfile.TemporaryDirectory() as temp_dir:
try:
temp_repo = git.Repo.clone_from(self.repo_path(repository_path, repo_name), temp_dir)
temp_repo.git.branch(branch_name, start_point)
temp_repo.git.push('origin', branch_name)
logger.info(f"Created branch {branch_name} in repository {repo_name} for user {username}")
return web.Response(text=f"Created branch {branch_name}")
except Exception as e:
logger.error(f"Error creating branch {branch_name} in {repo_name}: {str(e)}")
return web.Response(text=f"Error creating branch: {str(e)}", status=500)
@require_auth @require_auth
async def commit_log(self, request): async def commit_log(self,request):
username = request['username'] B=request;L=B[_B];C=B.match_info[_C];F=B[_A];G=self.check_repo_exists(F,C)
repo_name = request.match_info['repo_name'] if G:return G
repository_path = request['repository_path'] try:I=int(B.query.get('limit',10));H=B.query.get(_G,_I)
error_response = self.check_repo_exists(repository_path, repo_name) except ValueError:return web.Response(text='Invalid limit parameter',status=400)
if error_response: with tempfile.TemporaryDirectory()as J:
return error_response
try: try:
limit = int(request.query.get('limit', 10)) K=git.Repo.clone_from(self.repo_path(F,C),J);E=[]
branch = request.query.get('branch', 'main')
except ValueError:
return web.Response(text="Invalid limit parameter", status=400)
with tempfile.TemporaryDirectory() as temp_dir:
try: try:
temp_repo = git.Repo.clone_from(self.repo_path(repository_path, repo_name), temp_dir) for A in list(K.iter_commits(H,max_count=I)):E.append({'id':A.hexsha,'short_id':A.hexsha[:7],_M:f"{A.author.name} <{A.author.email}>",'date':A.committed_datetime.isoformat(),_N:A.message.strip()})
commits = [] except git.GitCommandError as D:
try: if'unknown revision or path'in str(D):E=[]
for commit in list(temp_repo.iter_commits(branch, max_count=limit)): else:raise
commits.append({ return web.json_response({_H:C,_G:H,'commits':E})
"id": commit.hexsha, except Exception as D:logger.error(f"Error getting commit log for {C}: {str(D)}");return web.Response(text=f"Error getting commit log: {str(D)}",status=500)
"short_id": commit.hexsha[:7],
"author": f"{commit.author.name} <{commit.author.email}>",
"date": commit.committed_datetime.isoformat(),
"message": commit.message.strip()
})
except git.GitCommandError as e:
if "unknown revision or path" in str(e):
commits = []
else:
raise
return web.json_response({
"repository": repo_name,
"branch": branch,
"commits": commits
})
except Exception as e:
logger.error(f"Error getting commit log for {repo_name}: {str(e)}")
return web.Response(text=f"Error getting commit log: {str(e)}", status=500)
@require_auth @require_auth
async def file_content(self, request): async def file_content(self,request):
username = request['username'] A=request;N=A[_B];B=A.match_info[_C];C=A.match_info.get('file_path','');E=A.query.get(_G,_I);F=A[_A];G=self.check_repo_exists(F,B)
repo_name = request.match_info['repo_name'] if G:return G
file_path = request.match_info.get('file_path', '') with tempfile.TemporaryDirectory()as H:
branch = request.query.get('branch', 'main')
repository_path = request['repository_path']
error_response = self.check_repo_exists(repository_path, repo_name)
if error_response:
return error_response
with tempfile.TemporaryDirectory() as temp_dir:
try: try:
temp_repo = git.Repo.clone_from(self.repo_path(repository_path, repo_name), temp_dir) J=git.Repo.clone_from(self.repo_path(F,B),H)
try: try:J.git.checkout(E)
temp_repo.git.checkout(branch) except git.GitCommandError:return web.Response(text=f"Branch '{E}' not found",status=404)
except git.GitCommandError: D=os.path.join(H,C)
return web.Response(text=f"Branch '{branch}' not found", status=404) if not os.path.exists(D):return web.Response(text=f"File '{C}' not found",status=404)
file_full_path = os.path.join(temp_dir, file_path) if os.path.isdir(D):K=os.listdir(D);return web.json_response({_H:B,'path':C,'type':'directory','contents':K})
if not os.path.exists(file_full_path):
return web.Response(text=f"File '{file_path}' not found", status=404)
if os.path.isdir(file_full_path):
files = os.listdir(file_full_path)
return web.json_response({
"repository": repo_name,
"path": file_path,
"type": "directory",
"contents": files
})
else: else:
try: try:
with open(file_full_path, 'r') as f: with open(D,'r')as L:M=L.read()
content = f.read() return web.Response(text=M)
return web.Response(text=content) except UnicodeDecodeError:return web.Response(text=f"Cannot display binary file content for '{C}'",status=400)
except UnicodeDecodeError: except Exception as I:logger.error(f"Error getting file content from {B}: {str(I)}");return web.Response(text=f"Error getting file content: {str(I)}",status=500)
return web.Response(text=f"Cannot display binary file content for '{file_path}'", status=400)
except Exception as e:
logger.error(f"Error getting file content from {repo_name}: {str(e)}")
return web.Response(text=f"Error getting file content: {str(e)}", status=500)
@require_auth @require_auth
async def git_smart_http(self, request): async def git_smart_http(self,request):
username = request['username'] B='POST';G='git-receive-pack';H='git-upload-pack';I='Content-Type';J='--stateless-rpc';D='/git-receive-pack';E='/git-upload-pack';F='/info/refs';A=request;P=A[_B];N=A[_A];C=A.path
repository_path = request['repository_path'] async def K():
path = request.path B=C.lstrip('/')
async def get_repository_path(): if B.endswith(F):A=B[:-len(F)]
req_path = path.lstrip('/') elif B.endswith(E):A=B[:-len(E)]
if req_path.endswith('/info/refs'): elif B.endswith(D):A=B[:-len(D)]
repo_name = req_path[:-len('/info/refs')] else:A=B
elif req_path.endswith('/git-upload-pack'): if A.endswith(_F):A=A[:-4]
repo_name = req_path[:-len('/git-upload-pack')] A=A[4:];G=N.joinpath(A+_F);logger.info(f"Resolved repo path: {G}");return G
elif req_path.endswith('/git-receive-pack'): async def O(service):
repo_name = req_path[:-len('/git-receive-pack')] C=service;D=await K();logger.info(f"handle_info_refs: {D}")
else: if not os.path.exists(D):return web.Response(text=_J,status=404)
repo_name = req_path L=[C,J,'--advertise-refs',str(D)]
if repo_name.endswith('.git'):
repo_name = repo_name[:-4]
repo_name = repo_name[4:]
repo_dir = repository_path.joinpath(repo_name + ".git")
logger.info(f"Resolved repo path: {repo_dir}")
return repo_dir
async def handle_info_refs(service):
repo_path = await get_repository_path()
logger.info(f"handle_info_refs: {repo_path}")
if not os.path.exists(repo_path):
return web.Response(text="Repository not found", status=404)
cmd = [service, '--stateless-rpc', '--advertise-refs', str(repo_path)]
try: try:
process = await asyncio.create_subprocess_exec( E=await asyncio.create_subprocess_exec(*L,stdout=asyncio.subprocess.PIPE,stderr=asyncio.subprocess.PIPE);M,F=await E.communicate()
*cmd, if E.returncode!=0:logger.error(f"Git command failed: {F.decode()}");return web.Response(text=f"Git error: {F.decode()}",status=500)
stdout=asyncio.subprocess.PIPE, B=web.StreamResponse(status=200,reason='OK',headers={I:f"application/x-{C}-advertisement",'Cache-Control':'no-cache'});await B.prepare(A);G=f"# service={C}\n";N=len(G)+4;O=f"{N:04x}";await B.write(f"{O}{G}0000".encode());await B.write(M);return B
stderr=asyncio.subprocess.PIPE except Exception as H:logger.error(f"Error handling info/refs: {str(H)}");return web.Response(text=f"Server error: {str(H)}",status=500)
) async def L(service):
stdout, stderr = await process.communicate() B=service;C=await K();logger.info(f"handle_service_rpc: {C}")
if process.returncode != 0: if not os.path.exists(C):return web.Response(text=_J,status=404)
logger.error(f"Git command failed: {stderr.decode()}") if not A.headers.get(I)==f"application/x-{B}-request":return web.Response(text='Invalid Content-Type',status=403)
return web.Response(text=f"Git error: {stderr.decode()}", status=500) G=await A.read();H=[B,J,str(C)]
response = web.StreamResponse(
status=200,
reason='OK',
headers={
'Content-Type': f'application/x-{service}-advertisement',
'Cache-Control': 'no-cache'
}
)
await response.prepare(request)
packet = f"# service={service}\n"
length = len(packet) + 4
header = f"{length:04x}"
await response.write(f"{header}{packet}0000".encode())
await response.write(stdout)
return response
except Exception as e:
logger.error(f"Error handling info/refs: {str(e)}")
return web.Response(text=f"Server error: {str(e)}", status=500)
async def handle_service_rpc(service):
repo_path = await get_repository_path()
logger.info(f"handle_service_rpc: {repo_path}")
if not os.path.exists(repo_path):
return web.Response(text="Repository not found", status=404)
if not request.headers.get('Content-Type') == f'application/x-{service}-request':
return web.Response(text="Invalid Content-Type", status=403)
body = await request.read()
cmd = [service, '--stateless-rpc', str(repo_path)]
try: try:
process = await asyncio.create_subprocess_exec( D=await asyncio.create_subprocess_exec(*H,stdin=asyncio.subprocess.PIPE,stdout=asyncio.subprocess.PIPE,stderr=asyncio.subprocess.PIPE);L,E=await D.communicate(input=G)
*cmd, if D.returncode!=0:logger.error(f"Git command failed: {E.decode()}");return web.Response(text=f"Git error: {E.decode()}",status=500)
stdin=asyncio.subprocess.PIPE, return web.Response(body=L,content_type=f"application/x-{B}-result")
stdout=asyncio.subprocess.PIPE, except Exception as F:logger.error(f"Error handling service RPC: {str(F)}");return web.Response(text=f"Server error: {str(F)}",status=500)
stderr=asyncio.subprocess.PIPE if A.method=='GET'and C.endswith(F):
) M=A.query.get('service')
stdout, stderr = await process.communicate(input=body) if M in(H,G):return await O(M)
if process.returncode != 0: else:return web.Response(text='Smart HTTP requires service parameter',status=400)
logger.error(f"Git command failed: {stderr.decode()}") elif A.method==B and E in C:return await L(H)
return web.Response(text=f"Git error: {stderr.decode()}", status=500) elif A.method==B and D in C:return await L(G)
return web.Response( return web.Response(text='Not found',status=404)
body=stdout, if __name__=='__main__':
content_type=f'application/x-{service}-result' try:import uvloop;asyncio.set_event_loop_policy(uvloop.EventLoopPolicy());logger.info('Using uvloop for improved performance')
) except ImportError:logger.info('uvloop not available, using standard event loop')
except Exception as e: app=GitApplication();logger.info('Starting Git server on port 8080');web.run_app(app,port=8080)
logger.error(f"Error handling service RPC: {str(e)}")
return web.Response(text=f"Server error: {str(e)}", status=500)
if request.method == 'GET' and path.endswith('/info/refs'):
service = request.query.get('service')
if service in ('git-upload-pack', 'git-receive-pack'):
return await handle_info_refs(service)
else:
return web.Response(text="Smart HTTP requires service parameter", status=400)
elif request.method == 'POST' and '/git-upload-pack' in path:
return await handle_service_rpc('git-upload-pack')
elif request.method == 'POST' and '/git-receive-pack' in path:
return await handle_service_rpc('git-receive-pack')
return web.Response(text="Not found", status=404)
if __name__ == '__main__':
try:
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
logger.info("Using uvloop for improved performance")
except ImportError:
logger.info("uvloop not available, using standard event loop")
app = GitApplication()
logger.info("Starting Git server on port 8080")
web.run_app(app, port=8080)

View File

@ -1,144 +1,67 @@
import functools _C='delete'
import json _B='set'
_A='get'
import functools,json
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__(A,app,max_items=CACHE_MAX_ITEMS_DEFAULT):A.app=app;A.cache={};A.max_items=max_items;A.stats={};A.lru=[];A.version=15505
self.app = app async def get(A,args):
self.cache = {} B=args;await A.update_stat(B,_A)
self.max_items = max_items try:A.lru.pop(A.lru.index(B))
self.stats = {} except:return
self.lru = [] A.lru.insert(0,B)
self.version = ((42 + 420 + 1984 + 1990 + 10 + 6 + 71 + 3004 + 7245) ^ 1337) + 4 while len(A.lru)>A.max_items:A.cache.pop(A.lru[-1]);A.lru.pop()
return A.cache[B]
async def get(self, args): async def get_stats(A):
await self.update_stat(args, "get") C=[]
try: for B in A.lru:C.append({'key':B,_B:A.stats[B][_B],_A:A.stats[B][_A],_C:A.stats[B][_C],'value':str(A.serialize(A.cache[B].record))})
self.lru.pop(self.lru.index(args)) return C
except: def serialize(C,obj):B=None;A=obj.copy();A.pop('created_at',B);A.pop('deleted_at',B);A.pop('email',B);A.pop('password',B);return A
# print("Cache miss!", args, flush=True) async def update_stat(A,key,action):
return None C=action;B=key
self.lru.insert(0, args) if B not in A.stats:A.stats[B]={_B:0,_A:0,_C:0}
while len(self.lru) > self.max_items: A.stats[B][C]=A.stats[B][C]+1
self.cache.pop(self.lru[-1]) def json_default(B,value):
self.lru.pop() A=value
# print("Cache hit!", args, flush=True) try:return json.dumps(A.__dict__,default=str)
return self.cache[args] except:return str(A)
async def create_cache_key(A,args,kwargs):return await security.hash(json.dumps({'args':args,'kwargs':kwargs},sort_keys=True,default=A.json_default))
async def get_stats(self): async def set(A,args,result):
all_ = [] B=args;C=B not in A.cache;A.cache[B]=result;await A.update_stat(B,_B)
for key in self.lru: try:A.lru.pop(A.lru.index(B))
all_.append( except(ValueError,IndexError):pass
{ A.lru.insert(0,B)
"key": key, while len(A.lru)>A.max_items:A.cache.pop(A.lru[-1]);A.lru.pop()
"set": self.stats[key]["set"], if C:A.version+=1
"get": self.stats[key]["get"], async def delete(A,args):
"delete": self.stats[key]["delete"], B=args;await A.update_stat(B,_C)
"value": str(self.serialize(self.cache[key].record)), if B in A.cache:
} try:A.lru.pop(A.lru.index(B))
) except IndexError:pass
return all_ del A.cache[B]
def async_cache(A,func):
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)
return cpy
async def update_stat(self, key, action):
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):
# if hasattr(value, "to_json"):
# return value.to_json()
try:
return json.dumps(value.__dict__, default=str)
except:
return str(value)
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,
)
)
async def set(self, args, result):
is_new = args not in self.cache
self.cache[args] = result
await self.update_stat(args, "set")
try:
self.lru.pop(self.lru.index(args))
except (ValueError, IndexError):
pass
self.lru.insert(0, args)
while len(self.lru) > self.max_items:
self.cache.pop(self.lru[-1])
self.lru.pop()
if is_new:
self.version += 1
# 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")
if args in self.cache:
try:
self.lru.pop(self.lru.index(args))
except IndexError:
pass
del self.cache[args]
def async_cache(self, func):
@functools.wraps(func) @functools.wraps(func)
async def wrapper(*args, **kwargs): async def B(*B,**C):
cache_key = await self.create_cache_key(args, kwargs) D=await A.create_cache_key(B,C);E=await A.get(D)
cached = await self.get(cache_key) if E:return E
if cached: F=await func(*B,**C);await A.set(D,F);return F
return cached return B
result = await func(*args, **kwargs) def async_delete_cache(A,func):
await self.set(cache_key, result)
return result
return wrapper
def async_delete_cache(self, func):
@functools.wraps(func) @functools.wraps(func)
async def wrapper(*args, **kwargs): async def B(*C,**D):
cache_key = await self.create_cache_key(args, kwargs) B=await A.create_cache_key(C,D)
if cache_key in self.cache: if B in A.cache:
try: try:A.lru.pop(A.lru.index(B))
self.lru.pop(self.lru.index(cache_key)) except IndexError:pass
except IndexError: del A.cache[B]
pass return await func(*C,**D)
del self.cache[cache_key] return B
return await func(*args, **kwargs)
return wrapper
def async_cache(func): def async_cache(func):
cache = {} B={}
@functools.wraps(func) @functools.wraps(func)
async def wrapper(*args): async def A(*A):
if args in cache: if A in B:return B[A]
return cache[args] C=await func(*A);B[A]=C;return C
result = await func(*args) return A
cache[args] = result
return result
return wrapper

View File

@ -1,120 +1,32 @@
# Written by retoor@molodetz.nl _B='fields'
_A=None
# This code defines a framework for handling HTML elements as Python objects, including specific classes for HTML, form input, and form button elements. It offers methods to convert these elements to JSON, manipulate them, and validate form data.
# This code uses the `snek.system.model` library for managing model fields.
# MIT License
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from snek.system import model from snek.system import model
class HTMLElement(model.ModelField): class HTMLElement(model.ModelField):
def __init__( def __init__(A,id=_A,tag='div',name=_A,html=_A,class_name=_A,text=_A,*B,**C):A.tag=tag;A.text=text;A.id=id;A.class_name=class_name or name;A.html=html;super().__init__(*B,name=name,**C)
self, async def to_json(B):A=await super().to_json();A['text']=B.text;A['id']=B.id;A['html']=B.html;A['class_name']=B.class_name;A['tag']=B.tag;return A
id=None, class FormElement(HTMLElement):0
tag="div",
name=None,
html=None,
class_name=None,
text=None,
*args,
**kwargs,
):
self.tag = tag
self.text = text
self.id = id
self.class_name = class_name or name
self.html = html
super().__init__(name=name, *args, **kwargs)
async def to_json(self):
result = await super().to_json()
result["text"] = self.text
result["id"] = self.id
result["html"] = self.html
result["class_name"] = self.class_name
result["tag"] = self.tag
return result
class FormElement(HTMLElement):
pass
class FormInputElement(FormElement): class FormInputElement(FormElement):
def __init__(self, type="text", place_holder=None, *args, **kwargs): def __init__(A,type='text',place_holder=_A,*B,**C):super().__init__(*B,tag='input',**C);A.place_holder=place_holder;A.type=type
super().__init__(tag="input", *args, **kwargs) async def to_json(B):A=await super().to_json();A['place_holder']=B.place_holder;A['type']=B.type;return A
self.place_holder = place_holder
self.type = type
async def to_json(self):
data = await super().to_json()
data["place_holder"] = self.place_holder
data["type"] = self.type
return data
class FormButtonElement(FormElement): class FormButtonElement(FormElement):
def __init__(self, tag="button", *args, **kwargs): def __init__(C,tag='button',*A,**B):super().__init__(*A,tag=tag,**B)
super().__init__(tag=tag, *args, **kwargs)
class Form(model.BaseModel): class Form(model.BaseModel):
@property @property
def html_elements(self): def html_elements(self):return[A for A in self.fields if isinstance(A,HTMLElement)]
return [element for element in self.fields if isinstance(element, HTMLElement)] def set_user_data(A,data):return super().set_user_data(data.get(_B))
async def to_json(D,encode=False):
def set_user_data(self, data): B='is_valid';E=await super().to_json();C={}
return super().set_user_data(data.get("fields")) for A in E.keys():
if A==B:continue
async def to_json(self, encode=False): F=getattr(D,A)
elements = await super().to_json() if isinstance(F,HTMLElement):
html_elements = {} try:C[A]=E[A]
for element in elements.keys(): except KeyError:pass
if element == "is_valid": G=all(A[B]for A in C.values());return{_B:C,B:G,'errors':await D.errors}
# is_valid is async get property so we can't do getattr on it
continue
field = getattr(self, element)
if isinstance(field, HTMLElement):
try:
html_elements[element] = elements[element]
except KeyError:
pass
is_valid = all(field["is_valid"] for field in html_elements.values())
return {
"fields": html_elements,
"is_valid": is_valid,
"errors": await self.errors,
}
@property @property
async def errors(self): async def errors(self):
result = [] A=[]
for field in self.html_elements: for B in self.html_elements:A+=await B.errors
result += await field.errors return A
return result
@property @property
async def is_valid(self): async def is_valid(self):return False
# This is not good, but timebox to resolve issue exceeded.
return False

View File

@ -1,110 +1,44 @@
# Written by retoor@molodetz.nl import asyncio,pathlib,uuid,zlib
# This script enables downloading, processing, and caching web content, including taking website screenshots and repairing links in HTML content.
# Imports used: aiohttp, aiohttp.web for creating web servers and handling async requests; app.cache for caching utilities; BeautifulSoup from bs4 for HTML parsing; imgkit for creating screenshots.
# The MIT License (MIT)
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import asyncio
import pathlib
import uuid
import zlib
from urllib.parse import urljoin from urllib.parse import urljoin
import aiohttp,imgkit
import aiohttp
import imgkit
from app.cache import time_cache_async from app.cache import time_cache_async
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
async def crc32(data): async def crc32(data):
try: A=data
data = data.encode() try:A=A.encode()
except: except:pass
pass return'crc32'+str(zlib.crc32(A))
return "crc32" + str(zlib.crc32(data)) async def get_file(name,suffix='.cache'):
A=name;A=await crc32(A);B=pathlib.Path('.').joinpath('cache')
if not B.exists():B.mkdir(parents=True,exist_ok=True)
async def get_file(name, suffix=".cache"): return B.joinpath(A+suffix)
name = await crc32(name) async def public_touch(name=None):A=pathlib.Path('.').joinpath(str(uuid.uuid4())+name);A.open('wb').close();return A
path = pathlib.Path(".").joinpath("cache")
if not path.exists():
path.mkdir(parents=True, exist_ok=True)
return path.joinpath(name + suffix)
async def public_touch(name=None):
path = pathlib.Path(".").joinpath(str(uuid.uuid4()) + name)
path.open("wb").close()
return path
async def create_site_photo(url): async def create_site_photo(url):
loop = asyncio.get_event_loop() A=url;C=asyncio.get_event_loop()
if not url.startswith("https"): if not A.startswith('https'):A='https://'+A
url = "https://" + url B=await get_file('site-screenshot-'+A,'.png')
output_path = await get_file("site-screenshot-" + url, ".png") if B.exists():return B
B.touch()
if output_path.exists(): def D():imgkit.from_url(A,B.absolute());return B
return output_path return await C.run_in_executor(None,D)
output_path.touch() async def repair_links(base_url,html_content):
D='http';E=base_url;B='src';C='href';F=BeautifulSoup(html_content,'html.parser')
def make_photo(): for A in F.find_all(['a','img','link']):
imgkit.from_url(url, output_path.absolute()) if A.has_attr(C)and not A[C].startswith(D):A[C]=urljoin(E,A[C])
return output_path if A.has_attr(B)and not A[B].startswith(D):A[B]=urljoin(E,A[B])
return F.prettify()
return await loop.run_in_executor(None, make_photo) async def is_html_content(content):
B=False;A=content
if not A:return B
async def repair_links(base_url, html_content): try:A=A.decode(errors='ignore')
soup = BeautifulSoup(html_content, "html.parser") except:pass
for tag in soup.find_all(["a", "img", "link"]): C=['<html','<img','<p','<span','<div'];A=A.lower()
if tag.has_attr("href") and not tag["href"].startswith("http"): for D in C:
tag["href"] = urljoin(base_url, tag["href"]) if D in A:return True
if tag.has_attr("src") and not tag["src"].startswith("http"): return B
tag["src"] = urljoin(base_url, tag["src"])
return soup.prettify()
async def is_html_content(content: bytes):
if not content:
return False
try:
content = content.decode(errors="ignore")
except:
pass
marks = ["<html", "<img", "<p", "<span", "<div"]
content = content.lower()
for mark in marks:
if mark in content:
return True
return False
@time_cache_async(120) @time_cache_async(120)
async def get(url): async def get(url):
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession()as B:
response = await session.get(url) C=await B.get(url);A=await C.text()
content = await response.text() if await is_html_content(A):A=(await repair_links(url,A)).encode()
if await is_html_content(content): return A
content = (await repair_links(url, content)).encode()
return content

View File

@ -1,70 +1,37 @@
DEFAULT_LIMIT = 30 _A='uid'
DEFAULT_LIMIT=30
import typing import typing
from snek.system.model import BaseModel from snek.system.model import BaseModel
class BaseMapper: class BaseMapper:
model_class:BaseModel=None;default_limit:int=DEFAULT_LIMIT;table_name:str=None
model_class: BaseModel = None def __init__(A,app):A.app=app;A.default_limit=A.__class__.default_limit
default_limit: int = DEFAULT_LIMIT
table_name: str = None
def __init__(self, app):
self.app = app
self.default_limit = self.__class__.default_limit
@property @property
def db(self): def db(self):return self.app.db
return self.app.db async def new(A):return A.model_class(mapper=A,app=A.app)
async def new(self):
return self.model_class(mapper=self, app=self.app)
@property @property
def table(self): def table(self):return self.db[self.table_name]
return self.db[self.table_name] async def get(B,uid=None,**C):
if uid:C[_A]=uid
async def get(self, uid: str = None, **kwargs) -> BaseModel: A=B.table.find_one(**C)
if uid: if not A:return
kwargs["uid"] = uid A=dict(A);D=await B.new()
record = self.table.find_one(**kwargs) for(E,F)in A.items():D[E]=F
if not record: return D;return await B.model_class.from_record(mapper=B,record=A)
return None async def exists(A,**B):return A.table.exists(**B)
record = dict(record) async def count(A,**B):return A.table.count(**B)
model = await self.new() async def save(B,model):
for key, value in record.items(): A=model
model[key] = value if not A.record.get(_A):raise Exception(f"Attempt to save without uid: {A.record}.")
return model A.updated_at.update();return B.table.upsert(A.record,[_A])
return await self.model_class.from_record(mapper=self, record=record) async def find(A,**B):
C='_limit'
async def exists(self, **kwargs): if not B.get(C):B[C]=A.default_limit
return self.table.exists(**kwargs) for E in A.table.find(**B):
D=await A.new()
async def count(self, **kwargs) -> int: for(F,G)in E.items():D[F]=G
return self.table.count(**kwargs) yield D
async def query(A,sql,*B):
async def save(self, model: BaseModel) -> bool: for C in A.db.query(sql,*B):yield dict(C)
if not model.record.get("uid"): async def delete(B,**A):
raise Exception(f"Attempt to save without uid: {model.record}.") if not A or not isinstance(A,dict):raise Exception("Can't execute delete with no filter.")
model.updated_at.update() return B.table.delete(**A)
return self.table.upsert(model.record, ["uid"])
async def find(self, **kwargs) -> typing.AsyncGenerator:
if not kwargs.get("_limit"):
kwargs["_limit"] = self.default_limit
for record in self.table.find(**kwargs):
model = await self.new()
for key, value in record.items():
model[key] = value
yield model
async def query(self, sql, *args):
for record in self.db.query(sql, *args):
yield dict(record)
async def delete(self, **kwargs) -> int:
if not kwargs or not isinstance(kwargs, dict):
raise Exception("Can't execute delete with no filter.")
return self.table.delete(**kwargs)

View File

@ -1,87 +1,35 @@
# Original source: https://brandonjay.dev/posts/2021/render-markdown-html-in-python-with-jinja2 _A=True
from types import SimpleNamespace from types import SimpleNamespace
from app.cache import time_cache_async from app.cache import time_cache_async
from mistune import HTMLRenderer, Markdown from mistune import HTMLRenderer,Markdown
from pygments import highlight from pygments import highlight
from pygments.formatters import html from pygments.formatters import html
from pygments.lexers import get_lexer_by_name from pygments.lexers import get_lexer_by_name
class MarkdownRenderer(HTMLRenderer): class MarkdownRenderer(HTMLRenderer):
_allow_harmful_protocols=_A
_allow_harmful_protocols = True def __init__(A,app,template):A.template=template;A.app=app;A.env=A.app.jinja2_env;B=html.HtmlFormatter();A.env.globals['highlight_styles']=B.get_style_defs()
def _escape(A,str):return str
def __init__(self, app, template): def get_lexer(A,lang,default='bash'):
self.template = template try:return get_lexer_by_name(lang,stripall=_A)
except:return get_lexer_by_name(default,stripall=_A)
self.app = app def block_code(B,code,lang=None,info=None):
self.env = self.app.jinja2_env A=lang
formatter = html.HtmlFormatter() if not A:A=info
self.env.globals["highlight_styles"] = formatter.get_style_defs() if not A:A='bash'
C=B.get_lexer(A);D=html.HtmlFormatter(lineseparator='<br>');E=highlight(code,C,D);return E
def _escape(self, str): def render(A):B=A.app.template_path.joinpath(A.template).read_text();C=MarkdownRenderer(A.app,A.template);D=Markdown(renderer=C);return D(B)
return str ##escape(str) def render_markdown_sync(app,markdown_string):A=MarkdownRenderer(app,None);B=Markdown(renderer=A);return B(markdown_string)
def get_lexer(self, lang, default="bash"):
try:
return get_lexer_by_name(lang, stripall=True)
except:
return get_lexer_by_name(default, stripall=True)
def block_code(self, code, lang=None, info=None):
if not lang:
lang = info
if not lang:
lang = "bash"
lexer = self.get_lexer(lang)
formatter = html.HtmlFormatter(lineseparator="<br>")
result = highlight(code, lexer, formatter)
return result
def render(self):
markdown_string = self.app.template_path.joinpath(self.template).read_text()
renderer = MarkdownRenderer(self.app, self.template)
markdown = Markdown(renderer=renderer)
return markdown(markdown_string)
def render_markdown_sync(app, markdown_string):
renderer = MarkdownRenderer(app, None)
markdown = Markdown(renderer=renderer)
return markdown(markdown_string)
@time_cache_async(120) @time_cache_async(120)
async def render_markdown(app, markdown_string): async def render_markdown(app,markdown_string):return render_markdown_sync(app,markdown_string)
return render_markdown_sync(app, markdown_string) from jinja2 import TemplateSyntaxError,nodes
from jinja2 import TemplateSyntaxError, nodes
from jinja2.ext import Extension from jinja2.ext import Extension
from jinja2.nodes import Const from jinja2.nodes import Const
# Source: https://ron.sh/how-to-write-a-jinja2-extension/
class MarkdownExtension(Extension): class MarkdownExtension(Extension):
tags = {"markdown"} tags={'markdown'}
def __init__(A,environment):B=environment;A.app=SimpleNamespace(jinja2_env=B);super(MarkdownExtension,A).__init__(B)
def __init__(self, environment): def parse(D,parser):
self.app = SimpleNamespace(jinja2_env=environment) A=parser;E=next(A.stream).lineno;B=[Const('')];C=''
super(MarkdownExtension, self).__init__(environment) try:B=[A.parse_expression()]
except TemplateSyntaxError:C=A.parse_statements(['name:endmarkdown'],drop_needle=_A)
def parse(self, parser): return nodes.CallBlock(D.call_method('_to_html',B),[],[],C).set_lineno(E)
line_number = next(parser.stream).lineno def _to_html(A,md_file,caller):return render_markdown_sync(A.app,caller())
md_file = [Const("")]
body = ""
try:
md_file = [parser.parse_expression()]
except TemplateSyntaxError:
body = parser.parse_statements(["name:endmarkdown"], drop_needle=True)
return nodes.CallBlock(
self.call_method("_to_html", md_file), [], [], body
).set_lineno(line_number)
def _to_html(self, md_file, caller):
return render_markdown_sync(self.app, caller())

View File

@ -1,53 +1,21 @@
# Written by retoor@molodetz.nl _D='Access-Control-Allow-Credentials'
_C='Access-Control-Allow-Headers'
# This code provides middleware functions for an aiohttp server to manage and modify CORS (Cross-Origin Resource Sharing) headers. _B='Access-Control-Allow-Methods'
_A='Access-Control-Allow-Origin'
# Imports from 'aiohttp' library are used to create middleware; they are not part of Python's standard library.
# MIT License: This code is distributed under the MIT License.
from aiohttp import web from aiohttp import web
@web.middleware @web.middleware
async def no_cors_middleware(request, handler): async def no_cors_middleware(request,handler):A=await handler(request);A.headers.pop(_A,None);return A
response = await handler(request)
response.headers.pop("Access-Control-Allow-Origin", None)
return response
@web.middleware @web.middleware
async def cors_allow_middleware(request, handler): async def cors_allow_middleware(request,handler):A=await handler(request);A.headers[_A]='*';A.headers[_B]='GET, POST, OPTIONS, PUT, DELETE, MOVE, COPY, HEAD, LOCK, UNLOCK, PATCH, PROPFIND';A.headers[_C]='*';A.headers[_D]='true';return A
response = await handler(request)
response.headers["Access-Control-Allow-Origin"] = "*"
response.headers["Access-Control-Allow-Methods"] = (
"GET, POST, OPTIONS, PUT, DELETE, MOVE, COPY, HEAD, LOCK, UNLOCK, PATCH, PROPFIND"
)
response.headers["Access-Control-Allow-Headers"] = "*"
response.headers["Access-Control-Allow-Credentials"] = "true"
return response
@web.middleware @web.middleware
async def auth_middleware(request, handler): async def auth_middleware(request,handler):
request["user"] = None B='uid';C='user';A=request;A[C]=None
if request.session.get("uid") and request.session.get("logged_in"): if A.session.get(B)and A.session.get('logged_in'):A[C]=await A.app.services.user.get(uid=A.app.session.get(B))
request["user"] = await request.app.services.user.get( return await handler(A)
uid=request.app.session.get("uid")
)
return await handler(request)
@web.middleware @web.middleware
async def cors_middleware(request, handler): async def cors_middleware(request,handler):
if request.headers.get("Allow"): C='Allow';D=handler;B=request
return await handler(request) if B.headers.get(C):return await D(B)
A=await D(B)
response = await handler(request) if B.headers.get(C):return A
if request.headers.get("Allow"): A.headers[_A]='*';A.headers[_B]='GET, POST, PUT, DELETE, OPTIONS';A.headers[_C]='*';A.headers[_D]='true';return A
return response
response.headers["Access-Control-Allow-Origin"] = "*"
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
response.headers["Access-Control-Allow-Headers"] = "*"
response.headers["Access-Control-Allow-Credentials"] = "true"
return response

View File

@ -1,377 +1,139 @@
# Written by retoor@molodetz.nl _I='deleted_at'
_H='updated_at'
# The script defines a flexible validation and field management system for models, with capabilities for setting attributes, validation, error handling, and JSON conversion. It includes classes for managing various field types with specific properties such as UUID, timestamps for creation and updates, and custom validation rules. _G='created_at'
_F='is_valid'
# This script utilizes external Python libraries such as 're' for regex operations, 'uuid' for generating unique identifiers, and 'json' for data interchange. The 'datetime' and 'timezone' modules from the Python standard library are used for date and time operations. 'OrderedDict' from 'collections' provides enhanced dictionary capabilities, and 'copy' allows deep copying of objects. _E='name'
_D=False
# MIT License _C='value'
# _B=True
# Permission is hereby granted, free of charge, to any person obtaining a copy _A=None
# of this software and associated documentation files (the "Software"), to deal import copy,json,re,uuid
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import copy
import json
import re
import uuid
from collections import OrderedDict from collections import OrderedDict
from datetime import datetime, timezone from datetime import datetime,timezone
TIMESTAMP_REGEX='^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{6}\\+\\d{2}:\\d{2}$'
TIMESTAMP_REGEX = r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{6}\+\d{2}:\d{2}$" def now():return str(datetime.now(timezone.utc))
def add_attrs(**A):
def B(func):
def now(): for(B,C)in A.items():setattr(func,B,C)
return str(datetime.now(timezone.utc))
def add_attrs(**kwargs):
def decorator(func):
for key, value in kwargs.items():
setattr(func, key, value)
return func return func
return B
return decorator def validate_attrs(required=_D,min_length=_A,max_length=_A,regex=_A,**A):
def B(func):return add_attrs(required=required,min_length=min_length,max_length=max_length,regex=regex,**A)(func)
def validate_attrs(
required=False, min_length=None, max_length=None, regex=None, **kwargs
):
def decorator(func):
return add_attrs(
required=required,
min_length=min_length,
max_length=max_length,
regex=regex,
**kwargs,
)(func)
class Validator: class Validator:
_index = 0 _index=0
@property @property
def value(self): def value(self):return self._value
return self._value
@value.setter @value.setter
def value(self, val): def value(self,val):self._value=json.loads(json.dumps(val,default=str))
self._value = json.loads(json.dumps(val, default=str))
@property @property
def initial_value(self): def initial_value(self):return self.value
return self.value def custom_validation(A):return _B
def __init__(A,required=_D,min_num=_A,max_num=_A,min_length=_A,max_length=_A,regex=_A,value=_A,kind=_A,help_text=_A,app=_A,model=_A,**B):A.index=Validator._index;Validator._index+=1;A.app=app;A.model=model;A.required=required;A.min_num=min_num;A.max_num=max_num;A.min_length=min_length;A.max_length=max_length;A.regex=regex;A._value=_A;A.value=value;A.kind=kind;A.help_text=help_text;A.__dict__.update(B)
def custom_validation(self):
return True
def __init__(
self,
required=False,
min_num=None,
max_num=None,
min_length=None,
max_length=None,
regex=None,
value=None,
kind=None,
help_text=None,
app=None,
model=None,
**kwargs,
):
self.index = Validator._index
Validator._index += 1
self.app = app
self.model = model
self.required = required
self.min_num = min_num
self.max_num = max_num
self.min_length = min_length
self.max_length = max_length
self.regex = regex
self._value = None
self.value = value
self.kind = kind
self.help_text = help_text
self.__dict__.update(kwargs)
@property @property
async def errors(self): async def errors(self):
error_list = [] A=self;B=[]
if self.value is None and self.required: if A.value is _A and A.required:B.append('Field is required.');return B
error_list.append("Field is required.") if A.value is _A:return B
return error_list if A.kind in[int,float]:
if A.min_num is not _A and A.value<A.min_num:B.append(f"Field should be minimal {A.min_num}.")
if self.value is None: if A.max_num is not _A and A.value>A.max_num:B.append(f"Field should be maximal {A.max_num}.")
return error_list if A.min_length is not _A and len(A.value)<A.min_length:B.append(f"Field should be minimal {A.min_length} characters long.")
if A.max_length is not _A and len(A.value)>A.max_length:B.append(f"Field should be maximal {A.max_length} characters long.")
if self.kind in [int, float]: if A.regex and A.value and not re.match(A.regex,A.value):B.append('Invalid value.')
if self.min_num is not None and self.value < self.min_num: if A.kind and not isinstance(A.value,A.kind):B.append(f"Invalid kind. It is supposed to be {A.kind}.")
error_list.append(f"Field should be minimal {self.min_num}.") return B
if self.max_num is not None and self.value > self.max_num: async def validate(B):
error_list.append(f"Field should be maximal {self.max_num}.") A=await B.errors
if self.min_length is not None and len(self.value) < self.min_length: if A:raise ValueError(f"Errors: {A}.")
error_list.append( return _B
f"Field should be minimal {self.min_length} characters long." def __repr__(A):return str(A.to_json())
)
if self.max_length is not None and len(self.value) > self.max_length:
error_list.append(
f"Field should be maximal {self.max_length} characters long."
)
if self.regex and self.value and not re.match(self.regex, self.value):
error_list.append("Invalid value.")
if self.kind and not isinstance(self.value, self.kind):
error_list.append(f"Invalid kind. It is supposed to be {self.kind}.")
return error_list
async def validate(self):
errors = await self.errors
if errors:
raise ValueError(f"Errors: {errors}.")
return True
def __repr__(self):
return str(self.to_json())
@property @property
async def is_valid(self): async def is_valid(self):
try: try:await self.validate();return _B
await self.validate() except ValueError:return _D
return True async def to_json(A):B=await A.errors;C=await A.is_valid;return{'required':A.required,'min_num':A.min_num,'max_num':A.max_num,'min_length':A.min_length,'max_length':A.max_length,'regex':A.regex,_C:A.value,'kind':str(A.kind),'help_text':A.help_text,'errors':B,_F:C,'index':A.index}
except ValueError:
return False
async def to_json(self):
errors = await self.errors
is_valid = await self.is_valid
return {
"required": self.required,
"min_num": self.min_num,
"max_num": self.max_num,
"min_length": self.min_length,
"max_length": self.max_length,
"regex": self.regex,
"value": self.value,
"kind": str(self.kind),
"help_text": self.help_text,
"errors": errors,
"is_valid": is_valid,
"index": self.index,
}
class ModelField(Validator): class ModelField(Validator):
index=1
index = 1 def __init__(A,name=_A,save=_B,*B,**C):A.name=name;A.save=save;super().__init__(*B,**C)
async def to_json(B):A=await super().to_json();A[_E]=B.name;return A
def __init__(self, name=None, save=True, *args, **kwargs):
self.name = name
self.save = save
super().__init__(*args, **kwargs)
async def to_json(self):
result = await super().to_json()
result["name"] = self.name
return result
class CreatedField(ModelField): class CreatedField(ModelField):
@property @property
def initial_value(self): def initial_value(self):return now()
return now() def update(A):
if not A.value:A.value=now()
def update(self):
if not self.value:
self.value = now()
class UpdatedField(ModelField): class UpdatedField(ModelField):
def update(A):A.value=now()
def update(self):
self.value = now()
class DeletedField(ModelField): class DeletedField(ModelField):
def update(A):A.value=now()
def update(self):
self.value = now()
class UUIDField(ModelField): class UUIDField(ModelField):
@property @property
def value(self): def value(self):return str(self._value)
return str(self._value)
@value.setter @value.setter
def value(self, val): def value(self,val):self._value=str(val)
self._value = str(val)
@property @property
def initial_value(self): def initial_value(self):return str(uuid.uuid4())
return str(uuid.uuid4())
class BaseModel: class BaseModel:
uid=UUIDField(name='uid',required=_B);created_at=CreatedField(name=_G,required=_B,regex=TIMESTAMP_REGEX,place_holder='Created at');updated_at=UpdatedField(name=_H,regex=TIMESTAMP_REGEX,place_holder='Updated at');deleted_at=DeletedField(name=_I,regex=TIMESTAMP_REGEX,place_holder='Deleted at')
uid = UUIDField(name="uid", required=True)
created_at = CreatedField(
name="created_at",
required=True,
regex=TIMESTAMP_REGEX,
place_holder="Created at",
)
updated_at = UpdatedField(
name="updated_at", regex=TIMESTAMP_REGEX, place_holder="Updated at"
)
deleted_at = DeletedField(
name="deleted_at", regex=TIMESTAMP_REGEX, place_holder="Deleted at"
)
@classmethod @classmethod
async def from_record(cls, record, mapper): async def from_record(B,record,mapper):A=B();A.mapper=mapper;A.record=record;return A
model = cls()
model.mapper = mapper
model.record = record
return model
@property @property
def mapper(self): def mapper(self):return self._mapper
return self._mapper
@mapper.setter @mapper.setter
def mapper(self, value): def mapper(self,value):self._mapper=value
self._mapper = value
@property @property
def record(self): def record(self):return{A:B.value for(A,B)in self.fields.items()}
return {key: field.value for key, field in self.fields.items()}
@record.setter @record.setter
def record(self, val): def record(self,val):
for key, value in val.items(): A=self
field = self.fields.get(key) for(B,C)in val.items():
if not field: D=A.fields.get(B)
continue if not D:continue
self[key] = value A[B]=C
return self return A
def __init__(A,*F,**C):
def __init__(self, *args, **kwargs): D='app';A._mapper=C.get('mapper');A.app=C.get(D);A.fields={}
self._mapper = kwargs.get("mapper") for B in dir(A.__class__):
self.app = kwargs.get("app") E=getattr(A.__class__,B)
self.fields = {} if isinstance(E,Validator):A.__dict__[B]=copy.deepcopy(E);A.__dict__[B].value=C.pop(B,A.__dict__[B].initial_value);A.fields[B]=A.__dict__[B];A.fields[B].model=A;A.fields[B].app=C.get(D)
for key in dir(self.__class__): def __setitem__(B,key,value):
obj = getattr(self.__class__, key) A=B.__dict__.get(key)
if isinstance(A,Validator):A.value=value
if isinstance(obj, Validator): def __getattr__(B,key):
self.__dict__[key] = copy.deepcopy(obj) A=B.__dict__.get(key)
self.__dict__[key].value = kwargs.pop( if isinstance(A,Validator):return A.value
key, self.__dict__[key].initial_value return A
) def set_user_data(C,data):
self.fields[key] = self.__dict__[key] for(D,A)in data.items():
self.fields[key].model = self B=C.fields.get(D)
self.fields[key].app = kwargs.get("app") if not B:continue
if A.get(_E):A=A.get(_C)
def __setitem__(self, key, value): B.value=A
obj = self.__dict__.get(key)
if isinstance(obj, Validator):
obj.value = value
def __getattr__(self, key):
obj = self.__dict__.get(key)
if isinstance(obj, Validator):
return obj.value
return obj
def set_user_data(self, data):
for key, value in data.items():
field = self.fields.get(key)
if not field:
continue
if value.get("name"):
value = value.get("value")
field.value = value
@property @property
async def is_valid(self): async def is_valid(self):return all([await A.is_valid for A in self.fields.values()])
return all([await field.is_valid for field in self.fields.values()]) def __getitem__(B,key):
A=B.__dict__.get(key)
def __getitem__(self, key): if isinstance(A,Validator):return A.value
obj = self.__dict__.get(key) def __setattr__(A,key,value):
if isinstance(obj, Validator): B=value;C=getattr(A,key)
return obj.value if isinstance(C,Validator):C.value=B
else:A.__dict__[key]=B
def __setattr__(self, key, value):
obj = getattr(self, key)
if isinstance(obj, Validator):
obj.value = value
else:
self.__dict__[key] = value
@property @property
async def recordz(self): async def recordz(self):
obj = await self.to_json() D=await self.to_json();B={}
record = {} for(C,A)in D.items():
for key, value in obj.items(): if not isinstance(A,dict)or _C not in A:continue
if not isinstance(value, dict) or "value" not in value: if getattr(self,C).save:B[C]=A.get(_C)
continue return B
if getattr(self, key).save: async def to_json(A,encode=_D):
record[key] = value.get("value") B=OrderedDict({'uid':A.uid.value,_G:A.created_at.value,_H:A.updated_at.value,_I:A.deleted_at.value,_F:await A.is_valid})
return record for(C,D)in A.fields.items():
if C=='record':continue
async def to_json(self, encode=False): D=A.__dict__[C]
model_data = OrderedDict( if hasattr(D,_C):B[C]=await D.to_json()
{ if encode:return json.dumps(B,indent=2)
"uid": self.uid.value, return B
"created_at": self.created_at.value,
"updated_at": self.updated_at.value,
"deleted_at": self.deleted_at.value,
"is_valid": await self.is_valid,
}
)
for key, value in self.fields.items():
if key == "record":
continue
value = self.__dict__[key]
if hasattr(value, "value"):
model_data[key] = await value.to_json()
if encode:
return json.dumps(model_data, indent=2)
return model_data
class FormElement(ModelField): class FormElement(ModelField):
def __init__(A,place_holder=_A,*B,**C):super().__init__(*B,**C);A.place_holder=place_holder
def __init__(self, place_holder=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.place_holder = place_holder
class FormElement(ModelField): class FormElement(ModelField):
def __init__(A,place_holder=_A,*B,**C):A.place_holder=place_holder;super().__init__(*B,**C)
def __init__(self, place_holder=None, *args, **kwargs): async def to_json(B):A=await super().to_json();A[_E]=B.name;A['place_holder']=B.place_holder;return A
self.place_holder = place_holder
super().__init__(*args, **kwargs)
async def to_json(self):
data = await super().to_json()
data["name"] = self.name
data["place_holder"] = self.place_holder
return data

View File

@ -1,13 +1,7 @@
class Object: class Object:
def __init__(A,*C,**D):
def __init__(self, *args, **kwargs): for B in C:
for arg in args: if isinstance(B,dict):A.__dict__.update(B)
if isinstance(arg, dict): A.__dict__.update(D)
self.__dict__.update(arg) def __getitem__(A,key):return A.__dict__[key]
self.__dict__.update(kwargs) def __setitem__(A,key,value):A.__dict__[key]=value
def __getitem__(self, key):
return self.__dict__[key]
def __setitem__(self, key, value):
self.__dict__[key] = value

View File

@ -1,46 +1,17 @@
import cProfile import cProfile,pstats,sys
import pstats
import sys
from aiohttp import web from aiohttp import web
profiler=None
profiler = None
import io import io
@web.middleware @web.middleware
async def profile_middleware(request, handler): async def profile_middleware(request,handler):
global profiler global profiler
if not profiler: if not profiler:profiler=cProfile.Profile()
profiler = cProfile.Profile() profiler.enable();B=await handler(request);profiler.disable();A=pstats.Stats(profiler,stream=sys.stdout);A.sort_stats('cumulative');A.print_stats();return B
profiler.enable() async def profiler_handler(request):A=io.StringIO();B=pstats.Stats(profiler,stream=A);C=request.query.get('sort','tot. percall');B.sort_stats(C);B.print_stats();return web.Response(text=A.getvalue())
response = await handler(request)
profiler.disable()
stats = pstats.Stats(profiler, stream=sys.stdout)
stats.sort_stats("cumulative")
stats.print_stats()
return response
async def profiler_handler(request):
output = io.StringIO()
stats = pstats.Stats(profiler, stream=output)
sort_by = request.query.get("sort", "tot. percall")
stats.sort_stats(sort_by)
stats.print_stats()
return web.Response(text=output.getvalue())
class Profiler: class Profiler:
def __init__(A):
def __init__(self):
global profiler global profiler
if profiler is None: if profiler is None:profiler=cProfile.Profile()
profiler = cProfile.Profile() A.profiler=profiler
self.profiler = profiler async def __aenter__(A):A.profiler.enable()
async def __aexit__(A,*B,**C):A.profiler.disable()
async def __aenter__(self):
self.profiler.enable()
async def __aexit__(self, *args, **kwargs):
self.profiler.disable()

View File

@ -1,77 +1,24 @@
import hashlib _A='snekker-de-snek-'
import uuid import hashlib,uuid
DEFAULT_SALT=_A
DEFAULT_SALT = "snekker-de-snek-" DEFAULT_NS=_A
DEFAULT_NS = "snekker-de-snek-"
class UIDNS: class UIDNS:
def __init__(self, name: str) -> None: def __init__(A,name):'Initialize UIDNS with a name.';A.name=name
"""Initialize UIDNS with a name."""
self.name = name
@property @property
def bytes(self) -> bytes: def bytes(self):'Return the bytes representation of the name.';return self.name.encode()
"""Return the bytes representation of the name.""" def uid(value=None,ns=DEFAULT_NS):
return self.name.encode() 'Generate a UUID based on the provided value and namespace.\n\n Args:\n value (str): The value to generate the UUID from. If None, a new UUID is created.\n ns (str): The namespace to use for UUID generation.\n\n Returns:\n str: The generated UUID as a string.\n ';A=value
try:ns=ns.decode()
except AttributeError:pass
def uid(value: str = None, ns: str = DEFAULT_NS) -> str: if not A:A=str(uuid.uuid4())
"""Generate a UUID based on the provided value and namespace. try:A=A.decode()
except AttributeError:pass
Args: return str(uuid.uuid5(UIDNS(ns),A))
value (str): The value to generate the UUID from. If None, a new UUID is created. async def hash(data,salt=DEFAULT_SALT):
ns (str): The namespace to use for UUID generation. 'Hash the given data with the specified salt using SHA-256.\n\n Args:\n data (str): The data to hash.\n salt (str): The salt to use for hashing.\n\n Returns:\n str: The hexadecimal representation of the hashed data.\n ';C='ignore';A=salt;B=data
try:B=B.encode(errors=C)
Returns: except AttributeError:pass
str: The generated UUID as a string. try:A=A.encode(errors=C)
""" except AttributeError:pass
try: D=A+B;E=hashlib.sha256(D);return E.hexdigest()
ns = ns.decode() async def verify(string,hashed):'Verify if the given string matches the hashed value.\n\n Args:\n string (str): The string to verify.\n hashed (str): The hashed value to compare against.\n\n Returns:\n bool: True if the string matches the hashed value, False otherwise.\n ';return await hash(string)==hashed
except AttributeError:
pass
if not value:
value = str(uuid.uuid4())
try:
value = value.decode()
except AttributeError:
pass
return str(uuid.uuid5(UIDNS(ns), value))
async def hash(data: str, salt: str = DEFAULT_SALT) -> str:
"""Hash the given data with the specified salt using SHA-256.
Args:
data (str): The data to hash.
salt (str): The salt to use for hashing.
Returns:
str: The hexadecimal representation of the hashed data.
"""
try:
data = data.encode(errors="ignore")
except AttributeError:
pass
try:
salt = salt.encode(errors="ignore")
except AttributeError:
pass
salted = salt + data
obj = hashlib.sha256(salted)
return obj.hexdigest()
async def verify(string: str, hashed: str) -> bool:
"""Verify if the given string matches the hashed value.
Args:
string (str): The string to verify.
hashed (str): The hashed value to compare against.
Returns:
bool: True if the string matches the hashed value, False otherwise.
"""
return await hash(string) == hashed

View File

@ -1,67 +1,42 @@
_B='uid'
_A=None
from snek.mapper import get_mapper from snek.mapper import get_mapper
from snek.model.user import UserModel from snek.model.user import UserModel
from snek.system.mapper import BaseMapper from snek.system.mapper import BaseMapper
class BaseService: class BaseService:
mapper_name:BaseMapper=_A
mapper_name: BaseMapper = None
@property @property
def services(self): def services(self):return self.app.services
return self.app.services def __init__(A,app):
A.app=app;A.cache=app.cache
def __init__(self, app): if A.mapper_name:A.mapper=get_mapper(A.mapper_name,app=A.app)
self.app = app else:A.mapper=_A
self.cache = app.cache async def exists(C,uid=_A,**A):
if self.mapper_name: B=uid
self.mapper = get_mapper(self.mapper_name, app=self.app) if B:
else: if not A and await C.cache.get(B):return True
self.mapper = None A[_B]=B
return await C.count(**A)>0
async def exists(self, uid=None, **kwargs): async def count(A,**B):return await A.mapper.count(**B)
if uid: async def new(A,**B):return await A.mapper.new()
if not kwargs and await self.cache.get(uid): async def query(A,sql,*B):
return True for C in A.app.db.query(sql,*B):yield C
kwargs["uid"] = uid async def get(B,uid=_A,**C):
return await self.count(**kwargs) > 0 D=uid
if D:
async def count(self, **kwargs): if not C:
return await self.mapper.count(**kwargs) A=await B.cache.get(D)
if False and A and A.__class__==B.mapper.model_class:return A
async def new(self, **kwargs): C[_B]=D
return await self.mapper.new() A=await B.mapper.get(**C)
if A:await B.cache.set(A[_B],A)
async def query(self, sql, *args): return A
for record in self.app.db.query(sql, *args): async def save(B,model):
yield record A=model
if await B.mapper.save(A):await B.cache.set(A[_B],A);return True
async def get(self, uid=None, **kwargs): C=await A.errors;raise Exception(f"Couldn't save model. Errors: f{C}")
if uid: async def find(C,**A):
if not kwargs: B='_limit'
result = await self.cache.get(uid) if B not in A or int(A.get(B))>30:A[B]=60
if False and result and result.__class__ == self.mapper.model_class: async for D in C.mapper.find(**A):yield D
return result async def delete(A,**B):return await A.mapper.delete(**B)
kwargs["uid"] = uid
result = await self.mapper.get(**kwargs)
if result:
await self.cache.set(result["uid"], result)
return result
async def save(self, model: UserModel):
# if model.is_valid: You Know why not
if await self.mapper.save(model):
await self.cache.set(model["uid"], model)
return True
errors = await model.errors
raise Exception(f"Couldn't save model. Errors: f{errors}")
async def find(self, **kwargs):
if "_limit" not in kwargs or int(kwargs.get("_limit")) > 30:
kwargs["_limit"] = 60
async for model in self.mapper.find(**kwargs):
yield model
async def delete(self, **kwargs):
return await self.mapper.delete(**kwargs)

File diff suppressed because one or more lines are too long

View File

@ -1,113 +1,49 @@
import asyncio _A=None
import os import asyncio,os
try:import pty
try: except Exception as ex:print('You are not able to run a terminal. See error:');print(ex)
import pty
except Exception as ex:
print("You are not able to run a terminal. See error:")
print(ex)
import subprocess import subprocess
commands={'alpine':'docker run -it alpine /bin/sh','r':'docker run -v /usr/local/bin:/usr/local/bin -it ubuntu:latest run.sh'}
commands = {
"alpine": "docker run -it alpine /bin/sh",
"r": "docker run -v /usr/local/bin:/usr/local/bin -it ubuntu:latest run.sh",
}
class TerminalSession: class TerminalSession:
def __init__(self, command): def __init__(A,command):A.master,A.slave=_A,_A;A.process=_A;A.sockets=[];A.history=b'';A.history_size=20480;A.command=command;A.start_process(A.command)
self.master, self.slave = None, None def start_process(A,command):
self.process = None if not A.is_running():
self.sockets = [] if A.master:os.close(A.master);os.close(A.slave);A.master=_A;A.slave=_A
self.history = b"" A.master,A.slave=pty.openpty();A.process=subprocess.Popen(command.split(' '),stdin=A.slave,stdout=A.slave,stderr=A.slave,bufsize=0,universal_newlines=True)
self.history_size = 1024 * 20 def is_running(A):
self.command = command if not A.process:return False
self.start_process(self.command) asyncio.get_event_loop();return A.process.poll()is _A
async def add_websocket(A,ws):A.start_process(A.command);asyncio.create_task(A.read_output(ws))
def start_process(self, command): async def read_output(A,ws):
if not self.is_running(): B=ws;A.sockets.append(B)
if self.master: if len(A.sockets)>1 and A.history:
os.close(self.master) D=0
os.close(self.slave) try:D=A.history.index(b'\n')
self.master = None except ValueError:pass
self.slave = None await B.send_bytes(A.history[D:]);return
E=asyncio.get_event_loop()
self.master, self.slave = pty.openpty()
self.process = subprocess.Popen(
command.split(" "),
stdin=self.slave,
stdout=self.slave,
stderr=self.slave,
bufsize=0,
universal_newlines=True,
)
def is_running(self):
if not self.process:
return False
asyncio.get_event_loop()
return self.process.poll() is None
async def add_websocket(self, ws):
self.start_process(self.command)
asyncio.create_task(self.read_output(ws))
async def read_output(self, ws):
self.sockets.append(ws)
if len(self.sockets) > 1 and self.history:
start = 0
try:
start = self.history.index(b"\n")
except ValueError:
pass
await ws.send_bytes(self.history[start:])
return
loop = asyncio.get_event_loop()
while True: while True:
try: try:
data = await loop.run_in_executor(None, os.read, self.master, 1024) C=await E.run_in_executor(_A,os.read,A.master,1024)
if not data: if not C:break
break A.history+=C
self.history += data if len(A.history)>A.history_size:A.history=A.history[:0-A.history_size]
if len(self.history) > self.history_size:
self.history = self.history[: 0 - self.history_size]
try: try:
for ws in self.sockets: for B in A.sockets:await B.send_bytes(C)
await ws.send_bytes(data) # Send raw bytes for ANSI support except:A.sockets.remove(B)
except: except Exception:await A.close();break
self.sockets.remove(ws) async def close(A):
except Exception: print('Terminating process')
await self.close() if A.process:A.process.terminate();A.process=_A
break if A.master:os.close(A.master);os.close(A.slave);A.master=_A;A.slave=_A
print('Terminated process')
async def close(self): for B in A.sockets:
print("Terminating process") try:await B.close()
if self.process: except Exception:pass
self.process.terminate() A.sockets=[]
self.process = None async def write_input(B,data):
if self.master: A=data
os.close(self.master) try:A=A.encode()
os.close(self.slave) except AttributeError:pass
self.master = None try:await asyncio.get_event_loop().run_in_executor(_A,os.write,B.master,A)
self.slave = None except Exception as C:print(C);await B.close()
print("Terminated process")
for ws in self.sockets:
try:
await ws.close()
except Exception:
pass
self.sockets = []
async def write_input(self, data):
try:
data = data.encode()
except AttributeError:
pass
try:
await asyncio.get_event_loop().run_in_executor(
None, os.write, self.master, data
)
except Exception as ex:
print(ex)
await self.close()

View File

@ -1,75 +1,31 @@
from aiohttp import web from aiohttp import web
from snek.system.markdown import render_markdown from snek.system.markdown import render_markdown
class BaseView(web.View): class BaseView(web.View):
login_required=False
login_required = False async def _iter(A):
if A.login_required and(not A.session.get('logged_in')or not A.session.get('uid')):return web.HTTPFound('/')
async def _iter(self):
if self.login_required and (
not self.session.get("logged_in") or not self.session.get("uid")
):
return web.HTTPFound("/")
return await super()._iter() return await super()._iter()
@property @property
def base_url(self): def base_url(self):return str(self.request.url.with_path('').with_query(''))
return str(self.request.url.with_path("").with_query(""))
@property @property
def app(self): def app(self):return self.request.app
return self.request.app
@property @property
def db(self): def db(self):return self.app.db
return self.app.db
@property @property
def services(self): def services(self):return self.app.services
return self.app.services async def json_response(B,data,**A):return web.json_response(data,**A)
async def json_response(self, data, **kwargs):
return web.json_response(data, **kwargs)
@property @property
def session(self): def session(self):return self.request.session
return self.request.session async def render_template(A,template_name,context=None):
C=context;B=template_name
async def render_template(self, template_name, context=None): if B.endswith('.md'):D=await A.request.app.render_template(B,A.request,C);E=await render_markdown(A.app,D.body.decode());return web.Response(body=E,content_type='text/html')
if template_name.endswith(".md"): return await A.request.app.render_template(B,A.request,C)
response = await self.request.app.render_template(
template_name, self.request, context
)
body = await render_markdown(self.app, response.body.decode())
return web.Response(body=body, content_type="text/html")
return await self.request.app.render_template(
template_name, self.request, context
)
class BaseFormView(BaseView): class BaseFormView(BaseView):
form=None
form = None async def get(A):B=A.form(app=A.app);return await A.json_response(await B.to_json())
async def post(A):
async def get(self): E='action';C=A.form(app=A.app);D=await A.request.json();C.set_user_data(D['form']);B=await C.to_json()
form = self.form(app=self.app) if D.get(E)=='validate':0
if D.get(E)=='submit'and B['is_valid']:B=await A.submit(C);return await A.json_response(B)
return await self.json_response(await form.to_json()) return await A.json_response(B)
async def submit(A,model=None):0
async def post(self):
form = self.form(app=self.app)
post = await self.request.json()
form.set_user_data(post["form"])
result = await form.to_json()
if post.get("action") == "validate":
# Pass
pass
if post.get("action") == "submit" and result["is_valid"]:
result = await self.submit(form)
return await self.json_response(result)
return await self.json_response(result)
async def submit(self, model=None):
pass

View File

@ -1,39 +1,5 @@
# Written by retoor@molodetz.nl
# This source code defines two classes, `AboutHTMLView` and `AboutMDView`, both inheriting from `BaseView`. They asynchronously return rendered templates for HTML and Markdown respectively.
# External Import: `BaseView` from `snek.system.view`
# MIT License
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from snek.system.view import BaseView from snek.system.view import BaseView
class AboutHTMLView(BaseView): class AboutHTMLView(BaseView):
async def get(A):return await A.render_template('about.html')
async def get(self):
return await self.render_template("about.html")
class AboutMDView(BaseView): class AboutMDView(BaseView):
async def get(A):return await A.render_template('about.md')
async def get(self):
return await self.render_template("about.md")

View File

@ -1,44 +1,10 @@
# Written by retoor@molodetz.nl
# This code defines a WebView class that inherits from BaseView and includes a method for rendering a web template, requiring login access for its usage.
# The code imports the BaseView class from the `snek.system.view` module.
# MIT License
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import uuid import uuid
from aiohttp import web from aiohttp import web
from multiavatar import multiavatar from multiavatar import multiavatar
from snek.system.view import BaseView from snek.system.view import BaseView
class AvatarView(BaseView): class AvatarView(BaseView):
login_required = False login_required=False
async def get(C):
async def get(self): A=C.request.match_info.get('uid')
uid = self.request.match_info.get("uid") if A=='unique':A=str(uuid.uuid4())
if uid == "unique": D=multiavatar.multiavatar(A,True,None);B=web.Response(text=D,content_type='image/svg+xml');B.headers['Cache-Control']=f"public, max-age={56154}";return B
uid = str(uuid.uuid4())
avatar = multiavatar.multiavatar(uid, True, None)
response = web.Response(text=avatar, content_type="image/svg+xml")
response.headers["Cache-Control"] = f"public, max-age={1337*42}"
return response

View File

@ -1,37 +1,5 @@
# Written by retoor@molodetz.nl
# This code defines two classes, DocsHTMLView and DocsMDView, which are intended to asynchronously render HTML and Markdown templates respectively. Both classes inherit from the BaseView class.
# Dependencies: BaseView is imported from the "snek.system.view" package.
# MIT License
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from snek.system.view import BaseView from snek.system.view import BaseView
class DocsHTMLView(BaseView): class DocsHTMLView(BaseView):
async def get(A):return await A.render_template('docs.html')
async def get(self):
return await self.render_template("docs.html")
class DocsMDView(BaseView): class DocsMDView(BaseView):
async def get(A):return await A.render_template('docs.md')
async def get(self):
return await self.render_template("docs.md")

View File

@ -1,269 +1,99 @@
_P='Path not found'
_O='application/octet-stream'
_N='items'
_M='size'
_L='mimetype'
_K='name'
_J='rel_path'
_I='dir'
_H='url'
_G=None
_F='path'
_E='status'
_D='file'
_C='uid'
_B='absolute_url'
_A='type'
from aiohttp import web from aiohttp import web
from snek.system.view import BaseView from snek.system.view import BaseView
import os,mimetypes
import os
import mimetypes
from aiohttp import web from aiohttp import web
from urllib.parse import unquote, quote from urllib.parse import unquote,quote
from datetime import datetime from datetime import datetime
'Run with: python server.py (Python\xa0\xa03.9)\nVisit http://localhost:8080 to try the demo.\n'
"""Run with: python server.py (Python  3.9)
Visit http://localhost:8080 to try the demo.
"""
from aiohttp import web from aiohttp import web
from pathlib import Path from pathlib import Path
import mimetypes, urllib.parse import mimetypes,urllib.parse
BASE_DIR=Path(__file__).parent.resolve()
# ---------- Configuration -------------------------------------------------- ROOT_DIR=(BASE_DIR/'storage').resolve()
BASE_DIR = Path(__file__).parent.resolve() ASSETS_DIR=(BASE_DIR/'assets').resolve()
ROOT_DIR = (BASE_DIR / "storage").resolve() # files shown to the outside world
ASSETS_DIR = (BASE_DIR / "assets").resolve() # JS & demo HTML
ROOT_DIR.mkdir(exist_ok=True) ROOT_DIR.mkdir(exist_ok=True)
ASSETS_DIR.mkdir(exist_ok=True) ASSETS_DIR.mkdir(exist_ok=True)
def safe_resolve_path(rel):
# ---------- Helpers -------------------------------------------------------- 'Return *absolute* path inside ROOT_DIR or raise FileNotFoundError.';A=(ROOT_DIR/rel.lstrip('/')).resolve()
if A==ROOT_DIR or ROOT_DIR in A.parents:return A
def safe_resolve_path(rel: str) -> Path: raise FileNotFoundError('Unsafe path')
"""Return *absolute* path inside ROOT_DIR or raise FileNotFoundError."""
target = (ROOT_DIR / rel.lstrip("/")).resolve()
if target == ROOT_DIR or ROOT_DIR in target.parents:
return target
raise FileNotFoundError("Unsafe path")
# ---------- API view -------------------------------------------------------
class DriveView(BaseView): class DriveView(BaseView):
async def get(self): async def get(C):
rel = self.request.query.get("path", "") H='limit';I='offset';D=C.request.query.get(_F,'');E=int(C.request.query.get(I,0));J=int(C.request.query.get(H,20));A=await C.services.user.get_home_folder(C.session.get(_C))
offset = int(self.request.query.get("offset", 0)) if D:A.joinpath(D)
limit = int(self.request.query.get("limit", 20)) if not A.exists():return web.json_response({'error':'Not found'},status=404)
target = await self.services.user.get_home_folder(self.session.get("uid")) if A.is_dir():
if rel: F=[]
target.joinpath(rel) for B in sorted(A.iterdir(),key=lambda p:(p.is_file(),p.name.lower())):K=(Path(D)/B.name).as_posix();M=mimetypes.guess_type(B.name)[0]if B.is_file()else'inode/directory';G=C.request.url.with_path(f"/drive/{urllib.parse.quote(K)}")if B.is_file()else _G;F.append({_K:B.name,_A:'directory'if B.is_dir()else _D,_L:M,_M:B.stat().st_size if B.is_file()else _G,_F:K,_H:G})
import json as L;N=len(F);O=F[E:E+J];return web.json_response({_N:L.loads(L.dumps(O,default=str)),'pagination':{I:E,H:J,'total':N}})
if not target.exists(): with open(A,'rb')as P:Q=P.read();return web.Response(body=Q,content_type=mimetypes.guess_type(A.name)[0])
return web.json_response({"error": "Not found"}, status=404) G=C.request.url.with_path(f"/drive/{urllib.parse.quote(D)}");return web.json_response({_K:A.name,_A:_D,_L:mimetypes.guess_type(A.name)[0],_M:A.stat().st_size,_F:D,_H:str(G)})
# ---- Directory listing -------------------------------------------
if target.is_dir():
entries = []
# Directories first, then files both alphabetical (caseinsensitive)
for p in sorted(target.iterdir(), key=lambda p: (p.is_file(), p.name.lower())):
item_path = (Path(rel) / p.name).as_posix()
mime = mimetypes.guess_type(p.name)[0] if p.is_file() else "inode/directory"
url = (self.request.url.with_path(f"/drive/{urllib.parse.quote(item_path)}")
if p.is_file() else None)
entries.append({
"name": p.name,
"type": "directory" if p.is_dir() else "file",
"mimetype": mime,
"size": p.stat().st_size if p.is_file() else None,
"path": item_path,
"url": url,
})
import json
total = len(entries)
items = entries[offset:offset+limit]
return web.json_response({
"items": json.loads(json.dumps(items,default=str)),
"pagination": {"offset": offset, "limit": limit, "total": total}
})
with open(target, "rb") as f:
content = f.read()
return web.Response(body=content, content_type=mimetypes.guess_type(target.name)[0])
# ---- Single file metadata ----------------------------------------
url = self.request.url.with_path(f"/drive/{urllib.parse.quote(rel)}")
return web.json_response({
"name": target.name,
"type": "file",
"mimetype": mimetypes.guess_type(target.name)[0],
"size": target.stat().st_size,
"path": rel,
"url": str(url),
})
class DriveView222(BaseView): class DriveView222(BaseView):
PAGE_SIZE = 20 PAGE_SIZE=20
async def base_path(A):return await A.services.user.get_home_folder(A.session.get(_C))
async def base_path(self): async def get_full_path(C,rel_path):
return await self.services.user.get_home_folder(self.session.get("uid")) A=await C.base_path();D=os.path.normpath(unquote(rel_path or''));B=os.path.abspath(os.path.join(A,D))
if not B.startswith(os.path.abspath(A)):raise web.HTTPForbidden(reason='Invalid path')
async def get_full_path(self, rel_path): return B
base_path = await self.base_path() async def make_absolute_url(B,rel_path):A=rel_path;A=A.lstrip('/');C=str(B.request.url.with_path(f"/drive/{quote(A)}"));return C
safe_path = os.path.normpath(unquote(rel_path or "")) async def entry_details(E,dir_path,entry,parent_rel_path):A=entry;B=os.path.join(dir_path,A);C=os.stat(B);D=os.path.isdir(B);F=_G if D else mimetypes.guess_type(B)[0]or _O;G=C.st_size if not D else _G;H=datetime.fromtimestamp(C.st_ctime).isoformat();I=datetime.fromtimestamp(C.st_mtime).isoformat();J=os.path.join(parent_rel_path,A).replace('\\','/');return{_K:A,_A:_I if D else _D,_L:F,_M:G,'created_at':H,'updated_at':I,_B:await E.make_absolute_url(J)}
full_path = os.path.abspath(os.path.join(base_path, safe_path)) async def get(A):
if not full_path.startswith(os.path.abspath(base_path)): F='page_size';G='page';C=A.request.match_info.get(_J,'');B=await A.get_full_path(C);H=int(A.request.query.get(G,1));D=int(A.request.query.get(F,A.PAGE_SIZE));I=await A.make_absolute_url(C)
raise web.HTTPForbidden(reason="Invalid path") if not os.path.exists(B):raise web.HTTPNotFound(reason=_P)
return full_path if os.path.isdir(B):E=os.listdir(B);E.sort();J=(H-1)*D;K=J+D;L=E[J:K];M=[await A.entry_details(B,D,C)for D in L];return web.json_response({_F:C,_B:I,'entries':M,'total':len(E),G:H,F:D})
async def make_absolute_url(self, rel_path):
rel_path = rel_path.lstrip("/")
url = str(self.request.url.with_path(f"/drive/{quote(rel_path)}"))
return url
async def entry_details(self, dir_path, entry, parent_rel_path):
entry_path = os.path.join(dir_path, entry)
stat = os.stat(entry_path)
is_dir = os.path.isdir(entry_path)
mimetype = None if is_dir else (mimetypes.guess_type(entry_path)[0] or "application/octet-stream")
size = stat.st_size if not is_dir else None
created_at = datetime.fromtimestamp(stat.st_ctime).isoformat()
updated_at = datetime.fromtimestamp(stat.st_mtime).isoformat()
rel_entry_path = os.path.join(parent_rel_path, entry).replace("\\", "/")
return {
"name": entry,
"type": "dir" if is_dir else "file",
"mimetype": mimetype,
"size": size,
"created_at": created_at,
"updated_at": updated_at,
"absolute_url": await self.make_absolute_url(rel_entry_path),
}
async def get(self):
rel_path = self.request.match_info.get("rel_path", "")
full_path = await self.get_full_path(rel_path)
page = int(self.request.query.get("page", 1))
page_size = int(self.request.query.get("page_size", self.PAGE_SIZE))
abs_url = await self.make_absolute_url(rel_path)
if not os.path.exists(full_path):
raise web.HTTPNotFound(reason="Path not found")
if os.path.isdir(full_path):
entries = os.listdir(full_path)
entries.sort()
start = (page - 1) * page_size
end = start + page_size
paged_entries = entries[start:end]
details = [await self.entry_details(full_path, entry, rel_path) for entry in paged_entries]
return web.json_response({
"path": rel_path,
"absolute_url": abs_url,
"entries": details,
"total": len(entries),
"page": page,
"page_size": page_size,
})
else: else:
with open(full_path, "rb") as f: with open(B,'rb')as N:O=N.read()
content = f.read() P=mimetypes.guess_type(B)[0]or _O;Q={'X-Absolute-Url':I};return web.Response(body=O,content_type=P,headers=Q)
mimetype = mimetypes.guess_type(full_path)[0] or "application/octet-stream" async def post(A):
headers = {"X-Absolute-Url": abs_url} C='created';D=A.request.match_info.get(_J,'');B=await A.get_full_path(D);E=await A.make_absolute_url(D)
return web.Response(body=content, content_type=mimetype, headers=headers) if os.path.exists(B):raise web.HTTPConflict(reason='File or directory already exists')
F=await A.request.post()
async def post(self): if F.get(_A)==_I:os.makedirs(B);return web.json_response({_E:C,_A:_I,_B:E})
rel_path = self.request.match_info.get("rel_path", "")
full_path = await self.get_full_path(rel_path)
abs_url = await self.make_absolute_url(rel_path)
if os.path.exists(full_path):
raise web.HTTPConflict(reason="File or directory already exists")
data = await self.request.post()
if data.get("type") == "dir":
os.makedirs(full_path)
return web.json_response({"status": "created", "type": "dir", "absolute_url": abs_url})
else: else:
file_field = data.get("file") G=F.get(_D)
if not file_field: if not G:raise web.HTTPBadRequest(reason='No file uploaded')
raise web.HTTPBadRequest(reason="No file uploaded") with open(B,'wb')as H:H.write(G.file.read())
with open(full_path, "wb") as f: return web.json_response({_E:C,_A:_D,_B:E})
f.write(file_field.file.read()) async def put(A):
return web.json_response({"status": "created", "type": "file", "absolute_url": abs_url}) C=A.request.match_info.get(_J,'');B=await A.get_full_path(C);D=await A.make_absolute_url(C)
if not os.path.exists(B):raise web.HTTPNotFound(reason='File not found')
async def put(self): if os.path.isdir(B):raise web.HTTPBadRequest(reason='Cannot overwrite directory')
rel_path = self.request.match_info.get("rel_path", "") E=await A.request.read()
full_path = await self.get_full_path(rel_path) with open(B,'wb')as F:F.write(E)
abs_url = await self.make_absolute_url(rel_path) return web.json_response({_E:'updated',_B:D})
if not os.path.exists(full_path): async def delete(B):
raise web.HTTPNotFound(reason="File not found") C='deleted';D=B.request.match_info.get(_J,'');A=await B.get_full_path(D);E=await B.make_absolute_url(D)
if os.path.isdir(full_path): if not os.path.exists(A):raise web.HTTPNotFound(reason=_P)
raise web.HTTPBadRequest(reason="Cannot overwrite directory") if os.path.isdir(A):os.rmdir(A);return web.json_response({_E:C,_A:_I,_B:E})
body = await self.request.read() else:os.remove(A);return web.json_response({_E:C,_A:_D,_B:E})
with open(full_path, "wb") as f:
f.write(body)
return web.json_response({"status": "updated", "absolute_url": abs_url})
async def delete(self):
rel_path = self.request.match_info.get("rel_path", "")
full_path = await self.get_full_path(rel_path)
abs_url = await self.make_absolute_url(rel_path)
if not os.path.exists(full_path):
raise web.HTTPNotFound(reason="Path not found")
if os.path.isdir(full_path):
os.rmdir(full_path)
return web.json_response({"status": "deleted", "type": "dir", "absolute_url": abs_url})
else:
os.remove(full_path)
return web.json_response({"status": "deleted", "type": "file", "absolute_url": abs_url})
class DriveViewi2(BaseView): class DriveViewi2(BaseView):
login_required=True
login_required = True async def get(A):
G='/drive.bin/';D=A.request.match_info.get('drive');H=A.request.query.get('before');E={}
async def get(self): if H:E['created_at__lt']=H
if D:
drive_uid = self.request.match_info.get("drive") E['drive_uid']=D;F=await A.services.drive.get(uid=D);I=[]
async for C in A.services.drive_item.find(**E):B=C.record;B[_H]=G+B[_C]+'.'+C.extension;I.append(B)
return web.json_response(I)
before = self.request.query.get("before") L=await A.services.user.get(uid=A.session.get(_C));J=[]
filters = {} async for F in A.services.drive.get_by_user(L[_C]):
if before: B=F.record;B[_N]=[]
filters["created_at__lt"] = before async for C in F.items:K=C.record;K[_H]=G+K[_C]+'.'+C.extension;B[_N].append(C.record)
J.append(B)
if drive_uid: return web.json_response(J)
filters['drive_uid'] = drive_uid
drive = await self.services.drive.get(uid=drive_uid)
drive_items = []
async for item in self.services.drive_item.find(**filters):
record = item.record
record["url"] = "/drive.bin/" + record["uid"] + "." + item.extension
drive_items.append(record)
return web.json_response(drive_items)
user = await self.services.user.get(uid=self.session.get("uid"))
drives = []
async for drive in self.services.drive.get_by_user(user["uid"]):
record = drive.record
record["items"] = []
async for item in drive.items:
drive_item_record = item.record
drive_item_record["url"] = (
"/drive.bin/" + drive_item_record["uid"] + "." + item.extension
)
record["items"].append(item.record)
drives.append(record)
return web.json_response(drives)

View File

@ -1,23 +1,6 @@
# Written by retoor@molodetz.nl
# This code defines an asynchronous IndexView class inheriting from BaseView with a method to render an HTML template.
# External imports: BaseView from snek.system.view
# MIT License
from aiohttp import web from aiohttp import web
from snek.system.view import BaseView from snek.system.view import BaseView
class IndexView(BaseView): class IndexView(BaseView):
async def get(self): async def get(A):
if self.session.get("uid"): if A.session.get('uid'):return web.HTTPFound('/web.html')
return web.HTTPFound("/web.html") return await A.render_template('index.html')
return await self.render_template("index.html")

View File

@ -1,44 +1,15 @@
# Written by retoor@molodetz.nl _B='/web.html'
_A='logged_in'
# This source code defines a LoginView class that inherits from BaseFormView and handles user authentication. It checks if a user is logged in, provides a JSON response or renders a login HTML template as needed, and processes form submissions to authenticate users.
# The code imports the LoginForm from snek.form.login and BaseFormView from snek.system.view, both of which are likely custom modules in the system, as well as web from aiohttp for handling HTTP responses.
# MIT License
from aiohttp import web from aiohttp import web
from snek.form.login import LoginForm from snek.form.login import LoginForm
from snek.system.view import BaseFormView from snek.system.view import BaseFormView
class LoginView(BaseFormView): class LoginView(BaseFormView):
form = LoginForm form=LoginForm;login_required=False
async def get(A):
login_required = False if A.session.get(_A):return web.HTTPFound(_B)
if A.request.path.endswith('.json'):return await super().get()
async def get(self): return await A.render_template('login.html',{'form':await A.form(app=A.app).to_json()})
if self.session.get("logged_in"): async def submit(B,form):
return web.HTTPFound("/web.html") D='color';E='uid';C='username'
if self.request.path.endswith(".json"): if await form.is_valid:A=await B.services.user.get(username=form[C],deleted_at=None);await B.services.user.save(A);B.session.update({_A:True,C:A[C],E:A[E],D:A[D]});return{'redirect_url':_B}
return await super().get() return{'is_valid':False}
return await self.render_template(
"login.html", {"form": await self.form(app=self.app).to_json()}
)
async def submit(self, form):
if await form.is_valid:
user = await self.services.user.get(
username=form["username"], deleted_at=None
)
await self.services.user.save(user)
self.session.update(
{
"logged_in": True,
"username": user["username"],
"uid": user["uid"],
"color": user["color"],
}
)
return {"redirect_url": "/web.html"}
return {"is_valid": False}

View File

@ -1,23 +1,8 @@
# Written by retoor@molodetz.nl
# This code defines an asynchronous view for handling a login form. It checks if the form is valid, sets session variables for a logged-in user, and provides a redirect URL if successful.
# Imports: LoginForm from snek.form.login and BaseFormView from snek.system.view
# MIT License
from snek.form.login import LoginForm from snek.form.login import LoginForm
from snek.system.view import BaseFormView from snek.system.view import BaseFormView
class LoginFormView(BaseFormView): class LoginFormView(BaseFormView):
form = LoginForm form=LoginForm
async def submit(A,form):
async def submit(self, form): B=form
if await form.is_valid(): if await B.is_valid():A.session['logged_in']=True;A.session['username']=B.username.value;A.session['uid']=B.uid.value;return{'redirect_url':'/web.html'}
self.session["logged_in"] = True return{'is_valid':False}
self.session["username"] = form.username.value
self.session["uid"] = form.uid.value
return {"redirect_url": "/web.html"}
return {"is_valid": False}

View File

@ -1,56 +1,14 @@
# Written by retoor@molodetz.nl _B='username'
_A='logged_in'
# This code provides a view for logging out users. It handles GET and POST requests, deletes session information, and redirects the user after logging out.
# This code imports 'web' from the 'aiohttp' library to handle HTTP operations and imports 'BaseView' from 'snek.system.view' to extend a base class for creating views.
# MIT License
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from aiohttp import web from aiohttp import web
from snek.system.view import BaseView from snek.system.view import BaseView
class LogoutView(BaseView): class LogoutView(BaseView):
redirect_url = "/" redirect_url='/';login_required=True
login_required = True async def get(A):
try:del A.session[_A];del A.session['uid'];del A.session[_B]
async def get(self): except KeyError:pass
try: return web.HTTPFound(A.redirect_url)
del self.session["logged_in"] async def post(A):
del self.session["uid"] try:del A.session[_A];del A.session['uid'];del A.session[_B]
del self.session["username"] except KeyError:pass
except KeyError: return await A.json_response({'redirect_url':A.redirect_url})
pass
return web.HTTPFound(self.redirect_url)
async def post(self):
try:
del self.session["logged_in"]
del self.session["uid"]
del self.session["username"]
except KeyError:
pass
return await self.json_response({"redirect_url": self.redirect_url})

View File

@ -1,41 +1,12 @@
# Written by retoor@molodetz.nl _B='/web.html'
_A='logged_in'
# This module defines a web view for user registration. It handles GET requests and form submissions for the registration process.
# The code makes use of 'RegisterForm' from 'snek.form.register' for handling registration forms and 'BaseFormView' from 'snek.system.view' for basic view functionalities.
# MIT License
from aiohttp import web from aiohttp import web
from snek.form.register import RegisterForm from snek.form.register import RegisterForm
from snek.system.view import BaseFormView from snek.system.view import BaseFormView
class RegisterView(BaseFormView): class RegisterView(BaseFormView):
form = RegisterForm form=RegisterForm;login_required=False
async def get(A):
login_required = False if A.session.get(_A):return web.HTTPFound(_B)
if A.request.path.endswith('.json'):return await super().get()
async def get(self): return await A.render_template('register.html',{'form':await A.form(app=A.app).to_json()})
if self.session.get("logged_in"): async def submit(C,form):D='color';E='username';F='uid';A=form;B=await C.app.services.user.register(A.email.value,A.username.value,A.password.value);C.request.session.update({F:B[F],E:B[E],_A:True,D:B[D]});return{'redirect_url':_B}
return web.HTTPFound("/web.html")
if self.request.path.endswith(".json"):
return await super().get()
return await self.render_template(
"register.html", {"form": await self.form(app=self.app).to_json()}
)
async def submit(self, form):
result = await self.app.services.user.register(
form.email.value, form.username.value, form.password.value
)
self.request.session.update(
{
"uid": result["uid"],
"username": result["username"],
"logged_in": True,
"color": result["color"],
}
)
return {"redirect_url": "/web.html"}

View File

@ -1,47 +1,5 @@
# Written by retoor@molodetz.nl
# This code defines a `RegisterFormView` class that handles the user registration process by using a form object, a view parent class, and asynchronously submitting the form data to register a user. It then stores the user's session details and provides a redirect URL to a specific page.
# Imports used but not part of the language:
# snek.form.register.RegisterForm, snek.system.view.BaseFormView
# MIT License
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from snek.form.register import RegisterForm from snek.form.register import RegisterForm
from snek.system.view import BaseFormView from snek.system.view import BaseFormView
class RegisterFormView(BaseFormView): class RegisterFormView(BaseFormView):
form = RegisterForm form=RegisterForm
async def submit(C,form):D='color';E='username';F='uid';A=form;B=await C.app.services.user.register(A.email.value,A.username.value,A.password.value);C.request.session.update({F:B[F],E:B[E],'logged_in':True,D:B[D]});return{'redirect_url':'/web.html'}
async def submit(self, form):
result = await self.app.services.user.register(
form.email.value, form.username.value, form.password.value
)
self.request.session.update(
{
"uid": result["uid"],
"username": result["username"],
"logged_in": True,
"color": result["color"],
}
)
return {"redirect_url": "/web.html"}

View File

@ -1,283 +1,105 @@
# Written by retoor@molodetz.nl _M='noresponse'
_L='deleted_at'
# This source code implements a WebSocket-based RPC (Remote Procedure Call) view that uses asynchronous methods to facilitate real-time communication and services for an authenticated user session in a web application. The class handles WebSocket events, user authentication, and various RPC interactions such as login, message retrieval, and more. _K='Not allowed'
_J='password'
# External imports are used from the aiohttp library for the WebSocket response handling and the snek.system view for the BaseView class. _I='logged_in'
_H='channel_uid'
# MIT License: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions. _G='last_ping'
_F='nick'
_E=None
import json _D=True
import traceback _C=False
_B='username'
_A='uid'
import json,traceback
from aiohttp import web from aiohttp import web
from snek.system.model import now from snek.system.model import now
from snek.system.profiler import Profiler from snek.system.profiler import Profiler
from snek.system.view import BaseView from snek.system.view import BaseView
class RPCView(BaseView): class RPCView(BaseView):
class RPCApi: class RPCApi:
def __init__(self, view, ws): def __init__(A,view,ws):A.view=view;A.app=A.view.app;A.services=A.app.services;A.ws=ws
self.view = view
self.app = self.view.app
self.services = self.app.services
self.ws = ws
@property @property
def user_uid(self): def user_uid(self):return self.view.session.get(_A)
return self.view.session.get("uid")
@property @property
def request(self): def request(self):return self.view.request
return self.view.request def _require_login(A):
if not A.is_logged_in:raise Exception('Not logged in')
def _require_login(self):
if not self.is_logged_in:
raise Exception("Not logged in")
@property @property
def is_logged_in(self): def is_logged_in(self):return self.view.session.get(_I,_C)
return self.view.session.get("logged_in", False) async def mark_as_read(A,channel_uid):A._require_login();await A.services.channel_member.mark_as_read(channel_uid,A.user_uid);return _D
async def login(A,username,password):
async def mark_as_read(self, channel_uid): D=username;E=await A.services.user.validate_login(D,password)
self._require_login() if not E:raise Exception('Invalid username or password')
await self.services.channel_member.mark_as_read(channel_uid, self.user_uid) B=await A.services.user.get(username=D);A.view.session[_A]=B[_A];A.view.session[_I]=_D;A.view.session[_B]=B[_B];A.view.session['user_nick']=B[_F];C=B.record;del C[_J];del C[_L];await A.services.socket.add(A.ws,A.view.request.session.get(_A))
return True async for F in A.services.channel_member.find(user_uid=A.view.request.session.get(_A),deleted_at=_E,is_banned=_C):await A.services.socket.subscribe(A.ws,F[_H],A.view.request.session.get(_A))
return C
async def login(self, username, password): async def search_user(A,query):A._require_login();return[A[_B]for A in await A.services.user.search(query)]
success = await self.services.user.validate_login(username, password) async def get_user(C,user_uid):
if not success: A=user_uid;C._require_login()
raise Exception("Invalid username or password") if not A:A=C.user_uid
user = await self.services.user.get(username=username) D=await C.services.user.get(uid=A);B=D.record;del B[_J];del B[_L]
self.view.session["uid"] = user["uid"] if A!=D[_A]:del B['email']
self.view.session["logged_in"] = True return B
self.view.session["username"] = user["username"] async def get_messages(A,channel_uid,offset=0,timestamp=_E):
self.view.session["user_nick"] = user["nick"] A._require_login();B=[]
record = user.record for C in await A.services.channel_message.offset(channel_uid,offset or 0,timestamp or _E):D=await A.services.channel_message.to_extended_dict(C);B.append(D)
del record["password"] return B
del record["deleted_at"] async def get_channels(B):
await self.services.socket.add( D='is_read_only';E='is_moderator';F='tag';G='color';C='new_count';B._require_login();H=[]
self.ws, self.view.request.session.get("uid") async for A in B.services.channel_member.find(user_uid=B.user_uid,is_banned=_C):
) I=await B.services.channel.get(uid=A[_H]);J=await I.get_last_message();K=_E
async for subscription in self.services.channel_member.find( if J:L=await J.get_user();K=L[G]
user_uid=self.view.request.session.get("uid"), H.append({'name':A['label'],_A:A[_H],F:I[F],C:A[C],E:A[E],D:A[D],C:A[C],G:K})
deleted_at=None, return H
is_banned=False, async def send_message(A,channel_uid,message):A._require_login();await A.services.chat.send(A.user_uid,channel_uid,message);return _D
): async def echo(A,*B):A._require_login();return B
await self.services.socket.subscribe( async def query(B,*C):
self.ws, B._require_login();E=C[0];D=E.lower()
subscription["channel_uid"], if any(A in D for A in['drop','alter','update','delete','replace','insert','truncate'])and'select'not in D:raise Exception(_K)
self.view.request.session.get("uid"), F=[dict(A)async for A in B.services.channel.query(C[0])]
) for A in F:
return record try:del A['email']
except KeyError:pass
async def search_user(self, query): try:del A[_J]
self._require_login() except KeyError:pass
return [user["username"] for user in await self.services.user.search(query)] try:del A['message']
except:pass
async def get_user(self, user_uid): try:del A['html']
self._require_login() except:pass
if not user_uid: return[dict(A)async for A in B.services.channel.query(C[0])]
user_uid = self.user_uid async def __call__(A,data):
user = await self.services.user.get(uid=user_uid) I='success';E='data';F=data;B='callId'
record = user.record
del record["password"]
del record["deleted_at"]
if user_uid != user["uid"]:
del record["email"]
return record
async def get_messages(self, channel_uid, offset=0, timestamp=None):
self._require_login()
messages = []
for message in await self.services.channel_message.offset(
channel_uid, offset or 0, timestamp or None
):
extended_dict = await self.services.channel_message.to_extended_dict(
message
)
messages.append(extended_dict)
return messages
async def get_channels(self):
self._require_login()
channels = []
async for subscription in self.services.channel_member.find(
user_uid=self.user_uid, is_banned=False
):
channel = await self.services.channel.get(
uid=subscription["channel_uid"]
)
last_message = await channel.get_last_message()
color = None
if last_message:
last_message_user = await last_message.get_user()
color = last_message_user["color"]
channels.append(
{
"name": subscription["label"],
"uid": subscription["channel_uid"],
"tag": channel["tag"],
"new_count": subscription["new_count"],
"is_moderator": subscription["is_moderator"],
"is_read_only": subscription["is_read_only"],
"new_count": subscription["new_count"],
"color": color,
}
)
return channels
async def send_message(self, channel_uid, message):
self._require_login()
await self.services.chat.send(self.user_uid, channel_uid, message)
return True
async def echo(self, *args):
self._require_login()
return args
async def query(self, *args):
self._require_login()
query = args[0]
lowercase = query.lower()
if (
any(
keyword in lowercase
for keyword in [
"drop",
"alter",
"update",
"delete",
"replace",
"insert",
"truncate",
]
)
and "select" not in lowercase
):
raise Exception("Not allowed")
records = [
dict(record) async for record in self.services.channel.query(args[0])
]
for record in records:
try: try:
del record["email"] G=F.get(B);C=F.get('method')
except KeyError: if C.startswith('_'):raise Exception(_K)
pass L=F.get('args')or[]
if hasattr(super(),C)or not hasattr(A,C):return await A._send_json({B:G,E:_K})
J=getattr(A,C.replace('.','_'),_E)
if not J:raise Exception('Method not found')
K=_D
try:H=await J(*L)
except Exception as D:H={'exception':str(D),'traceback':traceback.format_exc()};K=_C
if H!=_M:await A._send_json({B:G,I:K,E:H})
except Exception as D:print(str(D),flush=_D);await A._send_json({B:G,I:_C,E:str(D)})
async def _send_json(A,obj):await A.ws.send_str(json.dumps(obj,default=str))
async def get_online_users(A,channel_uid):A._require_login();return[{_A:A[_A],_B:A[_B],_F:A[_F],_G:A[_G]}async for A in A.services.channel.get_online_users(channel_uid)]
async def echo(A,obj):await A.ws.send_json(obj);return _M
async def get_users(A,channel_uid):A._require_login();return[{_A:A[_A],_B:A[_B],_F:A[_F],_G:A[_G]}async for A in A.services.channel.get_users(channel_uid)]
async def ping(A,callId,*C):
if A.user_uid:B=await A.services.user.get(uid=A.user_uid);B[_G]=now();await A.services.user.save(B)
return{'pong':C}
async def get(A):
B=web.WebSocketResponse();await B.prepare(A.request)
if A.request.session.get(_I):
await A.services.socket.add(B,A.request.session.get(_A))
async for D in A.services.channel_member.find(user_uid=A.request.session.get(_A),deleted_at=_E,is_banned=_C):await A.services.socket.subscribe(B,D[_H],A.request.session.get(_A))
E=RPCView.RPCApi(A,B)
async for C in B:
if C.type==web.WSMsgType.TEXT:
try: try:
del record["password"] async with Profiler():await E(C.json())
except KeyError: except Exception as F:print('Deleting socket',F,flush=_D);await A.services.socket.delete(B);break
pass elif C.type==web.WSMsgType.ERROR:0
try: elif C.type==web.WSMsgType.CLOSE:0
del record["message"] return B
except:
pass
try:
del record["html"]
except:
pass
return [
dict(record) async for record in self.services.channel.query(args[0])
]
async def __call__(self, data):
try:
call_id = data.get("callId")
method_name = data.get("method")
if method_name.startswith("_"):
raise Exception("Not allowed")
args = data.get("args") or []
if hasattr(super(), method_name) or not hasattr(self, method_name):
return await self._send_json(
{"callId": call_id, "data": "Not allowed"}
)
method = getattr(self, method_name.replace(".", "_"), None)
if not method:
raise Exception("Method not found")
success = True
try:
result = await method(*args)
except Exception as ex:
result = {"exception": str(ex), "traceback": traceback.format_exc()}
success = False
if result != "noresponse":
await self._send_json(
{"callId": call_id, "success": success, "data": result}
)
except Exception as ex:
print(str(ex), flush=True)
await self._send_json(
{"callId": call_id, "success": False, "data": str(ex)}
)
async def _send_json(self, obj):
await self.ws.send_str(json.dumps(obj, default=str))
async def get_online_users(self, channel_uid):
self._require_login()
return [
{
"uid": record["uid"],
"username": record["username"],
"nick": record["nick"],
"last_ping": record["last_ping"],
}
async for record in self.services.channel.get_online_users(channel_uid)
]
async def echo(self, obj):
await self.ws.send_json(obj)
return "noresponse"
async def get_users(self, channel_uid):
self._require_login()
return [
{
"uid": record["uid"],
"username": record["username"],
"nick": record["nick"],
"last_ping": record["last_ping"],
}
async for record in self.services.channel.get_users(channel_uid)
]
async def ping(self, callId, *args):
if self.user_uid:
user = await self.services.user.get(uid=self.user_uid)
user["last_ping"] = now()
await self.services.user.save(user)
return {"pong": args}
async def get(self):
ws = web.WebSocketResponse()
await ws.prepare(self.request)
if self.request.session.get("logged_in"):
await self.services.socket.add(ws, self.request.session.get("uid"))
async for subscription in self.services.channel_member.find(
user_uid=self.request.session.get("uid"),
deleted_at=None,
is_banned=False,
):
await self.services.socket.subscribe(
ws, subscription["channel_uid"], self.request.session.get("uid")
)
rpc = RPCView.RPCApi(self, ws)
async for msg in ws:
if msg.type == web.WSMsgType.TEXT:
try:
async with Profiler():
await rpc(msg.json())
except Exception as ex:
print("Deleting socket", ex, flush=True)
await self.services.socket.delete(ws)
break
elif msg.type == web.WSMsgType.ERROR:
pass
elif msg.type == web.WSMsgType.CLOSE:
pass
return ws

View File

@ -1,56 +1,12 @@
# Written by retoor@molodetz.nl
# This code implements a web view feature for searching users. It handles GET requests to retrieve user data based on a search query and also processes form submissions.
# Imports used that are not part of the Python language:
# - aiohttp: Used to handle asynchronous web server functionalities.
# - snek.form.search_user: Contains the definition for SearchUserForm, a form specific to searching users.
# - snek.system.view: Provides the BaseFormView class to facilitate form-related operations in a web context.
# MIT License
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from snek.form.search_user import SearchUserForm from snek.form.search_user import SearchUserForm
from snek.system.view import BaseFormView from snek.system.view import BaseFormView
class SearchUserView(BaseFormView): class SearchUserView(BaseFormView):
form = SearchUserForm form=SearchUserForm;login_required=True
login_required = True async def get(A):
C='query';D=[];B=A.request.query.get(C)
async def get(self): if B:D=[A.record for A in await A.app.services.user.search(B)]
users = [] if A.request.path.endswith('.json'):return await super().get()
query = self.request.query.get("query") E=await A.app.services.user.get(uid=A.session.get('uid'));return await A.render_template('search_user.html',{'users':D,C:B or'','current_user':E})
if query: async def submit(A,form):
users = [user.record for user in await self.app.services.user.search(query)] if await form.is_valid:return{'redirect_url':'/search-user.html?query='+form['username']}
return{'is_valid':False}
if self.request.path.endswith(".json"):
return await super().get()
current_user = await self.app.services.user.get(uid=self.session.get("uid"))
return await self.render_template(
"search_user.html",
{"users": users, "query": query or "", "current_user": current_user},
)
async def submit(self, form):
if await form.is_valid:
return {"redirect_url": "/search-user.html?query=" + form["username"]}
return {"is_valid": False}

View File

@ -1,9 +1,4 @@
from snek.system.view import BaseView from snek.system.view import BaseView
class SettingsIndexView(BaseView): class SettingsIndexView(BaseView):
login_required=True
login_required = True async def get(A):return await A.render_template('settings/index.html')
async def get(self):
return await self.render_template("settings/index.html")

View File

@ -1,38 +1,13 @@
_C='profile'
_B='uid'
_A='nick'
from aiohttp import web from aiohttp import web
from snek.form.settings.profile import SettingsProfileForm from snek.form.settings.profile import SettingsProfileForm
from snek.system.view import BaseFormView from snek.system.view import BaseFormView
class SettingsProfileView(BaseFormView): class SettingsProfileView(BaseFormView):
form = SettingsProfileForm form=SettingsProfileForm;login_required=True
async def get(A):
login_required = True C='user';B=A.form(app=A.app)
if A.request.path.endswith('.json'):B[_A]=A.request[C][_A];return web.json_response(await B.to_json())
async def get(self): D=await A.services.user_property.get(A.session.get(_B),_C);E=await A.services.user.get(uid=A.session.get(_B));return await A.render_template('settings/profile.html',{'form':await B.to_json(),C:E,_C:D or''})
form = self.form(app=self.app) async def post(A):C=await A.request.post();B=await A.services.user.get(uid=A.session.get(_B));B[_A]=C[_A];await A.services.user.save(B);await A.services.user_property.set(B[_B],_C,C[_C]);return web.HTTPFound('/settings/profile.html')
if self.request.path.endswith(".json"):
form["nick"] = self.request["user"]["nick"]
return web.json_response(await form.to_json())
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 ""},
)
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"])
return web.HTTPFound("/settings/profile.html")

View File

@ -1,86 +1,37 @@
_F='repository'
_E='/settings/repositories/index.html'
_D='is_private'
_C=True
_B='name'
_A='uid'
import asyncio import asyncio
from aiohttp import web from aiohttp import web
from snek.system.view import BaseFormView from snek.system.view import BaseFormView
import pathlib import pathlib
class RepositoriesIndexView(BaseFormView): class RepositoriesIndexView(BaseFormView):
login_required=_C
login_required = True async def get(A):
C=A.session.get(_A);B=[]
async def get(self): async for D in A.services.repository.find(user_uid=C):B.append(D.record)
E=await A.services.user.get(uid=A.session.get(_A));return await A.render_template('settings/repositories/index.html',{'repositories':B,'user':E})
user_uid = self.session.get("uid")
repositories = []
async for repository in self.services.repository.find(user_uid=user_uid):
repositories.append(repository.record)
user = await self.services.user.get(uid=self.session.get("uid"))
return await self.render_template("settings/repositories/index.html", {"repositories": repositories, "user": user})
class RepositoriesCreateView(BaseFormView): class RepositoriesCreateView(BaseFormView):
login_required=_C
login_required = True async def get(A):return await A.render_template('settings/repositories/create.html')
async def post(A):B=await A.request.post();C=await A.services.repository.create(user_uid=A.session.get(_A),name=B[_B],is_private=int(B.get(_D,0)));return web.HTTPFound(_E)
async def get(self):
return await self.render_template("settings/repositories/create.html")
async def post(self):
data = await self.request.post()
repository = await self.services.repository.create(user_uid=self.session.get("uid"), name=data['name'], is_private=int(data.get('is_private',0)))
return web.HTTPFound("/settings/repositories/index.html")
class RepositoriesUpdateView(BaseFormView): class RepositoriesUpdateView(BaseFormView):
login_required=_C
login_required = True async def get(A):
B=await A.services.repository.get(user_uid=A.session.get(_A),name=A.request.match_info[_B])
async def get(self): if not B:return web.HTTPNotFound()
return await A.render_template('settings/repositories/update.html',{_F:B.record})
repository = await self.services.repository.get( async def post(A):C=await A.request.post();B=await A.services.repository.get(user_uid=A.session.get(_A),name=A.request.match_info[_B]);B[_D]=int(C.get(_D,0));await A.services.repository.save(B);return web.HTTPFound(_E)
user_uid=self.session.get("uid"), name=self.request.match_info["name"]
)
if not repository:
return web.HTTPNotFound()
return await self.render_template("settings/repositories/update.html", {"repository": repository.record})
async def post(self):
data = await self.request.post()
repository = await self.services.repository.get(
user_uid=self.session.get("uid"), name=self.request.match_info["name"]
)
repository['is_private'] = int(data.get('is_private',0))
await self.services.repository.save(repository)
return web.HTTPFound("/settings/repositories/index.html")
class RepositoriesDeleteView(BaseFormView): class RepositoriesDeleteView(BaseFormView):
login_required=_C
login_required = True async def get(A):
B=await A.services.repository.get(user_uid=A.session.get(_A),name=A.request.match_info[_B])
async def get(self): if not B:return web.HTTPNotFound()
return await A.render_template('settings/repositories/delete.html',{_F:B.record})
repository = await self.services.repository.get( async def post(A):
user_uid=self.session.get("uid"), name=self.request.match_info["name"] B=A.session.get(_A);C=A.request.match_info[_B];D=await A.services.repository.get(user_uid=B,name=C)
) if not D:return web.HTTPNotFound()
if not repository: await A.services.repository.delete(user_uid=B,name=C);return web.HTTPFound(_E)
return web.HTTPNotFound()
return await self.render_template("settings/repositories/delete.html", {"repository": repository.record})
async def post(self):
user_uid = self.session.get("uid")
name = self.request.match_info["name"]
repository = await self.services.repository.get(
user_uid=user_uid, name=name
)
if not repository:
return web.HTTPNotFound()
await self.services.repository.delete(user_uid=user_uid, name=name)
return web.HTTPFound("/settings/repositories/index.html")

View File

@ -1,13 +1,5 @@
import json import json
from aiohttp import web from aiohttp import web
from snek.system.view import BaseView from snek.system.view import BaseView
class StatsView(BaseView): class StatsView(BaseView):
async def get(B):A=await B.app.cache.get_stats();A=json.dumps({'total':len(A),'stats':A},default=str,indent=1);return web.Response(text=A,content_type='application/json')
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")

View File

@ -1,73 +1,10 @@
# Written by retoor@molodetz.nl
# This code defines an async class-based view called StatusView for handling HTTP GET requests. It fetches user details and their associated channel memberships from a database and returns a JSON response with user information if the user is logged in.
# The code uses an imported module `BaseView`. There are dependencies on the `snek.system.view` module which provides the BaseView class.
# MIT License
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF
# CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from snek.system.view import BaseView from snek.system.view import BaseView
class StatusView(BaseView): class StatusView(BaseView):
async def get(self): async def get(C):
memberships = [] G='color';H='nick';I='email';J='username';K='is_banned';L='is_muted';M='is_read_only';N='is_moderator';O='user_uid';P='description';E='channel_uid';D='uid';Q=[];A={};F=C.session.get(D)
user = {} if F:
A=await C.app.services.user.get(uid=F)
user_id = self.session.get("uid") if not A:return await C.json_response({'error':'User not found'},status=404)
if user_id: async for B in C.app.services.channel_member.find(user_uid=F,deleted_at=None,is_banned=False):R=await C.app.services.channel.get(uid=B[E]);Q.append({'name':R['label'],P:B[P],O:B[O],N:B[N],M:B[M],L:B[L],K:B[K],E:B[E],D:B[D]})
user = await self.app.services.user.get(uid=user_id) A={J:A[J],I:A[I],H:A[H],D:A[D],G:A[G],'memberships':Q}
if not user: return await C.json_response({'user':A,'cache':await C.app.cache.create_cache_key(C.app.cache.cache,None)})
return await self.json_response({"error": "User not found"}, status=404)
async for model in self.app.services.channel_member.find(
user_uid=user_id, deleted_at=None, is_banned=False
):
channel = await self.app.services.channel.get(uid=model["channel_uid"])
memberships.append(
{
"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 = {
"username": user["username"],
"email": user["email"],
"nick": user["nick"],
"uid": user["uid"],
"color": user["color"],
"memberships": memberships,
}
return await self.json_response(
{
"user": user,
"cache": await self.app.cache.create_cache_key(
self.app.cache.cache, None
),
}
)

View File

@ -1,54 +1,23 @@
import pathlib _B=True
_A='uid'
import aiohttp import pathlib,aiohttp
from snek.system.terminal import TerminalSession from snek.system.terminal import TerminalSession
from snek.system.view import BaseView from snek.system.view import BaseView
class TerminalSocketView(BaseView): class TerminalSocketView(BaseView):
login_required=_B;user_sessions={}
login_required = True async def prepare_drive(C):
D=await C.services.user.get(uid=C.session.get(_A));A=pathlib.Path('drive').joinpath(D[_A]);A.mkdir(parents=_B,exist_ok=_B);E=pathlib.Path('terminal')
user_sessions = {} for B in E.iterdir():
F=A.joinpath(B.name)
async def prepare_drive(self): if not B.is_dir():F.write_bytes(B.read_bytes())
user = await self.services.user.get(uid=self.session.get("uid")) return A
root = pathlib.Path("drive").joinpath(user["uid"]) async def get(A):
root.mkdir(parents=True, exist_ok=True) B=aiohttp.web.WebSocketResponse();await B.prepare(A.request);D=await A.services.user.get(uid=A.session.get(_A));F=await A.prepare_drive();G=f"docker run -v ./{F}/:/root -it --memory 512M --cpus=0.5 -w /root snek_ubuntu /bin/bash";C=A.user_sessions.get(D[_A])
terminal_folder = pathlib.Path("terminal") if not C:A.user_sessions[D[_A]]=TerminalSession(command=G)
for path in terminal_folder.iterdir(): C=A.user_sessions[D[_A]];await C.add_websocket(B)
destination_path = root.joinpath(path.name) async for E in B:
if not path.is_dir(): if E.type==aiohttp.WSMsgType.BINARY:await C.write_input(E.data.decode())
destination_path.write_bytes(path.read_bytes()) return B
return root
async def get(self):
ws = aiohttp.web.WebSocketResponse()
await ws.prepare(self.request)
user = await self.services.user.get(uid=self.session.get("uid"))
root = await self.prepare_drive()
command = f"docker run -v ./{root}/:/root -it --memory 512M --cpus=0.5 -w /root snek_ubuntu /bin/bash"
session = self.user_sessions.get(user["uid"])
if not session:
self.user_sessions[user["uid"]] = TerminalSession(command=command)
session = self.user_sessions[user["uid"]]
await session.add_websocket(ws)
# asyncio.create_task(session.read_output(ws))
async for msg in ws:
if msg.type == aiohttp.WSMsgType.BINARY:
await session.write_input(msg.data.decode())
return ws
class TerminalView(BaseView): class TerminalView(BaseView):
login_required=_B
login_required = True async def get(A):return await A.request.app.render_template('terminal.html',A.request)
async def get(self):
return await self.request.app.render_template("terminal.html", self.request)

View File

@ -1,37 +1,11 @@
from snek.system.view import BaseView from snek.system.view import BaseView
class ThreadsView(BaseView): class ThreadsView(BaseView):
async def get(B):
async def get(self): I='color';J='user_uid';K='name_color';L='new_count';F='uid';C='last_message_on';G=[];M=await B.services.user.get(uid=B.session.get(F))
threads = [] async for H in M.get_channel_members():
user = await self.services.user.get(uid=self.session.get("uid")) A={};D=await B.services.channel.get(uid=H['channel_uid']);E=await D.get_last_message()
async for channel_member in user.get_channel_members(): if not E:continue
thread = {} A[F]=D[F];A['name']=await H.get_name();A[L]=H[L];A[C]=D[C];A['created_at']=A[C];A[K]='#f05a28';A['last_message_text']=E['message'];A['last_message_user_uid']=E[J];N=await B.app.services.user.get(uid=E[J])
channel = await self.services.channel.get(uid=channel_member["channel_uid"]) if D['tag']=='dm':A[K]=N[I]
last_message = await channel.get_last_message() A['last_message_user_color']=N[I];G.append(A)
if not last_message: G.sort(key=lambda x:x[C]or'',reverse=True);return await B.render_template('threads.html',{'threads':G,'user':M})
continue
thread["uid"] = channel["uid"]
thread["name"] = await channel_member.get_name()
thread["new_count"] = channel_member["new_count"]
thread["last_message_on"] = channel["last_message_on"]
thread["created_at"] = thread["last_message_on"]
thread["name_color"] = "#f05a28"
thread["last_message_text"] = last_message["message"]
thread["last_message_user_uid"] = last_message["user_uid"]
user_last_message = await self.app.services.user.get(
uid=last_message["user_uid"]
)
if channel["tag"] == "dm":
thread["name_color"] = user_last_message["color"]
thread["last_message_user_color"] = user_last_message["color"]
threads.append(thread)
threads.sort(key=lambda x: x["last_message_on"] or "", reverse=True)
return await self.render_template(
"threads.html", {"threads": threads, "user": user}
)

View File

@ -1,111 +1,19 @@
# Written by retoor@molodetz.nl _A='uid'
import pathlib,uuid,aiofiles
# This code defines a web application for uploading and retrieving files.
# It includes functionality to upload files through a POST request and retrieve them via a GET request.
# The code uses the following non-standard imports:
# - snek.system.view.BaseView: For extending view functionalities.
# - aiofiles: For asynchronous file operations.
# - aiohttp: For managing web server requests and responses.
# MIT License: This software is licensed under the MIT License, a permissive free software license.
import pathlib
import uuid
import aiofiles
from aiohttp import web from aiohttp import web
from snek.system.view import BaseView from snek.system.view import BaseView
class UploadView(BaseView): class UploadView(BaseView):
async def get(B):D=B.request.match_info.get(_A);C=await B.services.drive_item.get(D);A=web.FileResponse(C['path']);A.headers['Cache-Control']=f"public, max-age={561540}";A.headers['Content-Disposition']=f'attachment; filename="{C["name"]}"';return A
async def get(self): async def post(A):
uid = self.request.match_info.get("uid") K='](/drive.bin/';L='channel_uid';G='document';D='image';P=await A.request.multipart();M=[];Q=A.request.session.get(_A);E=await A.services.user.get_home_folder(Q);E=E.joinpath('upload');E.mkdir(parents=True,exist_ok=True);H=None;R=await A.services.drive.get_or_create(user_uid=A.request.session.get(_A));N={'.jpg':D,'.gif':D,'.png':D,'.jpeg':D,'.mp4':'video','.mp3':'audio','.pdf':G,'.doc':G,'.docx':G}
drive_item = await self.services.drive_item.get(uid) while(F:=await P.next()):
response = web.FileResponse(drive_item["path"]) if F.name==L:H=await F.text();continue
response.headers["Cache-Control"] = f"public, max-age={1337*420}" B=F.filename
response.headers["Content-Disposition"] = ( if not B:continue
f'attachment; filename="{drive_item["name"]}"' S=str(uuid.uuid4())+pathlib.Path(B).suffix;C=E.joinpath(S);M.append(C)
) async with aiofiles.open(str(C),'wb')as T:
return response while(U:=await F.read_chunk()):await T.write(U)
I=await A.services.drive_item.create(R[_A],B,str(C),C.stat().st_size,C.suffix);J='.'+B.split('.')[-1]
async def post(self): if J in N:N[J]
reader = await self.request.multipart() await A.services.drive_item.save(I);O='Uploaded ['+B+K+I[_A]+')';O='['+B+K+I[_A]+J+')';await A.services.chat.send(A.request.session.get(_A),H,O)
files = [] return web.json_response({'message':'Files uploaded successfully','files':[str(A)for A in M],L:H})
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.mkdir(parents=True, exist_ok=True)
channel_uid = None
drive = await self.services.drive.get_or_create(
user_uid=self.request.session.get("uid")
)
extension_types = {
".jpg": "image",
".gif": "image",
".png": "image",
".jpeg": "image",
".mp4": "video",
".mp3": "audio",
".pdf": "document",
".doc": "document",
".docx": "document",
}
while field := await reader.next():
if field.name == "channel_uid":
channel_uid = await field.text()
continue
filename = field.filename
if not filename:
continue
name = str(uuid.uuid4()) + pathlib.Path(filename).suffix
file_path = upload_dir.joinpath(name)
files.append(file_path)
async with aiofiles.open(str(file_path), "wb") as f:
while chunk := await field.read_chunk():
await f.write(chunk)
drive_item = await self.services.drive_item.create(
drive["uid"],
filename,
str(file_path),
file_path.stat().st_size,
file_path.suffix,
)
extension = "." + filename.split(".")[-1]
if extension in extension_types:
extension_types[extension]
await self.services.drive_item.save(drive_item)
response = (
"Uploaded [" + filename + "](/drive.bin/" + drive_item["uid"] + ")"
)
# response = "<iframe width=\"100%\" frameborder=\"0\" allowfullscreen title=\"Embedded\" src=\"" + self.base_url + "/drive.bin/" + drive_item["uid"] + "\"></iframe>\n"
response = (
"[" + filename + "](/drive.bin/" + drive_item["uid"] + extension + ")"
)
await self.services.chat.send(
self.request.session.get("uid"), channel_uid, response
)
return web.json_response(
{
"message": "Files uploaded successfully",
"files": [str(file) for file in files],
"channel_uid": channel_uid,
}
)

View File

@ -1,15 +1,3 @@
from snek.system.view import BaseView from snek.system.view import BaseView
class UserView(BaseView): class UserView(BaseView):
async def get(A):B='profile';C='user';D=A.request.match_info.get(C);E=await A.services.user.get(uid=D);F=await A.services.user_property.get(E['uid'],B)or'';return await A.render_template('user.html',{'user_uid':D,C:E.record,B:F})
async def get(self):
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},
)

View File

@ -1,79 +1,19 @@
# Written by retoor@molodetz.nl
# This code defines a WebView class that inherits from BaseView and includes a method for rendering a web template, requiring login access for its usage.
# The code imports the BaseView class from the `snek.system.view` module.
# MIT License
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from aiohttp import web from aiohttp import web
from snek.system.view import BaseView from snek.system.view import BaseView
class WebView(BaseView): class WebView(BaseView):
login_required = True login_required=True
async def get(A):
async def get(self): F='channel';B='uid'
if self.login_required and not self.session.get("logged_in"): if A.login_required and not A.session.get('logged_in'):return web.HTTPFound('/')
return web.HTTPFound("/") C=await A.services.channel.get(uid=A.request.match_info.get(F))
channel = await self.services.channel.get( if not C:
uid=self.request.match_info.get("channel") D=await A.services.user.get(uid=A.request.match_info.get(F))
) if D:
if not channel: C=await A.services.channel.get_dm(A.session.get(B),D[B])
user = await self.services.user.get( if C:return web.HTTPFound('/channel/{}.html'.format(C[B]))
uid=self.request.match_info.get("channel") if not C:return web.HTTPNotFound()
) E=await A.app.services.channel_member.get(user_uid=A.session.get(B),channel_uid=C[B])
if user: if not E:return web.HTTPNotFound()
channel = await self.services.channel.get_dm( E['new_count']=0;await A.app.services.channel_member.save(E);D=await A.services.user.get(uid=A.session.get(B));G=[await A.app.services.channel_message.to_extended_dict(B)for B in await A.app.services.channel_message.offset(C[B])]
self.session.get("uid"), user["uid"] for H in G:await A.app.services.notification.mark_as_read(A.session.get(B),H[B])
) I=await E.get_name();return await A.render_template('web.html',{'name':I,F:C,'user':D,'messages':G})
if channel:
return web.HTTPFound("/channel/{}.html".format(channel["uid"]))
if not channel:
return web.HTTPNotFound()
channel_member = await self.app.services.channel_member.get(
user_uid=self.session.get("uid"), channel_uid=channel["uid"]
)
if not channel_member:
return web.HTTPNotFound()
channel_member["new_count"] = 0
await self.app.services.channel_member.save(channel_member)
user = await self.services.user.get(uid=self.session.get("uid"))
messages = [
await self.app.services.channel_message.to_extended_dict(message)
for message in await self.app.services.channel_message.offset(
channel["uid"]
)
]
for message in messages:
await self.app.services.notification.mark_as_read(
self.session.get("uid"), message["uid"]
)
name = await channel_member.get_name()
return await self.render_template(
"web.html",
{"name": name, "channel": channel, "user": user, "messages": messages},
)

View File

@ -1,377 +1,145 @@
import logging _U='Lock-Token'
import pathlib _T='application/xml'
_S='{DAV:}exclusive'
_R='{DAV:}lockdiscovery'
_Q='{DAV:}prop'
_P='%a, %d %b %Y %H:%M:%S GMT'
_O='Source not found'
_N='http://localhost:8080/'
_M='Destination'
_L='application/octet-stream'
_K='File not found'
_J='{DAV:}write'
_I='{DAV:}locktype'
_H='{DAV:}lockscope'
_G='{DAV:}href'
_F='Content-Type'
_E=True
_D='filename'
_C='Basic realm="WebDAV"'
_B='WWW-Authenticate'
_A='home'
import logging,pathlib
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
import base64 import base64,datetime,mimetypes,os,shutil,uuid,aiofiles,aiohttp,aiohttp.web
import datetime
import mimetypes
import os
import shutil
import uuid
import aiofiles
import aiohttp
import aiohttp.web
from app.cache import time_cache_async from app.cache import time_cache_async
from lxml import etree from lxml import etree
@aiohttp.web.middleware @aiohttp.web.middleware
async def debug_middleware(request, handler): async def debug_middleware(request,handler):
print(request.method, request.path, request.headers) A=request;print(A.method,A.path,A.headers);B=await handler(A);print(B.status)
result = await handler(request) try:print(await B.text())
print(result.status) except:pass
try: return B
print(await result.text())
except:
pass
return result
class WebdavApplication(aiohttp.web.Application): class WebdavApplication(aiohttp.web.Application):
def __init__(self, parent, *args, **kwargs): def __init__(A,parent,*C,**D):B='/{filename:.*}';E=[debug_middleware];super().__init__(*C,middlewares=E,**D);A.locks={};A.relative_url='/webdav';A.router.add_route('OPTIONS',B,A.handle_options);A.router.add_route('GET',B,A.handle_get);A.router.add_route('PUT',B,A.handle_put);A.router.add_route('DELETE',B,A.handle_delete);A.router.add_route('MKCOL',B,A.handle_mkcol);A.router.add_route('MOVE',B,A.handle_move);A.router.add_route('COPY',B,A.handle_copy);A.router.add_route('PROPFIND',B,A.handle_propfind);A.router.add_route('PROPPATCH',B,A.handle_proppatch);A.router.add_route('LOCK',B,A.handle_lock);A.router.add_route('UNLOCK',B,A.handle_unlock);A.parent=parent
middlewares = [debug_middleware]
super().__init__(middlewares=middlewares, *args, **kwargs)
self.locks = {}
self.relative_url = "/webdav"
self.router.add_route("OPTIONS", "/{filename:.*}", self.handle_options)
self.router.add_route("GET", "/{filename:.*}", self.handle_get)
self.router.add_route("PUT", "/{filename:.*}", self.handle_put)
self.router.add_route("DELETE", "/{filename:.*}", self.handle_delete)
self.router.add_route("MKCOL", "/{filename:.*}", self.handle_mkcol)
self.router.add_route("MOVE", "/{filename:.*}", self.handle_move)
self.router.add_route("COPY", "/{filename:.*}", self.handle_copy)
self.router.add_route("PROPFIND", "/{filename:.*}", self.handle_propfind)
self.router.add_route("PROPPATCH", "/{filename:.*}", self.handle_proppatch)
self.router.add_route("LOCK", "/{filename:.*}", self.handle_lock)
self.router.add_route("UNLOCK", "/{filename:.*}", self.handle_unlock)
self.parent = parent
@property @property
def db(self): def db(self):return self.parent.db
return self.parent.db
@property @property
def services(self): def services(self):return self.parent.services
return self.parent.services async def authenticate(C,request):
D='Basic ';B='user';A=request;E=A.headers.get('Authorization','')
async def authenticate(self, request): if not E.startswith(D):return False
auth_header = request.headers.get("Authorization", "") F=E.split(D)[1];G=base64.b64decode(F).decode();H,I=G.split(':',1);A[B]=await C.services.user.authenticate(username=H,password=I)
if not auth_header.startswith("Basic "): try:A[_A]=await C.services.user.get_home_folder(A[B]['uid'])
return False except Exception:pass
encoded_creds = auth_header.split("Basic ")[1] return A[B]
decoded_creds = base64.b64decode(encoded_creds).decode() async def handle_get(D,request):
username, password = decoded_creds.split(":", 1) B=request
request["user"] = await self.services.user.authenticate( if not await D.authenticate(B):return aiohttp.web.Response(status=401,headers={_B:_C})
username=username, password=password E=B.match_info.get(_D,'');A=B[_A]/E
) if not A.exists():return aiohttp.web.Response(status=404,text=_K)
try: if A.is_dir():return aiohttp.web.Response(status=403,text='Cannot download a directory')
request["home"] = await self.services.user.get_home_folder( C,F=mimetypes.guess_type(str(A));C=C or _L;return aiohttp.web.FileResponse(path=str(A),headers={_F:C},chunk_size=8192)
request["user"]["uid"] async def handle_put(C,request):
) A=request
except Exception: if not await C.authenticate(A):return aiohttp.web.Response(status=401,headers={_B:_C})
pass B=A[_A]/A.match_info[_D];B.parent.mkdir(parents=_E,exist_ok=_E)
return request["user"] async with aiofiles.open(B,'wb')as D:
while(E:=await A.content.read(1024)):await D.write(E)
async def handle_get(self, request): return aiohttp.web.Response(status=201,text='File uploaded')
if not await self.authenticate(request): async def handle_delete(C,request):
return aiohttp.web.Response( B=request
status=401, headers={"WWW-Authenticate": 'Basic realm="WebDAV"'} if not await C.authenticate(B):return aiohttp.web.Response(status=401,headers={_B:_C})
) A=B[_A]/B.match_info[_D]
if A.is_file():A.unlink();return aiohttp.web.Response(status=204)
requested_path = request.match_info.get("filename", "") elif A.is_dir():shutil.rmtree(A);return aiohttp.web.Response(status=204)
abs_path = request["home"] / requested_path return aiohttp.web.Response(status=404,text='Not found')
async def handle_mkcol(C,request):
if not abs_path.exists(): A=request
return aiohttp.web.Response(status=404, text="File not found") if not await C.authenticate(A):return aiohttp.web.Response(status=401,headers={_B:_C})
B=A[_A]/A.match_info[_D]
if abs_path.is_dir(): if B.exists():return aiohttp.web.Response(status=405,text='Directory already exists')
return aiohttp.web.Response(status=403, text="Cannot download a directory") B.mkdir(parents=_E,exist_ok=_E);return aiohttp.web.Response(status=201,text='Directory created')
async def handle_move(C,request):
content_type, _ = mimetypes.guess_type(str(abs_path)) A=request
content_type = content_type or "application/octet-stream" if not await C.authenticate(A):return aiohttp.web.Response(status=401,headers={_B:_C})
B=A[_A]/A.match_info[_D];D=A[_A]/A.headers.get(_M,'').replace(_N,'')
return aiohttp.web.FileResponse( if not B.exists():return aiohttp.web.Response(status=404,text=_O)
path=str(abs_path), headers={"Content-Type": content_type}, chunk_size=8192 shutil.move(str(B),str(D));return aiohttp.web.Response(status=201,text='Moved successfully')
) async def handle_copy(D,request):
A=request
async def handle_put(self, request): if not await D.authenticate(A):return aiohttp.web.Response(status=401,headers={_B:_C})
if not await self.authenticate(request): B=A[_A]/A.match_info[_D];C=A[_A]/A.headers.get(_M,'').replace(_N,'')
return aiohttp.web.Response( if not B.exists():return aiohttp.web.Response(status=404,text=_O)
status=401, headers={"WWW-Authenticate": 'Basic realm="WebDAV"'} if B.is_file():shutil.copy2(str(B),str(C))
) else:shutil.copytree(str(B),str(C))
file_path = request["home"] / request.match_info["filename"] return aiohttp.web.Response(status=201,text='Copied successfully')
file_path.parent.mkdir(parents=True, exist_ok=True) async def handle_options(B,request):A={'DAV':'1, 2','Allow':'OPTIONS, GET, PUT, DELETE, MKCOL, MOVE, COPY, PROPFIND, PROPPATCH'};return aiohttp.web.Response(status=200,headers=A)
async with aiofiles.open(file_path, "wb") as f: def get_current_utc_time(C,filepath):
while chunk := await request.content.read(1024): B=filepath
await f.write(chunk) if B.exists():A=datetime.datetime.utcfromtimestamp(B.stat().st_mtime)
return aiohttp.web.Response(status=201, text="File uploaded") else:A=datetime.datetime.utcnow()
return A.strftime('%Y-%m-%dT%H:%M:%SZ'),A.strftime(_P)
async def handle_delete(self, request):
if not await self.authenticate(request):
return aiohttp.web.Response(
status=401, headers={"WWW-Authenticate": 'Basic realm="WebDAV"'}
)
file_path = request["home"] / request.match_info["filename"]
if file_path.is_file():
file_path.unlink()
return aiohttp.web.Response(status=204)
elif file_path.is_dir():
shutil.rmtree(file_path)
return aiohttp.web.Response(status=204)
return aiohttp.web.Response(status=404, text="Not found")
async def handle_mkcol(self, request):
if not await self.authenticate(request):
return aiohttp.web.Response(
status=401, headers={"WWW-Authenticate": 'Basic realm="WebDAV"'}
)
dir_path = request["home"] / request.match_info["filename"]
if dir_path.exists():
return aiohttp.web.Response(status=405, text="Directory already exists")
dir_path.mkdir(parents=True, exist_ok=True)
return aiohttp.web.Response(status=201, text="Directory created")
async def handle_move(self, request):
if not await self.authenticate(request):
return aiohttp.web.Response(
status=401, headers={"WWW-Authenticate": 'Basic realm="WebDAV"'}
)
src_path = request["home"] / request.match_info["filename"]
dest_path = request["home"] / request.headers.get("Destination", "").replace(
"http://localhost:8080/", ""
)
if not src_path.exists():
return aiohttp.web.Response(status=404, text="Source not found")
shutil.move(str(src_path), str(dest_path))
return aiohttp.web.Response(status=201, text="Moved successfully")
async def handle_copy(self, request):
if not await self.authenticate(request):
return aiohttp.web.Response(
status=401, headers={"WWW-Authenticate": 'Basic realm="WebDAV"'}
)
src_path = request["home"] / request.match_info["filename"]
dest_path = request["home"] / request.headers.get("Destination", "").replace(
"http://localhost:8080/", ""
)
if not src_path.exists():
return aiohttp.web.Response(status=404, text="Source not found")
if src_path.is_file():
shutil.copy2(str(src_path), str(dest_path))
else:
shutil.copytree(str(src_path), str(dest_path))
return aiohttp.web.Response(status=201, text="Copied successfully")
async def handle_options(self, request):
headers = {
"DAV": "1, 2",
"Allow": "OPTIONS, GET, PUT, DELETE, MKCOL, MOVE, COPY, PROPFIND, PROPPATCH",
}
return aiohttp.web.Response(status=200, headers=headers)
def get_current_utc_time(self, filepath):
if filepath.exists():
modified_time = datetime.datetime.utcfromtimestamp(filepath.stat().st_mtime)
else:
modified_time = datetime.datetime.utcnow()
return modified_time.strftime("%Y-%m-%dT%H:%M:%SZ"), modified_time.strftime(
"%a, %d %b %Y %H:%M:%S GMT"
)
@time_cache_async(10) @time_cache_async(10)
async def get_file_size(self, path): async def get_file_size(self,path):A=self.parent.loop;B=await A.run_in_executor(None,os.stat,path);return B.st_size
loop = self.parent.loop
stat = await loop.run_in_executor(None, os.stat, path)
return stat.st_size
@time_cache_async(10) @time_cache_async(10)
async def get_directory_size(self, directory): async def get_directory_size(self,directory):
total_size = 0 A=0
for dirpath, _, filenames in os.walk(directory): for(C,F,D)in os.walk(directory):
for f in filenames: for E in D:
fp = pathlib.Path(dirpath) / f B=pathlib.Path(C)/E
if fp.exists(): if B.exists():A+=await self.get_file_size(str(B))
total_size += await self.get_file_size(str(fp)) return A
return total_size
@time_cache_async(30) @time_cache_async(30)
async def get_disk_free_space(self, path="/"): async def get_disk_free_space(self,path='/'):B=self.parent.loop;A=await B.run_in_executor(None,os.statvfs,path);return A.f_bavail*A.f_frsize
loop = self.parent.loop async def create_node(C,request,response_xml,full_path,depth):
statvfs = await loop.run_in_executor(None, os.statvfs, path) F='{DAV:}lockentry';G=depth;H=response_xml;E=request;A=full_path;I=pathlib.Path(A);O=str(A.relative_to(E[_A]));D=f"{C.relative_url}/{O}".strip('.');D=D.replace('./','/');D=D.replace('//','/');J=etree.SubElement(H,'{DAV:}response');P=etree.SubElement(J,_G);P.text=D;K=etree.SubElement(J,'{DAV:}propstat');B=etree.SubElement(K,_Q);Q=etree.SubElement(B,'{DAV:}resourcetype')
return statvfs.f_bavail * statvfs.f_frsize if A.is_dir():etree.SubElement(Q,'{DAV:}collection')
R,S=C.get_current_utc_time(A);etree.SubElement(B,'{DAV:}creationdate').text=R;etree.SubElement(B,'{DAV:}quota-used-bytes').text=str(await C.get_file_size(A)if A.is_file()else await C.get_directory_size(A));etree.SubElement(B,'{DAV:}quota-available-bytes').text=str(await C.get_disk_free_space(E[_A]));etree.SubElement(B,'{DAV:}getlastmodified').text=S;etree.SubElement(B,'{DAV:}displayname').text=A.name;etree.SubElement(B,_R);T,Z=mimetypes.guess_type(A.name)
async def create_node(self, request, response_xml, full_path, depth): if A.is_file():etree.SubElement(B,'{DAV:}contenttype').text=T;etree.SubElement(B,'{DAV:}getcontentlength').text=str(await C.get_file_size(A)if A.is_file()else await C.get_directory_size(A))
abs_path = pathlib.Path(full_path) L=etree.SubElement(B,'{DAV:}supportedlock');M=etree.SubElement(L,F);U=etree.SubElement(M,_H);etree.SubElement(U,_S);V=etree.SubElement(M,_I);etree.SubElement(V,_J);N=etree.SubElement(L,F);W=etree.SubElement(N,_H);etree.SubElement(W,'{DAV:}shared');X=etree.SubElement(N,_I);etree.SubElement(X,_J);etree.SubElement(K,'{DAV:}status').text='HTTP/1.1 200 OK'
relative_path = str(full_path.relative_to(request["home"])) if I.is_dir()and G>0:
for Y in I.iterdir():await C.create_node(E,H,Y,G-1)
href_path = f"{self.relative_url}/{relative_path}".strip(".") async def handle_propfind(B,request):
href_path = href_path.replace("./", "/") A=request
href_path = href_path.replace("//", "/") if not await B.authenticate(A):return aiohttp.web.Response(status=401,headers={_B:_C})
C=0
response = etree.SubElement(response_xml, "{DAV:}response") try:C=int(A.headers.get('Depth','0'))
href = etree.SubElement(response, "{DAV:}href") except ValueError:pass
href.text = href_path F=A.match_info.get(_D,'');D=A[_A]/F
propstat = etree.SubElement(response, "{DAV:}propstat") if not D.exists():return aiohttp.web.Response(status=404,text='Directory not found')
prop = etree.SubElement(propstat, "{DAV:}prop") G={'D':'DAV:'};E=etree.Element('{DAV:}multistatus',nsmap=G);await B.create_node(A,E,D,C);H=etree.tostring(E,encoding='utf-8',xml_declaration=_E).decode();return aiohttp.web.Response(status=207,text=H,content_type=_T)
res_type = etree.SubElement(prop, "{DAV:}resourcetype") async def handle_proppatch(A,request):
if full_path.is_dir(): if not await A.authenticate(request):return aiohttp.web.Response(status=401,headers={_B:_C})
etree.SubElement(res_type, "{DAV:}collection") return aiohttp.web.Response(status=207,text='PROPPATCH OK (Not Implemented)')
creation_date, last_modified = self.get_current_utc_time(full_path) async def handle_lock(A,request):
etree.SubElement(prop, "{DAV:}creationdate").text = creation_date C=request
etree.SubElement(prop, "{DAV:}quota-used-bytes").text = str( if not await A.authenticate(C):return aiohttp.web.Response(status=401,headers={_B:_C})
await self.get_file_size(full_path) D=C.match_info.get(_D,'/');B=str(uuid.uuid4());A.locks[D]=B;E=await A.generate_lock_response(B);F={_U:f"opaquelocktoken:{B}",_F:_T};return aiohttp.web.Response(text=E,headers=F,status=200)
if full_path.is_file() async def handle_unlock(A,request):
else await self.get_directory_size(full_path) B=request
) if not await A.authenticate(B):return aiohttp.web.Response(status=401,headers={_B:_C})
etree.SubElement(prop, "{DAV:}quota-available-bytes").text = str( C=B.match_info.get(_D,'/');D=B.headers.get(_U,'').replace('opaquelocktoken:','')[1:-1]
await self.get_disk_free_space(request["home"]) if A.locks.get(C)==D:del A.locks[C];return aiohttp.web.Response(status=204)
) return aiohttp.web.Response(status=400,text='Invalid Lock Token')
etree.SubElement(prop, "{DAV:}getlastmodified").text = last_modified async def generate_lock_response(J,lock_id):B=lock_id;D={'D':'DAV:'};C=etree.Element(_Q,nsmap=D);E=etree.SubElement(C,_R);A=etree.SubElement(E,'{DAV:}activelock');F=etree.SubElement(A,_I);etree.SubElement(F,_J);G=etree.SubElement(A,_H);etree.SubElement(G,_S);etree.SubElement(A,'{DAV:}depth').text='Infinity';H=etree.SubElement(A,'{DAV:}owner');etree.SubElement(H,_G).text=B;etree.SubElement(A,'{DAV:}timeout').text='Infinite';I=etree.SubElement(A,'{DAV:}locktoken');etree.SubElement(I,_G).text=f"opaquelocktoken:{B}";return etree.tostring(C,pretty_print=_E,encoding='utf-8').decode()
etree.SubElement(prop, "{DAV:}displayname").text = full_path.name def get_last_modified(C,path):
etree.SubElement(prop, "{DAV:}lockdiscovery") if not path.exists():return
mimetype, _ = mimetypes.guess_type(full_path.name) A=path.stat().st_mtime;B=datetime.datetime.utcfromtimestamp(A);return B.strftime(_P)
if full_path.is_file(): async def handle_head(D,request):
etree.SubElement(prop, "{DAV:}contenttype").text = mimetype B=request
etree.SubElement(prop, "{DAV:}getcontentlength").text = str( if not await D.authenticate(B):return aiohttp.web.Response(status=401,headers={_B:_C})
await self.get_file_size(full_path) E=B.match_info.get(_D,'');A=B[_A]/E
if full_path.is_file() if not A.exists():return aiohttp.web.Response(status=404,text=_K)
else await self.get_directory_size(full_path) if A.is_dir():return aiohttp.web.Response(status=403,text='Cannot get metadata for a directory')
) C,H=mimetypes.guess_type(str(A));C=C or _L;F=A.stat().st_size;G={_F:C,'Content-Length':str(F),'Last-Modified':D.get_last_modified(A)};return aiohttp.web.Response(status=200,headers=G)
supported_lock = etree.SubElement(prop, "{DAV:}supportedlock")
lock_entry_1 = etree.SubElement(supported_lock, "{DAV:}lockentry")
lock_scope_1 = etree.SubElement(lock_entry_1, "{DAV:}lockscope")
etree.SubElement(lock_scope_1, "{DAV:}exclusive")
lock_type_1 = etree.SubElement(lock_entry_1, "{DAV:}locktype")
etree.SubElement(lock_type_1, "{DAV:}write")
lock_entry_2 = etree.SubElement(supported_lock, "{DAV:}lockentry")
lock_scope_2 = etree.SubElement(lock_entry_2, "{DAV:}lockscope")
etree.SubElement(lock_scope_2, "{DAV:}shared")
lock_type_2 = etree.SubElement(lock_entry_2, "{DAV:}locktype")
etree.SubElement(lock_type_2, "{DAV:}write")
etree.SubElement(propstat, "{DAV:}status").text = "HTTP/1.1 200 OK"
if abs_path.is_dir() and depth > 0:
for item in abs_path.iterdir():
await self.create_node(request, response_xml, item, depth - 1)
async def handle_propfind(self, request):
if not await self.authenticate(request):
return aiohttp.web.Response(
status=401, headers={"WWW-Authenticate": 'Basic realm="WebDAV"'}
)
depth = 0
try:
depth = int(request.headers.get("Depth", "0"))
except ValueError:
pass
requested_path = request.match_info.get("filename", "")
abs_path = request["home"] / requested_path
if not abs_path.exists():
return aiohttp.web.Response(status=404, text="Directory not found")
nsmap = {"D": "DAV:"}
response_xml = etree.Element("{DAV:}multistatus", nsmap=nsmap)
await self.create_node(request, response_xml, abs_path, depth)
xml_output = etree.tostring(
response_xml, encoding="utf-8", xml_declaration=True
).decode()
return aiohttp.web.Response(
status=207, text=xml_output, content_type="application/xml"
)
async def handle_proppatch(self, request):
if not await self.authenticate(request):
return aiohttp.web.Response(
status=401, headers={"WWW-Authenticate": 'Basic realm="WebDAV"'}
)
return aiohttp.web.Response(status=207, text="PROPPATCH OK (Not Implemented)")
async def handle_lock(self, request):
if not await self.authenticate(request):
return aiohttp.web.Response(
status=401, headers={"WWW-Authenticate": 'Basic realm="WebDAV"'}
)
resource = request.match_info.get("filename", "/")
lock_id = str(uuid.uuid4())
self.locks[resource] = lock_id
xml_response = await self.generate_lock_response(lock_id)
headers = {
"Lock-Token": f"opaquelocktoken:{lock_id}",
"Content-Type": "application/xml",
}
return aiohttp.web.Response(text=xml_response, headers=headers, status=200)
async def handle_unlock(self, request):
if not await self.authenticate(request):
return aiohttp.web.Response(
status=401, headers={"WWW-Authenticate": 'Basic realm="WebDAV"'}
)
resource = request.match_info.get("filename", "/")
lock_token = request.headers.get("Lock-Token", "").replace(
"opaquelocktoken:", ""
)[1:-1]
if self.locks.get(resource) == lock_token:
del self.locks[resource]
return aiohttp.web.Response(status=204)
return aiohttp.web.Response(status=400, text="Invalid Lock Token")
async def generate_lock_response(self, lock_id):
nsmap = {"D": "DAV:"}
root = etree.Element("{DAV:}prop", nsmap=nsmap)
lock_discovery = etree.SubElement(root, "{DAV:}lockdiscovery")
active_lock = etree.SubElement(lock_discovery, "{DAV:}activelock")
lock_type = etree.SubElement(active_lock, "{DAV:}locktype")
etree.SubElement(lock_type, "{DAV:}write")
lock_scope = etree.SubElement(active_lock, "{DAV:}lockscope")
etree.SubElement(lock_scope, "{DAV:}exclusive")
etree.SubElement(active_lock, "{DAV:}depth").text = "Infinity"
owner = etree.SubElement(active_lock, "{DAV:}owner")
etree.SubElement(owner, "{DAV:}href").text = lock_id
etree.SubElement(active_lock, "{DAV:}timeout").text = "Infinite"
lock_token = etree.SubElement(active_lock, "{DAV:}locktoken")
etree.SubElement(lock_token, "{DAV:}href").text = f"opaquelocktoken:{lock_id}"
return etree.tostring(root, pretty_print=True, encoding="utf-8").decode()
def get_last_modified(self, path):
if not path.exists():
return None
timestamp = path.stat().st_mtime
dt = datetime.datetime.utcfromtimestamp(timestamp)
return dt.strftime("%a, %d %b %Y %H:%M:%S GMT")
async def handle_head(self, request):
if not await self.authenticate(request):
return aiohttp.web.Response(
status=401, headers={"WWW-Authenticate": 'Basic realm="WebDAV"'}
)
requested_path = request.match_info.get("filename", "")
abs_path = request["home"] / requested_path
if not abs_path.exists():
return aiohttp.web.Response(status=404, text="File not found")
if abs_path.is_dir():
return aiohttp.web.Response(
status=403, text="Cannot get metadata for a directory"
)
content_type, _ = mimetypes.guess_type(str(abs_path))
content_type = content_type or "application/octet-stream"
file_size = abs_path.stat().st_size
headers = {
"Content-Type": content_type,
"Content-Length": str(file_size),
"Last-Modified": self.get_last_modified(abs_path),
}
return aiohttp.web.Response(status=200, headers=headers)

View File

@ -1,78 +1,25 @@
import asyncio _A=True
import logging import asyncio,logging,os,asyncssh
import os
import asyncssh
asyncssh.set_debug_level(2) asyncssh.set_debug_level(2)
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
# Configuration for SFTP server SFTP_ROOT='.'
SFTP_ROOT = "." # Directory to serve USERNAME='test'
USERNAME = "test" PASSWORD='woeii'
PASSWORD = "woeii" HOST='localhost'
HOST = "localhost" PORT=2225
PORT = 2225
class MySFTPServer(asyncssh.SFTPServer): class MySFTPServer(asyncssh.SFTPServer):
def __init__(self, chan): def __init__(A,chan):super().__init__(chan);A.root=os.path.abspath(SFTP_ROOT)
super().__init__(chan) async def stat(A,path):"Handles 'stat' command from SFTP client";B=os.path.join(A.root,path.lstrip('/'));return await super().stat(B)
self.root = os.path.abspath(SFTP_ROOT) async def open(A,path,flags,attrs):'Handles file open requests';B=os.path.join(A.root,path.lstrip('/'));return await super().open(B,flags,attrs)
async def listdir(A,path):'Handles directory listing';B=os.path.join(A.root,path.lstrip('/'));return await super().listdir(B)
async def stat(self, path):
"""Handles 'stat' command from SFTP client"""
full_path = os.path.join(self.root, path.lstrip("/"))
return await super().stat(full_path)
async def open(self, path, flags, attrs):
"""Handles file open requests"""
full_path = os.path.join(self.root, path.lstrip("/"))
return await super().open(full_path, flags, attrs)
async def listdir(self, path):
"""Handles directory listing"""
full_path = os.path.join(self.root, path.lstrip("/"))
return await super().listdir(full_path)
class MySSHServer(asyncssh.SSHServer): class MySSHServer(asyncssh.SSHServer):
"""Custom SSH server to handle authentication""" 'Custom SSH server to handle authentication'
def connection_made(A,conn):print(f"New connection from {conn.get_extra_info("peername")}")
def connection_made(self, conn): def connection_lost(A,exc):print('Client disconnected')
print(f"New connection from {conn.get_extra_info('peername')}") def begin_auth(A,username):return _A
def password_auth_supported(A):return _A
def connection_lost(self, exc): def validate_password(C,username,password):A=password;B=username;print(B,A);return _A;return B==USERNAME and A==PASSWORD
print("Client disconnected") async def start_sftp_server():os.makedirs(SFTP_ROOT,exist_ok=_A);await asyncssh.create_server(lambda:MySSHServer(),host=HOST,port=PORT,server_host_keys=['ssh_host_key'],process_factory=MySFTPServer);print(f"SFTP server running on {HOST}:{PORT}");await asyncio.Future()
if __name__=='__main__':
def begin_auth(self, username): try:asyncio.run(start_sftp_server())
return True # No additional authentication steps except(OSError,asyncssh.Error)as e:print(f"Error starting SFTP server: {e}")
def password_auth_supported(self):
return True # Support password authentication
def validate_password(self, username, password):
print(username, password)
return True
return username == USERNAME and password == PASSWORD
async def start_sftp_server():
os.makedirs(SFTP_ROOT, exist_ok=True) # Ensure the root directory exists
await asyncssh.create_server(
lambda: MySSHServer(),
host=HOST,
port=PORT,
server_host_keys=["ssh_host_key"],
process_factory=MySFTPServer,
)
print(f"SFTP server running on {HOST}:{PORT}")
await asyncio.Future() # Keep running forever
if __name__ == "__main__":
try:
asyncio.run(start_sftp_server())
except (OSError, asyncssh.Error) as e:
print(f"Error starting SFTP server: {e}")

View File

@ -1,77 +1,28 @@
import asyncio import asyncio,os,asyncssh
import os HOST='0.0.0.0'
PORT=2225
import asyncssh USERNAME='user'
PASSWORD='password'
# SSH Server Configuration SHELL='/bin/sh'
HOST = "0.0.0.0"
PORT = 2225
USERNAME = "user"
PASSWORD = "password"
SHELL = "/bin/sh" # Change to another shell if needed
class CustomSSHServer(asyncssh.SSHServer): class CustomSSHServer(asyncssh.SSHServer):
def connection_made(self, conn): def connection_made(A,conn):print(f"New connection from {conn.get_extra_info("peername")}")
print(f"New connection from {conn.get_extra_info('peername')}") def connection_lost(A,exc):print('Client disconnected')
def password_auth_supported(A):return True
def connection_lost(self, exc): def validate_password(A,username,password):return username==USERNAME and password==PASSWORD
print("Client disconnected")
def password_auth_supported(self):
return True
def validate_password(self, username, password):
return username == USERNAME and password == PASSWORD
async def custom_bash_process(process): async def custom_bash_process(process):
"""Spawns a custom bash shell process""" 'Spawns a custom bash shell process';A=process;B=os.environ.copy();B['TERM']='xterm-256color';C=await asyncio.create_subprocess_exec(SHELL,'-i',stdin=asyncio.subprocess.PIPE,stdout=asyncio.subprocess.PIPE,stderr=asyncio.subprocess.PIPE,env=B)
env = os.environ.copy() async def D():
env["TERM"] = "xterm-256color"
# Start the Bash shell
bash_proc = await asyncio.create_subprocess_exec(
SHELL,
"-i",
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
env=env,
)
async def read_output():
while True: while True:
data = await bash_proc.stdout.read(1) B=await C.stdout.read(1)
if not data: if not B:break
break A.stdout.write(B)
process.stdout.write(data) async def E():
async def read_input():
while True: while True:
data = await process.stdin.read(1) B=await A.stdin.read(1)
if not data: if not B:break
break C.stdin.write(B)
bash_proc.stdin.write(data) await asyncio.gather(D(),E())
async def start_ssh_server():'Starts the AsyncSSH server with Bash';await asyncssh.create_server(lambda:CustomSSHServer(),host=HOST,port=PORT,server_host_keys=['ssh_host_key'],process_factory=custom_bash_process);print(f"SSH server running on {HOST}:{PORT}");await asyncio.Future()
await asyncio.gather(read_output(), read_input()) if __name__=='__main__':
try:asyncio.run(start_ssh_server())
except(OSError,asyncssh.Error)as e:print(f"Error starting SSH server: {e}")
async def start_ssh_server():
"""Starts the AsyncSSH server with Bash"""
await asyncssh.create_server(
lambda: CustomSSHServer(),
host=HOST,
port=PORT,
server_host_keys=["ssh_host_key"],
process_factory=custom_bash_process,
)
print(f"SSH server running on {HOST}:{PORT}")
await asyncio.Future() # Keep running
if __name__ == "__main__":
try:
asyncio.run(start_ssh_server())
except (OSError, asyncssh.Error) as e:
print(f"Error starting SSH server: {e}")

View File

@ -1,74 +1,17 @@
#!/usr/bin/env python3.7 #!/usr/bin/env python3.7
# import asyncio,sys,asyncssh
# Copyright (c) 2013-2024 by Ron Frederick <ronf@timeheart.net> and others. async def handle_client(process):
# A=process;E,F,C,D=A.term_size;A.stdout.write(f"Terminal type: {A.term_type}, size: {E}x{F}")
# This program and the accompanying materials are made available under if C and D:A.stdout.write(f" ({C}x{D} pixels)")
# the terms of the Eclipse Public License v2.0 which accompanies this A.stdout.write('\nTry resizing your window!\n')
# distribution and is available at: while not A.stdin.at_eof():
# try:await A.stdin.read()
# http://www.eclipse.org/legal/epl-2.0/ except asyncssh.TerminalSizeChanged as B:
# A.stdout.write(f"New window size: {B.width}x{B.height}")
# This program may also be made available under the following secondary if B.pixwidth and B.pixheight:A.stdout.write(f" ({B.pixwidth}x{B.pixheight} pixels)")
# licenses when the conditions for such availability set forth in the A.stdout.write('\n')
# Eclipse Public License v2.0 are satisfied: async def start_server():await asyncssh.listen('',2230,server_host_keys=['ssh_host_key'],process_factory=handle_client)
# loop=asyncio.new_event_loop()
# GNU General Public License, Version 2.0, or any later versions of try:loop.run_until_complete(start_server())
# that license except(OSError,asyncssh.Error)as exc:sys.exit('Error starting server: '+str(exc))
#
# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
#
# Contributors:
# Ron Frederick - initial implementation, API, and documentation
# To run this program, the file ``ssh_host_key`` must exist with an SSH
# private key in it to use as a server host key. An SSH host certificate
# can optionally be provided in the file ``ssh_host_key-cert.pub``.
#
# The file ``ssh_user_ca`` must exist with a cert-authority entry of
# the certificate authority which can sign valid client certificates.
import asyncio
import sys
import asyncssh
async def handle_client(process: asyncssh.SSHServerProcess) -> None:
width, height, pixwidth, pixheight = process.term_size
process.stdout.write(
f"Terminal type: {process.term_type}, " f"size: {width}x{height}"
)
if pixwidth and pixheight:
process.stdout.write(f" ({pixwidth}x{pixheight} pixels)")
process.stdout.write("\nTry resizing your window!\n")
while not process.stdin.at_eof():
try:
await process.stdin.read()
except asyncssh.TerminalSizeChanged as exc:
process.stdout.write(f"New window size: {exc.width}x{exc.height}")
if exc.pixwidth and exc.pixheight:
process.stdout.write(f" ({exc.pixwidth}" f"x{exc.pixheight} pixels)")
process.stdout.write("\n")
async def start_server() -> None:
await asyncssh.listen(
"",
2230,
server_host_keys=["ssh_host_key"],
# authorized_client_keys='ssh_user_ca',
process_factory=handle_client,
)
loop = asyncio.new_event_loop()
try:
loop.run_until_complete(start_server())
except (OSError, asyncssh.Error) as exc:
sys.exit("Error starting server: " + str(exc))
loop.run_forever() loop.run_forever()

View File

@ -1,90 +1,24 @@
#!/usr/bin/env python3.7 #!/usr/bin/env python3.7
# import asyncio,sys
# Copyright (c) 2013-2024 by Ron Frederick <ronf@timeheart.net> and others.
#
# This program and the accompanying materials are made available under
# the terms of the Eclipse Public License v2.0 which accompanies this
# distribution and is available at:
#
# http://www.eclipse.org/legal/epl-2.0/
#
# This program may also be made available under the following secondary
# licenses when the conditions for such availability set forth in the
# Eclipse Public License v2.0 are satisfied:
#
# GNU General Public License, Version 2.0, or any later versions of
# that license
#
# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
#
# Contributors:
# Ron Frederick - initial implementation, API, and documentation
# To run this program, the file ``ssh_host_key`` must exist with an SSH
# private key in it to use as a server host key. An SSH host certificate
# can optionally be provided in the file ``ssh_host_key-cert.pub``.
import asyncio
import sys
from typing import Optional from typing import Optional
import asyncssh,bcrypt
import asyncssh passwords={'guest':b'','user':bcrypt.hashpw(b'user',bcrypt.gensalt())}
import bcrypt def handle_client(process):A=process;B=A.get_extra_info('username');A.stdout.write(f"Welcome to my SSH server, {B}!\n")
passwords = {
"guest": b"", # guest account with no password
"user": bcrypt.hashpw(b"user", bcrypt.gensalt()),
}
def handle_client(process: asyncssh.SSHServerProcess) -> None:
username = process.get_extra_info("username")
process.stdout.write(f"Welcome to my SSH server, {username}!\n")
# process.exit(0)
class MySSHServer(asyncssh.SSHServer): class MySSHServer(asyncssh.SSHServer):
def connection_made(self, conn: asyncssh.SSHServerConnection) -> None: def connection_made(B,conn):A=conn.get_extra_info('peername')[0];print(f"SSH connection received from {A}.")
peername = conn.get_extra_info("peername")[0] def connection_lost(A,exc):
print(f"SSH connection received from {peername}.") if exc:print('SSH connection error: '+str(exc),file=sys.stderr)
else:print('SSH connection closed.')
def connection_lost(self, exc: Optional[Exception]) -> None: def begin_auth(A,username):return passwords.get(username)!=b''
if exc: def password_auth_supported(A):return True
print("SSH connection error: " + str(exc), file=sys.stderr) def validate_password(D,username,password):
else: A=password;B=username
print("SSH connection closed.") if B not in passwords:return False
C=passwords[B]
def begin_auth(self, username: str) -> bool: if not A and not C:return True
# If the user's password is the empty string, no auth is required return bcrypt.checkpw(A.encode('utf-8'),C)
return passwords.get(username) != b"" async def start_server():await asyncssh.create_server(MySSHServer,'',2231,server_host_keys=['ssh_host_key'],process_factory=handle_client)
loop=asyncio.new_event_loop()
def password_auth_supported(self) -> bool: try:loop.run_until_complete(start_server())
return True except(OSError,asyncssh.Error)as exc:sys.exit('Error starting server: '+str(exc))
def validate_password(self, username: str, password: str) -> bool:
if username not in passwords:
return False
pw = passwords[username]
if not password and not pw:
return True
return bcrypt.checkpw(password.encode("utf-8"), pw)
async def start_server() -> None:
await asyncssh.create_server(
MySSHServer,
"",
2231,
server_host_keys=["ssh_host_key"],
process_factory=handle_client,
)
loop = asyncio.new_event_loop()
try:
loop.run_until_complete(start_server())
except (OSError, asyncssh.Error) as exc:
sys.exit("Error starting server: " + str(exc))
loop.run_forever() loop.run_forever()

View File

@ -1,112 +1,28 @@
#!/usr/bin/env python3.7 #!/usr/bin/env python3.7
# import asyncio,sys
# Copyright (c) 2016-2024 by Ron Frederick <ronf@timeheart.net> and others. from typing import List,cast
#
# This program and the accompanying materials are made available under
# the terms of the Eclipse Public License v2.0 which accompanies this
# distribution and is available at:
#
# http://www.eclipse.org/legal/epl-2.0/
#
# This program may also be made available under the following secondary
# licenses when the conditions for such availability set forth in the
# Eclipse Public License v2.0 are satisfied:
#
# GNU General Public License, Version 2.0, or any later versions of
# that license
#
# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
#
# Contributors:
# Ron Frederick - initial implementation, API, and documentation
# To run this program, the file ``ssh_host_key`` must exist with an SSH
# private key in it to use as a server host key. An SSH host certificate
# can optionally be provided in the file ``ssh_host_key-cert.pub``.
#
# The file ``ssh_user_ca`` must exist with a cert-authority entry of
# the certificate authority which can sign valid client certificates.
import asyncio
import sys
from typing import List, cast
import asyncssh import asyncssh
class ChatClient: class ChatClient:
_clients: List["ChatClient"] = [] _clients:List['ChatClient']=[]
def __init__(A,process):A._process=process
def __init__(self, process: asyncssh.SSHServerProcess):
self._process = process
@classmethod @classmethod
async def handle_client(cls, process: asyncssh.SSHServerProcess): async def handle_client(A,process):await A(process).run()
await cls(process).run() async def readline(A):return cast(str,A._process.stdin.readline())
def write(A,msg):A._process.stdout.write(msg)
async def readline(self) -> str: def broadcast(A,msg):
return cast(str, self._process.stdin.readline()) for B in A._clients:
if B!=A:B.write(msg)
def write(self, msg: str) -> None: def begin_auth(A,username):return True
self._process.stdout.write(msg) def password_auth_supported(A):return True
def validate_password(A,username,password):return True
def broadcast(self, msg: str) -> None: async def run(A):
for client in self._clients: A.write('Welcome to chat!\n\n');A.write('Enter your name: ');B=(await A.readline()).rstrip('\n');A.write(f"\n{len(A._clients)} other users are connected.\n\n");A._clients.append(A);A.broadcast(f"*** {B} has entered chat ***\n")
if client != self:
client.write(msg)
def begin_auth(self, username: str) -> bool:
# If the user's password is the empty string, no auth is required
# return False
return True # passwords.get(username) != b''
def password_auth_supported(self) -> bool:
return True
def validate_password(self, username: str, password: str) -> bool:
# if username not in passwords:
# return False
# pw = passwords[username]
# if not password and not pw:
# return True
return True
# return bcrypt.checkpw(password.encode('utf-8'), pw)
async def run(self) -> None:
self.write("Welcome to chat!\n\n")
self.write("Enter your name: ")
name = (await self.readline()).rstrip("\n")
self.write(f"\n{len(self._clients)} other users are connected.\n\n")
self._clients.append(self)
self.broadcast(f"*** {name} has entered chat ***\n")
try: try:
async for line in self._process.stdin: async for C in A._process.stdin:A.broadcast(f"{B}: {C}")
self.broadcast(f"{name}: {line}") except asyncssh.BreakReceived:pass
except asyncssh.BreakReceived: A.broadcast(f"*** {B} has left chat ***\n");A._clients.remove(A)
pass async def start_server():await asyncssh.listen('',2235,server_host_keys=['ssh_host_key'],process_factory=ChatClient.handle_client)
loop=asyncio.new_event_loop()
self.broadcast(f"*** {name} has left chat ***\n") try:loop.run_until_complete(start_server())
self._clients.remove(self) except(OSError,asyncssh.Error)as exc:sys.exit('Error starting server: '+str(exc))
async def start_server() -> None:
await asyncssh.listen(
"",
2235,
server_host_keys=["ssh_host_key"],
process_factory=ChatClient.handle_client,
)
loop = asyncio.new_event_loop()
try:
loop.run_until_complete(start_server())
except (OSError, asyncssh.Error) as exc:
sys.exit("Error starting server: " + str(exc))
loop.run_forever() loop.run_forever()