Progress.

This commit is contained in:
retoor 2025-04-03 08:34:25 +02:00
parent 81479e7058
commit d10768403d
11 changed files with 80 additions and 93 deletions

View File

@ -1,3 +1 @@

View File

@ -1,5 +1,6 @@
from aiohttp import web from aiohttp import web
from snek.app import Application from snek.app import Application
if __name__ == '__main__': if __name__ == "__main__":
web.run_app(Application(), port=8081,host='0.0.0.0') web.run_app(Application(), port=8081, host="0.0.0.0")

View File

@ -3,6 +3,7 @@ import logging
import pathlib import pathlib
import time import time
import uuid import uuid
from snek.view.threads import ThreadsView from snek.view.threads import ThreadsView
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
@ -24,7 +25,7 @@ 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 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
@ -37,12 +38,13 @@ from snek.view.logout import LogoutView
from snek.view.register import RegisterView from snek.view.register import RegisterView
from snek.view.rpc import RPCView from snek.view.rpc import RPCView
from snek.view.search_user import SearchUserView from snek.view.search_user import SearchUserView
from snek.view.settings.index import SettingsIndexView
from snek.view.settings.profile import SettingsProfileView
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.web import WebView from snek.view.web import WebView
from snek.webdav import WebdavApplication from snek.webdav import WebdavApplication
from snek.view.settings import SettingsView
SESSION_KEY = b"c79a0c5fda4b424189c427d28c9f7c34" SESSION_KEY = b"c79a0c5fda4b424189c427d28c9f7c34"
@ -76,6 +78,7 @@ class Application(BaseApplication):
session_setup(self, EncryptedCookieStorage(SESSION_KEY)) session_setup(self, EncryptedCookieStorage(SESSION_KEY))
self.tasks = asyncio.Queue() self.tasks = asyncio.Queue()
self._middlewares.append(session_middleware) self._middlewares.append(session_middleware)
self._middlewares.append(auth_middleware)
self.jinja2_env.add_extension(MarkdownExtension) self.jinja2_env.add_extension(MarkdownExtension)
self.jinja2_env.add_extension(LinkifyExtension) self.jinja2_env.add_extension(LinkifyExtension)
self.jinja2_env.add_extension(PythonExtension) self.jinja2_env.add_extension(PythonExtension)
@ -138,7 +141,9 @@ class Application(BaseApplication):
self.router.add_view("/docs.html", DocsHTMLView) self.router.add_view("/docs.html", DocsHTMLView)
self.router.add_view("/docs.md", DocsMDView) self.router.add_view("/docs.md", DocsMDView)
self.router.add_view("/status.json", StatusView) self.router.add_view("/status.json", StatusView)
self.router.add_view("/settings.html", SettingsView) 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("/web.html", WebView)
self.router.add_view("/login.html", LoginView) self.router.add_view("/login.html", LoginView)
self.router.add_view("/login.json", LoginView) self.router.add_view("/login.json", LoginView)
@ -189,8 +194,8 @@ class Application(BaseApplication):
channels = [] channels = []
if not context: if not context:
context = {} context = {}
context['rid'] = str(uuid.uuid4()) context["rid"] = str(uuid.uuid4())
if request.session.get("uid"): if request.session.get("uid"):
async for subscribed_channel in self.services.channel_member.find( async for subscribed_channel in self.services.channel_member.find(
user_uid=request.session.get("uid"), deleted_at=None, is_banned=False user_uid=request.session.get("uid"), deleted_at=None, is_banned=False

View File

@ -7,6 +7,7 @@ from snek.mapper.drive import DriveMapper
from snek.mapper.drive_item import DriveItemMapper from snek.mapper.drive_item import DriveItemMapper
from snek.mapper.notification import NotificationMapper from snek.mapper.notification import NotificationMapper
from snek.mapper.user import UserMapper from snek.mapper.user import UserMapper
from snek.mapper.user_property import UserPropertyMapper
from snek.system.object import Object from snek.system.object import Object
@ -21,6 +22,7 @@ def get_mappers(app=None):
"notification": NotificationMapper(app=app), "notification": NotificationMapper(app=app),
"drive_item": DriveItemMapper(app=app), "drive_item": DriveItemMapper(app=app),
"drive": DriveMapper(app=app), "drive": DriveMapper(app=app),
"user_property": UserPropertyMapper(app=app),
} }
) )

View File

@ -5,7 +5,11 @@ from snek.model.channel_member import ChannelMemberModel
# from snek.model.channel_message import ChannelMessageModel # from snek.model.channel_message import ChannelMessageModel
from snek.model.channel_message import ChannelMessageModel from snek.model.channel_message import ChannelMessageModel
from snek.model.drive import DriveModel
from snek.model.drive_item import DriveItemModel
from snek.model.notification import NotificationModel
from snek.model.user import UserModel from snek.model.user import UserModel
from snek.model.user_property import UserPropertyModel
from snek.system.object import Object from snek.system.object import Object
@ -17,6 +21,10 @@ def get_models():
"channel_member": ChannelMemberModel, "channel_member": ChannelMemberModel,
"channel": ChannelModel, "channel": ChannelModel,
"channel_message": ChannelMessageModel, "channel_message": ChannelMessageModel,
"drive_item": DriveItemModel,
"drive": DriveModel,
"notification": NotificationModel,
"user_property": UserPropertyModel,
} }
) )

View File

@ -28,6 +28,16 @@ async def cors_allow_middleware(request, handler):
return response return response
@web.middleware
async def auth_middleware(request, handler):
request["user"] = None
if request.session.get("uid") and request.session.get("logged_in"):
request["user"] = await request.app.services.user.get(
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"): if request.headers.get("Allow"):

View File

@ -31,7 +31,7 @@
<a class="no-select" href="/search-user.html">🔍</a> <a class="no-select" href="/search-user.html">🔍</a>
<a class="no-select" style="display:none" id="install-button" href="#">📥</a> <a class="no-select" style="display:none" id="install-button" href="#">📥</a>
<a class="no-select" href="/threads.html">👥</a> <a class="no-select" href="/threads.html">👥</a>
<a class="no-select" href="#">⚙️</a> <a class="no-select" href="/settings/index.html">⚙️</a>
<a class="no-select" href="/logout.html">🔒</a> <a class="no-select" href="/logout.html">🔒</a>
</nav> </nav>

View File

@ -13,24 +13,14 @@
{% endblock %} {% endblock %}
{% block main %} {% block logo %}
<h1>Setting page</h1> <h1>Setting page</h1>
<div id="profile_description"></div> {% endblock %}
{% block main %}
<script type="module">
require.config({ paths: { 'vs': 'https://cdn.bootcdn.net/ajax/libs/monaco-editor/0.20.0/min/vs' } });
require(['vs/editor/editor.main'], function () {
var editor = monaco.editor.create(document.getElementById('profile_description'), {
value: phpCode,
language: 'php'
});
})
</script>
{% endblock main %} {% endblock main %}

View File

@ -1,14 +0,0 @@
<style>
.channel-list-item-highlight {
/* xxx */
}
</style>
<aside class="sidebar" id="channelSidebar">
<h2>Settings</h2>
<ul>
<li><a class="no-select" href="/settings.html">Profile</a></li>
<li><a class="no-select" href="/settings-notifications.html">Notifications</a></li>
<li><a class="no-select" href="/settings-privacy.html">Privacy</a></li>
</ul>
</aside>

View File

@ -1,8 +0,0 @@
from snek.system.view import BaseView
class SettingsView(BaseView):
login_required = True
async def get(self):
return await self.render_template('settings.html')

View File

@ -1,9 +1,8 @@
import logging import logging
import pathlib import pathlib
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
import asyncio
import base64 import base64
import datetime import datetime
import mimetypes import mimetypes
@ -21,7 +20,7 @@ class WebdavApplication(aiohttp.web.Application):
def __init__(self, parent, *args, **kwargs): def __init__(self, parent, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.locks = {} self.locks = {}
self.router.add_route("OPTIONS", "/{filename:.*}", self.handle_options) self.router.add_route("OPTIONS", "/{filename:.*}", self.handle_options)
self.router.add_route("GET", "/{filename:.*}", self.handle_get) self.router.add_route("GET", "/{filename:.*}", self.handle_get)
self.router.add_route("PUT", "/{filename:.*}", self.handle_put) self.router.add_route("PUT", "/{filename:.*}", self.handle_put)
@ -31,22 +30,21 @@ class WebdavApplication(aiohttp.web.Application):
self.router.add_route("COPY", "/{filename:.*}", self.handle_copy) self.router.add_route("COPY", "/{filename:.*}", self.handle_copy)
self.router.add_route("PROPFIND", "/{filename:.*}", self.handle_propfind) self.router.add_route("PROPFIND", "/{filename:.*}", self.handle_propfind)
self.router.add_route("PROPPATCH", "/{filename:.*}", self.handle_proppatch) self.router.add_route("PROPPATCH", "/{filename:.*}", self.handle_proppatch)
#self.router.add_route("LOCK", "/{filename:.*}", self.handle_lock) # self.router.add_route("LOCK", "/{filename:.*}", self.handle_lock)
#self.router.add_route("UNLOCK", "/{filename:.*}", self.handle_unlock) # self.router.add_route("UNLOCK", "/{filename:.*}", self.handle_unlock)
self.parent = parent 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(self, request): async def authenticate(self, request):
#session = request.session # session = request.session
#if session.get('uid'): # if session.get('uid'):
# request['user'] = await self.services.user.get(uid=session['uid']) # request['user'] = await self.services.user.get(uid=session['uid'])
# try: # try:
# request['home'] = await self.services.user.get_home_folder(user_uid=request['user']['uid']) # request['home'] = await self.services.user.get_home_folder(user_uid=request['user']['uid'])
@ -60,13 +58,17 @@ class WebdavApplication(aiohttp.web.Application):
encoded_creds = auth_header.split("Basic ")[1] encoded_creds = auth_header.split("Basic ")[1]
decoded_creds = base64.b64decode(encoded_creds).decode() decoded_creds = base64.b64decode(encoded_creds).decode()
username, password = decoded_creds.split(":", 1) username, password = decoded_creds.split(":", 1)
request['user'] = await self.services.user.authenticate(username=username, password=password) request["user"] = await self.services.user.authenticate(
username=username, password=password
)
try: try:
request['home'] = await self.services.user.get_home_folder(request['user']['uid']) request["home"] = await self.services.user.get_home_folder(
request["user"]["uid"]
)
except Exception as ex: except Exception as ex:
print(ex) print(ex)
pass pass
return request['user'] return request["user"]
async def handle_get(self, request): async def handle_get(self, request):
if not await self.authenticate(request): if not await self.authenticate(request):
@ -75,7 +77,7 @@ class WebdavApplication(aiohttp.web.Application):
) )
requested_path = request.match_info.get("filename", "") requested_path = request.match_info.get("filename", "")
abs_path = request['home'] / requested_path abs_path = request["home"] / requested_path
if not abs_path.exists(): if not abs_path.exists():
return aiohttp.web.Response(status=404, text="File not found") return aiohttp.web.Response(status=404, text="File not found")
@ -95,7 +97,7 @@ class WebdavApplication(aiohttp.web.Application):
return aiohttp.web.Response( return aiohttp.web.Response(
status=401, headers={"WWW-Authenticate": 'Basic realm="WebDAV"'} status=401, headers={"WWW-Authenticate": 'Basic realm="WebDAV"'}
) )
file_path = request['home'] / request.match_info["filename"] file_path = request["home"] / request.match_info["filename"]
file_path.parent.mkdir(parents=True, exist_ok=True) file_path.parent.mkdir(parents=True, exist_ok=True)
async with aiofiles.open(file_path, "wb") as f: async with aiofiles.open(file_path, "wb") as f:
while chunk := await request.content.read(1024): while chunk := await request.content.read(1024):
@ -107,7 +109,7 @@ class WebdavApplication(aiohttp.web.Application):
return aiohttp.web.Response( return aiohttp.web.Response(
status=401, headers={"WWW-Authenticate": 'Basic realm="WebDAV"'} status=401, headers={"WWW-Authenticate": 'Basic realm="WebDAV"'}
) )
file_path = request['home'] / request.match_info["filename"] file_path = request["home"] / request.match_info["filename"]
if file_path.is_file(): if file_path.is_file():
file_path.unlink() file_path.unlink()
return aiohttp.web.Response(status=204) return aiohttp.web.Response(status=204)
@ -121,7 +123,7 @@ class WebdavApplication(aiohttp.web.Application):
return aiohttp.web.Response( return aiohttp.web.Response(
status=401, headers={"WWW-Authenticate": 'Basic realm="WebDAV"'} status=401, headers={"WWW-Authenticate": 'Basic realm="WebDAV"'}
) )
dir_path = request['home'] / request.match_info["filename"] dir_path = request["home"] / request.match_info["filename"]
if dir_path.exists(): if dir_path.exists():
return aiohttp.web.Response(status=405, text="Directory already exists") return aiohttp.web.Response(status=405, text="Directory already exists")
dir_path.mkdir(parents=True, exist_ok=True) dir_path.mkdir(parents=True, exist_ok=True)
@ -132,8 +134,8 @@ class WebdavApplication(aiohttp.web.Application):
return aiohttp.web.Response( return aiohttp.web.Response(
status=401, headers={"WWW-Authenticate": 'Basic realm="WebDAV"'} status=401, headers={"WWW-Authenticate": 'Basic realm="WebDAV"'}
) )
src_path = request['home'] / request.match_info["filename"] src_path = request["home"] / request.match_info["filename"]
dest_path = request['home'] / request.headers.get("Destination", "").replace( dest_path = request["home"] / request.headers.get("Destination", "").replace(
"http://localhost:8080/", "" "http://localhost:8080/", ""
) )
if not src_path.exists(): if not src_path.exists():
@ -146,8 +148,8 @@ class WebdavApplication(aiohttp.web.Application):
return aiohttp.web.Response( return aiohttp.web.Response(
status=401, headers={"WWW-Authenticate": 'Basic realm="WebDAV"'} status=401, headers={"WWW-Authenticate": 'Basic realm="WebDAV"'}
) )
src_path = request['home'] / request.match_info["filename"] src_path = request["home"] / request.match_info["filename"]
dest_path = request['home'] / request.headers.get("Destination", "").replace( dest_path = request["home"] / request.headers.get("Destination", "").replace(
"http://localhost:8080/", "" "http://localhost:8080/", ""
) )
if not src_path.exists(): if not src_path.exists():
@ -188,14 +190,13 @@ class WebdavApplication(aiohttp.web.Application):
statvfs = os.statvfs(path) statvfs = os.statvfs(path)
return statvfs.f_bavail * statvfs.f_frsize return statvfs.f_bavail * statvfs.f_frsize
async def create_node(self, request, response_xml, full_path, depth): async def create_node(self, request, response_xml, full_path, depth):
requested_path = request.match_info.get("filename", "") request.match_info.get("filename", "")
abs_path = pathlib.Path(full_path) abs_path = pathlib.Path(full_path)
relative_path = str(full_path.relative_to(request['home'])) relative_path = str(full_path.relative_to(request["home"]))
href_path = f"{relative_path}".strip("/") href_path = f"{relative_path}".strip("/")
#href_path = href_path.replace("./","/") # href_path = href_path.replace("./","/")
href_path = href_path.replace("//", "/") href_path = href_path.replace("//", "/")
response = etree.SubElement(response_xml, "{DAV:}response") response = etree.SubElement(response_xml, "{DAV:}response")
href = etree.SubElement(response, "{DAV:}href") href = etree.SubElement(response, "{DAV:}href")
@ -213,7 +214,7 @@ class WebdavApplication(aiohttp.web.Application):
else self.get_directory_size(full_path) else self.get_directory_size(full_path)
) )
etree.SubElement(prop, "{DAV:}quota-available-bytes").text = str( etree.SubElement(prop, "{DAV:}quota-available-bytes").text = str(
self.get_disk_free_space(request['home']) self.get_disk_free_space(request["home"])
) )
etree.SubElement(prop, "{DAV:}getlastmodified").text = last_modified etree.SubElement(prop, "{DAV:}getlastmodified").text = last_modified
etree.SubElement(prop, "{DAV:}displayname").text = full_path.name etree.SubElement(prop, "{DAV:}displayname").text = full_path.name
@ -222,10 +223,10 @@ class WebdavApplication(aiohttp.web.Application):
if full_path.is_file(): if full_path.is_file():
etree.SubElement(prop, "{DAV:}contenttype").text = mimetype etree.SubElement(prop, "{DAV:}contenttype").text = mimetype
etree.SubElement(prop, "{DAV:}getcontentlength").text = str( etree.SubElement(prop, "{DAV:}getcontentlength").text = str(
full_path.stat().st_size full_path.stat().st_size
if full_path.is_file() if full_path.is_file()
else self.get_directory_size(full_path) else self.get_directory_size(full_path)
) )
supported_lock = etree.SubElement(prop, "{DAV:}supportedlock") supported_lock = etree.SubElement(prop, "{DAV:}supportedlock")
lock_entry_1 = etree.SubElement(supported_lock, "{DAV:}lockentry") lock_entry_1 = etree.SubElement(supported_lock, "{DAV:}lockentry")
lock_scope_1 = etree.SubElement(lock_entry_1, "{DAV:}lockscope") lock_scope_1 = etree.SubElement(lock_entry_1, "{DAV:}lockscope")
@ -238,13 +239,10 @@ class WebdavApplication(aiohttp.web.Application):
lock_type_2 = etree.SubElement(lock_entry_2, "{DAV:}locktype") lock_type_2 = etree.SubElement(lock_entry_2, "{DAV:}locktype")
etree.SubElement(lock_type_2, "{DAV:}write") etree.SubElement(lock_type_2, "{DAV:}write")
etree.SubElement(propstat, "{DAV:}status").text = "HTTP/1.1 200 OK" etree.SubElement(propstat, "{DAV:}status").text = "HTTP/1.1 200 OK"
if abs_path.is_dir() and depth != -1: if abs_path.is_dir() and depth != -1:
for item in abs_path.iterdir(): for item in abs_path.iterdir():
await self.create_node(request,response_xml, item, depth - 1) await self.create_node(request, response_xml, item, depth - 1)
async def handle_propfind(self, request): async def handle_propfind(self, request):
if not await self.authenticate(request): if not await self.authenticate(request):
@ -257,14 +255,13 @@ class WebdavApplication(aiohttp.web.Application):
depth = int(request.headers.get("Depth", "0")) depth = int(request.headers.get("Depth", "0"))
except ValueError: except ValueError:
pass pass
requested_path = request.match_info.get("filename", "") requested_path = request.match_info.get("filename", "")
abs_path = request['home'] / requested_path abs_path = request["home"] / requested_path
if not abs_path.exists(): if not abs_path.exists():
return aiohttp.web.Response(status=404, text="Directory not found") return aiohttp.web.Response(status=404, text="Directory not found")
nsmap = {"D": "DAV:"} nsmap = {"D": "DAV:"}
response_xml = etree.Element("{DAV:}multistatus", nsmap=nsmap) response_xml = etree.Element("{DAV:}multistatus", nsmap=nsmap)
directories = [requested_path]
await self.create_node(request, response_xml, abs_path, depth) await self.create_node(request, response_xml, abs_path, depth)
xml_output = etree.tostring( xml_output = etree.tostring(
@ -286,9 +283,9 @@ class WebdavApplication(aiohttp.web.Application):
return aiohttp.web.Response( return aiohttp.web.Response(
status=401, headers={"WWW-Authenticate": 'Basic realm="WebDAV"'} status=401, headers={"WWW-Authenticate": 'Basic realm="WebDAV"'}
) )
resource = request.match_info.get("filename", "/") request.match_info.get("filename", "/")
lock_id = str(uuid.uuid4()) lock_id = str(uuid.uuid4())
#self.locks[resource] = lock_id # self.locks[resource] = lock_id
xml_response = self.generate_lock_response(lock_id) xml_response = self.generate_lock_response(lock_id)
headers = { headers = {
"Lock-Token": f"opaquelocktoken:{lock_id}", "Lock-Token": f"opaquelocktoken:{lock_id}",
@ -341,8 +338,8 @@ class WebdavApplication(aiohttp.web.Application):
) )
requested_path = request.match_info.get("filename", "") requested_path = request.match_info.get("filename", "")
print(requested_path) print(requested_path)
abs_path = request['home'] / requested_path abs_path = request["home"] / requested_path
if not abs_path.exists(): if not abs_path.exists():
return aiohttp.web.Response(status=404, text="File not found") return aiohttp.web.Response(status=404, text="File not found")
@ -363,5 +360,3 @@ class WebdavApplication(aiohttp.web.Application):
} }
return aiohttp.web.Response(status=200, headers=headers) return aiohttp.web.Response(status=200, headers=headers)