Update.
This commit is contained in:
parent
3412aa0bf0
commit
9133b7c3ce
@ -53,6 +53,7 @@ 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.view.channel import ChannelAttachmentView
|
||||||
from snek.webdav import WebdavApplication
|
from snek.webdav import WebdavApplication
|
||||||
from snek.sgit import GitApplication
|
from snek.sgit import GitApplication
|
||||||
|
|
||||||
@ -175,6 +176,8 @@ class Application(BaseApplication):
|
|||||||
self.router.add_get("/http-get", self.handle_http_get)
|
self.router.add_get("/http-get", self.handle_http_get)
|
||||||
self.router.add_get("/http-photo", self.handle_http_photo)
|
self.router.add_get("/http-photo", self.handle_http_photo)
|
||||||
self.router.add_get("/rpc.ws", RPCView)
|
self.router.add_get("/rpc.ws", RPCView)
|
||||||
|
self.router.add_view("/channel/{channel_uid}/attachment.bin",ChannelAttachmentView)
|
||||||
|
self.router.add_view("/channel/attachment/{relative_url:.*}",ChannelAttachmentView)
|
||||||
self.router.add_view("/channel/{channel}.html", WebView)
|
self.router.add_view("/channel/{channel}.html", WebView)
|
||||||
self.router.add_view("/threads.html", ThreadsView)
|
self.router.add_view("/threads.html", ThreadsView)
|
||||||
self.router.add_view("/terminal.ws", TerminalSocketView)
|
self.router.add_view("/terminal.ws", TerminalSocketView)
|
||||||
|
@ -9,6 +9,7 @@ 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.mapper.user_property import UserPropertyMapper
|
||||||
from snek.mapper.repository import RepositoryMapper
|
from snek.mapper.repository import RepositoryMapper
|
||||||
|
from snek.mapper.channel_attachment import ChannelAttachmentMapper
|
||||||
from snek.system.object import Object
|
from snek.system.object import Object
|
||||||
|
|
||||||
|
|
||||||
@ -25,6 +26,7 @@ def get_mappers(app=None):
|
|||||||
"drive": DriveMapper(app=app),
|
"drive": DriveMapper(app=app),
|
||||||
"user_property": UserPropertyMapper(app=app),
|
"user_property": UserPropertyMapper(app=app),
|
||||||
"repository": RepositoryMapper(app=app),
|
"repository": RepositoryMapper(app=app),
|
||||||
|
"channel_attachment": ChannelAttachmentMapper(app=app),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
7
src/snek/mapper/channel_attachment.py
Normal file
7
src/snek/mapper/channel_attachment.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from snek.model.channel_attachment import ChannelAttachmentModel
|
||||||
|
from snek.system.mapper import BaseMapper
|
||||||
|
|
||||||
|
|
||||||
|
class ChannelAttachmentMapper(BaseMapper):
|
||||||
|
table_name = "channel_attachment"
|
||||||
|
model_class = ChannelAttachmentModel
|
@ -11,6 +11,7 @@ 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.model.user_property import UserPropertyModel
|
||||||
from snek.model.repository import RepositoryModel
|
from snek.model.repository import RepositoryModel
|
||||||
|
from snek.model.channel_attachment import ChannelAttachmentModel
|
||||||
from snek.system.object import Object
|
from snek.system.object import Object
|
||||||
|
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ def get_models():
|
|||||||
"notification": NotificationModel,
|
"notification": NotificationModel,
|
||||||
"user_property": UserPropertyModel,
|
"user_property": UserPropertyModel,
|
||||||
"repository": RepositoryModel,
|
"repository": RepositoryModel,
|
||||||
|
"channel_attachment": ChannelAttachmentModel,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
16
src/snek/model/channel_attachment.py
Normal file
16
src/snek/model/channel_attachment.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from snek.system.model import BaseModel
|
||||||
|
from snek.system.model import BaseModel, ModelField
|
||||||
|
|
||||||
|
|
||||||
|
class ChannelAttachmentModel(BaseModel):
|
||||||
|
|
||||||
|
name = ModelField(name="name", required=True, kind=str)
|
||||||
|
channel_uid = ModelField(name="channel_uid", required=True, kind=str)
|
||||||
|
path = ModelField(name="path", required=True, kind=str)
|
||||||
|
size = ModelField(name="size", required=False, kind=int)
|
||||||
|
user_uid = ModelField(name="user_uid", required=True, kind=str)
|
||||||
|
mime_type = ModelField(name="type", required=True, kind=str)
|
||||||
|
relative_url = ModelField(name="relative_url", required=True, kind=str)
|
||||||
|
resource_type = ModelField(name="resource_type", required=True, kind=str,value="file")
|
||||||
|
|
||||||
|
|
@ -12,6 +12,7 @@ from snek.service.user import UserService
|
|||||||
from snek.service.user_property import UserPropertyService
|
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.service.channel_attachment import ChannelAttachmentService
|
||||||
from snek.system.object import Object
|
from snek.system.object import Object
|
||||||
from snek.service.db import DBService
|
from snek.service.db import DBService
|
||||||
|
|
||||||
@ -32,6 +33,7 @@ def get_services(app):
|
|||||||
"user_property": UserPropertyService(app=app),
|
"user_property": UserPropertyService(app=app),
|
||||||
"repository": RepositoryService(app=app),
|
"repository": RepositoryService(app=app),
|
||||||
"db": DBService(app=app),
|
"db": DBService(app=app),
|
||||||
|
"channel_attachment": ChannelAttachmentService(app=app),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -3,10 +3,19 @@ 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
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
|
||||||
class ChannelService(BaseService):
|
class ChannelService(BaseService):
|
||||||
mapper_name = "channel"
|
mapper_name = "channel"
|
||||||
|
|
||||||
|
async def get_attachment_folder(self, channel_uid,ensure=False):
|
||||||
|
path = pathlib.Path(f"./drive/{channel_uid}/attachments")
|
||||||
|
if ensure:
|
||||||
|
path.mkdir(
|
||||||
|
parents=True, exist_ok=True
|
||||||
|
)
|
||||||
|
return path
|
||||||
|
|
||||||
async def get(self, uid=None, **kwargs):
|
async def get(self, uid=None, **kwargs):
|
||||||
if uid:
|
if uid:
|
||||||
kwargs["uid"] = uid
|
kwargs["uid"] = uid
|
||||||
|
25
src/snek/service/channel_attachment.py
Normal file
25
src/snek/service/channel_attachment.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from snek.system.service import BaseService
|
||||||
|
import urllib.parse
|
||||||
|
import pathlib
|
||||||
|
import mimetypes
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
class ChannelAttachmentService(BaseService):
|
||||||
|
mapper_name="channel_attachment"
|
||||||
|
|
||||||
|
async def create_file(self, channel_uid, user_uid, name):
|
||||||
|
attachment = await self.new()
|
||||||
|
attachment["channel_uid"] = channel_uid
|
||||||
|
attachment['user_uid'] = user_uid
|
||||||
|
attachment["name"] = name
|
||||||
|
attachment["mime_type"] = mimetypes.guess_type(name)[0]
|
||||||
|
attachment['resource_type'] = "file"
|
||||||
|
real_file_name = f"{attachment['uid']}-{name}"
|
||||||
|
attachment["relative_url"] = urllib.parse.quote(f"{attachment['uid']}/{name}")
|
||||||
|
attachment_folder = await self.services.channel.get_attachment_folder(channel_uid)
|
||||||
|
attachment_path = attachment_folder.joinpath(real_file_name)
|
||||||
|
attachment["path"] = str(attachment_path)
|
||||||
|
if await self.save(attachment):
|
||||||
|
return attachment
|
||||||
|
raise Exception(f"Failed to create channel attachment: {attachment.errors}.")
|
||||||
|
|
@ -21,13 +21,13 @@ class UploadButtonElement extends HTMLElement {
|
|||||||
|
|
||||||
const files = fileInput.files;
|
const files = fileInput.files;
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('channel_uid', this.channelUid);
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
formData.append('files[]', files[i]);
|
formData.append('files[]', files[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const request = new XMLHttpRequest();
|
const request = new XMLHttpRequest();
|
||||||
request.open('POST', '/drive.bin', true);
|
|
||||||
|
request.responseType = 'json';
|
||||||
|
request.open('POST', `/channel/${this.channelUid}/attachment.bin`, true);
|
||||||
|
|
||||||
request.upload.onprogress = function (event) {
|
request.upload.onprogress = function (event) {
|
||||||
if (event.lengthComputable) {
|
if (event.lengthComputable) {
|
||||||
@ -35,9 +35,10 @@ class UploadButtonElement extends HTMLElement {
|
|||||||
uploadButton.innerText = `${Math.round(percentComplete)}%`;
|
uploadButton.innerText = `${Math.round(percentComplete)}%`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const me = this
|
||||||
request.onload = function () {
|
request.onload = function () {
|
||||||
if (request.status === 200) {
|
if (request.status === 200) {
|
||||||
|
me.dispatchEvent(new CustomEvent('uploaded', { detail: request.response }));
|
||||||
uploadButton.innerHTML = '📤';
|
uploadButton.innerHTML = '📤';
|
||||||
} else {
|
} else {
|
||||||
alert('Upload failed');
|
alert('Upload failed');
|
||||||
|
@ -47,6 +47,11 @@
|
|||||||
document.querySelector("upload-button").addEventListener("upload",function(e){
|
document.querySelector("upload-button").addEventListener("upload",function(e){
|
||||||
getInputField().focus();
|
getInputField().focus();
|
||||||
})
|
})
|
||||||
|
document.querySelector("upload-button").addEventListener("uploaded",function(e){
|
||||||
|
e.detail.files.forEach((file)=>{
|
||||||
|
app.rpc.sendMessage(channelUid,``)
|
||||||
|
})
|
||||||
|
})
|
||||||
textBox.addEventListener("paste", async (e) => {
|
textBox.addEventListener("paste", async (e) => {
|
||||||
try {
|
try {
|
||||||
const clipboardItems = await navigator.clipboard.read();
|
const clipboardItems = await navigator.clipboard.read();
|
||||||
|
53
src/snek/view/channel.py
Normal file
53
src/snek/view/channel.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
from snek.system.view import BaseView
|
||||||
|
import aiofiles
|
||||||
|
from aiohttp import web
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
class ChannelAttachmentView(BaseView):
|
||||||
|
|
||||||
|
async def get(self):
|
||||||
|
relative_path = self.request.match_info.get("relative_url")
|
||||||
|
channel_attachment = await self.services.channel_attachment.get(relative_url=relative_path)
|
||||||
|
response = web.FileResponse(channel_attachment["path"])
|
||||||
|
response.headers["Cache-Control"] = f"public, max-age={1337*420}"
|
||||||
|
response.headers["Content-Disposition"] = (
|
||||||
|
f'attachment; filename="{channel_attachment["name"]}"'
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
async def post(self):
|
||||||
|
|
||||||
|
channel_uid = self.request.match_info.get("channel_uid")
|
||||||
|
user_uid = self.request.session.get("uid")
|
||||||
|
|
||||||
|
channel_member = await self.services.channel_member.get(user_uid=user_uid, channel_uid=channel_uid,deleted_at=None,is_banned=False)
|
||||||
|
|
||||||
|
if not channel_member:
|
||||||
|
return web.HTTPNotFound()
|
||||||
|
|
||||||
|
reader = await self.request.multipart()
|
||||||
|
attachments = []
|
||||||
|
|
||||||
|
while field := await reader.next():
|
||||||
|
|
||||||
|
filename = field.filename
|
||||||
|
if not filename:
|
||||||
|
continue
|
||||||
|
|
||||||
|
attachment = await self.services.channel_attachment.create_file(
|
||||||
|
channel_uid=channel_uid, name=filename,user_uid=user_uid
|
||||||
|
)
|
||||||
|
|
||||||
|
attachments.append(attachment)
|
||||||
|
pathlib.Path(attachment['path']).parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
async with aiofiles.open(attachment['path'], "wb") as f:
|
||||||
|
while chunk := await field.read_chunk():
|
||||||
|
await f.write(chunk)
|
||||||
|
|
||||||
|
return web.json_response(
|
||||||
|
{
|
||||||
|
"message": "Files uploaded successfully",
|
||||||
|
"files": [attachment.record for attachment in attachments],
|
||||||
|
"channel_uid": channel_uid,
|
||||||
|
}
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user