✨ Instant upload
This commit is contained in:
		@@ -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"`
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 ?? []
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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(/\.[^/.]+$/, "")
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user