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.user import UserView
 | 
			
		||||
from snek.view.web import WebView
 | 
			
		||||
from snek.view.channel import ChannelAttachmentView
 | 
			
		||||
from snek.webdav import WebdavApplication
 | 
			
		||||
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-photo", self.handle_http_photo)
 | 
			
		||||
        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("/threads.html", ThreadsView)
 | 
			
		||||
        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_property import UserPropertyMapper
 | 
			
		||||
from snek.mapper.repository import RepositoryMapper
 | 
			
		||||
from snek.mapper.channel_attachment import ChannelAttachmentMapper
 | 
			
		||||
from snek.system.object import Object
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -25,6 +26,7 @@ def get_mappers(app=None):
 | 
			
		||||
            "drive": DriveMapper(app=app),
 | 
			
		||||
            "user_property": UserPropertyMapper(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_property import UserPropertyModel
 | 
			
		||||
from snek.model.repository import RepositoryModel
 | 
			
		||||
from snek.model.channel_attachment import ChannelAttachmentModel
 | 
			
		||||
from snek.system.object import Object
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -27,6 +28,7 @@ def get_models():
 | 
			
		||||
            "notification": NotificationModel,
 | 
			
		||||
            "user_property": UserPropertyModel,
 | 
			
		||||
            "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.util import UtilService
 | 
			
		||||
from snek.service.repository import RepositoryService
 | 
			
		||||
from snek.service.channel_attachment import ChannelAttachmentService
 | 
			
		||||
from snek.system.object import Object
 | 
			
		||||
from snek.service.db import DBService
 | 
			
		||||
 | 
			
		||||
@ -32,6 +33,7 @@ def get_services(app):
 | 
			
		||||
            "user_property": UserPropertyService(app=app),
 | 
			
		||||
            "repository": RepositoryService(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.service import BaseService
 | 
			
		||||
 | 
			
		||||
import pathlib 
 | 
			
		||||
 | 
			
		||||
class ChannelService(BaseService):
 | 
			
		||||
    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):
 | 
			
		||||
        if 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 formData = new FormData();
 | 
			
		||||
        formData.append('channel_uid', this.channelUid);
 | 
			
		||||
        for (let i = 0; i < files.length; i++) {
 | 
			
		||||
            formData.append('files[]', files[i]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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) {
 | 
			
		||||
            if (event.lengthComputable) {
 | 
			
		||||
@ -35,9 +35,10 @@ class UploadButtonElement extends HTMLElement {
 | 
			
		||||
                uploadButton.innerText = `${Math.round(percentComplete)}%`;
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const me = this
 | 
			
		||||
        request.onload = function () {
 | 
			
		||||
            if (request.status === 200) {
 | 
			
		||||
                me.dispatchEvent(new CustomEvent('uploaded', { detail: request.response }));
 | 
			
		||||
                uploadButton.innerHTML = '📤';
 | 
			
		||||
            } else {
 | 
			
		||||
                alert('Upload failed');
 | 
			
		||||
 | 
			
		||||
@ -47,6 +47,11 @@
 | 
			
		||||
        document.querySelector("upload-button").addEventListener("upload",function(e){
 | 
			
		||||
            getInputField().focus();
 | 
			
		||||
        })
 | 
			
		||||
        document.querySelector("upload-button").addEventListener("uploaded",function(e){
 | 
			
		||||
            e.detail.files.forEach((file)=>{
 | 
			
		||||
                app.rpc.sendMessage(channelUid,``)
 | 
			
		||||
            })
 | 
			
		||||
        })
 | 
			
		||||
        textBox.addEventListener("paste", async (e) => {
 | 
			
		||||
            try {
 | 
			
		||||
                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