Instant upload

This commit is contained in:
LittleSheep 2024-03-24 19:01:18 +08:00
parent 93d959e7a6
commit fb0c7860e0
6 changed files with 110 additions and 37 deletions

View File

@ -23,6 +23,7 @@ type Attachment struct {
Filesize int64 `json:"filesize"`
Filename string `json:"filename"`
Mimetype string `json:"mimetype"`
Hashcode string `json:"hashcode"`
Type AttachmentType `json:"type"`
ExternalUrl string `json:"external_url"`
Author Account `json:"author"`

View File

@ -18,12 +18,16 @@ func readAttachment(c *fiber.Ctx) error {
func uploadAttachment(c *fiber.Ctx) error {
user := c.Locals("principal").(models.Account)
hashcode := c.FormValue("hashcode")
if len(hashcode) != 64 {
return fiber.NewError(fiber.StatusBadRequest, "please provide a SHA256 hashcode, length should be 64 characters")
}
file, err := c.FormFile("attachment")
if err != nil {
return err
}
attachment, err := services.NewAttachment(user, file)
attachment, err := services.NewAttachment(user, file, hashcode)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
@ -39,10 +43,10 @@ func uploadAttachment(c *fiber.Ctx) error {
}
func deleteAttachment(c *fiber.Ctx) error {
id := c.Params("fileId")
id, _ := c.ParamsInt("id", 0)
user := c.Locals("principal").(models.Account)
attachment, err := services.GetAttachmentByUUID(id)
attachment, err := services.GetAttachmentByID(uint(id))
if err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
} else if attachment.AuthorID != user.ID {

View File

@ -68,7 +68,7 @@ func NewServer() {
CacheControl: true,
}), readAttachment)
api.Post("/attachments", authMiddleware, uploadAttachment)
api.Delete("/attachments/:fileId", authMiddleware, deleteAttachment)
api.Delete("/attachments/:id", authMiddleware, deleteAttachment)
api.Get("/feed", listFeed)

View File

@ -13,6 +13,16 @@ import (
"github.com/spf13/viper"
)
func GetAttachmentByID(id uint) (models.Attachment, error) {
var attachment models.Attachment
if err := database.C.Where(models.Attachment{
BaseModel: models.BaseModel{ID: id},
}).First(&attachment).Error; err != nil {
return attachment, err
}
return attachment, nil
}
func GetAttachmentByUUID(fileId string) (models.Attachment, error) {
var attachment models.Attachment
if err := database.C.Where(models.Attachment{
@ -23,40 +33,67 @@ func GetAttachmentByUUID(fileId string) (models.Attachment, error) {
return attachment, nil
}
func NewAttachment(user models.Account, header *multipart.FileHeader) (models.Attachment, error) {
attachment := models.Attachment{
FileID: uuid.NewString(),
Filesize: header.Size,
Filename: header.Filename,
Mimetype: "unknown/unknown",
Type: models.AttachmentOthers,
AuthorID: user.ID,
}
// Open file
file, err := header.Open()
if err != nil {
func GetAttachmentByHashcode(hashcode string) (models.Attachment, error) {
var attachment models.Attachment
if err := database.C.Where(models.Attachment{
Hashcode: hashcode,
}).First(&attachment).Error; err != nil {
return attachment, err
}
defer file.Close()
return attachment, nil
}
// Detect mimetype
fileHeader := make([]byte, 512)
_, err = file.Read(fileHeader)
func NewAttachment(user models.Account, header *multipart.FileHeader, hashcode string) (models.Attachment, error) {
var attachment models.Attachment
existsAttachment, err := GetAttachmentByHashcode(hashcode)
if err != nil {
return attachment, err
}
attachment.Mimetype = http.DetectContentType(fileHeader)
// Upload the new file
attachment = models.Attachment{
FileID: uuid.NewString(),
Filesize: header.Size,
Filename: header.Filename,
Hashcode: hashcode,
Mimetype: "unknown/unknown",
Type: models.AttachmentOthers,
AuthorID: user.ID,
}
switch strings.Split(attachment.Mimetype, "/")[0] {
case "image":
attachment.Type = models.AttachmentPhoto
case "video":
attachment.Type = models.AttachmentVideo
case "audio":
attachment.Type = models.AttachmentAudio
default:
attachment.Type = models.AttachmentOthers
// Open file
file, err := header.Open()
if err != nil {
return attachment, err
}
defer file.Close()
// Detect mimetype
fileHeader := make([]byte, 512)
_, err = file.Read(fileHeader)
if err != nil {
return attachment, err
}
attachment.Mimetype = http.DetectContentType(fileHeader)
switch strings.Split(attachment.Mimetype, "/")[0] {
case "image":
attachment.Type = models.AttachmentPhoto
case "video":
attachment.Type = models.AttachmentVideo
case "audio":
attachment.Type = models.AttachmentAudio
default:
attachment.Type = models.AttachmentOthers
}
} else {
// Instant upload, build link with the exists file
attachment = models.Attachment{
FileID: existsAttachment.FileID,
Filesize: header.Size,
Filename: header.Filename,
Hashcode: hashcode,
Mimetype: existsAttachment.Mimetype,
Type: existsAttachment.Type,
AuthorID: user.ID,
}
}
// Save into database
@ -66,9 +103,20 @@ func NewAttachment(user models.Account, header *multipart.FileHeader) (models.At
}
func DeleteAttachment(item models.Attachment) error {
var dupeCount int64
if err := database.C.
Where(&models.Attachment{Hashcode: item.Hashcode}).
Model(&models.Attachment{}).
Count(&dupeCount).Error; err != nil {
dupeCount = -1
}
if err := database.C.Delete(&item).Error; err != nil {
return err
} else {
}
if dupeCount != -1 && dupeCount <= 1 {
// Safe for deletion the physics file
basepath := viper.GetString("content")
fullpath := filepath.Join(basepath, item.FileID)

View File

@ -171,6 +171,7 @@ function pasteMedia(evt: ClipboardEvent) {
watch(editor.related, (val) => {
if (val.edit_to && val.edit_to.model_type === "moment") {
data.value = val.edit_to
data.value.attachments = val.edit_to.attachments ?? []
}
})

View File

@ -60,6 +60,8 @@ async function upload(file?: any) {
data.set("attachment", file)
}
data.set("hashcode", await calculateHashCode(picked.value[0]))
emits("update:uploading", true)
const res = await request("/api/attachments", {
method: "POST",
@ -83,16 +85,33 @@ async function dispose(idx: number) {
const item = media.splice(idx)[0]
emits("update:value", media)
const res = await request(`/api/attachments/${item.file_id}`, {
const res = await request(`/api/attachments/${item.id}`, {
method: "DELETE",
headers: { Authorization: `Bearer ${getAtk()}` },
headers: { Authorization: `Bearer ${getAtk()}` }
})
if (res.status !== 200) {
error.value = await res.text()
}
}
defineExpose({ upload })
defineExpose({ upload, dispose })
async function calculateHashCode(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = async () => {
const buffer = reader.result as ArrayBuffer
const hashBuffer = await crypto.subtle.digest("SHA-256", buffer)
const hashArray = Array.from(new Uint8Array(hashBuffer))
const hashHex = hashArray.map((byte) => byte.toString(16).padStart(2, "0")).join("")
resolve(hashHex)
}
reader.onerror = () => {
reject(reader.error)
}
reader.readAsArrayBuffer(file)
})
}
function getFileName(item: any) {
return item.filename.replace(/\.[^/.]+$/, "")