},
uploadImageHook(file, callback) {
- // image.png is the default name given to images copied from the clipboard. To avoid conflicts we'll append a timestamp.
- if (file.name == "image.png") {
- const currentDateString = new Date().toISOString().replace(/:/g, "-");
- file = new File([file], `image-${currentDateString}.png`, {
- type: file.type,
- });
- }
-
- // If the user has entered an alt text, use it. Otherwise, use the filename.
const altTextInputValue = document.getElementById(
"toastuiAltTextInput"
)?.value;
- const altText = altTextInputValue ? altTextInputValue : file.name;
// Upload the image then use the callback to insert the URL into the editor
- this.postAttachment(file).then(function (success) {
- if (success === true) {
- callback(`/attachments/${encodeURIComponent(file.name)}`, altText);
+ this.postAttachment(file).then(function (data) {
+ if (data) {
+ // If the user has entered an alt text, use it. Otherwise, use the filename returned by the API.
+ const altText = altTextInputValue ? altTextInputValue : data.filename;
+ callback(data.url, altText);
}
});
},
postAttachment(file) {
- let parent = this;
-
if (reservedFilenameCharacters.test(file.name)) {
this.badFilenameToast("filename");
- return false;
+ return;
}
EventBus.$emit("showToast", "success", "Uploading attachment...");
"Content-Type": "multipart/form-data",
},
})
- .then(function () {
+ .then(function (response) {
EventBus.$emit("showToast", "success", "Attachment uploaded ✓");
- return true;
+ return response.data;
})
.catch(function (error) {
if (error.response?.status == 409) {
"Failed to upload attachment ✘"
);
}
- return false;
+ return;
});
},
from fastapi import UploadFile
from fastapi.responses import FileResponse
+from .models import AttachmentCreateResponse
+
class BaseAttachments(ABC):
@abstractmethod
- def create(self, file: UploadFile) -> None:
+ def create(self, file: UploadFile) -> AttachmentCreateResponse:
"""Create a new attachment."""
pass
import os
import shutil
+import urllib.parse
+from datetime import datetime
from fastapi import UploadFile
from fastapi.responses import FileResponse
from helpers import get_env, is_valid_filename
from ..base import BaseAttachments
+from ..models import AttachmentCreateResponse
class FileSystemAttachments(BaseAttachments):
self.storage_path = os.path.join(self.base_path, "attachments")
os.makedirs(self.storage_path, exist_ok=True)
- def create(self, file: UploadFile) -> None:
+ def create(self, file: UploadFile) -> AttachmentCreateResponse:
"""Create a new attachment."""
is_valid_filename(file.filename)
- filepath = os.path.join(self.storage_path, file.filename)
- if os.path.exists(filepath):
- raise FileExistsError(f"'{file.filename}' already exists.")
- with open(filepath, "wb") as f:
- shutil.copyfileobj(file.file, f)
+ try:
+ self._save_file(file)
+ except FileExistsError:
+ file.filename = self._datetime_suffix_filename(file.filename)
+ self._save_file(file)
+ return AttachmentCreateResponse(
+ filename=file.filename, url=self._url_for_filename(file.filename)
+ )
def get(self, filename: str) -> FileResponse:
"""Get a specific attachment."""
if not os.path.isfile(filepath):
raise FileNotFoundError(f"'{filename}' not found.")
return FileResponse(filepath)
+
+ def _save_file(self, file: UploadFile):
+ filepath = os.path.join(self.storage_path, file.filename)
+ with open(filepath, "xb") as f:
+ shutil.copyfileobj(file.file, f)
+
+ def _datetime_suffix_filename(self, filename: str) -> str:
+ """Add a timestamp suffix to the filename."""
+ timestamp = datetime.utcnow().strftime("%Y-%m-%dT%H-%M-%SZ")
+ name, ext = os.path.splitext(filename)
+ return f"{name}_{timestamp}{ext}"
+
+ def _url_for_filename(self, filename: str) -> str:
+ """Return the URL for the given filename."""
+ return f"/attachments/{urllib.parse.quote(filename)}"
--- /dev/null
+from helpers import CustomBaseModel
+
+
+class AttachmentCreateResponse(CustomBaseModel):
+ filename: str
+ url: str
import api_messages
from attachments.base import BaseAttachments
+from attachments.models import AttachmentCreateResponse
from auth.base import BaseAuth
from auth.models import Login, Token
from global_config import AuthType, GlobalConfig, GlobalConfigResponseModel
@app.post(
"/api/attachments",
dependencies=auth_deps,
- response_model=None,
+ response_model=AttachmentCreateResponse,
)
def post_attachment(file: UploadFile):
"""Upload an attachment."""