From e9f6e1968e94435810a9b936d942a9ed0d0fc384 Mon Sep 17 00:00:00 2001 From: nyne Date: Sun, 13 Jul 2025 16:12:03 +0800 Subject: [PATCH] Add new file activity. --- frontend/src/i18n.ts | 3 ++ frontend/src/network/models.ts | 2 + frontend/src/pages/activities_page.tsx | 44 +++++++++++++++++++ .../src/pages/manage_server_config_page.tsx | 5 ++- frontend/src/pages/manage_storage_page.tsx | 22 +++++----- frontend/src/pages/resource_details_page.tsx | 22 +++++----- server/dao/activity.go | 9 ++++ server/dao/file.go | 30 ++++++++++++- server/dao/resource.go | 8 +++- server/model/activity.go | 2 + server/service/activity.go | 15 +++++++ server/service/resource.go | 7 +++ 12 files changed, 145 insertions(+), 24 deletions(-) diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index a3badcf..781e9e7 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -234,6 +234,7 @@ export const i18nData = { "Comment Details": "Comment Details", "Posted a comment": "Posted a comment", "Resources": "Resources", + "Added a new file": "Added a new file", }, }, "zh-CN": { @@ -461,6 +462,7 @@ export const i18nData = { "Posted a comment": "发布了一个评论", "Resources": "资源", + "Added a new file": "添加了新文件", }, }, "zh-TW": { @@ -688,6 +690,7 @@ export const i18nData = { "Posted a comment": "發布了評論", "Resources": "資源", + "Added a new file": "添加了新檔案", }, }, }; diff --git a/frontend/src/network/models.ts b/frontend/src/network/models.ts index 35361c6..06c0cfb 100644 --- a/frontend/src/network/models.ts +++ b/frontend/src/network/models.ts @@ -177,6 +177,7 @@ export enum ActivityType { ResourcePublished = 1, ResourceUpdated = 2, NewComment = 3, + NewFile = 4, } export interface Activity { @@ -187,4 +188,5 @@ export interface Activity { resource?: Resource; user?: User; comment?: Comment; + file?: RFile; } diff --git a/frontend/src/pages/activities_page.tsx b/frontend/src/pages/activities_page.tsx index f8732b3..c8d2645 100644 --- a/frontend/src/pages/activities_page.tsx +++ b/frontend/src/pages/activities_page.tsx @@ -6,6 +6,8 @@ import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router"; import Loading from "../components/loading.tsx"; import { CommentContent } from "../components/comment_tile.tsx"; +import {MdOutlineArchive, MdOutlinePhotoAlbum} from "react-icons/md"; +import Badge from "../components/badge.tsx"; export default function ActivitiesPage() { const [activities, setActivities] = useState([]); @@ -59,6 +61,18 @@ export default function ActivitiesPage() { ); } +function fileSizeToString(size: number) { + if (size < 1024) { + return size + "B"; + } else if (size < 1024 * 1024) { + return (size / 1024).toFixed(2) + "KB"; + } else if (size < 1024 * 1024 * 1024) { + return (size / 1024 / 1024).toFixed(2) + "MB"; + } else { + return (size / 1024 / 1024 / 1024).toFixed(2) + "GB"; + } +} + function ActivityCard({ activity }: { activity: Activity }) { const { t } = useTranslation(); @@ -67,6 +81,7 @@ function ActivityCard({ activity }: { activity: Activity }) { t("Published a resource"), t("Updated a resource"), t("Posted a comment"), + t("Added a new file"), ]; const navigate = useNavigate(); @@ -97,6 +112,33 @@ function ActivityCard({ activity }: { activity: Activity }) { ); + } else if (activity.type === ActivityType.NewFile) { + content = ( +
+

{activity.file!.filename}

+

+ {activity.file!.description} +

+

+ + + {activity.file!.is_redirect + ? t("Redirect") + : fileSizeToString(activity.file!.size)} + + + + {(() => { + let title = activity.resource!.title; + if (title.length > 20) { + title = title.slice(0, 20) + "..."; + } + return title; + })()} + +

+
+ ); } return ( @@ -112,6 +154,8 @@ function ActivityCard({ activity }: { activity: Activity }) { navigate(`/resources/${activity.resource?.id}`); } else if (activity.type === ActivityType.NewComment) { navigate(`/comments/${activity.comment?.id}`); + } else if (activity.type === ActivityType.NewFile) { + navigate(`/resources/${activity.resource?.id}#files`); } }} > diff --git a/frontend/src/pages/manage_server_config_page.tsx b/frontend/src/pages/manage_server_config_page.tsx index 5db90bb..8644247 100644 --- a/frontend/src/pages/manage_server_config_page.tsx +++ b/frontend/src/pages/manage_server_config_page.tsx @@ -171,7 +171,10 @@ export default function ManageServerConfigPage() { checked={config.allow_normal_user_upload} className="toggle-primary toggle" onChange={(e) => { - setConfig({ ...config, allow_normal_user_upload: e.target.checked }); + setConfig({ + ...config, + allow_normal_user_upload: e.target.checked, + }); }} /> diff --git a/frontend/src/pages/manage_storage_page.tsx b/frontend/src/pages/manage_storage_page.tsx index c076089..4d5b24e 100644 --- a/frontend/src/pages/manage_storage_page.tsx +++ b/frontend/src/pages/manage_storage_page.tsx @@ -145,7 +145,9 @@ export default function StorageView() { {s.name} - {s.isDefault && {t("Default")}} + {s.isDefault && ( + {t("Default")} + )} {new Date(s.createdAt).toLocaleString()} @@ -173,15 +175,15 @@ export default function StorageView() { > {t("Delete")} - {!s.isDefault && { - handleSetDefault(s.id); - }} - > - - t("Set as Default") - - } + {!s.isDefault && ( + { + handleSetDefault(s.id); + }} + > + t("Set as Default") + + )} , document.getElementById( `set_default_button_${s.id}`, diff --git a/frontend/src/pages/resource_details_page.tsx b/frontend/src/pages/resource_details_page.tsx index bf8f13d..b5a8504 100644 --- a/frontend/src/pages/resource_details_page.tsx +++ b/frontend/src/pages/resource_details_page.tsx @@ -395,7 +395,7 @@ function DeleteResourceDialog({ ); } -const context = createContext<() => void>(() => { }); +const context = createContext<() => void>(() => {}); function Article({ resource }: { resource: ResourceDetails }) { return ( @@ -503,10 +503,7 @@ function Article({ resource }: { resource: ResourceDetails }) { const href = props.href as string; const origin = window.location.origin; - if ( - href.startsWith(origin) || - href.startsWith("/") - ) { + if (href.startsWith(origin) || href.startsWith("/")) { let path = href; if (path.startsWith(origin)) { path = path.substring(origin.length); @@ -532,7 +529,13 @@ function Article({ resource }: { resource: ResourceDetails }) { ); } -function RelatedResourceCard({ r, content }: { r: Resource, content?: string }) { +function RelatedResourceCard({ + r, + content, +}: { + r: Resource; + content?: string; +}) { const navigate = useNavigate(); const [articleWidth, setArticleWidth] = useState(null); @@ -549,10 +552,9 @@ function RelatedResourceCard({ r, content }: { r: Resource, content?: string }) if (articleElement) { observer.observe(articleElement); } - }, []) + }, []); - const imgHeight = - r.image && r.image.width > r.image.height ? 320 : 420; + const imgHeight = r.image && r.image.width > r.image.height ? 320 : 420; let imgWidth = r.image ? (r.image.width / r.image.height) * imgHeight : undefined; @@ -561,7 +563,7 @@ function RelatedResourceCard({ r, content }: { r: Resource, content?: string }) } if (!articleWidth) { - return <> + return <>; } return ( diff --git a/server/dao/activity.go b/server/dao/activity.go index 8ce5598..5b992c5 100644 --- a/server/dao/activity.go +++ b/server/dao/activity.go @@ -29,6 +29,15 @@ func AddNewCommentActivity(userID, commentID uint) error { return db.Create(activity).Error } +func AddNewFileActivity(userID, fileID uint) error { + activity := &model.Activity{ + UserID: userID, + Type: model.ActivityTypeNewFile, + RefID: fileID, + } + return db.Create(activity).Error +} + func DeleteResourceActivity(resourceID uint) error { return db.Where("ref_id = ? AND (type = ? OR type = ?)", resourceID, model.ActivityTypeNewResource, model.ActivityTypeUpdateResource).Delete(&model.Activity{}).Error } diff --git a/server/dao/file.go b/server/dao/file.go index bafb4bb..6b59cbb 100644 --- a/server/dao/file.go +++ b/server/dao/file.go @@ -106,6 +106,8 @@ func CreateFile(filename string, description string, resourceID uint, storageID return nil, err } + _ = AddNewFileActivity(userID, f.ID) + return f, nil } @@ -120,6 +122,17 @@ func GetFile(id string) (*model.File, error) { return f, nil } +func GetFileByID(id uint) (*model.File, error) { + f := &model.File{} + if err := db.Preload("Storage").Where("id = ?", id).First(f).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, model.NewNotFoundError("file not found") + } + return nil, err + } + return f, nil +} + func DeleteFile(id string) error { f := &model.File{} if err := db.Where("uuid = ?", id).First(f).Error; err != nil { @@ -130,8 +143,21 @@ func DeleteFile(id string) error { if err := tx.Delete(f).Error; err != nil { return err } - return tx.Model(&model.User{}).Where("id = ?", f.UserID). - UpdateColumn("files_count", gorm.Expr("files_count - ?", 1)).Error + if err := tx. + Model(&model.User{}). + Where("id = ?", f.UserID). + UpdateColumn("files_count", gorm.Expr("files_count - ?", 1)). + Error; err != nil { + return err + } + if err := tx. + Model(&model.Activity{}). + Where("type = ? AND ref_id = ?", model.ActivityTypeNewFile, f.ID). + Delete(&model.Activity{}). + Error; err != nil { + return err + } + return nil }); err != nil { return err } diff --git a/server/dao/resource.go b/server/dao/resource.go index 3e38c85..92de4bd 100644 --- a/server/dao/resource.go +++ b/server/dao/resource.go @@ -122,6 +122,9 @@ func DeleteResource(id uint) error { } return err } + if err := tx.Unscoped().Model(&model.File{}).Where("resource_id = ?", id).Delete(&model.File{}).Error; err != nil { + return err + } if err := tx.Model(&model.User{}).Where("id = ?", r.UserID).Update("resources_count", gorm.Expr("resources_count - ?", 1)).Error; err != nil { return err } @@ -540,7 +543,10 @@ func RandomResource() (model.Resource, error) { return model.Resource{}, err } for { - randomID := uint(1 + rand.Int63n(maxID-1)) + randomID := uint(1) + if maxID > 1 { + randomID = uint(1 + rand.Int63n(maxID-1)) + } var resource model.Resource if err := db. Preload("User"). diff --git a/server/model/activity.go b/server/model/activity.go index f72ed45..43294e4 100644 --- a/server/model/activity.go +++ b/server/model/activity.go @@ -13,6 +13,7 @@ const ( ActivityTypeNewResource ActivityTypeUpdateResource ActivityTypeNewComment + ActivityTypeNewFile ) type Activity struct { @@ -29,4 +30,5 @@ type ActivityView struct { User UserView `json:"user"` Comment *CommentView `json:"comment,omitempty"` Resource *ResourceView `json:"resource,omitempty"` + File *FileView `json:"file,omitempty"` } diff --git a/server/service/activity.go b/server/service/activity.go index 22c6a15..064a4e3 100644 --- a/server/service/activity.go +++ b/server/service/activity.go @@ -22,6 +22,7 @@ func GetActivityList(page int) ([]model.ActivityView, int, error) { } var comment *model.CommentView var resource *model.ResourceView + var file *model.FileView switch activity.Type { case model.ActivityTypeNewComment: c, err := dao.GetCommentByID(activity.RefID) @@ -37,6 +38,19 @@ func GetActivityList(page int) ([]model.ActivityView, int, error) { } rv := r.ToView() resource = &rv + case model.ActivityTypeNewFile: + f, err := dao.GetFileByID(activity.RefID) + if err != nil { + return nil, 0, err + } + fv := f.ToView() + file = fv + r, err := dao.GetResourceByID(f.ResourceID) + if err != nil { + return nil, 0, err + } + rv := r.ToView() + resource = &rv } view := model.ActivityView{ ID: activity.ID, @@ -45,6 +59,7 @@ func GetActivityList(page int) ([]model.ActivityView, int, error) { Time: activity.CreatedAt, Comment: comment, Resource: resource, + File: file, } views = append(views, view) } diff --git a/server/service/resource.go b/server/service/resource.go index cfbec15..a84980f 100644 --- a/server/service/resource.go +++ b/server/service/resource.go @@ -176,6 +176,13 @@ func DeleteResource(uid, id uint) error { return model.NewUnAuthorizedError("You have not permission to delete this resource") } } + r, err := GetResource(id, "") + if err != nil { + return err + } + if len(r.Files) > 0 { + return model.NewRequestError("This resource has files, please delete them first") + } if err := dao.DeleteResource(id); err != nil { return err }