Merge branch 'main' into feat/push-notifications
# Conflicts: # src/snek/app.py
This commit is contained in:
commit
fcc2d7b748
@ -38,6 +38,7 @@ dependencies = [
|
|||||||
"humanize",
|
"humanize",
|
||||||
"Pillow",
|
"Pillow",
|
||||||
"pillow-heif",
|
"pillow-heif",
|
||||||
|
"IP2Location",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.packages.find]
|
||||||
|
BIN
src/snek/IP2LOCATION-LITE-DB11.BIN
Executable file
BIN
src/snek/IP2LOCATION-LITE-DB11.BIN
Executable file
Binary file not shown.
@ -2,14 +2,13 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
import pathlib
|
import pathlib
|
||||||
import ssl
|
import ssl
|
||||||
import time
|
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from snek import snode
|
from snek import snode
|
||||||
from snek.view.threads import ThreadsView
|
from snek.view.threads import ThreadsView
|
||||||
import json
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
from ipaddress import ip_address
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
@ -21,7 +20,7 @@ from aiohttp_session import (
|
|||||||
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
|
||||||
|
import IP2Location
|
||||||
from snek.sssh import start_ssh_server
|
from snek.sssh import start_ssh_server
|
||||||
|
|
||||||
from snek.docs.app import Application as DocsApplication
|
from snek.docs.app import Application as DocsApplication
|
||||||
@ -60,9 +59,15 @@ from snek.view.user import UserView
|
|||||||
from snek.view.web import WebView
|
from snek.view.web import WebView
|
||||||
from snek.view.channel import ChannelAttachmentView
|
from snek.view.channel import ChannelAttachmentView
|
||||||
from snek.view.channel import ChannelView
|
from snek.view.channel import ChannelView
|
||||||
from snek.view.settings.containers import ContainersIndexView, ContainersCreateView, ContainersUpdateView, ContainersDeleteView
|
from snek.view.settings.containers import (
|
||||||
|
ContainersIndexView,
|
||||||
|
ContainersCreateView,
|
||||||
|
ContainersUpdateView,
|
||||||
|
ContainersDeleteView,
|
||||||
|
)
|
||||||
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"
|
||||||
|
|
||||||
|
|
||||||
@ -73,6 +78,33 @@ async def session_middleware(request, handler):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@web.middleware
|
||||||
|
async def ip2location_middleware(request, handler):
|
||||||
|
response = await handler(request)
|
||||||
|
return response
|
||||||
|
ip = request.headers.get("X-Forwarded-For", request.remote)
|
||||||
|
ipaddress = ip_address(ip)
|
||||||
|
if ipaddress.is_private:
|
||||||
|
return response
|
||||||
|
if not request.app.session.get("uid"):
|
||||||
|
return response
|
||||||
|
user = await request.app.services.user.get(uid=request.app.session.get("uid"))
|
||||||
|
if not user:
|
||||||
|
return response
|
||||||
|
location = request.app.ip2location.get(ip)
|
||||||
|
original_city = user["city"]
|
||||||
|
if user["city"] != location.city:
|
||||||
|
user["country_long"] = location.country
|
||||||
|
user["country_short"] = locaion.country_short
|
||||||
|
user["city"] = location.city
|
||||||
|
user["region"] = location.region
|
||||||
|
user["latitude"] = location.latitude
|
||||||
|
user["longitude"] = location.longitude
|
||||||
|
user["ip"] = ip
|
||||||
|
await request.app.services.user.update(user)
|
||||||
|
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("/"):
|
if request.path and not request.path.endswith("/"):
|
||||||
@ -82,16 +114,19 @@ async def trailing_slash_middleware(request, handler):
|
|||||||
|
|
||||||
|
|
||||||
class Application(BaseApplication):
|
class Application(BaseApplication):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
middlewares = [
|
middlewares = [
|
||||||
cors_middleware,
|
cors_middleware,
|
||||||
web.normalize_path_middleware(merge_slashes=True),
|
web.normalize_path_middleware(merge_slashes=True),
|
||||||
|
ip2location_middleware,
|
||||||
]
|
]
|
||||||
self.template_path = pathlib.Path(__file__).parent.joinpath("templates")
|
self.template_path = pathlib.Path(__file__).parent.joinpath("templates")
|
||||||
self.static_path = pathlib.Path(__file__).parent.joinpath("static")
|
self.static_path = pathlib.Path(__file__).parent.joinpath("static")
|
||||||
super().__init__(
|
super().__init__(
|
||||||
middlewares=middlewares, template_path=self.template_path, client_max_size=1024*1024*1024*5 *args, **kwargs
|
middlewares=middlewares,
|
||||||
|
template_path=self.template_path,
|
||||||
|
client_max_size=1024 * 1024 * 1024 * 5 * args,
|
||||||
|
**kwargs,
|
||||||
)
|
)
|
||||||
session_setup(self, EncryptedCookieStorage(SESSION_KEY))
|
session_setup(self, EncryptedCookieStorage(SESSION_KEY))
|
||||||
self.tasks = asyncio.Queue()
|
self.tasks = asyncio.Queue()
|
||||||
@ -105,7 +140,6 @@ class Application(BaseApplication):
|
|||||||
self.ssh_host = "0.0.0.0"
|
self.ssh_host = "0.0.0.0"
|
||||||
self.ssh_port = 2242
|
self.ssh_port = 2242
|
||||||
|
|
||||||
|
|
||||||
self.setup_router()
|
self.setup_router()
|
||||||
self.ssh_server = None
|
self.ssh_server = None
|
||||||
self.sync_service = None
|
self.sync_service = None
|
||||||
@ -115,7 +149,10 @@ class Application(BaseApplication):
|
|||||||
self.mappers = get_mappers(app=self)
|
self.mappers = get_mappers(app=self)
|
||||||
self.broadcast_service = None
|
self.broadcast_service = None
|
||||||
self.user_availability_service_task = None
|
self.user_availability_service_task = None
|
||||||
|
base_path = pathlib.Path(__file__).parent
|
||||||
|
self.ip2location = IP2Location.IP2Location(
|
||||||
|
base_path.joinpath("IP2LOCATION-LITE-DB11.BIN")
|
||||||
|
)
|
||||||
self.on_startup.append(self.prepare_asyncio)
|
self.on_startup.append(self.prepare_asyncio)
|
||||||
self.on_startup.append(self.start_user_availability_service)
|
self.on_startup.append(self.start_user_availability_service)
|
||||||
self.on_startup.append(self.start_ssh_server)
|
self.on_startup.append(self.start_ssh_server)
|
||||||
@ -129,7 +166,7 @@ class Application(BaseApplication):
|
|||||||
def uptime(self):
|
def uptime(self):
|
||||||
return self._format_uptime(self.uptime_seconds)
|
return self._format_uptime(self.uptime_seconds)
|
||||||
|
|
||||||
def _format_uptime(self,seconds):
|
def _format_uptime(self, seconds):
|
||||||
seconds = int(seconds)
|
seconds = int(seconds)
|
||||||
days, seconds = divmod(seconds, 86400)
|
days, seconds = divmod(seconds, 86400)
|
||||||
hours, seconds = divmod(seconds, 3600)
|
hours, seconds = divmod(seconds, 3600)
|
||||||
@ -147,14 +184,16 @@ class Application(BaseApplication):
|
|||||||
|
|
||||||
return ", ".join(parts)
|
return ", ".join(parts)
|
||||||
|
|
||||||
|
|
||||||
async def start_user_availability_service(self, app):
|
async def start_user_availability_service(self, app):
|
||||||
app.user_availability_service_task = asyncio.create_task(app.services.socket.user_availability_service())
|
app.user_availability_service_task = asyncio.create_task(
|
||||||
|
app.services.socket.user_availability_service()
|
||||||
|
)
|
||||||
|
|
||||||
async def snode_sync(self, app):
|
async def snode_sync(self, app):
|
||||||
self.sync_service = asyncio.create_task(snode.sync_service(app))
|
self.sync_service = asyncio.create_task(snode.sync_service(app))
|
||||||
|
|
||||||
async def start_ssh_server(self, app):
|
async def start_ssh_server(self, app):
|
||||||
app.ssh_server = await start_ssh_server(app,app.ssh_host,app.ssh_port)
|
app.ssh_server = await start_ssh_server(app, app.ssh_host, app.ssh_port)
|
||||||
if app.ssh_server:
|
if app.ssh_server:
|
||||||
asyncio.create_task(app.ssh_server.wait_closed())
|
asyncio.create_task(app.ssh_server.wait_closed())
|
||||||
|
|
||||||
@ -253,12 +292,11 @@ class Application(BaseApplication):
|
|||||||
self.webdav = WebdavApplication(self)
|
self.webdav = WebdavApplication(self)
|
||||||
self.git = GitApplication(self)
|
self.git = GitApplication(self)
|
||||||
self.add_subapp("/webdav", self.webdav)
|
self.add_subapp("/webdav", self.webdav)
|
||||||
self.add_subapp("/git",self.git)
|
self.add_subapp("/git", self.git)
|
||||||
|
|
||||||
#self.router.add_get("/{file_path:.*}", self.static_handler)
|
# self.router.add_get("/{file_path:.*}", self.static_handler)
|
||||||
|
|
||||||
async def handle_test(self, request):
|
async def handle_test(self, request):
|
||||||
|
|
||||||
return await self.render_template(
|
return await self.render_template(
|
||||||
"test.html", request, context={"name": "retoor"}
|
"test.html", request, context={"name": "retoor"}
|
||||||
)
|
)
|
||||||
@ -285,7 +323,6 @@ class Application(BaseApplication):
|
|||||||
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
|
||||||
):
|
):
|
||||||
|
|
||||||
parent_object = await subscribed_channel.get_channel()
|
parent_object = await subscribed_channel.get_channel()
|
||||||
|
|
||||||
item = {}
|
item = {}
|
||||||
@ -335,9 +372,8 @@ class Application(BaseApplication):
|
|||||||
|
|
||||||
return rendered
|
return rendered
|
||||||
|
|
||||||
|
|
||||||
async def static_handler(self, request):
|
async def static_handler(self, request):
|
||||||
file_name = request.match_info.get('filename', '')
|
file_name = request.match_info.get("filename", "")
|
||||||
|
|
||||||
paths = []
|
paths = []
|
||||||
|
|
||||||
@ -371,7 +407,6 @@ class Application(BaseApplication):
|
|||||||
if user_template_path:
|
if user_template_path:
|
||||||
template_paths.append(user_template_path)
|
template_paths.append(user_template_path)
|
||||||
|
|
||||||
|
|
||||||
template_paths.append(self.template_path)
|
template_paths.append(self.template_path)
|
||||||
return FileSystemLoader(template_paths)
|
return FileSystemLoader(template_paths)
|
||||||
|
|
||||||
|
@ -30,6 +30,14 @@ class UserModel(BaseModel):
|
|||||||
last_ping = ModelField(name="last_ping", required=False, kind=str)
|
last_ping = ModelField(name="last_ping", required=False, kind=str)
|
||||||
|
|
||||||
is_admin = ModelField(name="is_admin", required=False, kind=bool)
|
is_admin = ModelField(name="is_admin", required=False, kind=bool)
|
||||||
|
|
||||||
|
country_short = ModelField(name="country_short", required=False, kind=str)
|
||||||
|
country_long = ModelField(name="country_long", required=False, kind=str)
|
||||||
|
city = ModelField(name="city", required=False, kind=str)
|
||||||
|
latitude = ModelField(name="latitude", required=False, kind=float)
|
||||||
|
longitude = ModelField(name="longitude", required=False, kind=float)
|
||||||
|
region = ModelField(name="region", required=False, kind=str)
|
||||||
|
ip = ModelField(name="ip", required=False, kind=str)
|
||||||
|
|
||||||
async def get_property(self, name):
|
async def get_property(self, name):
|
||||||
prop = await self.app.services.user_property.find_one(
|
prop = await self.app.services.user_property.find_one(
|
||||||
|
@ -23,15 +23,23 @@ class MessageList extends HTMLElement {
|
|||||||
const messagesContainer = this
|
const messagesContainer = this
|
||||||
messagesContainer.addEventListener('click', (e) => {
|
messagesContainer.addEventListener('click', (e) => {
|
||||||
if (e.target.tagName !== 'IMG' || e.target.classList.contains('avatar-img')) return;
|
if (e.target.tagName !== 'IMG' || e.target.classList.contains('avatar-img')) return;
|
||||||
|
|
||||||
const img = e.target;
|
const img = e.target;
|
||||||
|
|
||||||
const overlay = document.createElement('div');
|
const overlay = document.createElement('div');
|
||||||
overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.9);display:flex;justify-content:center;align-items:center;z-index:9999;'
|
overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.9);display:flex;justify-content:center;align-items:center;z-index:9999;'
|
||||||
|
|
||||||
|
const urlObj = new URL(img.currentSrc || img.src)
|
||||||
|
urlObj.searchParams.delete("width");
|
||||||
|
urlObj.searchParams.delete("height");
|
||||||
|
|
||||||
const fullImg = document.createElement('img');
|
const fullImg = document.createElement('img');
|
||||||
const urlObj = new URL(img.src); urlObj.search = '';
|
|
||||||
fullImg.src = urlObj.toString();
|
fullImg.src = urlObj.toString();
|
||||||
fullImg.alt = img.alt;
|
fullImg.alt = img.alt;
|
||||||
fullImg.style.maxWidth = '90%';
|
fullImg.style.maxWidth = '90%';
|
||||||
fullImg.style.maxHeight = '90%';
|
fullImg.style.maxHeight = '90%';
|
||||||
|
|
||||||
overlay.appendChild(fullImg);
|
overlay.appendChild(fullImg);
|
||||||
document.body.appendChild(overlay);
|
document.body.appendChild(overlay);
|
||||||
overlay.addEventListener('click', () => document.body.removeChild(overlay));
|
overlay.addEventListener('click', () => document.body.removeChild(overlay));
|
||||||
|
@ -74,6 +74,10 @@ class BaseMapper:
|
|||||||
for record in await self.run_in_executor(self.db.query,sql, *args):
|
for record in await self.run_in_executor(self.db.query,sql, *args):
|
||||||
yield dict(record)
|
yield dict(record)
|
||||||
|
|
||||||
|
async def update(self, model):
|
||||||
|
model.updated_at.update()
|
||||||
|
return await self.run_in_executor(self.table.update, model.record, ["uid"])
|
||||||
|
|
||||||
async def delete(self, **kwargs) -> int:
|
async def delete(self, **kwargs) -> int:
|
||||||
if not kwargs or not isinstance(kwargs, dict):
|
if not kwargs or not isinstance(kwargs, dict):
|
||||||
raise Exception("Can't execute delete with no filter.")
|
raise Exception("Can't execute delete with no filter.")
|
||||||
|
@ -26,6 +26,9 @@ class BaseService:
|
|||||||
kwargs["uid"] = uid
|
kwargs["uid"] = uid
|
||||||
return await self.count(**kwargs) > 0
|
return await self.count(**kwargs) > 0
|
||||||
|
|
||||||
|
async def update(self, model):
|
||||||
|
return await self.mapper.update(model)
|
||||||
|
|
||||||
async def count(self, **kwargs):
|
async def count(self, **kwargs):
|
||||||
return await self.mapper.count(**kwargs)
|
return await self.mapper.count(**kwargs)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user