mirror of
https://github.com/wgh136/nysoure.git
synced 2025-09-27 04:17:23 +00:00
Compare commits
6 Commits
4e709dd952
...
b8acd97c11
Author | SHA1 | Date | |
---|---|---|---|
b8acd97c11 | |||
8f240823ef | |||
a33171fb20 | |||
993e7f488d | |||
ebfe25e6d8 | |||
f0079003f2 |
@@ -4,14 +4,16 @@ export default function Badge({
|
||||
children,
|
||||
className,
|
||||
onClick,
|
||||
selectable = false,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
selectable?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<span
|
||||
className={`badge ${!className?.includes("badge-") && "badge-primary"} select-none ${className}`}
|
||||
className={`badge ${!className?.includes("badge-") && "badge-primary"} ${className} ${!selectable && "select-none"}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
|
@@ -203,7 +203,11 @@ export function QuickAddTagDialog({
|
||||
return;
|
||||
}
|
||||
setError(null);
|
||||
const names = text.split(separator).filter((n) => n.length > 0);
|
||||
let sep: string | RegExp = separator;
|
||||
if (sep === " ") {
|
||||
sep = /\s+/;
|
||||
}
|
||||
const names = text.split(sep).filter((n) => n.length > 0);
|
||||
setLoading(true);
|
||||
const res = await network.getOrCreateTags(names, type);
|
||||
setLoading(false);
|
||||
|
@@ -1,6 +1,10 @@
|
||||
@import "tailwindcss";
|
||||
@plugin "daisyui";
|
||||
|
||||
@theme {
|
||||
--breakpoint-xs: 30rem;
|
||||
}
|
||||
|
||||
/* Pink Theme */
|
||||
@plugin "daisyui/theme" {
|
||||
name: "pink";
|
||||
|
@@ -104,6 +104,7 @@ export interface RFile {
|
||||
is_redirect: boolean;
|
||||
user: User;
|
||||
resource?: Resource;
|
||||
hash?: string;
|
||||
}
|
||||
|
||||
export interface UploadingFile {
|
||||
|
@@ -37,6 +37,7 @@ import {
|
||||
MdOutlineFolderSpecial,
|
||||
MdOutlineLink,
|
||||
MdOutlineOpenInNew,
|
||||
MdOutlineVerifiedUser,
|
||||
} from "react-icons/md";
|
||||
import { app } from "../app.ts";
|
||||
import { uploadingManager } from "../network/uploading.ts";
|
||||
@@ -693,8 +694,8 @@ function FileTile({ file }: { file: RFile }) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className={"card shadow bg-base-100 mb-4"}>
|
||||
<div className={"p-4 flex flex-row items-center"}>
|
||||
<div className={"card shadow bg-base-100 mb-4 p-4"}>
|
||||
<div className={"flex flex-row items-center"}>
|
||||
<div className={"grow"}>
|
||||
<h4 className={"font-bold break-all"}>{file.filename}</h4>
|
||||
<div className={"text-sm my-1 comment_tile"}>
|
||||
@@ -725,11 +726,17 @@ function FileTile({ file }: { file: RFile }) {
|
||||
<MdOutlineArchive size={16} className={"inline-block"} />
|
||||
{file.is_redirect ? t("Redirect") : fileSizeToString(file.size)}
|
||||
</Badge>
|
||||
{
|
||||
file.hash && <Badge className={"badge-soft badge-accent text-xs mr-2 break-all hidden sm:inline-flex"} selectable={true}>
|
||||
<MdOutlineVerifiedUser size={16} className={"inline-block"} />
|
||||
Md5: {file.hash}
|
||||
</Badge>
|
||||
}
|
||||
<DeleteFileDialog fileId={file.id} uploaderId={file.user.id} />
|
||||
<UpdateFileInfoDialog file={file} />
|
||||
</p>
|
||||
</div>
|
||||
<div className={"flex flex-row items-center"}>
|
||||
<div className={`flex-row items-center hidden xs:flex`}>
|
||||
{file.size > 10 * 1024 * 1024 ? (
|
||||
<button
|
||||
ref={buttonRef}
|
||||
@@ -759,6 +766,11 @@ function FileTile({ file }: { file: RFile }) {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row-reverse xs:hidden p-2">
|
||||
<button className={"btn btn-primary btn-soft btn-sm"}>
|
||||
<MdOutlineDownload size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -1,12 +1,17 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"net/url"
|
||||
"nysoure/server/model"
|
||||
"nysoure/server/service"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
const (
|
||||
maxTagNameLength = 20
|
||||
)
|
||||
|
||||
func handleCreateTag(c fiber.Ctx) error {
|
||||
@@ -15,6 +20,9 @@ func handleCreateTag(c fiber.Ctx) error {
|
||||
return model.NewRequestError("name is required")
|
||||
}
|
||||
tag = strings.TrimSpace(tag)
|
||||
if len([]rune(tag)) > maxTagNameLength {
|
||||
return model.NewRequestError("Tag name too long")
|
||||
}
|
||||
uid, ok := c.Locals("uid").(uint)
|
||||
if !ok {
|
||||
return model.NewUnAuthorizedError("You must be logged in to create a tag")
|
||||
@@ -159,6 +167,9 @@ func getOrCreateTags(c fiber.Ctx) error {
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
if len([]rune(name)) > maxTagNameLength {
|
||||
return model.NewRequestError("Tag name too long: " + name)
|
||||
}
|
||||
names = append(names, name)
|
||||
}
|
||||
|
||||
|
@@ -73,7 +73,7 @@ func GetUploadingFilesOlderThan(time time.Time) ([]model.UploadingFile, error) {
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func CreateFile(filename string, description string, resourceID uint, storageID *uint, storageKey string, redirectUrl string, size int64, userID uint) (*model.File, error) {
|
||||
func CreateFile(filename string, description string, resourceID uint, storageID *uint, storageKey string, redirectUrl string, size int64, userID uint, hash string) (*model.File, error) {
|
||||
if storageID == nil && redirectUrl == "" {
|
||||
return nil, errors.New("storageID and redirectUrl cannot be both empty")
|
||||
}
|
||||
@@ -88,6 +88,7 @@ func CreateFile(filename string, description string, resourceID uint, storageID
|
||||
StorageKey: storageKey,
|
||||
Size: size,
|
||||
UserID: userID,
|
||||
Hash: hash,
|
||||
}
|
||||
|
||||
err := db.Transaction(func(tx *gorm.DB) error {
|
||||
@@ -200,13 +201,14 @@ func SetFileStorageKey(id string, storageKey string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetFileStorageKeyAndSize(id string, storageKey string, size int64) error {
|
||||
func SetFileStorageKeyAndSize(id string, storageKey string, size int64, hash string) error {
|
||||
f := &model.File{}
|
||||
if err := db.Where("uuid = ?", id).First(f).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
f.StorageKey = storageKey
|
||||
f.Size = size
|
||||
f.Hash = hash
|
||||
if err := db.Save(f).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return model.NewNotFoundError("file not found")
|
||||
|
@@ -36,7 +36,9 @@ func GetResourceByID(id uint) (model.Resource, error) {
|
||||
var r model.Resource
|
||||
if err := db.Preload("User").
|
||||
Preload("Images").
|
||||
Preload("Tags").
|
||||
Preload("Tags", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Select("id", "name", "type", "alias_of")
|
||||
}).
|
||||
Preload("Files").
|
||||
Preload("Files.User").
|
||||
First(&r, id).Error; err != nil {
|
||||
|
@@ -18,6 +18,7 @@ type File struct {
|
||||
UserID uint
|
||||
User User `gorm:"foreignKey:UserID"`
|
||||
Size int64
|
||||
Hash string `gorm:"default:null"`
|
||||
}
|
||||
|
||||
type FileView struct {
|
||||
@@ -28,6 +29,7 @@ type FileView struct {
|
||||
IsRedirect bool `json:"is_redirect"`
|
||||
User UserView `json:"user"`
|
||||
Resource *ResourceView `json:"resource,omitempty"`
|
||||
Hash string `json:"hash,omitempty"`
|
||||
}
|
||||
|
||||
func (f *File) ToView() *FileView {
|
||||
@@ -38,6 +40,7 @@ func (f *File) ToView() *FileView {
|
||||
Size: f.Size,
|
||||
IsRedirect: f.RedirectUrl != "",
|
||||
User: f.User.ToView(),
|
||||
Hash: f.Hash,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,5 +59,6 @@ func (f *File) ToViewWithResource() *FileView {
|
||||
IsRedirect: f.RedirectUrl != "",
|
||||
User: f.User.ToView(),
|
||||
Resource: resource,
|
||||
Hash: f.Hash,
|
||||
}
|
||||
}
|
||||
|
@@ -236,7 +236,7 @@ func FinishUploadingFile(uid uint, fid uint, md5Str string) (*model.FileView, er
|
||||
return nil, model.NewInternalServerError("failed to finish uploading file. please re-upload")
|
||||
}
|
||||
|
||||
dbFile, err := dao.CreateFile(uploadingFile.Filename, uploadingFile.Description, uploadingFile.TargetResourceID, &uploadingFile.TargetStorageID, storageKeyUnavailable, "", uploadingFile.TotalSize, uid)
|
||||
dbFile, err := dao.CreateFile(uploadingFile.Filename, uploadingFile.Description, uploadingFile.TargetResourceID, &uploadingFile.TargetStorageID, storageKeyUnavailable, "", uploadingFile.TotalSize, uid, sumStr)
|
||||
if err != nil {
|
||||
log.Error("failed to create file in db: ", err)
|
||||
_ = os.Remove(resultFilePath)
|
||||
@@ -310,7 +310,7 @@ func CreateRedirectFile(uid uint, filename string, description string, resourceI
|
||||
return nil, model.NewUnAuthorizedError("user cannot upload file")
|
||||
}
|
||||
|
||||
file, err := dao.CreateFile(filename, description, resourceID, nil, "", redirectUrl, 0, uid)
|
||||
file, err := dao.CreateFile(filename, description, resourceID, nil, "", redirectUrl, 0, uid, "")
|
||||
if err != nil {
|
||||
log.Error("failed to create file in db: ", err)
|
||||
return nil, model.NewInternalServerError("failed to create file in db")
|
||||
@@ -496,57 +496,61 @@ func testFileUrl(url string) (int64, error) {
|
||||
}
|
||||
|
||||
// downloadFile return nil if the download is successful or the context is cancelled
|
||||
func downloadFile(ctx context.Context, url string, path string) error {
|
||||
func downloadFile(ctx context.Context, url string, path string) (string, error) {
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
_ = os.Remove(path) // Remove the file if it already exists
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return model.NewRequestError("failed to create HTTP request")
|
||||
return "", model.NewRequestError("failed to create HTTP request")
|
||||
}
|
||||
client := http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
// Check if the error is due to context cancellation
|
||||
if ctx.Err() != nil {
|
||||
return nil
|
||||
return "", nil
|
||||
}
|
||||
return model.NewRequestError("failed to send HTTP request")
|
||||
return "", model.NewRequestError("failed to send HTTP request")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return model.NewRequestError("URL is not accessible, status code: " + resp.Status)
|
||||
return "", model.NewRequestError("URL is not accessible, status code: " + resp.Status)
|
||||
}
|
||||
file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
return model.NewInternalServerError("failed to open file for writing")
|
||||
return "", model.NewInternalServerError("failed to open file for writing")
|
||||
}
|
||||
defer file.Close()
|
||||
writer := bufio.NewWriter(file)
|
||||
|
||||
h := md5.New()
|
||||
|
||||
buf := make([]byte, 64*1024)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
return "", nil
|
||||
default:
|
||||
n, readErr := resp.Body.Read(buf)
|
||||
if n > 0 {
|
||||
if _, writeErr := writer.Write(buf[:n]); writeErr != nil {
|
||||
return model.NewInternalServerError("failed to write to file")
|
||||
return "", model.NewInternalServerError("failed to write to file")
|
||||
}
|
||||
h.Write(buf[:n])
|
||||
}
|
||||
if readErr != nil {
|
||||
if readErr == io.EOF {
|
||||
if err := writer.Flush(); err != nil {
|
||||
return model.NewInternalServerError("failed to flush writer")
|
||||
return "", model.NewInternalServerError("failed to flush writer")
|
||||
}
|
||||
return nil // Download completed successfully
|
||||
md5Sum := hex.EncodeToString(h.Sum(nil))
|
||||
return md5Sum, nil // Download completed successfully
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
return nil // Context cancelled, return nil
|
||||
return "", nil // Context cancelled, return nil
|
||||
}
|
||||
return model.NewInternalServerError("failed to read response body")
|
||||
return "", model.NewInternalServerError("failed to read response body")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -573,7 +577,7 @@ func CreateServerDownloadTask(uid uint, url, filename, description string, resou
|
||||
return nil, model.NewRequestError("server is busy, please try again later")
|
||||
}
|
||||
|
||||
file, err := dao.CreateFile(filename, description, resourceID, &storageID, storageKeyUnavailable, "", 0, uid)
|
||||
file, err := dao.CreateFile(filename, description, resourceID, &storageID, storageKeyUnavailable, "", 0, uid, "")
|
||||
if err != nil {
|
||||
log.Error("failed to create file in db: ", err)
|
||||
return nil, model.NewInternalServerError("failed to create file in db")
|
||||
@@ -624,11 +628,14 @@ func CreateServerDownloadTask(uid uint, url, filename, description string, resou
|
||||
}
|
||||
}()
|
||||
|
||||
hash := ""
|
||||
|
||||
for i := range 3 {
|
||||
if done.Load() {
|
||||
return
|
||||
}
|
||||
if err := downloadFile(ctx, url, tempPath); err != nil {
|
||||
hash, err = downloadFile(ctx, url, tempPath)
|
||||
if err != nil {
|
||||
log.Error("failed to download file: ", err)
|
||||
if i == 2 {
|
||||
_ = dao.DeleteFile(file.UUID)
|
||||
@@ -689,7 +696,7 @@ func CreateServerDownloadTask(uid uint, url, filename, description string, resou
|
||||
_ = os.Remove(tempPath)
|
||||
return
|
||||
}
|
||||
if err := dao.SetFileStorageKeyAndSize(file.UUID, storageKey, size); err != nil {
|
||||
if err := dao.SetFileStorageKeyAndSize(file.UUID, storageKey, size, hash); err != nil {
|
||||
log.Error("failed to set file storage key: ", err)
|
||||
_ = dao.DeleteFile(file.UUID)
|
||||
_ = iStorage.Delete(storageKey)
|
||||
|
Reference in New Issue
Block a user